Je veux convertir une table, représentée sous forme de liste de listes, en un Pandas DataFrame. A titre d'exemple extrêmement simplifié:
a = [['a', '1.2', '4.2'], ['b', '70', '0.03'], ['x', '5', '0']]
df = pd.DataFrame(a)
Quel est le meilleur moyen de convertir les colonnes en types appropriés, dans ce cas les colonnes 2 et 3 en floats? Existe-t-il un moyen de spécifier les types lors de la conversion en DataFrame? Ou est-il préférable de créer d'abord le DataFrame, puis de parcourir les colonnes pour changer le type de chaque colonne? Idéalement, j'aimerais procéder de manière dynamique car il peut y avoir des centaines de colonnes et je ne veux pas spécifier exactement quelles colonnes sont de quel type. Tout ce que je peux garantir, c'est que chaque colonne contient des valeurs du même type.
Vous avez trois options principales pour convertir des types en pandas.
to_numeric()
Le meilleur moyen de convertir une ou plusieurs colonnes d'un DataFrame en valeurs numériques consiste à utiliser pandas.to_numeric()
.
Cette fonction essaiera de changer les objets non numériques (tels que les chaînes) en nombres entiers ou en nombres à virgule flottante, selon le cas.
L'entrée dans to_numeric()
est une série ou une colonne unique d'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
Comme vous pouvez le constater, une nouvelle série est renvoyée. N'oubliez pas d'affecter cette sortie à un nom de variable ou de colonne pour continuer à l'utiliser:
# convert Series
my_series = pd.to_numeric(my_series)
# convert column "a" of a DataFrame
df["a"] = pd.to_numeric(df["a"])
Vous pouvez également l'utiliser pour convertir plusieurs colonnes d'un DataFrame via la méthode 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)
Tant que vos valeurs peuvent toutes être converties, c'est probablement tout ce dont vous avez besoin.
Mais que se passe-t-il si certaines valeurs ne peuvent pas être converties en un type numérique?
to_numeric()
utilise également un argument de mot clé errors
qui vous permet de forcer les valeurs non numériques à être NaN
ou simplement d’ignorer les colonnes contenant ces valeurs.
Voici un exemple utilisant une série de chaînes s
dont l'objet est dtype:
>>> s = pd.Series(['1', '2', '4.7', 'pandas', '10'])
>>> s
0 1
1 2
2 4.7
3 pandas
4 10
dtype: object
Le comportement par défaut est d'élever s'il ne peut pas convertir une valeur. Dans ce cas, il ne peut pas faire face à la chaîne 'pandas':
>>> pd.to_numeric(s) # or pd.to_numeric(s, errors='raise')
ValueError: Unable to parse string
Plutôt que d’échouer, nous voudrions peut-être que les «pandas» soient considérés comme une valeur numérique manquante/mauvaise. Nous pouvons contraindre les valeurs non valides à NaN
comme suit à l'aide de l'argument de mot clé errors
:
>>> pd.to_numeric(s, errors='coerce')
0 1.0
1 2.0
2 4.7
3 NaN
4 10.0
dtype: float64
La troisième option pour errors
consiste simplement à ignorer l'opération si une valeur non valide est rencontrée:
>>> pd.to_numeric(s, errors='ignore')
# the original Series is returned untouched
Cette dernière option est particulièrement utile lorsque vous souhaitez convertir l’ensemble de votre DataFrame, sans savoir quelle colonne peut être convertie de manière fiable en un type numérique. Dans ce cas, écrivez simplement:
df.apply(pd.to_numeric, errors='ignore')
La fonction sera appliquée à chaque colonne du DataFrame. Les colonnes pouvant être converties en un type numérique seront converties, tandis que les colonnes qui ne le peuvent pas (par exemple, elles contiennent des chaînes ou des dates non numériques) seront laissées seules.
Par défaut, la conversion avec to_numeric()
vous donnera un type int64
ou float64
dactylographié (ou une largeur quelconque, quelle que soit la largeur de l’entier, sur votre plate-forme).
C’est généralement ce que vous voulez, mais si vous vouliez économiser de la mémoire et utiliser un type de fichier plus compact, tel que float32
ou int8
?
to_numeric()
vous donne la possibilité de rétrograder en "entier", "signé", "non signé", "flottant". Voici un exemple pour une série simple s
de type entier:
>>> s = pd.Series([1, 2, -7])
>>> s
0 1
1 2
2 -7
dtype: int64
Le downcasting en 'entier' utilise le plus petit entier possible pouvant contenir les valeurs:
>>> pd.to_numeric(s, downcast='integer')
0 1
1 2
2 -7
dtype: int8
Le downcasting pour 'float' sélectionne de manière similaire un type flottant plus petit que la normale:
>>> pd.to_numeric(s, downcast='float')
0 1.0
1 2.0
2 -7.0
dtype: float32
astype()
La méthode astype()
vous permet d’être explicite sur le type de données que vous souhaitez attribuer à votre DataFrame ou à votre série. C'est très polyvalent en ce que vous pouvez essayer de passer d'un type à l'autre.
Il suffit de choisir un type: vous pouvez utiliser un type NumPy (par exemple, np.int16
), certains types Python (par exemple, bool) ou des types spécifiques aux pandas (comme le type catégorique).
Appelez la méthode sur l'objet que vous voulez convertir et astype()
essaiera de la convertir pour vous:
# 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')
Remarquez que j'ai dit "essayer" - si astype()
ne sait pas comment convertir une valeur dans Series ou DataFrame, une erreur sera générée. Par exemple, si vous avez une valeur NaN
ou inf
, vous obtiendrez une erreur en essayant de la convertir en entier.
A partir de pandas 0.20.0, cette erreur peut être supprimée en passant errors='ignore'
. Votre objet d'origine sera retourné intact.
astype()
est puissant, mais il convertit parfois les valeurs "de manière incorrecte". Par exemple:
>>> s = pd.Series([1, 2, -7])
>>> s
0 1
1 2
2 -7
dtype: int64
Ce sont de petits entiers, alors pourquoi ne pas convertir en un type 8 bits non signé pour économiser de la mémoire?
>>> s.astype(np.uint8)
0 1
1 2
2 249
dtype: uint8
La conversion a fonctionné, mais le -7 a été bouclé pour devenir 249 (c.-à-d. 28 - 7)!
Essayer à la baisse d'utiliser pd.to_numeric(s, downcast='unsigned')
pourrait aider à prévenir cette erreur.
infer_objects()
La version 0.21.0 de pandas a introduit la méthode infer_objects()
pour convertir les colonnes d'un DataFrame ayant un type de données d'objet en un type plus spécifique (conversions logicielles).
Par exemple, voici un DataFrame avec deux colonnes de type d'objet. L'un contient les entiers réels et l'autre des chaînes représentant des entiers:
>>> df = pd.DataFrame({'a': [7, 1, 5], 'b': ['3','2','1']}, dtype='object')
>>> df.dtypes
a object
b object
dtype: object
En utilisant infer_objects()
, vous pouvez changer le type de colonne 'a' en int64:
>>> df = df.infer_objects()
>>> df.dtypes
a int64
b object
dtype: object
La colonne 'b' a été laissée seule puisque ses valeurs étaient des chaînes, pas des entiers. Si vous voulez essayer de forcer la conversion des deux colonnes en un type entier, vous pouvez utiliser df.astype(int)
à la place.
Que dis-tu de ça?
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
ce code ci-dessous changera le type de données de la colonne.
df[['col.name1', 'col.name2'...]] = df[['col.name1', 'col.name2'..]].astype('data_type')
à la place du type de données, vous pouvez indiquer votre type de données.
Voici une fonction qui prend comme arguments un DataFrame et une liste de colonnes et convertit toutes les données des colonnes en nombres.
# 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')
Donc, pour votre exemple:
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'])
Que diriez-vous de créer deux cadres de données, chacun avec des types de données différents pour leurs colonnes, puis de les ajouter ensemble?
d1 = pd.DataFrame(columns=[ 'float_column' ], dtype=float)
d1 = d1.append(pd.DataFrame(columns=[ 'string_column' ], dtype=str))
Résultats
In[8}: d1.dtypes
Out[8]:
float_column float64
string_column object
dtype: object
Une fois le cadre de données créé, vous pouvez le renseigner avec des variables à virgule flottante dans la première colonne et des chaînes (ou tout type de données de votre choix) dans la deuxième colonne.
Quand je n'ai eu besoin que de spécifier des colonnes spécifiques et que je veux être explicite, j'ai utilisé (par DOCS LOCATION ):
dataframe = dataframe.astype({'col_name_1':'int','col_name_2':'float64', etc. ...})
Donc, en utilisant la question originale, mais en lui donnant des noms de colonne ...
a = [['a', '1.2', '4.2'], ['b', '70', '0.03'], ['x', '5', '0']]
df = pd.DataFrame(a, columns=['col_name_1', 'col_name_2', 'col_name_3'])
df = df.astype({'col_name_2':'float64', 'col_name_3':'float64'})
Je pensais avoir le même problème, mais en réalité, j'ai une légère différence qui le rend plus facile à résoudre. Pour les autres qui consultent cette question, il vaut la peine de vérifier le format de votre liste de saisie. Dans mon cas, les nombres sont initialement des flottants, pas des chaînes, comme dans la question:
a = [['a', 1.2, 4.2], ['b', 70, 0.03], ['x', 5, 0]]
mais en traitant trop la liste avant de créer la structure de données, je perds les types et tout devient une chaîne.
Création du cadre de données via un tableau numpy
df = pd.DataFrame(np.array(a))
df
Out[5]:
0 1 2
0 a 1.2 4.2
1 b 70 0.03
2 x 5 0
df[1].dtype
Out[7]: dtype('O')
donne le même cadre de données que dans la question, où les entrées des colonnes 1 et 2 sont considérées comme des chaînes. Cependant faire
df = pd.DataFrame(a)
df
Out[10]:
0 1 2
0 a 1.2 4.20
1 b 70.0 0.03
2 x 5.0 0.00
df[1].dtype
Out[11]: dtype('float64')
ne donne réellement un cadre de données avec les colonnes dans le format correct