Introduzione a Numpy e Pandas

Introduzione a Numpy e Pandas

 

Python è il linguaggio più popolare che incontrerai nel campo della scienza dei dati per la sua semplicità, la grande community e l’enorme disponibilità di librerie open source. 

Se stai lavorando su un progetto di scienza dei dati, i pacchetti Python faciliteranno la tua vita poiché ti basteranno poche righe di codice per eseguire operazioni complicate, come la manipolazione dei dati e l’applicazione di modelli di machine learning/deep learning.

Quando inizi il tuo percorso nella scienza dei dati, è consigliabile iniziare imparando due dei pacchetti Python più utili: NumPy e Pandas. In questo articolo, presentiamo queste due librerie. Iniziamo!

 

Cos’è NumPy?

 

NumPy sta per Numerical Python ed è utilizzato per eseguire calcoli efficienti di array e matrici dietro le quinte dei modelli di machine learning. Il blocco di costruzione di NumPy è l’array, che è una struttura dati molto simile alla lista, con la differenza che fornisce un’enorme quantità di funzioni matematiche. In altre parole, l’array NumPy è un oggetto di array multidimensionale.

 

Creare array NumPy

 

Possiamo definire gli array NumPy utilizzando una lista o una lista di liste:

import numpy as np
l = [[1,2,3],[4,5,6],[7,8,9]]
numpy_array = np.array(l)
numpy_array

 

array([[1, 2, 3],
       [4, 5, 6],
       [7, 8, 9]])

 

Diversamente da una lista di liste, possiamo visualizzare la matrice 3X3 con un’indentazione tra ogni riga. Inoltre, NumPy fornisce oltre 40 funzioni integrate per la creazione di array.

Per creare un array riempito di zeri, c’è la funzione np.zeros, in cui devi semplicemente specificare la forma desiderata:

zeros_array = np.zeros((3,4))
zeros_array

 

array([[0., 0., 0., 0.],
       [0., 0., 0., 0.],
       [0., 0., 0., 0.]])

 

Nello stesso modo, possiamo creare un array riempito di uno:

ones_array = np.ones((3,4))
ones_array

 

array([[1., 1., 1., 1.],
       [1., 1., 1., 1.],
       [1., 1., 1., 1.]])

 

C’è anche la possibilità di creare la matrice identità, che è un array quadrato con 1 sulla diagonale principale e gli elementi fuori diagonale sono 0:

identity_array = np.identity(3)
identity_array

 

array([[1., 0., 0.],
       [0., 1., 0.],
       [0., 0., 1.]])

 

Inoltre, NumPy fornisce diverse funzioni per creare array casuali. Per creare un array riempito con campioni casuali da una distribuzione uniforme su [0,1], basta utilizzare la funzione np.random.rand:

random_array = np.random.rand(3,4)
random_array

 

array([[0.84449279, 0.71146992, 0.48159787, 0.04927379],
       [0.03428534, 0.26851667, 0.65718662, 0.52284251],
       [0.1380207 , 0.91146148, 0.74171469, 0.57325424]])

 

In modo simile alla funzione precedente, possiamo definire un array con valori casuali, ma questa volta i valori vengono presi da una distribuzione normale standard:

randn_array = np.random.randn(10)
randn_array

 

array([-0.68398432, -0.25466784,  0.27020797,  0.29632334, -0.20064897,
        0.7988508 ,  1.34759319, -0.41418478, -0.35223377, -0.10282884])

 

Nel caso in cui siamo interessati a creare un array con numeri interi casuali che appartengono all’intervallo [low,high), abbiamo bisogno solo della funzione np.random.randint:

randint_array = np.random.randint(1,20,20)
randint_array

 

array([14,  3,  1,  2, 17, 15,  5, 17, 18,  9,  4, 19, 14, 14,  1, 10, 17,
       19,  4,  6])

 

Indicizzazione e Slicing

 

Oltre alle funzioni integrate per la creazione di array, un altro punto di forza di NumPy è che è possibile selezionare elementi dall’array utilizzando una serie di parentesi quadre. Ad esempio, possiamo provare a prendere la prima riga della matrice:

a1 = np.array([[1,2,3],[4,5,6]])
a1[0]

 

array([1, 2, 3])

 

