@ai-on-browser/data-analysis-models
Version:
Data analysis model package without any dependencies
82 lines (77 loc) • 1.88 kB
JavaScript
/**
* Ramer-Douglas-Peucker algorithm
*/
export default class RamerDouglasPeucker {
// https://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm
// https://siguniang.wordpress.com/2012/07/16/ramer-douglas-peucker-algorithm/
/**
* @param {number} [e] Threshold of distance
*/
constructor(e = 0.1) {
this._e = e
}
_d(l1, l2, p) {
return (
Math.abs((l2[1] - l1[1]) * p[0] - (l2[0] - l1[0]) * p[1] + l2[0] * l1[1] - l2[1] * l1[0]) /
Math.sqrt((l2[1] - l1[1]) ** 2 + (l2[0] - l1[0]) ** 2)
)
}
/**
* Fit model.
* @param {number[]} x Training data
* @param {number[]} y Target values
*/
fit(x, y) {
const n = x.length
const d = x.map((v, i) => [v, y[i]])
d.sort((a, b) => a[0] - b[0])
x = d.map(v => v[0])
y = d.map(v => v[1])
const seg = [[0, n - 1]]
while (true) {
let max_d = 0
let max_i = -1
let max_k = -1
for (let i = 0; i < seg.length; i++) {
const l1 = [x[seg[i][0]], y[seg[i][0]]]
const l2 = [x[seg[i][1]], y[seg[i][1]]]
for (let k = seg[i][0] + 1; k < seg[i][1]; k++) {
const d = this._d(l1, l2, [x[k], y[k]])
if (d > max_d) {
max_d = d
max_i = i
max_k = k
}
}
}
if (max_d === 0 || max_d < this._e) {
break
}
seg.splice(max_i, 1, [seg[max_i][0], max_k], [max_k, seg[max_i][1]])
}
this._x = x
this._y = y
this._seg = seg
}
/**
* Returns predicted values.
* @param {number[]} x Sample data
* @returns {number[]} Predicted values
*/
predict(x) {
const n = this._x.length
return x.map(v => {
if (v <= this._x[0]) {
return this._y[0]
} else if (v >= this._x[n - 1]) {
return this._y[n - 1]
}
for (const [s, e] of this._seg) {
if (v < this._x[e]) {
return this._y[s] + ((v - this._x[s]) * (this._y[e] - this._y[s])) / (this._x[e] - this._x[s])
}
}
return 0
})
}
}