Voglio convertire una tabella, rappresentata come una lista di liste, in un DataFrame Pandas. Come esempio estremamente semplificato:
a = [['a', '1.2', '4.2'], ['b', '70', '0.03'], ['x', '5', '0']]
df = pd.DataFrame(a)
Qual è il modo migliore per convertire le colonne nei tipi appropriati, in questo caso le colonne 2 e 3 in float? C'è un modo per specificare i tipi durante la conversione in DataFrame? O è meglio creare prima il DataFrame e poi fare un ciclo attraverso le colonne per cambiare il tipo per ogni colonna? Idealmente vorrei farlo in modo dinamico perché ci possono essere centinaia di colonne e non voglio specificare esattamente quali colonne sono di quale tipo. Tutto quello che posso garantire è che ogni colonna contenga valori dello stesso tipo.
Avete tre opzioni principali per convertire i tipi in pandas:
to_numeric()
- fornisce funzionalità per convertire in modo sicuro tipi non numerici (ad esempio stringhe) in un tipo numerico adatto. (Vedere anche to_datetime()
e to_timedelta()
.astype()
- converte (quasi) qualsiasi tipo in (quasi) qualsiasi altro tipo (anche se non è necessariamente sensato farlo). Permette anche di convertire in tipi categorial (molto utile).infer_objects()
- un metodo di utilità per convertire le colonne di oggetti che contengono oggetti Python in un tipo pandas, se possibile.
Continuate a leggere per spiegazioni più dettagliate e per l'uso di ciascuno di questi metodi.a_numero()
Il modo migliore per convertire una o più colonne di un DataFrame in valori numerici è usare pandas.to_numeric()
.
Questa funzione cercherà di cambiare gli oggetti non numerici (come le stringhe) in numeri interi o in numeri in virgola mobile, come appropriato.
L'input a to_numeric()
è una serie o una singola colonna di un DataFrame.
>>> s = pd.Series(["8", 6, "7.5", 3, "0.9"]) # mixed string and numeric values
>>> s
0 8
1 6
2 7.5
3 3
4 0.9
dtype: object
>>> pd.to_numeric(s) # convert everything to float values
0 8.0
1 6.0
2 7.5
3 3.0
4 0.9
dtype: float64
Come potete vedere, viene restituita una nuova serie. Ricordatevi di assegnare questo output a una variabile o a un nome di colonna per continuare a usarlo:
# convert Series
my_series = pd.to_numeric(my_series)
# convert column "a" of a DataFrame
df["a"] = pd.to_numeric(df["a"])
Si può anche usare per convertire più colonne di un DataFrame tramite il metodo apply()
:
# convert all columns of DataFrame
df = df.apply(pd.to_numeric) # convert all columns of DataFrame
# convert just columns "a" and "b"
df[["a", "b"]] = df[["a", "b"]].apply(pd.to_numeric)
Finché i tuoi valori possono essere tutti convertiti, questo è probabilmente tutto ciò di cui hai bisogno.
Ma cosa succede se alcuni valori non possono essere convertiti in un tipo numerico?
to_numeric()
accetta anche un argomento di parola chiave errors
che permette di forzare i valori non numerici ad essere NaN
, o semplicemente ignorare le colonne che contengono questi valori.
Ecco un esempio che utilizza una serie di stringhe s
che ha il dtype dell'oggetto:
>>> s = pd.Series(['1', '2', '4.7', 'pandas', '10'])
>>> s
0 1
1 2
2 4.7
3 pandas
4 10
dtype: object
Il comportamento predefinito è quello di sollevare se non può convertire un valore. In questo caso, non riesce a gestire la stringa 'pandas':
>>> pd.to_numeric(s) # or pd.to_numeric(s, errors='raise')
ValueError: Unable to parse string
Piuttosto che fallire, potremmo volere che 'pandas' sia considerato un valore numerico mancante/cattivo. Possiamo forzare i valori non validi a NaN
come segue, usando l'argomento della parola chiave errors
:
>>> pd.to_numeric(s, errors='coerce')
0 1.0
1 2.0
2 4.7
3 NaN
4 10.0
dtype: float64
La terza opzione per gli errori
è semplicemente ignorare l'operazione se si incontra un valore non valido:
>>> pd.to_numeric(s, errors='ignore')
# the original Series is returned untouched
Quest'ultima opzione è particolarmente utile quando si vuole convertire l'intero DataFrame, ma non si sa quale delle nostre colonne può essere convertita in modo affidabile in un tipo numerico. In questo caso basta scrivere:
df.apply(pd.to_numeric, errors='ignore')
La funzione sarà applicata ad ogni colonna del DataFrame. Le colonne che possono essere convertite in un tipo numerico saranno convertite, mentre le colonne che non possono (ad esempio contengono stringhe non numeriche o date) saranno lasciate stare.
Per impostazione predefinita, la conversione con to_numeric()
darà un dtype int64
o float64
(o qualsiasi larghezza intera sia nativa per la propria piattaforma).
Di solito è quello che volete, ma cosa succede se volete risparmiare un po' di memoria e usare un tipo più compatto, come float32
o int8
?
La funzione to_numeric()
dà la possibilità di fare il downcast a 'integer', 'signed', 'unsigned', 'float'. Ecco un esempio per una semplice serie s
di tipo intero:
>>> s = pd.Series([1, 2, -7])
>>> s
0 1
1 2
2 -7
dtype: int64
Il downcasting a 'integer' usa il più piccolo intero possibile che può contenere i valori:
>>> pd.to_numeric(s, downcast='integer')
0 1
1 2
2 -7
dtype: int8
Il downcasting a 'float' similmente sceglie un tipo floating più piccolo del normale:
>>> pd.to_numeric(s, downcast='float')
0 1.0
1 2.0
2 -7.0
dtype: float32
astype()
Il metodo astype()
ti permette di essere esplicito riguardo al tipo che vuoi che il tuo DataFrame o la tua serie abbiano. È molto versatile in quanto si può provare a passare da un tipo all'altro.
Scegliete semplicemente un tipo: potete usare un dtype NumPy (ad esempio np.int16
), alcuni tipi Python (ad esempio bool), o tipi specifici di pandas (come il dtype categorical).
Chiamate il metodo sull'oggetto che volete convertire e astype()
proverà a convertirlo per voi:
# convert all DataFrame columns to the int64 dtype
df = df.astype(int)
# convert column "a" to int64 dtype and "b" to complex type
df = df.astype({"a": int, "b": complex})
# convert Series to float16 type
s = s.astype(np.float16)
# convert Series to Python strings
s = s.astype(str)
# convert Series to categorical type - see docs for more details
s = s.astype('category')
Notate che ho detto "try" - se astype()
non sa come convertire un valore nella serie o nel DataFrame, solleverà un errore. Per esempio se hai un valore NaN
o inf
otterrai un errore cercando di convertirlo in un intero.
A partire da pandas 0.20.0, questo errore può essere soppresso passando errors='ignore'
. Il vostro oggetto originale verrà restituito intatto.
astype()
è potente, ma a volte convertirà valori "erroneamente". Per esempio:
>>> s = pd.Series([1, 2, -7])
>>> s
0 1
1 2
2 -7
dtype: int64
Questi sono piccoli numeri interi, quindi che ne dite di convertire in un tipo senza segno a 8 bit per risparmiare memoria?
>>> s.astype(np.uint8)
0 1
1 2
2 249
dtype: uint8
pd.to_numeric(s, downcast='unsigned')
invece potrebbe aiutare a prevenire questo errore.infer_objects()
La versione 0.21.0 di pandas ha introdotto il metodo infer_objects()
per convertire le colonne di un DataFrame che hanno un tipo di dato oggetto in un tipo più specifico (conversioni soft).
Per esempio, ecco un DataFrame con due colonne di tipo oggetto. Una contiene numeri interi reali e l'altra contiene stringhe che rappresentano numeri interi:
>>> df = pd.DataFrame({'a': [7, 1, 5], 'b': ['3','2','1']}, dtype='object')
>>> df.dtypes
a object
b object
dtype: object
Usando infer_objects()
, si può cambiare il tipo della colonna 'a' in int64:
>>> df = df.infer_objects()
>>> df.dtypes
a int64
b object
dtype: object
La colonna 'b' è stata lasciata da sola poiché i suoi valori erano stringhe, non interi. Se vuoi provare a forzare la conversione di entrambe le colonne a un tipo intero, puoi usare invece df.astype(int)
.
Che ne dite di questo?
a = [['a', '1.2', '4.2'], ['b', '70', '0.03'], ['x', '5', '0']]
df = pd.DataFrame(a, columns=['one', 'two', 'three'])
df
Out[16]:
one two three
0 a 1.2 4.2
1 b 70 0.03
2 x 5 0
df.dtypes
Out[17]:
one object
two object
three object
df[['two', 'three']] = df[['two', 'three']].astype(float)
df.dtypes
Out[19]:
one object
two float64
three float64
Ecco una funzione che prende come argomenti un DataFrame e una lista di colonne e forza tutti i dati nelle colonne in numeri.
# df is the DataFrame, and column_list is a list of columns as strings (e.g ["col1","col2","col3"])
# dependencies: pandas
def coerce_df_columns_to_numeric(df, column_list):
df[column_list] = df[column_list].apply(pd.to_numeric, errors='coerce')
Quindi, per il vostro esempio
import pandas as pd
def coerce_df_columns_to_numeric(df, column_list):
df[column_list] = df[column_list].apply(pd.to_numeric, errors='coerce')
a = [['a', '1.2', '4.2'], ['b', '70', '0.03'], ['x', '5', '0']]
df = pd.DataFrame(a, columns=['col1','col2','col3'])
coerce_df_columns_to_numeric(df, ['col2','col3'])