{count, plural, one {Corso} other {Corsi}}
Introduzione
La media mobile, detta anche media scorrevole o mobile, viene usata per analizzare dati di serie temporali calcolando le medie di diversi sottoinsiemi del dataset completo. Poiché comporta il calcolo della media del dataset nel tempo, è chiamata anche media mobile semplice (MM) o rolling mean.
Esistono vari modi per calcolare la media mobile; uno consiste nel prendere un sottoinsieme fisso da un’intera serie di numeri. La prima media mobile si calcola facendo la media del primo sottoinsieme fisso di numeri, poi il sottoinsieme viene modificato spostandosi in avanti al successivo sottoinsieme fisso (includendo il valore futuro nel sottogruppo ed escludendo il numero precedente dalla serie).
La media mobile si usa soprattutto con dati di serie temporali per catturare le fluttuazioni di breve periodo concentrandosi allo stesso tempo sui trend di lungo periodo.
Alcuni esempi di dati di serie temporali sono i prezzi azionari, i bollettini meteo, la qualità dell’aria, il prodotto interno lordo, l’occupazione, ecc.
In generale, la media mobile rende i dati più omogenei.
La media mobile è alla base di molti algoritmi; uno di questi è il modello ARIMA (Autoregressive Integrated Moving Average), che usa le medie mobili per fare previsioni sulle serie temporali.
Esistono diversi tipi di medie mobili:
-
Simple Moving Average (SMA): la Simple Moving Average (SMA) usa una finestra scorrevole per calcolare la media su un numero fissato di periodi temporali. È una media pesata in modo uniforme dei precedenti dati n.
Per capire meglio la SMA, prendiamo come esempio una sequenza di valori n:

l’allineamento della media mobile a pesi uguali per n punti dati sarà essenzialmente la media dei precedenti M punti dati, dove M è la dimensione della finestra scorrevole:

Allo stesso modo, per calcolare i valori successivi della media mobile, si aggiunge un nuovo valore alla somma e si elimina il valore del periodo precedente; dato che hai già la media dei periodi precedenti, non è necessaria la somma completa ogni volta:

- Cumulative Moving Average (CMA): A differenza della semplice media mobile, che elimina l’osservazione più vecchia quando ne viene aggiunta una nuova, la cumulative moving average considera tutte le osservazioni precedenti. La CMA non è una tecnica molto efficace per analizzare i trend e levigare i dati. Il motivo è che fa la media di tutti i dati precedenti fino al punto dati corrente, quindi una media pesata uniformemente della sequenza di n valori:
fino al tempo corrente è data da:
Analogamente, per aggiornare la media cumulativa a ogni nuovo valore in arrivo si può usare la seguente formula:

