Selecione por string parcial de um DataFrame do pandas

447

Eu tenho um DataFramecom 4 colunas, das quais 2 contêm valores de seqüência de caracteres. Eu queria saber se havia uma maneira de selecionar linhas com base em uma correspondência de seqüência parcial contra uma coluna específica?

Em outras palavras, uma função ou função lambda que faria algo como

re.search(pattern, cell_in_question) 

retornando um booleano. Estou familiarizado com a sintaxe de, df[df['A'] == "hello world"]mas parece que não consigo encontrar uma maneira de fazer o mesmo com uma correspondência de string parcial, digamos 'hello'.

Alguém seria capaz de me apontar na direção certa?

euforia
fonte

Respostas:

786

Com base na edição 620 do github , parece que em breve você poderá fazer o seguinte:

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

Atualização: métodos de string vetorizados (por exemplo, Series.str) estão disponíveis nos pandas 0.8.1 e superior.

Garrett
fonte
1
Como é que vamos dizer "Olá" e "Grã-Bretanha" se eu quiser encontrá-los com a condição "OU".
LonelySoul
56
. Desde str * métodos de tratar o padrão de entrada como uma expressão regular, você pode usardf[df['A'].str.contains("Hello|Britain")]
Garrett
7
É possível converter .str.containspara usar .query()api ?
Zyxue
3
df[df['value'].astype(str).str.contains('1234.+')]para filtrar colunas que não são do tipo string.
François Leblanc
213

Eu tentei a solução proposta acima:

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

e obteve um erro:

ValueError: não é possível mascarar com matriz que contém valores NA / NaN

você pode transformar valores de NA em False, assim:

df[df["A"].str.contains("Hello|Britain", na=False)]
Sharon
fonte
54
Ou você pode fazer: df [df ['A']. Str.contains ("Olá | Grã-Bretanha", na = False)]
joshlk
2
df[df['A'].astype(str).str.contains("Hello|Britain")]funcionou bem
Nagabhushan SN
108

Como faço para selecionar por string parcial de um DataFrame do pandas?

Este post é destinado a leitores que desejam

  • procure uma substring em uma coluna de string (o caso mais simples)
  • procure várias substrings (semelhante a isin)
  • corresponde a uma palavra inteira do texto (por exemplo, "azul" deve corresponder a "o céu é azul", mas não "bluejay")
  • corresponder várias palavras inteiras
  • Entenda o motivo por trás de "ValueError: não é possível indexar com vetor contendo valores NA / NaN"

... e gostaria de saber mais sobre quais métodos devem ser preferidos a outros.

(PS: Eu já vi muitas perguntas sobre tópicos semelhantes, achei que seria bom deixar isso aqui.)


Pesquisa Básica de Substring

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

      col
0     foo
1  foobar
2     bar
3     baz

str.containspode ser usado para executar pesquisas de substring ou pesquisa baseada em regex. O padrão da pesquisa é baseado em regex, a menos que você a desative explicitamente.

Aqui está um exemplo de pesquisa baseada em regex,

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

      col
1  foobar

Às vezes, a pesquisa regex não é necessária, então especifique regex=Falsepara desativá-la.

#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

Em termos de desempenho, a pesquisa regex é mais lenta que a pesquisa de substring:

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)

Evite usar a pesquisa baseada em regex se você não precisar.

Endereçamento ValueErrors
Às vezes, realizar uma pesquisa de substring e filtrar o resultado resultará em

ValueError: cannot index with vector containing NA / NaN values

Isso geralmente ocorre devido a dados mistos ou NaNs na coluna do objeto,

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)

Qualquer coisa que não seja uma sequência não pode ter métodos de sequência aplicados, portanto o resultado é NaN (naturalmente). Nesse caso, especifique na=Falsepara ignorar dados que não sejam de sequência,

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

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

Pesquisa por várias seqüências de caracteres

Isso é mais facilmente alcançado por meio de uma pesquisa de regex usando o canal regex OR.

# 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

Você também pode criar uma lista de termos e juntá-los:

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

          col
0     foo abc
1  foobar xyz
3      baz 45

Às vezes, é aconselhável escapar dos seus termos, caso eles tenham caracteres que possam ser interpretados como metacaracteres de expressões regulares . Se seus termos contiverem um dos seguintes caracteres ...

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

Então, você precisará usar re.escapepara escapar deles:

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

          col
0     foo abc
1  foobar xyz
3      baz 45

re.escape tem o efeito de escapar dos caracteres especiais para serem tratados literalmente.

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

Correspondência de palavras inteiras

Por padrão, a pesquisa de substring procura o substring / padrão especificado, independentemente de ser palavra completa ou não. Para combinar apenas palavras completas, precisaremos usar expressões regulares aqui - em particular, nosso padrão precisará especificar limites de palavras ( \b).

Por exemplo,

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

                     col
0        the sky is blue
1  bluejay by the window

Agora considere,

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

Pesquisa de Múltiplas Palavras Inteiras

Semelhante ao acima, exceto que adicionamos um limite de palavra ( \b) ao padrão unido.

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

       col
0  foo abc
3   baz 45

Onde pfica assim,

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

Uma ótima alternativa: use as compreensões da lista !

Porque você pode! E você deveria! Eles geralmente são um pouco mais rápidos que os métodos de string, porque os métodos de string são difíceis de vetorizar e geralmente têm implementações em loop.

Ao invés de,

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

Use o inoperador dentro de uma lista comp,

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

       col
0  foo abc
1   foobar

Ao invés de,

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

Use re.compile(para armazenar em cache seu regex) + Pattern.searchdentro de uma lista de composição,

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

      col
1  foobar

Se "col" tiver NaNs, em vez de

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

Usar,

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

