Analitiche con Pandas - 1. introduzione

Scarica zip esercizi

Naviga file online

Python mette a disposizione degli strumenti potenti per l’analisi dei dati - uno dei principali è Pandas, che fornisce strutture di dati veloci e flessibili, soprattutto per l’analisi dei dati in tempo reale. Pandas riusa librerie esistenti che abbiamo già visto come Numpy:

pydata-8243

In questa prima parte del tutorial vedremo:

  • Operazioni di filtraggio e trasformazione su dataframe Pandas

  • Plotting con MatPlotLib

  • Esempi con dataset AstroPi

  • Esercizi con dataset meteotrentino e altri

1. Che fare

  • scompatta lo zip in una cartella, dovresti ottenere qualcosa del genere:

pandas
    pandas1-intro.ipynb
    pandas1-intro-sol.ipynb
    pandas2-advanced.ipynb
    pandas2-advanced-sol.ipynb
    pandas3-chal.ipynb
    jupman.py

ATTENZIONE: Per essere visualizzato correttamente, il file del notebook DEVE essere nella cartella szippata.

  • apri il Jupyter Notebook da quella cartella. Due cose dovrebbero aprirsi, prima una console e poi un browser. Il browser dovrebbe mostrare una lista di file: naviga la lista e apri il notebook pandas1-intro.ipynb

  • Prosegui leggendo il file degli esercizi, ogni tanto al suo interno troverai delle scritte ESERCIZIO, che ti chiederanno di scrivere dei comandi Python nelle celle successive.

Scorciatoie da tastiera:

  • Per eseguire il codice Python dentro una cella di Jupyter, premi Control+Invio

  • Per eseguire il codice Python dentro una cella di Jupyter E selezionare la cella seguente, premi Shift+Invio

  • Per eseguire il codice Python dentro una cella di Jupyter E creare una nuova cella subito dopo, premi Alt+Invio

  • Se per caso il Notebook sembra inchiodato, prova a selezionare Kernel -> Restart

Controlla l’installazione

Prima di tutto vediamo se hai già installato pandas sul tuo sistema, prova ad eseguire questa cella con Ctrl Enter:

[1]:
import pandas as pd

Se non hai visto messaggi di errore, puoi saltare l’installazione, altrimenti fai così:

  • Se hai Anaconda - apri l’Anaconda Prompt e metti:

conda install pandas

  • Senza Anaconda: (--user installa nella propria home):

python3 -m pip install --user pandas

Quale pandas usare?

In questo tutorial adottiamo la versione 1 di pandas che è basata su numpy, perchè al momento (2023) è la più diffusa e di solito i tutorial che trovate in giro si riferiscono a questa versione. Va considerato che ad Aprile 2023 è uscita la versione 2 che è più efficiente, può supportare opzionalmente l’engine PyArrow e ha un miglior supporto per tipi ‘nullabili’.

2. Analisi dei dati di Astro Pi

Proviamo ad analizzare i dati registrati da una scheda elettronica RaspberryPi presente sulla Stazione Spaziale Internazionale (ISS in breve), scaricati da qui:

https://projects.raspberrypi.org/en/projects/astro-pi-flight-data-analysis

Nel sito è possibile trovare la descrizione dettagliata dei dati raccolti dai sensori, nel mese di febbraio 2016 (un record ogni 10 secondi). iss-2985744

2.1 Importiamo il file

Il metodo read_csv importa i dati da un file CSV e li memorizza in una struttura DataFrame.

In questo esercizio useremo il file astropi.csv (modificato rispetto all’originale sostituendo il campo ROW_ID con time_stamp)

[2]:
import pandas as pd   # importiamo pandas e per comodità lo rinominiamo in  'pd'
import numpy as np    # importiamo numpy e per comodità lo rinominiamo in 'np'

# ricordatevi l'encoding !
df = pd.read_csv('astropi.csv', encoding='UTF-8')
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 110869 entries, 0 to 110868
Data columns (total 19 columns):
 #   Column      Non-Null Count   Dtype
---  ------      --------------   -----
 0   time_stamp  110869 non-null  object
 1   temp_cpu    110869 non-null  float64
 2   temp_h      110869 non-null  float64
 3   temp_p      110869 non-null  float64
 4   humidity    110869 non-null  float64
 5   pressure    110869 non-null  float64
 6   pitch       110869 non-null  float64
 7   roll        110869 non-null  float64
 8   yaw         110869 non-null  float64
 9   mag_x       110869 non-null  float64
 10  mag_y       110869 non-null  float64
 11  mag_z       110869 non-null  float64
 12  accel_x     110869 non-null  float64
 13  accel_y     110869 non-null  float64
 14  accel_z     110869 non-null  float64
 15  gyro_x      110869 non-null  float64
 16  gyro_y      110869 non-null  float64
 17  gyro_z      110869 non-null  float64
 18  reset       110869 non-null  int64
dtypes: float64(17), int64(1), object(1)
memory usage: 16.1+ MB

2.2 Memoria

Pandas carica il dataset dal disco nella memoria RAM del tuo computer (che nel 2023 è tipicamente di 8 gigabyte). Se per caso il tuo dataset fosse più grande della memoria RAM, riceveresti un errore e dovresti cominciare a pensare ad usare altri strumenti per fare le tue analisi. Potrebbero sorgere anche problemi nel caso decidessi di fare successivamente copie del dataframe. E’ quindi molto importante capire quanto occupa. Se guardi in fondo, vedrai scritto memory usage: 16.1+ MB ma attenzione a quel +: Pandas ci sta dicendo che il dataset occupa in RAM almeno 16.1Mb, ma la dimensione effettiva potrebbe essere maggiore.

Per vedere lo spazio occupato vero, prova ad aggiungere alla chiamata di df.info il parametro memory_usage="deep" che è opzionale perchè a seconda del dataset potrebbe richiedere più tempo di calcolo. Noti differenze?

  • quanto occupa il file del dataset originale sul tuo disco? Prova a scoprirlo guardando in Esplora risorse.

Mostra soluzione
[3]:

# scrivi qui

2.3 Dimensioni

Possiamo vedere rapidamente righe e colonne del dataframe con l’attributo shape:

NOTA: shape non è seguito da parentesi tonde !

[4]:
df.shape
[4]:
(110869, 19)

2.4 Esploriamo

[5]:
df
[5]:
time_stamp temp_cpu temp_h temp_p humidity pressure pitch roll yaw mag_x mag_y mag_z accel_x accel_y accel_z gyro_x gyro_y gyro_z reset
0 2016-02-16 10:44:40 31.88 27.57 25.01 44.94 1001.68 1.49 52.25 185.21 -46.422753 -8.132907 -12.129346 -0.000468 0.019439 0.014569 0.000942 0.000492 -0.000750 20
1 2016-02-16 10:44:50 31.79 27.53 25.01 45.12 1001.72 1.03 53.73 186.72 -48.778951 -8.304243 -12.943096 -0.000614 0.019436 0.014577 0.000218 -0.000005 -0.000235 0
2 2016-02-16 10:45:00 31.66 27.53 25.01 45.12 1001.72 1.24 53.57 186.21 -49.161878 -8.470832 -12.642772 -0.000569 0.019359 0.014357 0.000395 0.000600 -0.000003 0
3 2016-02-16 10:45:10 31.69 27.52 25.01 45.32 1001.69 1.57 53.63 186.03 -49.341941 -8.457380 -12.615509 -0.000575 0.019383 0.014409 0.000308 0.000577 -0.000102 0
4 2016-02-16 10:45:20 31.66 27.54 25.01 45.18 1001.71 0.85 53.66 186.46 -50.056683 -8.122609 -12.678341 -0.000548 0.019378 0.014380 0.000321 0.000691 0.000272 0
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
110864 2016-02-29 09:24:21 31.56 27.52 24.83 42.94 1005.83 1.58 49.93 129.60 -15.169673 -27.642610 1.563183 -0.000682 0.017743 0.014646 -0.000264 0.000206 0.000196 0
110865 2016-02-29 09:24:30 31.55 27.50 24.83 42.72 1005.85 1.89 49.92 130.51 -15.832622 -27.729389 1.785682 -0.000736 0.017570 0.014855 0.000143 0.000199 -0.000024 0
110866 2016-02-29 09:24:41 31.58 27.50 24.83 42.83 1005.85 2.09 50.00 132.04 -16.646212 -27.719479 1.629533 -0.000647 0.017657 0.014799 0.000537 0.000257 0.000057 0
110867 2016-02-29 09:24:50 31.62 27.50 24.83 42.81 1005.88 2.88 49.69 133.00 -17.270447 -27.793136 1.703806 -0.000835 0.017635 0.014877 0.000534 0.000456 0.000195 0
110868 2016-02-29 09:25:00 31.57 27.51 24.83 42.94 1005.86 2.17 49.77 134.18 -17.885872 -27.824149 1.293345 -0.000787 0.017261 0.014380 0.000459 0.000076 0.000030 0

