Supprimez les lignes contenant des cellules vides d'un pandas DataFrame

87

J'ai un pd.DataFramequi a été créé en analysant des feuilles de calcul Excel. Une colonne qui contient des cellules vides. Par exemple, ci-dessous se trouve la sortie pour la fréquence de cette colonne, 32320 enregistrements ont des valeurs manquantes pour Tenant .

>>> value_counts(Tenant, normalize=False)
                              32320
    Thunderhead                8170
    Big Data Others            5700
    Cloud Cruiser              5700
    Partnerpedia               5700
    Comcast                    5700
    SDP                        5700
    Agora                      5700
    dtype: int64

J'essaie de supprimer les lignes où le locataire est manquant, mais l' .isnull()option ne reconnaît pas les valeurs manquantes.

>>> df['Tenant'].isnull().sum()
    0

La colonne a le type de données "Objet". Que se passe-t-il dans ce cas? Comment puis-je supprimer des enregistrements où le locataire est manquant?

Amrita Sawant
la source

Réponses:

174

Pandas reconnaîtra une valeur comme nulle s'il s'agit d'un np.nanobjet, qui s'imprimera comme NaNdans le DataFrame. Vos valeurs manquantes sont probablement des chaînes vides, que Pandas ne reconnaît pas comme nulles. Pour résoudre ce problème, vous pouvez convertir les stings vides (ou tout ce qui se trouve dans vos cellules vides) en np.nanobjets à l'aide replace(), puis appeler dropna()votre DataFrame pour supprimer les lignes avec des locataires nuls.

Pour démontrer, nous créons un DataFrame avec des valeurs aléatoires et des chaînes vides dans une Tenantscolonne:

>>> import pandas as pd
>>> import numpy as np
>>> 
>>> df = pd.DataFrame(np.random.randn(10, 2), columns=list('AB'))
>>> df['Tenant'] = np.random.choice(['Babar', 'Rataxes', ''], 10)
>>> print df

          A         B   Tenant
0 -0.588412 -1.179306    Babar
1 -0.008562  0.725239         
2  0.282146  0.421721  Rataxes
3  0.627611 -0.661126    Babar
4  0.805304 -0.834214         
5 -0.514568  1.890647    Babar
6 -1.188436  0.294792  Rataxes
7  1.471766 -0.267807    Babar
8 -1.730745  1.358165  Rataxes
9  0.066946  0.375640         

Maintenant, nous remplaçons toutes les chaînes vides de la Tenantscolonne par des np.nanobjets, comme ceci:

>>> df['Tenant'].replace('', np.nan, inplace=True)
>>> print df

          A         B   Tenant
0 -0.588412 -1.179306    Babar
1 -0.008562  0.725239      NaN
2  0.282146  0.421721  Rataxes
3  0.627611 -0.661126    Babar
4  0.805304 -0.834214      NaN
5 -0.514568  1.890647    Babar
6 -1.188436  0.294792  Rataxes
7  1.471766 -0.267807    Babar
8 -1.730745  1.358165  Rataxes
9  0.066946  0.375640      NaN

Maintenant, nous pouvons supprimer les valeurs nulles:

>>> df.dropna(subset=['Tenant'], inplace=True)
>>> print df

          A         B   Tenant
0 -0.588412 -1.179306    Babar
2  0.282146  0.421721  Rataxes
3  0.627611 -0.661126    Babar
5 -0.514568  1.890647    Babar
6 -1.188436  0.294792  Rataxes
7  1.471766 -0.267807    Babar
8 -1.730745  1.358165  Rataxes
McMath
la source
Merci beaucoup, je vais essayer et revenir!
Amrita Sawant
2
@mcmath, un peu curieux. Pourquoi importez-vous numpy et utilisez np.nan-vous quand vous pouvez le faire pd.np.nan?
propjk007
3
@ propjk007, comme pour beaucoup de choses dans la vie, il existe de nombreuses façons de faire beaucoup de choses
andrew
D'après mes tests , il semble que faire df[df['Tenant'].astype(bool)](en supposant qu'il n'y ait pas de caractères d'espace blanc - chaîne vide uniquement) est plus rapide quedf.replace('', np.nan).dropna(subset=['Tenant'])
cs95
42

