UNPKG

@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
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