Selezionare per stringa parziale da un DataFrame panda


450

Ho un DataFramecon 4 colonne di cui 2 contengono valori di stringa. Mi chiedevo se c'era un modo per selezionare le righe in base a una corrispondenza di stringa parziale rispetto a una colonna particolare?

In altre parole, una funzione o funzione lambda che farebbe qualcosa del genere

re.search(pattern, cell_in_question) 

restituendo un booleano. Ho familiarità con la sintassi di df[df['A'] == "hello world"]ma non riesco a trovare un modo per fare lo stesso con una corrispondenza di stringa parziale dire 'hello'.

Qualcuno sarebbe in grado di indicarmi la giusta direzione?

Risposte:


787

Sulla base del numero # 620 di github , sembra che presto sarai in grado di eseguire le seguenti operazioni:

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

Aggiornamento: i metodi di stringa vettoriale (es. Series.str) sono disponibili in Panda 0.8.1 e versioni successive.


1
Come possiamo andare su "Ciao" e "Gran Bretagna" se voglio trovarli con la condizione "OR".
LonelySoul,

56
Poiché i metodi str. * Considerano il modello di input come un'espressione regolare, puoi usaredf[df['A'].str.contains("Hello|Britain")]
Garrett il

7
È possibile convertire .str.containsper utilizzare .query()api ?
zyxue,


3
df[df['value'].astype(str).str.contains('1234.+')]per filtrare le colonne di tipo non stringa.
François Leblanc,

214

Ho provato la soluzione proposta sopra:

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

e ho ricevuto un errore:

ValueError: impossibile mascherare con array contenente valori NA / NaN

puoi trasformare i valori NA in False, in questo modo:

df[df["A"].str.contains("Hello|Britain", na=False)]

54
Oppure puoi fare: df [df ['A']. Str.contains ("Hello | Britain", na = False)]
joshlk

2
df[df['A'].astype(str).str.contains("Hello|Britain")]ha funzionato anche
Nagabhushan SN

108

Come faccio a selezionare per stringa parziale da un DataFrame Panda?

Questo post è destinato ai lettori che lo desiderano

  • cerca una sottostringa in una colonna di stringhe (il caso più semplice)
  • cerca più sottostringhe (simili a isin)
  • corrisponde a una parola intera dal testo (ad esempio, "blu" deve corrispondere a "il cielo è blu" ma non "bluejay")
  • abbina più parole intere
  • Comprendi il motivo dietro "ValueError: impossibile indicizzare con il vettore contenente valori NA / NaN"

... e vorrei sapere di più su quali metodi dovrebbero essere preferiti agli altri.

(PS: ho visto molte domande su argomenti simili, ho pensato che sarebbe stato bello lasciarlo qui.)


Ricerca di sottostringa di base

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

      col
0     foo
1  foobar
2     bar
3     baz

str.containspuò essere utilizzato per eseguire ricerche di sottostringa o ricerche basate su regex. Per impostazione predefinita, la ricerca si basa su regex a meno che non la si disabiliti esplicitamente.

Ecco un esempio di ricerca basata su regex,

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

      col
1  foobar

A volte non è richiesta la ricerca regex, quindi specifica regex=Falsedi disabilitarla.

#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

Per quanto riguarda le prestazioni, la ricerca regex è più lenta della ricerca di sottostringa:

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)

Evita di utilizzare la ricerca basata su regex se non ti serve.

Indirizzamento ValueErrors
A volte, si ottiene una ricerca di sottostringa e un filtro sul risultato

ValueError: cannot index with vector containing NA / NaN values

Ciò è generalmente dovuto a dati misti o NaN nella colonna dell'oggetto,

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)

Tutto ciò che non è una stringa non può avere metodi di stringa applicati su di essa, quindi il risultato è NaN (naturalmente). In questo caso, specificare na=Falsedi ignorare i dati non stringa,

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

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

Ricerca di sottostringhe multiple

Ciò si ottiene più facilmente attraverso una ricerca regex utilizzando la regex OR pipe.

# 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

Puoi anche creare un elenco di termini, quindi unirli:

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

          col
0     foo abc
1  foobar xyz
3      baz 45

A volte, è saggio sfuggire ai termini nel caso in cui abbiano caratteri che possono essere interpretati come metacaratteri regex . Se i tuoi termini contengono uno dei seguenti caratteri ...

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

Quindi, dovrai usarlo re.escapeper fuggire :

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

          col
0     foo abc
1  foobar xyz
3      baz 45

re.escape ha l'effetto di sfuggire ai personaggi speciali in modo che vengano trattati alla lettera.

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

Parole intere corrispondenti

Per impostazione predefinita, la ricerca di sottostringa cerca la sottostringa / il modello specificati indipendentemente dal fatto che sia una parola intera o meno. Per abbinare solo parole intere, avremo bisogno di utilizzare espressioni regolari qui, in particolare, il nostro modello dovrà specificare i confini delle parole ( \b).

Per esempio,

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

                     col
0        the sky is blue
1  bluejay by the window

Ora considera,

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

Ricerca multipla di parole intere

Simile al precedente, tranne per il fatto che aggiungiamo un limite di parola ( \b) al modello unito.

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

       col
0  foo abc
3   baz 45

Dove si ppresenta così,

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

Un'ottima alternativa: utilizzare le comprensioni dell'elenco !

Perché tu puoi! E dovresti! Di solito sono un po 'più veloci dei metodi di stringa, perché i metodi di stringa sono difficili da vettorializzare e di solito hanno implementazioni loop.

Invece di,

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

Usa l' inoperatore all'interno di un elenco comp,

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

       col
0  foo abc
1   foobar

Invece di,

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

Usa re.compile(per memorizzare la tua regex) + Pattern.searchall'interno di un elenco comp,

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

      col
