Wählen Sie durch Teilzeichenfolge aus einem Pandas DataFrame

445

Ich habe eine DataFramemit 4 Spalten, von denen 2 Zeichenfolgenwerte enthalten. Ich habe mich gefragt, ob es eine Möglichkeit gibt, Zeilen basierend auf einer teilweisen Zeichenfolgenübereinstimmung mit einer bestimmten Spalte auszuwählen.

Mit anderen Worten, eine Funktion oder Lambda-Funktion, die so etwas tun würde

re.search(pattern, cell_in_question) 

Rückgabe eines Booleschen Werts. Ich bin mit der Syntax von vertraut df[df['A'] == "hello world"], kann aber anscheinend keinen Weg finden, dasselbe mit einem partiellen String-Match zu tun 'hello'.

Würde mich jemand in die richtige Richtung weisen können?

Euforia
quelle

Antworten:

784

Basierend auf Github-Problem Nr. 620 werden Sie wahrscheinlich bald Folgendes tun können:

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

Update: Vektorisierte String-Methoden (dh Series.str) sind in Pandas 0.8.1 und höher verfügbar.

Garrett
quelle
1
Wie gehen wir bei "Hallo" und "Großbritannien" vor, wenn ich sie mit der Bedingung "ODER" finden möchte?
LonelySoul
56
Da str. * Methoden das Eingabemuster als regulären Ausdruck behandeln, können Siedf[df['A'].str.contains("Hello|Britain")]
Garrett
7
Ist es möglich, .str.containszur Verwendung von .query()API zu konvertieren ?
Zyxue
3
df[df['value'].astype(str).str.contains('1234.+')]zum Herausfiltern von Spalten ohne Zeichenfolge.
François Leblanc
213

Ich habe die oben vorgeschlagene Lösung ausprobiert:

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

und habe einen Fehler bekommen:

ValueError: Kann nicht mit einem Array maskiert werden, das NA / NaN-Werte enthält

Sie können NA-Werte Falsewie folgt umwandeln :

df[df["A"].str.contains("Hello|Britain", na=False)]
Sharon
quelle
54
Oder Sie können tun: df [df ['A']. Str.contains ("Hallo | Großbritannien", na = False)]
joshlk
2
df[df['A'].astype(str).str.contains("Hello|Britain")]funktionierte auch
Nagabhushan SN
108

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.containskann 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=FalseGeben 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 ValueErrors
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=Falsedass 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 \bdem 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 psieht 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 inOperator 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.searchin 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

Weitere Optionen für Teil String Matching: np.char.find, np.vectorize, DataFrame.query.

Zusätzlich zu str.containsund 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- strMethoden.

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 queryund evalMethodenfamilien finden Sie unter Dynamic Expression Evaluation in Pandas mit pd.eval () .


Empfohlene Verwendung Vorrang

  1. (Erstens) str.containsfür seine Einfachheit und Leichtigkeit beim Umgang mit NaNs und gemischten Daten
  2. Listen Sie das Verständnis für seine Leistung auf (insbesondere wenn Ihre Daten reine Zeichenfolgen sind).
  3. np.vectorize
  4. (Letzte) df.query
