Wie wähle ich eine Teilzeichenfolge aus einem Pandas-DataFrame aus?
Dieser Beitrag ist für Leser gedacht, die wollen
- Suche nach einer Teilzeichenfolge in einer Zeichenfolgenspalte (der einfachste Fall)
- Suche nach mehreren Teilzeichenfolgen (ähnlich wie
isin
)
- ein ganzes Wort aus dem Text abgleichen (z. B. sollte "blau" mit "der Himmel ist blau" übereinstimmen, aber nicht mit "bluejay")
- stimmen Sie mit mehreren ganzen Wörtern überein
- Verstehen Sie den Grund für "ValueError: Indizierung mit Vektor mit NA / NaN-Werten nicht möglich"
... und möchten mehr darüber erfahren, welche Methoden anderen vorgezogen werden sollten.
(PS: Ich habe viele Fragen zu ähnlichen Themen gesehen. Ich dachte, es wäre gut, dies hier zu belassen.)
Grundlegende Teilstringsuche
# setup
df1 = pd.DataFrame({'col': ['foo', 'foobar', 'bar', 'baz']})
df1
col
0 foo
1 foobar
2 bar
3 baz
str.contains
kann verwendet werden, um entweder Teilstringsuchen oder Regex-basierte Suche durchzuführen. Die Suche basiert standardmäßig auf regulären Ausdrücken, sofern Sie sie nicht explizit deaktivieren.
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 keine Regex-Suche erforderlich. regex=False
Geben Sie dies an , 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 Suche nach Teilzeichenfolgen:
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 von Regex-basierter Suche, wenn Sie diese nicht benötigen.
Adressierung ValueError
s
Manchmal führt das Durchführen einer Teilstringsuche und das Filtern des Ergebnisses zu
ValueError: cannot index with vector containing NA / NaN values
Dies liegt normalerweise an gemischten Daten oder NaNs in Ihrer Objektspalte.
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 kein String ist, können keine String-Methoden angewendet werden. Das Ergebnis ist also (natürlich) NaN. Geben Sie in diesem Fall an, na=False
dass Nicht-String-Daten ignoriert werden sollen.
s.str.contains('foo|bar', na=False)
0 True
1 True
2 False
3 True
4 False
5 False
dtype: bool
Suche nach mehreren Teilzeichenfolgen
Dies wird am einfachsten durch eine Regex-Suche mit der Regex-ODER-Pipe erreicht.
# 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 mit 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, sich Ihren Begriffen zu entziehen, wenn sie Zeichen enthalten, die als Regex-Metazeichen interpretiert werden können . Wenn Ihre Begriffe eines der folgenden Zeichen enthalten ...
. ^ $ * + ? { } [ ] \ | ( )
Dann müssen Sie verwenden re.escape
, um ihnen zu entkommen :
import re
df4[df4['col'].str.contains('|'.join(map(re.escape, terms)))]
col
0 foo abc
1 foobar xyz
3 baz 45
re.escape
hat den Effekt, dass die Sonderzeichen entkommen, sodass sie buchstäblich behandelt werden.
re.escape(r'.foo^')
# '\\.foo\\^'
Übereinstimmende ganze Wörter
Standardmäßig sucht die Teilstringsuche nach dem angegebenen Teilstring / Muster, unabhängig davon, ob es sich um ein vollständiges Wort handelt oder nicht. Um nur vollständige Wörter zu finden, 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
Suche nach mehreren ganzen Wörtern
Ähnlich wie oben, außer dass wir \b
dem verbundenen Muster eine Wortgrenze ( ) hinzufügen .
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 das so aus?
p
# '\\b(?:foo|baz)\\b'
Eine großartige Alternative: Verwenden Sie Listenverständnisse !
Weil du es kannst! Und du solltest! Sie sind normalerweise etwas schneller als String-Methoden, da String-Methoden schwer zu vektorisieren sind und normalerweise schleifenförmige Implementierungen aufweisen.
Anstatt,
df1[df1['col'].str.contains('foo', regex=False)]
Verwenden Sie den in
Operator in einer Liste comp,
df1[['foo' in x for x in df1['col']]]
col
0 foo abc
1 foobar
Anstatt,
regex_pattern = r'foo(?!$)'
df1[df1['col'].str.contains(regex_pattern)]
Verwenden Sie re.compile
(um Ihren regulären Ausdruck zwischenzuspeichern) + Pattern.search
in einer Listenkomposition.
p = re.compile(regex_pattern, flags=re.IGNORECASE)
df1[[bool(p.search(x)) for x in df1['col']]]
col
1 foobar
Wenn "col" NaNs hat, dann anstelle von
df1[df1['col'].str.contains(regex_pattern, na=False)]
Verwenden,
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 Listenverständnissen können Sie auch die folgenden Alternativen verwenden.
np.char.find
Unterstützt nur die Suche nach Teilzeichenfolgen (gelesen: kein regulärer Ausdruck).
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, 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 über die Python-Engine. Dies bietet keine sichtbaren Leistungsvorteile, ist jedoch hilfreich, um zu wissen, ob Sie Ihre Abfragen dynamisch generieren müssen.
df1.query('col.str.contains("foo")', engine='python')
col
0 foo
1 foobar
Weitere Informationen zu query
und eval
Methodenfamilien finden Sie unter Dynamic Expression Evaluation in Pandas mit pd.eval () .
Empfohlene Verwendung Vorrang
- (Erstens)
str.contains
für seine Einfachheit und Leichtigkeit beim Umgang mit NaNs und gemischten Daten
- Listen Sie das Verständnis für seine Leistung auf (insbesondere wenn Ihre Daten reine Zeichenfolgen sind).
np.vectorize
- (Letzte)
df.query