Cambiar el tipo de datos de columnas en Pandas

806

Quiero convertir una tabla, representada como una lista de listas, en a 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 manera de especificar los tipos al convertir a DataFrame? ¿O es mejor crear el DataFrame primero y luego recorrer las columnas para cambiar el tipo de cada columna? Idealmente, me gustaría hacer esto de forma dinámica porque puede haber cientos de columnas y no quiero especificar exactamente qué columnas son de qué tipo. Todo lo que puedo garantizar es que cada columna contiene valores del mismo tipo.

Sociópata
fuente
He visto enfoques para convertir cada columna y enfoques para convertir columnas específicamente nombradas, pero ¿qué hay de ciertas columnas que cumplen una determinada condición cuando no puede enumerar 100 columnas que desea convertir a la vez? Estoy pensando, por ejemplo, en todas las float64 -> float32 u otras tácticas de ahorro de memoria.
demongolem
@demongolem: podría hacer algo como df.apply(pd.to_numeric, downcast="integer", errors="ignore")bajar columnas enteras al dtype más pequeño (entero) que contendrá los valores.
Alex Riley

Respuestas:

1193

Tiene tres opciones principales para convertir tipos en pandas:

  1. to_numeric()- proporciona 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 le permite convertir a tipos categoriales (muy útil).

  3. infer_objects() - un método de utilidad para convertir columnas de objetos que contienen objetos de Python a un tipo de pandas si es posible.

Siga leyendo para obtener explicaciones más detalladas y el uso de cada uno de estos métodos.


1) to_numeric()

La mejor manera de convertir una o más columnas de un DataFrame en valores numéricos es usar pandas.to_numeric().

Esta función intentará cambiar objetos no numéricos (como cadenas) en números enteros o números de coma 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 continuar usá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 puede usarlo para convertir varias columnas de un DataFrame a través del apply()método:

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

Siempre y cuando todos sus valores se puedan convertir, eso es probablemente todo lo que necesita.

Manejo de errores

Pero, ¿qué pasa si algunos valores no se pueden convertir a un tipo numérico?

to_numeric()también toma un errorsargumento de palabra clave que le permite forzar valores no numéricos NaN, o simplemente ignorar las columnas que contienen estos valores.

Aquí hay un ejemplo usando una serie de cadenas sque tiene el tipo de 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 predeterminado es aumentar si no puede convertir un valor. En este caso, no puede hacer frente a 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 los pandas se consideren un valor numérico faltante / incorrecto. Podemos forzar valores no válidos de la NaNsiguiente manera usando el errorsargumento de la palabra clave:

>>> 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 errorses 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 particularmente útil cuando desea convertir su DataFrame completo, pero no sabe cuál de nuestras columnas se puede convertir de manera confiable a un tipo numérico. En ese caso solo escribe:

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

La función se aplicará a cada columna del DataFrame. Las columnas que se pueden convertir a un tipo numérico se convertirán, mientras que las columnas que no se pueden (por ejemplo, que contienen cadenas o fechas sin dígitos) se dejarán solas.

Abatido

Por defecto, la conversión con to_numeric()le dará a int64o float64dtype (o cualquier ancho entero nativo de su plataforma).

Por lo general, eso es lo que desea, pero ¿qué sucede si desea ahorrar algo de memoria y usar un tipo de letra más compacto, como float32, o int8?

to_numeric()le da la opción de bajar a 'entero', 'firmado', 'sin signo', 'flotante'. Aquí hay un ejemplo para una serie simple sde tipo entero:

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

Downcasting a 'integer' usa el entero más pequeño posible que puede contener los valores:

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

Downcasting to 'float' también elige 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 astype()método le permite ser explícito sobre el tipo de letra que desea que tenga su DataFrame o Series. Es muy versátil ya que puedes probar y pasar de un tipo a otro.

Uso básico

Simplemente elija un tipo: puede usar un tipo de letra NumPy (por ejemplo np.int16), algunos tipos de Python (por ejemplo, bool) o tipos específicos de pandas (como el tipo de letra categórico).

Llame al método en el objeto que desea convertir e astype()intentará 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')

Observe que dije "probar": si astype()no sabe cómo convertir un valor en la Serie o el Marco de datos, generará un error. Por ejemplo, si tiene un valor NaNo infobtendrá un error al intentar convertirlo en un entero.

A partir de pandas 0.20.0, este error se puede suprimir al pasar errors='ignore'. Su objeto original será devuelto intacto.

Ten cuidado

astype()es poderoso, pero a veces convertirá valores "incorrectamente". Por ejemplo:

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

Estos son enteros pequeños, entonces, ¿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 envolvió para convertirse en 249 (es decir, 2 8 - 7)!

Intentar bajar el uso usando en su pd.to_numeric(s, downcast='unsigned')lugar podría ayudar a prevenir este error.


3) infer_objects()

La versión 0.21.0 de pandas introdujo el método infer_objects()para convertir columnas de un DataFrame que tienen un tipo de datos de objeto a un tipo más específico (conversiones suaves).

Por ejemplo, aquí hay un DataFrame con dos columnas de tipo de objeto. Uno contiene enteros reales y el otro contiene cadenas que representan enteros:

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

Utilizando infer_objects(), puede cambiar el tipo de 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 desea tratar de forzar la conversión de ambas columnas a un tipo entero, puede usar df.astype(int)en su lugar.

