914 Stimmen

Pandas DataFrame nach Teilstring-Kriterien filtern

Ich habe ein Pandas DataFrame mit einer Spalte von String-Werten. Ich muss Zeilen basierend auf partiellen String-Übereinstimmungen auswählen.

So etwas wie diese Redewendung:

re.search(pattern, cell_in_question) 

einen booleschen Wert zurückgibt. Ich bin vertraut mit der Syntax von df[df['A'] == "hello world"] aber ich kann keine Möglichkeit finden, dasselbe mit einer teilweisen Zeichenkettenübereinstimmung zu tun, z. B. 'hello' .

1423voto

Garrett Punkte 41369

Vektorisierte String-Methoden (d.h. Series.str ) können Sie Folgendes tun:

df[df['A'].str.contains("hello")]

Dies ist in Pandas verfügbar 0.8.1 und aufwärts.

393voto

sharon Punkte 4266

Ich benutze pandas 0.14.1 auf macos in ipython notebook. Ich habe versucht, die vorgeschlagene Zeile oben:

df[df["A"].str.contains("Hello|Britain")]

und erhielt eine Fehlermeldung:

kann nicht mit einem Vektor indexieren, der NA / NaN-Werte enthält

aber es funktionierte perfekt, wenn eine "==True"-Bedingung hinzugefügt wurde, wie hier:

df[df['A'].str.contains("Hello|Britain")==True]

320voto

cs95 Punkte 325143

Wie wähle ich durch teilweise Zeichenfolge aus einem Pandas DataFrame?

Dieser Beitrag ist für Leser gedacht, die

  • Suche nach einer Teilzeichenkette in einer Zeichenkettenspalte (der einfachste Fall) wie in df1[df1['col'].str.contains(r'foo(?!$)')]
  • Suche nach mehreren Teilstrings (ähnlich wie bei isin ), z.B. mit df4[df4['col'].str.contains(r'foo|baz')]
  • ein ganzes Wort aus dem Text zu finden (z. B. "blau" sollte zu "der Himmel ist blau" passen, aber nicht zu "Blauhäher"), z. B. mit df3[df3['col'].str.contains(r'\bblue\b')]
  • mehrere ganze Wörter zuordnen
  • Verstehen Sie den Grund für "ValueError: cannot index with vector containing NA / NaN values" und korrigieren Sie ihn mit str.contains('pattern',na=False)

...und würde gerne mehr darüber erfahren, welche Methoden gegenüber anderen vorzuziehen sind.

(P.S.: Ich habe viele Fragen zu ähnlichen Themen gesehen, ich dachte, es wäre gut, dies hier zu lassen).

Freundliche Ausschlussklausel Dieser Beitrag ist lang .


Einfache Substringsuche

# setup
df1 = pd.DataFrame({'col': ['foo', 'foobar', 'bar', 'baz']})
df1

      col
0     foo
1  foobar
2     bar
3     baz

str.contains kann entweder für die Suche nach Teilstrings oder für die Regex-basierte Suche verwendet werden. Die Suche erfolgt standardmäßig auf Regex-Basis, es sei denn, Sie deaktivieren sie ausdrücklich.

Hier ist ein Beispiel für eine Regex-basierte Suche,

# find rows in `df1` which contain "foo" followed by something
df1[df1['col'].str.contains(r'foo(?!$)')]

      col
1  foobar

Manchmal ist die Regex-Suche nicht erforderlich, dann geben Sie regex=False um sie zu deaktivieren.

#select all rows containing "foo"
df1[df1['col'].str.contains('foo', regex=False)]
# same as df1[df1['col'].str.contains('foo')] but faster.

      col
0     foo
1  foobar

In Bezug auf die Leistung ist die Regex-Suche langsamer als die Teilstringsuche:

df2 = pd.concat([df1] * 1000, ignore_index=True)

%timeit df2[df2['col'].str.contains('foo')]
%timeit df2[df2['col'].str.contains('foo', regex=False)]

6.31 ms ± 126 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
2.8 ms ± 241 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Vermeiden Sie die Verwendung der Regex-basierten Suche, wenn Sie sie nicht benötigen.

Adressierung ValueError s
Manchmal führt die Durchführung einer Teilzeichensuche und das Filtern des Ergebnisses zu folgenden Ergebnissen

