Part II: Classical Signal Processing and Feature Engineering
Chapter 8: Feature Engineering and Dimensionality Reduction

Domain-specific feature libraries (tsfresh, catch22)

"I computed seven hundred and eighty-nine features before lunch. Nine of them meant anything. I spent the afternoon pretending I had known which nine all along."

An Overzealous AI Agent

Prerequisites

This section assumes you have met the individual feature families of the previous three sections: time-domain descriptors (Section 8.1), frequency and time-frequency features (Section 8.2), and the statistical, shape, and entropy features of Section 8.3. It leans on the multiple-testing and train/test hygiene ideas from Chapter 5, and it hands off to feature selection in Section 8.5. Basic Python and a working scikit-learn install are enough to run the code.

The Big Picture

The last three sections taught you to hand-carve features one physical intuition at a time: this band holds the fault energy, that ratio tracks fatigue, this entropy rises before a seizure. Domain-specific feature libraries flip the economics. Instead of guessing which of the hundreds of known descriptors your problem needs, you compute a large, curated bank of them mechanically and let a statistical filter tell you which ones carry signal. Two libraries dominate this space: tsfresh, which throws roughly 780 features at the wall and prunes with hypothesis tests, and catch22, which ships the 22 features that survived a global tournament across thousands of datasets. Knowing when to spray and when to use the distilled kit is the skill this section builds.

Why a library beats another afternoon of hand-carving

By now you can compute a dozen good features from first principles, and for a well-understood modality that is often the right move. But real projects rarely arrive that clean. You are handed a folder of vibration snippets from a gearbox you have never seen, or wrist accelerometer windows for an activity you cannot label by eye, and the honest answer to "which features matter here?" is that nobody knows yet. Hand-carving in that situation is a slow, biased search: you code the features you already believe in, so you can only rediscover your own priors.

A feature library reframes the task as compute broadly, then select. The literature already contains hundreds of interpretable time-series descriptors, autocorrelation structure, spectral concentration, distribution shape, complexity and entropy measures, stationarity statistics, all scattered across decades of papers. A library collects them, gives each a stable name and a tested implementation, and computes the whole bank in one call. The what is a standardized, reusable feature vocabulary. The why is coverage and reproducibility: you stop missing the one descriptor that happened to matter, and two teams computing "the mean of absolute changes" get byte-identical numbers. The when is early exploration on an unfamiliar signal, and any pipeline where a tree-based classifier will do the heavy lifting downstream.

tsfresh: spray, then prune with hypothesis tests

tsfresh (Time Series FeatuRe Extraction on the basis of Scalable Hypothesis tests) computes on the order of 780 features per input series by default, sweeping every descriptor family at once and expanding parameterized features across many settings (autocorrelation at dozens of lags, FFT coefficients across many bins, and so on). Fired blindly, that many columns on a modest sample count is a leakage and overfitting trap, so tsfresh pairs extraction with a built-in selector, the FRESH algorithm.

FRESH treats each feature independently and asks a single question: is this feature's distribution different across the target classes (or correlated with a numeric target)? It runs the appropriate hypothesis test per feature, Mann-Whitney or Kolmogorov-Smirnov for binary targets, Kendall rank correlation for continuous ones, producing a \(p\)-value for each candidate. Because you are testing hundreds of features at once, raw \(p\)-values are meaningless: at the 5% level you would expect around 39 of 780 pure-noise features to look "significant" by chance. FRESH therefore controls the false discovery rate with the Benjamini-Yekutieli procedure, which keeps the expected fraction of false positives among the selected features below a level you set. The retained set is small, ranked, and defensible.

Key Insight

Mass feature extraction is not the dangerous part; mass feature selection without multiple-testing correction is. Computing 780 features is cheap and harmless. Picking the "best" ones by looking at how well each separates your classes, on the same data you will report on, is how you manufacture a feature that predicts pure noise. tsfresh's value is not the 780 candidates; it is that its selector already builds in the false-discovery-rate control that a hand-rolled "keep the top-k by correlation" loop silently omits.

Common Misconception

The misconception is that select_features is a convenience you can run on the whole dataset before splitting. It is not. The selector looks at the target, so fitting it on all your data leaks test-set information into which features exist, and your reported accuracy inflates. Select on the training fold only, then apply the same chosen column list to validation and test, exactly as you would fit a scaler. This is the Chapter 5 leakage rule wearing a feature-engineering costume.

catch22: the 22 that survived a global tournament

Where tsfresh maximizes coverage, catch22 minimizes count. It descends from hctsa, a MATLAB behemoth of roughly 7700 time-series features. Researchers scored all of them across 93 classification datasets from the UEA/UCR archive, kept only features whose accuracy beat a null baseline, then clustered the survivors by performance correlation and picked one representative per cluster to strip redundancy. The result, published as "catch22: CAnonical Time-series CHaracteristics" (Lubba et al., Data Mining and Knowledge Discovery, 2019), is 22 features that together retain about 90% of the full set's classification accuracy while running in C at a tiny fraction of the cost.

The 22 are deliberately diverse: outlier-robust distribution statistics, linear and nonlinear autocorrelation, measures of how predictable the series is from its own past, spectral concentration, and symbolic-dynamics complexity. Crucially they are computed on \(z\)-scored input by default, so they capture shape and dynamics rather than raw amplitude and scale, which is exactly what you want when comparing series recorded at different gains or with different DC offsets. When you also need level and spread, the catch24 variant simply adds back the mean and standard deviation.