110869 rows × 19 columns

Nota che la prima colonna numerica in grassetto è un indice intero che Pandas ha automaticamente creato e assegnato al dataset al momento dell’apertura, e che parte da zero. Volendo, si potrebbe anche impostare un indice diverso ma in questo tutorial non lo faremo.

Il metodo head() restituisce le prime righe:

[6]:
df.head()
[6]:
time_stamp temp_cpu temp_h temp_p humidity pressure pitch roll yaw mag_x mag_y mag_z accel_x accel_y accel_z gyro_x gyro_y gyro_z reset
0 2016-02-16 10:44:40 31.88 27.57 25.01 44.94 1001.68 1.49 52.25 185.21 -46.422753 -8.132907 -12.129346 -0.000468 0.019439 0.014569 0.000942 0.000492 -0.000750 20
1 2016-02-16 10:44:50 31.79 27.53 25.01 45.12 1001.72 1.03 53.73 186.72 -48.778951 -8.304243 -12.943096 -0.000614 0.019436 0.014577 0.000218 -0.000005 -0.000235 0
2 2016-02-16 10:45:00 31.66 27.53 25.01 45.12 1001.72 1.24 53.57 186.21 -49.161878 -8.470832 -12.642772 -0.000569 0.019359 0.014357 0.000395 0.000600 -0.000003 0
3 2016-02-16 10:45:10 31.69 27.52 25.01 45.32 1001.69 1.57 53.63 186.03 -49.341941 -8.457380 -12.615509 -0.000575 0.019383 0.014409 0.000308 0.000577 -0.000102 0
4 2016-02-16 10:45:20 31.66 27.54 25.01 45.18 1001.71 0.85 53.66 186.46 -50.056683 -8.122609 -12.678341 -0.000548 0.019378 0.014380 0.000321 0.000691 0.000272 0

Il metodo tail() restituisce le ultime righe:

[7]:
df.tail()
[7]:
time_stamp temp_cpu temp_h temp_p humidity pressure pitch roll yaw mag_x mag_y mag_z accel_x accel_y accel_z gyro_x gyro_y gyro_z reset
110864 2016-02-29 09:24:21 31.56 27.52 24.83 42.94 1005.83 1.58 49.93 129.60 -15.169673 -27.642610 1.563183 -0.000682 0.017743 0.014646 -0.000264 0.000206 0.000196 0
110865 2016-02-29 09:24:30 31.55 27.50 24.83 42.72 1005.85 1.89 49.92 130.51 -15.832622 -27.729389 1.785682 -0.000736 0.017570 0.014855 0.000143 0.000199 -0.000024 0
110866 2016-02-29 09:24:41 31.58 27.50 24.83 42.83 1005.85 2.09 50.00 132.04 -16.646212 -27.719479 1.629533 -0.000647 0.017657 0.014799 0.000537 0.000257 0.000057 0
110867 2016-02-29 09:24:50 31.62 27.50 24.83 42.81 1005.88 2.88 49.69 133.00 -17.270447 -27.793136 1.703806 -0.000835 0.017635 0.014877 0.000534 0.000456 0.000195 0
110868 2016-02-29 09:25:00 31.57 27.51 24.83 42.94 1005.86 2.17 49.77 134.18 -17.885872 -27.824149 1.293345 -0.000787 0.017261 0.014380 0.000459 0.000076 0.000030 0

2.5 Qualche statistica

Il metodo describe vi da al volo tutta una serie di dati di riepilogo:

[8]:
df.describe()
[8]:
temp_cpu temp_h temp_p humidity pressure pitch roll yaw mag_x mag_y mag_z accel_x accel_y accel_z gyro_x gyro_y gyro_z reset
count 110869.000000 110869.000000 110869.000000 110869.000000 110869.000000 110869.000000 110869.000000 110869.00000 110869.000000 110869.000000 110869.000000 110869.000000 110869.000000 110869.000000 1.108690e+05 110869.000000 1.108690e+05 110869.000000
mean 32.236259 28.101773 25.543272 46.252005 1008.126788 2.770553 51.807973 200.90126 -19.465265 -1.174493 -6.004529 -0.000630 0.018504 0.014512 -8.959493e-07 0.000007 -9.671594e-07 0.000180
std 0.360289 0.369256 0.380877 1.907273 3.093485 21.848940 2.085821 84.47763 28.120202 15.655121 8.552481 0.000224 0.000604 0.000312 2.807614e-03 0.002456 2.133104e-03 0.060065
min 31.410000 27.200000 24.530000 42.270000 1001.560000 0.000000 30.890000 0.01000 -73.046240 -43.810030 -41.163040 -0.025034 -0.005903 -0.022900 -3.037930e-01 -0.378412 -2.970800e-01 0.000000
25% 31.960000 27.840000 25.260000 45.230000 1006.090000 1.140000 51.180000 162.43000 -41.742792 -12.982321 -11.238430 -0.000697 0.018009 0.014349 -2.750000e-04 -0.000278 -1.200000e-04 0.000000
50% 32.280000 28.110000 25.570000 46.130000 1007.650000 1.450000 51.950000 190.58000 -21.339485 -1.350467 -5.764400 -0.000631 0.018620 0.014510 -3.000000e-06 -0.000004 -1.000000e-06 0.000000
75% 32.480000 28.360000 25.790000 46.880000 1010.270000 1.740000 52.450000 256.34000 7.299000 11.912456 -0.653705 -0.000567 0.018940 0.014673 2.710000e-04 0.000271 1.190000e-04 0.000000
max 33.700000 29.280000 26.810000 60.590000 1021.780000 360.000000 359.400000 359.98000 33.134748 37.552135 31.003047 0.018708 0.041012 0.029938 2.151470e-01 0.389499 2.698760e-01 20.000000

DOMANDA: Manca qualche campo alla tabella prodotta da describe? Perchè non l’ha incluso ?

Il metodo corr permette di calcolare la correlazione tra le colonne del DataFrame, con valori da -1.0 a +1.0:

[9]:
df.corr()
[9]:
temp_cpu temp_h temp_p humidity pressure pitch roll yaw mag_x mag_y mag_z accel_x accel_y accel_z gyro_x gyro_y gyro_z reset
temp_cpu 1.000000 0.986872 0.991672 -0.297081 0.038065 0.008076 -0.171644 -0.117972 0.005145 -0.285192 -0.120838 -0.023582 -0.446358 -0.029155 0.002511 0.005947 -0.001250 -0.002970
temp_h 0.986872 1.000000 0.993260 -0.281422 0.070882 0.005145 -0.199628 -0.117870 0.000428 -0.276276 -0.098864 -0.032188 -0.510126 -0.043213 0.001771 0.005020 -0.001423 -0.004325
temp_p 0.991672 0.993260 1.000000 -0.288373 0.035496 0.006750 -0.163685 -0.118463 0.004338 -0.283427 -0.114407 -0.018047 -0.428884 -0.036505 0.001829 0.006127 -0.001623 -0.004205
humidity -0.297081 -0.281422 -0.288373 1.000000 0.434374 0.004050 0.101304 0.031664 -0.035146 0.077897 0.076424 -0.009741 0.226281 0.005281 0.004345 0.003457 0.001298 -0.002066
pressure 0.038065 0.070882 0.035496 0.434374 1.000000 0.003018 0.011815 -0.051697 -0.040183 -0.074578 0.092352 0.013556 -0.115642 -0.221208 -0.000611 -0.002493 -0.000615 -0.006259
pitch 0.008076 0.005145 0.006750 0.004050 0.003018 1.000000 0.087941 -0.011611 0.013331 0.006133 0.000540 0.043285 0.009015 -0.039146 0.066618 -0.015034 0.049340 -0.000176
roll -0.171644 -0.199628 -0.163685 0.101304 0.011815 0.087941 1.000000 0.095354 -0.020947 0.060297 -0.080620 0.116637 0.462630 -0.167905 -0.115873 -0.002509 -0.214202 0.000636
yaw -0.117972 -0.117870 -0.118463 0.031664 -0.051697 -0.011611 0.095354 1.000000 0.257971 0.549394 -0.328360 0.006943 0.044157 -0.013634 0.003106 0.003665 0.004020 -0.000558
mag_x 0.005145 0.000428 0.004338 -0.035146 -0.040183 0.013331 -0.020947 0.257971 1.000000 0.001239 -0.213070 -0.006629 0.027921 0.021524 -0.004954 -0.004429 -0.005052 -0.002879
mag_y -0.285192 -0.276276 -0.283427 0.077897 -0.074578 0.006133 0.060297 0.549394 0.001239 1.000000 -0.266351 0.014057 0.051619 -0.053016 0.001239 0.001063 0.001530 -0.001335
mag_z -0.120838 -0.098864 -0.114407 0.076424 0.092352 0.000540 -0.080620 -0.328360 -0.213070 -0.266351 1.000000 0.024718 -0.083914 -0.061317 -0.008470 -0.009557 -0.008997 -0.002151
accel_x -0.023582 -0.032188 -0.018047 -0.009741 0.013556 0.043285 0.116637 0.006943 -0.006629 0.014057 0.024718 1.000000 0.095286 -0.262305 0.035314 0.103449 0.197740 0.002173
accel_y -0.446358 -0.510126 -0.428884 0.226281 -0.115642 0.009015 0.462630 0.044157 0.027921 0.051619 -0.083914 0.095286 1.000000 0.120215 0.043263 -0.046463 0.009541 0.004648
accel_z -0.029155 -0.043213 -0.036505 0.005281 -0.221208 -0.039146 -0.167905 -0.013634 0.021524 -0.053016 -0.061317 -0.262305 0.120215 1.000000 0.078315 -0.075625 0.057075 0.000554
gyro_x 0.002511 0.001771 0.001829 0.004345 -0.000611 0.066618 -0.115873 0.003106 -0.004954 0.001239 -0.008470 0.035314 0.043263 0.078315 1.000000 -0.248968 0.337553 0.001009
gyro_y 0.005947 0.005020 0.006127 0.003457 -0.002493 -0.015034 -0.002509 0.003665 -0.004429 0.001063 -0.009557 0.103449 -0.046463 -0.075625 -0.248968 1.000000 0.190112 0.000593
gyro_z -0.001250 -0.001423 -0.001623 0.001298 -0.000615 0.049340 -0.214202 0.004020 -0.005052 0.001530 -0.008997 0.197740 0.009541 0.057075 0.337553 0.190112 1.000000 -0.001055
reset -0.002970 -0.004325 -0.004205 -0.002066 -0.006259 -0.000176 0.000636 -0.000558 -0.002879 -0.001335 -0.002151 0.002173 0.004648 0.000554 0.001009 0.000593 -0.001055 1.000000