- Exponential Moving Average (EMA): A differenza di SMA e CMA, la media mobile esponenziale attribuisce più peso ai prezzi recenti e, di conseguenza, può essere un modello migliore o catturare il movimento del trend in modo più rapido. La reattività dell’EMA è direttamente proporzionale all’andamento dei dati.
Poiché le EMA danno un peso maggiore ai dati recenti rispetto a quelli più vecchi, sono più sensibili alle ultime variazioni di prezzo rispetto alle SMA; questo rende i risultati delle EMA più tempestivi e quindi l’EMA è spesso preferita ad altre tecniche.
Basta teoria, giusto? Passiamo all’implementazione pratica della media mobile.
Implementare la media mobile su dati di serie temporali
Simple Moving Average (SMA)
Per prima cosa, creiamo dei dati fittizi di serie temporale e proviamo a implementare la SMA usando solo Python.
Supponiamo che ci sia una domanda per un prodotto osservata per 12 mesi (1 anno) e che tu debba trovare le medie mobili con finestre di 3 e 4 mesi.
Importa i moduli
import pandas as pd
import numpy as np
product = {'month' : [1,2,3,4,5,6,7,8,9,10,11,12],'demand':[290,260,288,300,310,303,329,340,316,330,308,310]}
df = pd.DataFrame(product)
df.head()
Esegui e modifica il codice da questo tutorial online
Esegui codice| month | demand | |
|---|---|---|
| 0 | 1 | 290 |
| 1 | 2 | 260 |
| 2 | 3 | 288 |
| 3 | 4 | 300 |
| 4 | 5 | 310 |
Calcoliamo la SMA con una finestra di 3: significa che prenderai in considerazione tre valori ogni volta per calcolare la media mobile e, per ogni nuovo valore, quello più vecchio verrà ignorato.
Per implementarlo, userai la funzione iloc di pandas: poiché ti serve la colonna demand, fisserai la posizione di quella nella funzione iloc, mentre la riga sarà una variabile i che itererai fino a raggiungere la fine del dataframe.
for i in range(0,df.shape[0]-2):
df.loc[df.index[i+2],'SMA_3'] = np.round(((df.iloc[i,1]+ df.iloc[i+1,1] +df.iloc[i+2,1])/3),1)
| month | demand | SMA_3 | |
|---|---|---|---|
| 0 | 1 | 290 | NaN |
| 1 | 2 | 260 | NaN |
| 2 | 3 | 288 | 279.3 |
| 3 | 4 | 300 | 282.7 |
| 4 | 5 | 310 | 299.3 |
Per un controllo rapido, usiamo anche la funzione integrata rolling di pandas e verifichiamo se coincide con la nostra media mobile semplice implementata in Python.
df['pandas_SMA_3'] = df.iloc[:,1].rolling(window=3).mean()
df.head()
| month | demand | SMA_3 | pandas_SMA_3 | |
|---|---|---|---|---|
| 0 | 1 | 290 | NaN | NaN |
| 1 | 2 | 260 | NaN | NaN |
| 2 | 3 | 288 | 279.3 | 279.333333 |
| 3 | 4 | 300 | 282.7 | 282.666667 |
| 4 | 5 | 310 | 299.3 | 299.333333 |
Ottimo: come puoi vedere, le medie mobili personalizzate e quelle di pandas coincidono esattamente, il che significa che la tua implementazione della SMA è corretta.
Calcoliamo anche rapidamente la media mobile semplice per una window_size di 4.
for i in range(0,df.shape[0]-3):
df.loc[df.index[i+3],'SMA_4'] = np.round(((df.iloc[i,1]+ df.iloc[i+1,1] +df.iloc[i+2,1]+df.iloc[i+3,1])/4),1)
df.head()
| month | demand | SMA_3 | pandas_SMA_3 | SMA_4 | |
|---|---|---|---|---|---|
| 0 | 1 | 290 | NaN | NaN | NaN |
| 1 | 2 | 260 | NaN | NaN | NaN |
| 2 | 3 | 288 | 279.3 | 279.333333 | NaN |
| 3 | 4 | 300 | 282.7 | 282.666667 | 284.5 |
| 4 | 5 | 310 | 299.3 | 299.333333 | 289.5 |
df['pandas_SMA_4'] = df.iloc[:,1].rolling(window=4).mean()
df.head()
| month | demand | SMA_3 | pandas_SMA_3 | SMA_4 | pandas_SMA_4 | |
|---|---|---|---|---|---|---|
| 0 | 1 | 290 | NaN | NaN | NaN | NaN |
| 1 | 2 | 260 | NaN | NaN | NaN | NaN |
| 2 | 3 | 288 | 279.3 | 279.333333 | NaN | NaN |
| 3 | 4 | 300 | 282.7 | 282.666667 | 284.5 | 284.5 |
| 4 | 5 | 310 | 299.3 | 299.333333 | 289.5 | 289.5 |
Ora traccerai i dati delle medie mobili che hai calcolato.
import matplotlib.pyplot as plt
%matplotlib inline
plt.figure(figsize=[15,10])
plt.grid(True)
plt.plot(df['demand'],label='data')
plt.plot(df['SMA_3'],label='SMA 3 Months')
plt.plot(df['SMA_4'],label='SMA 4 Months')
plt.legend(loc=2)
<matplotlib.legend.Legend at 0x11fe15080>

Media mobile cumulativa
Direi che ora siamo pronti per passare a un dataset reale.
Per la media mobile cumulativa, usiamo un air quality dataset che puoi scaricare da questo link.
df = pd.read_csv("AirQualityUCI/AirQualityUCI.csv", sep = ";", decimal = ",")
df = df.iloc[ : , 0:14]
df.head()
| Date | Time | CO(GT) | PT08.S1(CO) | NMHC(GT) | C6H6(GT) | PT08.S2(NMHC) | NOx(GT) | PT08.S3(NOx) | NO2(GT) | PT08.S4(NO2) | PT08.S5(O3) | T | RH | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 10/03/2004 | 18.00.00 | 2.6 | 1360.0 | 150.0 | 11.9 | 1046.0 | 166.0 | 1056.0 | 113.0 | 1692.0 | 1268.0 | 13.6 | 48.9 |
| 1 | 10/03/2004 | 19.00.00 | 2.0 | 1292.0 | 112.0 | 9.4 | 955.0 | 103.0 | 1174.0 | 92.0 | 1559.0 | 972.0 | 13.3 | 47.7 |
| 2 | 10/03/2004 | 20.00.00 | 2.2 | 1402.0 | 88.0 | 9.0 | 939.0 | 131.0 | 1140.0 | 114.0 | 1555.0 | 1074.0 | 11.9 | 54.0 |
| 3 | 10/03/2004 | 21.00.00 | 2.2 | 1376.0 | 80.0 | 9.2 | 948.0 | 172.0 | 1092.0 | 122.0 | 1584.0 | 1203.0 | 11.0 | 60.0 |
| 4 | 10/03/2004 | 22.00.00 | 1.6 | 1272.0 | 51.0 | 6.5 | 836.0 | 131.0 | 1205.0 | 116.0 | 1490.0 | 1110.0 | 11.2 | 59.6 |
Il preprocessing è uno step essenziale quando lavori con i dati. Per i dati numerici, uno dei passaggi più comuni è controllare la presenza di valori NaN (Null). Se ci sono valori NaN, puoi sostituirli con 0, con la media, con i valori precedenti o successivi, o persino eliminarli. Sebbene la sostituzione sia di solito preferibile alla rimozione, dato che in questo dataset i valori NULL sono pochi, eliminarli non comprometterà la continuità della serie.
df.isna().sum()
Date 114
Time 114
CO(GT) 114
PT08.S1(CO) 114
NMHC(GT) 114
C6H6(GT) 114
PT08.S2(NMHC) 114
NOx(GT) 114
PT08.S3(NOx) 114
NO2(GT) 114
PT08.S4(NO2) 114
PT08.S5(O3) 114
T 114
RH 114
dtype: int64
Dal risultato sopra puoi notare che ci sono circa 114 valori NaN in tutte le colonne; tuttavia, scoprirai che sono tutti alla fine della serie temporale, quindi rimuoviamoli velocemente.
df.dropna(inplace=True)
df.isna().sum()
Date 0
Time 0
CO(GT) 0
PT08.S1(CO) 0
NMHC(GT) 0
C6H6(GT) 0
PT08.S2(NMHC) 0
NOx(GT) 0
PT08.S3(NOx) 0
NO2(GT) 0
PT08.S4(NO2) 0
PT08.S5(O3) 0
T 0
RH 0
dtype: int64
Applicherai la media mobile cumulativa alla colonna Temperature (T), quindi separiamo rapidamente quella colonna dal resto dei dati.
df_T = pd.DataFrame(df.iloc[:,-2])
df_T.head()
| T | |
|---|---|
| 0 | 13.6 |
| 1 | 13.3 |
| 2 | 11.9 |
| 3 | 11.0 |
| 4 | 11.2 |
Ora userai il metodo expanding di pandas per trovare la media cumulativa dei dati sopra. Se ricordi dall’introduzione, a differenza della media mobile semplice, la media mobile cumulativa considera tutti i valori precedenti nel calcolo della media.
df_T['CMA_4'] = df_T.expanding(min_periods=4).mean()
df_T.head(10)
| T | CMA_4 | |
|---|---|---|
| 0 | 13.6 | NaN |
| 1 | 13.3 | NaN |
| 2 | 11.9 | NaN |
| 3 | 11.0 | 12.450000 |
| 4 | 11.2 | 12.200000 |
| 5 | 11.2 | 12.033333 |
| 6 | 11.3 | 11.928571 |
| 7 | 10.7 | 11.775000 |
| 8 | 10.7 | 11.655556 |
| 9 | 10.3 | 11.520000 |
I dati di serie temporale si tracciano rispetto al tempo, quindi uniamo le colonne di data e ora e convertiamole in un oggetto datetime. Per farlo userai il modulo datetime di Python (Fonte: Time Series Tutorial).
import datetime
df['DateTime'] = (df.Date) + ' ' + (df.Time)
df.DateTime = df.DateTime.apply(lambda x: datetime.datetime.strptime(x, '%d/%m/%Y %H.%M.%S'))
Modifichiamo l’indice del dataframe temperature con il datetime.
df_T.index = df.DateTime
Ora tracciamo la temperatura reale e la media mobile cumulativa rispetto al tempo.
plt.figure(figsize=[15,10])
plt.grid(True)
plt.plot(df_T['T'],label='temperature')
plt.plot(df_T['CMA_4'],label='CMA_4')
plt.legend(loc=2)
<matplotlib.legend.Legend at 0x1210a2d30>

