Convertir la colonne Pandas contenant NaNs en dtype `int`


175

J'ai lu les données d'un fichier .csv dans une trame de données Pandas comme ci-dessous. Pour l'une des colonnes, à savoir id, je veux spécifier le type de colonne comme int. Le problème est que la idsérie a des valeurs manquantes / vides.

Lorsque j'essaie de convertir la idcolonne en entier lors de la lecture du .csv, j'obtiens:

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

Alternativement, j'ai essayé de convertir le type de colonne après avoir lu comme ci-dessous, mais cette fois j'obtiens:

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

Comment puis-je résoudre ce problème?


3
Je pense que les valeurs entières ne peuvent pas être converties ou stockées dans une série / dataframe s'il y a des valeurs / NaN manquantes. Je pense que cela a à voir avec la compatibilité numpy (je suppose ici), si vous voulez la compatibilité des valeurs manquantes, je stockerais les valeurs sous forme de flottants
EdChum

1
voir ici: pandas.pydata.org/pandas-docs/dev/… ; vous devez avoir un dtype flottant quand u a des valeurs manquantes (ou techniquement un dtype d'objet mais c'est inefficace); quel est votre objectif en utilisant le type int?
Jeff

6
Je crois que c'est un problème NumPy, pas spécifique aux Pandas. C'est dommage car il y a tellement de cas où avoir un type int qui permet la possibilité de valeurs nulles est beaucoup plus efficace qu'une grande colonne de flottants.
ely

1
J'ai aussi un problème avec ça. J'ai plusieurs dataframes que je souhaite fusionner sur la base d'une représentation sous forme de chaîne de plusieurs colonnes "entières". Cependant, lorsqu'une de ces colonnes entières a un np.nan, la conversion de chaîne produit un ".0", ce qui annule la fusion. Rend simplement les choses un peu plus compliquées, ce serait bien s'il y avait une solution simple.
dermen du

1
@Rhubarb, le support facultatif des entiers nulles est maintenant officiellement ajouté sur pandas 0.24.0 - enfin :) - veuillez trouver une réponse mise à jour ci-dessous. notes de version pandas 0.24.x
mork

Réponses:


169

Le manque de représentant NaN dans les colonnes entières est un "gotcha" pandas .

La solution de contournement habituelle consiste simplement à utiliser des flottants.


13
Existe-t-il d'autres solutions de contournement en plus de les traiter comme des flotteurs?
NumenorForLife

3
@ jsc123 vous pouvez utiliser l'objet dtype. Cela vient avec un petit avertissement de santé mais fonctionne bien pour la plupart.
Andy Hayden

1
Pouvez-vous donner un exemple d'utilisation de l'objet dtype? J'ai parcouru les documents pandas et Google, et j'ai lu que c'était la méthode recommandée. Mais, je n'ai pas trouvé d'exemple d'utilisation de l'objet dtype.
MikeyE

30
Dans la v0.24, vous pouvez maintenant faire df = df.astype(pd.Int32Dtype())(pour convertir la totalité du dataFrame, ou) df['col'] = df['col'].astype(pd.Int32Dtype()). Les autres types d'entiers nullables acceptés sont pd.Int16Dtypeet pd.Int64Dtype. Choisissez votre poison.
cs95 le

1
C'est une valeur NaN, mais la vérification n'est pas du tout possible :(
Winston

119

Dans la version 0.24. + Pandas a acquis la possibilité de contenir des dtypes entiers avec des valeurs manquantes.

Type de données Nullable Integer .

Les pandas peuvent représenter des données entières avec des valeurs éventuellement manquantes en utilisant arrays.IntegerArray. Il s'agit d'une extension de types implémentée dans les pandas. Ce n'est pas le dtype par défaut pour les entiers et ne sera pas déduit; vous devez explicitement passer le dtype dans array()ou Series:

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

0      1
1      2
2    NaN
dtype: Int64

Pour convertir la colonne en entiers Nullable, utilisez:

df['myCol'] = df['myCol'].astype('Int64')

4
J'aime cette réponse.
cs95 le

9
Notez que dtype doit être "Int64"et non "int64"(le premier «i» doit être en majuscule)
Viacheslav Z

2
df.myCol = df.myCol.astype('Int64')oudf['myCol'] = df['myCol'].astype('Int64')
LoMaPh

43

Mon cas d'utilisation est la fusion des données avant de les charger dans une table 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)

Supprimez les NaN, convertissez en int, convertissez en str, puis réinsérez les NAN.

Ce n'est pas joli mais ça fait le boulot!


1
Je me suis arraché les cheveux en essayant de charger des numéros de série où certains sont nuls et le reste sont des flotteurs, cela m'a sauvé.
Chris Decker

1
L'OP veut une colonne d'entiers. Le convertir en chaîne ne remplit pas la condition.
Rishab Gupta

1
Fonctionne uniquement si col n'a pas déjà -1. Sinon, cela va gâcher les données
Sharvari Gc

alors comment revenir à int .. ??
abdoulsn le

5

Il est désormais possible de créer une colonne pandas contenant NaNs comme dtype int, puisqu'elle est désormais officiellement ajoutée sur pandas 0.24.0

notes de publication de pandas 0.24.x Citation: " Pandas a acquis la possibilité de contenir des dtypes entiers avec des valeurs manquantes


4

Si vous souhaitez absolument combiner des entiers et des NaN dans une colonne, vous pouvez utiliser le type de données 'objet':

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

Cela remplacera NaNs par un entier (peu importe lequel), convertira en int, convertira en objet et enfin réinsérera NaNs.


3

Si vous pouvez modifier vos données stockées, utilisez une valeur sentinelle pour les données manquantes id. Un cas d'utilisation courant, inféré par le nom de la colonne, étant idun entier, strictement supérieur à zéro, vous pouvez l'utiliser 0comme valeur sentinelle afin de pouvoir écrire

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

3

Vous pouvez utiliser .dropna()si c'est OK pour supprimer les lignes avec les valeurs NaN.

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

Vous pouvez également utiliser .fillna()et .astype()pour remplacer le NaN par des valeurs et les convertir en int.

J'ai rencontré ce problème lors du traitement d'un fichier CSV avec de grands entiers, alors que certains d'entre eux manquaient (NaN). Utiliser float comme type n'était pas une option, car je risquais de perdre la précision.

Ma solution était d' utiliser str comme type intermédiaire . Ensuite, vous pouvez convertir la chaîne en int comme vous le souhaitez plus tard dans le code. J'ai remplacé NaN par 0, mais vous pouvez choisir n'importe quelle valeur.

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

Pour l'illustration, voici un exemple de la façon dont les flotteurs peuvent perdre la précision:

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

Et le résultat est:

1.2345678901234567e+19 12345678901234567168 12345678901234567890

2

La plupart des solutions présentées ici vous indiquent comment utiliser un entier d'espace réservé pour représenter des valeurs nulles. Cette approche n'est pas utile si vous n'êtes pas sûr que l'entier n'apparaîtra pas dans vos données source. Ma méthode avec formatera les flottants sans leurs valeurs décimales et convertira les valeurs nulles en None. Le résultat est un type de données d'objet qui ressemblera à un champ entier avec des valeurs nulles lorsqu'il est chargé dans un CSV.

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

1

J'ai rencontré ce problème en travaillant avec pyspark. Comme il s'agit d'un frontend python pour le code exécuté sur un jvm, il nécessite la sécurité de type et l'utilisation de float au lieu de int n'est pas une option. J'ai contourné le problème en enveloppant les pandas pd.read_csvdans une fonction qui remplira les colonnes définies par l'utilisateur avec des valeurs de remplissage définies par l'utilisateur avant de les convertir au type requis. Voici ce que j'ai fini par utiliser:

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

1
import pandas as pd

df= pd.read_csv("data.csv")
df['id'] = pd.to_numeric(df['id'])

4
Y a-t-il une raison pour laquelle vous préférez cette formulation à celle proposée dans la réponse acceptée? Si tel est le cas, il serait utile de modifier votre réponse pour fournir cette explication - et d'autant plus qu'il y a dix réponses supplémentaires qui se disputent l'attention.
Jeremy Caney le

Bien que ce code puisse résoudre le problème de l'OP, il est préférable d'inclure une explication sur la manière / la raison pour laquelle votre code le résout. De cette manière, les futurs visiteurs peuvent apprendre de votre publication et l'appliquer à leur propre code. SO n'est pas un service de codage, mais une ressource de connaissances. De plus, les réponses complètes et de haute qualité sont plus susceptibles d'être votées. Ces fonctionnalités, ainsi que l'exigence que tous les messages soient autonomes, sont quelques-unes des forces de SO car une plate-forme le différencie des forums. Vous pouvez editajouter des informations supplémentaires et / ou compléter vos explications avec la documentation source.
SherylHohman

0

Supprimez d'abord les lignes contenant NaN. Ensuite, effectuez une conversion entière sur les lignes restantes. Enfin, insérez à nouveau les lignes supprimées. J'espère que ça fonctionnera


-1

En supposant que votre DateColumn formatée 3312018.0 doit être convertie en 31/03/2018 sous forme de chaîne. Et, certains enregistrements sont manquants ou 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'))
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.