Modifier le type de données des colonnes dans Pandas

806

Je souhaite convertir un tableau, représenté sous forme de liste de listes, en un Pandas DataFrame. À titre d'exemple extrêmement simplifié:

a = [['a', '1.2', '4.2'], ['b', '70', '0.03'], ['x', '5', '0']]
df = pd.DataFrame(a)

Quelle est la meilleure façon de convertir les colonnes en types appropriés, dans ce cas les colonnes 2 et 3 en flottants? 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, je voudrais le faire 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.

Sociopathe
la source
J'ai vu des approches pour convertir chaque colonne et des approches pour convertir des colonnes spécifiquement nommées, mais qu'en est-il de certaines colonnes qui remplissent une certaine condition lorsque vous ne pouvez pas répertorier 100 colonnes que vous souhaitez convertir en même temps? Je pense par exemple à toutes les float64 -> float32 ou autres tactiques d'économie de mémoire.
demongolem
@demongolem: vous pourriez faire quelque chose comme df.apply(pd.to_numeric, downcast="integer", errors="ignore")pour downcast des colonnes entières vers le plus petit (entier) dtype qui contiendra les valeurs.
Alex Riley

Réponses:

1194

Vous avez trois options principales pour convertir des types en pandas:

  1. to_numeric()- fournit des fonctionnalités pour convertir en toute sécurité des types non numériques (par exemple des chaînes) en un type numérique approprié. (Voir aussi to_datetime()et to_timedelta().)

  2. astype()- convertir (presque) tout type en (presque) tout autre type (même si cela n'est pas nécessairement judicieux). Vous permet également de convertir en types catégoriels (très utile).

  3. infer_objects() - une méthode utilitaire pour convertir les colonnes d'objets contenant des objets Python en type pandas si possible.

Lisez la suite pour des explications plus détaillées et l'utilisation de chacune de ces méthodes.


1. to_numeric()

La meilleure façon de convertir une ou plusieurs colonnes d'un DataFrame en valeurs numériques est d'utiliser pandas.to_numeric().

Cette fonction essaiera de changer des objets non numériques (tels que des chaînes) en nombres entiers ou en virgule flottante, selon le cas.

Utilisation basique

L'entrée de to_numeric()est une série ou une seule colonne 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 voir, une nouvelle série est retourné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 apply()méthode:

# 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.

La gestion des erreurs

Mais que faire si certaines valeurs ne peuvent pas être converties en un type numérique?

to_numeric()prend également un errorsargument de mot clé qui vous permet de forcer les valeurs non numériques à être NaN, ou tout simplement ignorer les colonnes contenant ces valeurs.

Voici un exemple utilisant une série de chaînes squi a le type d'objet:

>>> 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 consiste à augmenter 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 pourrions vouloir que «pandas» soit considéré comme une valeur numérique manquante / incorrecte. Nous pouvons contraindre les valeurs non valides NaNcomme suit à l'aide de l' errorsargument mot - clé:

>>> 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 errorsest simplement d'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'intégralité de votre DataFrame, mais que vous ne savez pas laquelle de nos colonnes 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 qui peuvent être converties en un type numérique seront converties, tandis que les colonnes qui ne peuvent pas (par exemple, elles contiennent des chaînes ou des dates non numériques) seront laissées seules.

Downcasting

Par défaut, la conversion avec to_numeric()vous donnera soit un int64oufloat64 dtype (ou quelle que soit la largeur entière native de votre plateforme).

C'est généralement ce que vous voulez, mais si vous vouliez économiser de la mémoire et utiliser un type plus compact, comme float32ouint8 ?

to_numeric()vous donne la possibilité de rétrograder en «entier», «signé», «non signé», «flottant». Voici un exemple pour une simple série sde type entier:

>>> s = pd.Series([1, 2, -7])
>>> s
0    1
1    2
2   -7
dtype: int64

La descente 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

La descente vers «flottant» choisit de la même manière 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

2. astype()

le astype() méthode vous permet d'être explicite sur le dtype que vous voulez que votre DataFrame ou Series ait. Il est très polyvalent en ce sens que vous pouvez essayer de passer d'un type à l'autre.

Utilisation basique

Choisissez simplement 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égoriel).

