Linear regression with two independent variables in javascript - javascript

The following will output the slope, intercept and correlation coefficient R^2 for a given set of x and y values.
let linearRegression = (y,x) => {
let lr = {}
let n = y.length
let sum_x = 0
let sum_y = 0
let sum_xy = 0
let sum_xx = 0
let sum_yy = 0
for (let i = 0; i < y.length; i++) {
sum_x += x[i]
sum_y += y[i]
sum_xy += (x[i]*y[i])
sum_xx += (x[i]*x[i])
sum_yy += (y[i]*y[i])
}
lr['slope'] = (n * sum_xy - sum_x * sum_y) / (n*sum_xx - sum_x * sum_x)
lr['intercept'] = (sum_y - lr.slope * sum_x)/n
lr['r2'] = Math.pow((n*sum_xy - sum_x*sum_y)/Math.sqrt((n*sum_xx-sum_x*sum_x)*(n*sum_yy-sum_y*sum_y)),2)
return lr
}
How can I adapt this to accept two independent variables x1, x2 rather than one?
This page goes into the modified formulas:
http://faculty.cas.usf.edu/mbrannick/regression/Reg2IV.html
But i've been struggling to adapt this to the above function.

Step-by-step
First, look at the input line:
let linearRegression = (y,x) => {. You have 2 variables, so we could call them x1 and x2: let linearRegression = (y,x1,x2) => {
Now the formulae for the regression depend on dot products between the pairs of variables - x1.x1, x1.x2, x1.y etc.
So instead of calculating sum_xx, sum_xy, sum_yy, we need to calculate the sums for all of those pairs (with 3 variables instead of 2, there are 6 sums to calculate).
Finally, the two-variable equation is y=a + b1.x1 + b2.x2, so there are 2 slopes to calculate rather than 1, and the page you linked to gives all the formulae you would need there.

Related

Upsampling a time series in Javascript

Let's assume a time series with n elements similar to this one:
[{value: 33209.203948532944, time: 1620178750},
{value: 33209.203948532944, time: 1620208647},
{value: 33610.0948868934, time: 1620219519},
{value: 34220.523450350825, time: 1620241262},
{value: 34220.523450350825, time: 1620242621}]
What is a simple algorithm in JavaScript to create another time series with m elements (m > n) which would have a similar visual when plotted on a chart?
The following function uses linear interpolation between each consecutive points by simply creating a segment between each 2 consecutive points and adding more points in-between as needed. The first and last point of the series are not modified.
const upSample = (series, threshold) => {
const l = series.length;
if (threshold <= l) { return series; }
const sampled = [];
// always add the first point
sampled.push(series[0]);
const lerp = (x, y, a) => x * (1 - a) + y * a;
for (var i = 1; i < threshold - 1; ++i) {
const delta = (i / (threshold - 2)) * (l - 2);
const left = Math.floor(delta);
const right = Math.min(Math.ceil(delta), l - 1);
const a = delta - left;
const value = lerp(series[left].value, series[right].value, a);
const time = Math.floor(lerp(series[left].time, series[right].time, a));
sampled.push({ value, time });
}
// always add last point
sampled.push(series[l - 1]);
return sampled;
};

How to get a random value from truncated normal distribution with mean and std in JavaScript?

What is a JS alternative to the same Python implementation?
import matplotlib.pyplot as plt
from scipy.stats import truncnorm
import numpy as np
mean = 1
std = 2
clip_a = -4
clip_b = 3
a, b = (clip_a - mean) / std, (clip_b - mean) / std
x_range = np.linspace(-3 * std, 3 * std, 1000)
plt.plot(x_range, truncnorm.pdf(x_range, a, b, loc = mean, scale = std));
I'd like to get a random value according to the distribution (in JS the same code with size=1):
dist = truncnorm.rvs(a, b, loc = mean, scale = std, size=1000000)
plt.hist(dist);
Here is a JS function that implements a truncated, skew-normal pseudo random number generator (PRNG). It is based on this blog post by Tom Liao and has been extended to consider lower and upper bounds (truncation).
Essentially, the function is called recursively until a variate within the desired bounds is found.
You can pass your own random number generator using the rng property, though Math.random will be used by default. Also, as you didn't ask for a skew-normal distribution, you can just ignore the skew property as it defaults to 0. This will leave you with a truncated normal PRNG, just as you asked for.
function randomTruncSkewNormal({
rng = Math.random,
range = [-Infinity, Infinity],
mean,
stdDev,
skew = 0
}) {
// Box-Muller transform
function randomNormals(rng) {
let u1 = 0,
u2 = 0;
//Convert [0,1) to (0,1)
while (u1 === 0) u1 = rng();
while (u2 === 0) u2 = rng();
const R = Math.sqrt(-2.0 * Math.log(u1));
const Θ = 2.0 * Math.PI * u2;
return [R * Math.cos(Θ), R * Math.sin(Θ)];
}
// Skew-normal transform
// If a variate is either below or above the desired range,
// we recursively call the randomSkewNormal function until
// a value within the desired range is drawn
function randomSkewNormal(rng, mean, stdDev, skew = 0) {
const [u0, v] = randomNormals(rng);
if (skew === 0) {
const value = mean + stdDev * u0;
if (value < range[0] || value > range[1])
return randomSkewNormal(rng, mean, stdDev, skew);
return value;
}
const sig = skew / Math.sqrt(1 + skew * skew);
const u1 = sig * u0 + Math.sqrt(1 - sig * sig) * v;
const z = u0 >= 0 ? u1 : -u1;
const value = mean + stdDev * z;
if (value < range[0] || value > range[1])
return randomSkewNormal(rng, mean, stdDev, skew);
return value;
}
return randomSkewNormal(rng, mean, stdDev, skew);
}
Calling this function in the following manner
const data = [];
for (let i = 0; i < 50000; i++) {
data.push({
x: i,
y: randomTruncSkewNormal({
range: [-4,3],
mean: 1,
stdDev: 2
})
});
}
and plotting the data using your charting library of choice should give your the desired output.
I also made a small Observable notebook interactively demonstrating the function which you might want to check out as well.

Finding numbers within an area of 10 in two arrays

I'm currently programming a Web game for my APCS end-of-year project. I'm trying to find the closest number in an array that is full of coords for towns. Since it is coords, I need to do this for an X and Y array. For example, x=[10, 20, 30], y=[20, 10, 23], the first town would be at [10, 20]. But since exactly finding this exact location would be hard, there is a area of 10 coords around it that you can be in, in order to discover the town.
Here's my current code:
function setTowns() {
for(i = 0; i < 9000; i++){
townLocations.x.push(random(-1000, 1000));
townLocations.y.push(random(-1000, 1000));
}
}
function checkTown() {
var townX = townLocations.x;
var townY = townLocations.y;
for(i = 0; i < townX.length; i++){
if((Math.abs(townX[i]) - Math.abs(bb.location.x)) < 10){
console.log(townX[i]);
for(i = 0; i < townY.length; i++){
if((Math.abs(townY[i]) - Math.abs(bb.location.y)) < 10){
console.log(townY[i]);
return true;
}
else {
return false;
}
}
}
}
}
checkTown() is called every time the player moves.
Imaging a map looking like this:
1 0 0 0 0 0
0 0 1 0 0 1
0 0 πŸšΆπŸ½β€β™€οΈ0 0 0
0 1 0 0 0 1
0 0 0 0 0 0
Ones are a town, and zeros contains nothing. πŸšΆπŸ½β€β™€οΈ marks the player.
You can use Pythagorean theorem to spot any kind of "collision". Let say the player is close to two towns (the ones) and each town has it's own coordinate (X, Y), as well as the player. Draw a triangle in your mind, where the Y coordinates for both places creates one side of the triangle, and the X coordinates are another side. The distance is the hypotenuse.
Let say that the distance for collision (read: detection) is 1. If the player is on coordinate (3, 3), would a town with coordinate (2, 4) be close enough? One side of the triangle would go from 3 to 2 and the other from 3 to 4. What's the hypotenuse, aka the distance?
OK, that's math for children, because you can probably see it straight away, but lets see the math behind it.
let playerXCoordinate = 3;
let playerYCoordinate = 3;
let townXCoordinate = 2;
let townYCoordinate = 4;
let xDifference = playerXCoordinate - townXCoordinate; // 3 - 2
let yDifference = playerYCoordinate - townYCoordinate; // 3 - 4
// Pythagorean theorem
let hypotenuseAsDistance = Math.sqr( Math.pow(xDifference, 2) + Math.pow(yDifference, 2))
// √(1^2 + 1^2) = √1 = 1
let isWithinDistance = hypotenuseAsDistance <= 1 // true
Lets take another example. This time with a town located at (5, 4).
let xDifference = 3 - 5;
let yDifference = 3 - 4
let hypotenuseAsDistance = Math.sqr( Math.pow(xDifference, 2) + Math.pow(yDifference, 2))
// √((3-5)^2 + (3-4)^2) = √(2^2+1^2) = √(4+1) = √5 = 2.23606797749979
let isWithinDistance = hypotenuseAsDistance <= 1 // false
A shorter version is, however, available as part of the Math object.
let xDifference = 3 - 5;
let yDifference = 3 - 4;
let hypotenuseAsDistance = Math.hypot(xDifference, yDifference);
So as for your example, it would be something like this.
let playerXPos = 3; // just an example
let playerXPos = 4; // just an example
let townArrLength = townLocations.x;
let detectionRange = 10;
let townXPos = 0;
let townYPos = 0;
let distance = 0;
for (let i = 0; i < townArrLength; i++) {
townXPos = townLocations.x[i];
townYPos = townLocations.y[i];
distance = Math.hypot(playerXPos - townXPos, playerYPos - townYPos);
if (distance <= detectionRange) {
// do something
}
}
For more reading:
https://developer.mozilla.org/en-US/docs/Games/Techniques/2D_collision_detection

Get reverse of an equation - JavaScript

Let's say I have this formula, for example:
function getExperience(level) {
let a = 0;
for (let x = 1; x < level; x += 1) {
a += Math.floor(x + (200 * (2 ** (x / 3))));
}
return Math.floor(a / 4);
}
for (var i = 1; i < 100; i++) {
console.log(`Level ${i}: ${getExperience(i)}`);
}
To get the experience needed for level 50, you'd do: getExperience(50).
But, how would you reverse that and get the LEVEL needed for experience? So, getLevel(20010272) would output 50.
Short answer
You can use 4.328085 * Math.log(0.00519842 * xp + 1.259921045) as a very good approximation of the corresponding level.
If you need an exact value, you could iterate over all levels until you find the desired range, as in this answer.
Long answer
Slightly modified function
I don't think it's possible to find an exact, closed-form expression for the inverse of this function. It should be possible if you modify getExperience(level) a bit, though.
First, you can notice that x grows much slower than 2 ** (x / 3).
Then, Math.floor doesn't have much influence over large numbers.
So let's remove them! Here's the slightly modified function:
function getExperienceEstimate(level) {
let a = 0;
for (let x = 1; x < level; x += 1) {
a += 200 * (2 ** (x / 3));
}
return a / 4;
}
The advantage of this method is that it's now a geometric series, so it's possible to calculate the sum directly, without any loop:
function getExperienceEstimate(level) {
let a = 50;
let r = 2 ** (1 / 3);
return a * (r**level - r) / (r - 1);
};
getExperienceEstimate(50) returns 20011971.993575357, which is only 0.0015% smaller than getExperience(50).
Inverse function
According to Wolfram Alpha, here's the inverse function of getExperienceEstimate:
function getLevelEstimate(xp){
let a = 50;
let r = 2 ** (1 / 3);
return Math.log(xp * (r - 1) / a + r) / Math.log(r);
};
With some minor precision loss, you can simplify it further:
function getLevelEstimate(xp){
return 4.328085 * Math.log(0.00519842 * xp + 1.259921045)
};
It's only an estimate, but it works pretty well and doesn't require any loop!
Test
For 20012272 XP, the approximate inverse function returns 50.00006263463371, which should be a good starting point if you want to find the exact result.
function getExperience(level) {
let a = 0;
for (let x = 1; x < level; x += 1) {
a += Math.floor(x + (200 * (2 ** (x / 3))));
}
return Math.floor(a / 4);
}
function getLevelEstimate(xp){
return 4.328085 * Math.log(0.00519842 * xp + 1.259921045)
};
for (var i = 1; i < 100; i++) {
console.log(`Level ${i} (XP = ${getExperience(i)}). Estimated level : ${getLevelEstimate(getExperience(i))}`);
}
You can use a binary search algorithm to avoid to loop over all possibilities.
Here is an example that I have adapted to your case.
You first need to create an array to map all your level => experience, this action should be done only ONCE, then you never have to do it again.
As you can see in my example, even with 1000 levels, you never have to iterate more than 9 times whatever level you are trying to find.
// You first have to create an array with all your levels.
// This has to be done only ONCE because it's an expensive one!
const list = [];
for (let i = 1; i <= 1000; i++) {
list[i] = getExperience(i);
}
function getExperience(level) {
let a = 0;
for (let x = 1; x < level; x += 1) {
a += Math.floor(x + (200 * (2 ** (x / 3))));
}
return Math.floor(a / 4);
}
function getLevel(value) {
// initial values for start, middle and end
let start = 0
let stop = list.length - 1
let middle = Math.floor((start + stop) / 2)
let iterations = 0;
// While the middle is not what we're looking for and the list does not have a single item.
while (list[middle] !== value && start < stop) {
iterations++;
if (value < list[middle]) {
stop = middle - 1
} else {
start = middle + 1
}
// Recalculate middle on every iteration.
middle = Math.floor((start + stop) / 2)
}
console.log(`${value} is Level ${middle} (Result found after ${iterations} iterations)`);
return middle;
}
// Then you can search your level according to the experience
getLevel(0);
getLevel(72);
getLevel(20010272);
getLevel(getExperience(50));
getLevel(33578608987644589722);
A brute-force (but inelegant) solution would be to just call getExperience for levels until you reach a level that requires more experience than the passed exp:
function getLevel(exp) {
if (exp === 0) return 0;
let level = 0;
let calcExp = 0;
while (exp > calcExp) {
calcExp = getExperience(level);
if (calcExp > exp) break;
level++;
}
return level - 1;
}
console.log(getLevel(20012272)); // experience required for 50 on the dot
console.log(getLevel(20012270));
console.log(getLevel(20012274));
console.log(getLevel(0));
function getExperience(level) {
let a = 0;
for (let x = 1; x < level; x += 1) {
a += Math.floor(x + (200 * (2 ** (x / 3))));
}
return Math.floor(a / 4);
}
You can use binary search to locate level value faster - in 7 steps max.
(while I doubt that gain is significant for length 100 list)

d3.js How to simplify a complex path - using a custom algorithm

I've got a very basic example here. http://jsfiddle.net/jEfsh/57/ that creates a complex path - with lots of points. I've read up on an algorithm that may look over the points and create a simpler set of coordinates. Does anyone have any experience with this - examples on how to loop through the path data and pass it through the algorithm - find the shortest set of points to create a more rudimentary version of the shape?
http://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm
var points = "M241,59L237,60L233,60L228,61L224,61L218,63L213,63L209,65L204,66L199,67L196,68L193,69L189,70L187,72L184,72L182,74L179,75L177,76L175,78L173,79L170,81L168,83L165,85L163,87L161,89L159,92L157,95L157,97L155,102L153,105L152,110L151,113L151,117L151,123L150,137L148,180L148,185L148,189L148,193L148,197L148,202L148,206L149,212L151,218L152,222L154,229L154,232L155,235L157,239L158,241L160,245L162,247L163,249L165,251L167,254L169,256L172,258L175,260L178,261L183,265L188,268L193,270L206,273L213,275L220,275L225,275L232,276L238,277L243,277L249,277L253,277L259,277L266,277L271,277L277,277L281,277L284,277L288,277L293,277L297,276L302,274L305,272L308,271L311,268L313,267L315,264L318,261L322,257L324,254L326,249L329,244L331,241L332,239L334,234L338,230L339,226L341,222L343,218L345,213L347,211L348,207L349,201L351,196L352,192L353,187L353,183L353,180L353,178L354,176L354,173L354,170L354,168L354,167L354,166L354,164L354,162L354,161L354,159L354,158L354,155L354,152L354,149L352,143L349,137L347,133L343,125L340,119 M241,59L340,119";
d3.select("#g-1").append("path").attr("d", points);
//simplify the path
function DouglasPeucker(){
}
/*
//http://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm
function DouglasPeucker(PointList[], epsilon)
// Find the point with the maximum distance
dmax = 0
index = 0
end = length(PointList)
for i = 2 to ( end - 1) {
d = shortestDistanceToSegment(PointList[i], Line(PointList[1], PointList[end]))
if ( d > dmax ) {
index = i
dmax = d
}
}
// If max distance is greater than epsilon, recursively simplify
if ( dmax > epsilon ) {
// Recursive call
recResults1[] = DouglasPeucker(PointList[1...index], epsilon)
recResults2[] = DouglasPeucker(PointList[index...end], epsilon)
// Build the result list
ResultList[] = {recResults1[1...end-1] recResults2[1...end]}
} else {
ResultList[] = {PointList[1], PointList[end]}
}
// Return the result
return ResultList[]
end
*/
It's not clear what your problem is exactly. Do you have problems to turn the SVG data string into a list of points? You can use this:
function path_from_svg(svg) {
var pts = svg.split(/[ML]/);
var path = [];
console.log(pts.length);
for (var i = 1; i < pts.length; i++) {
path.push(pts[i].split(","));
}
return path;
}
It is a very simple approach: It splits the string on all move (M) and line (L) commands and treats them as lines. It then splits all substrings on the comma. The first "substring" is ignored, because it is the empty string before the first M. If there is a way to do this better in d3 I haven't found it.
The reverse operation is easier:
function svg_to_path(path) {
return "M" + path.join("L");
}
This is equivalent to svg.line.interpolate("linear").
You can then implement the Douglas-Peucker algorithm on this path data recursively:
function path_simplify_r(path, first, last, eps) {
if (first >= last - 1) return [path[first]];
var px = path[first][0];
var py = path[first][1];
var dx = path[last][0] - px;
var dy = path[last][1] - py;
var nn = Math.sqrt(dx*dx + dy*dy);
var nx = -dy / nn;
var ny = dx / nn;
var ii = first;
var max = -1;
for (var i = first + 1; i < last; i++) {
var p = path[i];
var qx = p[0] - px;
var qy = p[1] - py;
var d = Math.abs(qx * nx + qy * ny);
if (d > max) {
max = d;
ii = i;
}
}
if (max < eps) return [path[first]];
var p1 = path_simplify_r(path, first, ii, eps);
var p2 = path_simplify_r(path, ii, last, eps);
return p1.concat(p2);
}
function path_simplify(path, eps) {
var p = path_simplify_r(path, 0, path.length - 1, eps);
return p.concat([path[path.length - 1]]);
}
The distance to the line is not calculated in a separate function but directly with the formula for the distance of a point to a 2d line from the normal {nx, ny} on the line vector {dx, dy} between the first and last point. The normal is normalised, nx*nx + ny*ny == 1.
When creating the paths, only the first point is added, the last point path[last] is implied and must be added in path_simplify, which is a front end to the recursive function path_simplify_r. This approach was chosen so that concatenating the left and right subpaths does not create a duplicate point in the middle. (This could equally well and maybe cleaner be done by joining p1 and p2.slice(1).)
Here's everything put together in a fiddle: http://jsfiddle.net/Cbk9J/3/
Lots of good references in the comments to this question -- alas they are comments and not suggested answers which can be truly voted on.
http://bost.ocks.org/mike/simplify/
shows interactive use of this kind of thing which references Douglas-Peucker but also Visvalingam.

Categories