Mais opções para Partial Matching string: np.char.find, np.vectorize, DataFrame.query.

Além de str.containse listar compreensões, você também pode usar as seguintes alternativas.

np.char.find
Suporta apenas pesquisas de substring (leia-se: sem regex).

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

          col
0     foo abc
1  foobar xyz

np.vectorize
Este é um invólucro em torno de um loop, mas com menos sobrecarga do que a maioria dos strmétodos de pandas .

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

Soluções Regex possíveis:

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
Suporta métodos de string por meio do mecanismo python. Isso não oferece benefícios visíveis de desempenho, mas é útil para saber se você precisa gerar dinamicamente suas consultas.

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

      col
0     foo
1  foobar

Mais informações querye evalfamília de métodos podem ser encontradas em Avaliação de Expressão Dinâmica em pandas usando pd.eval () .


Precedência de uso recomendada

  1. (Primeiro) str.contains, por sua simplicidade e facilidade de manipulação de NaNs e dados mistos
  2. Listagens de compreensão, por seu desempenho (especialmente se seus dados são puramente sequências de caracteres)
  3. np.vectorize
  4. (Último) df.query
cs95
fonte
Você poderia editar o método correto a ser usado ao procurar uma sequência em duas ou mais colunas? Basicamente: any(needle in haystack for needling in ['foo', 'bar'] and haystack in (df['col'], df['col2']))e variações, tentei todos sufocar (queixa-se any()e com razão ...) Mas o documento não é muito claro sobre como fazer essa consulta.
Denis de Bernardy
@DenisdeBernardydf[['col1', 'col2']].apply(lambda x: x.str.contains('foo|bar')).any(axis=1)
cs95
@ cs95 Extrair linhas com substring contendo espaços em branco após + nos pandas df Foi respondida em breve, mas você pode dar uma olhada.
ankii
@ankiiiiiii Parece que você perdeu a parte da minha resposta em que mencionei os metacaracteres regex: "Às vezes, é aconselhável escapar dos seus termos, caso eles tenham caracteres que possam ser interpretados como metacaracteres regex".
cs95
1
@ 00schneider r neste caso é usado para indicar uma literal de string bruta. Isso facilita a gravação de cadeias de expressão regulares. stackoverflow.com/q/2081640
cs95
53

Se alguém se perguntar como executar um problema relacionado: "Selecionar coluna por sequência parcial"

Usar:

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

E para selecionar linhas por correspondência parcial de string, passe axis=0para o filtro:

# selects rows which contain the word hello in their index label
df.filter(like='hello', axis=0)  
Philipp Schwarz
fonte
6
Isso pode ser destilado para:df.loc[:, df.columns.str.contains('a')]
elPastor 17/17
18
que pode ser mais destilado em:df.filter(like='a')
Ted Petrou
isso deve ser uma pergunta e uma resposta próprias, já 50 pessoas procuraram por ele ...
PV8
1
A pergunta PV8 já existe: stackoverflow.com/questions/31551412/… . Mas quando eu procuro no google por "pandas Selecionar coluna por string parcial", esse tópico aparece primeiro
Philipp Schwarz
28

Nota rápida: se você deseja fazer a seleção com base em uma sequência parcial contida no índice, tente o seguinte:

df['stridx']=df.index
df[df['stridx'].str.contains("Hello|Britain")]
cristão
fonte
5
Você pode simplesmente df [df.index.to_series (). Str.contains ('LLChit')]
Yury Bayda
21

Digamos que você tenha o seguinte DataFrame:

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

Você sempre pode usar o inoperador em uma expressão lambda para criar seu filtro.

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

O truque aqui é usar a axis=1opção na applypassagem de elementos para a função lambda linha por linha, em oposição a coluna por coluna.

Mike
fonte
Como modifico acima para dizer que x ['a'] existe apenas no início de x ['b']?
ComplexData
1
aplicar é uma má ideia aqui em termos de desempenho e memória. Veja esta resposta .
cs95
8

Aqui está o que eu acabei fazendo para correspondências parciais de string. Se alguém tiver uma maneira mais eficiente de fazer isso, entre em contato.

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
fonte
3
Deve ser 2x a 3x mais rápido se você regex compilação antes de loop: regex = re.compile (regex) e, em seguida, se regex.search (registro)
MarkokraM
1
O @MarkokraM docs.python.org/3.6/library/re.html#re.compile diz que os regexs mais recentes são armazenados em cache para você, para que você não precise se compilar.
Teepeemm
Não use iteritems para iterar sobre um DataFrame. Ele ocupa a última posição em termos de pandorability e desempenho
cs95
5

O uso de contains não funcionou bem para minha string com caracteres especiais. Encontre funcionou embora.

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

Antes disso, existem respostas que atendem ao recurso solicitado, de qualquer maneira, gostaria de mostrar da maneira mais geral:

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

Dessa forma, vamos obter a coluna que você procura, independentemente do caminho que for escrito.

(Obviusly, você deve escrever a expressão regex adequada para cada caso)

xpeiro
fonte
1
Isso filtra nos cabeçalhos das colunas . Não é geral, está incorreto.
cs95
@MicheldeRuiter que ainda está incorreto, que filtraria os rótulos dos índices!
cs95
Não responde a pergunta. Mas eu aprendi alguma coisa. :)
Michel de Ruiter
2

Talvez você queira procurar algum texto em todas as colunas do quadro de dados do Pandas, e não apenas no subconjunto deles. Nesse caso, o código a seguir ajudará.

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

Atenção. Este método é relativamente lento, embora conveniente.

Serhii Kushchenko
fonte
2

Se você precisar fazer uma pesquisa sem distinção entre maiúsculas e minúsculas para uma sequência de caracteres em uma coluna de quadro de dados do pandas:

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