Preditores Qualitativos

Autor

Douglas Braga

Nota

Esta seção corresponde à seção 3.3.1 de James et al. (2023).

Um coeficiente de regressão multiplica um número por outro, \(\beta_j \cdot x_j\). Todo preditor usado até aqui neste capítulo era numérico — dólares, unidades, anos. Credit, o conjunto que esta seção introduz, tem quatro colunas que não são: imovel_proprio, estudante e casado valem sim ou não; regiao vale Leste, Sul ou Oeste. Nenhuma delas multiplica coeficiente nenhum do jeito que está — e é essa a pergunta que a seção resolve.

credit = pd.read_csv("dados/Credit.csv")
credit.shape, credit.columns.tolist()
((400, 11),
 ['renda',
  'limite',
  'pontuacao',
  'cartoes',
  'idade',
  'escolaridade',
  'imovel_proprio',
  'estudante',
  'casado',
  'regiao',
  'saldo'])

São 400 clientes e onze colunas: renda, limite, pontuacao, cartoes, idade e escolaridade, numéricas; imovel_proprio, estudante e casado, sim/não; regiao, com três categorias; e saldo — a dívida média no cartão de crédito de cada cliente, em dólares, a resposta que o resto da seção tenta prever a partir de duas dessas colunas categóricas, estudante e regiao.

O saldo médio, por ser estudante ou não

A pergunta mais simples é a de duas categorias: quem é estudante carrega, em média, saldo diferente de quem não é? Uma variável indicadora responde trocando a categoria por um número.

Uma variável indicadora (ou dummy) troca uma categoria por 0 ou 1: vale 1 quando a observação está no nível escolhido, 0 nos demais. Um preditor qualitativo com \(k\) níveis entra no modelo como \(k - 1\) indicadoras, nunca \(k\) — a categoria que sobra sem indicadora própria é a base, e toda previsão para ela sai só do intercepto.

indicadora_estudante = pd.get_dummies(credit[["estudante"]], drop_first=True)
indicadora_estudante.columns.tolist()
['estudante_sim']

drop_first=True descarta a primeira categoria em ordem alfabética — não vem antes de sim — e fica só estudante_sim, que vale True para quem é estudante e False para quem não é. não é a base: toda observação sem a indicadora marcada pertence a ela.

modelo_estudante = LinearRegression().fit(indicadora_estudante, credit["saldo"])
intercepto_estudante = round(float(modelo_estudante.intercept_), 2)
coef_estudante_sim = round(float(modelo_estudante.coef_[0]), 2)

intercepto_estudante, coef_estudante_sim
(480.37, 396.46)
medias_estudante = credit.groupby("estudante")["saldo"].mean()
media_nao_estudante = round(float(medias_estudante["não"]), 2)
media_sim_estudante = round(float(medias_estudante["sim"]), 2)
diferenca_medias_estudante = round(media_sim_estudante - media_nao_estudante, 2)
estudantes_devem_mais = bool(diferenca_medias_estudante > 0)

media_nao_estudante, media_sim_estudante, diferenca_medias_estudante, estudantes_devem_mais
(480.37, 876.83, 396.46, True)
intercepto_bate_com_media = intercepto_estudante == media_nao_estudante
coeficiente_bate_com_diferenca = coef_estudante_sim == diferenca_medias_estudante

intercepto_bate_com_media, coeficiente_bate_com_diferenca
(True, True)

O modelo ajustado só com essa indicadora dá um intercepto de 480,37 e um coeficiente de 396,46. São os mesmos dois números que saem direto do dado, sem ajustar modelo nenhum: 480,37 é o saldo médio de quem não é estudante, e 396,46 é a diferença entre essa média e a de quem é, 876,83 (876,83 − 480,37 = 396,46). intercepto_bate_com_media e coeficiente_bate_com_diferenca confirmam a igualdade nos dois casos. estudantes_devem_mais confirma que quem é estudante carrega, em média, mais saldo que quem não é.

A base é uma escolha, e a previsão não sabe disso

Trocar qual categoria fica sem indicadora — a base — é uma escolha de quem ajusta o modelo, não um fato sobre o dado. get_dummies descarta, por padrão, a primeira categoria em ordem alfabética; para inverter, basta listar as categorias na ordem desejada antes de gerar a indicadora.

estudante_ordenado = pd.Categorical(credit["estudante"], categories=["sim", "não"])
indicadora_estudante_b = pd.get_dummies(pd.DataFrame({"estudante": estudante_ordenado}), drop_first=True)
indicadora_estudante_b.columns.tolist()
['estudante_não']
modelo_estudante_b = LinearRegression().fit(indicadora_estudante_b, credit["saldo"])
intercepto_estudante_b = round(float(modelo_estudante_b.intercept_), 2)
coef_estudante_nao = round(float(modelo_estudante_b.coef_[0]), 2)

intercepto_estudante_b, coef_estudante_nao
(876.83, -396.46)

