Tarefa4-AEDI / app.py
FernandezUNB's picture
Update app.py
2b937d8 verified
import gradio as gr
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from scipy import stats
import warnings
warnings.filterwarnings('ignore')
# Bibliotecas para modelagem
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
from sklearn.ensemble import RandomForestRegressor
# Configuração visual
plt.style.use('default')
sns.set_palette("husl")
plt.rcParams['figure.figsize'] = (10, 6)
plt.rcParams['font.size'] = 10
# Cache manual para melhor performance
_data_cache = {}
def load_data():
"""Carrega o dataset Ames Housing com cache manual"""
if 'data' in _data_cache:
return _data_cache['data']
try:
url = 'http://jse.amstat.org/v19n3/decock/AmesHousing.txt'
df = pd.read_csv(url, sep='\t')
_data_cache['data'] = df
return df
except Exception as e:
print(f"Erro ao carregar dados: {e}")
return None
# Todas as variáveis disponíveis
all_features = [
'Gr Liv Area', # Área habitável
'Overall Qual', # Qualidade geral
'Year Built', # Ano de construção
'Total Bsmt SF', # Área do porão
'Garage Cars', # Capacidade da garagem
'Full Bath', # Banheiros completos
'TotRms AbvGrd', # Total de cômodos
'Fireplaces', # Lareiras
'Overall Cond', # Condição geral
'1st Flr SF', # Área do primeiro andar
'2nd Flr SF', # Área do segundo andar
'Bedroom AbvGr', # Quartos acima do solo
'Kitchen Qual', # Qualidade da cozinha
'Garage Area', # Área da garagem
'Wood Deck SF', # Área do deck de madeira
'Open Porch SF', # Área da varanda aberta
'Lot Area', # Área do lote
'Year Remod/Add', # Ano de reforma
'Mas Vnr Area', # Área de revestimento
'BsmtFin SF 1' # Área acabada do porão 1
]
def preparar_dados(variaveis_selecionadas):
"""Prepara os dados para análise com as variáveis selecionadas"""
cache_key = f'prepared_data_{str(sorted(variaveis_selecionadas))}'
if cache_key in _data_cache:
return _data_cache[cache_key]
df = load_data()
if df is None:
return None
# Verifica se todas as variáveis selecionadas existem no dataset
variaveis_validas = [var for var in variaveis_selecionadas if var in df.columns]
variaveis_validas.append('SalePrice') # Sempre inclui o target
df_model = df[variaveis_validas].copy()
# Tratamento de valores ausentes
for col in variaveis_validas:
if col != 'SalePrice' and df_model[col].isnull().sum() > 0:
if df_model[col].dtype in ['float64', 'int64']:
df_model[col].fillna(df_model[col].median(), inplace=True)
else:
df_model[col].fillna(df_model[col].mode()[0] if len(df_model[col].mode()) > 0 else 0, inplace=True)
_data_cache[cache_key] = df_model
return df_model
def plot_distribuicao_preco(df):
"""Plota a distribuição do preço de venda"""
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
# Histograma
axes[0].hist(df['SalePrice'], bins=50, edgecolor='black', alpha=0.7, color='skyblue')
axes[0].set_xlabel('Preço de Venda ($)')
axes[0].set_ylabel('Frequência')
axes[0].set_title('Distribuição do Preço de Venda')
axes[0].axvline(df['SalePrice'].mean(), color='red', linestyle='--',
label=f'Média: ${df["SalePrice"].mean():,.0f}')
axes[0].axvline(df['SalePrice'].median(), color='green', linestyle='--',
label=f'Mediana: ${df["SalePrice"].median():,.0f}')
axes[0].legend(fontsize=8)
# Boxplot
axes[1].boxplot(df['SalePrice'], vert=True)
axes[1].set_ylabel('Preço de Venda ($)')
axes[1].set_title('Boxplot do Preço de Venda')
axes[1].grid(True, alpha=0.3)
plt.tight_layout()
return fig
def plot_correlacao(df):
"""Plota a matriz de correlação"""
# Calcula apenas correlações numéricas
numeric_df = df.select_dtypes(include=[np.number])
correlation_matrix = numeric_df.corr()
fig, ax = plt.subplots(figsize=(10, 8))
sns.heatmap(correlation_matrix, annot=True, fmt='.2f', cmap='coolwarm',
center=0, square=True, linewidths=0.5, cbar_kws={"shrink": 0.8}, ax=ax)
ax.set_title('Matriz de Correlação - Variáveis Selecionadas', fontsize=12, fontweight='bold')
plt.tight_layout()
return fig
def plot_relacoes_bivariadas(df, variaveis_selecionadas):
"""Plota as relações bivariadas com SalePrice"""
# Filtra apenas variáveis numéricas para os scatter plots
numeric_vars = [var for var in variaveis_selecionadas if var in df.select_dtypes(include=[np.number]).columns]
n_vars = len(numeric_vars)
if n_vars == 0:
# Gráfico vazio se não há variáveis numéricas
fig, ax = plt.subplots(figsize=(8, 6))
ax.text(0.5, 0.5, 'Nenhuma variável numérica selecionada',
ha='center', va='center', transform=ax.transAxes, fontsize=12)
ax.set_title('Relações com Preço de Venda')
return fig
n_cols = 3
n_rows = (n_vars + n_cols - 1) // n_cols
fig, axes = plt.subplots(n_rows, n_cols, figsize=(15, 4 * n_rows))
# Garante que axes seja sempre uma lista 2D
if n_rows == 1 and n_cols == 1:
axes = np.array([axes])
elif n_rows == 1:
axes = axes
else:
axes = axes.ravel()
for idx, feature in enumerate(numeric_vars):
if idx >= len(axes):
break
axes[idx].scatter(df[feature], df['SalePrice'], alpha=0.5, s=8, color='steelblue')
axes[idx].set_xlabel(feature)
axes[idx].set_ylabel('SalePrice ($)')
axes[idx].set_title(f'SalePrice vs {feature}', fontsize=10)
# Adicionar linha de tendência apenas se há dados suficientes
if len(df[feature].dropna()) > 1:
valid_data = df[[feature, 'SalePrice']].dropna()
if len(valid_data) > 1:
z = np.polyfit(valid_data[feature], valid_data['SalePrice'], 1)
p = np.poly1d(z)
axes[idx].plot(valid_data[feature], p(valid_data[feature]), "r--", alpha=0.8, linewidth=1.5)
# Correlação
corr = valid_data[feature].corr(valid_data['SalePrice'])
axes[idx].text(0.05, 0.95, f'r = {corr:.3f}', transform=axes[idx].transAxes,
verticalalignment='top', fontsize=9,
bbox=dict(boxstyle='round', facecolor='white', alpha=0.8))
# Remove eixos extras
for idx in range(len(numeric_vars), len(axes)):
if idx < len(axes):
fig.delaxes(axes[idx])
plt.tight_layout()
return fig
def plot_residuos(y_test, y_pred, modelo_tipo):
"""Plota análise de resíduos"""
residuos = y_test - y_pred
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
# Resíduos vs Preditos
axes[0].scatter(y_pred, residuos, alpha=0.5, color='steelblue', s=20)
axes[0].axhline(y=0, color='red', linestyle='--', linewidth=1)
axes[0].set_xlabel('Valores Preditos')
axes[0].set_ylabel('Resíduos')
axes[0].set_title(f'Resíduos vs Preditos - {modelo_tipo}')
axes[0].grid(True, alpha=0.3)
# QQ-Plot dos resíduos
stats.probplot(residuos, dist="norm", plot=axes[1])
axes[1].set_title(f'QQ-Plot dos Resíduos - {modelo_tipo}')
plt.tight_layout()
return fig
def treinar_modelo(variaveis_selecionadas, usar_modelo_robusto=False):
"""Treina o modelo de regressão com as variáveis selecionadas"""
cache_key = f'model_{str(sorted(variaveis_selecionadas))}_{usar_modelo_robusto}'
if cache_key in _data_cache:
return _data_cache[cache_key]
df_model = preparar_dados(variaveis_selecionadas)
if df_model is None:
return None
# Remove colunas não numéricas para o modelo
X = df_model[variaveis_selecionadas].select_dtypes(include=[np.number])
y = df_model['SalePrice']
# Verifica se há dados suficientes
if len(X.columns) == 0 or len(X) < 10:
return None
# Divisão treino/teste
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
# Escalonamento
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
if usar_modelo_robusto:
# Random Forest (modelo robusto)
modelo = RandomForestRegressor(n_estimators=100, random_state=42)
modelo_tipo = "Random Forest"
else:
# Regressão Linear
modelo = LinearRegression()
modelo_tipo = "Regressão Linear"
modelo.fit(X_train_scaled, y_train)
y_pred = modelo.predict(X_test_scaled)
# Métricas
mse = mean_squared_error(y_test, y_pred)
mae = mean_absolute_error(y_test, y_pred)
r2 = r2_score(y_test, y_pred)
# Feature importance para Random Forest
if usar_modelo_robusto:
importances = modelo.feature_importances_
feature_importance_df = pd.DataFrame({
'Feature': X.columns,
'Importance': importances
}).sort_values('Importance', ascending=False)
else:
feature_importance_df = None
resultados = {
'modelo': modelo,
'scaler': scaler,
'X_columns': X.columns.tolist(),
'X_test': X_test,
'y_test': y_test,
'y_pred': y_pred,
'metricas': {
'MSE': f"{mse:,.2f}",
'RMSE': f"${np.sqrt(mse):,.2f}",
'MAE': f"${mae:,.2f}",
'R²': f"{r2:.4f}"
},
'modelo_tipo': modelo_tipo,
'feature_importance': feature_importance_df
}
_data_cache[cache_key] = resultados
return resultados
def plot_importance_features(feature_importance_df):
"""Plota a importância das features para Random Forest"""
if feature_importance_df is None or len(feature_importance_df) == 0:
fig, ax = plt.subplots(figsize=(8, 4))
ax.text(0.5, 0.5, 'Nenhuma importância disponível',
ha='center', va='center', transform=ax.transAxes, fontsize=12)
ax.set_title('Importância das Variáveis')
return fig
fig, ax = plt.subplots(figsize=(10, 5))
sns.barplot(data=feature_importance_df, x='Importance', y='Feature', ax=ax)
ax.set_title('Importância das Variáveis - Random Forest', fontsize=12)
ax.set_xlabel('Importância')
plt.tight_layout()
return fig
def analise_completa(variaveis_selecionadas, usar_modelo_robusto=False):
"""Executa análise completa e retorna todos os gráficos e métricas"""
if not variaveis_selecionadas:
raise gr.Error("❌ Selecione pelo menos uma variável para análise.")
df_model = preparar_dados(variaveis_selecionadas)
if df_model is None:
raise gr.Error("❌ Erro ao carregar os dados. Verifique sua conexão com a internet.")
# Gráficos de análise exploratória
fig_distribuicao = plot_distribuicao_preco(df_model)
fig_correlacao = plot_correlacao(df_model)
fig_relacoes = plot_relacoes_bivariadas(df_model, variaveis_selecionadas)
# Treinar modelo
resultados = treinar_modelo(variaveis_selecionadas, usar_modelo_robusto)
if resultados is None:
raise gr.Error("❌ Erro ao treinar o modelo. Verifique as variáveis selecionadas.")
# Gráfico de resíduos
fig_residuos = plot_residuos(
resultados['y_test'],
resultados['y_pred'],
resultados['modelo_tipo']
)
# Feature importance (apenas para Random Forest)
if usar_modelo_robusto and resultados['feature_importance'] is not None:
fig_importance = plot_importance_features(resultados['feature_importance'])
else:
# Cria um gráfico vazio quando não há feature importance
fig_importance, ax = plt.subplots(figsize=(8, 4))
ax.text(0.5, 0.5, 'Feature Importance disponível apenas para Random Forest',
ha='center', va='center', transform=ax.transAxes, fontsize=12)
ax.set_title('Importância das Variáveis')
plt.tight_layout()
# Estatísticas descritivas
stats_descritivas = df_model['SalePrice'].describe().reset_index()
stats_descritivas.columns = ['Estatística', 'Valor']
stats_descritivas['Valor'] = stats_descritivas['Valor'].apply(
lambda x: f"${x:,.2f}" if isinstance(x, (int, float)) else str(x)
)
# Correlações com SalePrice (apenas numéricas)
numeric_df = df_model.select_dtypes(include=[np.number])
correlacoes = numeric_df.corr()['SalePrice'].sort_values(ascending=False).reset_index()
correlacoes.columns = ['Variável', 'Correlação']
correlacoes['Correlação'] = correlacoes['Correlação'].round(4)
return (fig_distribuicao, fig_correlacao, fig_relacoes, fig_residuos,
fig_importance, resultados['metricas'], stats_descritivas, correlacoes,
resultados['modelo_tipo'])
def predizer_preco(variaveis_selecionadas, inputs_dict, usar_modelo_robusto=False):
"""Prediz o preço baseado nas variáveis selecionadas e inputs"""
try:
if not variaveis_selecionadas:
return 0
resultados = treinar_modelo(variaveis_selecionadas, usar_modelo_robusto)
if resultados is None:
return 0
# Prepara os dados de entrada na ordem correta
input_data = []
for col in resultados['X_columns']:
input_data.append(inputs_dict.get(col, 0))
input_array = np.array([input_data])
# Escalonar e prever
input_scaled = resultados['scaler'].transform(input_array)
preco_predito = resultados['modelo'].predict(input_scaled)[0]
return max(0, preco_predito)
except Exception as e:
print(f"Erro na predição: {e}")
return 0
# Interface Gradio
with gr.Blocks(
title="Análise de Dados Imobiliários - Ames Housing",
css="""
.gradio-container {
max-width: 1400px !important;
}
.container {
max-width: 1400px;
margin: auto;
}
"""
) as demo:
gr.Markdown("""
# 🏠 Análise de Dados Imobiliários - Ames Housing
**Análise completa utilizando Machine Learning para prever preços de propriedades em Ames, Iowa**
*Dataset: Ames Housing (2930 propriedades, 82 variáveis)*
""")
with gr.Row():
with gr.Column(scale=1):
gr.Markdown("### 🎯 Seleção de Variáveis")
# Checkboxes para seleção de variáveis
variaveis_checkboxes = []
with gr.Group():
gr.Markdown("**Selecione as variáveis para análise:**")
for i, feature in enumerate(all_features):
checkbox = gr.Checkbox(
label=feature,
value=i < 9, # Primeiras 9 selecionadas por padrão
elem_id=f"checkbox_{feature}"
)
variaveis_checkboxes.append(checkbox)
with gr.Row():
usar_robusto = gr.Checkbox(
label="Usar Modelo Robusto (Random Forest)",
value=False,
info="Random Forest geralmente tem melhor performance"
)
btn_analisar = gr.Button(
"🔍 Executar Análise Completa",
variant="primary",
size="lg"
)
with gr.Tabs():
with gr.TabItem("📊 Análise Exploratória"):
with gr.Row():
plot_dist = gr.Plot(label="Distribuição do Preço")
with gr.Row():
plot_corr = gr.Plot(label="Matriz de Correlação")
with gr.Row():
plot_rel = gr.Plot(label="Relações com Preço")
with gr.TabItem("🤖 Modelo & Métricas"):
with gr.Row():
tipo_modelo = gr.Textbox(
label="🎯 Tipo de Modelo",
interactive=False,
value="Clique em 'Executar Análise' para começar"
)
with gr.Row():
metricas = gr.JSON(
label="📈 Métricas de Desempenho",
value={"Status": "Aguardando análise..."}
)
with gr.Row():
with gr.Column():
stats_desc = gr.DataFrame(
label="📋 Estatísticas Descritivas - SalePrice",
value=[["Aguardando...", "Aguardando..."]],
headers=["Estatística", "Valor"]
)
with gr.Column():
correlacoes = gr.DataFrame(
label="🔗 Correlações com SalePrice",
value=[["Aguardando...", "Aguardando..."]],
headers=["Variável", "Correlação"]
)
with gr.TabItem("📉 Validação do Modelo"):
with gr.Row():
plot_res = gr.Plot(label="Análise de Resíduos")
with gr.Row():
plot_imp = gr.Plot(label="Importância das Variáveis")
with gr.TabItem("🎲 Simular Predição"):
gr.Markdown("""
### 💰 Simule o Preço de uma Propriedade
Ajuste as características abaixo para prever o preço de venda:
""")
# Inputs dinâmicos para predição
input_components = {}
with gr.Row():
columns = [gr.Column() for _ in range(3)]
for i, feature in enumerate(all_features):
col_idx = i % 3
with columns[col_idx]:
if feature in ['Overall Qual', 'Overall Cond']:
input_comp = gr.Slider(1, 10, value=5, step=1, label=feature)
elif feature in ['Garage Cars', 'Full Bath', 'Fireplaces', 'Bedroom AbvGr', 'TotRms AbvGrd']:
input_comp = gr.Slider(0, 10, value=2, step=1, label=feature)
elif 'Year' in feature:
input_comp = gr.Slider(1800, 2020, value=1990, label=feature)
elif 'Area' in feature or 'SF' in feature:
input_comp = gr.Slider(0, 5000, value=1000, label=f"{feature} (sq ft)")
else:
input_comp = gr.Slider(0, 5000, value=1000, label=feature)
input_components[feature] = input_comp
with gr.Row():
btn_predizer = gr.Button("💰 Calcular Preço Predito", variant="secondary", size="lg")
predicao = gr.Number(
label="🏷️ Preço Predito ($)",
interactive=False,
value=0
)
# Funções de callback
def get_selected_variables(*checkbox_values):
"""Retorna a lista de variáveis selecionadas"""
selected = []
for i, is_selected in enumerate(checkbox_values):
if is_selected:
selected.append(all_features[i])
return selected
def executar_analise(*args):
try:
# Os primeiros len(all_features) argumentos são os checkboxes
checkbox_values = args[:len(all_features)]
usar_robusto = args[len(all_features)]
variaveis_selecionadas = get_selected_variables(*checkbox_values)
if not variaveis_selecionadas:
# Retorna valores padrão se nenhuma variável selecionada
empty_fig, ax = plt.subplots(figsize=(8, 4))
ax.text(0.5, 0.5, 'Selecione variáveis para ver a análise',
ha='center', va='center', transform=ax.transAxes, fontsize=12)
ax.set_title('Aguardando seleção')
plt.tight_layout()
empty_data = [["Selecione variáveis", "Selecione variáveis"]]
empty_json = {"Status": "Selecione variáveis para análise"}
return (empty_fig, empty_fig, empty_fig, empty_fig, empty_fig,
empty_json, empty_data, empty_data, "Nenhum modelo")
resultados = analise_completa(variaveis_selecionadas, usar_robusto)
# Garante que retornamos exatamente 9 valores
output = list(resultados)
return output
except Exception as e:
# Retorna gráficos de erro em caso de falha
error_fig, ax = plt.subplots(figsize=(8, 4))
ax.text(0.5, 0.5, f'Erro: {str(e)}',
ha='center', va='center', transform=ax.transAxes, fontsize=10)
ax.set_title('Erro na análise')
plt.tight_layout()
error_data = [["Erro", str(e)]]
error_json = {"Erro": str(e)}
return (error_fig, error_fig, error_fig, error_fig, error_fig,
error_json, error_data, error_data, "Erro")
def predizer_preco_wrapper(*args):
try:
# Os primeiros len(all_features) argumentos são os checkboxes
checkbox_values = args[:len(all_features)]
# Os próximos len(all_features) argumentos são os valores dos inputs
input_values = args[len(all_features):2*len(all_features)]
usar_robusto = args[2*len(all_features)]
variaveis_selecionadas = get_selected_variables(*checkbox_values)
if not variaveis_selecionadas:
return 0
# Cria dicionário com os inputs
inputs_dict = {}
for i, feature in enumerate(all_features):
inputs_dict[feature] = input_values[i]
return predizer_preco(variaveis_selecionadas, inputs_dict, usar_robusto)
except Exception as e:
print(f"Erro na predição: {e}")
return 0
# Conectando os callbacks
btn_analisar.click(
fn=executar_analise,
inputs=variaveis_checkboxes + [usar_robusto],
outputs=[plot_dist, plot_corr, plot_rel, plot_res, plot_imp,
metricas, stats_desc, correlacoes, tipo_modelo]
)
btn_predizer.click(
fn=predizer_preco_wrapper,
inputs=variaveis_checkboxes + list(input_components.values()) + [usar_robusto],
outputs=[predicao]
)
# Informações adicionais
gr.Markdown("""
## 📝 Sobre o Projeto
Desenvolvido como parte do Programa de Pós-graduação em Computação Aplicada - PPCA/UnB
**Dataset:** [Ames Housing](http://jse.amstat.org/v19n3/decock/AmesHousing.txt)
**Interpretação das Métricas:**
- **R²**: Proporção da variância explicada (0-1, quanto maior melhor)
- **RMSE**: Raiz do erro quadrático médio (em $, quanto menor melhor)
- **MAE**: Erro absoluto médio (em $, quanto menor melhor)
**💡 Dica:** Comece selecionando 3-5 variáveis para uma análise mais rápida.
""")
# Configuração para Spaces
if __name__ == "__main__":
demo.launch(debug=True)