Maintain the same average value while respecting ratios - math

I have such a problem on hand. Imagine there are 5 sliders with a range of values 0 to 100. In the beginning, they are all set to 50, so the average score is 50.
Goal 1. Maintain average at 50
Say, if I move the first slider to 70, in order to maintain the average at 50, I decrease the value of the other 4 sliders to 45, i.e. 50 - (20/4) = 45.
Goal 2. Maintain ratios between individual values
The above example was easy, because all 4 affected values where equal. However, if I decide to move the 5th slider to 50, I want all the other sliders to adjust so that the ratios between individual values (e.g. slider 1 / slider 2 is 70 / 45 -> 1,5555...) remain the same.
Here's the method I'm considering.
Step 1. Find the smallest value in the array of affected values (slider 1-4).
Step 2. Calculate ratios of each slider with the minimum-value-slider.
Step 3. This gives me a formula avg(ratio1*minV + ratio2*minV + ratio3*minV + *minV + newManualV) = 50
Step 4. Calculate minV and the remaining values using ratios.
So, in my example, it would be something like this:
newManualV = 50 (5th slider)
minV = 45 (any of the 2nd-4th sliders; let's say it's the 2nd)
ratio1 = 1.55556 (1st and minV)
ratio2 = 1 (3rd and minV)
ratio3 = 1 (3rd and minV)
(1.55556*minV + 1*minV + 1*minV + minV + 50) / 5 = 50
4.55556minV = 200
minV = 43.9
New (rounded) slider values are:
68 (43.9 * 1.55556)
44
44
44
50
Question. Is there a better way of doing this?

What is better? I think there is an easier way, as you do not need to explicitly calculate the ratio's. Just distribute the difference of the altered slider equally.
Denote the slide values with s1, ..., s5.
Suppose you change s1 with and amount d. Then, calculate s, the sum of the other sliders: s = s2+s3+s4+s5. Now, s2 -= (s2/s)*d.
The sum of s1 to s5 does not change (and so the average), and all other sliders are changed in proportion.

