Vedremo come la libreria pandas faciliti le operazioni viste finora per caricare dati, organizzarli in opportune strutture e analizzarli. Per poter procedere dobbiamo ricaricare le librerie usate finora, nonché il file heroes.csv
. Useremo anche la matplotlib magic che ci permette di visualizzare i grafici direttamente nel notebook.
import csv
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
plt.style.use('fivethirtyeight')
plt.rc('figure', figsize=(5.0, 2.0))
with open('data/heroes.csv', 'r') as heroes_file:
heroes_reader = csv.reader(heroes_file, delimiter=';', quotechar='"')
heroes = list(heroes_reader)[1:]
Va notato come pandas (così come, in generale, gran parte del software open source dedicato all'analisi dei dati) sia caratterizzato da una comunità di sviluppatori molto attiva. Ciò significa che il tempo tra il rilascio di due release successive sia di norma breve: se avete effettuato l'installazione in modo autonomo è quindi possibile che stiate utilizzando una versione più (o meno) recente di quella più aggiornata quando è stato scritto questo documento. Per verificare quale sia la versione installata è sufficiente accedere alla proprietà pd.__version__
:
pd.__version__
'1.1.3'
Utilizzando una versione diversa da questa, può capitare che il risultato ottenuto eseguendo alcune celle sia diverso da quello indicato. Alcune funzionalità potrebbero anche non essere implementate, deprecate o perfino rimosse. Se state invece utilizzando l'ambiente fornito tramite immagine Docker o tramite mybinder non dovreste avere problemi di questo genere.
Una delle classi principali implementate in pandas è Series
. Le sue istanze rappresentano serie di osservazioni di un certo carattere fatto su un insieme di individui. La cella seguente recupera dalla lista heroes
precedentemente creata i nomi dei supereroi e il loro anno di prima apparizione e li utilizza per creare una serie:
years = [int(h[7]) if h[7] else None for h in heroes]
names = [h[0] for h in heroes]
first_appearance = pd.Series(years, index = names)
Nella creazione della lista year
è stata utilizzata una list comprehension in cui l'espressione int(h[7]) if h[7] else None
utilizza un operatore ternario tramite cui la stringa vuota viene trasformata nel valore speciale None
, mentre tutte le altre vengono convertite nel corrispondente intero.
La differenza tra una serie e una lista o una tupla è legata alla possibilità di invocare su di essa delle funzioni specifiche. Inoltre a ogni serie è associato un indice che permette di identificare ogni elemento osservato. Nell'esempio sopra riportato, il primo argomento specificato nel costruttore è una lista (ma sarebbe andata bene anche una tupla) di anni che indicano la prima apparizione di un supereroe e il secondo rappresenta appunto l'indice, che in questo caso è la lista dei corrispondenti nomi. Quando si visualizza una serie, ogni osservazione viene associata al corrispondente elemento usando appunto l'indice:
first_appearance
A-Bomb 2008.0 Abraxas NaN Abomination NaN Adam Monroe NaN Agent 13 NaN ... Alan Scott 1940.0 Amazo 1960.0 Ant-Man 1962.0 Ajax 1998.0 Alex Mercer NaN Length: 735, dtype: float64
La visualizzazione della serie (che in questo caso riporta solo i primi e gli ultimi elementi perché la serie è troppo lunga) termina indicando il tipo di dato usato per rappresentare le varie osservazioni. Nell'esempio precedente viene utilizzato il tipo float64
(pandas utilizza internamente gli array di numpy, in cui è presente un'implementazione dei tipi in virgola mobile diversa da quella standard di python), nonostante i dati originari fossero numeri interi. Ciò è dovuto alla presenza di valori mancanti. Di norma vengono indicati con la sigla NA (dall'inglese "not available"), ma in pandas essi vengono rappresentati utilizzando il concetto di "not a number" dello standard IEEE per la virgola mobile: si noti come tutte le occorrenze di None
nella lista originale siano state automaticamente convertite in np.nan
.
L'accesso ai dati contenuti in una serie può avvenire in due modi:
loc
:(first_appearance['Wonder Woman'], first_appearance.loc['Wonder Woman'])
(1941.0, 1941.0)
iloc
:(first_appearance[128], first_appearance.iloc[128])
(1992.0, 1992.0)
È inoltre possibile utilizzare una notazione simile al list slicing specificando valori dell'indice oppure posizioni. Va però notato che gli slicing basati su indice comprenderanno il primo e l'ultimo valore specificato:
first_appearance['Wonder Girl':'Wonder Woman']
Wonder Girl 1996.0 Wonder Woman 1941.0 dtype: float64
mentre gli slice basati su posizione escluderanno l'ultimo elemento:
first_appearance[60:63]
Vegeta NaN Vixen 1981.0 Valkyrie NaN dtype: float64
L'accesso posizionale può anche fare riferimento a numeri negativi, contando in analogia a liste e tuple a partire dall'ultimo elemento:
first_appearance[-5:]
Alan Scott 1940.0 Amazo 1960.0 Ant-Man 1962.0 Ajax 1998.0 Alex Mercer NaN dtype: float64
È possibile accedere ai primi e ultimi elementi di una serie anche utilizzando le funzioni head
e tail
, che mostrano rispettivamente solo le prime e le ultime righe:
first_appearance.head(7)
A-Bomb 2008.0 Abraxas NaN Abomination NaN Adam Monroe NaN Agent 13 NaN Air-Walker NaN Agent Bob 2007.0 dtype: float64
L'accesso alle liste può anche essere fatto specificando una lista (ma non una tupla) di posizioni al posto di una sola posizione, con l'effetto di ottenere i corrispondenti elementi.
first_appearance[[1, 42, 709]]
Abraxas NaN Warbird NaN Astro Boy NaN dtype: float64
Questo tipo di accesso può essere fatto anche specificando una lista di valori per l'indice. Infine, si può utilizzare una lista di valori booleani in cui True
indica gli elementi da estrarre e False
quelli da filtrare:
first_appearance[[1970 <= y <1975 for y in first_appearance]]
Thundra 1972.0 Swamp Thing 1972.0 Shang-Chi 1973.0 Rambo 1972.0 Ra's Al Ghul 1971.0 Namorita 1972.0 Mockingbird 1971.0 Metron 1971.0 Man-Bat 1970.0 Man-Thing 1971.0 Luke Cage 1972.0 Jennifer Kale 1972.0 Iron Fist 1974.0 Ghost Rider 1972.0 Etrigan 1972.0 Drax the Destroyer 1973.0 Diamondback 1972.0 Doc Samson 1971.0 Darkseid 1970.0 Deathlok 1974.0 Brother Voodoo 1973.0 Blade 1973.0 dtype: float64
Infine, è possibile effettuare delle query su una serie specificando tra parentesi quadre un'espressione logica che indica quali elementi visualizzare, utilizzando la serie come simbolo che ne indica un suo generico elemento:
first_appearance[first_appearance > 2010]
Venompool 2011.0 The Cape 2011.0 Spider-Man 2011.0 Simon Baz 2012.0 Rey 2015.0 Kylo Ren 2015.0 Jyn Erso 2016.0 K-2SO 2016.0 Jessica Cruz 2013.0 Garbage Man 2011.0 Evil Deadpool 2011.0 Captain Cold 2012.0 Bloodhawk 2099.0 dtype: float64
Vediamo ora come utilizzando le serie sia molto più semplice calcolare e visualizzare le frequenze assolute: il metodo value_counts
restituisce un'altra serie in cui gli indici sono i valori osservati e i valori le corrispondenti frequenze assolute, ordinate in senso non crescente.
first_appearance.value_counts()
1964.0 18 1963.0 18 1965.0 14 2004.0 11 1976.0 10 .. 1939.0 1 1956.0 1 1978.0 1 1983.0 1 2013.0 1 Length: 71, dtype: int64
Va notato come il tipo delle frequenze sia, correttamente, intero e come i valori mancanti siano automaticamente esclusi dal calcolo delle frequenze, mentre sono sempre presenti gli outlier. Per ottenere una serie i cui elementi siano ordinati per valore non decrescente della voce nell'indice è sufficiente invocare il metodo sort_index
; già che ci siamo, è un buon momento per eliminare i valori fuori scala dal conteggio effettuando una query sulla serie:
first_app_freq = first_appearance[first_appearance < 2090].value_counts().sort_index()
first_app_freq.head(10)
1933.0 1 1939.0 1 1940.0 9 1941.0 7 1943.0 2 1944.0 2 1945.0 1 1947.0 2 1948.0 1 1950.0 1 dtype: int64
Pandas mette a disposizione l'oggetto plot
per visualizzare graficamente i contenuti di una serie, utilizzando matplotlib dietro le quinte; in particolare, il metodo bar
visualizza un grafico a barre:
# Don't try this at home (men che meno all'esame!)
first_appearance.plot.bar()
plt.show()
Il grafico ottenuto, diciamolo, fa schifo. Questo perché bar
considera un punto per ogni elemento della serie, in cui le ascisse corrispondono alla posizione (zero per la prima osservazione, uno per la seconda e così via, sebbene nel grafico sull'asse delle ascisse vengano poi visualizzati i valori dell'indice) e le ordinate al valore osservato. Per ognuno dei punti così ottenuti viene poi tracciato un segmento che lo congiunge perpendicolarmente all'asse delle ascisse. Il risultato è decisamente poco informativo, sia da un punto di vista grafico (le etichette sull'asse delle ascisse si sovrappongono, così che non si riesce a leggere nulla), sia da un punto di vista analitico: le barre hanno altezze simili e quindi le loro differenze sono poco apprezzabili a colpo d'occhio; inoltre il grafico dipende per esempio dall'ordine in cui sono elencate le osservazioni e non ci permette di solito di trarre alcuna informazione sulla relazione che lega tra loro le osservazioni.
Si ottengono dei risultati decisamente più interessanti se si visualizza un grafico analogo per le frequenze assolute:
first_app_freq.plot.bar()
plt.show()
Il grafico ottenuto è sicuramente migliore di quello precedente, ma rimane il problema di leggibilità dell'asse delle ascisse. Ciò è dovuto al fatto che pandas non inserisce le barre sul grafico nelle ascisse corrispondenti agli anni, ma le posiziona una accanto all'altra, come possiamo renderci conto visualizzando un po' meglio solo alcune delle etichette (in prima istanza non è importante capire come venga generato questo grafico, ma se siete cursori potete leggere l'approfondimento che trovate dopo il commento al grafico stesso):
years = np.arange(1945, 2010, 10)
index_pos = [first_app_freq.index.get_loc(y) for y in years]
first_app_freq.plot.bar()
plt.xticks(index_pos, years)
plt.ylim((0, 18.5))
plt.show()
Si può osservare che tra due valori successivi evidenziati nell'asse delle ascisse intercorre una distanza di dieci anni, ma le etichette non risultano equispaziate: ciò è dovuto al fatto che in realtà la prima barra ha ascissa 1, la seconda ha ascissa 2 e così via, mentre le etichette mostrate sull'asse delle ascisse corrispondono ai valori degli indici.
Per ottenere un grafico simile in cui le ascisse siano effettivamente gli anni di prima apparizione è necessario tornare a utilizzare esplicitamente matplotlib, passando al metodo bar
rispettivamente l'indice e i valori della serie, che si ottengono rispettivamente utilizzando la proprietà index
e invocando il metodo get_values
.
plt.bar(first_app_freq.index, first_app_freq.values)
plt.xlim((1935, 2015))
plt.ylim(0, 18.5)
plt.show()
Consideriamo le seguenti domande:
Per rispondere alla prima domanda dobbiamo isolare le frequenze che corrispondono agli anni di apparizione che vanno dal 1960 in avanti. Notiamo che l'indice della serie contiene i valori degli anni; è quindi possibile utilizzare l'accesso tramite list slicing per recuperare le frequenze degli anni di apparizione che vanno dal 1960 in avanti:
first_app_freq[1960:]
1960.0 8 1961.0 3 1962.0 4 1963.0 18 1964.0 18 1965.0 14 1966.0 6 1967.0 8 1968.0 5 1969.0 2 1970.0 2 1971.0 5 1972.0 9 1973.0 4 1974.0 2 1975.0 10 1976.0 10 1977.0 10 1978.0 1 1979.0 9 1980.0 9 1981.0 8 1982.0 2 1983.0 1 1984.0 6 1985.0 9 1986.0 10 1987.0 9 1988.0 1 1989.0 5 1990.0 4 1991.0 5 1992.0 9 1993.0 10 1994.0 10 1995.0 5 1996.0 2 1997.0 4 1998.0 4 1999.0 5 2000.0 3 2001.0 3 2003.0 4 2004.0 11 2005.0 7 2006.0 7 2007.0 5 2008.0 6 2009.0 1 2010.0 4 2011.0 5 2012.0 2 2013.0 1 2015.0 2 2016.0 2 dtype: int64
A questo punto è sufficiente invocare la funzione sum
sulla sotto-serie individuata per ottenere la somma delle frequenze:
sum(first_app_freq[1960:])
329
La seconda domanda trova risposta in modo analogo, filtrando le frequenze degli anni di apparizione tra il 1940 e il 1966:
sum(first_app_freq[1940:1966])
106
Analogamente, all'ultima domanda si risponde selezionando gli anni fino al 1969:
sum(first_app_freq[:1970])
130
Un modo alternativo per calcolare la somma dei valori in una serie è quella di invocare su di essa l'omonimo metodo sum
. Le serie sono inoltre in tutto e per tutto dei vettori, sui quali è possibile effettuare operazioni algebriche. Consideriamo per esempio le due serie contenenti altezza e peso dei supereroi:
height = pd.Series([float(h[4]) if h[4] else None for h in heroes], index=names)
weight = pd.Series([float(h[5]) if h[5] else None for h in heroes], index=names)
Una prima categoria di operazioni è quella che si ottiene indicando il nome di una serie all'interno di un'espressione aritmetica: il risultato è una nuova serie ottenuta calcolando l'espressione su tutti gli elementi della serie di partenza. Per esempio, la cella seguente crea la serie contenente l'altezza degli eroi misurata in metri e ne visualizza i primi dieci elementi:
(height/100)[:10]
A-Bomb 2.0321 Abraxas NaN Abomination 2.0304 Adam Monroe NaN Agent 13 1.7341 Air-Walker 1.8859 Agent Bob 1.7825 Abe Sapien 1.9124 Abin Sur 1.8552 Angela NaN dtype: float64
Quando si considerano operazioni più complicate, è possibile utilizzare il metodo apply
indicando come suo argomento la funzione da applicare agli elementi della serie. Per esempio, nella cella seguente viene creata una nuova serie ottenuta esprimendo le altezze dei supereroi in metri e successivamente elevando il risultato al quadrato.
height.apply(lambda h: (h/100)**2)[:10]
A-Bomb 4.129430 Abraxas NaN Abomination 4.122524 Adam Monroe NaN Agent 13 3.007103 Air-Walker 3.556619 Agent Bob 3.177306 Abe Sapien 3.657274 Abin Sur 3.441767 Angela NaN dtype: float64
Un'altra importante categoria di operazioni è quella che vede due serie indicate come argomenti di un operatore aritmetico binario. In questo caso verrà ancora creata una nuova serie, in cui l'operazione viene calcolata elemento per elemento nelle serie indicate. Per esempio, la cella seguente crea una nuova serie bmi
contenente l'indice di massa corporea (BMI) dei supereroi (ottenuto dividendo il peso specificato in chilogrammi per il quadrato dell'altezza misurata in metri), e mostra i quindici supereroi con il BMI più elevato.
bmi = weight / height.apply(lambda h: (h/100)**2)
bmi.sort_values(ascending=False)[:15]
Utgard-Loki 2501.321629 Giganta 1607.124545 Red Hulk 137.611973 Darkseid 114.366701 Machine Man 114.083519 Thanos 109.414534 Destroyer 107.579152 Abomination 107.211015 A-Bomb 107.024446 Hulk 105.622909 Bloodaxe 104.160435 Juggernaut 103.216295 King Kong 102.732873 Sasquatch 96.810738 Living Brain 91.318046 dtype: float64
A parte notare Hulk è solo il quindicesimo della classifica, va sottolineato che le operazioni fatte elemento per elemento allineano i vettori corrispondenti alle serie in base all'indice (e non alla posizione). Consideriamo per esempio la seguente cella, in cui vengono selezionati altezze e pesi più o meno plausibili per un essere umano, calcolando poi i corrispondenti BMI.
standard_weight = weight[(weight < 100) & (weight > 40)]
standard_height = height[(height < 210) & (height > 120)]/100
(standard_weight / (standard_height**2))[:15]
A-Bomb NaN Abe Sapien 17.868501 Abin Sur 26.410852 Abomination NaN Absorbing Man NaN Adam Strange 25.952943 Agent 13 20.295282 Agent Bob 25.634923 Agent Zero NaN Air-Walker NaN Ajax 24.245355 Alan Scott 27.725061 Alfred Pennyworth 22.966566 Ammo NaN Angel 20.589542 dtype: float64
Si nota un numero relativamente elevato di NaN
, e ciò è appunto dovuto al fatto che il rapporto alla base del calcolo del BMI viene fatto usando peso e altezza di valori che hanno lo stesso indice. Ora, non è detto che un supereroe che ha un peso plausibile abbia anche un'altezza plausibile, e viceversa. Quello che succede quando si esegue un'operazione tra due serie e solo una di essa è definita in corrispondenza di uno specifico valore dell'indice, il risultato conterrà NaN
per quel valore.
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.
heroes = pd.read_csv('data/heroes.csv', sep=';', index_col=0)
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
Identity | Birth place | Publisher | Height | Weight | Gender | First appearance | Eye color | Hair color | Strength | Intelligence | |
---|---|---|---|---|---|---|---|---|---|---|---|
Name | |||||||||||
A-Bomb | Richard Milhouse Jones | Scarsdale, Arizona | Marvel Comics | 203.21 | 441.95 | M | 2008.0 | Yellow | No Hair | 100.0 | moderate |
Abraxas | Abraxas | Within Eternity | Marvel Comics | NaN | NaN | M | NaN | Blue | Black | 100.0 | high |
Abomination | Emil Blonsky | Zagreb, Yugoslavia | Marvel Comics | 203.04 | 441.98 | M | NaN | Green | No Hair | 80.0 | good |
Adam Monroe | NaN | NaN | NBC - Heroes | NaN | NaN | M | NaN | Blue | Blond | 10.0 | good |
Agent 13 | Sharon Carter | NaN | Marvel Comics | 173.41 | 61.03 | F | NaN | Blue | Blond | NaN | NaN |
Air-Walker | Gabriel Lan | Xandar, a planet in the Tranta system, Androme... | Marvel Comics | 188.59 | 108.23 | M | NaN | Blue | White | 85.0 | average |
Agent Bob | Bob | NaN | Marvel Comics | 178.25 | 81.45 | M | 2007.0 | Brown | Brown | 10.0 | low |
Abe Sapien | Abraham Sapien | NaN | Dark Horse Comics | 191.24 | 65.35 | M | 1993.0 | Blue | No Hair | 30.0 | high |
Abin Sur | NaN | Ungara | DC Comics | 185.52 | 90.90 | M | 1959.0 | Blue | No Hair | 90.0 | average |
Angela | NaN | NaN | Image Comics | NaN | NaN | F | NaN | NaN | NaN | 100.0 | high |
Animal Man | Bernhard Baker | NaN | DC Comics | 183.80 | 83.39 | M | 1965.0 | Blue | Blond | 50.0 | average |
Agent Zero | Christoph Nord | Unrevealed location in former East Germany | Marvel Comics | 191.29 | 104.17 | M | NaN | NaN | NaN | 30.0 | good |
Colin Wagner | NaN | NaN | HarperCollins | NaN | NaN | M | NaN | Grey | Brown | NaN | NaN |
Angel Dust | Christina | NaN | Marvel Comics | 165.78 | 57.21 | F | NaN | Yellow | Black | 55.0 | moderate |
Angel Salvadore | Angel Salvadore Bohusk | NaN | Marvel Comics | 163.57 | 54.67 | F | 2001.0 | Brown | Black | 10.0 | moderate |
Zoom | Hunter Zolomon | NaN | DC Comics | 185.90 | 81.93 | M | NaN | Red | Brown | 10.0 | average |
Lady Deathstrike | Yuriko Oyama | Osaka, Japan | Marvel Comics | 175.85 | 58.89 | F | 1985.0 | Brown | Black | 30.0 | good |
Yoda | Yoda | NaN | George Lucas | 66.29 | 17.01 | M | 1980.0 | Brown | White | 55.0 | high |
Zatanna | Zatanna Zatara | NaN | DC Comics | 170.29 | 57.77 | F | NaN | Blue | Black | 10.0 | high |
Yellowjacket II | Rita DeMara | NaN | Marvel Comics | 165.58 | 52.36 | F | NaN | Blue | Strawberry Blond | 10.0 | average |
Yellowjacket | Hank Pym | Elmsford, New York | Marvel Comics | 183.03 | 83.71 | M | NaN | Blue | Blond | 10.0 | high |
Yellow Claw | NaN | Somewhere in mainland China | Marvel Comics | 188.40 | 95.09 | M | NaN | Blue | No Hair | NaN | NaN |
Absorbing Man | Carl Creel | New York City, New York | Marvel Comics | 193.36 | 122.28 | M | 1964.0 | Blue | No Hair | 80.0 | moderate |
X-Man | Nate Grey | American Northeast of Earth-295 | Marvel Comics | 175.82 | 61.80 | M | 1995.0 | Blue | Brown | 55.0 | high |
X-23 | Laura Kinney | The Facility, location unrevealed | Marvel Comics | 155.61 | 50.39 | F | NaN | Green | Black | 25.0 | good |
Wondra | NaN | NaN | Marvel Comics | NaN | NaN | F | NaN | NaN | NaN | NaN | NaN |
Adam Strange | Adam Strange | Chicago, Illinois | DC Comics | 185.10 | 88.92 | M | 1986.0 | Blue | Blond | 10.0 | good |
Wonder Girl | Cassandra Elizabeth Sandsmark | NaN | DC Comics | 165.74 | 51.33 | F | 1996.0 | Blue | Blond | 90.0 | good |
Wonder Woman | Diana Prince | Themyscira | DC Comics | 183.13 | 74.74 | F | 1941.0 | Blue | Black | 100.0 | high |
Wolverine | Logan | Alberta, Canada | Marvel Comics | 160.70 | 135.21 | M | NaN | Blue | Black | 35.0 | good |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
Ardina | Ardina | NaN | Marvel Comics | 193.98 | 98.68 | F | NaN | White | Orange | 100.0 | good |
Arclight | Philippa Sontag | Vietnam | Marvel Comics | 173.43 | 57.25 | F | 1986.0 | Violet | Purple | 65.0 | moderate |
Ares | NaN | NaN | Marvel Comics | 185.28 | 270.30 | M | NaN | Brown | Brown | 85.0 | good |
Archangel | Warren Kenneth Worthington III | Centerport, Long Island, New York | Marvel Comics | 183.82 | 68.63 | M | 1963.0 | Blue | Blond | 15.0 | good |
Astro Boy | Atom | NaN | NaN | NaN | NaN | M | NaN | Brown | Black | 95.0 | average |
Atom III | Adam Cray | NaN | DC Comics | NaN | NaN | M | NaN | NaN | Red | NaN | NaN |
Aqualad | Garth | Poseidonis, Atlantis | DC Comics | 178.89 | 106.83 | M | 1960.0 | Blue | Black | 45.0 | good |
Ariel | Ariel | NaN | Marvel Comics | 165.35 | 59.17 | F | 1987.0 | Purple | Pink | 10.0 | average |
Aquababy | Arthur Curry, Jr. | NaN | DC Comics | NaN | NaN | M | 1965.0 | Blue | Blond | 20.0 | low |
Aquaman | Orin | Atlantis | DC Comics | 185.71 | 146.96 | M | 1941.0 | Blue | Blond | 85.0 | high |
Apocalypse | En Sabah Nur | Akkaba, Egypt | Marvel Comics | 213.77 | 135.62 | M | 1986.0 | Red | Black | 100.0 | high |
Anti-Monitor | NaN | Moon of Qward | DC Comics | 61.37 | NaN | M | NaN | Yellow | No Hair | 100.0 | high |
Ant-Man II | Scott Lang | Coral Gables, Florida | Marvel Comics | 183.67 | 86.28 | M | 1979.0 | Blue | Blond | 20.0 | good |
Anti-Venom | Eddie Brock | San Francisco, California | Marvel Comics | 229.46 | 358.05 | M | NaN | Blue | Blond | 60.0 | good |
Anti-Spawn | Jason Wynn | NaN | Image Comics | NaN | NaN | M | NaN | NaN | NaN | 60.0 | good |
Annihilus | Annihilus | Planet of Arthros, Sector 17A, Negative Zone | Marvel Comics | 180.94 | 90.11 | M | 1968.0 | Green | No Hair | 80.0 | good |
Angel | Warren Kenneth Worthington III | Centerport, Long Island, New York | Marvel Comics | 183.05 | 68.99 | M | 1969.0 | Blue | Blond | 15.0 | good |
Ammo | NaN | NaN | Marvel Comics | 188.93 | 101.09 | M | 1988.0 | Brown | Black | NaN | NaN |
Arachne | Julia Carpenter | Los Angeles, California | Marvel Comics | 175.15 | 63.75 | F | NaN | Blue | Blond | 50.0 | average |
Angel | Liam | NaN | Dark Horse Comics | NaN | NaN | M | NaN | NaN | NaN | 30.0 | good |
Allan Quatermain | NaN | NaN | Wildstorm | NaN | NaN | M | NaN | NaN | NaN | NaN | NaN |
Alien | Xenomorph | Your chest :) | Dark Horse Comics | 244.30 | 169.66 | M | 1979.0 | NaN | No Hair | 30.0 | average |
Alfred Pennyworth | Alfred Thaddeus Crane Pennyworth | NaN | DC Comics | 178.26 | 72.98 | M | 1943.0 | Blue | Black | 10.0 | good |
Ando Masahashi | Ando Masahashi | NaN | NBC - Heroes | NaN | NaN | M | NaN | NaN | NaN | NaN | NaN |
Alex Woolsly | Alex Woolsly | NaN | NBC - Heroes | NaN | NaN | M | NaN | NaN | NaN | 40.0 | average |
Alan Scott | Alan Ladd Wellington Scott | Gotham City | DC Comics | 180.98 | 90.81 | M | 1940.0 | Blue | Blond | 80.0 | good |
Amazo | NaN | NaN | DC Comics | 257.49 | 173.95 | M | 1960.0 | Red | NaN | 100.0 | good |
Ant-Man | Hank Pym | Elmsford, New York | Marvel Comics | 211.74 | 122.44 | M | 1962.0 | Blue | Blond | 20.0 | high |
Ajax | Francis | NaN | Marvel Comics | 193.34 | 90.63 | M | 1998.0 | Brown | Black | 50.0 | average |
Alex Mercer | Alexander J. Mercer | NaN | Wildstorm | NaN | NaN | M | NaN | NaN | NaN | 80.0 | average |
735 rows × 11 columns
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:
index
, columns
e values
;heroes['Gender']
Name A-Bomb M Abraxas M Abomination M Adam Monroe M Agent 13 F .. Alan Scott M Amazo M Ant-Man M Ajax M Alex Mercer M Name: Gender, Length: 735, dtype: object
heroes['Agent 13':'Air-Walker']
Identity | Birth place | Publisher | Height | Weight | Gender | First appearance | Eye color | Hair color | Strength | Intelligence | |
---|---|---|---|---|---|---|---|---|---|---|---|
Name | |||||||||||
Agent 13 | Sharon Carter | NaN | Marvel Comics | 173.41 | 61.03 | F | NaN | Blue | Blond | NaN | NaN |
Air-Walker | Gabriel Lan | Xandar, a planet in the Tranta system, Androme... | Marvel Comics | 188.59 | 108.23 | M | NaN | Blue | White | 85.0 | average |
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 Charles Francis Xavier Birth place New York, New York Publisher Marvel Comics Height 183.74 Weight 86.89 Gender M First appearance 1963 Eye color Blue Hair color No Hair Strength 10 Intelligence high Name: Professor X, dtype: object
heroes.iloc[42:46]
Identity | Birth place | Publisher | Height | Weight | Gender | First appearance | Eye color | Hair color | Strength | Intelligence | |
---|---|---|---|---|---|---|---|---|---|---|---|
Name | |||||||||||
Warbird | Carol Danvers | Boston, Massachusetts | Marvel Comics | 180.56 | 54.75 | F | NaN | Blue | Blond | NaN | NaN |
Wildfire | Drake Burroughs | NaN | DC Comics | NaN | NaN | M | NaN | NaN | NaN | 35.0 | average |
Vulture | Adrian Toomes | Staten Island, New York City | Marvel Comics | 180.61 | 79.63 | M | NaN | Brown | No Hair | 25.0 | good |
Warp | Emil LaSalle | NaN | DC Comics | 173.42 | 67.42 | M | 1981.0 | Brown | Black | 10.0 | moderate |
È 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 183.74 Weight 86.89 Name: Professor X, dtype: object
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]]
Height | Weight | |
---|---|---|
Name | ||
Tempest | 163.02 | 54.36 |
Supergirl | 165.40 | 54.80 |
Volendo accedere direttamente a un elemento è possibile utilizzare le proprietà at
e iat
:
heroes.at['Superman', 'Strength']
100.0
heroes.iat[500, -1]
'high'
È 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 | Birth place | Publisher | Height | Weight | Gender | First appearance | Eye color | Hair color | Strength | Intelligence | |
---|---|---|---|---|---|---|---|---|---|---|---|
Name | |||||||||||
Sasquatch | Walter Langkowski | Edmonton, Alberta, Canada | Marvel Comics | 305.02 | 900.70 | M | NaN | Red | Orange | 80.0 | good |
Juggernaut | Cain Marko | Berkeley, California | Marvel Comics | 287.95 | 855.82 | M | 1965.0 | Blue | Red | 100.0 | average |
Darkseid | Uxas | NaN | DC Comics | 267.37 | 817.57 | M | 1970.0 | Red | No Hair | 100.0 | high |
Hulk | Bruce Banner | Dayton, Ohio | Marvel Comics | 244.40 | 630.90 | M | 1962.0 | Green | Green | 100.0 | high |
Giganta | Doris Zuel | NaN | DC Comics | 62.65 | 630.80 | F | 1944.0 | Green | Red | 90.0 | high |
heroes.sort_index()[-5:]
Identity | Birth place | Publisher | Height | Weight | Gender | First appearance | Eye color | Hair color | Strength | Intelligence | |
---|---|---|---|---|---|---|---|---|---|---|---|
Name | |||||||||||
Yellowjacket II | Rita DeMara | NaN | Marvel Comics | 165.58 | 52.36 | F | NaN | Blue | Strawberry Blond | 10.0 | average |
Ymir | Ymir | Niffleheim | Marvel Comics | 304.63 | NaN | M | NaN | White | No Hair | 100.0 | average |
Yoda | Yoda | NaN | George Lucas | 66.29 | 17.01 | M | 1980.0 | Brown | White | 55.0 | high |
Zatanna | Zatanna Zatara | NaN | DC Comics | 170.29 | 57.77 | F | NaN | Blue | Black | 10.0 | high |
Zoom | Hunter Zolomon | NaN | DC Comics | 185.90 | 81.93 | M | NaN | Red | Brown | 10.0 | average |
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 | Birth place | Publisher | Height | Weight | Gender | First appearance | Eye color | Hair color | Strength | Intelligence | |
---|---|---|---|---|---|---|---|---|---|---|---|
Name | |||||||||||
A-Bomb | Richard Milhouse Jones | Scarsdale, Arizona | Marvel Comics | 203.21 | 441.95 | M | 2008.0 | Yellow | No Hair | 100.0 | moderate |
Agent Bob | Bob | NaN | Marvel Comics | 178.25 | 81.45 | M | 2007.0 | Brown | Brown | 10.0 | low |
Abe Sapien | Abraham Sapien | NaN | Dark Horse Comics | 191.24 | 65.35 | M | 1993.0 | Blue | No Hair | 30.0 | high |
Abin Sur | NaN | Ungara | DC Comics | 185.52 | 90.90 | M | 1959.0 | Blue | No Hair | 90.0 | average |
Animal Man | Bernhard Baker | NaN | DC Comics | 183.80 | 83.39 | M | 1965.0 | Blue | Blond | 50.0 | average |