Pular para o conteúdo

    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:

    basico/cap08_filtrar.pylinhas 10 a 19
    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))
    
    Saída
    [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:

    basico/cap08_filtrar.pylinhas 24 a 32
    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)
    
    Saída
                   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:

    basico/cap08_filtrar.pylinhas 37 a 39
    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())
    
    Saída
    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:

    basico/cap08_filtrar.pylinhas 44 a 53
    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)
    
    Saída
    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:

    basico/cap08_filtrar.pylinhas 55 a 58
    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())
    
    Saída
    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 @:

    basico/cap08_filtrar.pylinhas 63 a 64
    limite = 7
    print(df.query("experiencia > 3 and nota_desempenho > @limite")["nome"].tolist())
    
    Saída
    ['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:

    basico/cap08_filtrar.pylinhas 69 a 75
    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())
    
    Saída
    [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 por NaN as que não passam, enquanto o filtro com máscara descarta as linhas. Eu uso o where quando 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.