Chapters

6 Edge Data: Sampling and Statistical Compression

analytics-ml
edge
acq
sampling

6.1 Overview

This first route protects signal evidence through sampling and compares lossless, statistical, and transform reduction.

This is part 1 of 2. Continue with Edge Data: Semantic Compression and Pipelines for the second focused route.

6.2 Start With the Story

Keep the Shape Before Shrinking the Data

Picture a motor whose vibration changes faster than a slow sensor check can see. If the device measures too rarely, no later data trick can rebuild the missing motion.

Sampling means taking readings at chosen times. The sampling rate is how many readings are taken in one second. Start with the fastest change that matters. Measure often enough to show that change before you remove or combine any values.

Compression means using fewer bits to carry useful information. First decide what the receiver must still know. Exact records may need a lossless method, which can rebuild every bit. Trends may allow summaries. Alarms may allow the device to send only a named event plus enough context to check it.

Compare the smaller result with the original. Test normal work, sharp changes, noise, and a lost link. Record the method, settings, time window, and error.

Start with one short raw record. Mark the fastest useful change. Mark the slow trend. Mark known noise. Mark any gap. Choose the rate. Take the sample. Check the shape. Keep the time. Keep the unit. Keep the test state.

Now make a smaller copy. Count its bits. Check its shape. Check its peak. Check its mean. Check its alarm. Rebuild it when the method allows that. Measure the error. Repeat with a sharp event. Repeat with a quiet event. Repeat after a restart.

Test the full field path. Fill the local store. Remove the link. Keep taking samples. Restore the link. Send old data in order. Mark repeats. Mark lost blocks. Check the far copy. Check the power used. Check the time needed.

Write one clear rule for use. State which facts may change. State which facts must not change. State the largest allowed error. State who owns the setting. State what change forces a new test. Keep the raw test set so a later method can be judged on the same evidence.

The simple rule has limits. Less data saves power and link use, but it can also hide faults.

Use Practitioner to choose rates and reduction steps. Use Under the Hood for signal, transform, battery, and error limits.

Picture an IoT team using the ideas in Edge Sampling and Compression during a live operations review. A device has produced messy evidence, an analytic step is about to change an alert or control decision, and someone has to explain why the result should be trusted.

Read this page as that path from sensor evidence to accountable action. Start with what the system observes, keep the model or data treatment visible, and finish with the check that would convince an operator, maintainer, or auditor to act.

6.3 Learning Objectives

By the end of this chapter, you will be able to:

  • Apply Nyquist Theorem: Calculate appropriate sampling rates for different sensor types
  • Implement Data Reduction Techniques: Use aggregation, compression, event-based reporting, and delta encoding
  • Select Compression Algorithms: Choose optimal algorithms based on data type and edge device constraints
  • Avoid Common Pitfalls: Prevent sampling aliasing, buffer overflow, and rate mismatch errors
Quick Check: Edge Sampling & Compression
In 60 Seconds

Adaptive sampling and on-device compression are the two most powerful techniques for reducing IoT data volume at the edge — adaptive sampling matches collection frequency to signal dynamics, while compression exploits redundancy to shrink the data that is transmitted. Together they can reduce bandwidth requirements by 90–99% without meaningful loss of analytical value.

Edge sampling and compression reduce the amount of data IoT devices need to transmit. Think of sending a friend the highlights of a movie instead of the entire film. By transmitting only important changes or compressed summaries, devices save battery power and network bandwidth while preserving the information that matters most.

6.4 Prerequisites

Before diving into this chapter, you should be familiar with:

  • Edge Data Acquisition: Architecture: Understanding device categories and data generation patterns
  • Basic signal processing concepts: Familiarity with frequency and time-domain representations
  • Python programming: Code examples use Python for data processing
Edge Data Reduction Basics

Core Concept: Transform raw sensor data into actionable information at the source - send summaries, statistics, and alerts rather than every reading.

Why It Matters: Transmitting data costs 10-100x more energy than processing it locally. A sensor sending 1000 samples/minute to the cloud uses 100x more bandwidth than one sending minute-averages - with identical analytical value for most applications.

Key Takeaway: Apply the 90% rule - if 90% of your data is “normal” readings that will never be analyzed individually, aggregate them locally. Send statistical summaries (min, max, mean, std) at lower frequency, and only transmit raw data when anomalies are detected. This extends battery life from days to years.