2.6 Guardiamo le colonne

La proprietà colums restituisce le intestazioni di colonna:

[10]:
df.columns
[10]:
Index(['time_stamp', 'temp_cpu', 'temp_h', 'temp_p', 'humidity', 'pressure',
       'pitch', 'roll', 'yaw', 'mag_x', 'mag_y', 'mag_z', 'accel_x', 'accel_y',
       'accel_z', 'gyro_x', 'gyro_y', 'gyro_z', 'reset'],
      dtype='object')

Come si vede qua sopra, il tipo dell’oggetto ritornato non è una lista, ma un contenitore speciale definito da pandas:

[11]:
type(df.columns)
[11]:
pandas.core.indexes.base.Index

Ciononostante, possiamo accedere agli elementi di questo contenitore usando indici dentro le parentesi quadre:

[12]:
df.columns[0]
[12]:
'time_stamp'
[13]:
df.columns[1]
[13]:
'temp_cpu'

2.7 Cos’è una colonna?

Possiamo accedere ad una colonna così:

[14]:
df['humidity']
[14]:
0         44.94
1         45.12
2         45.12
3         45.32
4         45.18
          ...
110864    42.94
110865    42.72
110866    42.83
110867    42.81
110868    42.94
Name: humidity, Length: 110869, dtype: float64

Ancora più comodamente, puoi usare la notazione con il punto:

[15]:
df.humidity
[15]:
0         44.94
1         45.12
2         45.12
3         45.32
4         45.18
          ...
110864    42.94
110865    42.72
110866    42.83
110867    42.81
110868    42.94
Name: humidity, Length: 110869, dtype: float64

ATTENZIONE: sembrano due colonne, ma in realtà è solo una!

La lista di numeri a sinistra è l’indice intero che Pandas ha automaticamente assegnato al dataset al momento dell’apertura (parte da zero).

ATTENZIONE agli spazi!

Nel caso il nome del campo avesse degli spazi (es. 'rotazioni frullatore'), non si può usare la notazione con il punto ma si è costretti ad usare la notazione con le quadre vista sopra (es: df.['rotazioni frullatore'])

Il tipo di una colonna è Series:

[16]:
type(df.humidity)
[16]:
pandas.core.series.Series

Alcune operazioni funzionano anche sulle colonne singole, per esempio describe():

[17]:
df.humidity.describe()
[17]:
count    110869.000000
mean         46.252005
std           1.907273
min          42.270000
25%          45.230000
50%          46.130000
75%          46.880000
max          60.590000
Name: humidity, dtype: float64

2.8 Esercizio - info meteo

✪ a. Crea un nuovo dataframe meteo importando i dati dal file meteo.csv, che contiene i dati meteo di Trento di novembre 2017 (fonte: www.meteotrentino.it). IMPORTANTE: assegna il dataframe ad una variabile chiamata meteo (così evitiamo confusione con il dataframe dell’AstroPi)

  1. Visualizza le informazioni relative a questo Dataframe.

Mostra soluzione
[18]:
# scrivi qui - crea il dataframe


COLONNE: Data, Pressione, Pioggia, Temp

INFO:
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 2878 entries, 0 to 2877
Data columns (total 4 columns):
 #   Column     Non-Null Count  Dtype
---  ------     --------------  -----
 0   Data       2878 non-null   object
 1   Pressione  2878 non-null   float64
 2   Pioggia    2878 non-null   float64
 3   Temp       2878 non-null   float64
dtypes: float64(3), object(1)
memory usage: 272.7 KB

PRIME RIGHE:
               Data  Pressione  Pioggia  Temp
0  01/11/2017 00:00      995.4      0.0   5.4
1  01/11/2017 00:15      995.5      0.0   6.0
2  01/11/2017 00:30      995.5      0.0   5.9
3  01/11/2017 00:45      995.7      0.0   5.4
4  01/11/2017 01:00      995.7      0.0   5.3

3. Rivediamo MatPlotLib

Abbiamo già visto MatplotLib nel tutorial sulla visualizzazione, e oggi lo useremo per disegnare grafici.

3.1 Un esempio

Proviamo a plottere una grafico usando l’approccio in stile Matlab, passando due liste di coordinate, una per le x e una per le y:

[19]:
%matplotlib inline
import matplotlib as mpl
import matplotlib.pyplot as plt

xs = [1, 2, 3, 4]
ys = [7, 6,10, 8]
plt.plot(xs, ys) # possiamo direttamente passare liste per le x e y
plt.title('Un esempio')
plt.show()

../_images/pandas_pandas1-intro-sol_52_0.png

Possiamo anche creare serie con numpy. Proviamo a fare una parabola:

[20]:
import numpy as np
x = np.arange(0.,5.,0.1)
#  '**' è l'operatore di elevamento a potenza in Python, NON '^'
y = x**2

Utilizziamo la funzione type per capire che tipo di dati sono x e y:

[21]:
type(x)
[21]:
numpy.ndarray
[22]:
type(y)
[22]:
numpy.ndarray

Si tratta quindi di vettori di NumPy.

Plottiamoli:

[23]:
plt.title('La parabola')
plt.plot(x,y);
../_images/pandas_pandas1-intro-sol_59_0.png

Se vogliamo che le unità dell’asse x siano della stessa dimensione di quelle dell’asse y, possiamo utilizzare la funzione gca

Per settare i limiti delle x e delle y, possiamo usare xlim e ylim:

[24]:
plt.xlim([0, 5])
plt.ylim([0,10])
plt.title('La parabola')

plt.gca().set_aspect('equal')
plt.plot(x,y);
../_images/pandas_pandas1-intro-sol_61_0.png

3.2 Grafici matplotlib da strutture pandas

Si possono ricavare grafici direttamente da strutture pandas usando lo stile matlab. Facciamo un esempio semplice, per casi più complessi rimandiamo alla documentazione di DataFrame.plot.

In caso di un numero molto elevato di dati, può essere utile avere un’idea qualitativa dei dati, mettendoli in grafico:

[25]:
df.humidity.plot(label="Humidity", legend=True)
# con secondary_y=True facciamo apparire i numeri per l'asse delle y
# del secondo grafico sulla destra
df.pressure.plot(secondary_y=True, label="Pressure", legend=True);
../_images/pandas_pandas1-intro-sol_63_0.png

Volendo, è sempre possibile usare direttamente la funzione originale plt.plot, basta passargli una serie per le coordinate x e una per le y. Per esempio, se vogliamo replicare l’esempio di sopra per l’umidità, per le coordinate x potremmo estrarci l’indice del dataframe che è un iterabile:

[26]:
df.index
[26]:
RangeIndex(start=0, stop=110869, step=1)

e passarlo come primo parametro a plt.plot. Come secondo parametro possiamo passare direttamente la serie dell’umidità, essendo anche quest’ultima un iterabile Python automaticamente riuscirà a prelevare i valori delle celle:

[27]:
plt.plot(df.index, df['humidity'])
plt.show() # previene visualizzazione caratteri strani
../_images/pandas_pandas1-intro-sol_67_0.png

4. Operazioni su righe

Se consideriamo le righe di un dataset, tipicamente le vorremo indicizzare, filtrare e ordinare.

4.1 Indicizzare con interi

Riportiamo qui l’indicizzazione più semplice tramite numeri di riga.

Per ottenere la i-esima serie si utilizza il metodo iloc[i] (qui riusiamo il dataset dell’AstroPI) :

[28]:
df.iloc[6]
[28]:
time_stamp    2016-02-16 10:45:41
temp_cpu                    31.68
temp_h                      27.53
temp_p                      25.01
humidity                    45.31
pressure                   1001.7
pitch                        0.63
roll                        53.55
yaw                         186.1
mag_x                  -50.447346
mag_y                   -7.937309
mag_z                  -12.188574
accel_x                  -0.00051
accel_y                  0.019264
accel_z                  0.014528
gyro_x                  -0.000111
gyro_y                    0.00032
gyro_z                   0.000222
reset                           0
Name: 6, dtype: object

È possibile selezionare un dataframe di posizioni contigue, utilizzando lo slicing, come abbiamo già fatto per stringhe e liste.

Qua per esempio selezioniamo le righe dalla 5 inclusa alla 7 esclusa :

[29]:
df.iloc[5:7]
[29]:
time_stamp temp_cpu temp_h temp_p humidity pressure pitch roll yaw mag_x mag_y mag_z accel_x accel_y accel_z gyro_x gyro_y gyro_z reset
5 2016-02-16 10:45:30 31.69 27.55 25.01 45.12 1001.67 0.85 53.53 185.52 -50.246476 -8.343209 -11.938124 -0.000536 0.019453 0.014380 0.000273 0.000494 -0.000059 0
6 2016-02-16 10:45:41 31.68 27.53 25.01 45.31 1001.70 0.63 53.55 186.10 -50.447346 -7.937309 -12.188574 -0.000510 0.019264 0.014528 -0.000111 0.000320 0.000222 0

Filtrando le righe possiamo ‘zommare’ nel dataset, selezionando per esempio nel nuovo dataframe df2 le righe tra la 12500esima (inclusa) e la 15000esima (esclusa):

[30]:
df2=df.iloc[12500:15000]
[31]:
plt.plot(df2.index, df2['humidity'])
plt.show()
../_images/pandas_pandas1-intro-sol_75_0.png
[32]:
df2.humidity.plot(label="Humidity", legend=True)
df2.pressure.plot(secondary_y=True, label="Pressure", legend=True)
plt.show()  # previene visualizzazione caratteri strani
../_images/pandas_pandas1-intro-sol_76_0.png

Differenza tra iloc e loc

iloc richiede sempre un intero e ritorna sempre la riga nell’ordine naturale del dataframe che abbiamo in considerazione.

loc invece ricerca nell’indice assegnato da pandas, che è quello rappresentato in grassetto quando mostriamo il dataset.

Apparentemente sembrano simili ma la differenza tra iloc e loc si esplicita in particolare quando agiamo su dataset filtrati. Guardiamo le prime righe del dataset filtrato df2:

[33]:
df2.head()
[33]:
time_stamp temp_cpu temp_h temp_p humidity pressure pitch roll yaw mag_x mag_y mag_z accel_x accel_y accel_z gyro_x gyro_y gyro_z reset
12500 2016-02-17 21:44:31 31.87 27.7 25.15 45.29 1004.56 0.85 52.78 357.18 30.517177 2.892431 0.371669 -0.000618 0.019318 0.014503 -0.000135 -0.000257 0.000121 0
12501 2016-02-17 21:44:40 31.84 27.7 25.16 45.32 1004.58 0.97 52.73 357.32 30.364154 2.315241 0.043272 -0.001196 0.019164 0.014545 0.000254 0.000497 -0.000010 0
12502 2016-02-17 21:44:51 31.83 27.7 25.15 45.23 1004.55 1.40 52.84 357.76 29.760987 1.904932 0.037701 -0.000617 0.019420 0.014672 0.000192 0.000081 0.000024 0
12503 2016-02-17 21:45:00 31.83 27.7 25.15 45.36 1004.58 2.14 52.84 357.79 29.882673 1.624020 -0.249268 -0.000723 0.019359 0.014691 0.000597 0.000453 -0.000118 0
12504 2016-02-17 21:45:10 31.83 27.7 25.15 45.20 1004.60 1.76 52.98 357.78 29.641547 1.532007 -0.336724 -0.000664 0.019245 0.014673 0.000373 0.000470 -0.000130 0

Prendiamo in considerazione il numero 0, in questo caso:

  • .iloc[0] seleziona la riga iniziale

  • .loc[0] seleziona la riga ad indice pandas con valore zero

DOMANDA: nel caso di df2, qual’è la riga iniziale? Che indice di pandas ha?

Mostra risposta

Vediamo la differenza di risultati. df2.loc[0] troverà effettivamente la riga zeresima:

[34]:
df2.iloc[0]
[34]:
time_stamp    2016-02-17 21:44:31
temp_cpu                    31.87
temp_h                       27.7
temp_p                      25.15
humidity                    45.29
pressure                  1004.56
pitch                        0.85
roll                        52.78
yaw                        357.18
mag_x                   30.517177
mag_y                    2.892431
mag_z                    0.371669
accel_x                 -0.000618
accel_y                  0.019318
accel_z                  0.014503
gyro_x                  -0.000135
gyro_y                  -0.000257
gyro_z                   0.000121
reset                           0
Name: 12500, dtype: object

Invece df2.loc[0] fallirà miseramente:

df2.loc[0]
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
~/.local/lib/python3.7/site-packages/pandas/core/indexes/range.py in get_loc(self, key, method, tolerance)
    384                 try:
--> 385                     return self._range.index(new_key)
    386                 except ValueError as err:

ValueError: 0 is not in range

Proviamo ad usare l’indice assegnato da pandas per la riga zeresima:

[35]:
df2.loc[12500]
[35]:
time_stamp    2016-02-17 21:44:31
temp_cpu                    31.87
temp_h                       27.7
temp_p                      25.15
humidity                    45.29
pressure                  1004.56
pitch                        0.85
roll                        52.78
yaw                        357.18
mag_x                   30.517177
mag_y                    2.892431
mag_z                    0.371669
accel_x                 -0.000618
accel_y                  0.019318
accel_z                  0.014503
gyro_x                  -0.000135
gyro_y                  -0.000257
gyro_z                   0.000121
reset                           0
Name: 12500, dtype: object

Come previsto questo è trovato.

4.2 Filtrare

È possibile filtrare i dati in base al soddisfacimento di una condizione, che si può esprimere indicando una colonna e un operatore di comparazione, per esempio:

[36]:
df.humidity < 45.2
[36]:
0          True
1          True
2          True
3         False
4          True
          ...
110864     True
110865     True
110866     True
110867     True
110868     True
Name: humidity, Length: 110869, dtype: bool

Vediamo che si tratta di una serie di valori True o False, a seconda se il valore di df.humidity è minore di 45.2. Qual’è il tipo di questo risultato?

[37]:
type(df.humidity < 45.2)
[37]:
pandas.core.series.Series

Combinare filtri

In modo analogo al filtraggio in Numpy, è possibile esprimere più condizioni con l’operatore speciale di congiunzione &

(df.humidity > 45.0) & (df.humidity < 45.2) sarà una serie di valori True o False, se df.humidity è contemporaneamente maggiore di 45.0 e minore di 45.2

[38]:
type((df.humidity > 45.0) & (df.humidity < 45.2))
[38]:
pandas.core.series.Series

Applicare un filtro

Se vogliamo le righe complete del dataframe che soddisfano la condizione, possiamo scrivere così:

IMPORTANTE: usiamo df all’esterno dell’espressione df[     ] iniziando e chiudendo con le parentesi quadrate per dire a Python che vogliamo filtrare sul dataframe df, e usiamo di nuovo df all’interno delle quadre per indicare su quali colonne e quali righe vogliamo filtrare

