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.contains
può 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=False
di 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 ValueError
s
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=False
di 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.escape
per 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 p
presenta così,
p
# '\\b(?:foo|baz)\\b'
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' in
operatore 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.search
all'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
Oltre a str.contains
ed 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 str
metodi 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 query
e eval
famiglia di metodi sono disponibili in Dynamic Expression Evaluation in panda usando pd.eval () .
Precedenza d'uso consigliata
- (Primo)
str.contains
, per la sua semplicità e facilità di gestione di NaN e dati misti
- Elenco delle comprensioni, per le sue prestazioni (specialmente se i tuoi dati sono puramente stringhe)
np.vectorize
- (Scorso)
df.query