Solte linhas contendo células vazias de um DataFrame do pandas

87

Eu tenho um pd.DataFrameque foi criado analisando algumas planilhas do Excel. Uma coluna com células vazias. Por exemplo, abaixo está a saída para a frequência dessa coluna, 32320 registros têm valores ausentes para Locatário .

>>> 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

Estou tentando descartar linhas em que o inquilino está ausente, mas a .isnull()opção não reconhece os valores ausentes.

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

A coluna possui tipo de dados "Objeto". O que está acontecendo neste caso? Como posso descartar registros onde o inquilino está faltando?

Amrita Sawant
fonte

Respostas:

174

O Pandas reconhecerá um valor como nulo se for um np.nanobjeto, que será impresso como NaNno DataFrame. Seus valores ausentes são provavelmente strings vazias, que o Pandas não reconhece como nulas. Para corrigir isso, você pode converter os stings vazios (ou o que quer que esteja em suas células vazias) em np.nanobjetos usando replace()e, em seguida, chamar dropna()em seu DataFrame para excluir linhas com inquilinos nulos.

Para demonstrar, criamos um DataFrame com alguns valores aleatórios e algumas strings vazias em uma Tenantscoluna:

>>> 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         

Agora, substituímos quaisquer strings vazias na Tenantscoluna por np.nanobjetos, assim:

>>> 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

Agora podemos descartar os valores nulos:

>>> 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
fonte
Muito obrigado, vou tentar e voltar!
Amrita Sawant
2
@mcmath, um pouco curioso. Por que você importa numpy e usa np.nanquando você pode fazer pd.np.nan?
propjk007
3
@ propjk007, como em muitas coisas na vida, existem muitas maneiras de fazer muitas coisas
andrew
Pelos meus testes , parece que df[df['Tenant'].astype(bool)](assumindo que não há caracteres de espaço em branco - apenas string vazia) é mais rápido do quedf.replace('', np.nan).dropna(subset=['Tenant'])
cs95
42

Pítônico + Pandorável: df[df['col'].astype(bool)]

Strings vazias são falsas, o que significa que você pode filtrar por valores booleanos como este:

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

Se seu objetivo é remover não apenas strings vazias, mas também strings que contêm apenas espaços em branco, use str.stripantes:

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

Mais rápido do que você pensa

.astypeé uma operação vetorizada, isso é mais rápido do que todas as opções apresentadas até agora. Pelo menos, pelos meus testes. YMMV.

Aqui está uma comparação de tempo, acrescentei alguns outros métodos que poderia imaginar.

insira a descrição da imagem aqui

Código de benchmarking, para referência:

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
fonte
33

value_counts omite NaN por padrão, então você provavelmente está lidando com "".

Então você pode apenas filtrá-los como

filter = df["Tenant"] != ""
dfNew = df[filter]
Bob Haffner
fonte
1
A solução @Bobs não funcionou para mim. df.dropna (subset = ['tenant'], inplace = True) funciona.
Amrita Sawant
1
Me desculpe por isso. Achei que você estava lidando com "" s. Você deve postar sua solução como uma resposta
Bob Haffner
8

Há uma situação em que a célula tem espaço em branco, você não consegue ver, use

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

para substituir o espaço em branco como NaN, então

df= df.dropna(subset=['col'])
Aprender
fonte
4

Você pode usar esta variação:

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

Isso produzirá (** - destacando apenas as linhas desejadas):

   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

Portanto, para descartar tudo que não tem um valor de 'educação', use o código abaixo:

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

('~' indicando NÃO)

Resultado:

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