@ai-on-browser/data-analysis-models
Version:
Data analysis model package without any dependencies
129 lines (124 loc) • 5.23 kB
JavaScript
/**
* Percentile anomaly detection
*/
export default class PercentileAnormaly {
/**
* @param {number} percentile Percentile value
* @param {'data' | 'normal'} [distribution] Distribution name
*/
constructor(percentile, distribution = 'data') {
this._percentile = percentile
this._distribution = distribution
this._thresholds = []
}
/**
* Fit model.
* @param {Array<Array<number>>} data Training data
*/
fit(data) {
this._thresholds = []
const x = data
const n = x.length
if (n <= 0) {
return
}
const dim = x[0].length
if (n === 1) {
for (let d = 0; d < dim; d++) {
this._thresholds[d] = [x[0][d], x[0][d]]
}
return
}
if (this._percentile === 0) {
for (let d = 0; d < dim; d++) {
this._thresholds[d] = [-Infinity, Infinity]
}
return
} else if (this._percentile === 0.5) {
for (let d = 0; d < dim; d++) {
this._thresholds[d] = [0, 0]
}
return
}
const sortDatas = []
if (this._distribution === 'data') {
const lidx = (n - 1) * this._percentile
const li = [Math.floor(lidx), lidx - Math.floor(lidx), Math.ceil(lidx)]
const uidx = n - 1 - lidx
const ui = [Math.floor(uidx), uidx - Math.floor(uidx), Math.ceil(uidx)]
for (let d = 0; d < dim; d++) {
const sd = x.map(v => v[d])
sd.sort((a, b) => a - b)
sortDatas.push(sd)
this._thresholds[d] = [
sd[li[0]] + (sd[li[2]] - sd[li[0]]) * li[1],
sd[ui[0]] + (sd[ui[2]] - sd[ui[0]]) * ui[1],
]
}
} else if (this._distribution === 'normal') {
// https://www.geisya.or.jp/~mwm48961/statistics/stddev11.htm
// u from 0.00 to 3.09 step 0.01
const ifnorm = [
0.0, 0.004, 0.008, 0.012, 0.016, 0.0199, 0.0239, 0.0279, 0.0319, 0.0359, 0.0398, 0.0438, 0.0478, 0.0517,
0.0557, 0.0596, 0.0636, 0.0675, 0.0714, 0.0753, 0.0793, 0.0832, 0.0871, 0.091, 0.0948, 0.0987, 0.1026,
0.1064, 0.1103, 0.1141, 0.1179, 0.1217, 0.1255, 0.1293, 0.1331, 0.1368, 0.1406, 0.1443, 0.148, 0.1517,
0.1554, 0.1591, 0.1628, 0.1664, 0.17, 0.1736, 0.1772, 0.1808, 0.1844, 0.1879, 0.1915, 0.195, 0.1985,
0.2019, 0.2054, 0.2088, 0.2123, 0.2157, 0.219, 0.2224, 0.2257, 0.2291, 0.2324, 0.2357, 0.2389, 0.2422,
0.2454, 0.2486, 0.2517, 0.2549, 0.258, 0.2611, 0.2642, 0.2673, 0.2704, 0.2734, 0.2764, 0.2794, 0.2823,
0.2852, 0.2881, 0.291, 0.2939, 0.2967, 0.2995, 0.3023, 0.3051, 0.3078, 0.3106, 0.3133, 0.3159, 0.3186,
0.3212, 0.3238, 0.3264, 0.3289, 0.3315, 0.334, 0.3365, 0.3389, 0.3413, 0.3438, 0.3461, 0.3485, 0.3508,
0.3531, 0.3554, 0.3577, 0.3599, 0.3621, 0.3643, 0.3665, 0.3686, 0.3708, 0.3729, 0.3749, 0.377, 0.379,
0.381, 0.383, 0.3849, 0.3869, 0.3888, 0.3907, 0.3925, 0.3944, 0.3962, 0.398, 0.3997, 0.4015, 0.4032,
0.4049, 0.4066, 0.4082, 0.4099, 0.4115, 0.4131, 0.4147, 0.4162, 0.4177, 0.4192, 0.4207, 0.4222, 0.4236,
0.4251, 0.4265, 0.4279, 0.4292, 0.4306, 0.4319, 0.4332, 0.4345, 0.4357, 0.437, 0.4382, 0.4394, 0.4406,
0.4418, 0.4429, 0.4441, 0.4452, 0.4463, 0.4474, 0.4484, 0.4495, 0.4505, 0.4515, 0.4525, 0.4535, 0.4545,
0.4554, 0.4564, 0.4573, 0.4582, 0.4591, 0.4599, 0.4608, 0.4616, 0.4625, 0.4633, 0.4641, 0.4649, 0.4656,
0.4664, 0.4671, 0.4678, 0.4686, 0.4693, 0.4699, 0.4706, 0.4713, 0.4719, 0.4726, 0.4732, 0.4738, 0.4744,
0.475, 0.4756, 0.4761, 0.4767, 0.4772, 0.4778, 0.4783, 0.4788, 0.4793, 0.4798, 0.4803, 0.4808, 0.4812,
0.4817, 0.4821, 0.4826, 0.483, 0.4834, 0.4838, 0.4842, 0.4846, 0.485, 0.4854, 0.4857, 0.4861, 0.4864,
0.4868, 0.4871, 0.4875, 0.4878, 0.4881, 0.4884, 0.4887, 0.489, 0.4893, 0.4896, 0.4898, 0.4901, 0.4904,
0.4906, 0.4909, 0.4911, 0.4913, 0.4916, 0.4918, 0.492, 0.4922, 0.4925, 0.4927, 0.4929, 0.4931, 0.4932,
0.4934, 0.4936, 0.4938, 0.494, 0.4941, 0.4943, 0.4945, 0.4946, 0.4948, 0.4949, 0.4951, 0.4952, 0.4953,
0.4955, 0.4956, 0.4957, 0.4959, 0.496, 0.4961, 0.4962, 0.4963, 0.4964, 0.4965, 0.4966, 0.4967, 0.4968,
0.4969, 0.497, 0.4971, 0.4972, 0.4973, 0.4974, 0.4974, 0.4975, 0.4976, 0.4977, 0.4977, 0.4978, 0.4979,
0.4979, 0.498, 0.4981, 0.4981, 0.4982, 0.4982, 0.4983, 0.4984, 0.4984, 0.4985, 0.4985, 0.4986, 0.4986,
0.4987, 0.4987, 0.4987, 0.4988, 0.4988, 0.4989, 0.4989, 0.4989, 0.499, 0.499,
]
let u = 0.5
const p = 0.5 - this._percentile
for (let i = 0; i < ifnorm.length - 1; i++) {
if (ifnorm[i] <= p && p < ifnorm[i + 1]) {
u = i * 0.01
break
}
}
for (let d = 0; d < dim; d++) {
const mean = x.reduce((m, v) => m + v[d], 0) / n
const variance = x.reduce((s, v) => s + (v[d] - mean) ** 2, 0) / n
const std = Math.sqrt(variance)
this._thresholds[d] = [mean - std * u, mean + std * u]
}
}
}
/**
* Returns predicted anomaly flags.
* @param {Array<Array<number>>} x Sample data
* @returns {Array<boolean>} true if a data is anomaly.
*/
predict(x) {
if (this._percentile === 0) {
return Array(x.length).fill(false)
} else if (this._percentile === 0.5) {
return Array(x.length).fill(true)
}
const outliers = []
for (let i = 0; i < x.length; i++) {
let f = false
for (let d = 0; d < this._thresholds.length; d++) {
f ||= x[i][d] < this._thresholds[d][0] || this._thresholds[d][1] < x[i][d]
}
outliers.push(f)
}
return outliers
}
}