In Practice: a bearing you have never met

A reliability team at a paper mill inherits accelerometers on a fleet of gearboxes with no historical fault labels beyond a handful of past failures. They have thousands of one-second vibration windows and almost no idea which descriptors matter for this specific machine geometry. Hand-carving from bearing-fault theory would point them at envelope spectra and specific defect frequencies (the approach of Chapter 7), and that is worth doing. But as a fast first pass they run catch22 on every window: 22 numbers each, computed in milliseconds, no domain assumptions. A gradient-boosted tree on those 22 columns separates the known-bad windows from the healthy ones well enough to triage the fleet in an afternoon, and the top-ranked features (a jump in a nonlinear autocorrelation measure and rising spectral concentration) point engineering straight at the physics worth modeling next. The distilled kit did not replace the domain expert; it told the expert where to aim. This exact workflow recurs in Chapter 37.

Choosing and combining the two

The two libraries answer different questions and it is normal to use both. Reach for catch22 when you need a compact, fast, low-variance feature set: streaming or edge deployment where compute is scarce (see Chapter 59), small sample counts where 780 columns would overfit, or a first-look baseline you can compute on a laptop in seconds. Reach for tsfresh when you have enough labeled windows to support wide extraction plus disciplined selection, when you suspect an unusual descriptor matters, or when you want the selector's ranking as evidence for which physical mechanism to investigate. A common pattern: use catch22 to establish a baseline in minutes, then, if the ceiling is too low, escalate to tsfresh's wider bank to hunt for the missing feature.

Both feed naturally into the same downstream stages. The selected feature matrix is exactly the input that Section 8.5 refines with model-based selection, that Section 8.6 compresses with PCA or UMAP, and that a compact classifier consumes. And when a feature-based pipeline of this kind matches or beats a neural network, which happens more often than deep-learning enthusiasm suggests, that is the story of Section 8.7. For human-activity recognition specifically, these banks are a standard strong baseline against the deep models of Chapter 26.

import numpy as np
import pycatch22
from tsfresh import extract_features, select_features
from tsfresh.utilities.dataframe_functions import impute
import pandas as pd

# --- catch22: 22 features per window, no target needed ---
window = np.random.randn(500)                      # one sensor window
c22 = pycatch22.catch22_all(window)                # dict: names + values
print(len(c22["values"]))                          # -> 22

# --- tsfresh: wide extraction, then LEAKAGE-SAFE selection on train only ---
# long-format frame: one row per (window id, time step)
extracted = extract_features(train_long_df, column_id="id", column_sort="t")
impute(extracted)                                  # tsfresh emits some NaNs
selected = select_features(extracted, y_train)     # FRESH + Benjamini-Yekutieli
kept_cols = selected.columns                        # freeze this list
X_test = extract_features(test_long_df, column_id="id", column_sort="t")
impute(X_test)
X_test = X_test.reindex(columns=kept_cols, fill_value=0.0)  # apply, do NOT re-select
Listing 8.4.1. catch22 returns a fixed 22-value dictionary per window with no target and no tuning. tsfresh instead extracts a wide bank, then select_features runs FRESH with Benjamini-Yekutieli false-discovery control against the training labels; the retained column list is frozen and merely reindexed onto the test set, never re-selected, which is the leakage-safe discipline the prose insists on.

The Right Tool

Hand-coding the equivalent of a broad feature bank plus its selector is not a dozen lines; it is a small project. You would implement and unit-test each descriptor (easily 300+ lines for even 30 features), write the per-feature hypothesis tests, and add a Benjamini-Yekutieli correction that is genuinely easy to get wrong. The two calls extract_features(...) and select_features(...) collapse several hundred lines of feature code plus the entire multiple-testing machinery into two lines, and pycatch22.catch22_all(x) replaces roughly 22 separately tested C implementations with one. The library handles extraction, imputation of undefined values, the per-feature tests, and the false-discovery correction; you still own the train-only discipline and the choice of which library fits your compute budget.

Exercise

Take any labeled windowed sensor dataset you have (or the UCR "GunPoint" set). Compute catch22 features for every window and train a gradient-boosted tree with proper train/test splitting; record the accuracy and wall-clock time. Now run tsfresh's full extraction plus select_features on the training fold only, freeze the kept columns, and train the same classifier. Compare accuracy, feature count, and extraction time. Then, as a deliberate mistake, run select_features on the full dataset before splitting and re-measure test accuracy. Explain the gap you see in terms of the leakage rule.

Self-Check

1. Why does computing 780 features cause no harm, while selecting among them by target correlation on the full dataset does?

2. What problem does the Benjamini-Yekutieli correction in FRESH solve, and what goes wrong if you skip it?

3. catch22 \(z\)-scores its input by default. Name one situation where that is exactly right and one where you would prefer catch24 instead.

What's Next

In Section 8.5, we take the feature matrix these libraries produce and ask a sharper question than "which features pass a hypothesis test": how to select a compact, non-redundant subset that a specific model actually benefits from, using filter, wrapper, and embedded methods, and how to do it without the leakage traps that quietly wreck feature-engineered pipelines.