Com sim como base, o intercepto salta para 876,83 — a mesma média de quem é estudante de antes — e o coeficiente muda de sinal, para -396,46: a mesma diferença de antes, contada na direção oposta.

previsao_original = modelo_estudante.predict(indicadora_estudante)
previsao_invertida = modelo_estudante_b.predict(indicadora_estudante_b)
previsoes_identicas = bool(np.allclose(previsao_original, previsao_invertida))

previsoes_identicas
True

previsoes_identicas confirma que, apesar do intercepto e do coeficiente mudarem, as previsões para os 400 clientes são as mesmas nos dois ajustes — a escolha da base muda a leitura dos coeficientes, não o que o modelo prevê.

Região: quando a categoria tem mais de dois nomes

regiao tem três categorias — Leste, Sul e Oeste —, não duas. Uma única indicadora não dá conta: sobra uma categoria sem representação. A regra do conceito acima vale de novo, com \(k = 3\): entram \(k - 1 = 2\) indicadoras.

indicadora_regiao = pd.get_dummies(credit[["regiao"]], drop_first=True)
indicadora_regiao.columns.tolist()
['regiao_Oeste', 'regiao_Sul']

Duas indicadoras, regiao_Oeste e regiao_Sul; Leste fica sem indicadora própria e é a base. Uma terceira indicadora, para Leste, seria redundante: quem não é Oeste nem Sul só pode ser Leste, e o valor dela já está determinado pelas outras duas.

modelo_regiao = LinearRegression().fit(indicadora_regiao, credit["saldo"])
coeficientes_regiao = dict(zip(modelo_regiao.feature_names_in_, modelo_regiao.coef_))
coef_oeste = round(float(coeficientes_regiao["regiao_Oeste"]), 2)
coef_sul = round(float(coeficientes_regiao["regiao_Sul"]), 2)
intercepto_regiao = round(float(modelo_regiao.intercept_), 2)

intercepto_regiao, coef_oeste, coef_sul
(531.0, -18.69, -12.5)
medias_regiao = credit.groupby("regiao")["saldo"].mean()
media_leste = round(float(medias_regiao["Leste"]), 2)
media_oeste = round(float(medias_regiao["Oeste"]), 2)
media_sul = round(float(medias_regiao["Sul"]), 2)
leste_tem_a_maior_media = bool(medias_regiao.idxmax() == "Leste")

media_leste, media_oeste, media_sul, leste_tem_a_maior_media
(531.0, 512.31, 518.5, True)

O intercepto, 531,0, é o saldo médio de quem mora no Leste — a base. Os coeficientes leem-se como diferenças contra essa base: -18,69 para Oeste e -12,5 para Sul, o mesmo que a tabela de médias mostra: 512,31 − 531,0 = -18,69 e 518,5 − 531,0 = -12,5. leste_tem_a_maior_media confirma que, das três regiões, é no Leste que o saldo médio é o mais alto.

O que o modelo prevê: a média do grupo, e só ela

Com um único preditor qualitativo, o modelo não tem outra informação para usar: toda previsão é a média do grupo a que a observação pertence, e nada mais fino que isso. Para estudante, só existem duas previsões possíveis, 480,37 e 876,83; para regiao, só três, 531,0, 512,31 e 518,5.

rng = np.random.default_rng(8)

fig, (ax_estudante, ax_regiao) = plt.subplots(1, 2, figsize=(10, 4.3), sharey=True)

paineis = [
    (ax_estudante, "estudante", ["não", "sim"], medias_estudante, "estudante"),
    (ax_regiao, "regiao", ["Leste", "Sul", "Oeste"], medias_regiao, "região"),
]
for eixo, coluna, ordem, medias, rotulo in paineis:
    for posicao, categoria in enumerate(ordem):
        valores = credit.loc[credit[coluna] == categoria, "saldo"]
        deslocamento = rng.uniform(-0.15, 0.15, size=len(valores))
        eixo.scatter(posicao + deslocamento, valores, s=12, alpha=0.4, color="C0")
        eixo.plot([posicao - 0.22, posicao + 0.22], [medias[categoria]] * 2, color="C1", linewidth=3)
    eixo.set_xticks(range(len(ordem)))
    eixo.set_xticklabels(ordem)
    eixo.set_xlabel(rotulo)

ax_estudante.set_ylabel("saldo (dólares)")
plt.tight_layout()
plt.show()
Figura 46.1: Saldo de cada um dos 400 clientes, por estudante (esquerda) e por região (direita). Os pontos têm um deslocamento horizontal aleatório só para não empilhar; o traço laranja marca a média de cada grupo — a mesma que o intercepto e os coeficientes reproduzem.

O traço laranja fica na mesma altura para todo ponto do grupo: é essa reta plana, por categoria, que um preditor qualitativo sozinho consegue desenhar.

James, Gareth, Daniela Witten, Trevor Hastie, Robert Tibshirani, e Jonathan Taylor. 2023. An Introduction to Statistical Learning with Applications in Python. Springer.