6.5 Nyquist Sampling Rate

Time: ~8 min | Difficulty: Intermediate | Reference: P10.C08.U03

Key Concepts

  • Adaptive sampling: Dynamically adjusting the sensor sampling rate based on signal variance or event rate — increasing frequency when the signal changes rapidly and decreasing it during quiet periods.
  • Nyquist-Shannon theorem: The fundamental sampling principle stating that a signal must be sampled at least twice its highest frequency component to be reconstructed accurately — the minimum sampling rate for any IoT sensor.
  • Delta encoding: A compression technique transmitting only the change between consecutive readings rather than absolute values, highly effective for slowly varying sensors.
  • Run-length encoding (RLE): Compressing sequences of identical values into a count-value pair — very effective for binary event streams or sensors with frequent identical readings.
  • Lossless compression: Compression that allows perfect reconstruction of the original data — required for financial billing data, safety-critical readings, and regulatory compliance.
  • Lossy compression: Compression that discards some information to achieve higher compression ratios — acceptable for analytics workloads where small accuracy loss is tolerable.
Chapter Roadmap
  1. First set the sampling rate from the highest frequency that matters, not from the most convenient device rate.
  2. Then reduce edge traffic with aggregation, event reporting, delta encoding, and outage-aware buffering.
  3. Next compare lossless, statistical, FFT, and semantic compression against the evidence the cloud still needs.
  4. Finally test the factory vibration case, choose a strategy, and use the quizzes to check that fidelity, bandwidth, and memory stay aligned.

Checkpoints recap the decision path. Deep-dive and collapsed callouts keep calculations and implementation details available without replacing the main sampling-and-compression flow.

To accurately capture a signal, the sampling rate must be at least twice the highest frequency component of interest:

fsample2×fmaxf_{sample} \geq 2 \times f_{max}

Practical examples:

Signal TypeMax FrequencyMin Sample RateTypical Rate
Temperature0.1 Hz (slow changes)0.2 Hz1 sample/minute
Vibration500 Hz1 kHz2-5 kHz
Audio20 kHz40 kHz44.1 kHz
Motion (IMU)50 Hz100 Hz100-200 Hz

The mathematical gist. A 1,800 RPM shaft rotates at 30 Hz. Eight rolling elements place the fault at 240 Hz and its third harmonic at 720 Hz, so strict Nyquist needs 1,440 Hz and the chapter’s 2.5× margin needs 3,600 Hz. At 4 kHz and two bytes per sample, raw data is 8,000 B/s; a 120 B/s peak record is a 66.7× reduction without discarding the input’s 16-bit resolution.

Math Bridge · guided foundationsWhy must bearing-fault sampling be settled before compression?Let Data Dora trace shaft speed, harmonics, Nyquist margin, aliases, and payload reduction.
Common Pitfall: Sampling Aliasing

The mistake: Sampling signals below the Nyquist rate (2x the highest frequency), causing phantom patterns (aliasing) that don’t exist in the original signal.

Symptoms:

  • Vibration analysis shows unexpected low-frequency patterns
  • Motor speed readings fluctuate despite constant RPM
  • Temperature data shows oscillations that don’t match physical reality
  • Frequency analysis reveals false peaks at wrong frequencies
  • Bearing fault detection produces false positives

Why it happens: Engineers apply “common sense” sampling rates without frequency analysis. Underestimating signal bandwidth - a 60 Hz motor generates harmonics at 120 Hz, 180 Hz, etc. Cost pressure drives lower sampling rates. Copy-pasting configurations between different sensor types.

The fix: Always sample at >2x the highest frequency of interest:

Signal TypeMax FrequencyMinimum Sample RateRecommended
Room temperature0.01 Hz0.02 Hz1/minute
HVAC response0.1 Hz0.2 Hz1/second
Motor vibration500 Hz1 kHz2.5 kHz
Bearing analysis5 kHz10 kHz25 kHz

Prevention: Perform frequency analysis on representative signals before deployment. Use anti-aliasing filters (low-pass hardware filters) before the ADC. When in doubt, oversample then downsample digitally with proper filtering.

