Tipps & Tricks

Effiziente Zeitreihen-Feature-Generierung mit Python Itertools

8 min Lesezeit
Effiziente Zeitreihen-Feature-Generierung mit Python Itertools

Einleitung

Die Generierung von Features für Zeitreihen folgt nicht den gleichen Regeln wie bei tabellarischen Daten. Beobachtungen sind nicht unabhängig, die Reihenfolge der Zeilen ist nicht zufällig, und die nützlichsten Merkmale sind selten einzelne Werte. Es ist erforderlich, Muster über die Zeit zu identifizieren, wie Änderungsraten, Verzögerungsvergleiche, Abweichungen von einem gleitenden Durchschnitt und mehr.

Das Erstellen von Verzögerungen, gleitenden Fenstern und Gruppierungen über verschiedene Auflösungen sind im Kern Iterationsprobleme über geordnete Sequenzen. Das Python-Modul itertools eignet sich hervorragend für diese Art von Arbeiten. Es ersetzt nicht die hochgradigen Abstraktionen von pandas wie .rolling(), bietet jedoch niedrigere Bausteine, um genau die benötigten Features zu konstruieren, mit vollständiger Kontrolle über die Logik. Für eine tiefere Einsicht in die Merkmalsauswahl können Sie unseren Artikel über effektive Merkmalsauswahl lesen.

In diesem Artikel werden sieben Kategorien von Zeitreihen-Features mithilfe von itertools erstellt. Zudem wird jede Kategorie auf einen Beispieldatensatz angewendet.

Der Code ist auf GitHub verfügbar.

Erstellung eines Beispieldatensatzes

Bevor wir mit der Erstellung der Features beginnen, erstellen wir einen Beispieldatensatz von Sensoren, mit dem wir im gesamten Artikel arbeiten werden.

import numpy as np
import pandas as pd
import itertools
np.random.seed(42)
periods = 168  # eine Woche stündlicher Messungen
index = pd.date_range(start="2024-03-01", periods=periods, freq="h")
hours = np.arange(periods)
# Temperatur (°C): täglicher Zyklus + allmählicher Drift + Rauschen
temp_base = 3.5
temp_daily = 1.2 * np.sin(2 * np.pi * hours / 24)
temp_drift = 0.003 * hours
temp_noise = np.random.normal(0, 0.3, periods)
temperature = temp_base + temp_daily + temp_drift + temp_noise
# Luftfeuchtigkeit (%): inverse Beziehung zur Temperatur + Rauschen
humidity = 78 - 2.1 * (temperature - temp_base) + np.random.normal(0, 1.2, periods)
# Stromverbrauch (kW): Spitzen während der Geschäftszeiten, höher an Wochentagen
day_of_week = index.dayofweek
business_hours = ((index.hour >= 8) & (index.hour < 17)).astype(int)
weekend_factor = np.where(day_of_week >= 5, 0.6, 1.0)
power = (42.0 + 18.0 * business_hours * weekend_factor + np.random.normal(0, 2.1, periods))
df = pd.DataFrame({
 "temperature_c": np.round(temperature, 3),
 "humidity_pct": np.round(humidity, 2),
 "power_kw": np.round(power, 2),
}, index=index)
df.index.name = "timestamp"
print(df.head(8))
print(f"\nShape: {df.shape}")

1. Generierung von Lag-Features mit islice

Lag-Features sind die grundlegendsten Merkmale von Zeitreihen: der Wert einer Variablen zu einem festen Zeitpunkt in der Vergangenheit. Beispielsweise können Werte von 1 Schritt, 6 Schritten oder 24 Schritten in der Vergangenheit jeweils unterschiedliche Muster erfassen, wie kurzfristige Schwankungen, wiederkehrendes Verhalten innerhalb eines Zeitraums und langfristige Trends oder Saisonalitäten. Um mehr über die Automatisierung der explorativen Datenanalyse zu erfahren, schauen Sie sich unsere Tipps zu effizienten Python-Skripten an.

Wir erstellen Lag-Features für unseren Beispieldatensatz mithilfe von islice:

sensor_readings = df["temperature_c"].tolist()
lag_offsets = [1, 6, 12, 24]
lag_features = {}
for lag in lag_offsets:
 lagged = list(itertools.islice(sensor_readings, 0, len(sensor_readings) - lag))
 # Anfang mit None auffüllen, um die Indexausrichtung zu bewahren
 lag_features[f"temp_lag_{lag}h"] = [None] * lag + lagged
lag_df = pd.DataFrame(lag_features, index=df.index)
lag_df["temperature_c"] = df["temperature_c"]
print(lag_df.iloc[24:30])

2. Erstellung von Gleitenden Fenster-Features mit islice und accumulate

