I have the following scenario. I have a game in Unity where a player is provided with varying amount of targets (we'll say 125 as an example). The accuracy is multi-class in that there is Perfect(bullseye), Great, Good, Miss (where miss is the target is missed entirely, no points awarded). I'm trying to find the right way to calculate a correct accuracy percentage in this scenario. If the player hits every target (125) as Perfect, the accuracy would be 100%. If they hit 124 Perfect and 1 Great, while every target was hit the accuracy percentage would still drop (99.8%). What would be the correct way to calculate this? Balanced Accuracy? Weighted Accuracy? Precision?
I'd like to understand the underlying calculation, not just how to implement this in code.
I appreciate any help I can get with this.
This can be calculated by assigning each accuracy a score between 0 and 100 (percentage) and then calculating the average score or arithmetic mean for all the shots.
You could use an enum to define the scores for the different accuracies.
public enum Accuracy
{
Perfect = 100,
Great = 80,
Good = 50,
Miss = 0
}
Then to calculate the average you just need to sum all the accuracy scores together and divide the result by the total number of shots.
int sum = 0;
foreach(Accuracy shot in shotsTaken)
{
sum += (int)shot;
}
double average = (double)sum / shotsTaken.Count;
Calculating the average can be simplified using System.Linq.
public class Tally
{
private readonly List<Accuracy> shotsTaken = new List<Accuracy>();
public void RecordShot(Accuracy shot) => shotsTaken.Add(shot);
public string CalculateAverageAccuracy() => shotsTaken.Average(shot => (int)shot).ToString("0.#") + "%";
}
You can test the results using this code:
[MenuItem("Test/Tally")]
public static void Test()
{
var tally = new Tally();
for(int i = 0; i < 124; i++)
{
tally.RecordShot(Accuracy.Perfect);
}
tally.RecordShot(Accuracy.Great);
Debug.Log(tally.CalculateAverageAccuracy());
}
Result:
Related
This question already has answers here:
Generating a list of random numbers, summing to 1
(12 answers)
Closed 2 years ago.
I am making a space game with planets that are randomly generated and I need their atmospheres to be made up of random amounts of different elements. So the thing is that I want the elements to be percentages of the complete atmosphere, an example: oxygen = 30, nitrogen = 20, carbondioxide = 50, hydrogen = 0. All these values should be completely randomized and the sum of them all has to be 100.
I basically want to fill a container to the top with random amounts of set elements, but I don't know how to randomize all of the variables and end up with a fixed sum.
This is my first time submitting anything to StackOverflow so please let me know if there is anything I need to clarify, I've been stuck on this issue for so long without finding any answers so I would appreciate any help, thanks :)
(I am using c# in unity in case that makes a difference)
Well, you could think of it from the other perspective, and generate random values for each element, then use their total as 100%.
For instance...
var rnd = New Random()
ChecmicalsInAir = 9;
Hydrogen = rnd.Next();
Oxygen = rnd.Next();
...
TotalAtmos = (Hydrogen + Oxygen + ...);
Yes - as pointed out by canton7, below:
Dividing the chemical by TotalAtmos will give you the percentage of atmosphere.
Just to add a little more to this answer, you may also wish to use a Dictionary to store the information, rather than simple variables; that way you can include different gases in the atmosphere, as not all planets may have oxygen or nitrogen, this could add different bonuses or penalties, but remains a straightforward process for calculating percentages.
Questions like "how would you design this..." are better suited for Game Development. Those questions tend to get downvoted here a lot.
Here's one way you could do it.
int elementCount = 3; // set to the number of elements you have
List<float> elementPercents = new List<float>();
// Assign an initial value between 0 and 1 to each element, and set totalWeight
// to the sum of all those values. Note that this lets you have 0 for an element.
// If you don't want 0 for an element, adjust the Random.Range
float totalWeight = 0;
for (int i = 0; i < elementCount; i++)
{
elementPercents.Add(UnityEngine.Random.Range(0f, 1f));
totalWeight += elementPercents[i];
}
// Set the percent of each element to its proportion of the total weight.
// For example, if Oxygen = 0.1 and Hydrogen = 0.2 and Nitrogen = 0.3 from the above statement,
// then Oxygen's percent = 0.1 / (0.1 + 0.2 + 0.3) = 0.167
for (int i = 0; i < elementCount; i++)
{
elementPercents[i] /= totalWeight;
}
// Assign the percents to each element according to your requirements, for example
float hydrogen = elementPercents[0];
float nitrogen = elementPercents[1];
float oxygen = elementPercents[2];
I've tried several combinations of Mathdotnet's LogNormal and Normal classes: https://numerics.mathdotnet.com/api/MathNet.Numerics.Distributions/LogNormal.
I seem to get a lot closer to the result I'm looking for using the mean and standard deviation as parameters. However, I notice that when I use larger numbers, like numberOfMinutes my results do not deviate past the mean like they do with smaller numbers like numberOfDays do. I know I'm not thinking about this right and could use some help.
Also, I'd like to use the geometric mean vs the mean but I didn't know what parameter to use for the variance given I couldn't pinpoint how to even use it for the mean.
Finally, I hope the answer to this also answers the same issue I'm having with the Normal distribution.
List<double> numberOfDays = new List<double> { 10, 12, 18, 30 };
double mean = numberOfDays.Mean(); // 17.5
double geometricMean = numberOfDays.GeometricMean(); // 15.954
double variance = numberOfDays.Variance(); // 81
double standardDeviation = numberOfDays.StandardDeviation(); // 9
// Do I need a Geometric Standard Deviation or Variance
double numberOfDaysSampleMV = LogNormal.WithMeanVariance(mean, variance).Sample(); // One example sample yielded 40.23
double numberOfDaysSampleMSD = LogNormal.WithMeanVariance(mean, standardDeviation).Sample(); // One example sample yielded 17.33
I believe you are confused about the parameters required. Using conventional notation, you have set X which you believe is LogNormal:
X = { 10, 12, 18, 30 }
mean: m = 17.5
standard deviation: sd = 9
from this you derive set Y which is Normal:
Y = {2.30,2.48,2.89,3.4}
mean: mu = 2.77
standard deviation: sigma = 0.487
Note that mu and sigma are computed from Y, not X. To create sample of the LogNormal data, you use mu and sigma, not m and sd.
double[] sample = new double[100];
LogNormal.Samples(sample, mu, sigma);
This is consistent with the Wikipedia article on the LogNormal distribution. The Numerics documentation is not clear.
Here is my test program which might be useful:
List<double> X = new List<double> { 10, 12, 18, 30 }; // assume to be LogNormal
double m = X.Mean(); // mean of log normal values = 17.5
double sd = X.StandardDeviation(); // standard deviation of log normal values = 9
List<double> Y = new List<double> { };
for (int i = 0; i < 4; i++)
{
Y.Add(Math.Log(X[i]));
}
// Y = {2.30,2.48,2.89,3.4}
double mu = Y.Mean(); // mean of normal values = 2.77
double sigma = Y.StandardDeviation(); // standard deviation of normal values = 0.487
double[] sample = new double[100];
LogNormal.Samples(sample, mu, sigma); // get sample
double sample_m = sample.Mean(); // 17.93, approximates m
double sample_sd = sample.StandardDeviation(); // 8.98, approximates sd
sample = new double[100];
Normal.Samples(sample, mu, sigma); // get sample
double sample_mu = sample.Mean(); //2.77, approximates mu
double sample_sigma = sample.StandardDeviation(); //0.517 approximates sigma
Using your test program above my samples came out like this.
Using LogNormal(mu, sigma)
I'm ultimately concerned about the values greater than 30 and less than 10.
However, by trail and error [accidentally], when I use the following method to get the samples using the original m and sd variable in your test program I get the results I'm looking for. I do not want to go forward with something I accidentally did.
sample = new double[100];
for (int i = 0; i < 100; i++)
{
sample[i] = LogNormal.WithMeanVariance(m, sd).Sample();
}
Using LogNormal.WithMeanVariance(m, sd)
My values are consistently between the Min and Max and concentrated around the Mean.
My example shows pretty clearly how to get a LogNormal sample that has the mean and standard deviation of the original data.
The min/max of 10/30 is unrealistic if you are going create your samples based on the mean and standard deviation of the sample. Suppose you took of random sample of the weights of 4 people out of a population of 1000 people. Would you expect your sample to include both the lightest and heaviest of the population?
LogNormal.WithMeanVariance(m, sd) is wrong. The units are wrong. It's expecting a variance would have the units of ln(days)^2 while sd has units of days.
I suggest you a) use LogNormal(mu,sigma) and discard any values that are outside your min/max range or b) use LogNormal(mu,c*sigma) for some value of c less than one to reduce the variance enough that all the values are in your min/max range. The choice depends on the nature of your project.
The Wikipedia entry on the LogNormal distribution has formulas for computing mu and sigma from m and sd which might be better than calculating from the Y data.
I am making a program in C# that creates a list of tree objects that have graphics that display them all in a grid like fashion. After creating them I will have it to where each individual tree is moveed a random distance between -10 and 10 over the x and y axis; this will hopefully produce a look of scattered trees. I'm still new to C# basically.
My main problems are:
I'm not sure if it's possible to generate a number between -10 and 10 with my method
int randX = RandomClass.Next(-10, 10);
When I "skew" (skew is a loose term that I used to mean "move" in this context, its nothing literal) each tree's position, it seems as though I am applying the save amount of vertical and horizontal movement to many trees and not to one single tree individually.
public void SkewTrees()
{
if (skewed == false)
{
Vector2 emptyVector = Vector2.Zero;
int randX = RandomClass.Next(0, 100);
int randY = RandomClass.Next(0, 100);
Vector2 randSkew = new Vector2(randX , randY);
position=new Vector2(position.X+randSkew.X, position.Y+randSkew.Y);
skewed = true;
}
}
Full Code with link to actual output:
http://pastebin.com/zZ246t7U
For the random between -10 to 10, try something this way :
int rand = RandomClass.Next(1, 10) - RandomClass.Next(1, 10);
Or
int rand = RandomClass.Next(0, 20) - 10;
Or
int rand = RandomClass.Next(-10, 10);
Works too.
The Random-class produces a deterministic and reproducible sequence of numbers. It takes its seed from Environment.TickCount which changes every 15ms approximately. Therefore every instance of Random that you construct within such a 15ms time interval will produce exactly the same numbers.
In order to fix your code you need to ensure that you either use a good seed value or reuse a single Random class.
I recommend that you create a static variable with a single Random instance. Your entire program can reuse this variable.
I'm currently implementing a software that measures certain values over time. The user may choose to measure the value 100 times over a duration of 28 days. (Just to give an example)
Linear distribution is not a problem, but I am currently trying to get a logarithmical distribution of the points over the time span.
The straight-forward implementation would be to iterate over the points and thus I'll need an exponential function. (I've gotten this far!)
My current algorithm (C#) is as follows:
long tRelativeLocation = 0;
double tValue;
double tBase = PhaseTimeSpan.Ticks;
int tLastPointMinute = 0;
TimeSpan tSpan;
for (int i = 0; i < NumberOfPoints; i++)
{
tValue = Math.Log(i + 1, NumberOfPoints);
tValue = Math.Pow(tBase, tValue);
tRelativeLocation = (long)tValue;
tSpan = new TimeSpan(tRelativeLocation);
tCurrentPoint = new DefaultMeasuringPointTemplate(tRelativeLocation);
tPoints.Add(tCurrentPoint);
}
this gives me a rather "good" result for 28 days and 100 points.
The first 11 points are all at 0 seconds,
12th point at 1 sec,
20th at 50 sec,
50th at 390 min,
95th at 28605 mins
99 th at 37697 mins (which makes 43 hours to the last point)
My question is:
Does anybody out there have a good idea how to get the first 20-30 points further apart from each other, maybe getting the last 20-30 a bit closer together?
I understand that I will eventually have to add some algorithm that sets the first points apart by at least one minute or so, because I won't be able to get that kind of behaviour into a strictly mathematical algorithm.
Something like this:
if (((int)tSpan.TotalMinutes) <= tLastPointMinute)
{
tSpan = new TimeSpan((tLastPointMinute +1) * 600000000L);
tRelativeLocation = tSpan.Ticks;
tLastPointMinute = (int)tSpan.TotalMinutes;
}
However, I'd like to get a slightly better distribution overall.
Any cool ideas from you math cracks out there would be greatly appreciated!
From a practical point of view, the log function squeezes your time point near the origin already. A power function squeezes them even more. How about simple multiplication?
tValue = Math.Log(i + 1, NumberOfPoints);
tValue = tBase * tValue;
Another way to flatten the curve is start farther from the origin.
for (int i = 0; i < NumberOfPoints; i++)
{
tValue = Math.Log(i + 10, NumberOfPoints + 9);
The range of tvalue is still 0 to 1.
How about this to have a minimum space of 1 second at the beginning?
double nextTick = 0;
for (int i = 0; i < NumberOfPoints; i++)
{
tValue = Math.Log(i + 1, NumberOfPoints);
tValue = Math.Pow(tBase, tValue);
if (tValue < nextTick) tValue = nextTick;
nextTick++;
The distribution curve you choose depends on what you're measuring.
A straight line, a sine wave, a polynomial or exponential curve may individually be the best distribution curve for a given set of measurements.
Once you've decided on the distribution curve, you calculate the missing data points by calculating the y value for any given time value (x value), using the mathematical formula of the curve.
As an example, for a straight line, all you need is one data point and the slope of the line. Let's say at time 0 the measured value is 10, and the measurement goes up by 2 every minute. The formula would by y = 2 * x + 10. if we wanted to calculate the measurement when x = 5 (minutes), the formula gives us a measurement of 20.
For a logarithmic curve, you'd use a logarithm formula. For simplicity, let's say that the actual measurements give us a formula of y = 2 ** x + 12; You plug in the time values (x values) you want to calculate, and calculate the measurements (y values).
Realize that you are introducing calculation errors by calculating data points instead of measuring. You should mark the calculated data points in some manner to help the person reading your graph differentiate them from actual measurements.
I am not exactly sure what you are trying to do, your code does not seem to match your example (it could be that I am screwing up the arithmetic). If you want your samples to have a minimum separation of 1 sec, and each point at a location of x times the last point (except for the first) then you want to find x such that x^(n - 1) = span. This is just x = exp(log(span) / (n - 1)). Then your points would be at x^i for(i = 0; i < n; i++)
I'm working in an application (C#) that applies some readability formulas to a text, like Gunning-Fog, Precise SMOG, Flesh-Kincaid.
Now, I need to implement the Fry-based Grade formula in my program, I understand the formula's logic, pretty much you take 3 100-words samples and calculate the average on sentences per 100-words and syllables per 100-words, and then, you use a graph to plot the values.
Here is a more detailed explanation on how this formula works.
I already have the averages, but I have no idea on how can I tell my program to "go check the graph and plot the values and give me a level." I don't have to show the graph to the user, I only have to show him the level.
I was thinking that maybe I can have all the values in memory, divided into levels, for example:
Level 1: values whose sentence average are between 10.0 and 25+, and whose syllables average are between 108 and 132.
Level 2: values whose sentence average are between 7.7 and 10.0, and .... so on
But the problem is that so far, the only place in which I have found the values that define a level, are in the graph itself, and they aren't too much accurate, so if I apply the approach commented above, trying to take the values from the graph, my level estimations would be too much imprecise, thus, the Fry-based Grade will not be accurate.
So, maybe any of you knows about some place where I can find exact values for the different levels of the Fry-based Grade, or maybe any of you can help me think in a way to workaround this.
Thanks
Well, I'm not sure about this being the most efficient solution, neither the best one, but at least it does the job.
I gave up to the idea of having like a math formula to get the levels, maybe there is such a formula, but I couldn't find it.
So I took the Fry's graph, with all the levels, and I painted each level of a different color, them I loaded the image on my program using:
Bitmap image = new Bitmap(#"C:\FryGraph.png");
image.GetPixel(int x, int y);
As you can see, after loading the image I use the GetPixel method to get the color at the specified coordinates. I had to do some conversion, to get the equivalent pixels for a given value on the graph, since the scale of the graph is not the equivalent to the pixels of the image.
In the end, I compare the color returned by GetPixel to see which was the Fry readability level of the text.
I hope this may be of any help for someone who faces the same problem.
Cheers.
You simply need to determine the formula for the graph. That is, a formula that accepts the number of sentences and number of syllables, and returns the level.
If you can't find the formula, you can determine it yourself. Estimate the linear equation for each of the lines on the graph. Also estimate the 'out-of-bounds' areas in the 'long words' and 'long sentences' areas.
Now for each point, just determine the region in which it resides; which lines it is above and which lines it is below. This is fairly simple algebra, unfortunately this is the best link I can find to describe how to do that.
I have made a first pass at solving this that I thought I would share in case someone else is looking sometime in the future. I built on the answer above and created a generic list of linear equations that one can use to determine an approximate grade level. First had to correct the values to make it more linear. This does not take into account the invalid areas, but I may revisit that.
The equation class:
public class GradeLineEquation
{
// using form y = mx+b
// or y=Slope(x)=yIntercept
public int GradeLevel { get; set; }
public float Slope { get; set; }
public float yIntercept { get; set; }
public float GetYGivenX(float x)
{
float result = 0;
result = (Slope * x) + yIntercept;
return result;
}
public GradeLineEquation(int gradelevel,float slope,float yintercept)
{
this.GradeLevel = gradelevel;
this.Slope = slope;
this.yIntercept = yintercept;
}
}
Here is the FryCalculator:
public class FryCalculator
{
//this class normalizes the plot on the Fry readability graph the same way a person would, by choosing points on the graph based on values even though
//the y-axis is non-linear and neither axis starts at 0. Just picking a relative point on each axis to plot the intercept of the zero and infinite scope lines
private List<GradeLineEquation> linedefs = new List<GradeLineEquation>();
public FryCalculator()
{
LoadLevelEquations();
}
private void LoadLevelEquations()
{
// load the estimated linear equations for each line with the
// grade level, Slope, and y-intercept
linedefs.Add(new NLPTest.GradeLineEquation(1, (float)0.5, (float)22.5));
linedefs.Add(new NLPTest.GradeLineEquation(2, (float)0.5, (float)20.5));
linedefs.Add(new NLPTest.GradeLineEquation(3, (float)0.6, (float)17.4));
linedefs.Add(new NLPTest.GradeLineEquation(4, (float)0.6, (float)15.4));
linedefs.Add(new NLPTest.GradeLineEquation(5, (float)0.625, (float)13.125));
linedefs.Add(new NLPTest.GradeLineEquation(6, (float)0.833, (float)7.333));
linedefs.Add(new NLPTest.GradeLineEquation(7, (float)1.05, (float)-1.15));
linedefs.Add(new NLPTest.GradeLineEquation(8, (float)1.25, (float)-8.75));
linedefs.Add(new NLPTest.GradeLineEquation(9, (float)1.75, (float)-24.25));
linedefs.Add(new NLPTest.GradeLineEquation(10, (float)2, (float)-35));
linedefs.Add(new NLPTest.GradeLineEquation(11, (float)2, (float)-40));
linedefs.Add(new NLPTest.GradeLineEquation(12, (float)2.5, (float)-58.5));
linedefs.Add(new NLPTest.GradeLineEquation(13, (float)3.5, (float)-93));
linedefs.Add(new NLPTest.GradeLineEquation(14, (float)5.5, (float)-163));
}
public int GetGradeLevel(float avgSylls,float avgSentences)
{
// first normalize the values given to cartesion positions on the graph
float x = NormalizeX(avgSylls);
float y = NormalizeY(avgSentences);
// given x find the first grade level equation that produces a lower y at that x
return linedefs.Find(a => a.GetYGivenX(x) < y).GradeLevel;
}
private float NormalizeY(float avgSentenceCount)
{
float result = 0;
int lower = -1;
int upper = -1;
// load the list of y axis line intervalse
List<double> intervals = new List<double> {2.0, 2.5, 3.0, 3.3, 3.5, 3.6, 3.7, 3.8, 4.0, 4.2, 4.3, 4.5, 4.8, 5.0, 5.2, 5.6, 5.9, 6.3, 6.7, 7.1, 7.7, 8.3, 9.1, 10.0, 11.1, 12.5, 14.3, 16.7, 20.0, 25.0 };
// find the first line lower or equal to the number we have
lower = intervals.FindLastIndex(a => ((double)avgSentenceCount) >= a);
// if we are not over the top or on the line grab the next higher line value
if(lower > -1 && lower < intervals.Count-1 && ((float) intervals[lower] != avgSentenceCount))
upper = lower + 1;
// set the integer portion of the respons
result = (float)lower;
// if we have an upper limit calculate the percentage above the lower line (to two decimal places) and add it to the result
if(upper != -1)
result += (float)Math.Round((((avgSentenceCount - intervals[lower])/(intervals[upper] - intervals[lower]))),2);
return result;
}
private float NormalizeX(float avgSyllableCount)
{
// the x axis is MUCH simpler. Subtract 108 and divide by 2 to get the x position relative to a 0 origin.
float result = (avgSyllableCount - 108) / 2;
return result;
}
}