Como filtrar linhas em pandas por regex

169

Gostaria de filtrar de maneira limpa um quadro de dados usando o regex em uma das colunas.

Para um exemplo artificial:

In [210]: foo = pd.DataFrame({'a' : [1,2,3,4], 'b' : ['hi', 'foo', 'fat', 'cat']})
In [211]: foo
Out[211]: 
   a    b
0  1   hi
1  2  foo
2  3  fat
3  4  cat

Quero filtrar as linhas para aquelas que começam com o fuso de uma regex. Primeiro vá:

In [213]: foo.b.str.match('f.*')
Out[213]: 
0    []
1    ()
2    ()
3    []

Isso não é muito útil. No entanto, isso me dará meu índice booleano:

In [226]: foo.b.str.match('(f.*)').str.len() > 0
Out[226]: 
0    False
1     True
2     True
3    False
Name: b

Então eu poderia fazer minha restrição:

In [229]: foo[foo.b.str.match('(f.*)').str.len() > 0]
Out[229]: 
   a    b
1  2  foo
2  3  fat

Isso me leva a colocar artificialmente um grupo na regex, e parece que talvez não seja o caminho limpo a seguir. Existe uma maneira melhor de fazer isso?

justinvf
fonte
5
Se você não é casado com regexes, foo[foo.b.str.startswith("f")]funcionará.
DSM
IMHO eu acho que foo[foo.b.str.match('(f.*)').str.len() > 0]é uma solução bastante boa! Mais personalizável e útil do que o startwith, pois inclui a versatilidade do regex.
Tumultous_rooster
3
isso pode ser um pouco tarde, mas nas versões mais recentes dos pandas, o problema está resolvido. a linha foo[foo.b.str.match('f.*')]funciona em pandas 0.24.2 para mim.
Behzad Mehrtash

Respostas:

198

Em vez disso, use contém :

In [10]: df.b.str.contains('^f')
Out[10]: 
0    False
1     True
2     True
3    False
Name: b, dtype: bool
waitingkuo
fonte
11
Como o booleano pode ser invertido? Encontrado: stackoverflow.com/questions/15998188/…
dmeu
4
É possível obter apenas as linhas com True?
Shockwave
2
@shockwave você deve usar:df.loc[df.b.str.contains('^f'), :]
Rafa
1
@shockwave Além disso, você pode apenas usardf[df.b.str.contains('^f'), :]
David Jung
23

Já existe uma função de manipulação de string Series.str.startswith(). Você deveria tentar foo[foo.b.str.startswith('f')].

Resultado:

    a   b
1   2   foo
2   3   fat

Eu acho o que você espera.

Como alternativa, você pode usar o contains com a opção regex. Por exemplo:

foo[foo.b.str.contains('oo', regex= True, na=False)]

Resultado:

    a   b
1   2   foo

na=False é evitar erros caso haja nan, valores nulos etc.

Erkan Şirin
fonte
Eu modifiquei para isso e funcionou para mimdf[~df.CITY.str.contains('~.*', regex= True, na=False)]
Patty Jula 22/01
Obrigado! esta é uma ótima solução
Kedar Joshi
20

Pesquisa em várias colunas com quadro de dados:

frame[frame.filename.str.match('*.'+MetaData+'.*') & frame.file_path.str.match('C:\test\test.txt')]
lakshman senathirajah
fonte
2
frame? e 'C:\test\test.txt'? Parece que você está respondendo a uma pergunta diferente.
tumultous_rooster
o quadro é df. está relacionado à mesma pergunta, mas responde como filtrar várias colunas ('filename' e 'file_path') em um código de linha.
Lakshman senathirajah
12

Pode ser um pouco tarde, mas agora é mais fácil fazer isso no Pandas. Você pode chamar match com as_indexer=Truepara obter resultados booleanos. Isso está documentado (junto com a diferença entre matche contains) aqui .

Michael Siler
fonte
11

Obrigado pela ótima resposta @ user3136169, aqui está um exemplo de como isso pode ser feito também removendo os valores NoneType.

def regex_filter(val):
    if val:
        mo = re.search(regex,val)
        if mo:
            return True
        else:
            return False
    else:
        return False

df_filtered = df[df['col'].apply(regex_filter)]

Também é possível adicionar regex como um argumento:

def regex_filter(val,myregex):
    ...

df_filtered = df[df['col'].apply(res_regex_filter,regex=myregex)]
pardal
fonte
1
obrigado, por causa disso, descobri uma maneira de filtrar uma coluna por predicado arbitrário.
jman
9

Escreva uma função booleana que verifique a regex e use apply na coluna

foo[foo['b'].apply(regex_function)]
user3136169
fonte
1

Usando str fatia

foo[foo.b.str[0]=='f']
Out[18]: 
   a    b
1  2  foo
2  3  fat
YOBEN_S
fonte