Zeitreihen-Feature-Engineering mit Python Itertools

# Einführung

Das Characteristic-Engineering für Zeitreihen folgt nicht denselben Regeln wie Tabellendaten. Beobachtungen sind nicht unabhängig, die Zeilenreihenfolge ist nicht zufällig und die nützlichsten Funktionen sind selten einzelne Messwerte. Sie müssen Muster im Zeitverlauf identifizieren, z. B. Änderungsraten, Verzögerungsvergleiche, Abweichungen von einer rollierenden Basislinie und mehr.

Aufbauverzögerungen, Schiebefenster und Gruppierung über Auflösungen hinweg sind im Kern allesamt Iterationsprobleme über geordnete Sequenzen. Das itertools-Modul von Python eignet sich hervorragend für diese Artwork von Arbeit. Es ersetzt kein Excessive-Degree Pandas Abstraktionen wie .rolling()aber es gibt Ihnen Bausteine ​​auf niedrigerer Ebene, um genau die Funktionen zu konstruieren, die Sie benötigen, mit voller Kontrolle über die Logik.

In diesem Artikel erstellen Sie mit itertools sieben Kategorien von Zeitreihenfunktionen. Sie wenden sie jeweils auch auf einen Beispieldatensatz an.

Den Code erhalten Sie auf GitHub.

# Erstellen eines Beispieldatensatzes

Bevor wir mit der Entwicklung der Funktionen beginnen, erstellen wir einen Beispielsensordatensatz, mit dem wir im gesamten Artikel arbeiten können.

import numpy as np
import pandas as pd
import itertools

np.random.seed(42)

durations = 168  # one week of hourly readings
index = pd.date_range(begin="2024-03-01", durations=durations, freq="h")
hours = np.arange(durations)

# Temperature (°C): day by day cycle + gradual drift + noise
temp_base = 3.5
temp_daily = 1.2 * np.sin(2 * np.pi * hours / 24)
temp_drift = 0.003 * hours
temp_noise = np.random.regular(0, 0.3, durations)
temperature = temp_base + temp_daily + temp_drift + temp_noise

# Humidity (%): inverse relationship with temperature + noise
humidity = 78 - 2.1 * (temperature - temp_base) + np.random.regular(0, 1.2, durations)

# Energy draw (kW): peaks throughout enterprise hours, larger on weekdays
day_of_week = index.dayofweek
business_hours = ((index.hour >= 8) & (index.hour <= 18)).astype(int)
weekend_factor = np.the place(day_of_week >= 5, 0.6, 1.0)
energy = (
    42.0
    + 18.0 * business_hours * weekend_factor
    + np.random.regular(0, 2.1, durations)
)

df = pd.DataFrame({
    "temperature_c": np.spherical(temperature, 3),
    "humidity_pct":  np.spherical(humidity, 2),
    "power_kw":      np.spherical(energy, 2),
}, index=index)
df.index.identify = "timestamp"

print(df.head(8))
print(f"nShape: {df.form}")

Ausgabe:

                     temperature_c  humidity_pct  power_kw
timestamp
2024-03-01 00:00:00          3.649         77.39     40.27
2024-03-01 01:00:00          3.772         76.52     41.33
2024-03-01 02:00:00          4.300         75.25     42.87
2024-03-01 03:00:00          4.814         74.26     40.82
2024-03-01 04:00:00          4.481         75.85     40.27
2024-03-01 05:00:00          4.604         76.09     42.51
2024-03-01 06:00:00          5.192         74.78     42.51
2024-03-01 07:00:00          4.910         76.03     40.94

Form: (168, 3)

Wir haben jetzt 168 stündliche Messwerte über drei Sensorkanäle. Lassen Sie uns nun Funktionen erstellen.

# 1. Generieren von Lag-Options mit islice