What sampling rate is needed to detect bearing faults in a 1800 RPM motor?

Given:

  • Motor speed: 1800 RPM = 30 Hz (revolutions per second)
  • Bearing has 8 rolling elements
  • Bearing fault frequency: Rolling element passes once per revolution = 8×30=2408 \times 30 = 240 Hz
  • Harmonics: 2nd harmonic at 480 Hz, 3rd at 720 Hz
  • Highest frequency of interest: 3rd harmonic = 720 Hz

Nyquist calculation:

fsample2×fmax=2×720 Hz=1440 Hz minimumf_{sample} \geq 2 \times f_{max} = 2 \times 720 \text{ Hz} = 1440 \text{ Hz minimum}

Practical safety factor (2.5x above Nyquist):

fsample=2.5×1440=3600 Hz4 kHzf_{sample} = 2.5 \times 1440 = 3600 \text{ Hz} \approx 4 \text{ kHz}

If sampled at only 100 Hz (common mistake):

  • Nyquist frequency = 50 Hz, but bearing fault is at 240 Hz
  • 240 Hz aliases to 240round(240/100)×100=240200=40|240 - \text{round}(240/100) \times 100| = |240 - 200| = 40 Hz
  • Result: False 40 Hz pattern appears instead of the real 240 Hz bearing fault
  • Impact: Bearing failure goes undetected until catastrophic failure

Memory and bandwidth check:

  • 4 kHz sampling × 2 bytes per sample = 8 kB/s raw data
  • 1-second FFT windows → 8 kB per analysis
  • Send top 10 frequency peaks → 120 bytes per second (67× reduction)
  • Conclusion: Edge FFT compression mandatory for battery-powered vibration monitoring

6.6 Nyquist Rate Calculator

Use this calculator to explore how motor speed, harmonic order, and safety factor affect the required sampling rate and resulting data volume.

6.7 Interactive: Nyquist Sampling Animation

Data DoraCheckpoint: Sampling Without Aliasing

You now know:

  • Nyquist starts from the highest frequency of interest, so a 60 Hz motor with 4th-harmonic faults needs a 480 Hz minimum sample rate.
  • The bearing example turns 1800 RPM into 30 Hz, then follows harmonics to 720 Hz before applying the 1440 Hz minimum and practical safety margin.
  • The calculator and animation are sanity checks for the same rule: low sample rates create false low-frequency patterns, not trustworthy compression.

6.8 Edge Data Reduction Techniques

Time: ~12 min | Difficulty: Intermediate | Reference: P10.C08.U03b

Before transmitting data to the cloud, edge devices can apply several reduction strategies:

  1. Aggregation: Compute statistics over time windows (mean, min, max, variance)
  2. Compression: Apply lossless (ZIP) or lossy (threshold-based) compression
  3. Event-based reporting: Only transmit when values exceed thresholds
  4. Delta encoding: Send only changes from previous values
# Example: Edge aggregation for temperature sensor
class EdgeAggregator:
    def __init__(self, window_size=60):  # 60 samples = 1 minute at 1 Hz
        self.window_size = window_size
        self.buffer = []

    def add_sample(self, value):
        self.buffer.append(value)
        if len(self.buffer) >= self.window_size:
            return self.compute_summary()
        return None

    def compute_summary(self):
        summary = {
            "min": min(self.buffer),
            "max": max(self.buffer),
            "mean": sum(self.buffer) / len(self.buffer),
            "samples": len(self.buffer)
        }
        self.buffer = []
        return summary

# Usage: Send 1 summary per minute instead of 60 raw samples
aggregator = EdgeAggregator(window_size=60)
for temp_reading in sensor_stream:
    summary = aggregator.add_sample(temp_reading)
    if summary:
        transmit_to_cloud(summary)  # 60x bandwidth reduction
Try It: Edge Aggregation Data Reduction

Adjust the sensor sampling rate and aggregation window size to see how edge aggregation reduces data volume. The widget calculates the bandwidth reduction and shows what information is preserved versus lost.

Battery life impact of edge aggregation vs raw transmission:

Scenario: Temperature sensor with 2500 mAh battery, 1 reading/minute