ValueError: cannot index with vector containing NA / NaN values

Dies ist in der Regel auf gemischte Daten oder NaNs in Ihrer Objektspalte zurückzuführen,

s = pd.Series(['foo', 'foobar', np.nan, 'bar', 'baz', 123])
s.str.contains('foo|bar')

0     True
1     True
2      NaN
3     True
4    False
5      NaN
dtype: object

s[s.str.contains('foo|bar')]
# ---------------------------------------------------------------------------
# ValueError                                Traceback (most recent call last)

Auf alles, was keine Zeichenkette ist, können keine Zeichenkettenmethoden angewendet werden, daher ist das Ergebnis (natürlich) NaN. In diesem Fall geben Sie an na=False um Nicht-String-Daten zu ignorieren,

s.str.contains('foo|bar', na=False)

0     True
1     True
2    False
3     True
4    False
5    False
dtype: bool

Wie kann ich dies auf mehrere Spalten gleichzeitig anwenden?
Die Antwort liegt in der Frage. Verwenden Sie DataFrame.apply :

# `axis=1` tells `apply` to apply the lambda function column-wise.
df.apply(lambda col: col.str.contains('foo|bar', na=False), axis=1)

       A      B
0   True   True
1   True  False
2  False   True
3   True  False
4  False  False
5  False  False

Alle nachstehenden Lösungen können auf mehrere Spalten "angewendet" werden, indem die spaltenweise apply Methode (was meiner Meinung nach in Ordnung ist, solange Sie nicht zu viele Spalten haben).

Wenn Sie einen DataFrame mit gemischten Spalten haben und nur die Objekt/String-Spalten auswählen wollen, schauen Sie sich select_dtypes .


Suche mit mehreren Teilstrichen

Dies lässt sich am einfachsten durch eine Regex-Suche mit der Regex-ODER-Pipe erreichen.

# Slightly modified example.
df4 = pd.DataFrame({'col': ['foo abc', 'foobar xyz', 'bar32', 'baz 45']})
df4

          col
0     foo abc
1  foobar xyz
2       bar32
3      baz 45

df4[df4['col'].str.contains(r'foo|baz')]

          col
0     foo abc
1  foobar xyz
3      baz 45

Sie können auch eine Liste von Begriffen erstellen und diese dann verbinden:

terms = ['foo', 'baz']
df4[df4['col'].str.contains('|'.join(terms))]

          col
0     foo abc
1  foobar xyz
3      baz 45

Manchmal ist es ratsam, Ihre Begriffe zu unterdrücken, falls sie Zeichen enthalten, die als Regex-Metacharaktere . Wenn Ihre Begriffe eines der folgenden Zeichen enthalten...

. ^ $ * + ? { } [ ] \ | ( )

Dann müssen Sie Folgendes verwenden re.escape à Flucht sie:

import re
df4[df4['col'].str.contains('|'.join(map(re.escape, terms)))]

          col
0     foo abc
1  foobar xyz
3      baz 45

re.escape bewirkt, dass die Sonderzeichen umgangen werden, so dass sie wörtlich behandelt werden.

re.escape(r'.foo^')
# '\\.foo\\^'

Gesamte(s) Wort(e) zuordnen

Standardmäßig wird bei der Teilzeichensuche nach der angegebenen Teilzeichenkette/dem angegebenen Muster gesucht, unabhängig davon, ob es sich um ein vollständiges Wort handelt oder nicht. Um nur mit ganzen Wörtern übereinzustimmen, müssen wir hier reguläre Ausdrücke verwenden - insbesondere muss unser Muster Wortgrenzen angeben ( \b ).

Zum Beispiel,

df3 = pd.DataFrame({'col': ['the sky is blue', 'bluejay by the window']})
df3

                     col
0        the sky is blue
1  bluejay by the window

Nun überlegen Sie,

df3[df3['col'].str.contains('blue')]

                     col
0        the sky is blue
1  bluejay by the window

v/s

df3[df3['col'].str.contains(r'\bblue\b')]

               col
0  the sky is blue

Mehrere ganze Wörter suchen

Ähnlich wie oben, außer dass wir eine Wortgrenze hinzufügen ( \b ) auf das verbundene Muster.

p = r'\b(?:{})\b'.format('|'.join(map(re.escape, terms)))
df4[df4['col'].str.contains(p)]

       col
0  foo abc
3   baz 45

Wo p sieht so aus,

p
# '\\b(?:foo|baz)\\b'

Eine großartige Alternative: Verwenden Sie Auflistung von Zusammenfassungen ¡!

Weil Sie es können! Und das sollten Sie auch! Sie sind in der Regel etwas schneller als String-Methoden, da String-Methoden nur schwer vektorisiert werden können und in der Regel mit Schleifen implementiert werden.

Anstelle von,

df1[df1['col'].str.contains('foo', regex=False)]

Verwenden Sie die in Operator innerhalb einer Liste comp,

df1[['foo' in x for x in df1['col']]]

       col
0  foo abc
1   foobar

Anstelle von,

regex_pattern = r'foo(?!$)'
df1[df1['col'].str.contains(regex_pattern)]

Verwenden Sie re.compile (zum Zwischenspeichern der Regex) + Pattern.search innerhalb einer Liste comp,

p = re.compile(regex_pattern, flags=re.IGNORECASE)
df1[[bool(p.search(x)) for x in df1['col']]]

      col
1  foobar

Wenn "col" NaNs enthält, dann wird anstelle von

df1[df1['col'].str.contains(regex_pattern, na=False)]

Verwendung,

def try_search(p, x):
    try:
        return bool(p.search(x))
    except TypeError:
        return False

p = re.compile(regex_pattern)
df1[[try_search(p, x) for x in df1['col']]]

      col
1  foobar

Weitere Optionen für partielle Zeichenfolgenübereinstimmung: np.char.find , np.vectorize , DataFrame.query .

Zusätzlich zu str.contains und List Comprehensions können Sie auch die folgenden Alternativen verwenden.

np.char.find
Unterstützt nur Teilstringsuchen (d.h. keine Regex).

df4[np.char.find(df4['col'].values.astype(str), 'foo') > -1]

          col
0     foo abc
1  foobar xyz

np.vectorize
Dies ist ein Wrapper um eine Schleife herum, aber mit geringerem Overhead als die meisten Pandas str Methoden.

f = np.vectorize(lambda haystack, needle: needle in haystack)
f(df1['col'], 'foo')
# array([ True,  True, False, False])

df1[f(df1['col'], 'foo')]

       col
0  foo abc
1   foobar

Regex-Lösungen möglich:

regex_pattern = r'foo(?!$)'
p = re.compile(regex_pattern)
f = np.vectorize(lambda x: pd.notna(x) and bool(p.search(x)))
df1[f(df1['col'])]

      col
1  foobar

DataFrame.query
Unterstützt String-Methoden durch die Python-Engine. Dies bietet keine sichtbaren Leistungsvorteile, ist aber dennoch nützlich zu wissen, wenn Sie Ihre Abfragen dynamisch generieren müssen.

df1.query('col.str.contains("foo")', engine='python')

      col
0     foo
1  foobar

Mehr Informationen über query y eval Familie von Methoden finden Sie unter Dynamische Auswertung eines Ausdrucks aus einer Formel in Pandas .


Empfohlene Verwendung Vorrangig

  1. (Erste) str.contains wegen seiner Einfachheit und leichten Handhabung von NaNs und gemischten Daten
  2. List comprehensions, wegen der Leistung (vor allem, wenn Ihre Daten nur aus Strings bestehen)
  3. np.vectorize
  4. (Letzter) df.query

66voto

Philipp Schwarz Punkte 14710

Falls sich jemand fragt, wie man ein ähnliches Problem lösen kann: "Spalte nach Teilstring auswählen"

Verwendung:

df.filter(like='hello')  # select columns which contain the word hello

Und um Zeilen durch partiellen Stringabgleich auszuwählen, übergeben Sie axis=0 zu filtern:

# selects rows which contain the word hello in their index label
df.filter(like='hello', axis=0)

29voto

Christian Punkte 299

Kurzer Hinweis: Wenn Sie eine Auswahl auf der Grundlage einer im Index enthaltenen Teilzeichenkette vornehmen möchten, versuchen Sie Folgendes:

df['stridx']=df.index
df[df['stridx'].str.contains("Hello|Britain")]

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X