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'
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
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
- (Erste)
str.contains
wegen seiner Einfachheit und leichten Handhabung von NaNs und gemischten Daten
- List comprehensions, wegen der Leistung (vor allem, wenn Ihre Daten nur aus Strings bestehen)
np.vectorize
- (Letzter)
df.query