Skip to content

Commit

Permalink
weighted linear regression
Browse files Browse the repository at this point in the history
  • Loading branch information
entorb committed Oct 9, 2023
1 parent f840b2e commit c6c4ea7
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 4 deletions.
47 changes: 46 additions & 1 deletion src/helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ function isNumeric(str) {
*/
function linreg(x, y) {
// from https://oliverjumpertz.com/simple-linear-regression-theory-math-and-implementation-in-javascript/
if (x.length !== y.length) {
throw new Error("Arrays x and y must have the same length.");
}
const sumX = x.reduce((prev, curr) => prev + curr, 0);
const avgX = sumX / x.length;
const xDifferencesToAverage = x.map((value) => avgX - value);
Expand All @@ -130,6 +133,47 @@ function linreg(x, y) {
return [slope, intercept];
}

/**
* Perform weighted linear regression calculation with normalized weights.
* @param {Array} x
* @param {Array} y
* @return {Array} of weighted slope, weighted intercept
*/
function linreg_weighted(x, y) {
if (x.length !== y.length) {
throw new Error("Arrays x and y must have the same length.");
}

const n = x.length;
let sumWeightedX = 0;
let sumWeightedY = 0;
let sumWeight = 0;

for (let i = 0; i < n; i++) {
const weight = i + 1; // Normalized weight based on position in the list.
sumWeight += weight;
sumWeightedX += x[i] * weight;
sumWeightedY += y[i] * weight;
}

const weightedAvgX = sumWeightedX / sumWeight;
const weightedAvgY = sumWeightedY / sumWeight;

let numerator = 0;
let denominator = 0;

for (let i = 0; i < n; i++) {
const weight = i + 1;
numerator += weight * (x[i] - weightedAvgX) * (y[i] - weightedAvgY);
denominator += weight * (x[i] - weightedAvgX) ** 2;
}

const weightedSlope = numerator / denominator;
const weightedIntercept = weightedAvgY - weightedSlope * weightedAvgX;

return [weightedSlope, weightedIntercept];
}

// data modification

/**
Expand Down Expand Up @@ -157,7 +201,7 @@ function calc_row_new_delta(row_new, row_last) {
if ("items_per_min" in row_new && row_new["items_per_min"] != 0) {
const ts_eta = Math.round(
row_new["timestamp"] +
(row_new["remaining"] / row_new["items_per_min"]) * 60000
(row_new["remaining"] / row_new["items_per_min"]) * 60000
);
row_new["eta_ts"] = ts_eta;
row_new["eta_str"] = timestamp_to_datestr(ts_eta);
Expand Down Expand Up @@ -247,6 +291,7 @@ module.exports = {
calc_remaining_items,
isNumeric,
linreg,
linreg_weighted,
calc_row_new_delta,
sort_data,
read_html_input_number,
Expand Down
8 changes: 5 additions & 3 deletions src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ enter remaining
prevent entering non-numbers
add delta1
html placeholders
weighted linreg instead, to put more weight on the latest items
TODO/IDEAS
chart: choose to display remaining instead of items
Expand Down Expand Up @@ -110,7 +111,8 @@ function update_total_eta_and_speed() {
// slope of remaining items
// is always negative
// slope is speed in items/ms
const slope = linreg(xArray, yArray)[0];
// const slope = linreg(xArray, yArray)[0];
const slope = linreg_weighted(xArray, yArray)[0];

total_timestamp_eta = Math.round(
last_row["timestamp"] - last_row["remaining"] / slope
Expand Down Expand Up @@ -210,8 +212,8 @@ function update_start_and_pct() {
percent =
Math.round(
10 *
100 *
(row_last["items"] / (row_first["items"] + row_first["remaining"]))
100 *
(row_last["items"] / (row_first["items"] + row_first["remaining"]))
) / 10;
}
html_text_pct.innerHTML = percent + "%";
Expand Down

0 comments on commit c6c4ea7

Please sign in to comment.