Five sliders equals 4 ratios to maintain. For example x_1/x_2. This is done only by multiplying all the slider by the same factor.
As soon as you set one value (for example slider #5) then all the others have to scale accordingly to maintain the ratios.
old_x_5 = x_5
x_5 = (new value)
scale = x_5/old_x_5
x_4 = x_4*scale
x_3 = x_3*scale
x_2 = x_2*scale
x_1 = x_1*scale
Now you have to re-scale everything in order to meet the average goal
average = (x_1+x_2+x_3+x_4+x_5)/5
if average>0
scale = 50/average
x_5 = x_5*scale
x_4 = x_4*scale
x_3 = x_3*scale
x_2 = x_2*scale
x_1 = x_1*scale
else
(now what?)
This changes the new x_5 to a value needed for the average goal instead of the user input. Also what happens if any of the sliders need to be over 100 to maintain the average.

Related

How to increase two values to match a ratio of 17:8 in Google Sheets

If I have two values with a calculated ratio for example:
Value 1 = 5000
Value 2 = 100
Calculated Ratio = 50:1
How do I distribute a value of 500 between value 1 and value 2 so I can get to a 17:8 ratio or as close as possible to 17:8 ratio without decreasing any of the values.
I tried adding all the values and then splitting them into 17:8 ratio but this will in some cases decrease one value to get to another.
Incorrect example as value one has decreased from its original value of 5000:
Value 1 = 3808
Value 2 = 1792
Calculated Ratio = 17:8
You have two equations and two unknowns. The two unknowns are the adjustment values a and b such as the ratio below is known (17/8)
aspect = (value1+a)/(value2+b)
but the combined value of the adjustments has to be a fixed amount (500)
sum = a + b
Soution 1
The solution if the aspect ratio is float type value 17/8=2.125, then the solution is
a = (aspect*(value2+sum) - value1)/(aspect+1)
b = (value1 - aspect*value2+sum)/(aspect+1)
In your case I get a = -1192 and b = 1692 for
value1 + a = 3808
value2 + b = 1792
The ratio 3808/1792 = 17/8 and the sum (1692) + (-1192) = 500
Solution 2
The solution if the aspect ratio is a rational number aspect = num/den is:
a = (num*(value2+sum) - den*value1)/(den + num)
b = (den*(value1+sum) - num*value2)/(den + num)
and again the sample calculation is (num=17, den=8)
a = (17*(100+500) - 8*5000)/(8 + 17) = -1192
b = (8*(5000+500) - 17*100)/(8 + 17) = +1692
Adjustments
If you constrain a>=0 and a<=sum as well as b>=0 and b<=sum then you would not reach the aspect ratio.
You can do this will the following code adjusting a and b
if (a<0)
{
a = 0;
b = sum;
}
else if(b<0)
{
a = sum;
b = 0;
}
Graph
Graphically this problem is a follows:
The blue line is the combination of Value 1 and Value 2 that have the aspect ratio desired.
The pink dot is the starting value (5000,100).
The slanted lines are the adjusted Value 1 and Value 2 for a given sum amount to adjust by. I have included lines for 500, 1000, 2000, and 4000.
Where the slanted lines intersect the blue line is the ideal solution. The solution
The red dot is where the above solution(s) lead you before adjustments. After adjusting for non-negative a and b you will end up at the black dot.
In google sheets, you need some extra columns to implement the above
This is not specific to sheets and is a basic math cross multiplication problem
So, 17 is to 8 as 500 is to x
17/8 = 500/x
Cross multiplying give us
8 * 500 = 4,000
17 * x = 17x
solving for x
x = 4,000/17
x = 235.29
This was a really fun problem thanks for sharing it. Solution
Spoiler alert! The integers are 5302 and 298 with a final ratio of 17.79 which can be found in row 303.
Edit 1:
I misunderstood the question. A 17:8 ratio can be simplified to 2.125:1. The spreadsheet lists all of the possible combinations, where the smallest ratio is 8.35:1. Thus it doesn't seem there is a solution close to a 17:8 ratio.

R radarchart: free axis to enhance records display?

I am trying to display my data using radarchart {fmsb}. The values of my records are highly variable. Therefore, low values are not visible on final plot.
Is there a was to "free" axis per each record, to visualize data independently of their scale?
Dummy example:
df<-data.frame(n = c(100, 0,0.3,60,0.3),
j = c(100,0, 0.001, 70,7),
v = c(100,0, 0.001, 79, 3),
z = c(100,0, 0.001, 80, 99))
n j v z
1 100.0 100.0 100.000 100.000 # max
2 0.0 0.0 0.000 0.000 # min
3 0.3 0.001 0.001 0.001 # small values -> no visible on final chart!!
4 60.0 0.001 79.000 80.000
5 0.3 0.0 3.000 99.000
Create radarchart
require(fmsb)
radarchart(df, axistype=0, pty=32, axislabcol="grey",# na.itp=FALSE,
seg = 5, centerzero = T)
Result: (only rows #2 and #3 are visible, row #1 with low values is not visible !!)
How to make visible all records (rows), i.e. how to "free" axis for any of my records? Thank you a lot,
If you want to be sure to see all 4 dimensions whatever the differences, you'll need a logarithmic scale.
As by design of the radar chart we cannot have negative values we are restricted on our choice of base by the range of values and by our number of segments (axis ticks).
If we want an integer base the minimum we can choose is:
seg0 <- 5 # your initial choice, could be changed
base <- ceiling(
max(apply(df[-c(1,2),],MARGIN = 1,max) / apply(df[-c(1,2),],MARGIN = 1,min))
^(1/(seg0-1))
)
Here we have a base 5.
Let's normalize and transform our data.
First we normalize the data by setting the maximum to 1 for all series,then we apply our logarithmic transformation, that will set the maximum of each series to seg0 (n for black, z for others) and the minimum among all series between 1 and 2 (here the v value of the black series).
df_normalized <- as.data.frame(df[-c(1,2),]/apply(df[-c(1,2),],MARGIN = 1,max))
df_transformed <- rbind(rep(seg0,4),rep(0,4),log(df_normalized,base) + seg0)
radarchart(df_transformed, axistype=0, pty=32, axislabcol="grey",# na.itp=FALSE,
seg = seg0, centerzero = T,maxmin=T)
If we look at the green series we see:
j and v have same order of magnitude
n is about 5^2 = 25 times smaller than j (5 i the value of the base, ^2 because 2 segments)
v is about 5^2 = 25 times (again) smaller than z
If we look at the black series we see that n is about 3.5^5 times bigger than the other dimensions.
If we look at the red series we see that the order of magnitude is the same among all dimensions.
Maybe a workaround for your problem:
If you would transform your data before running radarchart
(e.g. logarithm, square root ..) then you could also visualise small values.
Here an example using a cubic root transformation:
library(specmine)
df.c<-data.frame(cubic_root_transform(df)) # transform dataset
radarchart(df.c, axistype=0, pty=32, axislabcol="grey",# na.itp=FALSE,
seg = 5, centerzero = T)`
and the result will look like this:
EDIT:
If you want to zoom the small values even more you can do that with a higher order of the root.
e.g.
t<-5 # for fifth order root
df.t <- data.frame(apply(df, 2, function(x) FUN=x^(1/t))) # transform dataset
radarchart(df.t, axistype=0, pty=32, axislabcol="grey",# na.itp=FALSE,
seg = 5, centerzero = T)
You can adjust the "zoom" as you want by changing the value of t
So you should find a visualization that is suitable for you.
Here is an example using 10-th root transformation:
library(specmine)
df.c<-data.frame((df)^(1/10)) # transform dataset
radarchart(df.c, axistype=0, pty=32, axislabcol="grey",# na.itp=FALSE,
seg = 5, centerzero = T)`
and the result will look like this:
You can try n-th root for find the one that is best for you. N grows, the root of a number nearby zero grows faster.

Normalize number between 0 and 1 in user profile

I'm working on user-profile where each term exists in the user-profile have wight and the weight formulated from set of factors such as (duration, total number of visit ...etc) , I need to normalize the result of their summation to be number between 0 and 1, I performed this equation:
(x+y+z+......)/100
Where x, y and z are factors. I have suggested this equation to my self (I'm sorry I'm not very good in math :( ), but unfortunately it returns some value more than 1 , so is there any way that can be applied to limit the result of the summation between 0 and 1?
Many thanks in advance.
Ok, generally, to normalize, this is what you do:
Find the absolute minimum value, and subtract this from your number. (This may be 0, in which case you can skip this step.)
Find the absolute maximum value. Your total range after step 1 will be from 0..(maximum - minimum). Divide your number by this value, and everything will be in the range of 0..1.
To spin it back, you do the opposite: take your normalized number, multiply by the range (i.e. max - min), then add back the min.
The reason you're having a problem is because x + y + z + ... has a range that is not 100.
Example
If x has a range of 0-10, y has a range of 15-25 and z has a range of 10-25, and your specific values are x = 8, y = 17, z = 12:
x + y + z = 8 + 17 + 12 = 37
min = 0 + 15 + 10 = 25
max = 10 + 25 + 25 = 60
so your normalized value is calculated by doing:
(37 - 25) / (60 - 25) = (12 / 35) = 0.342857 (approximately).
To go back from normalized to a composite number, do the opposite:
0.342857 * 35 = 11.999995 = 12 once rounded.
12 + 25 = 37
If your variables are unbounded, nobody can reach the normalized value 1, because if someone achieved 1, another person with larger factors would exceed 1.
This said, you can transform every factor with a function that maps [0 +inf[ to [0 1[, like X/(X+a) or 1-2^(-X/a), where a is some scaling constant (chosen by you). You will apply this transform to the individual factors and average them, or just apply it to the global sum.

How to find 10 values, exponentially distributed, which sum to a value, x

I have a value, for example 2.8. I want to find 10 numbers which are on an exponential curve, which sum to this value.
That is, I want to end up with 10 numbers which sum to 2.8, and which, when plotted, look like the curve below (exponential decay). These 10 numbers should be equally spaced along the curve - that is, the 'x-step' between the values should be constant.
This value of 2.8 will be entered by the user, and therefore the way I calculate this needs to be some kind of algorithm that I can program (hence asking this on SO not Math.SE).
I have no idea where to start with this at all - any ideas?
You want to have 10 x values equally distributed, i.e. x_k = a + k * b. They shall fulfill sum(exp(-x_k)) = v with v being your target value (the 2.8). This means exp(-a) * sum(exp(-b)^k) = v.
Obviously, there is a solution for each choice of b if v is positive. Set b to an arbitrary value, and calculate a from it.
E.g. for v = 2.8 and b = 0.1, you get a = -log(v / sum(exp(-b)^k)) = -log(2.8/sum(0.90484^k)) = -log(2.8/6.6425) = -log(0.421526) = 0.86387.
So for this example, the x values would be 0.86387, 0.96387, ..., 1.76387 and the y values 0.421526, 0.381412, 0.345116, 0.312274, 0.282557, 0.255668, 0.231338, 0.209324, 0.189404, 0.171380.
Update:
As it has been clarified that the curve can be scaled arbitrarily and the xs are preferred to be 1, 2, 3 ... 9, this is much more simple.
Assuming the curve function is r*exp(-x), the 10 values would be r*exp(-1) ... r*exp(-9). Their sum is r*sum(exp(-x)) = r*0.58190489. So to reach a certain value (2.8) you just have to adjust the r accordingly:
r = 2.8/sum(exp(-x)) = 4.81178294
And you get the 10 values: 1.770156, 0.651204, 0.239565, 0.088131, 0.032422, 0.011927, 0.004388, 0.001614, 0.000594.
If I understand your question correctly then you want to find x which solves the equation
It can be solved as
(just sum numbers as geometric progression)
The equation under RootOf will always have 1 real square different from 1 for 2.8 or any other positive number. You can solve it using some root-finding algorithm (1 is always a root but it does not solve original task). For constant a you can choose any number you like.
After computing the x you can easily calculate 10 numbers as .
I'm going to generalize and assume you want N numbers summing to V.
Since your numbers are equally spaced on an exponential you can write your sum as
a + a*x + a*x^2 + ... + a*x^(N-1) = V
Where the first point has value a, and the second a*x etc.
You can take out a factor of a and get:
a ( 1 + x + x^2 + ... + x^(N-1) ) = V
If we're free to pick x then we can solve for a easily
a = V / ( 1 + x + x^2 + .. x^(N-1) )
= V*(x+1)/(x^N-1)
Substituting that back into
a, a*x, a*x^2, ..., a*x^(N-1)
gives the required sequence

Convert arbitrary length to a value between -1.0 a 1.0?

How can I convert a length into a value in the range -1.0 to 1.0?
Example: my stage is 440px in length and accepts mouse events. I would like to click in the middle of the stage, and rather than an output of X = 220, I'd like it to be X = 0. Similarly, I'd like the real X = 0 to become X = -1.0 and the real X = 440 to become X = 1.0.
I don't have access to the stage, so i can't simply center-register it, which would make this process a lot easier. Also, it's not possible to dynamically change the actual size of my stage, so I'm looking for a formula that will translate the mouse's real X coordinate of the stage to evenly fit within a range from -1 to 1.
-1 + (2/440)*x
where x is the distance
So, to generalize it, if the minimum normalized value is a and the maximum normalized value is b (in your example a = -1.0, b = 1.0 and the maximum possible value is k (in your example k = 440):
a + x*(b-a)/k
where x is >= 0 and <= k
This is essentially two steps:
Center the range on 0, so for example a range from 400 to 800 moves so it's from -200 to 200. Do this by subtracting the center (average) of the min and max of the range
Divide by the absolute value of the range extremes to convert from a -n to n range to a -1 to 1 range. In the -200 to 200 example, you'd divide by 200
Doesn't answer your question, but for future googlers looking for a continuous monotone function that maps all real numbers to (-1, 1), any sigmoid curve will do, such as atan or a logistic curve:
f(x) = atan(x) / (pi/2)
f(x) = 2/(1+e-x) - 1
(x - 220) / 220 = new X
Is that what you're looking for?
You need to shift the origin and normalize the range. So the expression becomes
(XCoordinate - 220) / 220.0
handling arbitrary stage widths (no idea if you've got threads to consider, which might require mutexes or similar depending on your language?)
stageWidth = GetStageWidth(); // which may return 440 in your case
clickedX = MouseInput(); // should be 0 to 440
x = -1.0 + 2.0 * (clickedX / stageWidth); // scale to -1.0 to +1.0
you may also want to limit x to the range [-1,1] here?
if ( x < -1 ) x = -1.0;
if ( x > 1 ) x = 1.0;
or provide some kind of feedback/warning/error if its out of bounds (only if it really matters and simply clipping it to the range [-1,1] isn't good enough).
You have an interval [a,b] that you'd like to map to a new interval [c,d], and a value x in the original coordinates that you'd like to map to y in the new coordinates. Then:
y = c + (x-a)*(c-d)/(b-a)
And for your example with [a,b] = [0,440] and [c,d] = [-1,1], with x=220:
y = -1 + (220-0)*(1 - -1)/(440-0)
= 0
and so forth.
By the way, this works even if x is outside of [a,b]. So as long as you know any two values in both systems, you can convert any value in either direction.

Resources