Löschen Sie Zeilen aus einem Pandas-DataFrame basierend auf einem bedingten Ausdruck mit len ​​(Zeichenfolge), der KeyError gibt

303

Ich habe einen Pandas DataFrame und möchte Zeilen daraus löschen, bei denen die Länge der Zeichenfolge in einer bestimmten Spalte größer als 2 ist.

Ich erwarte, dies tun zu können (gemäß dieser Antwort ):

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

aber ich bekomme nur den Fehler:

KeyError: u'no item named False'

Was mache ich falsch?

(Hinweis: Ich weiß, dass ich df.dropna()damit Zeilen NaNentfernen kann , die irgendwelche enthalten , aber ich habe nicht gesehen, wie Zeilen basierend auf einem bedingten Ausdruck entfernt werden können.)

sjs
quelle

Antworten:

168

Wenn Sie dies tun, erhalten len(df['column name'])Sie nur eine Zahl, nämlich die Anzahl der Zeilen im DataFrame (dh die Länge der Spalte selbst). Wenn Sie lenauf jedes Element in der Spalte anwenden möchten , verwenden Sie df['column name'].map(len). Also versuche

df[df['column name'].map(len) < 2]
BrenBarn
quelle
3
Ich habe einen Weg gefunden, ein Listenverständnis zu verwenden: df[[(len(x) < 2) for x in df['column name']]]aber deines ist viel schöner. Danke für Ihre Hilfe!
sjs
13
Wenn jemand einen komplexeren Vergleich benötigt, kann immer ein Lambda verwendet werden. df[df['column name'].map(lambda x: str(x)!=".")]
4lberto
1
Aus irgendeinem Grund hat keine der anderen Optionen für mich funktioniert, außer der von @ 4lberto. Ich bin auf pandas 0.23.4und Python 3.6
Goelakash
1
Ich würde .copy()am Ende ein hinzufügen , falls Sie diesen Datenrahmen später bearbeiten möchten (wenn Sie beispielsweise neue Spalten zuweisen, wird die Warnung "Ein Wert wird für eine Kopie eines Slice aus einem DataFrame festgelegt" ausgelöst.
PlasmaBinturong
806

Um den Originaltitel dieser Frage "So löschen Sie Zeilen aus einem Pandas-DataFrame basierend auf einem bedingten Ausdruck" direkt zu beantworten (was meines Wissens nicht unbedingt das Problem des OP ist, aber anderen Benutzern helfen könnte, auf diese Frage zu stoßen), besteht eine Möglichkeit darin die Drop- Methode:

df = df.drop(some labels)

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

Beispiel

So entfernen Sie alle Zeilen, in denen die Spalte 'score' <50 ist:

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

In-Place-Version (wie in den Kommentaren angegeben)

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

Mehrere Bedingungen

(siehe Boolesche Indizierung )

Die Operatoren sind: |für or, &für andund ~für not. Diese müssen in Klammern gruppiert werden.

So entfernen Sie alle Zeilen, in denen die Spalte 'score' <50 und> 20 ist

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

Benutzer
quelle
32
Ich möchte nur erwähnen, dass die Drop-Funktion das Ersetzen an Ort und Stelle unterstützt. Dh. Ihre Lösung ist dieselbe wie df.drop (df [df.score <50] .index, inplace = True). Trotzdem kannte der "Index" -Trick nicht. Hat mir sehr geholfen
Quickbeam2k1
9
Ich möchte nur darauf hinweisen, dass Sie vor der Verwendung dieses Indextricks sicherstellen müssen, dass Ihre Indexwerte eindeutig sind (oder aufgerufen werden reset_index()). Ich habe das auf die harte Tour herausgefunden, als viele Zeilen aus meinem Datenrahmen entfernt wurden.
Jay
3
Wie lösche ich alle Zeilen, in denen der Spaltentyp str ist? Ich möchte nur Listenspaltentypen behalten. Ich habe es versucht, test = df.drop(df[df['col1'].dtype == str].index)aber ich bekomme den Fehler, den KeyError: False ich auch versucht habe df.drop(df[df.col1.dtype == str].index)und df.drop(df[type(df.cleaned_norm_email) == str].index)aber nichts scheint zu funktionieren? Kann mir jemand raten. Vielen Dank! @ Benutzer
PyRsquared
1
Dies ist eine alte Frage, aber ... @ aquatisch herausgeforderter Fisch ist viel schneller als dieser. Beachten Sie, dass Sie df[(df.score < 50) & (df.score > 20)]als Teil Ihrer Antwort berechnen . Wenn Sie dies rückgängig machen df = df[(df.score >= 50) | (df.score <= 20)]würden, würden Sie Ihre Antwort viel schneller erhalten.
Roobie Nuby
1
@RoobieNuby - sie sind nicht der gleiche Zustand.
Nguai al
106

Sie können das DataFrameeiner gefilterten Version von sich selbst zuweisen :

df = df[df.score > 50]

Das ist schneller als 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
quelle
Wie überprüfe ich die Verwendung oder Bedingung mehrerer Spalten?
Piyush S. Wanare
9

Ich werde die generische Lösung von @ User erweitern, um eine dropkostenlose Alternative bereitzustellen . Dies ist für Leute, die hier basierend auf dem Titel der Frage gerichtet sind (nicht das Problem von OP).

Angenommen, Sie möchten alle Zeilen mit negativen Werten löschen. Eine Linerlösung ist: -

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

Schritt für Schritt Erklärung: -

Lassen Sie uns einen 5x5 zufälligen Normalverteilungsdatenrahmen erzeugen

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

Lassen Sie die Bedingung Negative löschen. Ein boolescher df, der die Bedingung erfüllt: -

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

Eine boolesche Reihe für alle Zeilen, die die Bedingung erfüllen. Hinweis: Wenn ein Element in der Zeile die Bedingung nicht erfüllt, wird die Zeile als falsch markiert

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

Filtern Sie schließlich Zeilen aus dem Datenrahmen basierend auf der Bedingung heraus

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

Sie können es zurück zu df zuweisen , um tatsächlich löschen vs Filter über ing getan
df = df[(df > 0).all(axis=1)]

Dies kann leicht erweitert werden, um Zeilen herauszufiltern, die NaNs enthalten (nicht numerische Einträge): -
df = df[(~df.isnull()).all(axis=1)]

Dies kann auch für folgende Fälle vereinfacht werden: Löschen Sie alle Zeilen, in denen Spalte E negativ ist

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

Ich möchte mit einigen Profilstatistiken enden, warum die @ User- dropLösung langsamer ist als die rohe spaltenbasierte Filterung: -

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

Eine Säule ist im Grunde ein Seriesdh ein NumPyArray, kann es ohne Kosten indexiert werden. Für Leute, die daran interessiert sind, wie sich die zugrunde liegende Speicherorganisation auf die Ausführungsgeschwindigkeit auswirkt, gibt es hier einen großartigen Link zur Beschleunigung von Pandas :

Zakir
quelle
6

In Pandas können Sie str.lenmit Ihrer Grenze arbeiten und das Boolesche Ergebnis verwenden, um sie zu filtern.

df[df['column name'].str.len().lt(2)]
YOBEN_S
quelle
3

Wenn Sie Zeilen eines Datenrahmens auf der Grundlage einer komplizierten Bedingung für den Spaltenwert löschen möchten, kann das Schreiben auf die oben gezeigte Weise kompliziert sein. Ich habe die folgende einfachere Lösung, die immer funktioniert. Nehmen wir an, Sie möchten die Spalte mit 'Kopfzeile' löschen, also nehmen Sie diese Spalte zuerst in eine Liste auf.

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

Wenden Sie nun eine Funktion auf jedes Element der Liste an und fügen Sie diese in eine Panda-Serie ein:

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

In meinem Fall habe ich nur versucht, die Anzahl der Token zu ermitteln:

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

Fügen Sie nun eine zusätzliche Spalte mit der obigen Reihe in den Datenrahmen ein:

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

Jetzt können wir Bedingungen auf die neue Spalte anwenden, wie zum Beispiel:

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
quelle