Corso
Nella sessione Facebook Live del 4 gennaio, abbiamo esaminato i dati di Google Trends per le keyword "diet", "gym" e "finance" per vedere come variano nel tempo. Ci siamo chiesti se a gennaio, quando tutti cerchiamo di ricominciare da capo, ci fossero più ricerche per questi termini.
In questo tutorial, ripercorrerai passo dopo passo il codice che abbiamo messo insieme durante la sessione. Non farai molta matematica: l'enfasi sarà tutta sull'esplorazione visiva del dataset in questione.
Per approfondire pandas, dai un'occhiata al percorso Data Manipulation with Python di DataCamp. Per saperne di più sulle serie temporali con pandas, vedi il corso Manipulating Time Series Data in Python.
Importazione di pacchetti e dati
Resta quindi la domanda: a gennaio, quando tutti cerchiamo di ricominciare da capo, potrebbero esserci più ricerche per questi termini?
Scopriamolo andando qui e controllando i dati. Nota che questo tutorial è ispirato a questo articolo di FiveThirtyEight.
Puoi anche scaricare i dati in formato .csv, salvarli su file e importarli nel tuo ambiente Python per eseguire la tua analisi. Lo farai adesso. Andiamo!
Per iniziare, importerai alcuni pacchetti: in questo caso userai numpy, pandas, matplotlib e seaborn.
Inoltre, se vuoi che le immagini vengano visualizzate nel Jupyter Notebook, puoi usare la magic di IPython aggiungendo %matplotlib inline al tuo codice. In alternativa, puoi passare ai default di Seaborn con sns.set():
# Import packages
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline
sns.set()
Importa i dati che hai scaricato con .read_csv() e visualizza le prime righe con .head().
Nota che aggiungi l'argomento skiprows per saltare la prima riga all'inizio del file.
df = pd.read_csv('data/multiTimeline.csv', skiprows=1)
df.head()
| Month | diet: (Worldwide) | gym: (Worldwide) | finance: (Worldwide) | |
|---|---|---|---|---|
| 0 | 2004-01 | 100 | 31 | 48 |
| 1 | 2004-02 | 75 | 26 | 49 |
| 2 | 2004-03 | 67 | 24 | 47 |
| 3 | 2004-04 | 70 | 22 | 48 |
| 4 | 2004-05 | 72 | 22 | 43 |
Puoi anche usare il metodo .info() per controllare i tipi di dato, il numero di righe e altro ancora:
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 168 entries, 0 to 167
Data columns (total 4 columns):
Month 168 non-null object
diet: (Worldwide) 168 non-null int64
gym: (Worldwide) 168 non-null int64
finance: (Worldwide) 168 non-null int64
dtypes: int64(3), object(1)
memory usage: 5.3+ KB
Ora che hai importato i tuoi dati da Google Trends e dato un'occhiata veloce, è il momento di sistemarli e portarli nella forma che desideri per prepararli all'analisi.
Pulisci e prepara i dati
La prima cosa da fare è rinominare le colonne del tuo DataFrame df in modo che non contengano spazi. Ci sono vari modi per farlo, ma per ora riassegnerai a df.columns un elenco con i nomi che vuoi dare alle colonne.
Ricontrolla il risultato della riassegnazione chiamando df.head():
df.columns = ['month', 'diet', 'gym', 'finance']
df.head()
| month | diet | gym | finance | |
|---|---|---|---|---|
| 0 | 2004-01 | 100 | 31 | 48 |
| 1 | 2004-02 | 75 | 26 | 49 |
| 2 | 2004-03 | 67 | 24 | 47 |
| 3 | 2004-04 | 70 | 22 | 48 |
| 4 | 2004-05 | 72 | 22 | 43 |
Successivamente, trasformerai la colonna 'month' in un tipo DateTime e la imposterai come indice del DataFrame.
Nota che lo fai perché dal risultato del metodo .info() hai visto che la colonna 'Month' era in realtà di tipo object. Questo tipo generico racchiude di tutto, dalle stringhe agli interi, ecc. Non è esattamente quello che vuoi quando lavori con dati di serie temporali. Per questo userai .to_datetime() per convertire la colonna 'month' del tuo DataFrame in un DateTime.
Attenzione! Assicurati di includere l'argomento inplace quando imposti l'indice del DataFrame df in modo da modificare effettivamente l'indice originale e impostarlo sulla colonna 'month'.
df.month = pd.to_datetime(df.month)
df.set_index('month', inplace=True)
df.head()
| diet | gym | finance | |
|---|---|---|---|
| month | |||
| 2004-01-01 | 100 | 31 | 48 |
| 2004-02-01 | 75 | 26 | 49 |
| 2004-03-01 | 67 | 24 | 47 |
| 2004-04-01 | 70 | 22 | 48 |
| 2004-05-01 | 72 | 22 | 43 |
Ora è il momento di esplorare visivamente il tuo DataFrame.
Un po' di Exploratory Data Analysis (EDA)
Puoi usare il metodo di visualizzazione integrato di pandas .plot() per tracciare i dati come 3 grafici a linee in un'unica figura (uno per ciascuna colonna, ovvero 'diet', 'gym' e 'finance').
Nota che puoi anche specificare alcuni argomenti di questo metodo, come figsize, linewidth e fontsize per impostare rispettivamente dimensione della figura, spessore delle linee e dimensione dei caratteri del grafico.
Inoltre, noterai che ciò che vedi sull'asse x non sono i mesi, come suggerisce l'etichetta predefinita, ma gli anni. Per rendere il grafico un po' più accurato, specificherai l'etichetta sull'asse x come 'Year' e imposterai anche la dimensione del font a 20.
Suggerimento: se vuoi sopprimere l'output di Matplotlib, aggiungi semplicemente un punto e virgola ; all'ultima riga di codice!
df.plot(figsize=(20,10), linewidth=5, fontsize=20)
plt.xlabel('Year', fontsize=20);

Nota che questi dati sono relativi. Come si legge su Google Trends:
I numeri rappresentano l'interesse di ricerca relativo al punto più alto del grafico per l'area e il periodo indicati. Un valore di 100 è il picco di popolarità del termine. Un valore di 50 significa che il termine è popolare la metà. Allo stesso modo, un punteggio di 0 significa che il termine è stato meno dell'1% popolare rispetto al picco.
Se vuoi, puoi anche tracciare la sola colonna 'diet' come serie temporale:
df[['diet']].plot(figsize=(20,10), linewidth=5, fontsize=20)
plt.xlabel('Year', fontsize=20);

Nota: la prima cosa da notare è la stagionalità: ogni gennaio c'è un grande balzo. Inoltre, sembra esserci una tendenza: pare salire leggermente, poi scendere, poi risalire e poi ridiscendere. In altre parole, queste serie temporali sembrano avere componenti di trend e stagionali.
Tenendo questo a mente, imparerai come identificare i trend nelle tue serie temporali!
Trend e stagionalità nei dati di serie temporali
Identificare i trend nelle serie temporali
Ci sono diversi modi per identificare i trend nelle serie temporali. Un modo comune è calcolare una media mobile, cioè, per ogni punto temporale, calcolare la media dei punti che lo circondano. Il numero di punti è specificato da una ampiezza della finestra, che devi scegliere.
Poiché prendi la media, questo tende a smussare il rumore e la stagionalità. Ecco un esempio: guarda questa media mobile di 'diet' usando i metodi integrati di pandas.
Per scegliere l'ampiezza della finestra, qui ha senso provare prima dodici mesi, dato che stai parlando di stagionalità annuale.
diet = df[['diet']]
diet.rolling(12).mean().plot(figsize=(20,10), linewidth=5, fontsize=20)
plt.xlabel('Year', fontsize=20);

Nota che nel blocco di codice sopra hai usato due coppie di parentesi quadre per estrarre la colonna 'diet' come DataFrame; se avessi usato una sola coppia, come df['diet'], avresti creato una Series di pandas.
Nel blocco di codice qui sopra, hai anche concatenato metodi: hai chiamato metodi su un oggetto uno dopo l'altro. Il method chaining è piuttosto diffuso e pandas è uno dei pacchetti che permette davvero di usare al massimo questo stile di programmazione!
Ora hai il trend che cercavi! Hai rimosso gran parte della stagionalità rispetto al grafico precedente.
Puoi anche tracciare la media mobile di 'gym' usando i metodi integrati di pandas con la stessa finestra usata per i dati di 'diet':
gym = df[['gym']]
gym.rolling(12).mean().plot(figsize=(20,10), linewidth=5, fontsize=20)
plt.xlabel('Year', fontsize=20);

Hai rimosso con successo la stagionalità e vedi un trend crescente per "gym"! Ma come si confrontano questi due termini di ricerca?
Puoi capirlo tracciando i trend di 'gym' e 'diet' nella stessa figura:
df_rm = pd.concat([diet.rolling(12).mean(), gym.rolling(12).mean()], axis=1)
df_rm.plot(figsize=(20,10), linewidth=5, fontsize=20)
plt.xlabel('Year', fontsize=20);

Hai creato un nuovo DataFrame df_rm con due colonne contenenti la media mobile di 'diet' e 'gym'. Hai usato la funzione pd.concat(), che prende un elenco di colonne come primo argomento e, poiché vuoi concatenarle come colonne, hai aggiunto anche l'argomento axis impostato a 1.
Poi hai tracciato il DataFrame con il metodo plot(), proprio come prima! Dunque, rimuovendo la stagionalità, vedi che diet ha potenzialmente una qualche forma di stagionalità, mentre gym è in crescita!
Identificati i trend nei dati, è il momento di pensare alla stagionalità, ovvero la natura ripetitiva della serie temporale. Come hai visto all'inizio del tutorial, sembravano esserci sia componenti di trend sia componenti stagionali.
Pattern stagionali nei dati di serie temporali
Un modo per ragionare sulle componenti stagionali è rimuovere il trend da una serie temporale, così da indagare più facilmente la stagionalità. Per rimuovere il trend, puoi sottrarre il trend calcolato sopra (media mobile) dal segnale originale. Tuttavia, questo dipenderà da quanti punti hai mediato.
Un altro modo per rimuovere il trend è la "differenziazione", in cui guardi la differenza tra punti dati successivi (chiamata "differenziazione di primo ordine", perché consideri la differenza tra un punto e quello che lo precede).
Differenziazione di primo ordine
Puoi usare pandas e i metodi diff() e plot() per calcolare e tracciare la differenza di primo ordine della Series 'diet':
diet.diff().plot(figsize=(20,10), linewidth=5, fontsize=20)
plt.xlabel('Year', fontsize=20);

Vedi che hai rimosso gran parte del trend e puoi davvero scorgere i picchi di gennaio ogni anno. Ogni gennaio c'è un enorme picco di 20 o più punti percentuali sull'elemento di ricerca più alto che hai visto!
Nota: puoi anche eseguire la differenziazione di 2° ordine, ossia guardare la differenza tra un punto e i due che lo precedono, se il trend non è ancora completamente rimosso. Vedi qui per maggiori informazioni sulla differenziazione.
La differenziazione è molto utile per trasformare la tua serie temporale in una serie stazionaria. Senza entrare troppo nel dettaglio, una serie stazionaria è una serie le cui proprietà statistiche (come media e varianza) non cambiano nel tempo. Queste serie sono utili perché molti metodi di forecasting per serie temporali si basano sull'assunzione che la serie sia approssimativamente stazionaria.
Con tutto questo a disposizione, ora analizzerai la periodicità nella tua serie temporale guardando la sua funzione di autocorrelazione. Ma prima, faremo una breve deviazione sulla correlazione.
Periodicità e autocorrelazione
Una serie temporale è periodica se si ripete a intervalli equidistanti, ad esempio ogni 12 mesi.
Un altro modo di vederla è: se la serie ha un picco in un punto, allora avrà un picco 12 mesi dopo; se ha un minimo in un punto, avrà un minimo 12 mesi dopo.
Ancora un altro modo di pensarci è che la serie temporale è correlata con se stessa traslata di 12 mesi. Ciò significa che, se sposti la serie di 12 mesi avanti o indietro, in qualche modo si sovrappone a se stessa.
Considerare la correlazione di una serie con una versione traslata di se stessa è proprio il concetto di autocorrelazione.
Ci arriverai tra un attimo.
Per prima cosa, rinfreschiamoci la memoria sulla correlazione e adottiamo un approccio intuitivo al concetto!
Il coefficiente di correlazione tra due variabili cattura quanto sono linearmente correlate. Per capirlo, guarderai un esempio pratico con l'aiuto del dataset iris, che contiene misurazioni di fiori.
Per studiarlo più nel dettaglio, importerai il dataset iris da scikit-learn, lo trasformerai in un DataFrame e ne visualizzerai le prime righe con .head():
from sklearn import datasets
iris = datasets.load_iris()
df_iris = pd.DataFrame(data= np.c_[iris['data'], iris['target']],
columns= iris['feature_names'] + ['target'])
df_iris.head()
| sepal length (cm) | sepal width (cm) | petal length (cm) | petal width (cm) | target | |
|---|---|---|---|---|---|
| 0 | 5.1 | 3.5 | 1.4 | 0.2 | 0.0 |
| 1 | 4.9 | 3.0 | 1.4 | 0.2 | 0.0 |
| 2 | 4.7 | 3.2 | 1.3 | 0.2 | 0.0 |
| 3 | 4.6 | 3.1 | 1.5 | 0.2 | 0.0 |
| 4 | 5.0 | 3.6 | 1.4 | 0.2 | 0.0 |
Per capire questo dataset, ricorda che tutti i fiori hanno un sepalo e un petalo. Il sepalo racchiude i petali ed è tipicamente verde e simile a una foglia, mentre i petali sono solitamente colorati. La colonna 'target', che è la variabile target, è la specie dei fiori di iris, che può essere Versicolor, Virginica o Setosa. Nella tabella sopra, sono codificate come 0, 1 e 2.
Ora, per ragionare sulla correlazione, osserverai come la lunghezza del sepalo dei fiori di iris è correlata con la larghezza del sepalo. Per farlo, userai pandas o seaborn per costruire uno scatter plot di 'sepal length' contro 'sepal width':
sns.lmplot(x='sepal length (cm)', y='sepal width (cm)', fit_reg=False, data=df_iris);

Nota che hai disattivato la regressione lineare impostando l'argomento fit_reg a False.
La lunghezza e la larghezza del sepalo sono positivamente o negativamente correlate su tutti i fiori? E all'interno di ciascuna specie sono positivamente o negativamente correlate? Questa è una distinzione essenziale.
Ricorda che nel primo caso, se la lunghezza del sepalo aumenta, anche la larghezza del sepalo aumenta in modo lineare. Nel secondo caso, se la lunghezza del sepalo aumenta, la larghezza del sepalo diminuisce in modo lineare.
A prima vista, nel grafico sopra sembra esserci una correlazione negativa: all'aumentare della lunghezza del sepalo, la larghezza del sepalo diminuisce leggermente.
Ora costruiamo uno scatter plot di 'sepal length' contro 'sepal width', colorato in base al target (species):
sns.lmplot(x='sepal length (cm)', y='sepal width (cm)', fit_reg=False, data=df_iris, hue='target');

A prima vista, sembra che il grafico sopra mostri una correlazione positiva: per ogni specie di iris, quando la lunghezza del sepalo aumenta, anche la larghezza del sepalo aumenta.
Le visualizzazioni sono ottime per farsi un'intuizione sulla correlazione, ma per approfondire puoi calcolare effettivamente un coefficiente di correlazione.
Puoi calcolare i coefficienti di correlazione di ogni coppia di misurazioni con il metodo .corr():
df_iris.corr()
| sepal length (cm) | sepal width (cm) | petal length (cm) | petal width (cm) | target | |
|---|---|---|---|---|---|
| sepal length (cm) | 1.000000 | -0.109369 | 0.871754 | 0.817954 | 0.782561 |
| sepal width (cm) | -0.109369 | 1.000000 | -0.420516 | -0.356544 | -0.419446 |
| petal length (cm) | 0.871754 | -0.420516 | 1.000000 | 0.962757 | 0.949043 |
| petal width (cm) | 0.817954 | -0.356544 | 0.962757 | 1.000000 | 0.956464 |
| target | 0.782561 | -0.419446 | 0.949043 | 0.956464 | 1.000000 |
Nota che "sepal length (cm)" e "sepal width (cm)" sembrano essere negativamente correlati! E lo sono, sull'intera popolazione di fiori misurati. Vedi che il coefficiente di correlazione è -0,1. Tuttavia, non sono negativamente correlati all'interno di ciascuna specie, dato che il coefficiente è 0,78.
Per chi fosse interessato, questo è noto come paradosso di Simpson ed è fondamentale quando si pensa all'inferenza causale. Puoi leggere di più qui.
Esploriamo ancora. Calcoliamo i coefficienti di correlazione di ogni coppia di misure all'interno di ciascuna specie. Il modo per farlo è concatenare i metodi .groupby() e .corr(), raggruppare per target e stampare il coefficiente di correlazione:
df_iris.groupby(['target']).corr()
| petal length (cm) | petal width (cm) | sepal length (cm) | sepal width (cm) | ||
|---|---|---|---|---|---|
| target | |||||
| 0.0 | petal length (cm) | 1.000000 | 0.306308 | 0.263874 | 0.176695 |
| petal width (cm) | 0.306308 | 1.000000 | 0.279092 | 0.279973 | |
| sepal length (cm) | 0.263874 | 0.279092 | 1.000000 | 0.746780 | |
| sepal width (cm) | 0.176695 | 0.279973 | 0.746780 | 1.000000 | |
| 1.0 | petal length (cm) | 1.000000 | 0.786668 | 0.754049 | 0.560522 |
| petal width (cm) | 0.786668 | 1.000000 | 0.546461 | 0.663999 | |
| sepal length (cm) | 0.754049 | 0.546461 | 1.000000 | 0.525911 | |
| sepal width (cm) | 0.560522 | 0.663999 | 0.525911 | 1.000000 | |
| 2.0 | petal length (cm) | 1.000000 | 0.322108 | 0.864225 | 0.401045 |
| petal width (cm) | 0.322108 | 1.000000 | 0.281108 | 0.537728 | |
| sepal length (cm) | 0.864225 | 0.281108 | 1.000000 | 0.457228 | |
| sepal width (cm) | 0.401045 | 0.537728 | 0.457228 | 1.000000 |
In questa matrice di correlazione puoi vedere che:
- Per il target 0, lunghezza e larghezza del sepalo hanno una correlazione di 0,75;
- Per il target 1, hai un coefficiente di 0,5; e
- Per il target 2, ottieni una correlazione di 0,46.
Si tratta tutte di correlazioni positive in diminuzione, ma sono tutte molto più positive rispetto alla correlazione negativa originale.
Questo è estremamente istruttivo ed è un chiaro promemoria dell'importanza di analizzare a fondo i dati.
Ora che hai dato un'occhiata più da vicino alla correlazione, sei pronto per analizzare la periodicità nella tua serie temporale guardando la sua funzione di autocorrelazione!
Per cominciare, traccia di nuovo tutte le tue serie temporali per ricordarti come appaiono:
df.plot(figsize=(20,10), linewidth=5, fontsize=20)
plt.xlabel('Year', fontsize=20);

Poi calcola i coefficienti di correlazione di tutte queste serie temporali con .corr():
df.corr()
| diet | gym | finance | |
|---|---|---|---|
| diet | 1.000000 | -0.100764 | -0.034639 |
| gym | -0.100764 | 1.000000 | -0.284279 |
| finance | -0.034639 | -0.284279 | 1.000000 |
Che cosa ti dice quanto sopra?
Concentriamoci su 'diet' e 'gym': sono negativamente correlati. Molto interessante! Ricorda che hai una componente stagionale e una di trend. Dal coefficiente di correlazione, "diet" e "gym" sono negativamente correlati. Tuttavia, guardando le serie temporali, sembra che le loro componenti stagionali siano positivamente correlate e i loro trend negativamente correlati.
Il coefficiente di correlazione sta infatti catturando entrambe queste componenti.
Quello che vuoi fare ora è tracciare le differenze di primo ordine di queste serie temporali e poi calcolarne la correlazione, perché quella sarà approssimativamente la correlazione delle componenti stagionali. Ricorda che rimuovere il trend può rivelare correlazioni nella stagionalità.
Inizia tracciando le differenze di primo ordine con .diff() e .plot():
df.diff().plot(figsize=(20,10), linewidth=5, fontsize=20)
plt.xlabel('Year', fontsize=20);

Vedi che 'diet' e 'gym' sono incredibilmente correlati una volta rimosso il trend. Ora calcolerai i coefficienti di correlazione delle differenze di primo ordine di queste serie temporali:
df.diff().corr()
| diet | gym | finance | |
|---|---|---|---|
| diet | 1.000000 | 0.758707 | 0.373828 |
| gym | 0.758707 | 1.000000 | 0.301111 |
| finance | 0.373828 | 0.301111 | 1.000000 |
Nota che ancora una volta c'era una lieve correlazione negativa quando consideravi insieme trend e componente stagionale. Ora vedi che sulla componente stagionale 'diet' e 'gym' sono altamente correlati, con un coefficiente di 0,76.
Autocorrelazione
Ora che hai approfondito la correlazione tra variabili e tra serie temporali, è il momento di tracciare l'autocorrelazione della serie 'diet': sull'asse x hai il lag e sull'asse y quanto la serie è correlata con se stessa a quel lag.
Quindi, se la serie originale si ripete ogni due giorni, ti aspetteresti un picco nella funzione di autocorrelazione a 2 giorni.
Qui guarderai il grafico e ciò che dovresti aspettarti è un picco nella funzione di autocorrelazione a 12 mesi: la serie è correlata con se stessa traslata di dodici mesi.
Usa l'interfaccia di pandas per il plotting, che ha la funzione autocorrelation_plot(). Puoi usarla per tracciare la serie 'diet':
pd.plotting.autocorrelation_plot(diet);

Se includessi più lag negli assi, vedresti che è a 12 mesi che hai questo enorme picco di correlazione. Hai un altro picco a 24 mesi, dove è di nuovo correlata con se stessa. Ce n'è un altro a 36, ma man mano che ti allontani, la correlazione diminuisce.
Ovviamente, hai una correlazione della serie con se stessa a lag 0.
Le linee tratteggiate nel grafico sopra indicano la significatività statistica della correlazione. In questo caso, puoi dire che la serie 'diet' è realmente autocorrelata con un lag di dodici mesi.
Hai identificato la stagionalità di questa ripetizione a 12 mesi!
Conclusione
In questo tutorial abbiamo coperto molto! Hai esaminato i dati di Google Trends per le keyword "diet", "gym" e dato una veloce occhiata a "finance" per vedere come variano nel tempo. Hai affrontato concetti come stagionalità, trend, correlazione, autocorrelazione, ...
Per chi ha voglia di approfondire subito, ci sono due cose che puoi fare:
- Approfondire la colonna "finance" e riportare ciò che trovi;
- Usare la modellazione ARIMA per fare previsioni su come evolveranno queste tendenze di ricerca nei prossimi anni. Jason Brownlee su Machine Learning Mastery ha un bellissimo tutorial sulla modellazione ARIMA in Python; DataCamp offre un ottimo corso su ARIMA Modeling con R e il corso Time Series with Python.