Option A: Raw transmission (every reading sent):

  • LoRa TX: 120 mA for 1.5s per transmission
  • Transmissions per hour: 60
  • TX energy per hour: (120 mA×1.5 s×60)/3600=3.0 mAh(120 \text{ mA} \times 1.5 \text{ s} \times 60) / 3600 = 3.0 \text{ mAh}
  • Plus sleep: 0.01 mAh/hour
  • Total: 3.01 mAh/hour → battery life = 2500 / 3.01 = 831 hours = 35 days

Option B: Edge aggregation (1 summary every 15 minutes):

  • Transmissions per hour: 4
  • TX energy per hour: (120 mA×1.5 s×4)/3600=0.20 mAh(120 \text{ mA} \times 1.5 \text{ s} \times 4) / 3600 = 0.20 \text{ mAh}
  • Plus sleep: 0.01 mAh/hour
  • Total: 0.21 mAh/hour → battery life = 2500 / 0.21 = 11,905 hours = 496 days = 1.4 years

Battery life improvement: 49635=14.2×\frac{496}{35} = 14.2\times longer

Data volume comparison:

  • Raw: 60 packets/hour × 20 bytes = 1.2 kB/hour = 28.8 kB/day
  • Aggregated: 4 packets/hour × 32 bytes (min/max/mean/count) = 128 bytes/hour = 3.1 kB/day
  • Bandwidth reduction: 28.83.1=9.3×\frac{28.8}{3.1} = 9.3\times

Key insight: Transmission dominates power budget. Aggregation provides 14× battery improvement with minimal information loss for trend analysis.

6.9 Battery vs Aggregation Calculator

Adjust the parameters below to see how edge aggregation affects battery life and bandwidth for a LoRa-connected sensor.

Common Pitfall: Sampling Rate Mismatch

The mistake: Combining data from sensors with different sampling rates without proper resampling, leading to incorrect correlations and temporal misalignment.

Symptoms:

  • Correlation analysis shows unexpected null or spurious relationships
  • Merged datasets have many NaN/missing values at certain timestamps
  • Time-series plots show “jagged” or misaligned signals
  • ML models perform poorly despite good individual sensor data

Why it happens: Teams often assume all sensors operate at the same rate. A temperature sensor at 1 Hz combined with a vibration sensor at 100 Hz creates 99 missing values per temperature reading. Naive timestamp matching drops 99% of vibration data.

The fix: Use proper resampling/interpolation before combining:

# Resample high-frequency data to match low-frequency
vibration_1hz = vibration_100hz.resample('1S').mean()
# Or upsample low-frequency with interpolation
temp_100hz = temp_1hz.resample('10ms').interpolate(method='linear')
# Then merge on aligned timestamps
merged = pd.merge_asof(vibration_1hz, temp_1hz, on='timestamp', tolerance=pd.Timedelta('500ms'))

Prevention: Document sampling rates in sensor metadata. Create a data alignment layer that resamples all sources to a common time base before analysis.

Common Pitfall: Edge Buffer Overflow
  1. Data Dora crosses out an average-only buffer plan in red; the same panel shows a network outage, a burst filling the buffer, dropped readings, and a larger worst-case plan.

    Wrong: Average traffic is enough to size the buffer. Outages and bursts can fill it and erase readings.

CP-0048 misconception buster: Configuring edge device buffers without considering worst-case scenarios, causing data loss during network outages or traffic spikes.

The mistake: Configuring edge device buffers without considering worst-case scenarios, causing data loss during network outages or traffic spikes.

Symptoms:

  • Gaps in time-series data after network recovery
  • “Buffer full, dropping oldest data” warnings in device logs
  • Critical events missing during high-activity periods
  • Inconsistent data counts between edge and cloud
  • Post-incident analysis reveals missing sensor readings

Why it happens: Buffer sizes calculated for average conditions, not peak loads. Network outage duration underestimated. Sensor burst rates during events (motion, vibration) exceed steady-state assumptions. Memory constraints on edge devices force small buffers.

The fix: Size buffers for worst-case, not average:

# Buffer sizing calculation
samples_per_second = 10
max_outage_duration_seconds = 3600  # 1 hour
safety_margin = 1.5

min_buffer_size = samples_per_second * max_outage_duration_seconds * safety_margin
# = 10 * 3600 * 1.5 = 54,000 samples

