Excluir linhas de um DataFrame do pandas com base em uma expressão condicional envolvendo len (string) fornecendo KeyError


303

Eu tenho um DataFrame do pandas e quero excluir linhas dele onde o comprimento da string em uma coluna específica é maior que 2.

Espero poder fazer isso (por esta resposta ):

df[(len(df['column name']) < 2)]

mas acabei de receber o erro:

KeyError: u'no item named False'

O que estou fazendo de errado?

(Nota: eu sei que posso usar df.dropna()para me livrar de linhas que contenham alguma NaN, mas não vi como remover linhas com base em uma expressão condicional.)

Respostas:


168

Quando você len(df['column name'])obtém apenas um número, ou seja, o número de linhas no DataFrame (ou seja, o comprimento da própria coluna). Se você deseja aplicar lena cada elemento da coluna, use df['column name'].map(len). Então tente

df[df['column name'].map(len) < 2]

3
Eu criei uma maneira de entender a lista: df[[(len(x) < 2) for x in df['column name']]]mas a sua é muito melhor. Obrigado pela ajuda!
— SJS

13
Caso alguém precise de uma comparação mais complexa, um lambda sempre pode ser usado. df[df['column name'].map(lambda x: str(x)!=".")]
— 4lberto

1
Por alguma razão, nenhuma das outras opções funcionou para mim, exceto a postada por @ 4lberto. Eu estou no pandas 0.23.4e python 3.6
— goelakash

1
Eu adicionaria um .copy()no final, caso você queira editar posteriormente esse dataframe (por exemplo, atribuir novas colunas aumentaria o aviso "Um valor está tentando ser definido em uma cópia de uma fatia de um DataFrame").
— PlasmaBinturong

807

Para responder diretamente ao título original desta pergunta "Como excluir linhas de um DataFrame do pandas com base em uma expressão condicional" (que eu entendo não é necessariamente o problema do OP, mas pode ajudar outros usuários a encontrar essa pergunta), uma maneira de fazer isso é usar o método drop :

df = df.drop(some labels)

df = df.drop(df[<some boolean condition>].index)

Exemplo

Para remover todas as linhas em que a coluna 'score' é <50:

df = df.drop(df[df.score < 50].index)

Versão no local (conforme indicado nos comentários)

df.drop(df[df.score < 50].index, inplace=True)

Várias condições

(consulte Indexação booleana )

Os operadores são: |para or, &para ande ~para not. Estes devem ser agrupados usando parênteses.

Para remover todas as linhas em que a coluna 'score' é <50 e> 20

df = df.drop(df[(df.score < 50) & (df.score > 20)].index)


32
Eu só quero observar que a função drop suporta substituição no local. Ou seja, sua solução é a mesma que df.drop (df [df.score <50] .index, inplace = True). No entanto, não conhecia o truque do "índice". Me ajudou muito
— Quickbeam2k1

9
Só quero ressaltar que antes de usar esse truque de índice, você precisa ter certeza de que seus valores de índice são únicos (ou chamados reset_index()). Descobri isso da maneira mais difícil quando o caminho para muitas linhas foi retirado do meu quadro de dados.
— Jay

3
como eu largo todas as linhas onde o tipo de coluna é str? Eu quero manter apenas os tipos de coluna da lista. Eu tentei test = df.drop(df[df['col1'].dtype == str].index), mas eu recebo o erro KeyError: False Eu também tentei df.drop(df[df.col1.dtype == str].index)e df.drop(df[type(df.cleaned_norm_email) == str].index), mas nada parece funcionar? Alguém pode aconselhar. Obrigado! @User
— PyRsquared

1
Esta é uma pergunta antiga, mas ... @ o peixe com desafios aquáticos é muito mais rápido do que este. Observe que você calcula df[(df.score < 50) & (df.score > 20)]como parte de sua resposta. Se você revertesse isso df = df[(df.score >= 50) | (df.score <= 20)], obteria sua resposta muito mais rapidamente.
— Roobie Nuby 26/03

1
@ RoobieNuby - eles não são a mesma condição.
— Nguai al

106

Você pode atribuir a DataFrameuma versão filtrada de si mesma:

df = df[df.score > 50]

Isso é mais rápido que drop:

%%timeit
test = pd.DataFrame({'x': np.random.randn(int(1e6))})
test = test[test.x < 0]
# 54.5 ms ± 2.02 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

%%timeit
test = pd.DataFrame({'x': np.random.randn(int(1e6))})
test.drop(test[test.x > 0].index, inplace=True)
# 201 ms ± 17.9 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

%%timeit
test = pd.DataFrame({'x': np.random.randn(int(1e6))})
test = test.drop(test[test.x > 0].index)
# 194 ms ± 7.03 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

Como verifico várias colunas usando ou condição?
— Piyush S. Wanare


9

Expandirei a solução genérica do @ User para fornecer uma dropalternativa gratuita. Isto é para pessoas direcionadas aqui com base no título da pergunta (não no problema do OP)

Digamos que você queira excluir todas as linhas com valores negativos. Uma solução de revestimento é: -

df = df[(df > 0).all(axis=1)]

Explicação passo a passo: -

Vamos gerar um quadro de dados de distribuição normal aleatória 5x5

np.random.seed(0)
df = pd.DataFrame(np.random.randn(5,5), columns=list('ABCDE'))
      A         B         C         D         E
0  1.764052  0.400157  0.978738  2.240893  1.867558
1 -0.977278  0.950088 -0.151357 -0.103219  0.410599
2  0.144044  1.454274  0.761038  0.121675  0.443863
3  0.333674  1.494079 -0.205158  0.313068 -0.854096
4 -2.552990  0.653619  0.864436 -0.742165  2.269755

Deixe a condição excluir negativos. Um DF booleano que satisfaça a condição: -

df > 0
      A     B      C      D      E
0   True  True   True   True   True
1  False  True  False  False   True
2   True  True   True   True   True
3   True  True  False   True  False
4  False  True   True  False   True

Uma série booleana para todas as linhas que satisfazem a condição Observe que, se algum elemento da linha falhar, a condição é marcada como falsa

(df > 0).all(axis=1)
0     True
1    False
2     True
3    False
4    False
dtype: bool

Por fim, filtre as linhas do quadro de dados com base na condição

df[(df > 0).all(axis=1)]
      A         B         C         D         E
0  1.764052  0.400157  0.978738  2.240893  1.867558
2  0.144044  1.454274  0.761038  0.121675  0.443863

Você pode atribuí-lo novamente ao df para realmente excluir vs filtragem feita acima
df = df[(df > 0).all(axis=1)]

Isso pode ser facilmente estendido para filtrar linhas contendo NaN s (entradas não numéricas): -
df = df[(~df.isnull()).all(axis=1)]

Isso também pode ser simplificado para casos como: Exclua todas as linhas em que a coluna E é negativa

df = df[(df.E>0)]

Gostaria de terminar com algumas estatísticas de criação de perfil sobre por que a dropsolução do @ User é mais lenta que a filtragem baseada em coluna bruta: -

%timeit df_new = df[(df.E>0)]
345 µs ± 10.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%timeit dft.drop(dft[dft.E < 0].index, inplace=True)
890 µs ± 94.9 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

Uma coluna é basicamente uma matriz, Seriesou seja NumPy, pode ser indexada sem nenhum custo. Para as pessoas interessadas em como a organização da memória subjacente atua na velocidade de execução, aqui está um ótimo link para acelerar o Pandas :


6

Nos pandas, você pode fazer o str.lenseu limite e usar o resultado booleano para filtrá-lo.

df[df['column name'].str.len().lt(2)]

3

Se você deseja descartar linhas do quadro de dados com base em alguma condição complicada no valor da coluna, escrever isso da maneira mostrada acima pode ser complicado. Eu tenho a seguinte solução mais simples que sempre funciona. Vamos supor que você queira soltar a coluna com 'cabeçalho', para que essa coluna seja listada primeiro.

text_data = df['name'].tolist()

Agora aplique alguma função em todos os elementos da lista e coloque-a em uma série de panda:

text_length = pd.Series([func(t) for t in text_data])

no meu caso, eu estava apenas tentando obter o número de tokens:

text_length = pd.Series([len(t.split()) for t in text_data])

agora adicione uma coluna extra com a série acima no quadro de dados:

df = df.assign(text_length = text_length .values)

agora podemos aplicar a condição na nova coluna, como:

df = df[df.text_length  >  10]
def pass_filter(df, label, length, pass_type):

    text_data = df[label].tolist()

    text_length = pd.Series([len(t.split()) for t in text_data])

    df = df.assign(text_length = text_length .values)

    if pass_type == 'high':
        df = df[df.text_length  >  length]

    if pass_type == 'low':
        df = df[df.text_length  <  length]

    df = df.drop(columns=['text_length'])

    return df
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.