Verzögerungsmerkmale sind das grundlegendste Zeitreihenmerkmal: der Wert einer Variablen bei einer festen Anzahl von Schritten in der Vergangenheit. Beispielsweise können Werte von vor einem Schritt, vor 6 Schritten oder vor 24 Schritten jeweils unterschiedliche Muster erfassen, z. B. kurzfristige Schwankungen, wiederkehrendes Verhalten innerhalb einer Periode und längerfristige Developments oder Saisonalität.

Lassen Sie uns mithilfe von Verzögerungsfunktionen für unseren Beispieldatensatz erstellen islice:

sensor_readings = df("temperature_c").tolist()
lag_offsets = (1, 6, 12, 24)

lag_features = {}
for lag in lag_offsets:
    lagged = checklist(itertools.islice(sensor_readings, 0, len(sensor_readings) - lag))
    # Pad the start with None to protect index alignment
    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))

Ausgabe:

                     temp_lag_1h  temp_lag_6h  temp_lag_12h  temp_lag_24h  
timestamp
2024-03-02 00:00:00        2.831        2.082         3.609         3.649
2024-03-02 01:00:00        3.409        1.974         2.654         3.772
2024-03-02 02:00:00        3.919        2.960         2.425         4.300
2024-03-02 03:00:00        3.833        2.647         2.528         4.814
2024-03-02 04:00:00        4.542        2.986         2.205         4.481
2024-03-02 05:00:00        4.443        2.831         2.486         4.604

                     temperature_c
timestamp
2024-03-02 00:00:00          3.409
2024-03-02 01:00:00          3.919
2024-03-02 02:00:00          3.833
2024-03-02 03:00:00          4.542
2024-03-02 04:00:00          4.443
2024-03-02 05:00:00          4.659

islice(sensor_readings, 0, len - lag) extrahiert die um Verzögerungsschritte nach hinten verschobene Sequenz, ohne eine Kopie der vollständigen Liste zu erstellen. Der None Die Polsterung an der Vorderseite sorgt dafür, dass jedes Verzögerungsmerkmal mit dem ursprünglichen Index übereinstimmt. Dies ist wichtig, wenn Sie später NaNs für das Modelltraining abgeben.

# 2. Erstellen von Rolling-Window-Funktionen mit islice Und accumulate

Ein einzelner Verzögerungswert sagt Ihnen, was der Sensor zu einem Zeitpunkt in der Vergangenheit gemessen hat. Eine fortlaufende Statistik zeigt Ihnen, was der Sensor über einen bestimmten Zeitraum getan hat, was oft weitaus nützlicher ist.

readings = df("temperature_c").tolist()
window_size = 6  # 6-hour rolling window

rolling_features = ()

for i in vary(len(readings)):
    if i < window_size:
        rolling_features.append({
            "rolling_mean_6h": None,
            "rolling_std_6h":  None,
            "rolling_min_6h":  None,
            "rolling_max_6h":  None,
        })
        proceed

    window = checklist(itertools.islice(readings, i - window_size, i))

    # Use accumulate to compute operating sum for imply
    running_sum = checklist(itertools.accumulate(window))
    window_mean = running_sum(-1) / window_size
    window_mean_sq = sum(x**2 for x in window) / window_size

    rolling_features.append({
        "rolling_mean_6h": spherical(window_mean, 4),
        "rolling_std_6h":  spherical((window_mean_sq - window_mean**2) ** 0.5, 4),
        "rolling_min_6h":  spherical(min(window), 4),
        "rolling_max_6h":  spherical(max(window), 4),
    })

roll_df = pd.DataFrame(rolling_features, index=df.index)
roll_df("temperature_c") = df("temperature_c")

print(roll_df.iloc(6:12))

Ausgabe:

                     rolling_mean_6h  rolling_std_6h  rolling_min_6h  
timestamp
2024-03-01 06:00:00           4.2700          0.4256           3.649
2024-03-01 07:00:00           4.5272          0.4386           3.772
2024-03-01 08:00:00           4.7168          0.2929           4.300
2024-03-01 09:00:00           4.7372          0.2662           4.422
2024-03-01 10:00:00           4.6912          0.2728           4.422
2024-03-01 11:00:00           4.6095          0.3769           3.991

                     rolling_max_6h  temperature_c