# If memory-constrained, implement tiered retention:
# - Last 5 minutes: Full resolution
# - 5-60 minutes: 10x downsampled
# - Beyond 60 minutes: Statistical summary only
Buffer Sizing for Outages

Calculate the required buffer size for your edge device based on sampling rate, expected outage duration, and available memory. See how tiered retention can help when memory is constrained.

Prevention: Monitor buffer utilization as a health metric. Alert at 70% capacity. Implement graceful degradation (reduce resolution before dropping data). Test with simulated network outages lasting 2x your expected maximum.

Data DoraCheckpoint: Reduce Before Transmit

You now know:

  • Aggregation can replace 60 raw samples with 1 summary when min, max, mean, and count preserve the decision evidence.
  • Transmission dominates many edge power budgets: the worked LoRa case moves from 35 days to 496 days by sending 4 summaries per hour.
  • Buffer plans need peak-load math, including the 54,000-sample outage calculation, before teams trust compressed or tiered retention data.

6.10 Compression Algorithms Deep Dive

Time: ~20 min | Difficulty: Advanced | Reference: P10.C08.U03c

Edge devices face a fundamental trade-off: transmit less data (save power, bandwidth, cost) while preserving information needed for downstream analytics. This deep dive compares compression techniques across three dimensions: compression ratio, computational cost, and information preservation.

6.11 Compression Algorithm Categories

CategoryCompression RatioCPU CostInformation LossBest For
Lossless2:1 - 4:1MediumNoneCritical data, audit logs
Lossy Statistical10:1 - 100:1LowControlledTrend analysis, dashboards
Lossy Transform50:1 - 500:1HighControlledPattern detection, ML features
Semantic100:1 - 1000:1Low-MediumSignificantEvent detection, alerts

6.12 Lossless Compression: DEFLATE/GZIP

Standard lossless compression works well for structured IoT data:

import gzip
import json

def compress_batch(readings: list[dict]) -> bytes:
    """
    Compress a batch of sensor readings losslessly.
    Typical compression: 3-5x for JSON sensor data.
    """
    json_str = json.dumps(readings)
    compressed = gzip.compress(json_str.encode('utf-8'), compresslevel=6)
    return compressed

# Example: 100 temperature readings
readings = [{"ts": 1704067200 + i, "v": 22.5 + (i % 10) * 0.1} for i in range(100)]
raw_size = len(json.dumps(readings).encode())       # ~4,500 bytes
compressed_size = len(compress_batch(readings))     # ~1,200 bytes
# Compression ratio: 3.75:1
Try It: GZIP Compression Estimator

Explore how batch size, data format, and compression level affect lossless GZIP compression of IoT sensor readings.

Performance characteristics:

MetricGZIP Level 1GZIP Level 6GZIP Level 9
Compression ratio2.5:13.5:14:1
Compress speed50 MB/s20 MB/s5 MB/s
Decompress speed100 MB/s100 MB/s100 MB/s
Edge CPU impactLowMediumHigh

When to use: Audit trails, compliance data, any data that may be queried in original form. Do not use for real-time streams on constrained MCUs.

6.13 Lossy Statistical: Aggregation Windows

Compute statistics over time windows, discard raw samples:

import statistics
from dataclasses import dataclass
from typing import Optional

@dataclass
class AggregatedWindow:
    timestamp: int      # Window start
    count: int          # Number of samples
    mean: float
    min_val: float
    max_val: float
    std_dev: float
    p95: Optional[float] = None  # Optional percentile

class WindowAggregator:
    def __init__(self, window_seconds: int = 60):
        self.window_seconds = window_seconds
        self.buffer: list[float] = []
        self.window_start: Optional[int] = None

    def add_sample(self, timestamp: int, value: float) -> Optional[AggregatedWindow]:
        if self.window_start is None:
            self.window_start = timestamp

        # Check if window complete
        if timestamp - self.window_start >= self.window_seconds:
            result = self._compute_aggregate()
            self.buffer = [value]
            self.window_start = timestamp
            return result

        self.buffer.append(value)
        return None

    def _compute_aggregate(self) -> AggregatedWindow:
        sorted_buffer = sorted(self.buffer)
        p95_idx = int(len(sorted_buffer) * 0.95)

        return AggregatedWindow(
            timestamp=self.window_start,
            count=len(self.buffer),
            mean=statistics.mean(self.buffer),
            min_val=min(self.buffer),
            max_val=max(self.buffer),
            std_dev=statistics.stdev(self.buffer) if len(self.buffer) > 1 else 0,
            p95=sorted_buffer[p95_idx] if p95_idx < len(sorted_buffer) else None
        )

# Example: 1 Hz sensor, 60-second windows
# Input: 60 samples x 8 bytes = 480 bytes
# Output: 1 aggregate x 48 bytes = 48 bytes
# Compression ratio: 10:1
# Preserved: Trend (mean), anomaly detection (min/max/std), health (count)
Window Aggregation Calculator

Configure the sensor rate and aggregation window to see how statistical summaries compress continuous data while preserving trend and anomaly detection capability.

Information loss analysis:

What’s PreservedWhat’s Lost
Average value (trend)Individual sample timing
Min/max (bounds)Exact sequence of values
Standard deviation (stability)Sub-window patterns
Sample count (health)Correlation with other sensors at sample level

When to use: Temperature, humidity, air quality - any slowly changing signal where trends matter more than exact samples.

6.14 Lossy Transform: FFT-Based Compression

Transform to frequency domain, keep only significant components:

import numpy as np
from dataclasses import dataclass

@dataclass
class FFTCompressed:
    timestamp: int
    sample_rate: float
    duration: float
    frequencies: list[float]    # Top N frequency components
    magnitudes: list[float]     # Corresponding magnitudes
    phases: list[float]         # Phase angles for reconstruction

def fft_compress(samples: np.ndarray, sample_rate: float,
                 timestamp: int, top_n: int = 10) -> FFTCompressed:
    """
    Compress time-series data using FFT, keeping top N frequency components.

    Typical compression: 100:1 to 500:1 depending on signal complexity.
    Best for: Vibration, audio, periodic signals.
    """
    # Compute FFT
    fft_result = np.fft.rfft(samples)
    freqs = np.fft.rfftfreq(len(samples), 1/sample_rate)

    # Get magnitudes and find top N (excluding DC component)
    magnitudes = np.abs(fft_result[1:])  # Skip DC
    phases = np.angle(fft_result[1:])
    freqs = freqs[1:]

    # Select top N by magnitude
    top_indices = np.argsort(magnitudes)[-top_n:]

    return FFTCompressed(
        timestamp=timestamp,
        sample_rate=sample_rate,
        duration=len(samples) / sample_rate,
        frequencies=freqs[top_indices].tolist(),
        magnitudes=magnitudes[top_indices].tolist(),
        phases=phases[top_indices].tolist()
    )

def fft_decompress(compressed: FFTCompressed, num_samples: int) -> np.ndarray:
    """
    Reconstruct signal from FFT components (lossy reconstruction).
    """
    t = np.linspace(0, compressed.duration, num_samples)
    signal = np.zeros(num_samples)

    for freq, mag, phase in zip(compressed.frequencies,
                                 compressed.magnitudes,
                                 compressed.phases):
        signal += mag * np.cos(2 * np.pi * freq * t + phase)

    return signal

# Example: Vibration sensor, 1 second at 1000 Hz
samples = np.sin(2*np.pi*50*np.linspace(0, 1, 1000))  # 50 Hz signal
samples += 0.3 * np.sin(2*np.pi*150*np.linspace(0, 1, 1000))  # 150 Hz harmonic

# Input: 1000 samples x 4 bytes = 4000 bytes
# Output: 10 freq-mag-phase tuples x 12 bytes = 120 bytes + 20 bytes metadata
# Compression ratio: ~30:1

compressed = fft_compress(samples, 1000.0, 1704067200, top_n=10)
reconstructed = fft_decompress(compressed, 1000)

# Reconstruction error for this example: ~5% RMS
# Bearing fault detection: Still works (frequency peaks preserved)
Try It: FFT Compression Explorer

Explore how FFT-based compression trades off between keeping more frequency components (higher fidelity) and achieving higher compression. Adjust the signal parameters and number of retained components.

When to use: Vibration analysis, acoustic monitoring, any signal where frequency content matters more than exact waveform.

6.15 Continue to Part 2

Continue with Edge Data: Semantic Compression and Pipelines.