import pandas as pd
import altair as alt
import numpy as np
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import classification_report, confusion_matrix, roc_auc_score, roc_curve
# Carregar dataset Palmer Penguins
penguins = pd.read_csv('https://raw.githubusercontent.com/allisonhorst/palmerpenguins/master/inst/extdata/penguins.csv')
penguins = penguins.dropna()
# Filtrar apenas Adelie e Chinstrap (excluir Gentoo)
penguins_bin = penguins[penguins['species'].isin(['Adelie', 'Chinstrap'])].copy()
# Criar variável binária: 1 para Chinstrap, 0 para Adelie
penguins_bin['species_binary'] = (penguins_bin['species'] == 'Chinstrap').astype(int)
print(f"Total de pinguins: {len(penguins_bin)}")
print(f"Adelie (0): {sum(penguins_bin['species_binary'] == 0)}")
print(f"Chinstrap (1): {sum(penguins_bin['species_binary'] == 1)}")Regressão Logística
Em muitas situações na Ciência de Dados, o objetivo não é prever um número contínuo, como o preço de um produto ou a temperatura amanhã. Em vez disso, queremos prever uma classificação, como:
- Se um cliente vai ou não cancelar o serviço (churn);
- Se um e-mail é ou não spam;
- Se um paciente tem ou não uma doença.
Nestes casos, usamos a Regressão Logística.
Apesar do nome, a regressão logística não estima valores contínuos, e sim a probabilidade de uma observação pertencer a uma classe. É especialmente útil quando a variável resposta é binária (0 ou 1, sim ou não, positivo ou negativo).

