5.2. Dataframe#
Un dataframe è una collezione di serie che hanno lo stesso indice, ed è
quindi un insieme di osservazioni di vari caratteri per una popolazione di
individui. Tra i vari modi che sono disponibili in pandas per creare un
dataframe, noi faremo riferimento al metodo read_csv della classe
pd.DataFrame, che permette di leggere i contenuti di un file in formato CSV e
convertirli automaticamente in un dataframe.
import pandas as pd
heroes = pd.read_csv('data/heroes.csv', index_col='name',
usecols=['name', 'identity', 'intelligence', 'strength',
'first_appearance', 'alignment', 'height', 'weight',
'eye_color', 'hair_color'])
Usando lo stesso file a cui abbiamo fatto riferimento nei paragrafi precedenti,
è stato necessario utilizzare l'argomento opzionale sep per indicare il
carattere usato per separare i campi in ogni record. La visualizzazione dei
dataframe viene automaticamente formattata in un formato tabellare facile da
leggere se si utilizza jupyter:
heroes.head()
| identity | intelligence | strength | first_appearance | alignment | height | weight | eye_color | hair_color | |
|---|---|---|---|---|---|---|---|---|---|
| name | |||||||||
| Laufey | NaN | High | 100 | 2018.0 | Good | NaN | NaN | Blue | Strawberry Blond |
| Laugh-a-Lot Bear | NaN | Average | 45 | NaN | Good | NaN | NaN | Black | Orange |
| Laughing Jack | NaN | Average | 1 | NaN | Bad | 213.4 | NaN | Brown | Black |
| Laura Moon | NaN | Average | 30 | NaN | Neutral | NaN | NaN | NaN | NaN |
| Laura Palmer | Laura Palmer | High | 100 | NaN | NaN | NaN | NaN | NaN | NaN |
Ci riferiremo spesso alle righe e alle colonne di un dataframe per indicare rispettivamente le osservazioni e i caratteri. Per esempio la prima riga si riferisce all'osservazione relativa ad "A-Bomb", mentre la prima colonna corrisponde al carattere "identity". Vi sono molti modi per interagire con un dataframe:
l'insieme degli indici, dei caratteri e dei valori si ottengono, nell'ordine, alle proprietà
index,columnsevalues;una serie corrispondente a una colonna/carattere può essere selezionata usando una sintassi simile a quella dei dizionari, specificando il nome del carattere come chiave:
heroes['eye_color']
name
Laufey Blue
Laugh-a-Lot Bear Black
Laughing Jack Brown
Laura Moon NaN
Laura Palmer NaN
...
Aphoom-Zhah NaN
Aphrodite Pink
Aphrodite NaN
Aphrodite Blue
Aphrodite Blue
Name: eye_color, Length: 17355, dtype: object
Nomenclatura
In alternativa è possibile usare una sintassi basata su dot notation in cui
il nome della colonna, senza essere racchiuso tra apici, segue il dataframe.
In altre parole, heroes['Gender'] e heroes.Gender sono equivalenti. Questa
seconda notazione è però utilizzabile solamente se non vi sono spazi nei nomi
delle colonne. Nel seguito utilizzeremo quindi sempre la prima delle due
notazioni.
tramite uno slicing sulle posizioni o sui valori dell'indice è possibile selezionare un sottoinsieme delle righe del dataframe (e come nel caso delle serie, l'estremo superiore è incluse se si usano gli indici ed escluso se si usano le posizioni):
heroes['Agent 13':'Agent 711']
| identity | intelligence | strength | first_appearance | alignment | height | weight | eye_color | hair_color | |
|---|---|---|---|---|---|---|---|---|---|
| name | |||||||||
| Agent 13 | Sharon Carter | Good | 1 | NaN | Good | 172.7 | 61.2 | Blue | Blond |
| Agent 33 | Kara Palamas | Average | 1 | NaN | Bad | NaN | NaN | NaN | Black |
| Agent 355 | NaN | Average | 1 | NaN | Good | NaN | NaN | Brown | Brown |
| Agent 37 | Dick Grayson | Good | 5 | NaN | NaN | 177.8 | 79.4 | Blue | Black |
| Agent 47 | Agent 47 | High | 8 | NaN | Neutral | 188.0 | 85.0 | Blue | No Hair |
| Agent 47 (2015) | NaN | Good | 6 | NaN | Good | NaN | NaN | Blue | No Hair |
| Agent 711 | Jacqueline | Average | 1 | NaN | Good | NaN | NaN | Brown | Brown |
Queste modalità di accesso possono effettivamente creare confusione: usando una
sintassi molto simile, specificando un valore si accede a una colonna e
specificando uno slice si accede a un insieme di righe. Per scrivere codice
più chiaro è meglio selezionare le righe utilizzando le proprietà loc e
iloc nello stesso modo in cui queste funzionano per le serie, con la
differenza che quando queste sono usate specificando un solo valore, viene
restituita una serie, e quando sono utilizzate con uno slice o con una lista
viene restituito un dataframe.
heroes.loc['Professor X']
| identity | intelligence | strength | first_appearance | alignment | height | weight | eye_color | hair_color | |
|---|---|---|---|---|---|---|---|---|---|
| name | |||||||||
| Professor X | Charles Francis Xavier | High | 1 | 1963.0 | Good | 182.9 | 86.2 | Blue | No Hair |
| Professor X | Charles Francis Xavier | Good | 1 | NaN | Good | 170.2 | 67.1 | Blue | No Hair |
| Professor X | Charles Xapier | Good | 1 | 2008.0 | Good | NaN | NaN | Black | Brown |
heroes.iloc[42:46]
| identity | intelligence | strength | first_appearance | alignment | height | weight | eye_color | hair_color | |
|---|---|---|---|---|---|---|---|---|---|
| name | |||||||||
| Legion | David Haller | High | 25 | NaN | Neutral | NaN | NaN | NaN | NaN |
| Legion (Delphic) | David Haller | High | 100 | NaN | NaN | NaN | NaN | White | Black |
| Legolas | Legolas Of Mirkwood | Good | 18 | NaN | Good | 203.2 | 94.8 | Blue | Blond |
| Lei-Kung | Lei-Kung | Good | 45 | NaN | Good | NaN | NaN | Brown | No Hair |
È inoltre possibile selezionare una o più righe e visualizzare solo un
sottoinsieme dei caratteri, passando a loc o iloc un secondo argomento in
cui si specificano i caratteri da mostrare, utilizzando anche in questo caso un
valore, una lista di valori oppure uno slice:
heroes.loc['Professor X', 'height':'weight']
| height | weight | |
|---|---|---|
| name | ||
| Professor X | 182.9 | 86.2 |
| Professor X | 170.2 | 67.1 |
| Professor X | NaN | NaN |
Va notato che loc accetta solo valori simbolici, mentre iloc solamente
posizioni, e ciò riguarda anche il loro secondo argomento:
heroes.iloc[[106, 103], [3, 4]]
| first_appearance | alignment | |
|---|---|---|
| name | ||
| Leviathan | NaN | NaN |
| Levi Ackerman | NaN | Good |
Volendo accedere direttamente a un elemento è possibile utilizzare le proprietà at e iat:
heroes.at['Superman', 'strength']
name
Superman 100
Superman 100
Superman 100
Superman 100
Superman 100
Superman 100
Superman 100
Superman 100
Superman 100
Superman 0
Superman 100
Superman 100
Superman 100
Superman 100
Superman 100
Superman 100
Superman 100
Superman 100
Superman 100
Superman 100
Superman 100
Superman 100
Superman 100
Superman 100
Superman 100
Superman 75
Superman 85
Superman 100
Superman 100
Superman 100
Superman 100
Superman 100
Superman 100
Superman 0
Superman 95
Superman 100
Superman 100
Superman 50
Superman 100
Superman 85
Superman 100
Superman 100
Name: strength, dtype: int64
heroes.iat[500, -1]
'Pink'
Nomenclatura
È anche possibile utilizzare loc e iloc per estrarre un singolo elemento:
per esempio, heroes.loc['Superman', 'strength'] equivale alla prima delle due
istruzioni appena elencate. Tuttavia, at e iat sono implementate in modo da
essere più efficienti.
È infine possibile riordinare le righe di un dataframe invocando i metodi
sort_values e sort_index: il primo basa l'ordinamento sul valore di una
colonna, il cui nome va specificato tramite l'argomento by e il secondo è
invece basato sui valori dell'indice. È inoltre possibile indicare un valore
booleano per l'argomento ascending che permette di ordinare in verso
crescente o decrescente.
heroes.sort_values(by='weight', ascending=False)[:5]
| identity | intelligence | strength | first_appearance | alignment | height | weight | eye_color | hair_color | |
|---|---|---|---|---|---|---|---|---|---|
| name | |||||||||
| Obsidian | Obsidian | High | 100 | NaN | Good | 10000.0 | 4.535920e+10 | Orange | Black |
| King Joe | King Joe | Average | 80 | NaN | Neutral | 5500.0 | 4.800000e+08 | NaN | No Hair |
| Arch Belial | Belial | High | 95 | NaN | Bad | 30000.0 | 3.000000e+08 | Orange | No Hair |
| Bagan (SNES) | Bagan | Average | 80 | 1993.0 | Bad | 15000.0 | 2.800000e+08 | NaN | No Hair |
| Biollante (Heisei) | Erika Shiragami | Average | 75 | 1989.0 | Neutral | 12000.0 | 2.000000e+08 | NaN | NaN |
heroes.sort_index()[-5:]
| identity | intelligence | strength | first_appearance | alignment | height | weight | eye_color | hair_color | |
|---|---|---|---|---|---|---|---|---|---|
| name | |||||||||
| Zzxz (Klyntar Symbiote) | Zzzxx | Average | 30 | 2009.0 | NaN | NaN | NaN | White | No Hair |
| Zzzax | NaN | Moderate | 30 | NaN | Bad | 1219.0 | NaN | No eyes | No Hair |
| victorion (Enigma of Combination) | victorion | High | 100 | NaN | NaN | NaN | NaN | NaN | NaN |
| Águila | Alejandro Montoya | Moderate | 1 | NaN | NaN | NaN | NaN | NaN | NaN |
| Ōetsu Nimaiya | Ōetsu Nimaiya | Good | 50 | NaN | Good | NaN | 62.0 | NaN | Black |
Va notato che entrambi i metodi restituiscono una copia del dataframe. Anche per i dataframe è possibile utilizzare una lista di valori booleani che identificano le righe da selezionare, e tale lista può essere prodotta effettuando una query. In questo caso però le condizioni possono riguardare le varie colonne, ognuna delle quali va specificata usando una delle due sintassi precedentemente introdotte (quella analoga ai dizionari oppure quella basata su dot notation). Per esempio possiamo selezionare gli eroi per cui l'anno di apparizione esiste e rappresenta un valore non fuori scala nel modo seguente:
heroes_with_year = heroes[heroes['first_appearance'] > 1900]
heroes_with_year.head()
| identity | intelligence | strength | first_appearance | alignment | height | weight | eye_color | hair_color | |
|---|---|---|---|---|---|---|---|---|---|
| name | |||||||||
| Laufey | NaN | High | 100 | 2018.0 | Good | NaN | NaN | Blue | Strawberry Blond |
| Layla Miller | Layla Rose Miller-Madrox | Good | 1 | 2005.0 | Good | 170.0 | 59.0 | Green | Strawberry Blond |
| Lazav | Lazav | High | 13 | 2013.0 | Bad | NaN | NaN | NaN | NaN |
| Leapfrog | Eugene Paul Patilio Jr. | Average | 1 | 2022.0 | Good | 180.3 | 84.1 | Blue | Blond |
| Leatherface | Jed Sawyer | Moderate | 10 | 1974.0 | Bad | 198.1 | 98.4 | Brown | Brown |