Core Concept: Statistical methods detect anomalies by measuring how far a value deviates from βnormalβ - either in standard deviations (Z-score) or quartile ranges (IQR).
Why It Matters: Statistical methods are computationally lightweight, require no training data, and can run on resource-constrained edge devices. They catch 80% of anomalies with 10% of the complexity of ML approaches.
Key Takeaway: Use Z-score for Gaussian data, IQR for skewed or bounded data, and adaptive thresholds when βnormalβ changes over time.
1356.2 Prerequisites
Before diving into this chapter, you should be familiar with:
Anomaly Types: Understanding the difference between point, contextual, and collective anomalies
Statistical approaches form the foundation of anomaly detection. They are computationally lightweight, suitable for edge deployment, and work well when data follows known distributions.
1356.4 Z-Score (Standard Deviation Method)
Core Concept: Measure how many standard deviations a data point is from the mean.
Formula:
Z = (x - mu) / sigma
Where:
- x = observed value
- mu = mean of dataset
- sigma = standard deviation
- |Z| > 3 typically indicates anomaly (99.7% confidence interval)
When It Works: - Data follows Gaussian (normal) distribution - You have sufficient historical data to calculate mu and sigma - Distribution is stable over time (no concept drift)
When It Fails: - Non-normal distributions (skewed, multi-modal) - Data with seasonal patterns - When βnormalβ range changes dynamically
{const container =document.getElementById('kc-anomaly-2');if (container &&typeof InlineKnowledgeCheck !=='undefined') { container.innerHTML=''; container.appendChild(InlineKnowledgeCheck.create({question:"A temperature sensor monitoring a refrigerated warehouse reports 4.2C. The 30-day historical mean is 3.8C with standard deviation 0.5C. Using a Z-score threshold of 3.0 for anomaly detection, what is the Z-score and is this reading flagged as an anomaly?",options: [ {text:"Z-score = 0.8, Not an anomaly (within normal range)",correct:true,feedback:"Correct! Z = (4.2 - 3.8) / 0.5 = 0.4 / 0.5 = 0.8. Since |0.8| < 3.0, this is within normal variation and not flagged as an anomaly. The refrigerator is operating normally."}, {text:"Z-score = 0.4, Not an anomaly (below threshold)",correct:false,feedback:"Incorrect calculation. Remember Z = (x - mu) / sigma = (4.2 - 3.8) / 0.5 = 0.8, not 0.4. However, your conclusion is correct - this is not an anomaly."}, {text:"Z-score = 4.0, Anomaly detected (exceeds threshold)",correct:false,feedback:"Incorrect. Z = (4.2 - 3.8) / 0.5 = 0.8, not 4.0. A Z-score of 4.0 would require a temperature of 3.8 + (4.0 x 0.5) = 5.8C."}, {text:"Z-score = 0.8, Anomaly detected (any deviation is concerning)",correct:false,feedback:"Incorrect conclusion. While Z = 0.8 is correct, a Z-score below the threshold of 3.0 indicates normal variation, not an anomaly. Small fluctuations are expected in any sensor system."} ],difficulty:"easy",topic:"anomaly-detection" })); }}
1356.5 Interquartile Range (IQR)
TipUnderstanding Outlier Detection
Core Concept: Outlier detection identifies data points that fall significantly outside the expected distribution of normal values - distinguishing genuine anomalies from sensor noise and measurement errors.
Why It Matters: Not all unusual values indicate real-world problems. A temperature spike from 22C to -40C is almost certainly a sensor malfunction, not actual weather. Conversely, a gradual drift from 22C to 28C over weeks might indicate genuine HVAC degradation. Proper outlier detection separates actionable anomalies from data quality issues, preventing both missed alerts and false alarms that erode operator trust.
Key Takeaway: Use IQR (Interquartile Range) when your data is skewed or has natural outliers - it is robust because it ignores extreme values. Use Z-score when data is normally distributed and you need speed (simpler calculation). For IoT sensors with physical constraints (humidity 0-100%, voltage 0-5V), combine statistical methods with hard bounds - any reading outside physical limits is immediately flagged as a sensor fault, not an anomaly.
Core Concept: Identify outliers based on the spread of the middle 50% of data.
Formula:
IQR = Q3 - Q1
Lower Bound = Q1 - 1.5 x IQR
Upper Bound = Q3 + 1.5 x IQR
Outlier if: x < Lower Bound OR x > Upper Bound
Where:
- Q1 = 25th percentile
- Q3 = 75th percentile
Advantages over Z-Score: - No distribution assumptions (works with skewed data) - Robust to extreme outliers - Good for bounded sensor ranges
IoT Use Cases: - Battery voltage monitoring (naturally bounded 0-4.2V) - Humidity sensors (0-100% RH) - Any sensor with physical constraints on range
Implementation:
import numpy as npdef iqr_anomaly_detection(data, value):""" Detect if a new value is anomalous using IQR method """ q1 = np.percentile(data, 25) q3 = np.percentile(data, 75) iqr = q3 - q1 lower_bound = q1 -1.5* iqr upper_bound = q3 +1.5* iqr is_anomaly = (value < lower_bound) or (value > upper_bound)return is_anomaly, lower_bound, upper_bound# Example: Battery voltage monitoring (normal: 3.3-4.1V)battery_readings = [3.85, 3.92, 3.78, 3.88, 3.95, 3.82, 3.90,3.87, 3.93, 3.81, 3.89, 3.86]# Check new readingnew_reading =2.1# Low battery or sensor faultanomaly, lower, upper = iqr_anomaly_detection(battery_readings, new_reading)print(f"Battery: {new_reading}V")print(f"Expected range: {lower:.2f}V - {upper:.2f}V")print(f"Anomaly: {anomaly}")# Output: Battery: 2.1V, Expected range: 3.57V - 4.16V, Anomaly: True
Show code
{const container =document.getElementById('kc-anomaly-3');if (container &&typeof InlineKnowledgeCheck !=='undefined') { container.innerHTML=''; container.appendChild(InlineKnowledgeCheck.create({question:"Your IoT deployment monitors battery voltage on solar-powered sensors. The data is heavily right-skewed (most readings cluster near 4.1V when fully charged, with occasional drops to 3.3V during cloudy periods). Which statistical method is more appropriate for anomaly detection?",options: [ {text:"Z-score method - it handles all types of data distributions effectively",correct:false,feedback:"Incorrect. Z-score assumes a Gaussian (normal) distribution. For right-skewed data, the mean and standard deviation are distorted by the asymmetric distribution, leading to inaccurate thresholds and missed anomalies."}, {text:"IQR method - it is robust to skewed distributions and outliers",correct:true,feedback:"Correct! IQR uses percentiles (Q1, Q3) which are not affected by extreme values or distribution shape. For skewed battery voltage data, IQR will correctly identify values outside the expected range regardless of the asymmetric distribution."}, {text:"Both methods work equally well for any distribution",correct:false,feedback:"Incorrect. The choice matters significantly. Z-score is optimal for Gaussian distributions; IQR is robust to any distribution including skewed, multi-modal, or heavy-tailed data common in IoT sensors."}, {text:"Neither method works - you need deep learning for skewed data",correct:false,feedback:"Incorrect. IQR handles skewed data very well. Deep learning is valuable for complex multivariate patterns, but simple statistical methods remain effective for univariate outlier detection when chosen appropriately."} ],difficulty:"medium",topic:"anomaly-detection" })); }}
1356.6 Moving Statistics (Adaptive Thresholds)
Core Concept: Calculate mean and standard deviation over a sliding window to adapt to changing conditions.
Why It Matters for IoT: - Sensor characteristics drift over time (aging, temperature effects) - Environmental conditions change (seasons, occupancy patterns) - Fixed thresholds become obsolete
Implementation:
from collections import dequeimport numpy as npclass AdaptiveThresholdDetector:def__init__(self, window_size=50, n_std=3.0):self.window = deque(maxlen=window_size)self.n_std = n_stddef update(self, value):""" Add new value and check if it's anomalous Returns: (is_anomaly, threshold_lower, threshold_upper) """# Calculate thresholds from current windowiflen(self.window) >10: mean = np.mean(self.window) std = np.std(self.window) lower = mean -self.n_std * std upper = mean +self.n_std * std is_anomaly = (value < lower) or (value > upper)else:# Not enough data yet is_anomaly =False lower, upper =None, None# Add to window AFTER checking (don't contaminate with anomalies)ifnot is_anomaly:self.window.append(value)return is_anomaly, lower, upper# Example: Indoor temperature monitoring with day/night cyclesdetector = AdaptiveThresholdDetector(window_size=50, n_std=2.5)# Simulate temperature readings over timeimport randomfor hour inrange(24):# Normal pattern: warmer during day (8am-6pm)if8<= hour <=18: base_temp =23.0else: base_temp =19.0# Add small random variation temp = base_temp + random.uniform(-0.5, 0.5) anomaly, lower, upper = detector.update(temp)if lower:print(f"Hour {hour}: {temp:.1f}C, "f"Range: [{lower:.1f}, {upper:.1f}], "f"Anomaly: {anomaly}")
Window Size Trade-Offs:
Window Size
Responsiveness
Stability
Best For
Small (10-50)
High - detects sudden changes
Low - sensitive to noise
Fast-changing environments
Medium (50-200)
Balanced
Balanced
General IoT monitoring
Large (200+)
Low - slow to adapt
High - ignores noise
Stable industrial processes
Show code
{const container =document.getElementById('kc-anomaly-4');if (container &&typeof InlineKnowledgeCheck !=='undefined') { container.innerHTML=''; container.appendChild(InlineKnowledgeCheck.create({question:"A smart building uses adaptive threshold detection with a 50-sample sliding window for HVAC temperature monitoring. During a sudden heatwave, outdoor temperatures rise 15C over 2 hours. What is the main risk of using adaptive thresholds in this scenario?",options: [ {text:"The system will trigger too many alerts as it detects the rapid temperature change",correct:false,feedback:"Incorrect. While this might happen briefly, the bigger risk is the opposite: the adaptive baseline will shift to incorporate the abnormal readings, eventually treating the heatwave conditions as 'normal'."}, {text:"The sliding window will 'chase' the rising temperatures, treating abnormal values as the new normal",correct:true,feedback:"Correct! This is concept drift in action. As the window fills with heatwave readings, the baseline shifts upward. The system may stop alerting on genuinely abnormal conditions because they have become the 'new normal' in its short-term memory. This is why combining adaptive methods with hard physical limits (e.g., never accept >35C) is critical."}, {text:"The window size is too small to detect any meaningful patterns",correct:false,feedback:"Incorrect. A 50-sample window is reasonable for many IoT applications. The issue is not window size but rather the fundamental limitation of adaptive methods during rapid environmental changes."}, {text:"Z-score calculations will fail due to division by zero",correct:false,feedback:"Incorrect. Z-score only fails if standard deviation is exactly zero, which requires all values to be identical - highly unlikely in real sensor data. The risk here is about baseline drift, not mathematical errors."} ],difficulty:"hard",topic:"anomaly-detection" })); }}
WarningTradeoff: Fixed Thresholds vs Adaptive Detection
Option A: Fixed statistical thresholds (Z-score > 3, IQR bounds) Option B: Adaptive thresholds with moving statistics Decision Factors: Fixed thresholds are simpler to implement and explain (auditable for compliance), work well when normal behavior is stable, and have lower computational overhead. Adaptive methods handle concept drift, seasonal patterns, and changing sensor characteristics, but require tuning window sizes and may temporarily miss anomalies during adaptation periods. Choose fixed for stable industrial processes; choose adaptive for environments with natural variation like building HVAC or outdoor sensors.
Experiment with different anomaly detection methods on simulated IoT sensor data. Adjust the threshold and detection method to see how they affect true positive and false positive rates.
functiongenerateSensorData(n, anomalyRate, seed) {const data = [];const random =mulberry32(seed + regenerate);// Normal sensor readings: mean=50, std=5 (e.g., temperature in Celsius)const normalMean =50;const normalStd =5;for (let i =0; i < n; i++) {const isActualAnomaly =random() < anomalyRate;let value;if (isActualAnomaly) {// Anomaly: either much higher or lowerconst direction =random() >0.5?1:-1;const magnitude =3+random() *4;// 3-7 std deviations value = normalMean + direction * magnitude * normalStd; } else {// Normal reading with Gaussian noise value = normalMean +gaussianRandom(random) * normalStd; } data.push({time: i,value: value,isActualAnomaly: isActualAnomaly }); }return data;}// Gaussian random using Box-MullerfunctiongaussianRandom(random) {let u =0, v =0;while (u ===0) u =random();while (v ===0) v =random();returnMath.sqrt(-2.0*Math.log(u)) *Math.cos(2.0*Math.PI* v);}// Seeded random number generatorfunctionmulberry32(seed) {returnfunction() {let t = seed +=0x6D2B79F5; t =Math.imul(t ^ t >>>15, t |1); t ^= t +Math.imul(t ^ t >>>7, t |61);return ((t ^ t >>>14) >>>0) /4294967296; }}// Generate the datasensorData =generateSensorData(200, anomalyRate,42)
Show code
functiondetectAnomalies(data, method, threshold, windowSize) {const results = [];if (method ==="z-score") {// Calculate global mean and stdconst values = data.map(d => d.value);const mean = values.reduce((a, b) => a + b,0) / values.length;const std =Math.sqrt(values.map(v => (v - mean) **2).reduce((a, b) => a + b,0) / values.length);for (let i =0; i < data.length; i++) {const zScore =Math.abs((data[i].value- mean) / std);const detected = zScore > threshold; results.push({...data[i],zScore: zScore,detected: detected,upperBound: mean + threshold * std,lowerBound: mean - threshold * std,movingAvg: mean }); } } elseif (method ==="moving-average") {// Moving average with adaptive boundsfor (let i =0; i < data.length; i++) {const start =Math.max(0, i - windowSize);const windowData = data.slice(start, i +1).map(d => d.value);const mean = windowData.reduce((a, b) => a + b,0) / windowData.length;const std = windowData.length>1?Math.sqrt(windowData.map(v => (v - mean) **2).reduce((a, b) => a + b,0) / windowData.length):5;// Default std if not enough dataconst zScore = std >0?Math.abs((data[i].value- mean) / std) :0;const detected = zScore > threshold && i >= windowSize; results.push({...data[i],zScore: zScore,detected: detected,upperBound: mean + threshold * std,lowerBound: mean - threshold * std,movingAvg: mean }); } } elseif (method ==="isolation-concept") {// Simplified Isolation Forest concept: detect based on distance from medianconst values = data.map(d => d.value).sort((a, b) => a - b);const median = values[Math.floor(values.length/2)];const mad = values.map(v =>Math.abs(v - median)).sort((a, b) => a - b)[Math.floor(values.length/2)];const modifiedZScore = mad >0?0.6745:1;for (let i =0; i < data.length; i++) {// Modified Z-score using median and MADconst score = mad >0?Math.abs((data[i].value- median) / (mad *1.4826)) :0;const detected = score > threshold; results.push({...data[i],zScore: score,detected: detected,upperBound: median + threshold * mad *1.4826,lowerBound: median - threshold * mad *1.4826,movingAvg: median }); } }return results;}detectionResults =detectAnomalies(sensorData, detectionMethod, threshold, windowSize)
Figure 1356.1: Comparison matrix showing computational characteristics and deployment recommendations for each statistical method. Z-score suits resource-constrained edge devices, moving average handles concept drift at gateways, and IQR combined with ensembles provides maximum robustness in cloud deployments.
1356.9 Summary
Statistical methods provide the foundation for anomaly detection in IoT:
Z-Score: Fast, simple, works for Gaussian data. Best for edge devices with minimal memory.
IQR: Robust to outliers and skewed data. Requires more memory for sorting.
Adaptive Thresholds: Handles concept drift. Requires tuning of window size.
Key Takeaway: Start with statistical methods. They catch 80% of anomalies with minimal resources. Only escalate to ML when statistical approaches fail.
1356.10 Whatβs Next
Continue learning about anomaly detection methods:
Time-Series Methods: ARIMA, exponential smoothing, and seasonal decomposition for temporal anomalies