timestamp
2024-03-01 06:00:00           4.814          5.192
2024-03-01 07:00:00           5.192          4.910
2024-03-01 08:00:00           5.192          4.422
2024-03-01 09:00:00           5.192          4.538
2024-03-01 10:00:00           5.192          3.991
2024-03-01 11:00:00           5.192          3.704

Der accumulate Der Aufruf hier berechnet die laufende Summe des Fensters, sodass wir die Gesamtsumme in einem Durchgang erhalten – running_sum(-1) – ohne anzurufen sum() separat. Bei großen Datensätzen, die im Streaming-Verfahren verarbeitet werden, ist es effizient, redundante Durchgänge über dieselben Daten zu vermeiden.

# 3. Erstellen saisonaler Interaktionsfunktionen mit product

Viele Zeitreihen weisen eine geschichtete Saisonalität auf, bei der mehrere zeitliche Zyklen interagieren – beispielsweise Tageszeit, Wochentag und breitere betriebliche oder zyklische Zeiträume. Interaktionsfunktionen, die diese Dimensionen kombinieren, können Muster erfassen, die einzelne Zeitkomponenten allein möglicherweise übersehen.

Lassen Sie uns nun Interaktionsfunktionen erstellen product:

hours_of_day = checklist(vary(24))
day_types = ("weekday", "weekend")
operational_shifts = ("off_peak", "on_peak")  # on_peak: 08:00–18:00

# Construct a full lookup grid for all mixtures
season_grid = checklist(itertools.product(hours_of_day, day_types, operational_shifts))
season_df = pd.DataFrame(season_grid, columns=("hour", "day_type", "shift"))