Ein einzelner Lag-Wert zeigt, was der Sensor zu einem bestimmten Zeitpunkt in der Vergangenheit gemessen hat. Eine gleitende Statistik zeigt, was der Sensor über einen Zeitraum hinweg gemacht hat, was oft viel nützlicher ist.

readings = df["temperature_c"].tolist()
window_size = 6  # 6-Stunden gleitendes Fenster
rolling_features = []
for i in range(len(readings)):
 if i >= window_size - 1:
 rolling_mean = np.mean(readings[i - window_size + 1:i + 1])
 rolling_std = np.std(readings[i - window_size + 1:i + 1])
 rolling_min = np.min(readings[i - window_size + 1:i + 1])
 rolling_max = np.max(readings[i - window_size + 1:i + 1])
 rolling_features.append((rolling_mean, rolling_std, rolling_min, rolling_max))
rolling_df = pd.DataFrame(rolling_features, columns=["rolling_mean_6h", "rolling_std_6h", "rolling_min_6h", "rolling_max_6h"], index=df.index[window_size - 1:])
rolling_df["temperature_c"] = df["temperature_c"].iloc[window_size - 1:]
print(rolling_df.head(8))

3. Erstellung von Saisonalen Interaktions-Features mit product

Viele Zeitreihen zeigen geschichtete Saisonalität, bei der mehrere zeitliche Zyklen interagieren — wie Tageszeit, Wochentag und breitere betriebliche oder zyklische Perioden. Interaktionsmerkmale, die diese Dimensionen kombinieren, können Muster erfassen, die einzelne Zeitkomponenten allein möglicherweise übersehen.

Jetzt erstellen wir Interaktionsmerkmale mit product:

hours_of_day = list(range(24))
day_types = ["weekday", "weekend"]
operational_shifts = ["off_peak", "on_peak"]  # on_peak: 08:00–18:00
# Erstellen eines vollständigen Lookup-Rasters für alle Kombinationen
season_grid = list(itertools.product(hours_of_day, day_types, operational_shifts))
season_df = pd.DataFrame(season_grid, columns=["hour", "day_type", "shift"])
# Simulieren der erwarteten Basistemperatur pro Kombination
np.random.seed(14)
season_df["baseline_temp_c"] = np.round(
 3.5 + 0.8 * np.sin(2 * np.pi * season_df["hour"] / 24)
 + np.where(season_df["day_type"] == "weekend", 0.3, 0.0)
 + np.where(season_df["shift"] == "on_peak", 0.5, 0.0)
 + np.random.normal(0, 0.1, len(season_df)),
 3
)
print(season_df[season_df["hour"].isin([0, 8, 14, 20])].head(16).to_string(index=False))
print(f"\nTotal grid combinations: {len(season_df)}")

4. Extrahieren von Gleitenden Fenster-Statistiken mit tee

Manchmal müssen Sie dieselbe Sequenz gleichzeitig durch mehrere statistische Linsen verarbeiten — Mittelwert, Varianz, Änderungsrate — ohne sie mehrfach zu iterieren. itertools.tee erstellt unabhängige Iteratoren aus einer einzigen Quelle, was genau das ist, was Sie benötigen.

def sliding_window_stats(series, window_size):
 """Berechnet Mittelwert, Bereich und Änderungsrate über gleitende Fenster mithilfe von tee."""
 results = []
 it = iter(series)
 window = list(itertools.islice(it, window_size))
 if len(window) >= window_size:
 mean = np.mean(window)
 range_val = np.max(window) - np.min(window)
 rate_of_change = (window[-1] - window[0]) / window_size
 results.append((mean, range_val, rate_of_change))
 return results

5. Kombinieren von Multi-Resolution Zeit-Features mit chain

Nützliche Zeitreihen-Features stammen oft gleichzeitig aus mehreren zeitlichen Auflösungen: der Rohmessung, einem 6-Stunden gleitenden Mittelwert, einem 24-Stunden gleitenden Mittelwert und einem Kalender-Feature wie der Stunde des Tages. Diese befinden sich normalerweise in separaten Arrays und müssen zu einer sauberen Feature-Liste zusammengefügt werden. Hier ist, wie Sie chain verwenden können, um solche Features zu kombinieren:

humidity = df["humidity_pct"].tolist()
def rolling_means(series, window):
 means = []
 for i in range(len(series)):
 if i >= window - 1:
 mean = np.mean(series[i - window + 1:i + 1])
 means.append(mean)
 return means
