class DynamicClusterDeviation { constructor() { this.clusters = []; // Stores clusters as { center, spread, count } } update(value) { console.log(`\nProcessing value: ${value}`); // If no clusters exist, create the first one if (this.clusters.length === 0) { this.clusters.push({ center: value, spread: 0, count: 1 }); console.log(` → First cluster created at ${value}`); return { value, isOutlier: false }; } // Step 1: Find the closest cluster let bestMatch = null; let minDistance = Infinity; for (const cluster of this.clusters) { const distance = Math.abs(value - cluster.center); console.log(` Checking against cluster at ${cluster.center} (spread: ${cluster.spread}, count: ${cluster.count}) → distance: ${distance}`); if (distance < minDistance) { bestMatch = cluster; minDistance = distance; } } console.log(` Closest cluster found at ${bestMatch.center} with distance: ${minDistance}`); // Step 2: Compute dynamic threshold const dynamicThreshold = 1 + 5 / Math.sqrt(bestMatch.count + 1); const allowedDeviation = dynamicThreshold * (bestMatch.spread || 1); console.log(` Dynamic threshold: ${dynamicThreshold.toFixed(2)}, Allowed deviation: ${allowedDeviation.toFixed(2)}`); // Step 3: Check if value fits within the dynamically adjusted cluster spread if (minDistance <= allowedDeviation) { // Update cluster dynamically const newCenter = (bestMatch.center * bestMatch.count + value) / (bestMatch.count + 1); const newSpread = Math.max(bestMatch.spread, minDistance); bestMatch.center = newCenter; bestMatch.spread = newSpread; bestMatch.count += 1; console.log(` ✅ Value fits in cluster! Updating cluster:`); console.log(` → New center: ${newCenter.toFixed(2)}`); console.log(` → New spread: ${newSpread.toFixed(2)}`); console.log(` → New count: ${bestMatch.count}`); return { value, isOutlier: false }; } else { // If too far, create a new cluster this.clusters.push({ center: value, spread: 0, count: 1 }); console.log(` ❌ Outlier detected! New cluster created at ${value}`); return { value, isOutlier: true }; } } } // Rolling window simulation with outlier detection /* const detector = new DynamicClusterDeviation(); const dataStream = [10, 10.2, 10.5, 9.8, 11, 50, 10.3, 200, 201, 200.1, 205, 202, 250, 260, 270, 280, 290, 300]; // Define the number of elements per rolling window chunk. const windowSize = 5; let rollingWindow = []; dataStream.forEach((value, index) => { console.log(`\n=== Processing value ${index + 1} ===`); rollingWindow.push(value); const result = detector.update(value); console.log(`Current rolling window: [${rollingWindow.join(', ')}]`); console.log(`Result: value=${result.value} (${result.isOutlier ? 'Outlier' : 'Inlier'})`); // Once the window size is reached, show current cluster states and reset the window for the next chunk. if (rollingWindow.length === windowSize) { console.log("\n--- Rolling window chunk finished ---"); console.log("Detector cluster states:", JSON.stringify(detector.clusters, null, 2)); rollingWindow = []; } }); console.log("\nFinal detector cluster states:", JSON.stringify(detector.clusters, null, 2)); */