tutorial skipfooter read_csv read panda into functions csv pandas int nan missing-data

skipfooter - read_csv arguments



Exportación de ints con valores faltantes a csv en Pandas (4)

Al guardar un DataFrame de Pandas en csv, algunos enteros se convierten en flotantes. Sucede cuando una columna de flotantes tiene valores faltantes ( np.nan ).

¿Hay alguna manera simple de evitarlo? (Especialmente de forma automática: a menudo trato con muchas columnas de varios tipos de datos).

Por ejemplo

import pandas as pd import numpy as np df = pd.DataFrame([[1,2],[3,np.nan],[5,6]], columns=["a","b"], index=["i_1","i_2","i_3"]) df.to_csv("file.csv")

rendimientos

,a,b i_1,1,2.0 i_2,3, i_3,5,6.0

Lo que me gustaría obtener es

,a,b i_1,1,2 i_2,3, i_3,5,6

EDITAR: Estoy completamente al tanto de Soporte para entero NA - Advertencias y Gotchas de Pandas . La pregunta es qué es una buena solución (especialmente en el caso de que haya muchas otras columnas de varios tipos y no sé de antemano qué columnas "enteras" tienen valores faltantes).


La sugerencia de @EdChum es que el comentario es bueno, también se puede usar el argumento float_format (ver en los documentos )

In [28]: a Out[28]: a b 0 0 1 1 1 NaN 2 2 3 In [31]: a.to_csv(r''c:/x.csv'', float_format = ''%.0f'')

Da a cabo:

,a,b 0,0,1 1,1, 2,2,3


Estoy expandiendo los datos de muestra aquí para asegurarme de que esto maneje las situaciones que está tratando:

df = pd.DataFrame([[1.1,2,9.9,44,1.0], [3.3,np.nan,4.4,22,3.0], [5.5,8,np.nan,66,4.0]], columns=list(''abcde''), index=["i_1","i_2","i_3"]) a b c d e i_1 1.1 2 9.9 44 1 i_2 3.3 NaN 4.4 22 3 i_3 5.5 8 NaN 66 4 df.dtypes a float64 b float64 c float64 d int64 e float64

Creo que si quieres una solución general, tendrá que estar codificada explícitamente debido a que los pandas no permiten NaN en columnas int. Lo que hago aquí abajo es verificar los valores enteros (ya que no podemos verificar realmente el tipo ya que se han refundido para que floten si contienen NaN), y si se trata de un valor entero, conviértalo a un formato de cadena y también conviértalo ''NAN'' a '''' (vacío). Por supuesto, esta no es la forma en que desea almacenar los enteros, excepto como un paso final antes de la salida.

for col in df.columns: if any( df[col].isnull() ): tmp = df[col][ df[col].notnull() ] if all( tmp.astype(int).astype(float) == tmp.astype(float) ): df[col] = df[col].map(''{:.0F}''.format).replace(''NAN'','''') df.to_csv(''x.csv'')

Aquí está el archivo de salida y también cómo se ve si lo vuelves a leer en pandas, aunque el propósito de esto es presumiblemente leerlo en otros paquetes numéricos.

%more x.csv ,a,b,c,d,e i_1,1.1,2,9.9,44,1.0 i_2,3.3,,4.4,22,3.0 i_3,5.5,8,,66,4.0 pd.read_csv(''x.csv'') Unnamed: 0 a b c d e 0 i_1 1.1 2 9.9 44 1 1 i_2 3.3 NaN 4.4 22 3 2 i_3 5.5 8 NaN 66 4


Este fragmento hace lo que quiere y debería ser relativamente eficiente al hacerlo.

import numpy as np import pandas as pd EPSILON = 1e-9 def _lost_precision(s): """ The total amount of precision lost over Series `s` during conversion to int64 dtype """ try: return (s - s.fillna(0).astype(np.int64)).sum() except ValueError: return np.nan def _nansafe_integer_convert(s): """ Convert Series `s` to an object type with `np.nan` represented as an empty string "" """ if _lost_precision(s) < EPSILON: # Here''s where the magic happens as_object = s.fillna(0).astype(np.int64).astype(np.object) as_object[s.isnull()] = "" return as_object else: return s def nansafe_to_csv(df, *args, **kwargs): """ Write `df` to a csv file, allowing for missing values in integer columns Uses `_lost_precision` to test whether a column can be converted to an integer data type without losing precision. Missing values in integer columns are represented as empty fields in the resulting csv. """ df.apply(_nansafe_integer_convert).to_csv(*args, **kwargs)

Podemos probar esto con un DataFrame simple que debería cubrir todas las bases:

In [75]: df = pd.DataFrame([[1,2, 3.1, "i"],[3,np.nan, 4.0, "j"],[5,6, 7.1, "k"]] columns=["a","b", "c", "d"], index=["i_1","i_2","i_3"]) In [76]: df Out[76]: a b c d i_1 1 2 3.1 i i_2 3 NaN 4.0 j i_3 5 6 7.1 k In [77]: nansafe_to_csv(df, ''deleteme.csv'', index=False)

Que produce el siguiente archivo csv :

a,b,c,d 1,2,3.1,i 3,,4.0,j 5,6,7.1,k


Utilizar float_format = ''%.12g'' dentro de la función to_csv resolvió un problema similar para mí. Mantiene los decimales para flotantes legítimos con hasta 12 dígitos significativos, pero los deja caer para que los ints se vean obligados a flotar por la presencia de NaN:

In [4]: df Out[4]: a b i_1 1 2.0 i_2 3 NaN i_3 5.9 6.0 In [5]: df.to_csv(''file.csv'', float_format = ''%.12g'')

La salida es:

, a, b i_1, 1, 2 i_2, 3, i_3, 5.9, 6