Na animação acima, vemos como a regressão logística encontra a melhor curva sigmoide (formato de S) que separa duas classes. Diferentemente da regressão linear que traça uma linha reta, a regressão logística usa uma função matemática especial que sempre produz valores entre 0 e 1 - perfeita para representar probabilidades!
Conceito
A regressão logística é um modelo estatístico que estima a probabilidade de uma observação pertencer à classe 1 (ou seja, de ter um evento positivo).
A equação geral é:
\[ P(Y = 1) = \frac{1}{1 + e^{-(\beta_0 + \beta_1 X_1 + \dots + \beta_n X_n)}} \]
- A saída do modelo é uma probabilidade entre 0 e 1.
- Essa probabilidade pode ser convertida em uma classe, com base em um limite (threshold) — por padrão, 0.5.
Diferença da Regressão Linear
| Regressão Linear | Regressão Logística |
|---|---|
| Saída é numérica | Saída é probabilidade |
| Pode prever qualquer valor | Prevê entre 0 e 1 |
Regressão sobre Y contínuo |
Classificação binária |
Pressupostos da Regressão Logística
Embora mais flexível que a regressão linear, a regressão logística também tem pressupostos importantes. Vamos mostrar como verificar cada um deles na prática mais adiante no material.
| Pressuposto | Descrição | Como Verificar |
|---|---|---|
| 1. Variável resposta binária | A variável Y deve ter dois resultados possíveis (0/1, sim/não) | Verificação direta dos dados |
| 2. Independência | Observações independentes entre si | Análise do design do estudo/coleta |
| 3. Linearidade no logit | Relação linear entre variáveis e log-odds da probabilidade | Teste Box-Tidwell ou análise residual |
| 4. Ausência de multicolinearidade | Variáveis explicativas não altamente correlacionadas | VIF (Variance Inflation Factor) |
Exemplo Prático com Python
Vamos usar o dataset dos pinguins de Palmer para classificar espécies. Focaremos em um problema de classificação binária interessante: distinguir entre Adelie e Chinstrap, duas espécies que frequentemente se confundem devido às suas características físicas similares.
Vamos prever se um pinguim é da espécie Adelie (0) ou Chinstrap (1) com base em suas características físicas:
- Comprimento do bico (
bill_length_mm) - Profundidade do bico (
bill_depth_mm) - Comprimento da nadadeira (
flipper_length_mm) - Massa corporal (
body_mass_g)
Total de pinguins: 214
Adelie (0): 146
Chinstrap (1): 68
# Visualizar distribuição das espécies por características
alt.Chart(penguins_bin).mark_circle(size=80, opacity=0.7).encode(
x=alt.X('bill_length_mm:Q',
title='Comprimento do Bico (mm)',
scale=alt.Scale(domain=[30, 60])),
y=alt.Y('bill_depth_mm:Q',
title='Profundidade do Bico (mm)',
scale=alt.Scale(domain=[15, 22])),
color=alt.Color('species:N',
scale=alt.Scale(scheme='category10'),
title='Espécie'),
tooltip=['species:N', 'bill_length_mm:Q', 'bill_depth_mm:Q', 'flipper_length_mm:Q', 'body_mass_g:Q']
).properties(
title='Adelie vs Chinstrap: Observe a sobreposição!',
width=600,
height=400
)Podemos ver que há uma sobreposição entre Adelie e Chinstrap - isso torna nosso problema de classificação desafiador e interessante! Agora vamos preparar os dados e treinar nosso modelo:
# Selecionar variáveis explicativas
X = penguins_bin[['bill_length_mm', 'bill_depth_mm', 'flipper_length_mm', 'body_mass_g']]
y = penguins_bin['species_binary']
# Separar treino (70%) e teste (30%)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42, stratify=y)
# Padronizar os dados (importante para regressão logística)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)Agora vamos treinar nosso modelo de regressão logística:
# Ajustar modelo
modelo_log = LogisticRegression(random_state=42)
modelo_log.fit(X_train_scaled, y_train)
# Mostrar coeficientes
coef_df = pd.DataFrame({
'Variável': X.columns,
'Coeficiente': modelo_log.coef_[0],
'Interpretação': [
'Impacto do comprimento do bico',
'Impacto da profundidade do bico',
'Impacto do comprimento da nadadeira',
'Impacto da massa corporal'
]
})
print("Coeficientes do Modelo de Regressão Logística:")
coef_df.round(3)Coeficientes do Modelo de Regressão Logística:
| Variável | Coeficiente | Interpretação | |
|---|---|---|---|
| 0 | bill_length_mm | 3.774 | Impacto do comprimento do bico |
| 1 | bill_depth_mm | -0.621 | Impacto da profundidade do bico |
| 2 | flipper_length_mm | 0.138 | Impacto do comprimento da nadadeira |
| 3 | body_mass_g | -0.749 | Impacto da massa corporal |
Os coeficientes estimados indicam o impacto de cada variável nos log-odds da resposta ser 1 (ou seja, do pinguim ser Chinstrap).
Fazendo Predições
Agora que nosso modelo está treinado, vamos usá-lo para classificar os pinguins do conjunto de teste:
# Fazer predições no conjunto de teste
y_pred = modelo_log.predict(X_test_scaled)
y_proba = modelo_log.predict_proba(X_test_scaled)[:, 1]
# Criar DataFrame para visualização (resetar índices para alinhar)
test_results = pd.DataFrame({
'bill_length_mm': X_test['bill_length_mm'].values,
'bill_depth_mm': X_test['bill_depth_mm'].values,
'species_true': pd.Series(y_test.values).map({0: 'Adelie', 1: 'Chinstrap'}),
'species_pred': pd.Series(y_pred).map({0: 'Adelie', 1: 'Chinstrap'}),
'probability_chinstrap': y_proba,
'correct': y_test.values == y_pred
})
print(f"Acurácia no conjunto de teste: {(y_test == y_pred).mean():.3f}")Acurácia no conjunto de teste: 1.000
Vamos visualizar os resultados das predições:
# Criar gráfico simples com predições
prediction_chart = alt.Chart(test_results).mark_circle(size=80).encode(
x=alt.X('bill_length_mm:Q',
scale=alt.Scale(domain=[30, 60]),
title='Comprimento do Bico (mm)'),
y=alt.Y('bill_depth_mm:Q',
scale=alt.Scale(domain=[15, 22]),
title='Profundidade do Bico (mm)'),
color=alt.Color('probability_chinstrap:Q',
scale=alt.Scale(scheme='redblue'),
title='Probabilidade Chinstrap')
).properties(
title='Predições do Modelo de Regressão Logística',
width=500,
height=400
)
prediction_chartEsse gráfico simples mostra como nosso modelo atribui probabilidades aos diferentes pinguins com base em suas características físicas.
O
y_prednos dá a classe (0 ou 1) para cada pinguim, enquantoy_probanos mostra a probabilidade estimada de ser Chinstrap.
Diagnóstico do Modelo
Vamos verificar se nosso modelo atende aos pressupostos da regressão logística. Como vimos anteriormente, precisamos checar principalmente a linearidade no logit e a ausência de multicolinearidade.
Verificação da Linearidade no Logit
Verificação de Multicolinearidade
Vamos usar o VIF (Variance Inflation Factor) para detectar correlações altas entre as variáveis:
from statsmodels.stats.outliers_influence import variance_inflation_factor
# Calcular VIF
vif_data = pd.DataFrame()
vif_data['Variável'] = X.columns
vif_data['VIF'] = [variance_inflation_factor(X_train_scaled, i) for i in range(X_train_scaled.shape[1])]
print("Fatores de Inflação da Variância (VIF):")
print(vif_data.round(2))
print("\n🔍 Interpretação dos valores de VIF:")
print("• VIF ≤ 5: Correlação baixa a moderada (aceitável)")
print("• 5 < VIF ≤ 10: Correlação elevada (atenção necessária)")
print("• VIF > 10: Multicolinearidade crítica (problema sério)")Fatores de Inflação da Variância (VIF):
Variável VIF
0 bill_length_mm 1.31
1 bill_depth_mm 1.56
2 flipper_length_mm 1.55
3 body_mass_g 1.72
🔍 Interpretação dos valores de VIF:
• VIF ≤ 5: Correlação baixa a moderada (aceitável)
• 5 < VIF ≤ 10: Correlação elevada (atenção necessária)
• VIF > 10: Multicolinearidade crítica (problema sério)
Avaliação do Desempenho
Vamos avaliar como nosso modelo está performando usando várias métricas importantes:
Matriz de Confusão
# Calcular e mostrar matriz de confusão
cm = confusion_matrix(y_test, y_pred)
print("Matriz de Confusão:")
print(f" Predito")
print(f"Real Adelie Chinstrap")
print(f"Adelie {cm[0,0]:2d} {cm[0,1]:2d}")
print(f"Chinstrap {cm[1,0]:2d} {cm[1,1]:2d}")
print("\n📊 Interpretação:")
print(f"• Verdadeiros Negativos (VN): {cm[0,0]} - Adelie classificado como Adelie")
print(f"• Falsos Positivos (FP): {cm[0,1]} - Adelie classificado como Chinstrap")
print(f"• Falsos Negativos (FN): {cm[1,0]} - Chinstrap classificado como Adelie")
print(f"• Verdadeiros Positivos (VP): {cm[1,1]} - Chinstrap classificado como Chinstrap")Matriz de Confusão:
Predito
Real Adelie Chinstrap
Adelie 44 0
Chinstrap 0 21
📊 Interpretação:
• Verdadeiros Negativos (VN): 44 - Adelie classificado como Adelie
• Falsos Positivos (FP): 0 - Adelie classificado como Chinstrap
• Falsos Negativos (FN): 0 - Chinstrap classificado como Adelie
• Verdadeiros Positivos (VP): 21 - Chinstrap classificado como Chinstrap
Métricas de Classificação
# Relatório completo de classificação
print("Relatório de Classificação:")
print(classification_report(y_test, y_pred, target_names=['Adelie', 'Chinstrap']))
# Calcular AUC-ROC
auc_score = roc_auc_score(y_test, y_proba)
print(f"\nAUC-ROC Score: {auc_score:.3f}")Relatório de Classificação:
precision recall f1-score support
Adelie 1.00 1.00 1.00 44
Chinstrap 1.00 1.00 1.00 21
accuracy 1.00 65
macro avg 1.00 1.00 1.00 65
weighted avg 1.00 1.00 1.00 65
AUC-ROC Score: 1.000
Curva ROC
A curva ROC nos mostra como o modelo discrimina entre as duas classes em diferentes thresholds:
# Calcular curva ROC
fpr, tpr, thresholds = roc_curve(y_test, y_proba)
roc_data = pd.DataFrame({
'fpr': fpr,
'tpr': tpr,
'threshold': thresholds
})
# Curva ROC
roc_curve_chart = alt.Chart(roc_data).mark_line(strokeWidth=3, color='blue').encode(
x=alt.X('fpr:Q', title='Taxa de Falsos Positivos'),
y=alt.Y('tpr:Q', title='Taxa de Verdadeiros Positivos')
)
# Linha diagonal (classificador aleatório)
diagonal = alt.Chart(pd.DataFrame({'x': [0, 1], 'y': [0, 1]})).mark_line(
strokeDash=[5, 5], color='red'
).encode(x='x:Q', y='y:Q')
# Combinar gráficos
(roc_curve_chart + diagonal).properties(
title=f'Curva ROC (AUC = {auc_score:.3f})',
width=400,
height=400
)📋 Resumo do Desempenho:
Nosso modelo conseguiu uma boa separação entre as espécies Adelie e Chinstrap, considerando que elas são visualmente similares. A curva ROC acima da diagonal indica que o modelo é melhor que um classificador aleatório.
Conclusão
A regressão logística é uma ferramenta poderosa para problemas de classificação binária, transformando características numéricas em probabilidades que podem ser interpretadas de forma intuitiva. Através do estudo das espécies Adelie e Chinstrap, pudemos ver como o modelo consegue distinguir entre grupos similares usando a função sigmoide.
Os principais pontos que vimos foram:
- A regressão logística usa a função sigmoide para mapear qualquer valor real em probabilidades entre 0 e 1
- O diagnóstico de pressupostos é crucial, especialmente linearidade no logit e multicolinearidade
- Visualizações ajudam muito a entender o comportamento do modelo e identificar padrões
- Métricas como AUC-ROC e matriz de confusão são essenciais para avaliar o desempenho
A regressão logística pode ser aplicada em diferentes contextos, desde detecção de spam até diagnóstico médico, sempre fornecendo interpretações claras sobre quais características mais influenciam a classificação e apoiando a tomada de decisão baseada em evidências.
Com essa base, você está pronto para aplicar regressão logística em seus próprios projetos de ciência de dados!
Recursos Adicionais
- Livros:
- Introduction to Statistical Learning (James et al.) - Capítulo 4
- Hands-On Machine Learning with Scikit-Learn and TensorFlow (Aurélien Géron) - Capítulo 4
- The Elements of Statistical Learning (Hastie et al.) - Capítulo 4
- Documentação:
- Tutoriais Online: