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, columns e values;

  • 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