cs95
quelle
Könnten Sie die richtige Methode für die Suche nach einer Zeichenfolge in zwei oder mehr Spalten verwenden? Grundsätzlich: any(needle in haystack for needling in ['foo', 'bar'] and haystack in (df['col'], df['col2']))und Variationen habe ich alle Drossel versucht (es beschwert sich any()und zu Recht so ... Aber der Doc ist selig unklar, wie man eine solche Abfrage macht.
Denis de Bernardy
@ DenisdeBernardydf[['col1', 'col2']].apply(lambda x: x.str.contains('foo|bar')).any(axis=1)
cs95
@ cs95 Zeilen mit Teilzeichenfolge extrahieren, die Leerzeichen nach + in pandas df enthält. Es wurde bald beantwortet, aber vielleicht möchten Sie es sich ansehen.
Ankii
@ankiiiiiii Sieht so aus, als hätten Sie den Teil meiner Antwort verpasst, in dem ich Regex-Metazeichen erwähnt habe: "Manchmal ist es ratsam, sich Ihren Begriffen zu entziehen, wenn sie Zeichen enthalten, die als Regex-Metazeichen interpretiert werden können."
CS95
1
@ 00schneider r wird in diesem Fall verwendet, um ein Raw-String-Literal anzugeben. Diese erleichtern das Schreiben von Zeichenfolgen für reguläre Ausdrücke. stackoverflow.com/q/2081640
cs95
53

Wenn sich jemand fragt, wie ein verwandtes Problem ausgeführt werden soll: "Spalte nach Teilzeichenfolge auswählen"

Verwenden:

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

Um Zeilen durch partielle Zeichenfolgenübereinstimmung auszuwählen, übergeben Sie sie axis=0an den Filter:

# selects rows which contain the word hello in their index label
df.filter(like='hello', axis=0)  
Philipp Schwarz
quelle
6
Dies kann destilliert werden zu:df.loc[:, df.columns.str.contains('a')]
elPastor
18
das kann weiter destilliert werdendf.filter(like='a')
Ted Petrou
Dies sollte eine eigene Frage + Antwort sein, bereits 50 Leute haben danach gesucht ...
PV8
1
@ PV8 Frage existiert bereits: stackoverflow.com/questions/31551412/… . Aber wenn ich auf Google nach "Pandas Spalte nach Teilzeichenfolge auswählen" suche, erscheint dieser Thread zuerst
Philipp Schwarz
28

Kurzer Hinweis: Wenn Sie eine Auswahl anhand einer im Index enthaltenen Teilzeichenfolge vornehmen möchten, versuchen Sie Folgendes:

df['stridx']=df.index
df[df['stridx'].str.contains("Hello|Britain")]
Christian
quelle
5
Sie können einfach df [df.index.to_series (). Str.contains ('LLChit')]
Yury Bayda
21

Angenommen, Sie haben Folgendes DataFrame:

>>> df = pd.DataFrame([['hello', 'hello world'], ['abcd', 'defg']], columns=['a','b'])
>>> df
       a            b
0  hello  hello world
1   abcd         defg

Sie können den inOperator immer in einem Lambda-Ausdruck verwenden, um Ihren Filter zu erstellen.

>>> df.apply(lambda x: x['a'] in x['b'], axis=1)
0     True
1    False
dtype: bool

Der Trick dabei ist, die axis=1Option in applyzu verwenden, um Elemente zeilenweise an die Lambda-Funktion zu übergeben, im Gegensatz zu spaltenweise.

Mike
quelle
Wie ändere ich oben, um zu sagen, dass x ['a'] nur am Anfang von x ['b'] existiert?
ComplexData
1
Anwenden ist hier eine schlechte Idee in Bezug auf Leistung und Speicher. Siehe diese Antwort .
CS95
8

Folgendes habe ich letztendlich für Teilstring-Übereinstimmungen getan. Wenn jemand eine effizientere Möglichkeit hat, lassen Sie es mich bitte wissen.

def stringSearchColumn_DataFrame(df, colName, regex):
    newdf = DataFrame()
    for idx, record in df[colName].iteritems():

        if re.search(regex, record):
            newdf = concat([df[df[colName] == record], newdf], ignore_index=True)

    return newdf
Euforia
quelle
3
Sollte 2x bis 3x schneller sein, wenn Sie Regex vor der Schleife kompilieren: regex = re.compile (Regex) und dann, wenn regex.search (Datensatz)
MarkokraM
1
@MarkokraM docs.python.org/3.6/library/re.html#re.compile besagt, dass die neuesten regulären Ausdrücke für Sie zwischengespeichert werden, sodass Sie sich nicht selbst kompilieren müssen.
Teepeemm
Verwenden Sie keine Iteritems, um über einen DataFrame zu iterieren. Es steht an letzter Stelle in Bezug auf Pandorabilität und Leistung
cs95
5

Die Verwendung von enthält hat für meine Zeichenfolge mit Sonderzeichen nicht gut funktioniert. Find hat aber funktioniert.

df[df['A'].str.find("hello") != -1]
Katu
quelle
2

Davor gibt es Antworten, die die angeforderte Funktion erfüllen, trotzdem möchte ich den allgemeinsten Weg zeigen:

df.filter(regex=".*STRING_YOU_LOOK_FOR.*")

Auf diese Weise erhalten Sie die Spalte, nach der Sie suchen, unabhängig davon, wie sie geschrieben wurde.

(Offensichtlich müssen Sie für jeden Fall den richtigen Regex-Ausdruck schreiben.)

xpeiro
quelle
1
Dies filtert die Spalte Spaltenüberschriften . Es ist nicht allgemein, es ist falsch.
CS95
@MicheldeRuiter das ist immer noch falsch, das würde stattdessen nach Indexbezeichnungen filtern!
CS95
Beantwortet die Frage nicht. Aber ich habe etwas gelernt. :)
Michel de Ruiter
2

Vielleicht möchten Sie in allen Spalten des Pandas-Datenrahmens nach Text suchen, und nicht nur in deren Teilmenge. In diesem Fall hilft der folgende Code.

df[df.apply(lambda row: row.astype(str).str.contains('String To Find').any(), axis=1)]

Warnung. Diese Methode ist relativ langsam, wenn auch zweckmäßig.

Serhii Kushchenko
quelle
1

Sollten Sie eine Suche ohne Berücksichtigung der Groß- und Kleinschreibung nach einer Zeichenfolge in einer Pandas-Datenrahmenspalte durchführen müssen:

df[df['A'].str.contains("hello", case=False)]
Kardamom
quelle