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:
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__)
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:
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())
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:
largo = m["mean"].unstack("cidade")
print(largo.shape, largo.columns.tolist())
print(m.swaplevel().sort_index().index.names)
print(m.reset_index().columns.tolist())
(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.