# Simulate anticipated baseline temperature per mixture
np.random.seed(14)
season_df("baseline_temp_c") = np.spherical(
    3.5
    + 0.8 * np.sin(2 * np.pi * season_df("hour") / 24)
    + np.the place(season_df("day_type") == "weekend", 0.3, 0.0)
    + np.the place(season_df("shift") == "on_peak", 0.5, 0.0)
    + np.random.regular(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 mixtures: {len(season_df)}")

Ausgabe:

hour day_type    shift  baseline_temp_c
   0  weekday off_peak            3.655
   0  weekday  on_peak            4.008
   0  weekend off_peak            3.817
   0  weekend  on_peak            4.293
   8  weekday off_peak            4.325
   8  weekday  on_peak            4.601
   8  weekend off_peak            4.446
   8  weekend  on_peak            4.978
  14  weekday off_peak            3.370
  14  weekday  on_peak            3.628
  14  weekend off_peak            3.279
  14  weekend  on_peak            3.959
  20  weekday off_peak            2.726
  20  weekday  on_peak            3.256
  20  weekend off_peak            3.056
  20  weekend  on_peak            3.530

Complete grid mixtures: 96

Dieses Raster wird wieder mit Ihrem Hauptdatensatz zusammengeführt baseline_temp_c Characteristic professional Zeile – jeder Lesung wird ein kontextbezogener erwarteter Wert zugewiesen. Die Abweichung von dieser Grundlinie, temperature_c - baseline_temp_cist dann eine nützliche Funktion zur Anomalieerkennung.

# 4. Extrahieren von Schiebefensterstatistiken mit tee

Manchmal müssen Sie dieselbe Sequenz durch mehrere statistische Linsen gleichzeitig verarbeiten – Mittelwert, Varianz, Änderungsrate –, ohne sie mehrmals zu durchlaufen. itertools.tee erstellt unabhängige Iteratoren aus einer einzigen Quelle, was genau das ist, was Sie brauchen.

def sliding_window_stats(sequence, window_size):
    """Compute imply, vary and rate-of-change over sliding home windows utilizing tee."""
    outcomes = ()
    it = iter(sequence)

    window = checklist(itertools.islice(it, window_size))
    if len(window) < window_size:
        return outcomes

    outcomes.append({
        "window_mean":    spherical(sum(window) / window_size, 4),
        "window_range":   spherical(max(window) - min(window), 4),
        "rate_of_change": spherical(window(-1) - window(0), 4),
    })

    for next_val in it:
        window = window(1:) + (next_val)

        # tee creates two unbiased iterators over the identical window
        iter_a, iter_b = itertools.tee(iter(window))

        values_a = checklist(iter_a)
        values_b = checklist(iter_b)

        mean_val = sum(values_a) / window_size
        outcomes.append({
            "window_mean":    spherical(mean_val, 4),
            "window_range":   spherical(max(values_b) - min(values_b), 4),
            "rate_of_change": spherical(window(-1) - window(0), 4),
        })

    return outcomes

power_readings = df("power_kw").tolist()
stats = sliding_window_stats(power_readings, window_size=8)

stats_df = pd.DataFrame(stats, index=df.index(7:))
stats_df("power_kw") = df("power_kw").iloc(7:).values

print(stats_df.iloc(0:8))

Ausgabe:

                     window_mean  window_range  rate_of_change  power_kw
timestamp
2024-03-01 07:00:00      41.4400          2.60            0.67     40.94
2024-03-01 08:00:00      43.7825         18.74           17.68     59.01
2024-03-01 09:00:00      46.1775         20.22           17.62     60.49
2024-03-01 10:00:00      47.9387         20.22           16.14     56.96
2024-03-01 11:00:00      49.9663         20.22           16.77     57.04
2024-03-01 12:00:00      52.2437         19.55           15.98     58.49
2024-03-01 13:00:00      54.3738         19.55           17.04     59.55
2024-03-01 14:00:00      56.6412         19.71           19.71     60.65

Wie gesehen, tee ermöglicht es Ihnen, denselben Fensteriterator in zwei separate Downstream-Berechnungen zu übergeben, ohne die Liste selbst zurückspulen oder kopieren zu müssen.

# 5. Kombination von Zeitfunktionen mit mehreren Auflösungen mit chain

Nützliche Zeitreihenfunktionen ergeben sich häufig aus mehreren zeitlichen Auflösungen gleichzeitig: dem rohen Stundenwert, einem gleitenden 6-Stunden-Mittelwert, einem gleitenden 24-Stunden-Mittelwert und einer Kalenderfunktion wie der Tageszeit. Diese befinden sich normalerweise in separaten Arrays und müssen zu einer übersichtlichen Funktionsliste zusammengefasst werden. Hier erfahren Sie, wie Sie es verwenden können chain um solche Funktionen zu kombinieren:

humidity = df("humidity_pct").tolist()

def rolling_means(sequence, window):
    means = ()
    for i in vary(len(sequence)):
        if i < window:
            means.append(None)
        else:
            w = checklist(itertools.islice(sequence, i - window, i))
            means.append(spherical(sum(w) / window, 3))
    return means

rolling_6h       = rolling_means(humidity, 6)
rolling_24h      = rolling_means(humidity, 24)
hour_of_day      = df.index.hour.tolist()
is_business_hour = (1 if 8 <= h <= 18 else 0 for h in hour_of_day)

# chain assembles characteristic identify checklist from logically grouped sublists
feature_names = checklist(itertools.chain(
    ("humidity_raw"),
    ("humidity_roll_6h", "humidity_roll_24h"),
    ("hour_of_day", "is_business_hour"),
))

multi_res_df = pd.DataFrame({
    identify: vals for identify, vals in zip(
        feature_names,
        (humidity, rolling_6h, rolling_24h, hour_of_day, is_business_hour)
    )
}, index=df.index)

print(multi_res_df.iloc(24:30))

Ausgabe:

                     humidity_raw  humidity_roll_6h  humidity_roll_24h  
timestamp
2024-03-02 00:00:00         78.45            79.622             78.055
2024-03-02 01:00:00         75.63            79.105             78.100
2024-03-02 02:00:00         77.51            78.190             78.062
2024-03-02 03:00:00         76.27            78.088             78.157
2024-03-02 04:00:00         74.96            77.805             78.240
2024-03-02 05:00:00         75.75            77.208             78.203

                     hour_of_day  is_business_hour
timestamp
2024-03-02 00:00:00            0                 0
2024-03-02 01:00:00            1                 0
2024-03-02 02:00:00            2                 0
2024-03-02 03:00:00            3                 0
2024-03-02 04:00:00            4                 0
2024-03-02 05:00:00            5                 0

chain Hier wird die Funktionsnamenliste aus logisch gruppierten Unterlisten zusammengestellt – Rohsensor, rollierende Mixture, Kalenderfunktionen. Wenn Ihr Funktionsumfang über mehr Sensorkanäle und mehr Auflösungen wächst, chain sorgt dafür, dass die Baugruppe lesbar und leicht zu erweitern ist.

# 6. Berechnen paarweiser zeitlicher Korrelationen mit mixtures

In einer Umgebung mit mehreren Sensoren enthalten die Beziehungen zwischen Variablen über die Zeit oft wertvolle Signale, die einzelne Messungen allein nicht erfassen können. Beispielsweise können gleichzeitige Erhöhungen über zwei Sensoren neue Bedingungen oder Wechselwirkungen aufdecken, die bei der isolierten Analyse jeder Serie nicht erkennbar wären.

Durch die Einbeziehung von Funktionen, die diese gemeinsame Dynamik widerspiegeln, kann die Fähigkeit eines Modells verbessert werden, subtile Muster und Abhängigkeiten zu erkennen. Versuchen wir, paarweise Korrelationen zu erstellen mixtures:

sensor_cols = ("temperature_c", "humidity_pct", "power_kw")
window_size = 12

pairwise_features = {}

for col_a, col_b in itertools.mixtures(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 vary(len(series_a)):
        if i < window_size:
            correlations.append(None)
            proceed

        win_a = checklist(itertools.islice(series_a, i - window_size, i))
        win_b = checklist(itertools.islice(series_b, i - window_size, i))

        mean_a = sum(win_a) / window_size
        mean_b = sum(win_b) / window_size

        cov   = sum((a - mean_a) * (b - mean_b) for a, b in zip(win_a, win_b)) / window_size
        std_a = (sum((a - mean_a)**2 for a in win_a) / window_size) ** 0.5
        std_b = (sum((b - mean_b)**2 for b in win_b) / window_size) ** 0.5

        corr = spherical(cov / (std_a * std_b), 4) if std_a > 0 and std_b > 0 else None
        correlations.append(corr)

    pairwise_features(feature_name) = correlations

corr_df = pd.DataFrame(pairwise_features, index=df.index)
print(corr_df.iloc(12:18))

Ausgabe:

                     corr_temp_humi_12h  corr_temp_powe_12h  
timestamp
2024-03-01 12:00:00             -0.6700             -0.2281
2024-03-01 13:00:00             -0.7208             -0.4960
2024-03-01 14:00:00             -0.7442             -0.6669
2024-03-01 15:00:00             -0.7678             -0.7076
2024-03-01 16:00:00             -0.8116             -0.7265
2024-03-01 17:00:00             -0.8368             -0.7482

                     corr_humi_powe_12h
timestamp
2024-03-01 12:00:00              0.5380
2024-03-01 13:00:00              0.6614
2024-03-01 14:00:00              0.7202
2024-03-01 15:00:00              0.7311
2024-03-01 16:00:00              0.7233
2024-03-01 17:00:00              0.7219

# 7. Laufende Grundlinien akkumulieren mit accumulate

Ein gegebener Wert kann unterschiedliche Bedeutung haben, je nachdem, wann er in einer Sequenz auftritt. Entscheidend ist die Abweichung von der sich entwickelnden Grundlinie – dem laufenden Mittel bis zu diesem Zeitpunkt. Mit einem inkrementellen Ansatz wie z accumulatekönnen Sie diesen laufenden Mittelwert effizient berechnen, ohne den gesamten Verlauf zu speichern.

readings = df("temperature_c").tolist()

running_sums   = checklist(itertools.accumulate(readings))
running_counts = checklist(itertools.accumulate((1) * len(readings)))
running_means  = (
    spherical(s / c, 4)
    for s, c in zip(running_sums, running_counts)
)

# Working max — highest temperature seen up to now, helpful for breach monitoring
running_max = checklist(itertools.accumulate(readings, func=max))

deviation_from_baseline = (
    spherical(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.iloc(20:28))

Ausgabe:

                     temperature_c  running_mean  running_max  
timestamp
2024-03-01 20:00:00          2.960        3.5857        5.192
2024-03-01 21:00:00          2.647        3.5430        5.192
2024-03-01 22:00:00          2.986        3.5188        5.192
2024-03-01 23:00:00          2.831        3.4902        5.192
2024-03-02 00:00:00          3.409        3.4869        5.192
2024-03-02 01:00:00          3.919        3.5035        5.192
2024-03-02 02:00:00          3.833        3.5157        5.192
2024-03-02 03:00:00          4.542        3.5524        5.192

                     deviation_from_baseline
timestamp
2024-03-01 20:00:00                  -0.6257
2024-03-01 21:00:00                  -0.8960
2024-03-01 22:00:00                  -0.5328
2024-03-01 23:00:00                  -0.6592
2024-03-02 00:00:00                  -0.0779
2024-03-02 01:00:00                   0.4155
2024-03-02 02:00:00                   0.3173
2024-03-02 03:00:00                   0.9896

# Zusammenfassung

Beim Time Collection Characteristic Engineering geht es im Wesentlichen um die Beschreibung Kontext – Was hat dieses Sign im Vergleich zu dem, was wir erwarten, bewirkt? Jede hier behandelte Funktion ist eine andere Möglichkeit, diese Frage in eine Zahl zu formalisieren, aus der ein Modell lernen kann.

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

itertools-Funktion Zeitreihenfunktion Beispiel
islice Verzögerungsfunktionen Temperatur vor 1h, 6h, 24h
islice + accumulate Rolling-Window-Statistiken 6 Stunden Mittelwert, Customary, Min., Max
product Saisonales Interaktionsraster Stunde × Tagestyp × Schichtgrundlinie
tee Parallele Fensterstatistiken Mittelwert + Bereich + Änderungsrate
chain Characteristic-Meeting mit mehreren Auflösungen Roh-, Roll- und Kalenderfunktionen
mixtures Paarweise Kreuzsensorkorrelationen Temperatur-Feuchtigkeit, Temperatur-Leistungs-Rollkorr
accumulate Laufende Grundlinie + Abweichung Abweichungserkennung vom historischen Mittelwert

Und da itertools auf Iteratorebene arbeitet, fügen sich alle diese Muster auch sauber in Streaming-Pipelines ein. Viel Spaß beim Characteristic-Engineering!

Bala Priya C ist ein Entwickler und technischer Redakteur aus Indien. Sie arbeitet gerne an der Schnittstelle von Mathematik, Programmierung, Datenwissenschaft und Inhaltserstellung. Zu ihren Interessen- und Fachgebieten gehören DevOps, Datenwissenschaft und Verarbeitung natürlicher Sprache. Sie liebt es zu lesen, zu schreiben, zu programmieren und Kaffee zu trinken! Derzeit arbeitet sie daran, zu lernen und ihr Wissen mit der Entwickler-Neighborhood zu teilen, indem sie Tutorials, Anleitungen, Meinungsbeiträge und mehr verfasst. Bala erstellt außerdem ansprechende Ressourcenübersichten und Programmier-Tutorials.



Von admin

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert