Datumswerte und Zeitreihen
Kurz gesagt: Eine Zeitreihe sind Daten, die nach Datum geordnet sind; in pandas wird Text mit to_datetime zu einem Datum, resample fasst sie nach Tag, Woche und Monat zusammen, und rolling liefert einen gleitenden Durchschnitt.
Problem
„Wie läuft das Jahr?“
Der Shop hat die Bestellungen eines ganzen Jahres: von November 2025 bis Ende Oktober 2026. Der Chef fragt, welcher Monat der stärkste ist, an welchem Wochentag am meisten verkauft wird und wie sich die Einnahmen von Monat zu Monat ändern.
Es sind 11.116 Bestellungen. Aber die Spalte ordered_at ist str: Für pandas ist „2025-11-01 07:20“ nur Text. Den Monat kannst du daraus nicht nehmen:
Die Idee
Der Text wird zum Datum
parse_dates sagt read_csv, welche Spalten als Datumswerte gelesen werden. pd.to_datetime(orders['ordered_at']) macht dasselbe mit einer vorhandenen Spalte, wie du es in der Lektion über Typen gesehen hast:
Der Typ ist datetime64: ein Zeitpunkt, mit Datum und Uhrzeit. Damit kannst du vergleichen, subtrahieren und Teile entnehmen. Der Unterschied zwischen der letzten und der ersten Bestellung ist 364 Tage, 15 Stunden und 41 Minuten. Das ist ein Zeitunterschied (Timedelta), keine Zahl.
Eine Tabelle, in der jede Zeile ein Zeitpunkt oder ein Zeitraum ist und die Zeilen der Zeit folgen, heißt Zeitreihe.
Die Idee
Teile des Datums: .dt
.dt liefert die Teile jedes Datums in der Spalte: year, month, day, hour, dayofweek (0 ist Montag), day_name(). Wann am Tag wird am meisten bestellt?
Es gibt zwei Gipfel: mittags (zwischen 12 und 14 Uhr, etwa 800 Bestellungen pro Stunde) und abends (zwischen 19 und 21 Uhr, etwa 1.200 pro Stunde). Morgens und spät am Abend sind es weniger als halb so viele.
day_name() liefert die Namen auf Englisch („Saturday“). Die deutschen Namen setzt du gleich selbst mit einem Dictionary ein, etwas weiter unten.
Die Idee
Filtern nach Datum
Eine Datumsspalte kann mit Text wie „2025-12-01“ verglichen werden: pandas macht daraus ein Datum. Die Bestellungen im Dezember:
Die richtige Antwort ist 1285. Mit <= '2025-12-31' bekommst du 1250, und die letzte Bestellung darunter ist vom 30. Dezember. Der Grund ist die Uhrzeit: „2025-12-31“ ist der 31. Dezember um 00:00, und jede Bestellung am 31. kommt nach Mitternacht.
Für einen Zeitraum schreibst du also „ab dem ersten Tag, eingeschlossen, bis zum ersten Tag des nächsten Zeitraums, ausgeschlossen“: >= '2025-12-01' und < '2026-01-01'.
Probier es aus
Die Woche vor Weihnachten
Wie viele Bestellungen gibt es vom 22. bis 28. Dezember 2025 einschließlich? Leg diese Bestellungen in week ab.
Lösung
import pandas as pd
orders = pd.read_csv('year_orders.csv', parse_dates=['ordered_at'])
week = orders[(orders['ordered_at'] >= '2025-12-22') & (orders['ordered_at'] < '2025-12-29')]
print(len(week))orders[(orders["ordered_at"] >= "2025-12-22") & (orders["ordered_at"] < "2025-12-29")] liefert 309 Bestellungen. Mit <= "2025-12-28" bekommst du 279: Dir fehlt der ganze 28. Dezember, weil ein Datum ohne Uhrzeit Mitternacht zu Beginn des Tages ist.
Die Idee
Nach Monat: resample
Für die Einnahmen nach Monat brauchst du zuerst den Umsatz jeder Bestellung. Er ist die Summe ihrer Zeilen: groupby auf order_id, dann merge mit den Bestellungen. Stornierte Bestellungen sind kein Umsatz, also lässt sold sie weg.
Dann fasst resample die Zeilen in Zeiträume zusammen. Es ist ein groupby, dessen Gruppen Zeiträume sind: Tage ('D'), Wochen ('W'), Monate ('MS'). Es arbeitet auf einem Index aus Datumswerten, also zuerst set_index('ordered_at'):
Der Dezember ist der stärkste Monat: 15.915,90 €. Januar (7.228,20 €) und Februar (7.622,30 €) sind schwach, wie es nach den Feiertagen oft ist. Der schwächste ist aber der September: 7.128,80 €. Warum, finden wir im Projekt am Ende des Kurses heraus.
Genauer, für Neugierige
'MS' (month start) beschriftet jeden Monat mit seinem ersten Tag und 'ME' (month end) mit seinem letzten. Die Summen sind dieselben.
'W' beschriftet jede Woche mit dem Sonntag, an dem sie endet. Die erste Woche hier besteht nur aus dem 1. und 2. November (Samstag und Sonntag), also ist sie viel kleiner als die anderen. Der erste und der letzte Zeitraum sind oft unvollständig: Prüfe sie, bevor du sie vergleichst.
Wenn es an einem Tag keine Bestellungen gibt, liefert resample('D').sum() trotzdem die Zeile, mit 0. In diesen Daten gibt es an jedem Tag Bestellungen.
Probier es aus
Die stärkste Woche
Welche Woche hat am meisten Umsatz gebracht? Leg den Umsatz nach Woche (ohne die stornierten Bestellungen) in weekly ab und das Datum der stärksten Woche in best_week.
Lösung
import pandas as pd
orders = pd.read_csv('year_orders.csv', parse_dates=['ordered_at'])
items = pd.read_csv('year_items.csv')
items['revenue'] = items['quantity'] * items['unit_price']
per_order = items.groupby('order_id', as_index=False)['revenue'].sum()
orders = orders.merge(per_order, left_on='id', right_on='order_id', how='left', validate='one_to_one')
# Der Umsatz lässt die stornierten Bestellungen weg.
sold = orders[orders['status'] != 'cancelled']
weekly = sold.set_index('ordered_at')['revenue'].resample('W').sum()
best_week = weekly.idxmax()
print(best_week, weekly.max().round(2))sold.set_index("ordered_at")["revenue"].resample("W").sum() liefert 53 Wochen. idxmax() ist der 28. Dezember 2025: die Woche vom 22. bis 28. Dezember, mit 3.865,80 €. Wenn du die stornierten Bestellungen mitzählst, kommt dieselbe Woche auf 4.039,40 €. max() würde den Betrag liefern, nicht welche Woche es ist.
Die Idee
Gleitender Durchschnitt: rolling
Die Tageseinnahmen springen auf und ab: hoch an Freitagen, niedrig an Sonntagen. Hinter diesem Rauschen ist schwer zu sehen, ob es aufwärts geht. Ein gleitender Durchschnitt berechnet für jeden Tag den Durchschnitt der letzten 7 Tage, einschließlich dieses Tages:
rolling(7) ist ein Fenster von 7 Zeilen, das Zeile für Zeile nach unten gleitet. Die ersten 6 Werte sind NaN: Vor dem 7. November gibt es keine 7 Tage zurück. Am 7. November ist der Durchschnitt der ersten sieben Tage 374,23 €. Danach kommt mit jedem Tag ein neuer Tag hinein und der älteste geht heraus, also ändert sich die Linie sanft.
Die Idee
Wochentag
Wie hoch sind die durchschnittlichen Einnahmen an einem Montag, an einem Dienstag und so weiter? Du gruppierst die Tageseinnahmen nach der Nummer des Tages aus dem Index, daily.index.dayofweek, und nimmst den Mittelwert. Du gruppierst nach der Nummer und nicht nach day_name(), weil Namen alphabetisch sortiert werden und der Freitag zuerst käme. Dann gibt ein Dictionary (und map) den Nummern ihre Namen:
Der Freitag ist der stärkste: durchschnittlich 350,79 €. Der Sonntag ist der schwächste: 250,85 €. Das ist der Wochenrhythmus, den rolling(7) glättet: Ein 7-Tage-Fenster enthält jeden Wochentag genau einmal.
Hier besteht der Index schon aus Datumswerten, also ist dayofweek direkt auf dem Index richtig. Bei einer Spalte schreibst du .dt.dayofweek.
Die Idee
Monat zu Monat: pct_change
Der Chef fragt nicht „wie viel“, sondern „wie viel mehr oder weniger als im letzten Monat“. pct_change() teilt jeden Wert durch den davor und zieht 1 ab: Es liefert die Veränderung als Anteil. Mal 100 ist es ein Prozentsatz:
Der November ist NaN: Es gibt keinen Monat davor. Der Dezember liegt 71,2 % über dem November, und der Januar liegt 54,6 % unter dem Dezember. Der September liegt 19,1 % unter dem August, und der Oktober kommt um 39,2 % wieder hoch.
Ob der Einbruch im September echt oder nur Rauschen ist und was ihn verursacht hat, ist die Frage des Projekts. Für jetzt weißt du, dass er da ist und wie groß er ist.
Übung
Wann wurde der Kaffee teurer?
Die Kaffeebohnen (Produkt 17) wurden irgendwann im Jahr teurer. Leg in price_by_month den mittleren Preis des Kaffees für jeden Monat ab (in der zeitlichen Reihenfolge der Monate) und in first_new das Datum und die Uhrzeit des ersten Verkaufs zum neuen Preis von 14.9.
Lösung
import pandas as pd
orders = pd.read_csv('year_orders.csv', parse_dates=['ordered_at'])
items = pd.read_csv('year_items.csv')
coffee = items[items['product_id'] == 17]
coffee = coffee.merge(orders[['id', 'ordered_at']], left_on='order_id', right_on='id', validate='many_to_one')
price_by_month = coffee.set_index('ordered_at')['unit_price'].resample('MS').mean()
first_new = coffee[coffee['unit_price'] == 14.9]['ordered_at'].min()
print(price_by_month)
print(first_new)coffee.set_index("ordered_at")["unit_price"].resample("MS").mean() liefert 13,90 € von November bis Februar und 14,90 € ab März. Der erste Verkauf zu 14,90 € ist am 3. März 2026 um 18:00 Uhr, und der letzte zum alten Preis am 28. Februar. Mit groupby auf .dt.month verlieren die Monate ihr Jahr: November und Dezember 2025 rutschen hinter den Oktober 2026, und die zeitliche Reihenfolge geht verloren.
Test
Teste dich
Verwandte Lektionen
Abschnitt „Verwandte Lektionen“- Baut auf: Neue Spalten · Typen und fehlende Werte · Gruppen: groupby · Verbinden: merge
- Geht weiter in: Welches Diagramm für welche Frage · Ehrliche Diagramme · Eine Hypothese testen · Korrelation und Ursache
- Aufgaben zum Thema: Daten aufbereiten