Convierta la columna Pandas que contiene NaNs a dtype `int`

175

Leí datos de un archivo .csv a un marco de datos de Pandas como se muestra a continuación. Para una de las columnas, a saber id, quiero especificar el tipo de columna como int. El problema es que la idserie tiene valores faltantes / vacíos.

Cuando intento convertir la idcolumna a entero mientras leo el .csv, obtengo:

df= pd.read_csv("data.csv", dtype={'id': int}) 
error: Integer column has NA values

Alternativamente, intenté convertir el tipo de columna después de leer como se muestra a continuación, pero esta vez obtengo:

df= pd.read_csv("data.csv") 
df[['id']] = df[['id']].astype(int)
error: Cannot convert NA to integer

¿Cómo puedo abordar esto?

Zhubarb
fuente
3
Creo que los valores enteros no se pueden convertir o almacenar en una serie / marco de datos si faltan valores / NaN. Creo que esto tiene que ver con la compatibilidad numpy (supongo que aquí), si desea la compatibilidad de valores perdidos, entonces almacenaría los valores como flotantes
EdChum
1
ver aquí: pandas.pydata.org/pandas-docs/dev/… ; debe tener un dtype flotante cuando le faltan valores (o técnicamente un dtype de objeto pero eso es ineficiente); ¿Cuál es su objetivo de usar int type?
Jeff
66
Creo que este es un problema de NumPy, no específico de Pandas. Es una pena, ya que hay muchos casos en los que tener un tipo int que permita la posibilidad de valores nulos es mucho más eficiente que una gran columna de flotantes.
ely
1
Tengo un problema con esto también. Tengo varios marcos de datos que quiero fusionar en función de una representación de cadena de varias columnas "enteras". Sin embargo, cuando una de esas columnas enteras tiene un np.nan, la conversión de cadena produce un ".0", que elimina la fusión. Simplemente hace las cosas un poco más complicadas, sería bueno si hubiera una solución simple.
dermen
1
@Rhubarb, ahora se agregó oficialmente el Soporte Entero Nullable Opcional en pandas 0.24.0 - finalmente :) - encuentre una respuesta actualizada a continuación. Notas de la versión de pandas 0.24.x
mork

Respuestas:

169

La falta de representante de NaN en columnas enteras es un panda "gotcha" .

La solución habitual es simplemente usar flotadores.

Andy Hayden
fuente
13
¿Hay alguna otra solución además de tratarlos como flotadores?
NumenorForLife
3
@ jsc123 puede usar el objeto dtype. Esto viene con una pequeña advertencia de salud, pero en su mayor parte funciona bien.
Andy Hayden
1
¿Puedes dar un ejemplo de cómo usar dtype de objeto? He estado revisando los documentos de los pandas y buscando en Google, y he leído que es el método recomendado. Pero no he encontrado un ejemplo de cómo usar el objeto dtype.
MikeyE
28
En v0.24, ahora puede hacer df = df.astype(pd.Int32Dtype())(para convertir todo el DataFrame, o) df['col'] = df['col'].astype(pd.Int32Dtype()). Otros tipos enteros anulables aceptados son pd.Int16Dtypey pd.Int64Dtype. Elige tu veneno.
cs95
1
Es un valor NaN pero la comprobación isnan no funciona en absoluto :(
Winston
116

En la versión 0.24. + Pandas ha ganado la capacidad de mantener tipos enteros con valores faltantes.

Tipo de datos entero anulable .

Los pandas pueden representar datos enteros con valores posiblemente perdidos utilizando arrays.IntegerArray. Este es un tipo de extensión implementado dentro de pandas. No es el dtype predeterminado para enteros, y no se deducirá; debe pasar explícitamente la dtype en array()o Series:

arr = pd.array([1, 2, np.nan], dtype=pd.Int64Dtype())
pd.Series(arr)

0      1
1      2
2    NaN
dtype: Int64

Para convertir columnas a enteros anulables, use:

df['myCol'] = df['myCol'].astype('Int64')
jezrael
fuente
44
Me gusta esta respuesta
cs95
8
Tenga en cuenta que dtype debe ser "Int64"y no "int64"(primero 'i' debe estar en mayúscula)
Viacheslav Z
2
df.myCol = df.myCol.astype('Int64')odf['myCol'] = df['myCol'].astype('Int64')
LoMaPh
43

Mi caso de uso es mezclar datos antes de cargarlos en una tabla DB:

df[col] = df[col].fillna(-1)
df[col] = df[col].astype(int)
df[col] = df[col].astype(str)
df[col] = df[col].replace('-1', np.nan)

Elimine los NaN, conviértalos en int, conviértalos en str y luego vuelva a insertar los NAN.

¡No es bonito pero hace el trabajo!

hibernado
fuente
1
Me he estado sacando el pelo tratando de cargar números de serie donde algunos son nulos y el resto son flotantes, esto me salvó.
Chris Decker
1
El OP quiere una columna de enteros. Convertirlo a cadena no cumple la condición.
Rishab Gupta
1
Funciona solo si col aún no tiene -1. De lo contrario, se
meterá
entonces como volver a int .. ??
Abdoulsn
5

Ahora es posible crear una columna de pandas que contenga NaNs como dtype int, ya que ahora se agrega oficialmente en pandas 0.24.0

Notas de la versión de pandas 0.24.x Cita: " Pandas ha ganado la capacidad de mantener tipos enteros con valores perdidos

mork
fuente
4

Si desea combinar enteros y NaN en una columna, puede usar el tipo de datos 'objeto':

df['col'] = (
    df['col'].fillna(0)
    .astype(int)
    .astype(object)
    .where(df['col'].notnull())
)

Esto reemplazará los NaN con un número entero (no importa cuál), se convertirá en int, se convertirá en objeto y finalmente volverá a insertar los NaN.

jmenglund
fuente
3

Si puede modificar sus datos almacenados, use un valor centinela para los que faltan id. Un caso de uso común, inferido por el nombre de la columna, ya que ides un número entero, estrictamente mayor que cero, podría usarlo 0como un valor centinela para poder escribir

if row['id']:
   regular_process(row)
else:
   special_process(row)
gboffi
fuente
3

Podría usar .dropna()si está bien soltar las filas con los valores NaN.

df = df.dropna(subset=['id'])

Alternativamente, use .fillna()y .astype()para reemplazar el NaN con valores y conviértalos a int.

Me encontré con este problema al procesar un archivo CSV con enteros grandes, mientras faltaban algunos de ellos (NaN). Usar float como tipo no era una opción, porque podría perder la precisión.

Mi solución fue usar str como el tipo intermedio . Luego puede convertir la cadena a int como desee más adelante en el código. Reemplacé NaN con 0, pero puedes elegir cualquier valor.

df = pd.read_csv(filename, dtype={'id':str})
df["id"] = df["id"].fillna("0").astype(int)

Para la ilustración, aquí hay un ejemplo de cómo los flotadores pueden perder precisión:

s = "12345678901234567890"
f = float(s)
i = int(f)
i2 = int(s)
print (f, i, i2)

Y la salida es:

1.2345678901234567e+19 12345678901234567168 12345678901234567890
elomage
fuente
2

La mayoría de las soluciones aquí le indican cómo usar un número entero de marcador de posición para representar nulos. Sin embargo, ese enfoque no es útil si no está seguro de que el entero no aparezca en sus datos de origen. Mi método con formateará flotantes sin sus valores decimales y convertirá nulos a Ninguno. El resultado es un tipo de datos de objeto que se verá como un campo entero con valores nulos cuando se carga en un CSV.

keep_df[col] = keep_df[col].apply(lambda x: None if pandas.isnull(x) else '{0:.0f}'.format(pandas.to_numeric(x)))
Corbin
fuente
1

Me encontré con este problema trabajando con pyspark. Como se trata de una interfaz de Python para el código que se ejecuta en un jvm, requiere seguridad de tipo y el uso de float en lugar de int no es una opción. Trabajé en torno al problema envolviendo los pandas pd.read_csven una función que llenará las columnas definidas por el usuario con los valores de relleno definidos por el usuario antes de convertirlos al tipo requerido. Esto es lo que terminé usando:

def custom_read_csv(file_path, custom_dtype = None, fill_values = None, **kwargs):
    if custom_dtype is None:
        return pd.read_csv(file_path, **kwargs)
    else:
        assert 'dtype' not in kwargs.keys()
        df = pd.read_csv(file_path, dtype = {}, **kwargs)
        for col, typ in custom_dtype.items():
            if fill_values is None or col not in fill_values.keys():
                fill_val = -1
            else:
                fill_val = fill_values[col]
            df[col] = df[col].fillna(fill_val).astype(typ)
    return df
Neuneck
fuente
1
import pandas as pd

df= pd.read_csv("data.csv")
df['id'] = pd.to_numeric(df['id'])
Monaheng Ramochele
fuente
44
¿Hay alguna razón por la que prefiera esta formulación sobre la propuesta en la respuesta aceptada? Si es así, sería útil editar su respuesta para proporcionar esa explicación, y especialmente porque hay diez respuestas adicionales que compiten por la atención.
Jeremy Caney
Si bien este código puede resolver el problema del OP, es mejor incluir una explicación de cómo / por qué su código lo aborda. De esta forma, los futuros visitantes pueden aprender de su publicación y aplicarla a su propio código. SO no es un servicio de codificación, sino un recurso para el conocimiento. Además, es más probable que se voten las respuestas completas de alta calidad. Estas características, junto con el requisito de que todas las publicaciones sean independientes, son algunos de los puntos fuertes de SO, ya que una plataforma lo diferencia de los foros. Puede editagregar información adicional y / o complementar sus explicaciones con la documentación de origen.
SherylHohman
0

Primero elimine las filas que contienen NaN. Luego, haga la conversión entera en las filas restantes. Por fin inserte las filas eliminadas nuevamente. Espero que funcione

kamran kausar
fuente
-1

Suponiendo que su DateColumn formateado 3312018.0 se convierta a 31/03/2018 como una cadena. Y faltan algunos registros o 0.

df['DateColumn'] = df['DateColumn'].astype(int)
df['DateColumn'] = df['DateColumn'].astype(str)
df['DateColumn'] = df['DateColumn'].apply(lambda x: x.zfill(8))
df.loc[df['DateColumn'] == '00000000','DateColumn'] = '01011980'
df['DateColumn'] = pd.to_datetime(df['DateColumn'], format="%m%d%Y")
df['DateColumn'] = df['DateColumn'].apply(lambda x: x.strftime('%m/%d/%Y'))
Justin Malinchak
fuente