Pular para o conteúdo

    Capítulo 25, Intermediário

    MultiIndex

    Um índice com mais de um nível aparece sozinho depois de qualquer `groupby` com duas chaves. Ele é poderoso para selecionar fatias, e confuso quando você não sabe o que ele é. Aqui estão os poucos movimentos que importam.

    De onde ele vem

    Agrupar por duas colunas devolve um resultado com um índice de dois níveis, e agregar com duas funções devolve colunas de dois níveis:

    intermediario/cap25_multiindex.pylinhas 10 a 24
    import pandas as pd
    
    pd.set_option("display.width", 170)
    pd.set_option("display.max_columns", 20)
    
    CANONICO = {"engenharia": "Engenharia", "marketing": "Marketing", "finanças": "Finanças",
                "ciência de dados": "Ciência de Dados", "rh": "RH"}
    df = pd.read_csv("dados/funcionarios_100.csv").drop_duplicates("id_funcionario", keep="last")
    df["salario"] = pd.to_numeric(df["salario"].str.replace("R$ ", "", regex=False))
    df["departamento"] = df["departamento"].str.strip().str.lower().map(CANONICO)
    
    m = df.groupby(["departamento", "cidade"])["salario"].agg(["mean", "count"]).round(0)
    print(type(m.index).__name__, m.index.names)
    print(m.head(4))
    print(type(df.groupby("departamento").agg({"salario": ["mean", "max"]}).columns).__name__)
    
    Saída
    MultiIndex ['departamento', 'cidade']
                                        mean  count
    departamento     cidade                        
    Ciência de Dados Belo Horizonte   9790.0      4
                     Curitiba         9242.0      4
                     Porto Alegre     8213.0      3
                     Rio de Janeiro  11610.0      6
    MultiIndex
    

    Selecionar por nível

    Uma tupla seleciona uma linha exata. O xs seleciona por nível, atravessando o outro. O IndexSlice permite fatias, mas exige o índice ordenado:

    intermediario/cap25_multiindex.pylinhas 29 a 34
    print(float(m.loc[("Engenharia", "São Paulo"), "mean"]))
    print(m.xs("Engenharia", level="departamento").shape)
    
    ordenado = m.sort_index()
    em_sp = ordenado.loc[pd.IndexSlice[:, "São Paulo"], :]
    print(em_sp.index.get_level_values("departamento").tolist())
    
    Saída
    9040.0
    (5, 2)
    ['Ciência de Dados', 'Engenharia', 'Finanças', 'Marketing', 'RH']
    

    Mudar a forma

    O unstack leva um nível para as colunas e o reset_index devolve todos os níveis a colunas comuns. O swaplevel troca a ordem dos níveis:

    intermediario/cap25_multiindex.pylinhas 39 a 42
    largo = m["mean"].unstack("cidade")
    print(largo.shape, largo.columns.tolist())
    print(m.swaplevel().sort_index().index.names)
    print(m.reset_index().columns.tolist())
    
    Saída
    (5, 5) ['Belo Horizonte', 'Curitiba', 'Porto Alegre', 'Rio de Janeiro', 'São Paulo']
    ['cidade', 'departamento']
    ['departamento', 'cidade', 'mean', 'count']
    

    Quando evitar

    O MultiIndex não é necessário para agrupar, resumir ou juntar, e a maior parte da confusão com ele vem de se carregar o índice duplo adiante sem precisar. A minha regra: use-o para ler o resultado de um groupby ou uma tabela dinâmica, e tire-o com reset_index() assim que for continuar a análise, filtrar, juntar ou gravar o resultado em arquivo.

    Exercício 1

    Um ponto da tabela

    Escreva media_do_grupo(m, depto, cidade), que devolva o salário médio de um departamento em uma cidade a partir de m, ou None se a combinação não existir.