@visactor/vmind
Version:
<div align="center"> <a href="https://github.com/VisActor#gh-light-mode-only" target="_blank"> <img alt="VisActor Logo" width="200" src="https://github.com/VisActor/.github/blob/main/profile/logo_500_200_light.svg"/> </a> <a href="https://githu
71 lines (62 loc) • 3.88 kB
JavaScript
import euclideanDistance from "euclidean-distance";
import { isArray } from "@visactor/vutils";
import { InsightType } from "../../type";
import { ChartType } from "../../../../types";
import { isPercenSeries } from "../../utils";
const knn = (k, pIndex, distanceMap) => {
const distanceArr = distanceMap[pIndex];
return distanceMap.map(((data, index) => [ index, distanceArr[index] ])).filter((([index, distance]) => index !== pIndex)).sort((([index1, distance1], [index2, distance2]) => distance1 - distance2)).slice(0, k);
}, kd = (knnMap, pIndex) => {
const kNeighbors = knnMap[pIndex], [index, distance] = kNeighbors.reduce(((acc, dis) => acc[1] > dis[1] ? acc : dis));
return distance;
}, rd = (knnMap, distanceMap, pIndex, oIndex) => Math.max(kd(knnMap, oIndex), distanceMap[pIndex][oIndex]), sigmaRdCalc = (nearestArray, knnMap, distanceMap, pIndex) => nearestArray.map((([oIndex, distance]) => rd(knnMap, distanceMap, pIndex, oIndex))).reduce(((d1, d2) => d1 + d2)), lrd = (pIndex, knnMap, distanceMap) => {
const nearestArray = knnMap[pIndex], sigmaRd = sigmaRdCalc(nearestArray, knnMap, distanceMap, pIndex);
return sigmaRd - 0 <= Number.EPSILON ? 1 : nearestArray.length / sigmaRd;
}, onePointLOF = (dataIndex, knnMap, lrdArray) => {
const nearestArray = knnMap[dataIndex];
return nearestArray.map((([oIndex]) => lrdArray[oIndex] / lrdArray[dataIndex])).reduce(((d1, d2) => d1 + d2)) / nearestArray.length;
}, autoK = dataLength => dataLength > 10 ? 8 : dataLength > 4 ? 4 : 2;
export const LOF = (dataList, threshold = 3, propsK) => {
const k = propsK || ((dataLength = dataList.length) > 10 ? 8 : dataLength > 4 ? 4 : 2);
var dataLength;
if (k >= dataList.length) return [];
const distanceMap = new Array(dataList.length).fill(0).map((d => []));
for (let i = 0; i < dataList.length; i++) for (let j = i + 1; j < dataList.length; j++) {
const distance = euclideanDistance([ dataList[i] ], [ dataList[j] ]);
distanceMap[i][j] = distance, distanceMap[j][i] = distance;
}
const knnMap = dataList.map(((v, index) => knn(k, index, distanceMap))), lrdArr = dataList.map(((v, index) => lrd(index, knnMap, distanceMap)));
return dataList.map(((v, index) => onePointLOF(index, knnMap, lrdArr))).map(((score, index) => score >= threshold ? {
index: index,
score: score
} : void 0)).filter(Boolean);
};
const lofAlgoFunc = (context, options) => {
const result = [], {k: k, threshold: threshold = 3} = options || {}, {seriesDataMap: seriesDataMap, cell: cell, spec: spec} = context, {y: celly} = cell, yField = isArray(celly) ? celly.flat() : [ celly ];
return Object.keys(seriesDataMap).forEach((group => {
const dataset = seriesDataMap[group];
yField.forEach((field => {
if (isPercenSeries(spec, field)) return;
const dataList = dataset.map((d => Number(d.dataItem[field])));
LOF(dataList, threshold, k).forEach((insight => {
const {score: score, index: index} = insight, insightDataItem = dataset[index], lofInsight = {
type: InsightType.Outlier,
data: [ insightDataItem ],
fieldId: field,
value: insightDataItem.dataItem[field],
significant: score / threshold,
seriesName: group
};
result.push(lofInsight);
}));
}));
})), result;
};
export const LOFOutlier = {
name: "lof",
forceChartType: [ ChartType.DualAxisChart, ChartType.LineChart, ChartType.BarChart, ChartType.AreaChart, ChartType.RadarChart, ChartType.PieChart, ChartType.RoseChart, ChartType.WaterFallChart ],
insightType: InsightType.Outlier,
algorithmFunction: lofAlgoFunc,
supportPercent: !1
};
//# sourceMappingURL=lof.js.map