[39]:
df[  (df.humidity > 45.0) & (df.humidity < 45.2) ]
[39]:
time_stamp temp_cpu temp_h temp_p humidity pressure pitch roll yaw mag_x mag_y mag_z accel_x accel_y accel_z gyro_x gyro_y gyro_z reset
1 2016-02-16 10:44:50 31.79 27.53 25.01 45.12 1001.72 1.03 53.73 186.72 -48.778951 -8.304243 -12.943096 -0.000614 0.019436 0.014577 0.000218 -0.000005 -0.000235 0
2 2016-02-16 10:45:00 31.66 27.53 25.01 45.12 1001.72 1.24 53.57 186.21 -49.161878 -8.470832 -12.642772 -0.000569 0.019359 0.014357 0.000395 0.000600 -0.000003 0
4 2016-02-16 10:45:20 31.66 27.54 25.01 45.18 1001.71 0.85 53.66 186.46 -50.056683 -8.122609 -12.678341 -0.000548 0.019378 0.014380 0.000321 0.000691 0.000272 0
5 2016-02-16 10:45:30 31.69 27.55 25.01 45.12 1001.67 0.85 53.53 185.52 -50.246476 -8.343209 -11.938124 -0.000536 0.019453 0.014380 0.000273 0.000494 -0.000059 0
10 2016-02-16 10:46:20 31.68 27.53 25.00 45.16 1001.72 1.32 53.52 186.24 -51.616473 -6.818130 -11.860839 -0.000530 0.019477 0.014500 0.000268 0.001194 0.000106 0
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
108001 2016-02-29 01:23:30 32.32 28.20 25.57 45.05 1005.74 1.32 50.04 338.15 15.549799 -1.424077 -9.087291 -0.000754 0.017375 0.014826 0.000908 0.000447 0.000149 0
108003 2016-02-29 01:23:50 32.28 28.18 25.57 45.10 1005.76 1.65 50.03 338.91 15.134025 -1.776843 -8.806690 -0.000819 0.017378 0.014974 0.000048 -0.000084 -0.000039 0
108004 2016-02-29 01:24:00 32.30 28.18 25.57 45.11 1005.74 1.70 50.21 338.19 14.799790 -1.695364 -8.895130 -0.000739 0.017478 0.014792 -0.000311 -0.000417 -0.000008 0
108006 2016-02-29 01:24:20 32.29 28.19 25.57 45.02 1005.73 0.81 49.81 339.24 14.333920 -2.173228 -8.694976 -0.000606 0.017275 0.014725 -0.000589 -0.000443 -0.000032 0
108012 2016-02-29 01:25:21 32.27 28.19 25.57 45.01 1005.72 0.44 50.34 342.01 14.364146 -2.974811 -8.287531 -0.000754 0.017800 0.014704 -0.000033 -0.000491 0.000309 0

7275 rows × 19 columns

Altro esempio: se vogliamo cercare i record in cui l’umidità è massima, potremmo utilizzare la proprietà values della serie su cui calcoliamo il valore massimo (altre statistiche comuni sono min,count, mean)

[40]:
df[  (df.humidity == df.humidity.values.max())  ]
[40]:
time_stamp temp_cpu temp_h temp_p humidity pressure pitch roll yaw mag_x mag_y mag_z accel_x accel_y accel_z gyro_x gyro_y gyro_z reset
35068 2016-02-20 12:57:40 31.58 27.41 24.83 60.59 1008.91 1.86 51.78 192.83 -53.325819 10.641053 -6.898934 -0.000657 0.018981 0.014993 0.000608 0.000234 -0.000063 0
35137 2016-02-20 13:09:20 31.60 27.50 24.89 60.59 1008.97 1.78 51.91 208.49 -29.012379 14.546882 -8.387606 -0.000811 0.019145 0.015148 0.000038 -0.000182 0.000066 0

DOMANDA: se ricordi, nel foglio sulle basi dei float, abbiamo detto che comparare float tramite uguaglianza è in realtà una cosa da evitare. Ricordi perchè? Ha veramente importanza in questo caso?

Mostra risposta

4.3 Ordinare

Per avere un NUOVO dataframe ordinato in base a una o più colonne possiamo usare il metodo sort_values:

[41]:
df.sort_values('pressure',ascending=False).head()
[41]:
time_stamp temp_cpu temp_h temp_p humidity pressure pitch roll yaw mag_x mag_y mag_z accel_x accel_y accel_z gyro_x gyro_y gyro_z reset
77602 2016-02-25 12:13:20 32.44 28.31 25.74 47.57 1021.78 1.10 51.82 267.39 -0.797428 10.891803 -15.728202 -0.000612 0.018170 0.014295 -0.000139 -0.000179 -0.000298 0
77601 2016-02-25 12:13:10 32.45 28.30 25.74 47.26 1021.75 1.53 51.76 266.12 -1.266335 10.927442 -15.690558 -0.000661 0.018357 0.014533 0.000152 0.000459 -0.000298 0
77603 2016-02-25 12:13:30 32.44 28.30 25.74 47.29 1021.75 1.86 51.83 268.83 -0.320795 10.651441 -15.565123 -0.000648 0.018290 0.014372 0.000049 0.000473 -0.000029 0
77604 2016-02-25 12:13:40 32.43 28.30 25.74 47.39 1021.75 1.78 51.54 269.41 -0.130574 10.628383 -15.488983 -0.000672 0.018154 0.014602 0.000360 0.000089 -0.000002 0
77608 2016-02-25 12:14:20 32.42 28.29 25.74 47.36 1021.73 0.86 51.89 272.77 0.952025 10.435951 -16.027235 -0.000607 0.018186 0.014232 -0.000260 -0.000059 -0.000187 0

4.4 Esercizio - statistiche meteo

✪ Analizza i dati del Dataframe meteo per trovare:

  • i valori di pressione media, minima e massima

  • la temperatura media

  • le date delle giornate di pioggia

Mostra soluzione
[42]:
# scrivi qui


Media pressione : 986.3408269631689
Minimo pressione : 966.3
Massimo pressione : 998.3
Media temperatura : 6.410701876302988
[42]:
Data Pressione Pioggia Temp
433 05/11/2017 12:15 979.2 0.2 8.6
435 05/11/2017 12:45 978.9 0.2 8.4
436 05/11/2017 13:00 979.0 0.2 8.4
437 05/11/2017 13:15 979.1 0.8 8.2
438 05/11/2017 13:30 979.0 0.6 8.2
... ... ... ... ...
2754 29/11/2017 17:15 976.1 0.2 0.9
2755 29/11/2017 17:30 975.9 0.2 0.9
2802 30/11/2017 05:15 971.3 0.2 1.3
2803 30/11/2017 05:30 971.3 0.2 1.1
2804 30/11/2017 05:45 971.5 0.2 1.1

107 rows × 4 columns

4.5 Esercizio - plot meteo

✪ Mettere in grafico l’andamento delle temperature del dataframe meteo:

[43]:
import matplotlib as mpl
import matplotlib.pyplot as plt
%matplotlib inline

# scrivi qui


Mostra soluzione
[44]:

../_images/pandas_pandas1-intro-sol_117_0.png

4.6 Esercizio - Pressione meteo e pioggia

✪ Nello stesso plot di sopra mostra la pressione e l’ammontare di pioggia.

[45]:
# scrivi qui



Mostra soluzione
[46]:

../_images/pandas_pandas1-intro-sol_123_0.png

5. Valori object e stringhe

In generale, quando vogliamo manipolare oggetti di un tipo conosciuto, diciamo stringhe che hanno il tipo str, possiamo scrivere .str dopo una serie e poi trattare il risultato come se fosse una stringa singola, usando un qualsiasi operatore (es: slice) o un metodo consentito da quella particolare classe, o altri forniti da pandas.

Per il testo in particolare ci sono vari modi di manipolarlo, qua ne indichiamo un paio, per maggiori dettagli vedere la documentazione di pandas.

5.1 Filtrare per valori testuali

Quando vogliamo filtrare per valori testuali, possiamo usare .str.contains, qua per esempio selezioniamo tutte le rilevazioni degli ultimi giorni di febbraio (che hanno quindi il timestamp che contiene 2016-02-2) :

[47]:
df[  df['time_stamp'].str.contains('2016-02-2')  ]
[47]:
time_stamp temp_cpu temp_h temp_p humidity pressure pitch roll yaw mag_x mag_y mag_z accel_x accel_y accel_z gyro_x gyro_y gyro_z reset
30442 2016-02-20 00:00:00 32.30 28.12 25.59 45.05 1008.01 1.47 51.82 51.18 9.215883 -12.947023 4.066202 -0.000612 0.018792 0.014558 -0.000042 0.000275 0.000157 0
30443 2016-02-20 00:00:10 32.25 28.13 25.59 44.82 1008.02 0.81 51.53 52.21 8.710130 -13.143595 3.499386 -0.000718 0.019290 0.014667 0.000260 0.001011 0.000149 0
30444 2016-02-20 00:00:41 33.07 28.13 25.59 45.08 1008.09 0.68 51.69 57.36 7.383435 -13.827667 4.438656 -0.000700 0.018714 0.014598 0.000299 0.000343 -0.000025 0
30445 2016-02-20 00:00:50 32.63 28.10 25.60 44.87 1008.07 1.42 52.13 59.95 7.292313 -13.999682 4.517029 -0.000657 0.018857 0.014565 0.000160 0.000349 -0.000190 0
30446 2016-02-20 00:01:01 32.55 28.11 25.60 44.94 1008.07 1.41 51.86 61.83 6.699141 -14.065591 4.448778 -0.000678 0.018871 0.014564 -0.000608 -0.000381 -0.000243 0
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
110864 2016-02-29 09:24:21 31.56 27.52 24.83 42.94 1005.83 1.58 49.93 129.60 -15.169673 -27.642610 1.563183 -0.000682 0.017743 0.014646 -0.000264 0.000206 0.000196 0
110865 2016-02-29 09:24:30 31.55 27.50 24.83 42.72 1005.85 1.89 49.92 130.51 -15.832622 -27.729389 1.785682 -0.000736 0.017570 0.014855 0.000143 0.000199 -0.000024 0
110866 2016-02-29 09:24:41 31.58 27.50 24.83 42.83 1005.85 2.09 50.00 132.04 -16.646212 -27.719479 1.629533 -0.000647 0.017657 0.014799 0.000537 0.000257 0.000057 0
110867 2016-02-29 09:24:50 31.62 27.50 24.83 42.81 1005.88 2.88 49.69 133.00 -17.270447 -27.793136 1.703806 -0.000835 0.017635 0.014877 0.000534 0.000456 0.000195 0
110868 2016-02-29 09:25:00 31.57 27.51 24.83 42.94 1005.86 2.17 49.77 134.18 -17.885872 -27.824149 1.293345 -0.000787 0.017261 0.014380 0.000459 0.000076 0.000030 0

