Scale a series between two points
It's straight-forward to create a small function to do this using basic arithmetic:
s = sort(rexp(100))
range01 <- function(x){(x-min(x))/(max(x)-min(x))}
range01(s)
[1] 0.000000000 0.003338782 0.007572326 0.012192201 0.016055006 0.017161145
[7] 0.019949532 0.023839810 0.024421602 0.027197168 0.029889484 0.033039408
[13] 0.033783376 0.038051265 0.045183382 0.049560233 0.056941611 0.057552543
[19] 0.062674982 0.066001242 0.066420884 0.067689067 0.069247825 0.069432174
[25] 0.070136067 0.076340460 0.078709590 0.080393512 0.085591881 0.087540132
[31] 0.090517295 0.091026499 0.091251213 0.099218526 0.103236344 0.105724733
[37] 0.107495340 0.113332392 0.116103438 0.124050331 0.125596034 0.126599323
[43] 0.127154661 0.133392300 0.134258532 0.138253452 0.141933433 0.146748798
[49] 0.147490227 0.149960293 0.153126478 0.154275371 0.167701855 0.170160948
[55] 0.180313542 0.181834891 0.182554291 0.189188137 0.193807559 0.195903010
[61] 0.208902645 0.211308713 0.232942314 0.236135220 0.251950116 0.260816843
[67] 0.284090255 0.284150541 0.288498370 0.295515143 0.299408623 0.301264703
[73] 0.306817872 0.307853369 0.324882091 0.353241217 0.366800517 0.389474449
[79] 0.398838576 0.404266315 0.408936260 0.409198619 0.415165553 0.433960390
[85] 0.440690262 0.458692639 0.464027428 0.474214070 0.517224262 0.538532221
[91] 0.544911543 0.559945121 0.585390414 0.647030109 0.694095422 0.708385079
[97] 0.736486707 0.787250428 0.870874773 1.000000000
How can I relatively scale something between two points?
Tweening in terms of position.
Keys and keyFrames
In animation we define known positions and states as key frames, normally we index the key frames in terms of time.
// an array of keys. The ? represents the applicable number value
var keys = [{
time : 0, // the state of an object at time 0
pos : {x : ? , y : ? }, // position
scale : ?,
rotation : ?,
colour : [?,?,?], // rgb colour, just for the hell of it
// and whatever else you may want to animate
},{
time : 100, // the state of the object at time 100
pos : {x : ? , y : ? },
scale : ?,
rotation : ?,
colour : [?,?,?],
// and whatever else you may want to animate
}
]
Normalised time
To get the state of an object at any time t between the key frames we find the normalised time ( a value from 0 to 1) between the times and multiply that to the difference between other states then add that to the beginning state.
So say the time is 50 first we get the normalised time
var currentTime = 50;
var timeDif = currentTime - keys[0].time; // difference from start time to current
// to get the normalised time divid by the differance
var normTime = timeDif / (keys[1].time - keys[0].time); // divide by the differance in time between keys
Now you have the normalised time you can easily calculate any of the states
var scaleDif = keys[1].scale - keys[0].scale; // get diff in scale
var scaleChange = scaleDif * normTime; // multiply by the normalised time
var currentScale = keys[0].scale + scaleChange; // add to the starting scale
That is all a little long winded but that is to ease you into what is happening. the complete keying function would look like.
function tweenKeys(time,key1,key2){
var nt = (time - key1.time) / (key2.time - key1.time); // get normalised time
// because you can not divide by zero we need a little check. Javascript return infinity if we div by zero but we want the value 0
nt = nt < Infinity ? nt : 0; // zero if there was a divide by zero
var ck = {}; // ck for current key. the key represents the state at time
ck.scale = key1.scale + (key2.scale - key1.scale) * nt;
ck.rotation = key1.rotation + (key2.rotation - key1.rotation ) * nt;
ck.pos.x = key1.pos.x + (key2.pos.x- key1.pos.x) * nt;
ck.pos.y = key1.pos.y + (key2.pos.y- key1.pos.y) * nt;
ck.colour[0] = key1.colour[0] + (key2.colour[0] - key1.colour[0]) * nt;
ck.colour[1] = key1.colour[1] + (key2.colour[1] - key1.colour[1]) * nt;
ck.colour[2] = key1.colour[2] + (key2.colour[2] - key1.colour[2]) * nt;
return ck; // return the newly create state
}
Thats the basics of keyframing and you can find more on it in this answer How would I animate... ?
In space rather than time
All good but for your problem this has not helped, you are not using time you are using position to determine the current state of the object. Well it does not matter what we use to find our current state, any of the values in the key frame can be used to determine that state of all the others. All we need to do is find the normalised difference and then apply that like we did normalised time to all the other values.
Normalised position
So lets look at position
Consider two points p1 and p2, defined as
var p1 = {x : ?, y : ?}; // ? represent some number value
var p2 = {x : ?, y : ?}; // ? represent some number value
And representing your positions A,B
If we have a 3rd point C
var c = {x : ?, y : ?}; // ? represent some number value
somewhere on the 2D plane. We want a formula that will return a 0 when C is at point p1 and 1 when the point c is at point p2. This will be our normalised position used to get the current state.
As position is 2d we need to involve both the x and y in the calculations. We get the distance from p1 to point c and the divide that by the distance between point p1 and p2. that will give us the value we want. To find the distance we use the pythag solution. root of the sum of the squares
var dist = Math.sqrt( Math.pow( p2.x - p1.x, 2) + Math.pow( p2.y - p1.y, 2)); // for the twisted world of IE users and
var dist = Math.hypot(p2.x - p1.x, p2.y - p1.y); // for all good browsers
So the normalised distance is
var normDist = Math.hypot(c.x - p1.x, c.y - p1.y) / Math.hypot(p2.x - p1.x, p2.y - p1.y);
// because you can not divide by zero we need a little check. Javascript returns infinity if we div by zero but we want the value 0
normDist = normDist < Infinity ? normDist : 0; // zero if there was a divide by zero
Then apply that (normDist) to all the key states.
var currentScale = (keys[1].scale - keys[0].scale) * normDist + keys[0].scale;
Problems with positioning
Ok you say thanks, sorry but that is not the solution, it would be if you knew that the point c is always on the line between p1, p2 but that is not always the case, and under a strict examination it is hardly ever because computers store digital information so there will be a little error in any calculation that requires very fine detail. Also the above method will return 1 for normalized distance for any point that is distance to p2 away from p1, that describes a circle around the point p1. We need to constrain this value a bit more. Also if c is befor the point p1 or after the point p2 it would be handy to know. Thus we can use the following to do so.
// get the unit distance on the line p1,p2 of point c representing
// the distance along the line that is closest to c
function unitDistOfPoint(p1,p2,c){
var v1 = {}; // working vectors
var v2 = {};
v1.x = p2.x - p1.x; // vector between p1,p2
v1.y = p2.y - p1.y;
v2.x = c.x - p1.x; // vector to c from p1
v2.y = c.y - p1.y;
// a little math magic. Divide the dot product of the vectors v2, v1
// by the square of line length
return (v2.x * v1.x + v2.y * v1.y) / (v1.y * v1.y + v1.x * v1.x);
}
Now we can do the tweening and get your scale
// return the state for a object at point c in terms of key1, to key2
function tweenKeysViaPos(c,key1,key2){
// get the normalised distance of the point c between keys 1 and 2
var nd = unitDistOfPoint(c, key1.pos, key2.pos); // nd for normalised distance
// you may want to constrain the position to only between the points
// do that by clamping the value nd between 0 and 1 inclusive
nd = Math.max(0, Math.min(1, nd)); // clamp the normalise distance
var ck = {}; // ck for current key. the key represents the state at time
ck.scale = key1.scale + (key2.scale - key1.scale) * nt;
ck.rotation = key1.rotation + (key2.rotation - key1.rotation ) * nt;
ck.pos.x = key1.pos.x + (key2.pos.x- key1.pos.x) * nt;
ck.pos.y = key1.pos.y + (key2.pos.y- key1.pos.y) * nt;
ck.colour[0] = key1.colour[0] + (key2.colour[0] - key1.colour[0]) * nt;
ck.colour[1] = key1.colour[1] + (key2.colour[1] - key1.colour[1]) * nt;
ck.colour[2] = key1.colour[2] + (key2.colour[2] - key1.colour[2]) * nt;
return ck; // return the newly create state
}
That is the answer. As a side benefit if the point c does stray away from the line between the keys then the above function also return the position it should be.
For more if needed
You may want to extend this to adapt to many key frames. Normally for more than two key frames and using time it is easy to find the keys that we want by finding where time is greater than the first key and less than the next key. But this is not as simple if you are using the position to work out at which key you are at. So to help a more complex solution you will find this function handy
// returns the distance point c is from the line p1,p2. If on the line
// the the return value is 0. If befor point p1 or after p2 then the distance
// is the distance to p1, or p2 respectively
function distFromLine(p1,p2,c){
var v1 = {}; // working vectors
var v2 = {};
v1.x = p2.x - p1.x; // vector between p1,p2
v1.y = p2.y - p1.y;
v2.x = c.x - p1.x; // vector to c from p1
v2.y = c.y - p1.y;
// a little math magic. Divide the dot product of the vectors v2, v1
// by the square of line length
var u = (v2.x * v1.x + v2.y * v1.y) / (v1.y * v1.y + v1.x * v1.x);
var v3 = {};
if(u < 0){ // befor the start
return Math.hypot(v2.x,v2.y); // distance to p1
}
if(u > 1){ // after end
return Math.hypot(c.x - p2.x,c.y p2.y); // distance to p2
}
// get the point on the line that is closest
v3.x = p1.x + v1.x * u;
v3.y = p1.y + v1.y * u;
// return the distance from that point to c
return Math.hypot(c.x - v3.x,c.y - v3.y); // distance from line of c
}
You can then find the two keys you need by finding the keys that return the smallest distance from the line between them. You and then define a complicated line by defining many key frames and where ever you put an object you can calculate where it should be and in what state.
Hope this helps and did not go over the top. If anything is unclear to anyone that reads please do say so in the comments and I will clarify.
How to scale down a range of numbers with a known min and max value
Let's say you want to scale a range [min,max]
to [a,b]
. You're looking for a (continuous) function that satisfies
f(min) = a
f(max) = b
In your case, a
would be 1 and b
would be 30, but let's start with something simpler and try to map [min,max]
into the range [0,1]
.
Putting min
into a function and getting out 0 could be accomplished with
f(x) = x - min ===> f(min) = min - min = 0
So that's almost what we want. But putting in max
would give us max - min
when we actually want 1. So we'll have to scale it:
x - min max - min
f(x) = --------- ===> f(min) = 0; f(max) = --------- = 1
max - min max - min
which is what we want. So we need to do a translation and a scaling. Now if instead we want to get arbitrary values of a
and b
, we need something a little more complicated:
(b-a)(x - min)
f(x) = -------------- + a
max - min
You can verify that putting in min
for x
now gives a
, and putting in max
gives b
.
You might also notice that (b-a)/(max-min)
is a scaling factor between the size of the new range and the size of the original range. So really we are first translating x
by -min
, scaling it to the correct factor, and then translating it back up to the new minimum value of a
.
How to scale int values in R between 0 and 100
With treshold I mean, that all values < 50 keep their value.
I got it now with:
employ.data$value <- replace(employ.data$value, employ.data$value > 50, 100)
And my result:
> employ.data
employee value startdate
1 John Doe 1 2010-11-01
2 Peter Gynn 3 2008-03-25
3 Jolie Hope 100 2007-03-14
Range standardization (0 to 1) in R
s = sort(rexp(100))
range01 <- function(x){(x-min(x))/(max(x)-min(x))}
range01(s)
[1] 0.000000000 0.003338782 0.007572326 0.012192201 0.016055006 0.017161145
[7] 0.019949532 0.023839810 0.024421602 0.027197168 0.029889484 0.033039408
[13] 0.033783376 0.038051265 0.045183382 0.049560233 0.056941611 0.057552543
[19] 0.062674982 0.066001242 0.066420884 0.067689067 0.069247825 0.069432174
[25] 0.070136067 0.076340460 0.078709590 0.080393512 0.085591881 0.087540132
[31] 0.090517295 0.091026499 0.091251213 0.099218526 0.103236344 0.105724733
[37] 0.107495340 0.113332392 0.116103438 0.124050331 0.125596034 0.126599323
[43] 0.127154661 0.133392300 0.134258532 0.138253452 0.141933433 0.146748798
[49] 0.147490227 0.149960293 0.153126478 0.154275371 0.167701855 0.170160948
[55] 0.180313542 0.181834891 0.182554291 0.189188137 0.193807559 0.195903010
[61] 0.208902645 0.211308713 0.232942314 0.236135220 0.251950116 0.260816843
[67] 0.284090255 0.284150541 0.288498370 0.295515143 0.299408623 0.301264703
[73] 0.306817872 0.307853369 0.324882091 0.353241217 0.366800517 0.389474449
[79] 0.398838576 0.404266315 0.408936260 0.409198619 0.415165553 0.433960390
[85] 0.440690262 0.458692639 0.464027428 0.474214070 0.517224262 0.538532221
[91] 0.544911543 0.559945121 0.585390414 0.647030109 0.694095422 0.708385079
[97] 0.736486707 0.787250428 0.870874773 1.000000000
Adding ...
will allow you to pass through na.rm = T
if you want to omit missing values from the calculation (they will still be present in the results):
range01 <- function(x, ...){(x - min(x, ...)) / (max(x, ...) - min(x, ...))}
Scale relative to a value in each group (via dplyr)
This solution is very similar to @thelatemail, but I think it's sufficiently different enough to merit its own answer because it chooses the index based on a condition:
data %>%
group_by(category) %>%
mutate(value = value/value[year == baseYear])
# category year value
#... ... ... ...
#7 A 2002 1.00000000
#8 B 2002 1.00000000
#9 C 2002 1.00000000
#10 A 2003 0.86462789
#11 B 2003 1.07217943
#12 C 2003 0.82209897
(Data output has been truncated. To replicate these results, set.seed(123)
when creating data
.)
Related Topics
Explain a Lazy Evaluation Quirk
Workflow For Statistical Analysis and Report Writing
How to Number/Label Data-Table by Group-Number from Group_By
Basic Lag in R Vector/Dataframe
How to Create an R Function Programmatically
How to Display the Frequency At the Top of Each Factor in a Barplot in R
Return Elements of List as Independent Objects in Global Environment
R Shiny - Add Tabpanel to Tabsetpanel Dynamically (With the Use of Renderui)
Adding a Regression Line on a Ggplot
Tools For Making Latex Tables in R
Multiple Plots in For Loop Ignoring Par
Count the Number of All Words in a String
What Is Meaning of First Tilde in Purrr::Map
Reshaping Time Series Data from Wide to Tall Format (For Plotting)
Dplyr Join on By=(A = B), Where a and B Are Variables Containing Strings