Pythonique + Pandorable: df[df['col'].astype(bool)]

Les chaînes vides sont fausses, ce qui signifie que vous pouvez filtrer sur des valeurs booléennes comme ceci:

df = pd.DataFrame({
    'A': range(5),
    'B': ['foo', '', 'bar', '', 'xyz']
})
df
   A    B
0  0  foo
1  1     
2  2  bar
3  3     
4  4  xyz
df['B'].astype(bool)                                                                                                                      
0     True
1    False
2     True
3    False
4     True
Name: B, dtype: bool

df[df['B'].astype(bool)]                                                                                                                  
   A    B
0  0  foo
2  2  bar
4  4  xyz

Si votre objectif est de supprimer non seulement les chaînes vides, mais également les chaînes contenant uniquement des espaces, utilisez str.stripau préalable:

df[df['B'].str.strip().astype(bool)]
   A    B
0  0  foo
2  2  bar
4  4  xyz

Plus rapide que vous ne le pensez

.astypeest une opération vectorisée, c'est plus rapide que toutes les options présentées jusqu'à présent. Au moins, d'après mes tests. YMMV.

Voici une comparaison de temps, j'ai lancé d'autres méthodes auxquelles je pourrais penser.

entrez la description de l'image ici

Code d'analyse comparative, pour référence:

import pandas as pd
import perfplot

df1 = pd.DataFrame({
    'A': range(5),
    'B': ['foo', '', 'bar', '', 'xyz']
})

perfplot.show(
    setup=lambda n: pd.concat([df1] * n, ignore_index=True),
    kernels=[
        lambda df: df[df['B'].astype(bool)],
        lambda df: df[df['B'] != ''],
        lambda df: df[df['B'].replace('', np.nan).notna()],  # optimized 1-col
        lambda df: df.replace({'B': {'': np.nan}}).dropna(subset=['B']),  
    ],
    labels=['astype', "!= ''", "replace + notna", "replace + dropna", ],
    n_range=[2**k for k in range(1, 15)],
    xlabel='N',
    logx=True,
    logy=True,
    equality_check=pd.DataFrame.equals)
cs95
la source
33

value_counts omet NaN par défaut, vous avez donc probablement affaire à "".

Donc, vous pouvez simplement les filtrer comme

filter = df["Tenant"] != ""
dfNew = df[filter]
Bob Haffner
la source
1
La solution @Bobs n'a pas fonctionné pour moi. df.dropna (subset = ['tenant'], inplace = True) fonctionne.
Amrita Sawant
1
Désolé pour ça. Je pensais que vous parliez de "" s. Vous devriez poster votre solution comme réponse
Bob Haffner
8

Il y a une situation où la cellule a un espace blanc, vous ne pouvez pas le voir, utilisez

df['col'].replace('  ', np.nan, inplace=True)

pour remplacer l'espace blanc par NaN, puis

df= df.dropna(subset=['col'])
Apprendre
la source
4

Vous pouvez utiliser cette variante:

import pandas as pd
vals = {
    'name' : ['n1', 'n2', 'n3', 'n4', 'n5', 'n6', 'n7'],
    'gender' : ['m', 'f', 'f', 'f',  'f', 'c', 'c'],
    'age' : [39, 12, 27, 13, 36, 29, 10],
    'education' : ['ma', None, 'school', None, 'ba', None, None]
}
df_vals = pd.DataFrame(vals) #converting dict to dataframe

Cela produira (** - ne mettant en évidence que les lignes souhaitées):

   age education gender name
0   39        ma      m   n1 **
1   12      None      f   n2    
2   27    school      f   n3 **
3   13      None      f   n4
4   36        ba      f   n5 **
5   29      None      c   n6
6   10      None      c   n7

Donc, pour supprimer tout ce qui n'a pas de valeur `` éducation '', utilisez le code ci-dessous:

df_vals = df_vals[~df_vals['education'].isnull()] 

('~' indiquant NON)

Résultat:

   age education gender name
0   39        ma      m   n1
2   27    school      f   n3
4   36        ba      f   n5
Amir F
la source