Cambiar el tipo de datos de las columnas en Pandas

Quiero convertir una tabla, representada como una lista de listas, en un Pandas DataFrame. Como un ejemplo extremadamente simplificado:

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

¿Cuál es la mejor manera de convertir las columnas a los tipos apropiados, en este caso las columnas 2 y 3 en flotantes? ¿Hay alguna forma de especificar los tipos mientras se convierte a DataFrame? ¿O es mejor crear primero el DataFrame y luego recorrer las columnas para cambiar el tipo de cada columna? Lo ideal sería hacer esto de forma dinámica porque puede haber cientos de columnas y no quiero especificar exactamente qué columnas son de qué tipo. Lo único que puedo garantizar es que cada columna contenga valores del mismo tipo.

Solución

Tienes tres opciones principales para convertir tipos en pandas:

  1. to_numeric() - proporciona una funcionalidad para convertir de forma segura tipos no numéricos (por ejemplo, cadenas) a un tipo numérico adecuado. (Ver también to_datetime() y to_timedelta().)
  2. astype() - convierte (casi) cualquier tipo a (casi) cualquier otro tipo (incluso si no es necesariamente sensato hacerlo). También permite convertir a tipos categoriales (muy útil).
  3. infer_objects() - un método de utilidad para convertir columnas de objetos de Python a un tipo de pandas si es posible. Siga leyendo para ver explicaciones más detalladas y el uso de cada uno de estos métodos.

    1. a_numérico()

    La mejor manera de convertir una o más columnas de un DataFrame a valores numéricos es utilizar pandas.to_numeric(). Esta función intentará cambiar los objetos no numéricos (como las cadenas) en enteros o números de punto flotante, según corresponda.

    Uso básico

    La entrada a to_numeric() es una Serie o una sola columna de 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

Como puede ver, se devuelve una nueva Serie. Recuerde asignar esta salida a una variable o nombre de columna para seguir utilizándola:

# convert Series
my_series = pd.to_numeric(my_series)

# convert column "a" of a DataFrame
df["a"] = pd.to_numeric(df["a"])

También se puede utilizar para convertir múltiples columnas de un DataFrame a través del método 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)

Mientras tus valores puedan ser convertidos todos, eso es probablemente todo lo que necesitas.

Manejo de errores

¿Pero qué pasa si algunos valores no pueden ser convertidos a un tipo numérico? to_numeric() también toma un argumento de palabra clave errors que le permite forzar los valores no numéricos a ser NaN, o simplemente ignorar las columnas que contienen estos valores. Aquí hay un ejemplo usando una serie de cadenas s que tiene el objeto dtype:

>>> s = pd.Series(['1', '2', '4.7', 'pandas', '10'])
>>> s
0         1
1         2
2       4.7
3    pandas
4        10
dtype: object

El comportamiento por defecto es levantar si no puede convertir un valor. En este caso, no puede con la cadena pandas:

>>> pd.to_numeric(s) # or pd.to_numeric(s, errors='raise')
ValueError: Unable to parse string

En lugar de fallar, podríamos querer que 'pandas' se considere un valor numérico no válido. Podemos coaccionar los valores no válidos a NaN de la siguiente manera utilizando el argumento de la palabra clave errors:

>>> pd.to_numeric(s, errors='coerce')
0     1.0
1     2.0
2     4.7
3     NaN
4    10.0
dtype: float64

La tercera opción para errors es simplemente ignorar la operación si se encuentra un valor no válido:

>>> pd.to_numeric(s, errors='ignore')
# the original Series is returned untouched

Esta última opción es especialmente útil cuando se quiere convertir todo el DataFrame, pero no se sabe cuáles de nuestras columnas se pueden convertir de forma fiable a un tipo numérico. En ese caso basta con escribir

df.apply(pd.to_numeric, errors='ignore')

La función se aplicará a cada columna del DataFrame. Las columnas que puedan ser convertidas a un tipo numérico serán convertidas, mientras que las columnas que no puedan (por ejemplo, que contengan cadenas no numéricas o fechas) se dejarán como están.

Downcasting

Por defecto, la conversión con to_numeric() le dará un tipo int64 o float64 (o cualquier ancho de entero nativo de su plataforma). Eso es normalmente lo que quieres, pero ¿qué pasa si quieres ahorrar algo de memoria y usar un tipo más compacto, como float32, o int8? to_numeric() te da la opción de convertirlo en "entero", "con signo", "sin signo" o "float". Aquí hay un ejemplo para una serie simple s de tipo entero:

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

Al pasar a 'integer' se utiliza el menor entero posible que pueda contener los valores:

>>> pd.to_numeric(s, downcast='integer')
0    1
1    2
2   -7
dtype: int8

El downcasting a 'float' elige de forma similar un tipo flotante más pequeño de lo normal:

>>> pd.to_numeric(s, downcast='float')
0    1.0
1    2.0
2   -7.0
dtype: float32

2. astype()

El método astype() te permite ser explícito sobre el tipo que quieres que tenga tu DataFrame o Serie. Es muy versátil en el sentido de que puedes intentar pasar de un tipo a otro cualquiera.

Uso básico

Sólo tienes que elegir un tipo: puedes usar un dtype de NumPy (por ejemplo np.int16), algunos tipos de Python (por ejemplo bool), o tipos específicos de pandas (como el dtype categorical). Llame al método en el objeto que desea convertir y astype() tratará de convertirlo por usted:

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

Fíjate que he dicho "intentar" - si astype() no sabe cómo convertir un valor en la Serie o DataFrame, dará un error. Por ejemplo, si tiene un valor NaN o inf obtendrá un error al intentar convertirlo en un entero. A partir de pandas 0.20.0, este error puede suprimirse pasando errors='ignore'. Su objeto original se devolverá intacto.

Tenga cuidado

astype() es potente, pero a veces convierte valores "incorrectamente". Por ejemplo:

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

Estos son enteros pequeños, así que ¿qué tal si se convierte a un tipo de 8 bits sin signo para ahorrar memoria?

>>> s.astype(np.uint8)
0      1
1      2
2    249
dtype: uint8

La conversión funcionó, pero el -7 se redondeó para convertirse en 249 (es decir, 28 - 7). Intentar la conversión hacia abajo usando pd.to_numeric(s, downcast='unsigned') podría ayudar a prevenir este error.

3. inferir_objetos()

La versión 0.21.0 de pandas introdujo el método infer_objects() para convertir las columnas de un DataFrame que tienen un tipo de dato de objeto a un tipo más específico (conversiones suaves). Por ejemplo, aquí tenemos un DataFrame con dos columnas de tipo objeto. Una contiene números enteros reales y la otra contiene cadenas que representan números enteros:

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

Usando infer_objects(), puedes cambiar el tipo de la columna 'a' a int64:

>>> df = df.infer_objects()
>>> df.dtypes
a     int64
b    object
dtype: object

La columna 'b' se ha dejado sola ya que sus valores eran cadenas, no enteros. Si quieres forzar la conversión de ambas columnas a un tipo entero, puedes utilizar df.astype(int) en su lugar.

Comentarios (8)

¿Qué te parece esto?

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
Comentarios (10)

Esta es una función que toma como argumentos un DataFrame y una lista de columnas y convierte todos los datos de las columnas en números.

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

Así, para su ejemplo:

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'])
Comentarios (1)