1  foobar

Se "col" ha NaNs, allora invece di

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

Uso,

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

Altre opzioni per la parziale string matching: np.char.find, np.vectorize, DataFrame.query.

Oltre a str.containsed elencare le comprensioni, puoi anche usare le seguenti alternative.

np.char.find
Supporta solo ricerche di sottostringa (leggi: no regex).

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

          col
0     foo abc
1  foobar xyz

np.vectorize
Questo è un wrapper attorno a un loop, ma con un sovraccarico minore rispetto alla maggior parte dei strmetodi Panda .

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

Possibili soluzioni Regex:

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
Supporta metodi di stringa tramite il motore Python. Ciò non offre vantaggi in termini di prestazioni visibili, ma è comunque utile sapere se è necessario generare dinamicamente le query.

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

      col
0     foo
1  foobar

Maggiori informazioni querye evalfamiglia di metodi sono disponibili in Dynamic Expression Evaluation in panda usando pd.eval () .


Precedenza d'uso consigliata

  1. (Primo) str.contains, per la sua semplicità e facilità di gestione di NaN e dati misti
  2. Elenco delle comprensioni, per le sue prestazioni (specialmente se i tuoi dati sono puramente stringhe)
  3. np.vectorize
  4. (Scorso) df.query

Potresti modificare il metodo corretto da utilizzare quando cerchi una stringa in due o più colonne? Fondamentalmente: any(needle in haystack for needling in ['foo', 'bar'] and haystack in (df['col'], df['col2']))e le variazioni che ho provato sono tutte soffocate (si lamenta any()e giustamente ... Ma il documento non è beato in modo chiaro su come fare una simile domanda.
Denis de Bernardy,

@DenisdeBernardydf[['col1', 'col2']].apply(lambda x: x.str.contains('foo|bar')).any(axis=1)
cs95

@ cs95 Estrazione di righe con sottostringa contenente spazi bianchi dopo + in pandas df La risposta è stata presto, ma potresti dare un'occhiata.
ankii,

@ankiiiiiii Sembra che tu abbia perso la parte della mia risposta in cui ho citato metacaratteri regex: "A volte, è saggio sfuggire ai tuoi termini nel caso in cui abbiano personaggi che possono essere interpretati come metacaratteri regex".
cs95,

1
@ 00schneider r in questo caso viene utilizzato per indicare una stringa non elaborata letterale. Ciò semplifica la scrittura di stringhe di espressioni regolari. stackoverflow.com/q/2081640
CS95

53

Se qualcuno si chiede come eseguire un problema correlato: "Seleziona colonna per stringa parziale"

Uso:

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

E per selezionare le righe mediante una corrispondenza parziale della stringa, passa axis=0al filtro:

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

6
Questo può essere distillato in:df.loc[:, df.columns.str.contains('a')]
elPastor il

18
che può essere ulteriormente distillato indf.filter(like='a')
Ted Petrou il

questa dovrebbe essere una domanda + risposta, già 50 persone l'hanno cercata ...
PV8

1
La domanda @ PV8 esiste già: stackoverflow.com/questions/31551412/… . Ma quando cerco su "panda Seleziona colonna per stringa parziale" su Google, questa discussione appare per prima
Philipp Schwarz

28

Nota rapida: se si desidera effettuare la selezione in base a una stringa parziale contenuta nell'indice, provare quanto segue:

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

5
Puoi semplicemente df [df.index.to_series (). Str.contains ('LLChit')]
Yury Bayda

21

Di 'che hai il seguente DataFrame:

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

È sempre possibile utilizzare l' inoperatore in un'espressione lambda per creare il filtro.

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

Il trucco qui è usare l' axis=1opzione nel applyper passare elementi alla funzione lambda riga per riga, anziché colonna per colonna.


Come posso modificare sopra per dire che x ['a'] esiste solo all'inizio di x ['b']?
ComplexData,

1
applicare è una cattiva idea qui in termini di prestazioni e memoria. Vedere questa risposta .
cs95,

8

Ecco cosa ho finito per le corrispondenze di stringhe parziali. Se qualcuno ha un modo più efficiente di farlo, per favore fatemelo sapere.

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

3
Dovrebbe essere da 2 a 3 volte più veloce se compili regex prima del ciclo: regex = re.compile (regex) e poi se regex.search (record)
MarkokraM

1
@MarkokraM docs.python.org/3.6/library/re.html#re.compile afferma che le regex più recenti sono memorizzate nella cache per te, quindi non è necessario compilare te stesso.
Teepeemm,

Non utilizzare iteritems per iterare su un DataFrame. È l'ultimo in termini di pandorability e prestazioni
cs95

5

L'uso di contiene non ha funzionato bene per la mia stringa con caratteri speciali. Trova funzionato però.

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

3

Se dovessi eseguire una ricerca senza distinzione tra maiuscole e minuscole per una stringa in una colonna di frame di dati Panda:

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

2

Ci sono risposte prima che realizzano la funzione richiesta, comunque vorrei mostrare il modo più generale:

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

In questo modo ti consente di ottenere la colonna che cerchi qualunque sia il modo in cui è scritto.

(Ovviamente, devi scrivere l'espressione regex corretta per ogni caso)


1
Questo filtra le intestazioni di colonna . Non è generale, non è corretto.
cs95,

@MicheldeRuiter che è ancora errato, che invece filtrerebbe sulle etichette degli indici!
cs95,

Non risponde alla domanda Ma ho imparato qualcosa. :)
Michel de Ruiter,

2

Forse vuoi cercare del testo in tutte le colonne del dataframe di Pandas e non solo nel loro sottoinsieme. In questo caso, il seguente codice sarà di aiuto.

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

Avvertimento. Questo metodo è relativamente lento, sebbene conveniente.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.