80427 rows × 19 columns

ATTENZIONE: NON usare l’operatore in:

Potresti essere tentato di usarlo per filtrare ma scoprirai presto che non funziona. Il motivo è che in produce un solo valore ma quando filtriamo vogliamo produrre una serie di booleani con \(n\) valori, uno per riga

[48]:
# Prova a verificare che succede se lo usi:


5.2 Estrarre stringhe

Per estrarre solo il giorno dalla colonna time_stamp, possiamo usare str con l’operatore slice e parentesi quadre:

[50]:
df['time_stamp'].str[8:10]
[50]:
0         16
1         16
2         16
3         16
4         16
          ..
110864    29
110865    29
110866    29
110867    29
110868    29
Name: time_stamp, Length: 110869, dtype: object

6. Operazioni su colonne

Vediamo ora come selezionare, aggiungere e trasformare colonne.

6.1 - Selezionare colonne

Se vogliamo un sotto-insieme di colonne, possiamo indicare i nomi in una lista così:

NOTA: dentro le quadre esterne c’è una semplice lista di stringhe senza df!

[51]:
df[  ['temp_h', 'temp_p', 'time_stamp']  ]
[51]:
temp_h temp_p time_stamp
0 27.57 25.01 2016-02-16 10:44:40
1 27.53 25.01 2016-02-16 10:44:50
2 27.53 25.01 2016-02-16 10:45:00
3 27.52 25.01 2016-02-16 10:45:10
4 27.54 25.01 2016-02-16 10:45:20
... ... ... ...
110864 27.52 24.83 2016-02-29 09:24:21
110865 27.50 24.83 2016-02-29 09:24:30
110866 27.50 24.83 2016-02-29 09:24:41
110867 27.50 24.83 2016-02-29 09:24:50
110868 27.51 24.83 2016-02-29 09:25:00

110869 rows × 3 columns

Come sempre la selezione di colonne non cambia il dataframe originale:

[52]:
df.head()
[52]:
time_stamp temp_cpu temp_h temp_p humidity pressure pitch roll yaw mag_x mag_y mag_z accel_x accel_y accel_z gyro_x gyro_y gyro_z reset
0 2016-02-16 10:44:40 31.88 27.57 25.01 44.94 1001.68 1.49 52.25 185.21 -46.422753 -8.132907 -12.129346 -0.000468 0.019439 0.014569 0.000942 0.000492 -0.000750 20
1 2016-02-16 10:44:50 31.79 27.53 25.01 45.12 1001.72 1.03 53.73 186.72 -48.778951 -8.304243 -12.943096 -0.000614 0.019436 0.014577 0.000218 -0.000005 -0.000235 0
2 2016-02-16 10:45:00 31.66 27.53 25.01 45.12 1001.72 1.24 53.57 186.21 -49.161878 -8.470832 -12.642772 -0.000569 0.019359 0.014357 0.000395 0.000600 -0.000003 0
3 2016-02-16 10:45:10 31.69 27.52 25.01 45.32 1001.69 1.57 53.63 186.03 -49.341941 -8.457380 -12.615509 -0.000575 0.019383 0.014409 0.000308 0.000577 -0.000102 0
4 2016-02-16 10:45:20 31.66 27.54 25.01 45.18 1001.71 0.85 53.66 186.46 -50.056683 -8.122609 -12.678341 -0.000548 0.019378 0.014380 0.000321 0.000691 0.000272 0

6.2 - Aggiungere colonne

E’ possibile ottenere nuove colonne effettuando calcoli da campi di altri colonne in modo molto naturale. Per esempio, qua ricaviamo la nuova colonna mag_tot, cioè il campo magnetico assoluto rilevato dalla stazione spaziale ricavandolo a partire da mag_x, mag_y, e mag_z, e poi la plottiamo:

[53]:
df['mag_tot'] = df['mag_x']**2 + df['mag_y']**2 + df['mag_z']**2
[54]:
df.mag_tot.plot()
plt.show()
../_images/pandas_pandas1-intro-sol_139_0.png

Troviamo quando il campo magnetico era al massimo:

[55]:
df['time_stamp'][df.mag_tot == df.mag_tot.values.max()]
[55]:
96156    2016-02-27 16:12:31
Name: time_stamp, dtype: object

Prova a inserire il valore trovato sul sito isstracker.com/historical per rilevare la posizione in cui il campo magnetico è più forte.

6.2.1 Esercizio - temperatura meteo in Fahrenheit

Nel dataframe meteo, crea una colonna TempFahrenheit con la temperatura misurata in gradi Fahrenheit

Formula per calcolare la conversione dai gradi Celsius (C):

\(Fahrenheit = \frac{9}{5}C + 32\)

[56]:
# scrivi qui


Mostra soluzione
[57]:


       **************  OUTPUT SOLUZIONE  **************
[57]:
Data Pressione Pioggia Temp TempFahrenheit
0 01/11/2017 00:00 995.4 0.0 5.4 41.72
1 01/11/2017 00:15 995.5 0.0 6.0 42.80
2 01/11/2017 00:30 995.5 0.0 5.9 42.62
3 01/11/2017 00:45 995.7 0.0 5.4 41.72
4 01/11/2017 01:00 995.7 0.0 5.3 41.54

6.2.2 Esercizio - Pressione vs Temperatura

Secondo la Legge di Gay-Lussac, in un ambiente chiuso la pressione dovrebbe essere direttamente proporzionale alla temperatura:

\(\frac{P}{T} = k\)

E’ vero per il dataset del meteo? Prova a scoprirlo calcolando direttamente la formula e comparando con i risultati del metodo corr().

Mostra soluzione
[58]:

6.3 Scrivere colonne filtrate con loc

Se vogliamo scrivere solo in alcune righe di colonne (esistenti o nuove), possiamo farlo con la properietà .loc

Per esempio, proviamo a scrivere il valore True nella nuova colonna 'cpu_troppo_calda' nelle sole righe dove la temperatura cpu è eccessiva:

[59]:
df.loc[ (df.temp_cpu > 31.68), 'cpu_troppo_calda' ] = True

Vediamo la tabella risultante (scorri fino in fondo per vedere la nuova colonna). Notiamo come i valori delle righe che non abbiamo filtrato vengono rappresentati con NaN, che letteralmente significa not a number:

[60]:
df.head()
[60]:
time_stamp temp_cpu temp_h temp_p humidity pressure pitch roll yaw mag_x ... mag_z accel_x accel_y accel_z gyro_x gyro_y gyro_z reset mag_tot cpu_troppo_calda
0 2016-02-16 10:44:40 31.88 27.57 25.01 44.94 1001.68 1.49 52.25 185.21 -46.422753 ... -12.129346 -0.000468 0.019439 0.014569 0.000942 0.000492 -0.000750 20 2368.337207 True
1 2016-02-16 10:44:50 31.79 27.53 25.01 45.12 1001.72 1.03 53.73 186.72 -48.778951 ... -12.943096 -0.000614 0.019436 0.014577 0.000218 -0.000005 -0.000235 0 2615.870247 True
2 2016-02-16 10:45:00 31.66 27.53 25.01 45.12 1001.72 1.24 53.57 186.21 -49.161878 ... -12.642772 -0.000569 0.019359 0.014357 0.000395 0.000600 -0.000003 0 2648.484927 NaN
3 2016-02-16 10:45:10 31.69 27.52 25.01 45.32 1001.69 1.57 53.63 186.03 -49.341941 ... -12.615509 -0.000575 0.019383 0.014409 0.000308 0.000577 -0.000102 0 2665.305485 True
4 2016-02-16 10:45:20 31.66 27.54 25.01 45.18 1001.71 0.85 53.66 186.46 -50.056683 ... -12.678341 -0.000548 0.019378 0.014380 0.000321 0.000691 0.000272 0 2732.388620 NaN

5 rows × 21 columns

6.4 Scrivere colonne filtrate con np.where

Pandas è una libreria molto flessibile, e fornisce diversi modi per ottenere gli stessi obbiettivi.