Exponential Moving Average
df_T['EMA'] = df_T.iloc[:,0].ewm(span=40,adjust=False).mean()
df_T.head()
| T | CMA_4 | EMA | |
|---|---|---|---|
| DateTime | |||
| 2004-03-10 18:00:00 | 13.6 | NaN | 13.600000 |
| 2004-03-10 19:00:00 | 13.3 | NaN | 13.585366 |
| 2004-03-10 20:00:00 | 11.9 | NaN | 13.503153 |
| 2004-03-10 21:00:00 | 11.0 | 12.45 | 13.381048 |
| 2004-03-10 22:00:00 | 11.2 | 12.20 | 13.274655 |
plt.figure(figsize=[15,10])
plt.grid(True)
plt.plot(df_T['T'],label='temperature')
plt.plot(df_T['CMA_4'],label='CMA_4')
plt.plot(df_T['EMA'],label='EMA')
plt.legend(loc=2)
<matplotlib.legend.Legend at 0x14b2a41d0>

Wow! Come puoi osservare dal grafico sopra, la Exponential Moving Average (EMA) fa un ottimo lavoro nel catturare il pattern dei dati, mentre la Cumulative Moving Average (CMA) resta indietro in misura considerevole.
Vai oltre!
Congratulazioni per aver completato il tutorial.
Questo tutorial è stato un buon punto di partenza per capire come calcolare le medie mobili dei tuoi dati e dar loro un senso.
Prova a scrivere il codice Python per la media mobile cumulativa ed esponenziale senza usare la libreria pandas. Ti darà una comprensione più profonda di come vengono calcolate e in che modo differiscono tra loro.
C’è ancora molto da sperimentare. Prova a calcolare la correlazione parziale tra i dati in ingresso e la media mobile, e cerca di trovare qualche relazione tra i due.
Se vuoi saperne di più sui DataFrame in pandas, segui il corso interattivo pandas Foundations di DataCamp.
Riferimenti:
Per qualsiasi domanda su questo tutorial, scrivi pure nei commenti qui sotto.

