Analitiche con Pandas

1. Introduzione

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 questo tutorial vedremo:

  • analisi dati con Pandas

  • plotting con MatPlotLib

  • Esempi con dataset AstroPi

  • Esercizi con dataset meteotrentino

  • mappa regioni italiane con GeoPandas

Che fare

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

pandas
    pandas.ipynb
    pandas-sol.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 pandas.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 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

2. Analisi dei dati di Astro Pi

Proviamo ad analizzare i dati registrati dal Raspberry presente sulla Stazione Spaziale Internazionale, 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

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 Columbus_Ed_astro_pi_datalog.csv

[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('Columbus_Ed_astro_pi_datalog.csv', encoding='UTF-8')
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 110869 entries, 0 to 110868
Data columns (total 20 columns):
ROW_ID        110869 non-null int64
temp_cpu      110869 non-null float64
temp_h        110869 non-null float64
temp_p        110869 non-null float64
humidity      110869 non-null float64
pressure      110869 non-null float64
pitch         110869 non-null float64
roll          110869 non-null float64
yaw           110869 non-null float64
mag_x         110869 non-null float64
mag_y         110869 non-null float64
mag_z         110869 non-null float64
accel_x       110869 non-null float64
accel_y       110869 non-null float64
accel_z       110869 non-null float64
gyro_x        110869 non-null float64
gyro_y        110869 non-null float64
gyro_z        110869 non-null float64
reset         110869 non-null int64
time_stamp    110869 non-null object
dtypes: float64(17), int64(2), object(1)
memory usage: 16.9+ MB

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

NOTA: shape non è seguito da parentesi tonde !

[3]:
df.shape
[3]:
(110869, 20)

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

[4]:
df.describe()
[4]:
ROW_ID 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.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 55435.000000 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 32005.267835 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 1.000000 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% 27718.000000 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% 55435.000000 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% 83152.000000 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 110869.000000 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 ?

Per limitare describe ad una sola colonna come humidity, puoi scrivere così:

[5]:
df['humidity'].describe()
[5]:
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

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

[6]:
df.humidity.describe()
[6]:
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

ATTENZIONE agli spazi!

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

Il metodo head() restituisce le prime righe:

[7]:
df.head()
[7]:
ROW_ID 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 time_stamp
0 1 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 2016-02-16 10:44:40
1 2 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 2016-02-16 10:44:50
2 3 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 2016-02-16 10:45:00
3 4 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 2016-02-16 10:45:10
4 5 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 2016-02-16 10:45:20

Il metodo tail() restituisce le ultime righe:

[8]:
df.tail()
[8]:
ROW_ID 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 time_stamp
110864 110865 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 2016-02-29 09:24:21
110865 110866 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 2016-02-29 09:24:30
110866 110867 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 2016-02-29 09:24:41
110867 110868 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 2016-02-29 09:24:50
110868 110869 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 2016-02-29 09:25:00

La proprietà colums restituisce le intestazioni di colonna:

[9]:
df.columns
[9]:
Index(['ROW_ID', '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', 'time_stamp'],
      dtype='object')

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

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

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

[11]:
df.columns[0]
[11]:
'ROW_ID'
[12]:
df.columns[1]
[12]:
'temp_cpu'

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

[13]:
df.corr()
[13]:
ROW_ID 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
ROW_ID 1.000000 0.361391 0.435289 0.339916 -0.110241 0.243337 -0.007741 -0.390159 -0.034877 -0.035966 -0.025818 0.108138 -0.132291 -0.918587 -0.080899 -0.001906 -0.004913 -0.000211 -0.005202
temp_cpu 0.361391 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.435289 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.339916 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.110241 -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.243337 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.007741 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.390159 -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.034877 -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.035966 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.025818 -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.108138 -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.132291 -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.918587 -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.080899 -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.001906 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.004913 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.000211 -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.005202 -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.1 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: https://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
[14]:
# scrivi qui - crea il dataframe


COLUMNS:

Index(['Data', 'Pressione', 'Pioggia', 'Temp'], dtype='object')

INFO:
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 2878 entries, 0 to 2877
Data columns (total 4 columns):
Data         2878 non-null object
Pressione    2878 non-null float64
Pioggia      2878 non-null float64
Temp         2878 non-null float64
dtypes: float64(3), object(1)
memory usage: 90.1+ KB
None

HEAD():
[14]:
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 nella parte sulla visualizzazione, e oggi lo useremo Matplotlib per disegnare grafici.

3.1 Un esempio

Riprendiamo un esempio usando l’approccio in stile Matlab. Plotteremo una retta passando due liste di coordinate, una per le x e una per le y:

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

x = [1,2,3,4]
y = [2,4,6,8]
plt.plot(x, y) # possiamo direttamente passare liste per le x e y
plt.title('Qualche numero')
plt.show()

../_images/pandas_pandas1-sol_39_0.png

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

[17]:
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:

[18]:
type(x)
[18]:
numpy.ndarray
[19]:
type(y)
[19]:
numpy.ndarray

Si tratta quindi di vettori di NumPy.

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:

[20]:
plt.title('La parabola')
plt.plot(x,y);
../_images/pandas_pandas1-sol_47_0.png
[21]:
plt.xlim([0, 5])
plt.ylim([0,10])
plt.title('La parabola')

plt.gca().set_aspect('equal')
plt.plot(x,y);


../_images/pandas_pandas1-sol_48_0.png

3.2 Grafici matplotlib da strutture pandas

Si possono ricavare grafici direttamente da strutture pandas, sempre 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:

[22]:
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-sol_50_0.png

Proviamo a mettere valori di pressione sull’asse orizzontale, e vedere quali valori di umidità sull’asse verticale corrispondono ad una certa pressione:

[23]:
plt.plot(df['pressure'], df['humidity'])
[23]:
[<matplotlib.lines.Line2D at 0x7f4d7ff7ff50>]
../_images/pandas_pandas1-sol_52_1.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) :

[24]:
df.iloc[6]
[24]:
ROW_ID                          7
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.4473
mag_y                    -7.93731
mag_z                    -12.1886
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
time_stamp    2016-02-16 10:45:41
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 :

[25]:
df.iloc[5:7]
[25]:
ROW_ID 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 time_stamp
5 6 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 2016-02-16 10:45:30
6 7 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 2016-02-16 10:45:41

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

[26]:
df2=df.iloc[12500:15000]
[27]:
plt.plot(df2['pressure'], df2['humidity'])
[27]:
[<matplotlib.lines.Line2D at 0x7f4d7fedddd0>]
../_images/pandas_pandas1-sol_60_1.png
[28]:
df2.humidity.plot(label="Humidity", legend=True)
df2.pressure.plot(secondary_y=True, label="Pressure", legend=True)
[28]:
<AxesSubplot:label='c053845d-24c2-48d0-a523-5088edb1dc97'>
../_images/pandas_pandas1-sol_61_1.png

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:

[29]:
df.ROW_ID >= 6
[29]:
0         False
1         False
2         False
3         False
4         False
          ...
110864     True
110865     True
110866     True
110867     True
110868     True
Name: ROW_ID, Length: 110869, dtype: bool

Vediamo che si tratta di una serie di valori True o False, a seconda se il valore di ROW_ID è maggiore o uguale a 6. Qual’è il tipo di questo risultato?

[30]:
type(df.ROW_ID >= 6)
[30]:
pandas.core.series.Series

In modo analogo (df.ROW_ID >= 6) & (df.ROW_ID <= 10) è una serie di valori True o False, se ROW_ID è contemporaneamente maggiore o uguale a 6 e minore e uguale a 10.

[31]:
type((df.ROW_ID >= 6) & (df.ROW_ID <= 10))
[31]:
pandas.core.series.Series

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

[32]:
df[  (df.ROW_ID >= 6) & (df.ROW_ID <= 10)  ]
[32]:
ROW_ID 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 time_stamp
5 6 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 2016-02-16 10:45:30
6 7 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 2016-02-16 10:45:41
7 8 31.66 27.55 25.01 45.34 1001.70 1.49 53.65 186.08 -50.668232 -7.762600 -12.284196 -0.000523 0.019473 0.014298 -0.000044 0.000436 0.000301 0 2016-02-16 10:45:50
8 9 31.67 27.54 25.01 45.20 1001.72 1.22 53.77 186.55 -50.761529 -7.262934 -11.981090 -0.000522 0.019385 0.014286 0.000358 0.000651 0.000187 0 2016-02-16 10:46:01
9 10 31.67 27.54 25.01 45.41 1001.75 1.63 53.46 185.94 -51.243832 -6.875270 -11.672494 -0.000581 0.019390 0.014441 0.000266 0.000676 0.000356 0 2016-02-16 10:46:10

Quindi se cerchiamo il record in cui la pressione è massima, utilizziamo la proprietà values della serie su cui calcoliamo il valore massimo:

[33]:
df[  (df.pressure == df.pressure.values.max())  ]
[33]:
ROW_ID 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 time_stamp
77602 77603 32.44 28.31 25.74 47.57 1021.78 1.1 51.82 267.39 -0.797428 10.891803 -15.728202 -0.000612 0.01817 0.014295 -0.000139 -0.000179 -0.000298 0 2016-02-25 12:13:20

4.3 Ordinare

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

[34]:
df.sort_values('pressure',ascending=False).head()
[34]:
ROW_ID 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 time_stamp
77602 77603 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 2016-02-25 12:13:20
77601 77602 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 2016-02-25 12:13:10
77603 77604 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 2016-02-25 12:13:30
77604 77605 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 2016-02-25 12:13:40
77608 77609 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 2016-02-25 12:14:20

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
[35]:
# scrivi qui


Media pressione : 986.3408269631689
Minimo pressione : 966.3
Massimo pressione : 998.3
Media temperatura : 6.410701876302988
[35]:
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

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) :

[36]:
df[  df['time_stamp'].str.contains('2016-02-2')  ]
[36]:
ROW_ID 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 time_stamp
30442 30443 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 2016-02-20 00:00:00
30443 30444 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 2016-02-20 00:00:10
30444 30445 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 2016-02-20 00:00:41
30445 30446 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 2016-02-20 00:00:50
30446 30447 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 2016-02-20 00:01:01
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
110864 110865 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 2016-02-29 09:24:21
110865 110866 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 2016-02-29 09:24:30
110866 110867 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 2016-02-29 09:24:41
110867 110868 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 2016-02-29 09:24:50
110868 110869 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 2016-02-29 09:25:00

80427 rows × 20 columns

5.2 Estrarre stringhe

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

[37]:
df['time_stamp'].str[8:10]
[37]:
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!

[38]:
df[  ['temp_h', 'temp_p', 'time_stamp']  ]
[38]:
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:

[39]:
df.head()
[39]:
ROW_ID 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 time_stamp
0 1 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 2016-02-16 10:44:40
1 2 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 2016-02-16 10:44:50
2 3 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 2016-02-16 10:45:00
3 4 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 2016-02-16 10:45:10
4 5 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 2016-02-16 10:45:20

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:

[40]:
df['mag_tot'] = df['mag_x']**2 + df['mag_y']**2 + df['mag_z']**2
df.mag_tot.plot()
[40]:
<AxesSubplot:>
../_images/pandas_pandas1-sol_90_1.png

Troviamo dove il campo magnetico era al massimo:

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

Inserendo il valore trovato sul sito isstracker.com/historical, possiamo rilevare le posizioni in cui il campo magnetico è più forte.

Scrivere solo in alcune righe

La properietà loc ci permette di filtrare righe secondo una proprietà e selezionare una colonna, che può essere anche nuova. In questo caso, per le righe dove la temperatura cpu è eccessiva, scriviamo il valore True nei campi della colonna con intestazione 'Too hot':

[42]:
df.loc[(df.temp_cpu > 31.68),'Too hot'] = 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:

[43]:
df.head()
[43]:
ROW_ID 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 time_stamp mag_tot Too hot
0 1 31.88 27.57 25.01 44.94 1001.68 1.49 52.25 185.21 -46.422753 ... -0.000468 0.019439 0.014569 0.000942 0.000492 -0.000750 20 2016-02-16 10:44:40 2368.337207 True
1 2 31.79 27.53 25.01 45.12 1001.72 1.03 53.73 186.72 -48.778951 ... -0.000614 0.019436 0.014577 0.000218 -0.000005 -0.000235 0 2016-02-16 10:44:50 2615.870247 True
2 3 31.66 27.53 25.01 45.12 1001.72 1.24 53.57 186.21 -49.161878 ... -0.000569 0.019359 0.014357 0.000395 0.000600 -0.000003 0 2016-02-16 10:45:00 2648.484927 NaN
3 4 31.69 27.52 25.01 45.32 1001.69 1.57 53.63 186.03 -49.341941 ... -0.000575 0.019383 0.014409 0.000308 0.000577 -0.000102 0 2016-02-16 10:45:10 2665.305485 True
4 5 31.66 27.54 25.01 45.18 1001.71 0.85 53.66 186.46 -50.056683 ... -0.000548 0.019378 0.014380 0.000321 0.000691 0.000272 0 2016-02-16 10:45:20 2732.388620 NaN

5 rows × 22 columns

Pandas è una libreria molto flessibile, e fornisce diversi modi per ottenere gli stessi obbiettivi. Per esempio, possiamo effettuare la stessa operazione di sopra con il comando np.where come qua sotto. Ad esempio, aggiungiamo una colonna che mi dice se la pressione è sopra o sotto la media.

[44]:
pressione_media = df.pressure.values.mean()
df['check_p'] = np.where(df.pressure <= pressione_media, 'sotto', 'sopra')

6.2.1 Esercizio - temperatura meteo in Fahrenheit

Nel dataframe meteo, crea una colonna Temp (Fahrenheit) con la temperatura misurata in gradi Fahrenheit

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

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

[45]:
# scrivi qui


Mostra soluzione
[46]:


       **************  OUTPUT SOLUZIONE  **************
[46]:
Data Pressione Pioggia Temp Temp (Fahrenheit)
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

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

\(\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
[47]:

6.3 Trasformare colonne

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

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

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

Vorremmo applicare questa funzione a tutti gli elementi della colonna humidity.

Per farlo, possiamo chiamare il metodo transform e passargli la funzione int come parametro

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

[49]:
df['humidity'].transform(int)
[49]:
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!)

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

df['humidity'].transform(mia_f)
[50]:
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:

[51]:
df['humidity'].transform( lambda x: int(x) )
[51]:
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:

[52]:
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 110869 entries, 0 to 110868
Data columns (total 23 columns):
ROW_ID        110869 non-null int64
temp_cpu      110869 non-null float64
temp_h        110869 non-null float64
temp_p        110869 non-null float64
humidity      110869 non-null float64
pressure      110869 non-null float64
pitch         110869 non-null float64
roll          110869 non-null float64
yaw           110869 non-null float64
mag_x         110869 non-null float64
mag_y         110869 non-null float64
mag_z         110869 non-null float64
accel_x       110869 non-null float64
accel_y       110869 non-null float64
accel_z       110869 non-null float64
gyro_x        110869 non-null float64
gyro_y        110869 non-null float64
gyro_z        110869 non-null float64
reset         110869 non-null int64
time_stamp    110869 non-null object
mag_tot       110869 non-null float64
Too hot       105315 non-null object
check_p       110869 non-null object
dtypes: float64(18), int64(2), object(3)
memory usage: 19.5+ MB

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

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

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

[54]:
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 110869 entries, 0 to 110868
Data columns (total 24 columns):
ROW_ID          110869 non-null int64
temp_cpu        110869 non-null float64
temp_h          110869 non-null float64
temp_p          110869 non-null float64
humidity        110869 non-null float64
pressure        110869 non-null float64
pitch           110869 non-null float64
roll            110869 non-null float64
yaw             110869 non-null float64
mag_x           110869 non-null float64
mag_y           110869 non-null float64
mag_z           110869 non-null float64
accel_x         110869 non-null float64
accel_y         110869 non-null float64
accel_z         110869 non-null float64
gyro_x          110869 non-null float64
gyro_y          110869 non-null float64
gyro_z          110869 non-null float64
reset           110869 non-null int64
time_stamp      110869 non-null object
mag_tot         110869 non-null float64
Too hot         105315 non-null object
check_p         110869 non-null object
humidity_int    110869 non-null int64
dtypes: float64(18), int64(3), object(3)
memory usage: 20.3+ MB

7. Raggruppare

Riferimenti:

Per raggruppare oggetti ed effettuare statistiche su ogni gruppo si può usare il metodo groupby. Supponiamo di voler calcolare quante letture di humidity sono state fatte per ciascun valore intero di umidità humidity_int (qua usiamo il metodo pandas groupby, ma per gli istogrammi potresti anche usare numpy)

Dopo il metodo groupby indichiamo prima la colonna su cui raggruppare (humidity_int), poi la colonna su cui effettuare la statistica ('humidity') e infine la statistica da calcolare, in questo caso .count() (altre comuni sono sum(), min(), max() e media mean()):

[55]:
df.groupby(['humidity_int'])['humidity'].count()
[55]:
humidity_int
42     2776
43     2479
44    13029
45    32730
46    35775
47    14176
48     7392
49      297
50      155
51      205
52      209
53      128
54      224
55      164
56      139
57      183
58      237
59      271
60      300
Name: humidity, dtype: int64

Nota che abbiamo ottenuto solo 19 righe. Per avere una serie che riempia tutta la tabella, assegnando a ciascuna riga il conteggio del proprio gruppo, possiamo usare transform così:

[56]:
df.groupby(['humidity_int'])['humidity'].transform('count')
[56]:
0         13029
1         32730
2         32730
3         32730
4         32730
          ...
110864     2776
110865     2776
110866     2776
110867     2776
110868     2776
Name: humidity, Length: 110869, dtype: int64

Come al solito, group_by non modifica il dataframe, se vogliamo che il risultato sia salvato nel dataframe dobbiamo assegnare il risultato ad una nuova colonna:

[57]:
df['Conteggio umidità'] = df.groupby(['humidity_int'])['humidity'].transform('count')
[58]:
df
[58]:
ROW_ID temp_cpu temp_h temp_p humidity pressure pitch roll yaw mag_x ... gyro_x gyro_y gyro_z reset time_stamp mag_tot Too hot check_p humidity_int Conteggio umidità
0 1 31.88 27.57 25.01 44.94 1001.68 1.49 52.25 185.21 -46.422753 ... 0.000942 0.000492 -0.000750 20 2016-02-16 10:44:40 2368.337207 True sotto 44 13029
1 2 31.79 27.53 25.01 45.12 1001.72 1.03 53.73 186.72 -48.778951 ... 0.000218 -0.000005 -0.000235 0 2016-02-16 10:44:50 2615.870247 True sotto 45 32730
2 3 31.66 27.53 25.01 45.12 1001.72 1.24 53.57 186.21 -49.161878 ... 0.000395 0.000600 -0.000003 0 2016-02-16 10:45:00 2648.484927 NaN sotto 45 32730
3 4 31.69 27.52 25.01 45.32 1001.69 1.57 53.63 186.03 -49.341941 ... 0.000308 0.000577 -0.000102 0 2016-02-16 10:45:10 2665.305485 True sotto 45 32730
4 5 31.66 27.54 25.01 45.18 1001.71 0.85 53.66 186.46 -50.056683 ... 0.000321 0.000691 0.000272 0 2016-02-16 10:45:20 2732.388620 NaN sotto 45 32730
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
110864 110865 31.56 27.52 24.83 42.94 1005.83 1.58 49.93 129.60 -15.169673 ... -0.000264 0.000206 0.000196 0 2016-02-29 09:24:21 996.676408 NaN sotto 42 2776
110865 110866 31.55 27.50 24.83 42.72 1005.85 1.89 49.92 130.51 -15.832622 ... 0.000143 0.000199 -0.000024 0 2016-02-29 09:24:30 1022.779594 NaN sotto 42 2776
110866 110867 31.58 27.50 24.83 42.83 1005.85 2.09 50.00 132.04 -16.646212 ... 0.000537 0.000257 0.000057 0 2016-02-29 09:24:41 1048.121268 NaN sotto 42 2776
110867 110868 31.62 27.50 24.83 42.81 1005.88 2.88 49.69 133.00 -17.270447 ... 0.000534 0.000456 0.000195 0 2016-02-29 09:24:50 1073.629703 NaN sotto 42 2776
110868 110869 31.57 27.51 24.83 42.94 1005.86 2.17 49.77 134.18 -17.885872 ... 0.000459 0.000076 0.000030 0 2016-02-29 09:25:00 1095.760426 NaN sotto 42 2776

110869 rows × 25 columns

8. Esercizi col meteo

8.1 Plot meteo

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

[59]:
# scrivi qui


Mostra soluzione
[60]:

[60]:
<AxesSubplot:>
../_images/pandas_pandas1-sol_138_1.png

8.2 Pressione meteo e pioggia

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

[61]:
# scrivi qui



Mostra soluzione
[62]:

../_images/pandas_pandas1-sol_144_0.png

8.3 Temperatura media del meteo

✪✪✪ Calcola la temperatura media giornaliera per ciascun giorno, e mostrala nel plot, così da avere una coppia di nuove colonne come queste:

    Giorno      Temp_media_giorno
01/11/2017      7.983333
01/11/2017      7.983333
01/11/2017      7.983333
    .               .
    .               .
02/11/2017      7.384375
02/11/2017      7.384375
02/11/2017      7.384375
    .               .
    .               .

SUGGERIMENTO 1: aggiungi la colonna 'Giorno' estraendo solo il giorno dalla data. Per farlo usa la funzione .str applicata a tutta la colonna.

SUGGERIMENTO 2: Ci sono vari modi per risolvere il problema:

  • il più efficiente ed elegante è con l’operatore groupby, vedere Pandas trasform - more than meets the eye

  • Come alternative, potresti usare un for per ciclare i giorni. Tipicamente usare un for non è una buona idea con Pandas, perchè con dataset larghi ci può voler molto ad eseguire gli aggiornamenti. Comunque, dato che questo dataset è piccolo a sufficienza, puoi provare ad usare un for per ciclare sui giorni e dovresti ottenere i risultati in un tempo ragionevole

[63]:
# scrivi qui


Mostra soluzione
[64]:


    *******    SOLUZIONE 1 OUTPUT - ricalcola media per ogni riga - lento !

CON TEMPERATURA MEDIA
               Data  Pressione  Pioggia  Temp      Giorno  Temp_media_giorno
0  01/11/2017 00:00      995.4      0.0   5.4  01/11/2017           7.983333
1  01/11/2017 00:15      995.5      0.0   6.0  01/11/2017           7.983333
2  01/11/2017 00:30      995.5      0.0   5.9  01/11/2017           7.983333
3  01/11/2017 00:45      995.7      0.0   5.4  01/11/2017           7.983333
4  01/11/2017 01:00      995.7      0.0   5.3  01/11/2017           7.983333
[64]:
<AxesSubplot:>
../_images/pandas_pandas1-sol_150_2.png
Mostra soluzione
[65]:



********  OUTPUT SOLUZIONE 2
  ricalcola media solo 30 volte usando un dizionario diz_avg,
  più veloce ma ancora non ottimale
               Data  Pressione  Pioggia  Temp      Giorno  Temp_media_giorno
0  01/11/2017 00:00      995.4      0.0   5.4  01/11/2017           7.983333
1  01/11/2017 00:15      995.5      0.0   6.0  01/11/2017           7.983333
2  01/11/2017 00:30      995.5      0.0   5.9  01/11/2017           7.983333
3  01/11/2017 00:45      995.7      0.0   5.4  01/11/2017           7.983333
4  01/11/2017 01:00      995.7      0.0   5.3  01/11/2017           7.983333
[65]:
<AxesSubplot:>
../_images/pandas_pandas1-sol_154_2.png
Mostra soluzione
[66]:


********  OUTPUT SOLUZIONE 3  -  soluzione migliore con groupby e transform

CON TEMPERATURA MEDIA
               Data  Pressione  Pioggia  Temp      Giorno  Temp_media_giorno
0  01/11/2017 00:00      995.4      0.0   5.4  01/11/2017           7.983333
1  01/11/2017 00:15      995.5      0.0   6.0  01/11/2017           7.983333
2  01/11/2017 00:30      995.5      0.0   5.9  01/11/2017           7.983333
3  01/11/2017 00:45      995.7      0.0   5.4  01/11/2017           7.983333
4  01/11/2017 01:00      995.7      0.0   5.3  01/11/2017           7.983333
[66]:
<AxesSubplot:>
../_images/pandas_pandas1-sol_158_2.png

9 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

9.1 - caricare il file

✪ Carica in pandas il file aria.csv

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

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

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

Mostra soluzione
[67]:

# scrivi qui


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

9.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
[68]:
# scrivi qui


[68]:
11.385752688172044

9.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

[71]:
# scrivi qui



Mostra soluzione
[72]:

../_images/pandas_pandas1-sol_176_0.png

10. Unire tabelle

Supponi di voler aggiungere una colonna con la posizione geografica della ISS. Per farlo, avresti bisogno di unire il nostro dataset con un altro che contenga questa informazione. Prendiamo per esempio il dataset iss_coords.csv

[73]:
iss_coords = pd.read_csv('iss-coords.csv', encoding='UTF-8')
[74]:
iss_coords
[74]:
timestamp lat lon
0 2016-01-01 05:11:30 -45.103458 14.083858
1 2016-01-01 06:49:59 -37.597242 28.931170
2 2016-01-01 11:52:30 17.126141 77.535602
3 2016-01-01 11:52:30 17.126464 77.535861
4 2016-01-01 14:54:08 7.259561 70.001561
... ... ... ...
333 2016-02-29 13:23:17 -51.077590 -31.093987
334 2016-02-29 13:44:13 30.688553 -135.403820
335 2016-02-29 13:44:13 30.688295 -135.403533
336 2016-02-29 18:44:57 27.608774 -130.198781
337 2016-02-29 21:36:47 27.325186 -129.893278

338 rows × 3 columns

Notiamo che c’è una colonna timestamp, che sfortunatamente ha un nome leggermente diverse dalla colonna time_stamp (nota l’underscore _) nel dataset original astropi:

[75]:
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 110869 entries, 0 to 110868
Data columns (total 25 columns):
ROW_ID               110869 non-null int64
temp_cpu             110869 non-null float64
temp_h               110869 non-null float64
temp_p               110869 non-null float64
humidity             110869 non-null float64
pressure             110869 non-null float64
pitch                110869 non-null float64
roll                 110869 non-null float64
yaw                  110869 non-null float64
mag_x                110869 non-null float64
mag_y                110869 non-null float64
mag_z                110869 non-null float64
accel_x              110869 non-null float64
accel_y              110869 non-null float64
accel_z              110869 non-null float64
gyro_x               110869 non-null float64
gyro_y               110869 non-null float64
gyro_z               110869 non-null float64
reset                110869 non-null int64
time_stamp           110869 non-null object
mag_tot              110869 non-null float64
Too hot              105315 non-null object
check_p              110869 non-null object
humidity_int         110869 non-null int64
Conteggio umidità    110869 non-null int64
dtypes: float64(18), int64(4), object(3)
memory usage: 21.1+ MB

Per fondere i dataset in base a due colonne, possiamo usare il comando merge così:

[76]:
# ricorda che merge produce un NUOVO dataframe:

geo_astropi = df.merge(iss_coords, left_on='time_stamp', right_on='timestamp')

# merge aggiungere sia la colonna time_stamp che timestamp,
# perciò rimuoviamo la colonna duplicata 'timestamp'

geo_astropi = geo_astropi.drop('timestamp', axis=1)
[77]:
geo_astropi
[77]:
ROW_ID temp_cpu temp_h temp_p humidity pressure pitch roll yaw mag_x ... gyro_z reset time_stamp mag_tot Too hot check_p humidity_int Conteggio umidità lat lon
0 23231 32.53 28.37 25.89 45.31 1006.04 1.31 51.63 34.91 21.125001 ... 0.000046 0 2016-02-19 03:49:00 2345.207992 True sotto 45 32730 31.434741 52.917464
1 27052 32.30 28.12 25.62 45.57 1007.42 1.49 52.29 333.49 16.083471 ... 0.000034 0 2016-02-19 14:30:40 323.634786 True sotto 45 32730 -46.620658 -57.311657
2 27052 32.30 28.12 25.62 45.57 1007.42 1.49 52.29 333.49 16.083471 ... 0.000034 0 2016-02-19 14:30:40 323.634786 True sotto 45 32730 -46.620477 -57.311138
3 46933 32.21 28.05 25.50 47.36 1012.41 0.67 52.40 27.57 15.441683 ... 0.000221 0 2016-02-21 22:14:11 342.159257 True sopra 47 14176 19.138359 -140.211489
4 64572 32.32 28.18 25.61 47.45 1010.62 1.14 51.41 33.68 11.994554 ... 0.000030 0 2016-02-23 23:40:50 264.655601 True sopra 47 14176 4.713819 80.261665
5 68293 32.39 28.26 25.70 46.83 1010.51 0.61 51.91 287.86 6.554283 ... 0.000171 0 2016-02-24 10:05:51 436.876111 True sopra 46 35775 -46.061583 22.246025
6 73374 32.38 28.18 25.62 46.52 1008.28 0.90 51.77 30.80 9.947132 ... -0.000375 0 2016-02-25 00:23:01 226.089258 True sopra 46 35775 47.047346 137.958918
7 90986 32.42 28.34 25.76 45.72 1006.79 0.57 49.85 10.57 7.805606 ... -0.000047 0 2016-02-27 01:43:10 149.700293 True sotto 45 32730 -41.049112 30.193004
8 90986 32.42 28.34 25.76 45.72 1006.79 0.57 49.85 10.57 7.805606 ... -0.000047 0 2016-02-27 01:43:10 149.700293 True sotto 45 32730 -8.402991 -100.981726
9 102440 32.62 28.62 26.02 45.15 1006.06 1.12 50.44 301.74 10.348327 ... -0.000061 0 2016-02-28 09:48:40 381.014223 True sotto 45 32730 50.047523 175.566751

10 rows × 27 columns

10.1 Esercizio - migliorare merge

Se noti, la tabella sopra ha le colonne lat e lon, ma pochissime righe. Perchè? Prova a fondere le tabelle in qualche modo utile in modo da avere tutte le righe originali e tutte le celle di lat e lon riempite.

  • Per altre strategie di merge, leggi l’attributo how Why And How To Use Merge With Pandas in Python

  • Per riempire valori mancanti non usare tecniche di interpolazione, semplicemente metti la posizione della stazione in quel dato giorno o ora.

Mostra soluzione
[78]:
# scrivi qui


[78]:
ROW_ID temp_cpu temp_h temp_p humidity pressure pitch roll yaw mag_x ... reset time_stamp mag_tot Too hot check_p humidity_int Conteggio umidità timestamp lat lon
0 1 31.88 27.57 25.01 44.94 1001.68 1.49 52.25 185.21 -46.422753 ... 20 2016-02-16 10:44:40 2368.337207 True sotto 44 13029 NaN NaN NaN
1 2 31.79 27.53 25.01 45.12 1001.72 1.03 53.73 186.72 -48.778951 ... 0 2016-02-16 10:44:50 2615.870247 True sotto 45 32730 NaN NaN NaN
2 3 31.66 27.53 25.01 45.12 1001.72 1.24 53.57 186.21 -49.161878 ... 0 2016-02-16 10:45:00 2648.484927 NaN sotto 45 32730 NaN NaN NaN
3 4 31.69 27.52 25.01 45.32 1001.69 1.57 53.63 186.03 -49.341941 ... 0 2016-02-16 10:45:10 2665.305485 True sotto 45 32730 NaN NaN NaN
4 5 31.66 27.54 25.01 45.18 1001.71 0.85 53.66 186.46 -50.056683 ... 0 2016-02-16 10:45:20 2732.388620 NaN sotto 45 32730 NaN NaN NaN
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
110866 110865 31.56 27.52 24.83 42.94 1005.83 1.58 49.93 129.60 -15.169673 ... 0 2016-02-29 09:24:21 996.676408 NaN sotto 42 2776 NaN NaN NaN
110867 110866 31.55 27.50 24.83 42.72 1005.85 1.89 49.92 130.51 -15.832622 ... 0 2016-02-29 09:24:30 1022.779594 NaN sotto 42 2776 NaN NaN NaN
110868 110867 31.58 27.50 24.83 42.83 1005.85 2.09 50.00 132.04 -16.646212 ... 0 2016-02-29 09:24:41 1048.121268 NaN sotto 42 2776 NaN NaN NaN
110869 110868 31.62 27.50 24.83 42.81 1005.88 2.88 49.69 133.00 -17.270447 ... 0 2016-02-29 09:24:50 1073.629703 NaN sotto 42 2776 NaN NaN NaN
110870 110869 31.57 27.51 24.83 42.94 1005.86 2.17 49.77 134.18 -17.885872 ... 0 2016-02-29 09:25:00 1095.760426 NaN sotto 42 2776 NaN NaN NaN

110871 rows × 28 columns

11. GeoPandas

ATTENZIONE: Questa parte del tutorial è SPERIMENTALE, mancano commenti

Pandas è anche molto comodo per gestire dati geografici, con l’estensione GeoPandas

Installiamola subito:

Anaconda:

conda install geopandas

e poi

conda install -c conda-forge descartes

Linux/Mac (--user installa nella propria home):

  • python3 -m pip install --user geopandas descartes

11.1 Un esempio semplice con GeoPandas

Faremo un esempio mostrando regioni italiane colorate in base alla popolazione residente:

image0

Quando si parla di mappe, tipicamente vogliamo mostrare delle regioni o nazioni colorate in base ad un valore associato ad ogni zona. Quindi servono sempre almeno due cose:

  1. le forme geometriche delle zone da raffigurare

  2. i valori da associare ad ogni zona da far corrispondere alle gradazioni di colore

Tipicamente questi dati vengono presi da almeno due dataset diversi, uno geografico e uno di statistiche, ma vi troverete spesso con il problema che nel dataset geografico le zone vengono chiamate con un nome o codice diverso da quello del dataset con le statistiche.

Divideremo l’esempio in due parti:

  • nella prima, useremo tabelle già ripulite che trovate nella stessa cartella di questo foglio. Questo ci permetterà di comprendere i meccanismi di base di GeoPandas e del fuzzy matching

  • nella seconda parte, proporremo di risolvere un esercizio completo che prevede lo scaricamento online del file html e pulizia

Vediamo il nostro esempio, in cui le zone geografiche vengono prese dal sito dell’istat da file geografici in formato shapefile. Il file è già salvato nella cartella qui: reg2011/reg2011_g.shp , se volete vedere dove era online guardate basi territoriali qua: https://www.istat.it/it/archivio/104317

11.2 Leggere shapefiles in GeoPandas

Leggiamo con geopandas lo shapefile:

[79]:
import geopandas as gpd

df_regioni = gpd.read_file(filename="reg2011/reg2011_g.shp")
df_regioni.head()
[79]:
COD_REG NOME_REG SHAPE_Leng SHAPE_Area geometry
0 1 PIEMONTE 1.236869e+06 2.539410e+10 POLYGON ((457832.312 5145701.000, 458745.249 5...
1 2 VALLE D'AOSTA/VALLÉE D'AOSTE\r\nVALLE D'AOSTA/... 3.111651e+05 3.259041e+09 POLYGON ((390734.999 5091965.001, 390830.999 5...
2 3 LOMBARDIA 1.411265e+06 2.386270e+10 MULTIPOLYGON (((595736.187 5163715.001, 596126...
3 4 TRENTINO-ALTO ADIGE/SUDTIROL 8.005341e+05 1.360802e+10 POLYGON ((743386.080 5219948.900, 743472.190 5...
4 5 VENETO 1.057856e+06 1.840550e+10 POLYGON ((768209.001 5175597.001, 768220.251 5...

Oltre alla solita tabella di Pandas, notiamo che tra le colonne ci sono dei codice COD_REG per identificare le regioni, i loro nomi NOME_REG e la geometria geometry. Chiamando plot() sul dataframe di geopandas possiamo vedere la cartina risultante:

[80]:
%matplotlib inline
df_regioni.plot()
[80]:
<AxesSubplot:>
../_images/pandas_pandas1-sol_196_1.png

11.3 Prendiamo statistiche da visualizzare

Nel nostro esempio, estraiamo statistiche sulla popolazione delle regioni italiane da una pagina HTML. Metteremo poi i dati estratti in un dataframe Pandas (non GeoPandas) chiamato df_popolazione. Per comodità abbiamo salvato tale pagina nel file popolazione.html (se volete vedere la versione online, andate su questo sito: https://www.tuttitalia.it/regioni/popolazione)

ATTENZIONE: Per il momento puoi ignorare il codice che segue, ci serve solo per caricare i dati nel dataframe df_popolazione

[81]:
import pandas as pd


# prende la riga di una tabella html, e ritorna un dizionario con i dati estratti
def estrai_dizionario(riga_html):
    colonne = riga_html.select('td')
    return dict(name=colonne[1].text,
                population=colonne[2].text.replace('.', '').replace(',', '.'),
                area=colonne[3].text.replace('.', '').replace(',', '.'))

# Estrae la popolazione per regione da popolazione.html, e restituisce un dataframe Pandas (non GeoPandas)
def estrai_popolazione():
    from bs4 import BeautifulSoup
    with open('popolazione.html', encoding='utf-8') as f:
        testo = f.read()
        listona = []    # listona di dizionari, ogni dizionario rappresenta una riga
        # usiamo il parser html5lib invece di lxml perchè il sito è complesso
        soup = BeautifulSoup(testo, 'html5lib')
        righe_html = soup.select('table.ut tr')[1:21]
        for riga_html in righe_html:
            listona.append(estrai_dizionario(riga_html))
        return pd.DataFrame(listona)

Vediamo qui il contenuto del file:

[82]:
df_popolazione = estrai_popolazione()
df_popolazione
[82]:
name population area
0 Lombardia 10019166 23863.65
1 Lazio 5898124 17232.29
2 Campania 5839084 13670.95
3 Sicilia 5056641 25832.39
4 Veneto 4906210 18345.35
5 Em.-Romagna 4448841 22452.78
6 Piemonte 4392526 25387.07
7 Puglia 4063888 19540.90
8 Toscana 3742437 22987.04
9 Calabria 1965128 15221.90
10 Sardegna 1653135 24100.02
11 Liguria 1565307 5416.21
12 Marche 1538055 9401.38
13 Abruzzo 1322247 10831.84
14 Friuli VG 1219191 7924.36
15 Trentino-AA 1062860 13605.50
16 Umbria 888908 8464.33
17 Basilicata 570365 10073.32
18 Molise 310449 4460.65
19 V. d'Aosta 126883 3260.90

Se compariamo i nomi in questa tabella con il dataframe della prima, notiamo subito che parecchi nomi non sono identici. Per esempio, nello shapefile troviamo TRENTINO-ALTO ADIGE/SUDTIROL mentre nelle statistiche c’è Trentino-AA. Volendo creare una tabella unica, occorrerà quindi fare integrazione dati cercando di ottenere un matching tra le righe dei due dataset. Per venti regioni potremmo farla a mano ma chiaramente farlo per migliaia di righe sarebbe estremamente oneroso. Per agevolare questa operazione, ci conviene eseguire una cosiddetta fuzzy join, che cerca stringhe simili nei due dataset e in base ad un misura di similarità tra stringhe stabilisce come associare righe della prima tabella a righe della seconda.

Per

[83]:

def fuzzy_join(df_geo, df_right, name_left, name_right):
    """ Prende:
         - un data frame di geo pandas df_geo che contiene una colonna chiamata name_left
         - un'altro dataframe generico df_right che contiene una colonna chiamata name_right
        Ritorna :
        - un nuovo dataframe che è la join dei due dataframe in base alla similirità tra
          le colonne name_left e name_right

          ATTENZIONE: a volte l'agoritmo di similarità può confondersi e considerare uguale due nomi
                      che invece dovrebbero essere distinti !
                      Per quanto possibile, verificare sempre i risultati manualmente.
    """
    from functools import partial
    from itertools import product
    import difflib
    import heapq
    #from pprint import pprint

    df1 = df_geo.set_index(name_left)
    df1.index = df1.index.str.lower()
    df2 = df_right.set_index(name_right)
    df2.index = df2.index.str.lower()


    def get_matcher_smart(dfl, dfr):
        heap = []
        for l, r in product(dfl.index, dfr.index):
            sm = difflib.SequenceMatcher(lambda x: ' .\n\t', l, r)
            heapq.heappush(heap, (1. - sm.quick_ratio(), l, r))
        ass_l, ass_r, ass_map = set(), set(), {}
        while len(ass_map) < len(dfl):
            score, l, r = heapq.heappop(heap)
            if not (l in ass_l or r in ass_r):
                ass_map[l] = r
                ass_l.add(l)
                ass_r.add(r)
        #pprint(ass_map)
        return dfl.index.map(lambda x: ass_map[x])

    df1.index = get_matcher_smart(df1, df2)

    return df1.join(df2)


[84]:
tabellona = fuzzy_join(df_regioni, df_popolazione, 'NOME_REG', 'name')

tabellona
[84]:
COD_REG SHAPE_Leng SHAPE_Area geometry population area
NOME_REG
piemonte 1 1.236869e+06 2.539410e+10 POLYGON ((457832.312 5145701.000, 458745.249 5... 4392526 25387.07
v. d'aosta 2 3.111651e+05 3.259041e+09 POLYGON ((390734.999 5091965.001, 390830.999 5... 126883 3260.90
lombardia 3 1.411265e+06 2.386270e+10 MULTIPOLYGON (((595736.187 5163715.001, 596126... 10019166 23863.65
trentino-aa 4 8.005341e+05 1.360802e+10 POLYGON ((743386.080 5219948.900, 743472.190 5... 1062860 13605.50
veneto 5 1.057856e+06 1.840550e+10 POLYGON ((768209.001 5175597.001, 768220.251 5... 4906210 18345.35
friuli vg 6 6.674897e+05 7.864294e+09 MULTIPOLYGON (((852211.994 5080672.916, 852270... 1219191 7924.36
liguria 7 8.342245e+05 5.415465e+09 MULTIPOLYGON (((400403.625 4851436.938, 400257... 1565307 5416.21
em.-romagna 8 1.164723e+06 2.245147e+10 MULTIPOLYGON (((760714.748 4937319.399, 760723... 4448841 22452.78
toscana 9 1.316658e+06 2.298443e+10 MULTIPOLYGON (((593650.250 4867988.000, 593553... 3742437 22987.04
umbria 10 6.203152e+05 8.464008e+09 MULTIPOLYGON (((771407.451 4833282.073, 771402... 888908 8464.33
marche 11 6.292090e+05 9.401178e+09 MULTIPOLYGON (((863162.283 4840139.829, 863195... 1538055 9401.38
lazio 12 1.055355e+06 1.722762e+10 MULTIPOLYGON (((802704.568 4594643.932, 802540... 5898124 17232.29
abruzzo 13 6.145137e+05 1.082910e+10 POLYGON ((901880.250 4760558.000, 901910.750 4... 1322247 10831.84
molise 14 4.338747e+05 4.461149e+09 POLYGON ((984090.000 4670910.250, 985154.250 4... 310449 4460.65
campania 15 8.923791e+05 1.366399e+10 MULTIPOLYGON (((925294.173 4528798.912, 925329... 5839084 13670.95
puglia 16 1.176243e+06 1.953708e+10 MULTIPOLYGON (((1141891.750 4588535.750, 11418... 4063888 19540.90
basilicata 17 6.142192e+05 1.007326e+10 MULTIPOLYGON (((1074503.688 4446135.938, 10744... 570365 10073.32
calabria 18 8.381944e+05 1.521668e+10 MULTIPOLYGON (((1084888.411 4414364.607, 10848... 1965128 15221.90
sicilia 19 1.339974e+06 2.582478e+10 MULTIPOLYGON (((803299.052 4188096.575, 803275... 5056641 25832.39
sardegna 20 1.460657e+06 2.409417e+10 MULTIPOLYGON (((432678.313 4492760.313, 432811... 1653135 24100.02
[85]:
tabellona.plot(column='population', cmap='OrRd', edgecolor='k', legend=False)
[85]:
<AxesSubplot:>
../_images/pandas_pandas1-sol_204_1.png

11.4 Esempio di integrazione

ATTENZIONE: QUESTA PARTE E’ INCOMPLETA

Vediamo l’esempio di integrazione completo. Ti serviranno anche requests, beautifulsoup4, e html5lib. Installali così:

Anaconda:

  • conda install requests beautifulsoup4 html5lib

Linux/Mac (--user installa nella propria home):

  • python3 -m pip install --user requests beautifulsoup4 html5lib

Per fare un esempio di integrazione, useremo una pagina HTML con i dati delle regioni italiane:

Per capire come estrarre la popoloziona dall’HTML, guarda il tutorial sull’estrazione

Nel menu basi territoriali qua invece abbiamo dei file geografici in formato shapefile delle regioni:

[86]:
# Scarica la pagina HTML della popolazione, e la salva nel file 'popolazione.html'
def scarica_popolazione():
    from bs4 import BeautifulSoup
    import requests

    r = requests.get("https://www.tuttitalia.it/regioni/popolazione/")
    if r.status_code == 200:
        testo = r.text
        with open('popolazione.html', 'w', encoding='utf-8') as f:
            f.write(testo)
            print("Ho salvato il file 'popolazione.html'")
    else:
        # se il codice non è 200, qualcosa è probabilmente andato storto
        # e blocchiamo l'esecuzione dello script
        raise Exception('Errore durante lo scaricamento : %s' % r)

# scarica_popolazione()
[ ]: