Upuść wiersze zawierające puste komórki z pandy DataFrame

87

Mam plik pd.DataFrameutworzony przez analizę niektórych arkuszy kalkulacyjnych programu Excel. Kolumna zawierająca puste komórki. Na przykład poniżej przedstawiono dane wyjściowe dla częstotliwości tej kolumny, w 32320 rekordach brakuje wartości dla dzierżawcy .

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

Próbuję usunąć wiersze, w których brakuje dzierżawcy, jednak .isnull()opcja nie rozpoznaje brakujących wartości.

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

Kolumna ma typ danych „Obiekt”. Co się dzieje w tym przypadku? Jak mogę usunąć rekordy w przypadku braku najemcy ?

Amrita Sawant
źródło

Odpowiedzi:

174

Pandy rozpoznają wartość jako null, jeśli jest to np.nanobiekt, który zostanie wydrukowany tak, jak NaNw DataFrame. Twoje brakujące wartości to prawdopodobnie puste łańcuchy, których Pandy nie rozpoznaje jako zerowe. Aby to naprawić, możesz przekonwertować puste żądła (lub cokolwiek jest w pustych komórkach) na np.nanobiekty przy użyciu replace(), a następnie wywołać dropna()ramkę DataFrame, aby usunąć wiersze z pustymi dzierżawami.

Aby to zademonstrować, tworzymy DataFrame z pewnymi przypadkowymi wartościami i kilkoma pustymi ciągami w Tenantskolumnie:

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

Teraz zastępujemy puste ciągi w Tenantskolumnie np.nanobiektami, na przykład:

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

Teraz możemy usunąć wartości null:

>>> 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
źródło
Wielkie dzięki, spróbuję i wrócę!
Amrita Sawant
2
@mcmath, trochę zaciekawiony. Dlaczego importujesz numpy i używasz, np.nankiedy możesz pd.np.nan?
propjk007
3
@ propjk007, podobnie jak w przypadku wielu rzeczy w życiu, jest wiele sposobów na zrobienie wielu rzeczy
andrew
Z moich testów wynika, że ​​robi df[df['Tenant'].astype(bool)](zakładając brak białych znaków - tylko pusty ciąg) jest szybszy niżdf.replace('', np.nan).dropna(subset=['Tenant'])
cs95
43

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

Puste ciągi są fałszywe, co oznacza, że ​​możesz filtrować wartości logiczne w następujący sposób:

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

Jeśli Twoim celem jest usunięcie nie tylko pustych napisów, ale także ciągów zawierających tylko spacje, użyj str.stripwcześniej:

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

Szybciej niż myślisz

.astypejest operacją zwektoryzowaną, jest to szybsze niż wszystkie dotychczas prezentowane opcje. Przynajmniej z moich testów. YMMV.

Oto porównanie czasu, dorzuciłem kilka innych metod, o których mogłem pomyśleć.

wprowadź opis obrazu tutaj

Kod porównawczy, w celach informacyjnych:

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
źródło
33

value_counts domyślnie pomija NaN, więc najprawdopodobniej masz do czynienia z „”.

Możesz więc po prostu je odfiltrować, np

filter = df["Tenant"] != ""
dfNew = df[filter]
Bob Haffner
źródło
1
Rozwiązanie @Bobs nie działa dla mnie. df.dropna (subset = ['najemca'], inplace = True) działa.
Amrita Sawant
1
Przepraszam za to. Myślałem, że masz do czynienia z „”. Powinieneś zamieścić swoje rozwiązanie jako odpowiedź
Bob Haffner
8

Jest sytuacja, w której komórka ma spację, nie możesz jej zobaczyć, użyj

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

aby zastąpić białe znaki jako NaN, a następnie

df= df.dropna(subset=['col'])
Uczyć się
źródło
4

Możesz użyć tej odmiany:

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

Spowoduje to wyświetlenie (** - podświetlanie tylko wybranych wierszy):

   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

Aby usunąć wszystko, co nie ma wartości „edukacyjnej”, użyj poniższego kodu:

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

('~' oznacza NIE)

Wynik:

   age education gender name
0   39        ma      m   n1
2   27    school      f   n3
4   36        ba      f   n5
Amir F.
źródło