humidity_roll_6h = rolling_means(humidity, 6)
humidity_roll_24h = rolling_means(humidity, 24)
combined_df = pd.DataFrame({
 "humidity_raw": humidity,
 "humidity_roll_6h": humidity_roll_6h,
 "humidity_roll_24h": humidity_roll_24h,
 "hour_of_day": df.index.hour,
 "is_business_hour": (df.index.hour >= 8) & (df.index.hour < 17)
}, index=df.index)
print(combined_df.head(8))

6. Berechnung paarweiser zeitlicher Korrelationen mit combinations

In einem Multi-Sensor-Setting enthalten die Beziehungen zwischen Variablen über die Zeit oft wertvolle Signale, die einzelne Messungen allein nicht erfassen können. Beispielsweise können gleichzeitige Anstiege über zwei Sensoren auf aufkommende Bedingungen oder Interaktionen hinweisen, die nicht offensichtlich sind, wenn jede Serie isoliert analysiert wird.

Die Einbeziehung von Features, die diese gemeinsamen Dynamiken widerspiegeln, kann die Fähigkeit eines Modells verbessern, subtile Muster und Abhängigkeiten zu erkennen. Lassen Sie uns versuchen, paarweise Korrelationen mithilfe von combinations zu erstellen:

sensor_cols = ["temperature_c", "humidity_pct", "power_kw"]
window_size = 12
pairwise_features = {}
for col_a, col_b in itertools.combinations(sensor_cols, 2):
 feature_name = f"corr_{col_a[:4]}_{col_b[:4]}_12h"
 correlations = []
 series_a = df[col_a].tolist()
 series_b = df[col_b].tolist()
 for i in range(len(series_a)):
 if i >= window_size - 1:
 corr = np.corrcoef(series_a[i - window_size + 1:i + 1], series_b[i - window_size + 1:i + 1])[0, 1]
 correlations.append(corr)
 pairwise_features[feature_name] = correlations
corr_df = pd.DataFrame(pairwise_features, index=df.index[window_size - 1:])
print(corr_df.head(8))

7. Akkumulieren von laufenden Baselines mit accumulate

Ein gegebener Wert kann je nach Zeitpunkt in einer Sequenz unterschiedliche Bedeutungen haben. Entscheidend ist seine Abweichung von der sich entwickelnden Basislinie — dem laufenden Mittelwert bis zu diesem Zeitpunkt. Mit einem inkrementellen Ansatz wie accumulate können Sie diesen laufenden Mittelwert effizient berechnen, ohne die gesamte Historie zu speichern.

readings = df["temperature_c"].tolist()
running_sums = list(itertools.accumulate(readings))
running_counts = list(itertools.accumulate([1] * len(readings)))
running_means = [
 round(s / c, 4)
 for s, c in zip(running_sums, running_counts)
]
# Laufender Maximalwert — höchste bisher gesehene Temperatur, nützlich für die Überwachung von Überschreitungen
running_max = list(itertools.accumulate(readings, func=max))
deviation_from_baseline = [
 round(r - m, 4)
 for r, m in zip(readings, running_means)
]
baseline_df = pd.DataFrame({
 "temperature_c": readings,
 "running_mean": running_means,
 "running_max": running_max,
 "deviation_from_baseline": deviation_from_baseline,
}, index=df.index)
print(baseline_df.head(8))

Zusammenfassung

Die Feature-Generierung für Zeitreihen dreht sich grundlegend darum, den Kontext zu beschreiben — was hat dieses Signal relativ zu dem, was wir erwarten, getan? Jede hier behandelte Funktion ist eine andere Möglichkeit, diese Frage in eine Zahl zu formalisieren, die ein Modell lernen kann.

Hier ist eine Zusammenfassung der Muster, die wir in diesem Artikel behandelt haben:

  • itertools Funktion: Lag-Features, Beispiel: Temperatur 1h, 6h, 24h zuvor
  • islice + accumulate: Gleitende Fensterstatistiken, Beispiel: 6h Mittelwert, Std, Min, Max
  • product: Saisonales Interaktionsraster, Beispiel: Stunde × Wochentyp × Schicht-Baseline
  • tee: Parallele Fensterstatistiken, Beispiel: Mittelwert + Bereich + Änderungsrate
  • chain: Multi-Resolution Feature-Zusammenstellung, Beispiel: Roh + gleitend + Kalender-Features
  • combinations: Paarweise Cross-Sensor-Korrelationen, Beispiel: Temp–Luftfeuchtigkeit, Temp–Strom rollierende Korrelation
  • accumulate: Laufende Basislinie + Abweichung, Beispiel: Drift-Erkennung vom historischen Mittelwert

Und da itertools auf der Iterator-Ebene arbeitet, lassen sich all diese Muster sauber in Streaming-Pipelines integrieren. Viel Spaß beim Feature Engineering!

```


Quellen: kdnuggets

Bildquelle: KI generiert

KI Snack