python pandas coercion

python - Evite la coerción de marcos de datos de pandas mientras indexa e inserta filas



coercion (5)

Estoy trabajando con filas individuales de marcos de datos de pandas, pero me encuentro con problemas de coerción al indexar e insertar filas. Parece que Pandas siempre quiere coaccionar de un tipo mixto int / float a todo tipo float, y no puedo ver ningún control obvio sobre este comportamiento.

Por ejemplo, aquí hay un marco de datos simple con a como int y b como float :

import pandas as pd pd.__version__ # ''0.25.2'' df = pd.DataFrame({''a'': [1], ''b'': [2.2]}) print(df) # a b # 0 1 2.2 print(df.dtypes) # a int64 # b float64 # dtype: object

Aquí hay un problema de coerción al indexar una fila:

print(df.loc[0]) # a 1.0 # b 2.2 # Name: 0, dtype: float64 print(dict(df.loc[0])) # {''a'': 1.0, ''b'': 2.2}

Y aquí hay un problema de coerción al insertar una fila:

df.loc[1] = {''a'': 5, ''b'': 4.4} print(df) # a b # 0 1.0 2.2 # 1 5.0 4.4 print(df.dtypes) # a float64 # b float64 # dtype: object

En ambos casos, quiero que la columna permanezca como un tipo entero, en lugar de ser forzada a un tipo flotante.


Después de excavar un poco, aquí hay algunas soluciones terriblemente feas. (Se aceptará una mejor respuesta).

Una peculiaridad que se encuentra aquí es que las columnas no numéricas detienen la coerción, así que aquí está cómo indexar una fila a un dict :

dict(df.assign(_='''').loc[0].drop(''_'', axis=0)) # {''a'': 1, ''b'': 2.2}

E insertar una fila se puede hacer creando un nuevo marco de datos con una fila:

df = df.append(pd.DataFrame({''a'': 5, ''b'': 4.4}, index=[1])) print(df) # a b # 0 1 2.2 # 1 5 4.4

Ambos trucos no están optimizados para grandes marcos de datos, por lo que agradecería mucho una mejor respuesta.


En el primer caso, puede trabajar con el tipo de datos entero anulable . La selección de la serie no obliga a float y los valores se colocan en un contenedor de object . El diccionario se crea correctamente, con el valor subyacente almacenado como np.int64 .

df = pd.DataFrame({''a'': [1], ''b'': [2.2]}) df[''a''] = df[''a''].astype(''Int64'') d = dict(df.loc[0]) #{''a'': 1, ''b'': 2.2} type(d[''a'']) #numpy.int64

Con su sintaxis, esto casi funciona también para el segundo caso, pero esto se convierte en una object , por lo que no es genial:

df.loc[1] = {''a'': 5, ''b'': 4.4} # a b #0 1 2.2 #1 5 4.4 df.dtypes #a object #b float64 #dtype: object

Sin embargo, podemos hacer un pequeño cambio en la sintaxis para agregar una fila al final (con un RangeIndex) y ahora los tipos se tratan correctamente.

df = pd.DataFrame({''a'': [1], ''b'': [2.2]}) df[''a''] = df[''a''].astype(''Int64'') df.loc[df.shape[0], :] = [5, 4.4] # a b #0 1 2.2 #1 5 4.4 df.dtypes #a Int64 #b float64 #dtype: object


La raíz del problema es que

  1. La indexación del marco de datos de pandas devuelve una serie de pandas

Podemos ver eso:

type(df.loc[0]) # pandas.core.series.Series

Y una serie solo puede tener un tipo de letra, en su caso int64 o float64.

Se me ocurren dos soluciones:

print(df.loc[[0]]) # this will return a dataframe instead of series # so the result will be # a b # 0 1 2.2 # but the dictionary is hard to read print(dict(df.loc[[0]])) # {''a'': 0 1 # Name: a, dtype: int64, ''b'': 0 2.2 # Name: b, dtype: float64}

o

print(df.astype(object).loc[0]) # this will change the type of value to object first and then print # so the result will be # a 1 # b 2.2 # Name: 0, dtype: object print(dict(df.astype(object).loc[0])) # in this way the dictionary is as expected # {''a'': 1, ''b'': 2.2}

  1. Cuando agrega un diccionario a un marco de datos, primero convertirá el diccionario a una Serie y luego lo agregará. (Entonces el mismo problema vuelve a ocurrir)

https://github.com/pandas-dev/pandas/blob/master/pandas/core/frame.py#L6973

if isinstance(other, dict): other = Series(other)

Entonces, su recorrido es realmente sólido, o de lo contrario podríamos:

df.append(pd.Series({''a'': 5, ''b'': 4.4}, dtype=object, name=1)) # a b # 0 1 2.2 # 1 5 4.4


Siempre que obtenga datos de un marco de datos o anexe datos a un marco de datos y necesite mantener el mismo tipo de datos, evite la conversión a otras estructuras internas que no conozcan los tipos de datos necesarios.

Cuando haces df.loc[0] se convierte a pd.Series ,

>>> type(df.loc[0]) <class ''pandas.core.series.Series''>

Y ahora, la Series solo tendrá un tipo único. Por lo tanto, obligar a int a float .

En su lugar, mantenga la estructura como pandas.pydata.org/pandas-docs/stable/reference/api/… ,

>>> type(df.loc[[0]]) <class ''pandas.core.frame.DataFrame''>

Seleccione la fila necesaria como marco y luego convierta a dict

>>> df.loc[[0]].to_dict(orient=''records'') [{''a'': 1, ''b'': 2.2}]

De manera similar, para agregar una nueva fila, use la función pandas pd.DataFrame.append ,

>>> df = df.append([{''a'': 5, ''b'': 4.4}]) # NOTE: To append as a row, use [] a b 0 1 2.2 0 5 4.4

Lo anterior no causará conversión de tipo,

>>> df.dtypes a int64 b float64 dtype: object


Un enfoque diferente con ligeras manipulaciones de datos:

Suponga que tiene una lista de diccionarios (o marcos de datos)

lod=[{''a'': [1], ''b'': [2.2]}, {''a'': [5], ''b'': [4.4]}]

donde cada diccionario representa una fila (observe las listas en el segundo diccionario). Entonces puede crear un marco de datos fácilmente a través de:

pd.concat([pd.DataFrame(dct) for dct in lod]) a b 0 1 2.2 0 5 4.4

y mantienes los tipos de las columnas. Ver concat

Entonces, si tiene un marco de datos y una lista de dictos, simplemente puede usar

pd.concat([df] + [pd.DataFrame(dct) for dct in lod])