Appelez la méthode sur l'objet que vous souhaitez convertir et astype()essayez de le 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 "essayez" - si astype()ne sait pas comment convertir une valeur dans la série ou le DataFrame, cela soulèvera une erreur. Par exemple, si vous avez un NaNouinf valeur , vous obtiendrez une erreur en essayant de la convertir en entier.

Depuis pandas 0.20.0, cette erreur peut être supprimée en passant errors='ignore' . Votre objet d'origine sera retourné intact.

Faites attention

astype()est puissant, mais il convertit parfois des valeurs "incorrectement". Par exemple:

>>> s = pd.Series([1, 2, -7])
>>> s
0    1
1    2
2   -7
dtype: int64

Ce sont de petits entiers, alors que diriez-vous de 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 (soit 2 8 - 7)!

Essayer de rétrograder à la pd.to_numeric(s, downcast='unsigned')place pourrait aider à éviter cette erreur.


3. infer_objects()

La version 0.21.0 de pandas a introduit la méthode infer_objects()de conversion des colonnes d'un DataFrame qui ont 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 des entiers réels et l'autre contient 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 car ses valeurs étaient des chaînes, pas des entiers. Si vous souhaitez essayer de forcer la conversion des deux colonnes en un type entier, vous pouvez utiliser à la df.astype(int)place.

Alex Riley
la source
8
De plus, contrairement à .astype (float), cela convertira les chaînes en NaNs au lieu de générer une erreur
Rob
11
.convert_objectsest obsolète depuis 0.17- utilisez à la df.to_numericplace
Matti Lyra
4
Merci - je devrais mettre à jour cette réponse. Il vaut peut-être la peine de le noter pd.to_numericet ses méthodes compagnes ne fonctionneront que sur une colonne à la fois, contrairement à convert_objects. La discussion sur une fonction de remplacement dans l'API semble être en cours ; J'espère qu'une méthode qui fonctionne sur l'ensemble du DataFrame restera car elle est très utile.
Alex Riley
Quelle est la meilleure façon de convertir toutes les colonnes qui sont actuellement, disons, int64en int32?
RoyalTS
4
@RoyalTS: probablement préférable d'utiliser astype(comme dans l'autre réponse), c'est à dire .astype(numpy.int32).
Alex Riley
447

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
hernamesbarbara
la source
10
Oui! pd.DataFramea un dtypeargument qui pourrait vous permettre de faire ce que vous cherchez. df = pd.DataFrame (a, colonnes = ['un', 'deux', 'trois'], dtype = float) In [2]: df.dtypes Out [2]: un objet deux float64 trois float64 dtype: object
hernamesbarbara
17
Lorsque j'essaye comme suggéré, je reçois un avertissement SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame. Try using .loc[row_index,col_indexer] = value instead. Cela a peut-être été introduit dans une version plus récente de pandas et je ne vois rien de mal en conséquence, mais je me demande simplement en quoi consiste cet avertissement. Une idée?
orange
2
@orange, l'avertissement est d'alerter les utilisateurs sur un comportement potentiellement déroutant avec des opérations chaînées et avec des pandas renvoyant des copies plutôt que de modifier des trames de données. voir stackoverflow.com/questions/20625582/… et connexes.
A.Wan
19
C'est une bonne méthode, mais cela ne fonctionne pas lorsqu'il y a du NaN dans une colonne. Je n'ai aucune idée pourquoi NaN ne peut tout simplement pas rester NaN lors du lancement de float to int:ValueError: Cannot convert NA to integer
Vitaly Isaev
7
@GillBates oui, dans un dictionnaire. df = pd.DataFrame(a, columns=['one', 'two', 'three'], dtype={'one': str, 'two': int, 'three': float}). J'ai du mal à trouver la spécification pour les valeurs "dtype" acceptées. Une liste serait bien (actuellement je le fais dict(enumerate(my_list))).
FichteFoll
39

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')

au lieu du type de données, vous pouvez donner votre type de données. que voulez-vous comme str, float, int etc.

