Prednáška 6 – Časové rady¶
V tejto prednáške sa oboznámime so základnými konceptmi časových radov (time series) a ako ich môžeme spracovávať v Pythone pomocou knižníc Pandas, NumPy a ďalších. Ukážeme si, ako pracovať s dátovými indexami reprezentujúcimi čas, ako vykonávať základné prehľady, vizualizácie, jednoduché analýzy a vybrané predspracovanie.
Obsah¶
- Základné pojmy a teória časových radov
- Načítanie dát a úprava indexu na čas (dataset
flights
) - Časové funkcie v Pandas, základná analýza a vizualizácia
- Vyhladzovanie, rolovanie a ďalšie predspracovanie
- Dekompozícia časového radu
- Rozšírené použitia a ukážky
- Záver
1. Základné pojmy a teória časových radov¶
Čo je časový rad?¶
Časový rad (time series) je postupnosť meraní (hodnôt, pozorovaní) usporiadaných podľa času, zvyčajne v pravidelných intervaloch (denne, týždenne, mesačne, ročne a pod.). Typické príklady:
- Denné uzatváracie ceny akciového indexu (napr. S&P 500),
- Počty pasažierov lietadla zaznamenané mesačne,
- Denná teplota meraná na meteorologickej stanici,
- Ekonomické indikátory (HDP, inflácia) na ročnej báze.
Dôležité charakteristiky časových radov¶
- Trend – dlhodobé smerovanie dát (rastúci, klesajúci, stabilný).
- Sezónnosť – pravidelné periodické výkyvy (napr. v priebehu roka, mesiaca).
- Stacionarita – časový rad je stacionárny, ak jeho štatistické charakteristiky (napr. priemer, rozptyl) ostávajú konštantné v čase. Pri nelineárnych trendoch či sezónnosti rad často nie je stacionárny.
- Autokorelácia – do akej miery je hodnota v čase t korelovaná s hodnotami v čase t–1, t–2, ... (využíva sa pri ARIMA modeloch a iných metódach).
Typické úlohy s časovými radmi¶
- Predikcia (forecasting) – odhad budúcich hodnôt na základe historických dát,
- Dekompozícia – rozklad na trend, sezónu, rezíduá,
- Detekcia anomálií – hľadanie nezvyčajných hodnôt,
- Filtrácia a vyhladzovanie – potlačenie šumu, zviditeľnenie trendov,
- Resampling – zmena frekvencie (napr. denné dáta na mesačné).
2. Načítanie dát a úprava indexu na čas (dataset flights
)¶
V tejto ukážke využijeme vstavaný dataset flights
z knižnice Seaborn, ktorý obsahuje mesačné údaje o počte pasažierov lietadla (fiktívny alebo historický záznam) v rokoch 1949 – 1960. Toto je veľmi známy príklad pre demonštráciu časových radov.
- Načítame dataset
flights
pomocousns.load_dataset('flights')
. - Dataset obsahuje stĺpce:
year
(rok: 1949, 1950, ...),month
(názov mesiaca: January, February, ...),passengers
(počet pasažierov v danom mesiaci).
- Vytvoríme z neho datetime index tak, že spojíme stĺpce
year
amonth
do jedného dátumu (napr. 1. january 1949). Potom tento dátum nastavíme ako index.
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
# 1. Načítanie datasetu flights
df = sns.load_dataset("flights")
print("Rozmery datasetu:", df.shape)
df.head()
Rozmery datasetu: (144, 3)
year | month | passengers | |
---|---|---|---|
0 | 1949 | Jan | 112 |
1 | 1949 | Feb | 118 |
2 | 1949 | Mar | 132 |
3 | 1949 | Apr | 129 |
4 | 1949 | May | 121 |
df["month"].dtype
CategoricalDtype(categories=['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], , ordered=False, categories_dtype=object)
Ďalej spojíme year
a month
do formátu dátumu. Napr. povieme, že pre každý záznam definujeme prvý deň v danom mesiaci (-01
).
# Prekonvertujeme month na jeho skrátený formát (Jan, Feb ...), aby sme mohli použiť %b
df['month_str'] = df['month'].str.slice(0,3)
# Vytvoríme reťazec s formátom YYYY-MMM-01, napr. "1949-Jan-01"
df['date_str'] = df['year'].astype(str) + '-' + df['month_str'] + '-01'
df['date'] = pd.to_datetime(df['date_str'], format="%Y-%b-%d")
df.drop(columns=['month_str','date_str'], inplace=True)
df.head()
year | month | passengers | date | |
---|---|---|---|---|
0 | 1949 | Jan | 112 | 1949-01-01 |
1 | 1949 | Feb | 118 | 1949-02-01 |
2 | 1949 | Mar | 132 | 1949-03-01 |
3 | 1949 | Apr | 129 | 1949-04-01 |
4 | 1949 | May | 121 | 1949-05-01 |
Teraz stĺpec date
obsahuje reálny datetime
. Index nastavíme na tento dátum:
df_ts = df.set_index('date').drop(columns=['year','month'])
df_ts.head()
passengers | |
---|---|
date | |
1949-01-01 | 112 |
1949-02-01 | 118 |
1949-03-01 | 132 |
1949-04-01 | 129 |
1949-05-01 | 121 |
df_ts
teraz obsahuje index typu DatetimeIndex
, v stĺpcoch je len passengers
.
Pre úplnosť overíme:
df_ts.index
DatetimeIndex(['1949-01-01', '1949-02-01', '1949-03-01', '1949-04-01', '1949-05-01', '1949-06-01', '1949-07-01', '1949-08-01', '1949-09-01', '1949-10-01', ... '1960-03-01', '1960-04-01', '1960-05-01', '1960-06-01', '1960-07-01', '1960-08-01', '1960-09-01', '1960-10-01', '1960-11-01', '1960-12-01'], dtype='datetime64[ns]', name='date', length=144, freq=None)
3. Časové funkcie v Pandas, základná analýza a vizualizácia¶
Časové funkcie v Pandas¶
Pandas poskytuje množstvo funkcií špeciálne navrhnutých pre časové rady:
resample(freq)
– umožňuje zmeniť frekvenciu dát (denné, mesačné, ročné, atď.). Napr.resample('A')
ročne,resample('M')
mesačne,resample('Q')
kvartálne,rolling(window=...)
– kĺzavé (rolling) štatistiky na vyhladzovanie,shift(lag)
– posun časovej osi o určitý počet období,asfreq(freq)
– zmena frekvencie (v prípade chýbajúcich dát sa môžu pridať NaN),- string-based indexing – v
DatetimeIndex
môžeme vyberať data akodf_ts['1950']
(celý rok 1950) alebodf_ts['1950-03':'1951-05']
.
Základné analýzy¶
- Skontrolujme štatistiky
df_ts
. - Urobíme line plot.
- Vyskúšame aj výber údajov za konkrétny rok (napr. 1950).
# 1. Štatistiky
df_ts.describe()
passengers | |
---|---|
count | 144.000000 |
mean | 280.298611 |
std | 119.966317 |
min | 104.000000 |
25% | 180.000000 |
50% | 265.500000 |
75% | 360.500000 |
max | 622.000000 |
# 2. Line plot
plt.figure(figsize=(12,4))
plt.plot(df_ts.index, df_ts['passengers'], marker='o')
plt.title('Počet pasažierov (1949-1960)')
plt.show()
Vidíme jasne rastúci trend a sezónne výkyvy (v strede roka vyššie hodnoty).
Výber podľa string-based indexu¶
Napr. môžeme vybrať dáta len za rok 1950:
# Výber roku 1950
df_1950 = df_ts.loc['1950'] # funguje, ak index je DatetimeIndex
df_1950
passengers | |
---|---|
date | |
1950-01-01 | 115 |
1950-02-01 | 126 |
1950-03-01 | 141 |
1950-04-01 | 135 |
1950-05-01 | 125 |
1950-06-01 | 149 |
1950-07-01 | 170 |
1950-08-01 | 170 |
1950-09-01 | 158 |
1950-10-01 | 133 |
1950-11-01 | 114 |
1950-12-01 | 140 |
Alebo interval 1950-03
až 1951-05
:
# Interval
df_interval = df_ts.loc['1950-03':'1951-05']
df_interval
passengers | |
---|---|
date | |
1950-03-01 | 141 |
1950-04-01 | 135 |
1950-05-01 | 125 |
1950-06-01 | 149 |
1950-07-01 | 170 |
1950-08-01 | 170 |
1950-09-01 | 158 |
1950-10-01 | 133 |
1950-11-01 | 114 |
1950-12-01 | 140 |
1951-01-01 | 145 |
1951-02-01 | 150 |
1951-03-01 | 178 |
1951-04-01 | 163 |
1951-05-01 | 172 |
Týmto spôsobom môžeme veľmi pohodlne pracovať s časovými výsekmi.
df_ts['ma_12'] = df_ts['passengers'].rolling(window=12).mean()
plt.figure(figsize=(12,5))
plt.plot(df_ts['passengers'], label='Počet pasažierov')
plt.plot(df_ts['ma_12'], label='Kĺzavý priemer (12 mes.)', color='red')
plt.title('Moving Average vyhladzovanie')
plt.legend()
plt.show()
Exponenciálne vyhladzovanie¶
Ďalší prístup je exponentially weighted moving average (EWMA):
df_ts['ewm_alpha_03'] = df_ts['passengers'].ewm(alpha=0.1).mean()
plt.figure(figsize=(12,5))
plt.plot(df_ts['passengers'], label='Počet pasažierov')
plt.plot(df_ts['ewm_alpha_03'], label='EWMA (alpha=0.3)', color='green')
plt.title('Exponenciálne vyhladzovanie')
plt.legend()
plt.show()
Iné predspracovanie: difference, shift¶
df_ts['diff_1'] = df_ts['passengers'].diff(1)
– prvý rozdiel (hodnota v čase t minus hodnota v čase t–1), používa sa napr. pri stacionarizácii,df_ts['shift_1'] = df_ts['passengers'].shift(1)
– posun hodnôt o 1 obdobie dopredu/dozadu, užitočné pri tvorbe lagových atribútov.
# Príklad difference a shift
df_ts['diff_1'] = df_ts['passengers'].diff(1)
df_ts['shift_1'] = df_ts['passengers'].shift(1)
df_ts.head(15)
passengers | ma_12 | ewm_alpha_03 | diff_1 | shift_1 | |
---|---|---|---|---|---|
date | |||||
1949-01-01 | 112 | NaN | 112.000000 | NaN | NaN |
1949-02-01 | 118 | NaN | 115.157895 | 6.0 | 112.0 |
1949-03-01 | 132 | NaN | 121.372694 | 14.0 | 118.0 |
1949-04-01 | 129 | NaN | 123.590579 | -3.0 | 132.0 |
1949-05-01 | 121 | NaN | 122.957974 | -8.0 | 129.0 |
1949-06-01 | 135 | NaN | 125.527987 | 14.0 | 121.0 |
1949-07-01 | 148 | NaN | 129.835420 | 13.0 | 135.0 |
1949-08-01 | 148 | NaN | 133.024803 | 0.0 | 148.0 |
1949-09-01 | 136 | NaN | 133.510486 | -12.0 | 148.0 |
1949-10-01 | 119 | NaN | 131.282633 | -17.0 | 136.0 |
1949-11-01 | 104 | NaN | 127.306671 | -15.0 | 119.0 |
1949-12-01 | 118 | 126.666667 | 126.009701 | 14.0 | 104.0 |
1950-01-01 | 115 | 126.916667 | 124.533501 | -3.0 | 118.0 |
1950-02-01 | 126 | 127.583333 | 124.723651 | 11.0 | 115.0 |
1950-03-01 | 141 | 128.333333 | 126.773288 | 15.0 | 126.0 |
Rozdiel diff_1
nám hovorí, o koľko sa zmenil počet pasažierov oproti predchádzajúcemu mesiacu. Posun shift_1
ukazuje hodnotu pasažierov z minulého mesiaca na riadku aktuálneho mesiaca.
5. Dekompozícia časového radu¶
Dekompozícia nám pomáha rozložiť časový rad na trend, sezónnu zložku a rezíduá. Ukážeme si to pomocou funkcie seasonal_decompose
z statsmodels.tsa.seasonal
.
- Musíme mať DatetimeIndex a definovanú frekvenciu (napr. mesačnú).
- Zavoláme
seasonal_decompose(ts_data, model='additive', period=12)
.
from statsmodels.tsa.seasonal import seasonal_decompose
# Nastavíme si mesačnú frekvenciu (ak tam nie je)
ts_data = df_ts['passengers'].asfreq('MS') # MS = month start
decomp_result = seasonal_decompose(ts_data, model='additive', period=12)
fig = decomp_result.plot()
fig.set_size_inches(10,6)
plt.tight_layout()
plt.show()
Výsledok zvyčajne obsahuje štyri panely:
- Observed – pôvodný časový rad,
- Trend – dlhodobý vývoj,
- Seasonal – periodické kolísanie v rámci jednej periódy (12 mesiacov),
- Residual – zostatky po odčítaní trendu a sezónnosti.
# Ročný súčet
df_annual = df_ts['passengers'].resample('A').sum()
df_annual
/tmp/ipykernel_39/1105813642.py:2: FutureWarning: 'A' is deprecated and will be removed in a future version, please use 'YE' instead. df_annual = df_ts['passengers'].resample('A').sum()
date 1949-12-31 1520 1950-12-31 1676 1951-12-31 2042 1952-12-31 2364 1953-12-31 2700 1954-12-31 2867 1955-12-31 3408 1956-12-31 3939 1957-12-31 4421 1958-12-31 4572 1959-12-31 5140 1960-12-31 5714 Freq: YE-DEC, Name: passengers, dtype: int64
Ak to vykreslíme:
df_annual.plot(marker='o', figsize=(8,4), title='Ročný súčet pasažierov')
plt.show()
Vidíme jasne rastúcu líniu.
Čiastočná autokorelácia¶
Pri pokročilejšom modelovaní (napr. ARIMA, SARIMA) by sme mohli skúmať autokoreláciu a čiastočnú autokoreláciu. Tieto funkcie ponúka statsmodels.graphics.tsaplots
(napr. plot_acf
, plot_pacf
).
Nie je to predmetom tejto prednášky, ale ilustračne:
from statsmodels.graphics.tsaplots import plot_acf, plot_pacf
# Autokorelácia - ACF
fig, ax = plt.subplots(2,1, figsize=(10,6))
plot_acf(ts_data, ax=ax[0], lags=20)
ax[0].set_title('ACF - autokorelácia')
# Čiastočná autokorelácia - PACF
plot_pacf(ts_data, ax=ax[1], lags=20)
ax[1].set_title('PACF - čiastočná autokorelácia')
plt.tight_layout()
plt.show()
Vidíme, že dáta majú silnú sezónnu komponentu, a korelované hodnoty na viacerých oneskoreniach.
7. Záver¶
V tejto prednáške sme si ukázali:
- Čo sú časové rady a aké majú typické charakteristiky (trend, sezónnosť, stacionarita, autokorelácia),
- Ako načítať a upraviť dataset
flights
zo Seaborn do formy time-series DataFrame (s datetime indexom), - Základné Pandas funkcie pre časové rady (
resample
,rolling
,ewm
,shift
,diff
), - Dekompozíciu na trend, sezónu a rezíduá,
- Rozšírené použitia ako resampling na inú frekvenciu, autokorelácia a pod.
Ďalšie zdroje¶
- Dokumentácia Pandas Time Series
- Statsmodels TSA (Time Series Analysis)
- Autokorelácia a čiastočná autokorelácia (ACF, PACF)
Toto bol len základný prehľad. V ďalších krokoch by sme mohli hlbšie preskúmať sezónnosť, využiť stacionarizáciu (napr. differencing) a vyskúšať predikčné modely.
Prajeme úspešné experimenty s časovými radmi!¶