La flessibilità delle strutture dati di Pandas
rende questa libreria il principale motore per importare ed elaborare dati nella DataScience: qualsiasi operazione ci troveremo a
svolgere nel Machine Learning infatti prenderà tipicamente le mosse o da DataFrame Pandas o da array NumPy.
È importante pertanto imparare a manipolare dati presenti in queste strutture in modo da poter preparare il dataset su cui lavorare
nel modo più confacente ai nostri scopi. In questa lezione, daremo proprio uno sguardo a tecniche di questo genere focalizzate soprattutto
nel produrre dinamicamente nuove colonne.
Creazione di una nuova colonna
I dataset su cui lavoriamo offrono dati forniti da un qualche soggetto per la descrizione di un processo. Molto spesso
è necessario saper creare una nuova colonna che rappresenti l'applicazione di una funzione sui dati in esso presenti e che, ad esempio, potrà
esserci utile per proseguire con un'esplorazione preliminare dello spazio di informazioni. Iniziamo creando
un DataFrame per esercitarci, eccolo:
import pandas as pd
atleti = {'nome': ['Simone', 'Elena', 'Mario', 'Noemi'],
'cognome': ['Neri', 'Gialli', 'Rossi', 'Verdi'],
'eta': [16, 19, 21, 15]}
squadra = pd.DataFrame(atleti)
La sua forma sarà questa:
nome cognome eta
0 Simone Neri 16
1 Elena Gialli 19
2 Mario Rossi 21
3 Noemi Verdi 15
Il DataFrame squadra
rappresenta una selezione di atleti molto giovani, non tutti maggiorenni. Supponiamo di voler
vedere a colpo d'occhio, per ogni sportivo, se ha già compiuto 18 anni. Si può fare così:
squadra['eta'] >= 18
ed il risultato sarà una Series (esattamente, pandas.core.series.Series
) che avrà questo contenuto:
0 False
1 True
2 True
3 False
Ciò ha prodotto il risultato che ci interessava ma non ha apportato modifiche al DataFrame originale mentre
ciò che vogliamo imparare a fare qui è espandere il DataFrame con colonne create da noi con operazioni di questo tipo. Pandas
rende il tutto facilissimo, sarà infatti sufficiente indicare il nome della nuova colonna ed offrire una regola che determinerà
come questa dovrà essere popolata. Con la direttiva:
squadra['maggiorenne'] = squadra['eta'] >= 18
il DataFrame squadra
diventa così:
nome cognome eta maggiorenne
0 Simone Neri 16 False
1 Elena Gialli 19 True
2 Mario Rossi 21 True
3 Noemi Verdi 15 False
dove apparirà una nuova colonna, creata direttamente in virtù dell'assegnazione, che mostrerà i valori
booleani richiesti.
Applicazione di funzioni
Oltre a espressioni di questo genere, si può svolgere la stessa operazione con delle funzioni, eventualmente create
da noi. Supponiamo che ogni atleta debba avere associato anche uno username, diciamo per fare accesso ad un qualche sistema
informatico legato sempre alle attività della squadra e che tale username venga scelto automaticamente usando i primi
due caratteri del nome, i primi due del cognome ed associando il tutto ad un numero casuale di quattro cifre (non ci preoccupiamo qui
di problemi come generazione di eventuali alias o nome/cognome troppo corti). Vogliamo che tale funzione elabori una riga alla
volta il DataFrame ed aggiunga lo username creato in una nuova colonna. Questo il codice che useremo:
def genera_username(riga):
from random import randint
return f'{riga.nome[:2].lower()}{riga.cognome[:2].lower()}{randint(1000,9999)}'
Per prima cosa, è fondamentale provarla per essere sicuri di ottenere il comportamento che desideriamo.
Nasce per lavorare su una singola riga del DataFrame (l'argomento in input rappresenta proprio questo) pertanto
faremo una prova con il primo elemento di squadra
:
genera_username(squadra.iloc[0])
Otteniamo in risposta lo username sine4439, correttamente prodotto dal nome Simone, il cognome Neri e
l'aggiunta di un numero a quattro cifre. Per creare ora, una nuova colonna come applicazione di tale funzione riga per riga
dobbiamo rivolgerci al metodo apply
:
squadra['username']=squadra.apply(genera_username, axis=1)
e squadra
conterrà questo (non stupisce che lo username della prima riga sia diverso da quello della prova in quanto
generato in parte casualmente):
nome cognome eta maggiorenne username
0 Simone Neri 16 False sine2233
1 Elena Gialli 19 True elgi2715
2 Mario Rossi 21 True maro1382
3 Noemi Verdi 15 False nove3626
Osserviamo bene quanto fatto. Per prima cosa, vediamo che apply
ha ricevuto come primo argomento il riferimento
alla funzione da noi creata e come secondo un valore per axis
. Quest'ultimo è un parametro fondamentale che specifica
se apply
dovrà lavorare per righe o per colonne: axis=1
significa che ogni riga del DataFrame diventa di volta in volta
l'argomento della funzione mentre axis=0
(impostazione di default) cercherebbe di produrre risultati lavorando su ogni singola colonna ma
in questo caso provocherebbe errore.
Elaborazione indipendente di ogni elemento
Infine, esiste un altro metodo, da non confondere con apply
, chiamato applymap
. La sua particolarità, spesso
utile, consiste nell'applicare una funzione ad ogni elemento del DataFrame. Supponiamo di voler avere un DataFrame in cui in ogni cella
ci sia scritto il tipo di dato del corrispondente elemento di squadra
. Ecco come fare:
squadra.applymap(type)
e questo è cosa otteniamo:
nome cognome eta maggiorenne username
0 <class 'str'> <class 'str'> <class 'int'> <class 'bool'> <class 'str'>
1 <class 'str'> <class 'str'> <class 'int'> <class 'bool'> <class 'str'>
2 <class 'str'> <class 'str'> <class 'int'> <class 'bool'> <class 'str'>
3 <class 'str'> <class 'str'> <class 'int'> <class 'bool'> <class 'str'>
Al posto di ogni elemento abbiamo l'indicazione del suo tipo di dato (stringa per tutti tranne che per eta
e
maggiorenne
) ma il DataFrame originale non sarà stato minimamente intaccato infatti il suo contenuto è rimasto il
medesimo:
In [20]: squadra
Out[20]:
nome cognome eta maggiorenne username
0 Simone Neri 16 False sine2233
1 Elena Gialli 19 True elgi2715
2 Mario Rossi 21 True maro1382
3 Noemi Verdi 15 False nove3626