Usuń wiersze z pandy DataFrame na podstawie wyrażenia warunkowego obejmującego len (ciąg) podający KeyError

303

Mam pand DataFrame i chcę usunąć z niej wiersze, w których długość ciągu w określonej kolumnie jest większa niż 2.

Oczekuję, że będę w stanie to zrobić (według tej odpowiedzi ):

df[(len(df['column name']) < 2)]

ale dostaję błąd:

KeyError: u'no item named False'

Co ja robię źle?

(Uwaga: Wiem, że mogę df.dropna()się pozbyć wierszy zawierających takie wiersze NaN, ale nie widziałem, jak usunąć wiersze na podstawie wyrażenia warunkowego).

sjs
źródło

Odpowiedzi:

168

Kiedy to zrobisz len(df['column name']), otrzymujesz tylko jedną liczbę, a mianowicie liczbę wierszy w ramce danych (tj. Długość samej kolumny). Jeśli chcesz zastosować lendo każdego elementu w kolumnie, użyj df['column name'].map(len). Więc spróbuj

df[df['column name'].map(len) < 2]
BrenBarn
źródło
3
Wymyśliłem sposób, używając listowego zrozumienia: df[[(len(x) < 2) for x in df['column name']]]ale twój jest o wiele ładniejszy. Dzięki za pomoc!
sjs
13
W przypadku, gdy ktoś potrzebuje bardziej złożonego porównania, zawsze można zastosować lambda. df[df['column name'].map(lambda x: str(x)!=".")]
4lberto
1
Z jakiegoś powodu żadna inna opcja nie działała dla mnie, z wyjątkiem tej opublikowanej przez @ 4lberto. Jestem włączony pandas 0.23.4i python 3.6
goelakash
1
Dodałbym .copy()na końcu, na wypadek gdybyś chciał później edytować tę ramkę danych (na przykład przypisanie nowych kolumn spowodowałoby podniesienie ostrzeżenia „Próbuję ustawić wartość na kopii wycinka z DataFrame”.
PlasmaBinturong
806

Aby bezpośrednio odpowiedzieć na pierwotny tytuł tego pytania „Jak usunąć wiersze z pandy DataFrame na podstawie wyrażenia warunkowego” (co, jak rozumiem, niekoniecznie jest problemem OP, ale może pomóc innym użytkownikom napotkanym na to pytanie) jednym ze sposobów na to jest użycie spadek sposób:

df = df.drop(some labels)

df = df.drop(df[<some boolean condition>].index)

Przykład

Aby usunąć wszystkie wiersze, w których wynik „kolumny” wynosi <50:

df = df.drop(df[df.score < 50].index)

Wersja na miejscu (jak wskazano w komentarzach)

df.drop(df[df.score < 50].index, inplace=True)

Wiele warunków

(patrz Indeksowanie boolowskie )

Operatory to: |for or, &for andi ~for not. Muszą być pogrupowane za pomocą nawiasów.

Aby usunąć wszystkie wiersze, w których kolumna „score” wynosi <50 i> 20

df = df.drop(df[(df.score < 50) & (df.score > 20)].index)

Użytkownik
źródło
32
Chcę tylko zauważyć, że funkcja upuszczania obsługuje zastępowanie w miejscu. To znaczy,. twoje rozwiązanie jest takie samo jak df.drop (df [df.score <50] .index, inplace = True). Niemniej jednak nie znałem sztuczki „indeksowania”. Bardzo mi
pomogło
9
Po prostu chcę zauważyć, że przed użyciem tej sztuczki indeksu należy upewnić się, że wartości indeksu są unikalne (lub wywołać reset_index()). Przekonałem się o tym na własnej skórze, gdy z mojej ramki danych spadła droga do wielu wierszy.
Jay,
3
jak upuścić wszystkie wiersze, w których typ kolumny to str? Chcę zachować tylko typy kolumn listy. Próbowałem test = df.drop(df[df['col1'].dtype == str].index), ale pojawia się błąd KeyError: False Próbowałem również df.drop(df[df.col1.dtype == str].index)i df.drop(df[type(df.cleaned_norm_email) == str].index)ale nic nie wydaje się do pracy? Czy ktoś może doradzić. Dzięki! @ Użytkownik
PyRsquared
1
To stare pytanie, ale ... @ ryba-wyzwanie-ryba jest o wiele szybsza niż ta. Pamiętaj, że obliczasz df[(df.score < 50) & (df.score > 20)]jako część swojej odpowiedzi. Jeśli to zrobisz df = df[(df.score >= 50) | (df.score <= 20)], uzyskasz odpowiedź znacznie szybciej.
Roobie Nuby
1
@RoobieNuby - nie są w tym samym stanie.
Nguai al
106

Możesz przypisać DataFramedo odfiltrowanej wersji samego siebie:

df = df[df.score > 50]

Jest to szybsze niż drop:

%%timeit
test = pd.DataFrame({'x': np.random.randn(int(1e6))})
test = test[test.x < 0]
# 54.5 ms ± 2.02 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

%%timeit
test = pd.DataFrame({'x': np.random.randn(int(1e6))})
test.drop(test[test.x > 0].index, inplace=True)
# 201 ms ± 17.9 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

%%timeit
test = pd.DataFrame({'x': np.random.randn(int(1e6))})
test = test.drop(test[test.x > 0].index)
# 194 ms ± 7.03 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
Kabard
źródło
Jak sprawdzić, czy wiele kolumn używa lub czy warunek?
Piyush S. Wanare
9

Rozwijam ogólne rozwiązanie @ User, aby zapewnić drop bezpłatną alternatywę. Dotyczy to osób skierowanych tutaj na podstawie tytułu pytania (nie problemu OP)

Powiedz, że chcesz usunąć wszystkie wiersze z wartościami ujemnymi. Jednym rozwiązaniem liniowym jest:

df = df[(df > 0).all(axis=1)]

Krok po kroku Objaśnienie: -

Wygenerujmy losową ramkę danych o rozkładzie normalnym 5x5

np.random.seed(0)
df = pd.DataFrame(np.random.randn(5,5), columns=list('ABCDE'))
      A         B         C         D         E
0  1.764052  0.400157  0.978738  2.240893  1.867558
1 -0.977278  0.950088 -0.151357 -0.103219  0.410599
2  0.144044  1.454274  0.761038  0.121675  0.443863
3  0.333674  1.494079 -0.205158  0.313068 -0.854096
4 -2.552990  0.653619  0.864436 -0.742165  2.269755

Niech warunek usunie negatywne. Wartość logiczna df spełniająca warunek:

df > 0
      A     B      C      D      E
0   True  True   True   True   True
1  False  True  False  False   True
2   True  True   True   True   True
3   True  True  False   True  False
4  False  True   True  False   True

Szereg boolowski dla wszystkich wierszy spełniających warunek Uwaga: jeśli dowolny element w wierszu nie spełni warunku, wiersz zostanie oznaczony jako fałsz

(df > 0).all(axis=1)
0     True
1    False
2     True
3    False
4    False
dtype: bool

Na koniec odfiltruj wiersze z ramki danych na podstawie warunku

df[(df > 0).all(axis=1)]
      A         B         C         D         E
0  1.764052  0.400157  0.978738  2.240893  1.867558
2  0.144044  1.454274  0.761038  0.121675  0.443863

Możesz przypisać go z powrotem do df, aby faktycznie usunąć vs filtr wykonane powyżej
df = df[(df > 0).all(axis=1)]

Można to łatwo rozszerzyć, aby odfiltrować wiersze zawierające NaN (wpisy nienumeryczne): -
df = df[(~df.isnull()).all(axis=1)]

Można to również uprościć dla przypadków takich jak: Usuń wszystkie wiersze, w których kolumna E jest ujemna

df = df[(df.E>0)]

Chciałbym zakończyć niektórymi statystykami profilowania, dlaczego droprozwiązanie @ User jest wolniejsze niż filtrowanie oparte na surowej kolumnie: -

%timeit df_new = df[(df.E>0)]
345 µs ± 10.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%timeit dft.drop(dft[dft.E < 0].index, inplace=True)
890 µs ± 94.9 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

Kolumna jest w zasadzie Seriesczyli NumPytablica może być indeksowane bez żadnych kosztów. Dla osób zainteresowanych tym, w jaki sposób podstawowa organizacja pamięci odgrywa rolę w szybkości wykonywania, tutaj jest świetne łącze na temat przyspieszania pand :

Zakir
źródło
6

W pandach możesz wykonać str.lengranicę i użyć wyniku logicznego do jej przefiltrowania.

df[df['column name'].str.len().lt(2)]
YOBEN_S
źródło
3

Jeśli chcesz upuścić wiersze ramki danych na podstawie jakiegoś skomplikowanego warunku na wartość kolumny, wówczas zapisanie tego w sposób pokazany powyżej może być skomplikowane. Mam następujące prostsze rozwiązanie, które zawsze działa. Załóżmy, że chcesz upuścić kolumnę z nagłówkiem, więc najpierw umieść tę kolumnę na liście.

text_data = df['name'].tolist()

teraz zastosuj jakąś funkcję do każdego elementu listy i umieść ją w serii panda:

text_length = pd.Series([func(t) for t in text_data])

w moim przypadku po prostu starałem się uzyskać liczbę tokenów:

text_length = pd.Series([len(t.split()) for t in text_data])

teraz dodaj jedną dodatkową kolumnę z powyższą serią w ramce danych:

df = df.assign(text_length = text_length .values)

teraz możemy zastosować warunek w nowej kolumnie, taki jak:

df = df[df.text_length  >  10]
def pass_filter(df, label, length, pass_type):

    text_data = df[label].tolist()

    text_length = pd.Series([len(t.split()) for t in text_data])

    df = df.assign(text_length = text_length .values)

    if pass_type == 'high':
        df = df[df.text_length  >  length]

    if pass_type == 'low':
        df = df[df.text_length  <  length]

    df = df.drop(columns=['text_length'])

    return df
jayanti prasad
źródło