Akash Nayak
la source
Rappelez-vous que lorsque vous appliquez cela sur une colonne contenant les chaînes `` `` True '' `` et '' `` False '' `` en utilisant le type de données bool, tout est changé en True.
H.Vabri
Cette option, vous pouvez également la convertir en type "catégorie"
neves
17

Lorsque 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 d'origine, mais en lui fournissant des noms de colonnes ...

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'})
Thom Ives
la source
15

Voici une fonction qui prend comme arguments un DataFrame et une liste de colonnes et contraint toutes les données des colonnes à des 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'])
Harry Stevens
la source
Et si vous vouliez utiliser des index de colonnes au lieu de noms de colonnes?
jvalenti
8

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 la trame de données créée, vous pouvez la remplir avec des variables à virgule flottante dans la 1ère colonne et des chaînes (ou tout type de données souhaité) dans la 2ème colonne.

MikeyE
la source
4

pandas> = 1.0

Voici un tableau qui résume certaines des conversions les plus importantes chez les pandas.

entrez la description de l'image ici

Les conversions en chaîne sont triviales .astype(str) et ne sont pas représentées sur la figure.

Conversions "dures" contre "douces"

Notez que les «conversions» dans ce contexte peuvent faire référence à la conversion de données texte en leur type de données réel (conversion matérielle) ou à l'inférence de types de données plus appropriés pour les données dans les colonnes d'objets (conversion logicielle). Pour illustrer la différence, jetez un œil à

df = pd.DataFrame({'a': ['1', '2', '3'], 'b': [4, 5, 6]}, dtype=object)
df.dtypes                                                                  

a    object
b    object
dtype: object

# Actually converts string to numeric - hard conversion
df.apply(pd.to_numeric).dtypes                                             

a    int64
b    int64
dtype: object

# Infers better data types for object data - soft conversion
df.infer_objects().dtypes                                                  

a    object  # no change
b     int64
dtype: object

# Same as infer_objects, but converts to equivalent ExtensionType
df.convert_dtypes().dtypes                                                     
cs95
la source
1

Je pensais que j'avais le même problème mais en fait j'ai une légère différence qui rend le problème plus facile à résoudre. Pour ceux qui regardent 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 et non 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 trame de données, je perds les types et tout devient une chaîne.

Création de la trame 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 bloc de données que dans la question, où les entrées dans les 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')

donne en fait un bloc de données avec les colonnes au format correct

SarahD
la source
0

À partir de pandas 1.0.0, nous l'avons pandas.DataFrame.convert_dtypes. Vous pouvez même contrôler quels types convertir!

In [40]: df = pd.DataFrame(
    ...:     {
    ...:         "a": pd.Series([1, 2, 3], dtype=np.dtype("int32")),
    ...:         "b": pd.Series(["x", "y", "z"], dtype=np.dtype("O")),
    ...:         "c": pd.Series([True, False, np.nan], dtype=np.dtype("O")),
    ...:         "d": pd.Series(["h", "i", np.nan], dtype=np.dtype("O")),
    ...:         "e": pd.Series([10, np.nan, 20], dtype=np.dtype("float")),
    ...:         "f": pd.Series([np.nan, 100.5, 200], dtype=np.dtype("float")),
    ...:     }
    ...: )

In [41]: dff = df.copy()

In [42]: df 
Out[42]: 
   a  b      c    d     e      f
0  1  x   True    h  10.0    NaN
1  2  y  False    i   NaN  100.5
2  3  z    NaN  NaN  20.0  200.0

In [43]: df.dtypes
Out[43]: 
a      int32
b     object
c     object
d     object
e    float64
f    float64
dtype: object

In [44]: df = df.convert_dtypes()

In [45]: df.dtypes
Out[45]: 
a      Int32
b     string
c    boolean
d     string
e      Int64
f    float64
dtype: object

In [46]: dff = dff.convert_dtypes(convert_boolean = False)

In [47]: dff.dtypes
Out[47]: 
a      Int32
b     string
c     object
d     string
e      Int64
f    float64
dtype: object
Sohail
la source