Supponiamo che vogliamo selezionare il terzo elemento della prima riga. In questo caso, è necessario specificare due indici, l’indice della riga e l’indice della colonna:

print(a1[0,2]) #3

 

Un’alternativa è utilizzare a1[0][2], ma è considerata inefficiente perché crea prima l’array contenente la prima riga e, quindi, seleziona l’elemento da quella riga.

Inoltre, possiamo prendere porzioni (slices) della matrice con la sintassi start:stop:step all’interno delle parentesi quadre, dove l’indice stop non è incluso. Ad esempio, vogliamo di nuovo selezionare la prima riga, ma prendiamo solo i primi due elementi:

print(a1[0,0:2]) 

 

[1 2]

 

Se preferiamo selezionare tutte le righe, ma vogliamo estrarre il primo elemento di ogni riga:

print(a1[:,0])

 

[1 4]

 

Oltre all’indicizzazione con array di interi, esiste anche l’indicizzazione con array booleani per selezionare gli elementi di un array. Supponiamo che vogliamo solo gli elementi che rispettano la seguente condizione:

a1>5

 

array([[False, False, False],
       [False, False,  True]])

 

Se filtriamo l’array in base a questa condizione, l’output mostrerà solo gli elementi True:

a1[a1>5]

 

array([6])

 

Manipolazione degli Array

 

Quando si lavora in progetti di data science, spesso si ha la necessità di ridimensionare un array a una nuova forma senza modificare i dati.

Ad esempio, partiamo con un array di dimensione 2X3. Se non siamo sicuri della forma del nostro array, c’è l’attributo shape che ci può aiutare:

a1 = np.array([[1,2,3],[4,5,6]])
print(a1)
print('Forma dell'array: ',a1.shape)

 

[[1 2 3]
 [4 5 6]]
Forma dell'array:  (2, 3)

 

Per ridimensionare l’array alla dimensione 3X2, possiamo semplicemente utilizzare la funzione reshape:

a1 = a1.reshape(3,2)
print(a1)
print('Forma dell'array: ',a1.shape)

 

[[1 2]
 [3 4]
 [5 6]]
Forma dell'array:  (3, 2)

 

Un’altra situazione comune è trasformare un array multidimensionale in un array unidimensionale. Questo è possibile specificando -1 come forma:

a1 = a1.reshape(-1)
print(a1)
print('Forma dell'array: ',a1.shape)

 

[1 2 3 4 5 6]
Forma dell'array:  (6,)

 

Può anche accadere che tu abbia bisogno di ottenere un array trasposto:

a1 = np.array([[1,2,3,4,5,6]])
print('Prima della forma dell'array: ',a1.shape)
a1 = a1.T
print(a1)
print('Dopo la forma dell'array: ',a1.shape)

 

Prima della forma dell'array:  (1, 6)
[[1]
 [2]
 [3]
 [4]
 [5]
 [6]]
Dopo la forma dell'array:  (6, 1)

 

Nello stesso modo, puoi applicare la stessa trasformazione utilizzando np.transpose(a1).

 

Moltiplicazione di array

 

Se provi a costruire algoritmi di apprendimento automatico da zero, sicuramente avrai bisogno di calcolare il prodotto matriciale di due array. Questo è possibile utilizzando la funzione np.matmul quando l’array ha più di 1 dimensione:

a1 = np.array([[1,2,3],[4,5,6]])
a2 = np.array([[1,2],[4,5],[7,8]])
print('Forma dell'array a1: ',a1.shape)
print('Forma dell'array a2: ',a2.shape)
a3 = np.matmul(a1,a2) 
# a3 = a1 @ a2
print(a3)
print('Forma dell'array a3: ',a3.shape)

 

Forma dell'array a1:  (2, 3)
Forma dell'array a2:  (3, 2)
[[30 36]
 [66 81]]
Forma dell'array a3:  (2, 2)

 

@ può essere una breve alternativa a np.matmul.

Se moltiplichi una matrice per uno scalare, np.dot è la scelta migliore:

a1 = np.array([[1,2,3],[4,5,6]])
a3 = np.dot(a1,2)
# a3 = a1 * 2
print(a3)
print('Forma dell'array a3: ',a3.shape)

 

[[ 2  4  6]
 [ 8 10 12]]
Forma dell'array a3:  (2, 3)

 

In questo caso, * è una breve alternativa a np.dot.

 

Funzioni matematiche

 

NumPy fornisce una vasta gamma di funzioni matematiche, come le funzioni trigonometriche, le funzioni di arrotondamento, gli esponenziali, i logaritmi, ecc. Puoi trovare l’elenco completo qui. Mostriamo le funzioni più importanti che puoi applicare ai tuoi problemi.

Le funzioni esponenziali e il logaritmo naturale sono sicuramente le trasformazioni più popolari e conosciute:

a1 = np.array([[1,2,3],[4,5,6]])
print(np.exp(a1))

 

[[  2.71828183   7.3890561   20.08553692]
 [ 54.59815003 148.4131591  403.42879349]]

 

a1 = np.array([[1,2,3],[4,5,6]])
print(np.log(a1))

 

[[0.         0.69314718 1.09861229]
 [1.38629436 1.60943791 1.79175947]]

 

Se vogliamo estrarre il minimo e il massimo in una singola riga di codice, dobbiamo semplicemente chiamare le seguenti funzioni:

a1 = np.array([[1,2,3],[4,5,6]])
print(np.min(a1),np.max(a1))  # 1 6

 

Possiamo anche calcolare la radice quadrata di ogni elemento dell’array:

a1 = np.array([[1,2,3],[4,5,6]])
print(np.sqrt(a1))

 

[[1.         1.41421356 1.73205081]
 [2.         2.23606798 2.44948974]]

 

Cos’è Pandas?

 

Pandas è basato su Numpy ed è utile per manipolare il dataset. Ci sono due strutture dati principali: Serie e Dataframe. Mentre la Serie è una sequenza di valori, il dataframe è una tabella con righe e colonne. In altre parole, la serie è una colonna del dataframe.

 

Creare Serie e Dataframe

 

Per creare la Serie, possiamo semplicemente passare la lista di valori al metodo:

import pandas as pd
type_house = pd.Series(['Loft','Villa'])
type_house

 

0     Loft
1    Villa
dtype: object

 

Possiamo creare un Dataframe passando un dizionario di oggetti, in cui le chiavi corrispondono ai nomi delle colonne e i valori sono le voci delle colonne:

df = pd.DataFrame({'Price': [100000, 300000], 'date_construction': [1960, 2010]})
df.head()

 

 

Una volta creato il Dataframe, possiamo controllare il tipo di ogni colonna:

type(df.Price),type(df.date_construction)

 

(pandas.core.series.Series, pandas.core.series.Series)

 

Dovrebbe essere chiaro che le colonne sono strutture dati di tipo Serie.

 

Funzioni di riepilogo

 

D’ora in poi, mostriamo le potenzialità di Pandas utilizzando il dataset di condivisione delle biciclette, disponibile su Kaggle. Possiamo importare il file CSV nel seguente modo:

df = pd.read_csv('/kaggle/input/bike-sharing-demand/train.csv')
df.head()

 

 

Pandas non permette solo la lettura dei file CSV, ma anche dei file Excel, JSON, Parquet e altri tipi di file. Puoi trovare l’elenco completo qui.

Dall’output, possiamo visualizzare le prime cinque righe del dataframe. Se vogliamo visualizzare le ultime quattro righe del dataset, utilizziamo il metodo tail():

df.tail(4)

 

 

Pochi righe non sono sufficienti per avere un’idea precisa dei dati che abbiamo. Un buon modo per iniziare l’analisi è guardare la forma del dataset:

df.shape                    #(10886, 12)

 

Abbiamo 10886 righe e 12 colonne. Vuoi vedere i nomi delle colonne? E’ molto intuitivo farlo:

df.columns

 

 

C’è un metodo che permette di visualizzare tutte queste informazioni in un unico output:

df.info()

 

 

Se vogliamo visualizzare le statistiche di ogni colonna, possiamo utilizzare il metodo describe:

df.describe()

 

 

È anche importante estrarre informazioni dai campi categorici. Possiamo trovare i valori univoci e il numero di valori univoci della colonna “season”:

df.season.unique(), df.season.nunique()

 

Output:

(array([1, 2, 3, 4]), 4)

 

Possiamo vedere che i valori sono 1, 2, 3, 4. Quindi, ci sono quattro possibili valori. Questa verifica è cruciale per capire le variabili categoriche e prevenire possibili errori contenuti nella colonna.

Per visualizzare la frequenza di ogni livello, possiamo utilizzare il metodo value_counts():

df.season.value_counts()

 

 

L’ultimo passaggio dovrebbe essere l’ispezione dei valori mancanti in ogni colonna:

df.isnull().sum()

 

 

Felizmente non abbiamo nessun valore mancante in nessuno di questi campi.

 

Indicizzazione e Slicing

 

Come in Numpy, esiste la selezione basata sull’indice per selezionare dati dalla struttura dati. Ci sono due metodi principali per prendere elementi dal dataframe:

  • iloc seleziona gli elementi in base alla posizione intera
  • loc prende gli elementi in base alle etichette o a un array booleano.

Per selezionare la prima riga, iloc è la scelta migliore:

df.iloc[0]

 

 

Se invece vogliamo selezionare tutte le righe e solo la seconda colonna, possiamo fare quanto segue:

df.iloc[:,1]

 

 

È anche possibile selezionare più colonne contemporaneamente:

df.iloc[0:3,[0,1,2,5]]

 

 

Diventa complesso selezionare le colonne in base agli indici. Sarebbe meglio specificare i nomi delle colonne. Questo è possibile utilizzando loc:

df.loc[0:3,['datetime','season','holiday','temp']]

 

 

Similmente a Numpy, è possibile filtrare il dataframe in base a delle condizioni. Ad esempio, vogliamo restituire tutte le righe in cui il tempo è uguale a 1:

df[df.weather==1]

 

 

Nel caso in cui vogliamo restituire un output con colonne specifiche, possiamo utilizzare loc:

df.loc[df.weather==1,['season','holiday']]

 

 

Creazione di nuove variabili

 

La creazione di nuove variabili ha un enorme impatto nell’estrazione di ulteriori informazioni dai dati e nell’incremento della loro interpretabilità. Possiamo creare una nuova variabile categorica basata sui valori di workingday:

df['workingday_c'] = df['workingday'].apply(lambda x: 'lavoro' if x==1 else 'riposo')
df[['workingday','workingday_c']].head()

 

 

Se ci sono più di una condizione, è meglio mappare i valori utilizzando un dizionario e il metodo map:

diz_season = {1:'inverno',2:'primavera',3:'estate',4:'autunno'}
df['season_c'] = df['season'].map(lambda x: diz_season[x])
df[['season','season_c']].head()

 

 

Raggruppamento e ordinamento

 

Può accadere che si desideri raggruppare i dati in base a una o più colonne categoriche. Ciò è possibile utilizzando il metodo groupby:

df.groupby('season_c').agg({'count':['mediana','massimo']})

 

 

Per ogni livello della stagione, possiamo osservare la mediana e il massimo numero di biciclette noleggiate. Questo output può essere confusionario senza un ordinamento basato su una colonna. Possiamo farlo utilizzando il metodo sort_values():

df.groupby('season_c').agg({'count':['mediana','massimo']}).reset_index().sort_values(by=('count', 'mediana'),ascending=False)

 

 

Ora l’output ha più senso. Possiamo dedurre che il numero più elevato di biciclette noleggiate si registra in estate, mentre l’inverno non è un buon mese per il noleggio di biciclette. 

 

Considerazioni finali

 

Questo è tutto! Spero che tu abbia trovato utile questa guida per imparare le basi di NumPy e Pandas. Spesso vengono studiati separatamente, ma può essere illuminante comprendere prima NumPy e poi Pandas, che è costruito sopra NumPy. 

Ci sono sicuramente metodi che non ho coperto nel tutorial, ma l’obiettivo era coprire i metodi più importanti e popolari di queste due librerie. Il codice può essere trovato su Kaggle. Grazie per la lettura! Buona giornata!     Eugenia Anello è attualmente una ricercatrice presso il Dipartimento di Ingegneria dell’Informazione dell’Università di Padova, Italia. Il suo progetto di ricerca è incentrato sull’apprendimento continuo combinato con la rilevazione delle anomalie.