Alex Riley
fuente
8
Además, a diferencia de .astype (float), esto convertirá las cadenas a NaN en lugar de generar un error
Rob
11
.convert_objectsestá depravado desde 0.17- use df.to_numericen su lugar
Matti Lyra
44
Gracias. Debería actualizar esta respuesta. Quizás valga la pena señalar eso pd.to_numericy sus métodos complementarios solo funcionarán en una columna a la vez, a diferencia convert_objects. La discusión sobre una función de reemplazo en la API parece estar en curso ; Espero que se mantenga un método que funcione en todo el DataFrame porque es muy útil.
Alex Riley
¿Cuál es la mejor manera de convertir todas las columnas que están actualmente, digamos, int64a int32?
RoyalTS
44
@RoyalTS: probablemente el mejor para usar astype(como en la otra respuesta), es decir .astype(numpy.int32).
Alex Riley
447

¿Qué tal 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
hernamesbarbara
fuente
10
¡Si! pd.DataFrametiene un dtypeargumento que podría permitirte hacer lo que estás buscando. df = pd.DataFrame (a, columnas = ['uno', 'dos', 'tres'], dtype = float) In [2]: df.dtypes Out [2]: un objeto dos float64 tres float64 dtype: objeto
hernamesbarbara
17
Cuando intento como se sugiere, recibo una advertencia 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. Esto puede haberse introducido en una versión más nueva de pandas y, como resultado, no veo nada malo, pero me pregunto de qué se trata esta advertencia. ¿Alguna idea?
naranja
2
@orange la advertencia es alertar a los usuarios sobre comportamientos potencialmente confusos con operaciones encadenadas y con pandas que devuelven copias de los marcos de datos en lugar de editarlos. ver stackoverflow.com/questions/20625582/… y relacionados.
A.Wan
19
Ese es un buen método, pero no funciona cuando hay NaN en una columna. No tengo idea de por qué NaN simplemente no puede permanecer NaN cuando se lanza el flotador a int:ValueError: Cannot convert NA to integer
Vitaly Isaev
77
@GillBates sí, en un diccionario. df = pd.DataFrame(a, columns=['one', 'two', 'three'], dtype={'one': str, 'two': int, 'three': float}). Sin embargo, me resulta difícil encontrar la especificación para los valores "dtype" aceptados. Una lista estaría bien (actualmente lo hago dict(enumerate(my_list))).
FichteFoll
39

El siguiente código cambiará el tipo de datos de la columna.

df[['col.name1', 'col.name2'...]] = df[['col.name1', 'col.name2'..]].astype('data_type')

en lugar del tipo de datos, puede dar su tipo de datos. ¿Qué desea, como str, float, int, etc.

Akash Nayak
fuente
Tenga en cuenta que al aplicar esto en una columna que contiene las cadenas `` '' Verdadero '' `` y '' `` Falso '' `` usando data_type bool, todo cambia a True.
H. Vabri
Esta opción también se puede convertir para escribir "categoría"
neves
17

Cuando solo necesito especificar columnas específicas, y quiero ser explícito, he usado (por DOCS LOCATION ):

dataframe = dataframe.astype({'col_name_1':'int','col_name_2':'float64', etc. ...})

Entonces, usando la pregunta original, pero proporcionándole nombres de columna ...

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
fuente
15

Aquí hay una función que toma como argumentos un DataFrame y una lista de columnas y coacciona todos los datos de las columnas a 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')

Entonces, 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'])
Harry Stevens
fuente
¿Qué sucede si desea utilizar índices de columna en lugar de nombres de columna?
jvalenti
8

¿Qué tal crear dos marcos de datos, cada uno con diferentes tipos de datos para sus columnas, y luego agregarlos juntos?

d1 = pd.DataFrame(columns=[ 'float_column' ], dtype=float)
d1 = d1.append(pd.DataFrame(columns=[ 'string_column' ], dtype=str))

Resultados

In[8}:  d1.dtypes
Out[8]: 
float_column     float64
string_column     object
dtype: object

Después de crear el marco de datos, puede rellenarlo con variables de punto flotante en la primera columna y cadenas (o cualquier tipo de datos que desee) en la segunda columna.

MikeyE
fuente
4

pandas> = 1.0

Aquí hay un cuadro que resume algunas de las conversiones más importantes en pandas.

ingrese la descripción de la imagen aquí

Las conversiones a cadenas son triviales .astype(str)y no se muestran en la figura.

Conversiones "duras" versus "suaves"

Tenga en cuenta que las "conversiones" en este contexto podrían referirse a la conversión de datos de texto en su tipo de datos real (conversión de hardware) o inferir tipos de datos más apropiados para los datos en columnas de objeto (conversión de software). Para ilustrar la diferencia, eche un vistazo a

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
fuente
1

Pensé que tenía el mismo problema, pero en realidad tengo una ligera diferencia que hace que el problema sea más fácil de resolver. Para otros que miran esta pregunta, vale la pena verificar el formato de su lista de entrada. En mi caso, los números son inicialmente flotantes, no cadenas como en la pregunta:

a = [['a', 1.2, 4.2], ['b', 70, 0.03], ['x', 5, 0]]

pero al procesar la lista demasiado antes de crear el marco de datos, pierdo los tipos y todo se convierte en una cadena.

Crear el marco de datos a través de una matriz 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')

da el mismo marco de datos que en la pregunta, donde las entradas en las columnas 1 y 2 se consideran cadenas. Sin embargo haciendo

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

en realidad da un marco de datos con las columnas en el formato correcto

SarahD
fuente
0

A partir de pandas 1.0.0, tenemos pandas.DataFrame.convert_dtypes. ¡Incluso puedes controlar qué tipos 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
fuente