Per esempio, se vogliamo scrivere in una colonna dei valori in corrispondenza di righe per cui un filtro è soddisfatto, e altri valori nel caso non lo sia, si può usare un solo comando np.where.

Proviamo ad aggiungere una colonna 'controllo_pressione' che mi dice se la pressione è sotto o sopra la media (scorri fino in fondo per vederla):

[61]:
pressione_media = df.pressure.values.mean()
[62]:
df['controllo_pressione'] = np.where(df.pressure <= pressione_media, 'sotto', 'sopra')
[63]:
df.iloc[29735:29745]  # selezioniamo delle righe per vedere variazioni
[63]:
time_stamp temp_cpu temp_h temp_p humidity pressure pitch roll yaw mag_x ... accel_x accel_y accel_z gyro_x gyro_y gyro_z reset mag_tot cpu_troppo_calda controllo_pressione
29735 2016-02-19 22:00:51 32.24 28.03 25.52 44.55 1008.11 1.83 52.10 272.50 0.666420 ... -0.000630 0.018846 0.014576 0.000127 0.000002 0.000234 0 481.466349 True sotto
29736 2016-02-19 22:01:00 32.18 28.05 25.52 44.44 1008.11 1.16 51.73 273.26 1.028125 ... -0.000606 0.018716 0.014755 0.000536 0.000550 0.000103 0 476.982306 True sotto
29737 2016-02-19 22:01:11 32.22 28.04 25.52 44.40 1008.12 2.10 52.16 274.66 1.416078 ... -0.000736 0.018774 0.014626 0.000717 0.000991 0.000309 0 484.654588 True sotto
29738 2016-02-19 22:01:20 32.18 28.04 25.52 44.38 1008.14 1.38 52.01 275.22 1.702723 ... -0.000595 0.018928 0.014649 0.000068 0.000222 0.000034 0 485.716793 True sopra
29739 2016-02-19 22:01:30 32.24 28.03 25.52 44.43 1008.10 1.42 51.98 275.80 1.910006 ... -0.000619 0.018701 0.014606 -0.000093 -0.000080 0.000018 0 481.830794 True sotto
29740 2016-02-19 22:01:40 32.26 28.04 25.52 44.37 1008.11 1.47 52.08 277.11 2.413142 ... -0.000574 0.018719 0.014614 0.000451 0.000524 0.000078 0 486.220778 True sotto
29741 2016-02-19 22:01:50 32.22 28.04 25.52 44.49 1008.15 1.60 52.17 278.52 2.929722 ... -0.000692 0.018716 0.014602 0.000670 0.000455 0.000109 0 480.890508 True sopra
29742 2016-02-19 22:02:01 32.21 28.04 25.52 44.48 1008.13 1.47 52.24 279.44 3.163792 ... -0.000639 0.019034 0.014692 0.000221 0.000553 0.000138 0 483.919953 True sopra
29743 2016-02-19 22:02:10 32.23 28.05 25.52 44.45 1008.11 1.88 51.81 280.36 3.486707 ... -0.000599 0.018786 0.014833 -0.000020 0.000230 0.000134 0 476.163984 True sotto
29744 2016-02-19 22:02:21 32.24 28.05 25.52 44.60 1008.12 1.26 51.83 281.22 3.937303 ... -0.000642 0.018701 0.014571 0.000042 0.000156 0.000071 0 478.775309 True sotto

10 rows × 22 columns

6.5 Trasformare colonne

Supponiamo di voler convertire tutti i valori della colonna umidità da float a interi.

Sappiamo che per convertire un singolo float in un intero c’è la funzione predefinita di Python int:

[64]:
int(23.7)
[64]:
23

Come applicare questa funzione a tutti gli elementi della colonna humidity?

Per farlo, possiamo chiamare il metodo transform e passargli l’oggetto funzione int come parametro

NOTA: non ci sono parentesi tonde dopo int !!!

[65]:
df['humidity'].transform(int)
[65]:
0         44
1         45
2         45
3         45
4         45
          ..
110864    42
110865    42
110866    42
110867    42
110868    42
Name: humidity, Length: 110869, dtype: int64

Per chiarire cosa voglia dire passare una funzione, vediamo altri due modi completamente equivalenti che avremmo potuto usare per passare la funzione.

Definire una funzione: Avremmo potuto definire una funzione mia_f come questa (nota che la funzione DEVE RITORNARE qualcosa!)

[66]:
def mia_f(x):
    return int(x)

df['humidity'].transform(mia_f)
[66]:
0         44
1         45
2         45
3         45
4         45
          ..
110864    42
110865    42
110866    42
110867    42
110868    42
Name: humidity, Length: 110869, dtype: int64

funzione lambda: Avremmo potuto usare una funzione lambda, cioè una funzione senza un nome che è definita su una sola linea:

[67]:
df['humidity'].transform( lambda x: int(x) )
[67]:
0         44
1         45
2         45
3         45
4         45
          ..
110864    42
110865    42
110866    42
110867    42
110868    42
Name: humidity, Length: 110869, dtype: int64

Indipendentemente dal modo in cui scegliamo di passare la funzione, il metodo tranform NON cambia il dataframe originale:

[68]:
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 110869 entries, 0 to 110868
Data columns (total 22 columns):
 #   Column               Non-Null Count   Dtype
---  ------               --------------   -----
 0   time_stamp           110869 non-null  object
 1   temp_cpu             110869 non-null  float64
 2   temp_h               110869 non-null  float64
 3   temp_p               110869 non-null  float64
 4   humidity             110869 non-null  float64
 5   pressure             110869 non-null  float64
 6   pitch                110869 non-null  float64
 7   roll                 110869 non-null  float64
 8   yaw                  110869 non-null  float64
 9   mag_x                110869 non-null  float64
 10  mag_y                110869 non-null  float64
 11  mag_z                110869 non-null  float64
 12  accel_x              110869 non-null  float64
 13  accel_y              110869 non-null  float64
 14  accel_z              110869 non-null  float64
 15  gyro_x               110869 non-null  float64
 16  gyro_y               110869 non-null  float64
 17  gyro_z               110869 non-null  float64
 18  reset                110869 non-null  int64
 19  mag_tot              110869 non-null  float64
 20  cpu_troppo_calda     105315 non-null  object
 21  controllo_pressione  110869 non-null  object
dtypes: float64(18), int64(1), object(3)
memory usage: 18.6+ MB

Se vogliamo aggiungere una nuova colonna, diciamo humidity_int, dobbiamo esplicitamente assegnare il risultato di transform alla nuova serie:

[69]:
df['humidity_int'] = df['humidity'].transform( lambda x: int(x) )

Nota come pandas automaticamente inferisce il tipo int64 per la colonna appena creata:

[70]:
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 110869 entries, 0 to 110868
Data columns (total 23 columns):
 #   Column               Non-Null Count   Dtype
---  ------               --------------   -----
 0   time_stamp           110869 non-null  object
 1   temp_cpu             110869 non-null  float64
 2   temp_h               110869 non-null  float64
 3   temp_p               110869 non-null  float64
 4   humidity             110869 non-null  float64
 5   pressure             110869 non-null  float64
 6   pitch                110869 non-null  float64
 7   roll                 110869 non-null  float64
 8   yaw                  110869 non-null  float64
 9   mag_x                110869 non-null  float64
 10  mag_y                110869 non-null  float64
 11  mag_z                110869 non-null  float64
 12  accel_x              110869 non-null  float64
 13  accel_y              110869 non-null  float64
 14  accel_z              110869 non-null  float64
 15  gyro_x               110869 non-null  float64
 16  gyro_y               110869 non-null  float64
 17  gyro_z               110869 non-null  float64
 18  reset                110869 non-null  int64
 19  mag_tot              110869 non-null  float64
 20  cpu_troppo_calda     105315 non-null  object
 21  controllo_pressione  110869 non-null  object
 22  humidity_int         110869 non-null  int64
dtypes: float64(18), int64(2), object(3)
memory usage: 19.5+ MB

7. Altri esercizi

7.1 Esercizio - Inquinanti aria

Proviamo ad analizzare i dati orari delle stazioni di monitoraggio della qualità dell’aria della Provincia Autonoma di Trento validati dall’Agenzia per l’ambiente.

Fonte: dati.trentino.it

7.1.1 - caricare il file

✪ Carica in pandas il file aria.csv

IMPORTANTE 1: metti il dataframe nella variabile aria, così da non confonderlo coi dataframe precedenti

IMPORTANTE 2: metti come encoding 'latin-1' (altrimenti a seconda del tuo sistema operativo potrebbe non caricarlo dando strani messaggi d’errore)

IMPORTANTE 3: se ricevi altri strani messaggi d’errore, aggiungi anche il parametro engine=python

Mostra soluzione
[71]:

# scrivi qui

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 20693 entries, 0 to 20692
Data columns (total 6 columns):
 #   Column           Non-Null Count  Dtype
