Capítulo 8, Básico
Filtrar com condições
Filtrar é descrever **quais** linhas você quer, em vez de percorrer as linhas perguntando "esta serve?". Uma condição vira uma máscara de verdadeiro e falso, e a máscara seleciona.
A máscara
Comparar uma coluna com um valor produz uma Series de booleanos. Usá-la como índice mantém as linhas verdadeiras. Como True vale 1, somar a máscara conta e tirar a média dá a proporção:
import pandas as pd
pd.set_option("display.width", 170)
pd.set_option("display.max_columns", 20)
df = pd.read_csv("dados/funcionarios_15.csv")
mascara = df["experiencia"] > 5
print(mascara.tolist())
print(df.loc[mascara, ["nome", "experiencia"]])
print(int(mascara.sum()), round(float(mascara.mean()), 3))
[False, False, False, False, False, True, True, True, False, False, False, False, False, True, True]
nome experiencia
5 Felipe Araújo 6
6 Gabriela Nunes 7
7 Henrique Costa 8
13 Nelson Ribeiro 6
14 Ana Souza 7
5 0.333
Combinar condições: `&`, `|`, `~`
Para combinar condições, use & (e), | (ou) e ~ (não), e cada condição entre parênteses: esses operadores têm precedência maior que a comparação. O and e o or do Python não funcionam, porque precisam decidir se uma Series inteira é verdadeira ou falsa:
experiente_e_bom = df[(df["experiencia"] > 3) & (df["nota_desempenho"] > 7)]
print(experiente_e_bom[["nome", "experiencia", "nota_desempenho"]])
print(len(df[(df["departamento"] == "Engenharia") | (df["departamento"] == "RH")]))
print(len(df[~(df["cidade"] == "Curitiba")]))
try:
df[(df["experiencia"] > 3) and (df["nota_desempenho"] > 7)]
except ValueError as erro:
print("ValueError:", erro)
nome experiencia nota_desempenho
4 Elisa Fernandes 5 8
5 Felipe Araújo 6 9
11 Lucas Barbosa 4 9
5
13
ValueError: The truth value of a Series is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().
Repare no ~(df["cidade"] == "Curitiba"): ele mantém também as linhas em que a cidade está ausente, porque NaN == "Curitiba" é falso, e a negação de falso é verdadeiro. Uma máscara é sempre sobre valores, e um ausente nunca "é igual" a nada.
Os atalhos: `isin`, `between` e `str.contains`
Três métodos substituem as expressões mais verbosas:
print(len(df[df["departamento"].isin(["Engenharia", "Ciência de Dados"])]))
print(df[df["experiencia"].between(2, 4)]["nome"].tolist())
print(df[df["nome"].str.contains("Souza")]["nome"].tolist())
4
['Bruno Lima', 'Carla Mendes', 'Diego Rocha', 'João Pereira', 'Karina Alves', 'Lucas Barbosa']
['Ana Souza', 'Ana Souza']
O isin troca uma cadeia de |. O between é inclusivo nos dois extremos por padrão (inclusive="both") e se lê bem em faixas de valores. O str.contains serve para buscas em texto, como uma caixa de pesquisa.
Texto com ausentes: o que acontece depende do tipo
Em uma coluna de texto com valores ausentes, o str.contains precisa decidir o que responder para eles. A resposta depende do tipo da coluna, e é uma das mudanças do Pandas 3:
busca = df["departamento"].str.contains("eng", case=False)
print(busca.dtype, bool(busca.isna().any()), bool(busca[8]))
como_objeto = df["departamento"].astype(object)
busca_objeto = como_objeto.str.contains("eng", case=False)
print(busca_objeto.dtype, bool(busca_objeto.isna().any()))
try:
df[busca_objeto]
except ValueError as erro:
print("ValueError:", erro)
bool False False
object True
ValueError: Cannot mask with non-boolean array containing NA / NaN values
No tipo str do Pandas 3, o ausente vira False automaticamente, e o filtro funciona. No tipo object (código escrito para as versões antigas, ou uma coluna que mistura tipos), o resultado tem NaN e o filtro falha. Para não depender do tipo, diga o que fazer com os ausentes com o parâmetro na, e a mesma linha funciona em qualquer versão:
com_na_false = df["departamento"].str.contains("eng", case=False, na=False)
com_na_true = df["departamento"].str.contains("eng", case=False, na=True)
print(int(com_na_false.sum()), int(com_na_true.sum()))
print(df[com_na_false]["nome"].tolist())
3 4
['Ana Souza', 'Carla Mendes', 'Karina Alves']
O na=False encontra 3 funcionários, e o na=True encontra 4: o quarto é o funcionário sem departamento, que o na=True trata como "contém". A escolha é sua, e escrever o na explícito deixa a decisão visível para quem lê.
`query`: filtrar com texto
O query escreve a condição como uma expressão, sem repetir df[...] nem parênteses, e acessa variáveis do Python com @:
limite = 7
print(df.query("experiencia > 3 and nota_desempenho > @limite")["nome"].tolist())
['Elisa Fernandes', 'Felipe Araújo', 'Lucas Barbosa']
Alterar só as linhas que passam no filtro
Para mudar valores em linhas filtradas, use .loc[máscara, coluna] = valor, em um único passo. Encadear (df[mascara]["coluna"] = valor) altera uma cópia temporária, e no Pandas 3 isso nunca afeta a tabela original:
copia = df.copy()
copia.loc[copia["experiencia"] > 6, "nota_desempenho"] = 10
print(copia["nota_desempenho"].tolist())
encadeado = df.copy()
encadeado[encadeado["experiencia"] > 6]["nota_desempenho"] = 10
print(encadeado["nota_desempenho"].tolist() == df["nota_desempenho"].tolist())
[4, 5, 6, 7, 8, 9, 10, 10, 6, 7, 8, 9, 4, 5, 10]
True
Mantenha a forma com `where`
O
df["coluna"].where(condição)mantém todas as linhas e troca porNaNas que não passam, enquanto o filtro com máscara descarta as linhas. Eu uso owherequando preciso preservar o alinhamento com a tabela original.
Exercício 1
Quem merece atenção
Escreva precisam_de_apoio(tabela), que devolva os nomes de quem tem nota_desempenho menor que 6 ou (experiencia maior ou igual a 5 e projetos_concluidos menor que 7). Cuidado com os parênteses, e confira o resultado contra um laço escrito à mão.