---  ------           --------------  -----
 0   Stazione         20693 non-null  object
 1   Inquinante       20693 non-null  object
 2   Data             20693 non-null  object
 3   Ora              20693 non-null  int64
 4   Valore           20693 non-null  float64
 5   Unità di misura  20693 non-null  object
dtypes: float64(1), int64(1), object(4)
memory usage: 970.1+ KB

7.1.2 - media inquinanti

✪ Trova la media dei valori di inquinanti PM10 al Parco S. Chiara (media su tutte le giornate). Dovresti ottenere il valore 11.385752688172044

Mostra soluzione
[72]:
# scrivi qui


[72]:
11.385752688172044

7.1.3 - Grafico PM10

✪ Usando plt.plot come visto in un esempio precedente (quindi passandogli direttamente le serie rilevanti di Pandas), mostra in un grafico l’andamento dei valori di inquinanti PM10 nella giornata del 7 Maggio 2019

[75]:
# scrivi qui



Mostra soluzione
[76]:

../_images/pandas_pandas1-intro-sol_195_0.png

7.2 Esercizio - Game of Thrones

Apri con Pandas il file game-of-thrones.csv che contiene gli episodi in varie annate.

  • usa l’encoding UTF-8

  • IMPORTANTE: metti il dataframe nella variabile game, così da non confonderlo coi dataframe precedenti

Data source: Kaggle - License: CC0: Public Domain

7.2.1 Esercizio - fan

Ti viene fornito un dizionario preferiti con gli episodi preferiti di un gruppo di persone, che però non si ricordano esattamente i vari titoli che sono quindi spesso incompleti: Seleziona gli episodi preferiti da Paolo e Chiara

  • assumi che la capitalizzazione in preferiti sia quella corretta

  • NOTA: il dataset contiene insidiose doppie virgolette " attorno ai titoli, ma se scrivi il codice nel modo giusto questo non dovrebbe essere un problema

Mostra soluzione
[77]:

import pandas as pd import numpy as np preferiti = { "Paolo" : 'Winter Is', "Chiara" : 'Wolf and the Lion', "Anselmo" : 'Fire and', "Letizia" : 'Garden of' } # scrivi qui

[77]:
No. overall No. in season Season Title Directed by Written by Novel(s) adapted Original air date U.S. viewers(millions) Imdb rating
0 1 1 1 "Winter Is Coming" Tim Van Patten David Benioff & D. B. Weiss A Game of Thrones 17-Apr-11 2.22 9.1
4 5 5 1 "The Wolf and the Lion" Brian Kirk David Benioff & D. B. Weiss A Game of Thrones 15-May-11 2.58 9.1

7.2.2 Esercizio - prima messa in onda

Seleziona tutti gli episodi che sono stati mandati per la prima volta in onda in un certo anno (colonna Original air date)

  • NOTA: anno ti viene fornito come int

Mostra soluzione
[78]:
anno = 17

# scrivi qui


[78]:
No. overall No. in season Season Title Directed by Written by Novel(s) adapted Original air date U.S. viewers(millions) Imdb rating
61 62 2 7 "Stormborn" Mark Mylod Bryan Cogman Outline from A Dream of Spring and original co... 23-Jul-17 9.27 8.9
62 63 3 7 "The Queen's Justice" Mark Mylod David Benioff & D. B. Weiss Outline from A Dream of Spring and original co... 30-Jul-17 9.25 9.2
63 64 4 7 "The Spoils of War" Matt Shakman David Benioff & D. B. Weiss Outline from A Dream of Spring and original co... 6-Aug-17 10.17 9.8
64 65 5 7 "Eastwatch" Matt Shakman Dave Hill Outline from A Dream of Spring and original co... 13-Aug-17 10.72 8.8
65 66 6 7 "Beyond the Wall" Alan Taylor David Benioff & D. B. Weiss Outline from A Dream of Spring and original co... 20-Aug-17 10.24 9.0
66 67 7 7 "The Dragon and the Wolf" Jeremy Podeswa David Benioff & D. B. Weiss Outline from A Dream of Spring and original co... 27-Aug-17 12.07 9.4

7.3 Esercizio - Strutture sanitarie

✪✪ Esaminiamo ora il dataset SANSTRUT001.csv che contiene le strutture sanitarie del Trentino, e per ciascuna indica il tipo di assistenza che offre (attività clinica, diagnostica, etc), con il CAP e il nome del comune dove si trova.

Fonte dati: dati.trentino.it Licenza: Creative Commons Attribution 4.0

Scrivi una funzione che prende in input un codice comune e una stringa di testo, apre con pandas (encoding UTF-8) e:

  1. STAMPA anche il numero di righe trovate.

  2. RITORNA un dataframe con selezionate solo le righe aventi quel codice comune e che contengono la stringa nella colonna ASSISTENZA. Il dataset ritornato deve avere solo le colonne STRUTTURA, ASSISTENZA, COD_COMUNE, COMUNE.

Mostra soluzione
[79]:
import pandas as pd
import numpy as np

def strutsan(cod_comune, assistenza):
    raise Exception('TODO IMPLEMENT ME !')

[80]:
strutsan(22050, '')  # nessun filtro assistenza
***** SOLUZIONE
Trovate 6 strutture
[80]:
STRUTTURA ASSISTENZA COD_COMUNE COMUNE
0 PRESIDIO OSPEDALIERO DI CAVALESE ATTIVITA` CLINICA 22050 CAVALESE
1 PRESIDIO OSPEDALIERO DI CAVALESE DIAGNOSTICA STRUMENTALE E PER IMMAGINI 22050 CAVALESE
2 PRESIDIO OSPEDALIERO DI CAVALESE ATTIVITA` DI LABORATORIO 22050 CAVALESE
3 CENTRO SALUTE MENTALE CAVALESE ASSISTENZA PSICHIATRICA 22050 CAVALESE
4 CENTRO DIALISI CAVALESE ATTIVITA` CLINICA 22050 CAVALESE
5 CONSULTORIO CAVALESE ATTIVITA` DI CONSULTORIO MATERNO-INFANTILE 22050 CAVALESE
[81]:
strutsan(22205, 'CLINICA')
***** SOLUZIONE
Trovate 16 strutture
[81]:
STRUTTURA ASSISTENZA COD_COMUNE COMUNE
59 PRESIDIO OSPEDALIERO S.CHIARA ATTIVITA` CLINICA 22205 TRENTO
62 CENTRO DIALISI TRENTO ATTIVITA` CLINICA 22205 TRENTO
63 POLIAMBULATORI S.CHIARA ATTIVITA` CLINICA 22205 TRENTO
64 PRESIDIO OSPEDALIERO VILLA IGEA ATTIVITA` CLINICA 22205 TRENTO
73 OSPEDALE CLASSIFICATO S.CAMILLO ATTIVITA` CLINICA 22205 TRENTO
84 NEUROPSICHIATRIA INFANTILE - UONPI 1 ATTIVITA` CLINICA 22205 TRENTO
87 CASA DI CURA VILLA BIANCA SPA ATTIVITA` CLINICA 22205 TRENTO
90 CENTRO SERVIZI SANITARI ATTIVITA` CLINICA 22205 TRENTO
93 PSICOLOGIA CLINICA ATTIVITA` CLINICA 22205 TRENTO
122 ASSOCIAZIONE TRENTINA SCLEROSI MULTIPLA, ONLUS ATTIVITA` CLINICA 22205 TRENTO
123 ANFFAS TRENTINO ONLUS ATTIVITA` CLINICA 22205 TRENTO
124 COOPERATIVA SOCIALE IRIFOR DEL TRENTINO ONLUS ATTIVITA` CLINICA 22205 TRENTO
126 AGSAT ASSOCIAZIONE GENITORI SOGGETTI AUTISTICI... ATTIVITA` CLINICA 22205 TRENTO
127 AZIENDA PUBBLICA SERVIZI ALLA PERSONA - RSA PO... ATTIVITA` CLINICA 22205 TRENTO
130 CST TRENTO ATTIVITA` CLINICA 22205 TRENTO
133 A.P.S.P. 'BEATO DE TSCHIDERER' - AMB. LOGO-AUD... ATTIVITA` CLINICA 22205 TRENTO
[82]:
strutsan(22205, 'LABORATORIO')
***** SOLUZIONE
Trovate 5 strutture
[82]:
STRUTTURA ASSISTENZA COD_COMUNE COMUNE
61 PRESIDIO OSPEDALIERO S.CHIARA ATTIVITA` DI LABORATORIO 22205 TRENTO
85 LABORATORI ADIGE SRL ATTIVITA` DI LABORATORIO 22205 TRENTO
86 LABORATORIO DRUSO SRL ATTIVITA` DI LABORATORIO 22205 TRENTO
89 CASA DI CURA VILLA BIANCA SPA ATTIVITA` DI LABORATORIO 22205 TRENTO
92 CENTRO SERVIZI SANITARI ATTIVITA` DI LABORATORIO 22205 TRENTO

Prosegui

Continua con operazioni un po’ più avanzate