# streamlit_dashboard_unificado.py import streamlit as st import pandas as pd import numpy as np import statsmodels.api as sm from statsmodels.formula.api import ols from scipy.stats import shapiro, levene, kruskal, anderson from statsmodels.stats.outliers_influence import variance_inflation_factor from sklearn.metrics import mean_squared_error, mean_absolute_error import matplotlib.pyplot as plt import seaborn as sns # Configuração geral do Streamlit st.set_page_config(layout="wide", page_title="Dashboard Imobiliário Integrado") # --- Funções de Carregamento de Dados --- @st.cache_data def load_data_anova(): """Carrega o Ames Housing Dataset e retorna para o módulo ANOVA.""" urls_tentativas = [ "https://raw.githubusercontent.com/Viniciusalgueiro/Ameshousing/refs/heads/main/AmesHousing.csv" ] df = None url_carregada = "" for url in urls_tentativas: try: df = pd.read_csv(url) url_carregada = url break except Exception: continue if df is None: return None, None, [], [] df.columns = df.columns.str.replace('[^A-Za-z0-9_]+', '', regex=True).str.lower() coluna_preco_nome = None if 'saleprice' in df.columns: coluna_preco_nome = 'saleprice' elif 'sale_price' in df.columns: df.rename(columns={'sale_price': 'saleprice'}, inplace=True) coluna_preco_nome = 'saleprice' if coluna_preco_nome: df[coluna_preco_nome] = pd.to_numeric(df[coluna_preco_nome], errors='coerce') df.dropna(subset=[coluna_preco_nome], inplace=True) # Identificar colunas categóricas potenciais (inclui numéricas discretas < 20 níveis) colunas_categoricas_potenciais = df.select_dtypes(include=['object']).columns.tolist() colunas_numericas_discretas = [ col for col in df.select_dtypes(include=np.number).columns if df[col].nunique() < 20 and col != coluna_preco_nome ] colunas_categoricas_potenciais.extend(colunas_numericas_discretas) colunas_categoricas_potenciais = sorted( list(set(col for col in colunas_categoricas_potenciais if col != coluna_preco_nome)) ) return df, coluna_preco_nome, colunas_categoricas_potenciais, df.columns.tolist() @st.cache_data def load_data_reg(): """Carrega o Ames Housing Dataset e retorna para o módulo de Regressão.""" fixed_url = "https://raw.githubusercontent.com/Viniciusalgueiro/Ameshousing/refs/heads/main/AmesHousing.csv" try: df = pd.read_csv(fixed_url) url_carregada = fixed_url except Exception as e: return None, None, [], [], [] st.success(f"Dataset carregado com sucesso de: {url_carregada} (Shape: {df.shape})") df.columns = df.columns.str.replace('[^A-Za-z0-9_]+', '', regex=True).str.lower() coluna_preco_nome = None possible_price_cols = ['saleprice', 'sale_price', 'price'] for col_candidate in possible_price_cols: if col_candidate in df.columns: if coluna_preco_nome is None: coluna_preco_nome = 'saleprice' if col_candidate != 'saleprice': df.rename(columns={col_candidate: 'saleprice'}, inplace=True) break if coluna_preco_nome is None: for col_candidate in df.columns: if 'price' in col_candidate and 'sale' in col_candidate: coluna_preco_nome = 'saleprice' if col_candidate != 'saleprice': df.rename(columns={col_candidate: 'saleprice'}, inplace=True) st.warning(f"Coluna de preço identificada como '{col_candidate}' e renomeada para 'saleprice'.") break if coluna_preco_nome is None: return df, None, [], [], df.columns.tolist() df[coluna_preco_nome] = pd.to_numeric(df[coluna_preco_nome], errors='coerce') df.dropna(subset=[coluna_preco_nome], inplace=True) # Colunas contínuas e categóricas potenciais vars_sempre_continuas_para_reg = [ 'grlivarea', 'overallqual', 'yearbuilt', 'totalbsmtsf', 'lotarea', 'masvnrarea', 'bsmtfinsf1', 'bsmtunfsf', '1stflrsf', '2ndflrsf', 'garagearea', 'wooddecksf', 'openporchsf', 'yrsold', 'lotfrontage', 'garageyrblt', 'screensf', 'poolarea', 'miscval', 'mosold', 'lowqualfinsf', 'bsmthalfbath', 'fullbath', 'halfbath', 'bedroomabvgr', 'kitchenabvgr', 'totrmsabvgrd', 'fireplaces', 'garagecars' ] colunas_categoricas_potenciais = df.select_dtypes(include=['object', 'category']).columns.tolist() colunas_numericas_discretas = [ col for col in df.select_dtypes(include=np.number).columns if df[col].nunique() < 20 and col != coluna_preco_nome and col not in vars_sempre_continuas_para_reg ] colunas_categoricas_potenciais.extend(colunas_numericas_discretas) colunas_categoricas_potenciais = sorted( list(set(col for col in colunas_categoricas_potenciais if col in df.columns and col != coluna_preco_nome)) ) colunas_continuas_potenciais = [ col for col in df.select_dtypes(include=np.number).columns if ( col not in colunas_categoricas_potenciais or col in vars_sempre_continuas_para_reg ) and col != coluna_preco_nome ] colunas_continuas_potenciais = sorted(list(set(col for col in colunas_continuas_potenciais if col in df.columns))) return df, coluna_preco_nome, colunas_categoricas_potenciais, colunas_continuas_potenciais, df.columns.tolist() # --- Funções de ANOVA --- def perform_anova_for_variable(df_analysis, var_cat, col_preco): """Executa ANOVA e testes de pressupostos para uma variável categórica.""" results = {"var_cat": var_cat, "plots": {}} df_var = df_analysis[[var_cat, col_preco]].copy() if df_var[var_cat].dtype != 'object' and not pd.api.types.is_categorical_dtype(df_var[var_cat]): df_var[var_cat] = df_var[var_cat].astype('category') df_var.dropna(inplace=True) if df_var[var_cat].nunique() < 2 or len(df_var) < 10: results["error"] = "Dados insuficientes ou poucos níveis após limpeza." return results formula = f'{col_preco} ~ C({var_cat})' try: modelo = ols(formula, data=df_var).fit() results["anova_table"] = sm.stats.anova_lm(modelo, typ=2) if f'C({var_cat})' in results["anova_table"].index: results["p_valor_anova"] = results["anova_table"].loc[f'C({var_cat})', 'PR(>F)'] else: results["p_valor_anova"] = results["anova_table"]['PR(>F)'].iloc[0] residuos = modelo.resid results["residuos_count"] = len(residuos) normalidade_ok = False if len(residuos) >= 3: if len(residuos) <= 5000: stat_shapiro, p_shapiro = shapiro(residuos) results["shapiro_test"] = (stat_shapiro, p_shapiro) if p_shapiro >= 0.05: normalidade_ok = True else: ad_result = anderson(residuos) results["anderson_test"] = ad_result sig_level_idx = ad_result.significance_level.tolist().index(5.0) if ad_result.statistic < ad_result.critical_values[sig_level_idx]: normalidade_ok = True results["normalidade_ok"] = normalidade_ok # Plots de normalidade fig_norm, ax_norm = plt.subplots(1, 2, figsize=(10, 4)) if len(residuos) > 1: sns.histplot(residuos, kde=True, ax=ax_norm[0], stat="density", bins=30) ax_norm[0].set_title(f'Histograma Resíduos ({var_cat})', fontsize=10) sm.qqplot(residuos, line='s', ax=ax_norm[1], markerfacecolor="skyblue", markeredgecolor="dodgerblue", alpha=0.7) ax_norm[1].set_title(f'Q-Q Plot Resíduos ({var_cat})', fontsize=10) else: ax_norm[0].text(0.5, 0.5, "Poucos dados", ha='center', va='center') ax_norm[1].text(0.5, 0.5, "Poucos dados", ha='center', va='center') plt.tight_layout() results["plots"]["normalidade"] = fig_norm # Teste de homocedasticidade (Levene) homocedasticidade_ok = False grupos = [df_var[col_preco][df_var[var_cat] == categoria].dropna() for categoria in df_var[var_cat].unique()] grupos_validos = [g for g in grupos if len(g) >= 2] if len(grupos_validos) >= 2: stat_levene, p_levene = levene(*grupos_validos) results["levene_test"] = (stat_levene, p_levene) if p_levene >= 0.05: homocedasticidade_ok = True results["homocedasticidade_ok"] = homocedasticidade_ok # Teste de Kruskal-Wallis (se necessário) if not normalidade_ok or not homocedasticidade_ok: if len(grupos_validos) >= 2: stat_kruskal, p_kruskal = kruskal(*grupos_validos) results["kruskal_test"] = (stat_kruskal, p_kruskal) # Boxplot fig_box, ax_box = plt.subplots(figsize=(10, 5)) unique_cats = df_var[var_cat].nunique() order_boxplot = None if 5 < unique_cats < 50: try: order_boxplot = df_var.groupby(var_cat)[col_preco].median().sort_values().index except Exception: order_boxplot = df_var[var_cat].unique() sns.boxplot(x=var_cat, y=col_preco, data=df_var, order=order_boxplot, ax=ax_box, palette="viridis") ax_box.set_title(f'Distribuição de {col_preco} por {var_cat}', fontsize=12) if unique_cats > 10: plt.setp(ax_box.get_xticklabels(), rotation=45, ha='right', fontsize=8) else: plt.setp(ax_box.get_xticklabels(), fontsize=9) plt.tight_layout() results["plots"]["boxplot"] = fig_box except Exception as e: results["error"] = str(e) return results # --- Função de Regressão Linear --- def run_linear_regression_analysis(df_original, target_column_name, selected_cont_vars, selected_cat_vars): """Executa a análise de regressão linear.""" results_regression = {} df_reg = df_original.copy() all_selected_vars = selected_cont_vars + selected_cat_vars if not all_selected_vars: return {"error": "Nenhuma variável explicativa selecionada."} actual_selected_vars = [var for var in all_selected_vars if var in df_reg.columns] missing_vars = [var for var in all_selected_vars if var not in df_reg.columns] if missing_vars: st.warning(f"Variáveis ignoradas (não encontradas): {missing_vars}") selected_cont_vars = [v for v in selected_cont_vars if v in actual_selected_vars] selected_cat_vars = [v for v in selected_cat_vars if v in actual_selected_vars] all_selected_vars = selected_cont_vars + selected_cat_vars if not all_selected_vars: return {"error": "Nenhuma variável válida para regressão após filtragem."} df_reg = df_reg[all_selected_vars + [target_column_name]].copy() df_reg.dropna(subset=all_selected_vars + [target_column_name], inplace=True) if df_reg.empty: return {"error": "DataFrame vazio após remoção de NaNs."} if df_reg[target_column_name].min() > 0: df_reg['log_saleprice'] = np.log(df_reg[target_column_name]) else: df_reg['log_saleprice'] = np.log1p(df_reg[target_column_name]) new_target_column = 'log_saleprice' transformed_continuous_vars = [] for var in selected_cont_vars: log_var_name = f'log_{var}' if var in df_reg.columns: if var in ['overallqual', 'yearbuilt', 'yrsold', 'mosold', 'fireplaces', 'garagecars', 'bsmthalfbath', 'fullbath', 'halfbath', 'bedroomabvgr', 'kitchenabvgr', 'totrmsabvgrd']: transformed_continuous_vars.append(var) elif df_reg[var].min() > 0: df_reg[log_var_name] = np.log(df_reg[var]) transformed_continuous_vars.append(log_var_name) else: df_reg[log_var_name] = np.log1p(df_reg[var]) transformed_continuous_vars.append(log_var_name) processed_categorical_vars = [] for cat_var in selected_cat_vars: if cat_var in df_reg.columns: if df_reg[cat_var].dtype not in ['object', 'category']: df_reg[cat_var] = df_reg[cat_var].astype('category') processed_categorical_vars.append(cat_var) df_reg = pd.get_dummies(df_reg, columns=processed_categorical_vars, drop_first=True, dtype=float) final_explanatory_vars = [var for var in transformed_continuous_vars if var in df_reg.columns] for cat_orig in processed_categorical_vars: dummy_cols = [col for col in df_reg.columns if col.startswith(f"{cat_orig}_")] final_explanatory_vars.extend(dummy_cols) final_explanatory_vars = sorted(set(final_explanatory_vars)) final_explanatory_vars = [ var for var in final_explanatory_vars if var in df_reg.columns and df_reg[var].isnull().sum() < len(df_reg) and df_reg[var].std(skipna=True) > 0 ] if not final_explanatory_vars: return {"error": "Nenhuma variável explicativa válida após pré-processamento."} X = df_reg[final_explanatory_vars] y = df_reg[new_target_column] X = sm.add_constant(X, has_constant='add') try: model = sm.OLS(y, X).fit() results_regression['model_summary_obj'] = model.summary() results_regression['model_object'] = model except Exception as e: return {"error": f"Erro ao ajustar modelo: {str(e)}. Variáveis em X: {X.columns.tolist()}"} fitted_values = model.fittedvalues r_squared = model.rsquared adj_r_squared = model.rsquared_adj rmse_log = np.sqrt(mean_squared_error(y, fitted_values)) mae_log = mean_absolute_error(y, fitted_values) results_regression['performance_metrics'] = { 'R-squared': r_squared, 'Adjusted R-squared': adj_r_squared, 'RMSE (log)': rmse_log, 'MAE (log)': mae_log } # Interpretação de coeficientes coeff_notes = """ **Interpretação dos Coeficientes (`log_saleprice`):** - Variáveis contínuas transformadas em log (ex: `log_grlivarea`): Aumento de 1% na variável resulta em ~[coef * 1]% de variação no `saleprice`. - Variáveis contínuas não transformadas (ex: `overallqual`): Aumento unitário resulta em ~[(exp(coef)-1)*100]% de variação no `saleprice`. - Dummies (ex: `neighborhood_stonebr`): Presença da categoria resulta em ~[(exp(coef)-1)*100]% de variação no `saleprice`. **Significância:** Verifique `P>|t|` < 0.05. """ results_regression['coefficients_interpretation_notes'] = coeff_notes # Recomendações práticas recommendations_data = [] if hasattr(model, 'params') and hasattr(model, 'pvalues'): params_df = pd.DataFrame({'Coeficiente': model.params, 'P-valor': model.pvalues}) significant_params = params_df[params_df['P-valor'] < 0.05] if 'const' in significant_params.index: significant_params = significant_params.drop('const') if not significant_params.empty: for var, row in significant_params.iterrows(): coef = row['Coeficiente'] is_log = var.startswith("log_") and var in transformed_continuous_vars original_var = var.replace("log_", "") if is_log else var display_name = original_var.replace('_', ' ').title() # Dummies is_dummy = False for cat_orig in selected_cat_vars: if var.startswith(f"{cat_orig}_"): is_dummy = True parts = var.split('_', 1) cat_display = parts[1].replace('_', ' ').title() if len(parts) > 1 else "Categoria" display_name = f"{cat_orig.replace('_', ' ').title()}: {cat_display}" break if is_log: tipo = "Aumento Percentual (elasticidade)" magnitude = f"{coef:.2f}% de variação no preço para 1% de aumento" interpret = f"1% em '{original_var.title()}' causa {coef:.2f}% no preço." else: percentage_change = (np.exp(coef) - 1) * 100 tipo = "Aumento Percentual (nível ou dummy)" magnitude = f"{percentage_change:.2f}% de variação no preço" if is_dummy: interpret = f"Presença em '{display_name}' causa {percentage_change:.2f}% no preço." else: interpret = f"Aumento unitário em '{display_name}' causa {percentage_change:.2f}% no preço." recommendations_data.append({ "Variável": display_name, "Tipo de Impacto": tipo, "Interpretação": interpret, "Magnitude Estimada": magnitude }) else: recommendations_data.append({ "Variável": "N/A", "Tipo de Impacto": "-", "Interpretação": "Nenhum coeficiente significativo.", "Magnitude Estimada": "-" }) else: recommendations_data.append({ "Variável": "N/A", "Tipo de Impacto": "-", "Interpretação": "Modelo não ajustado.", "Magnitude Estimada": "-" }) results_regression['practical_recommendations_table_data'] = recommendations_data return results_regression # --- Lógica de Navegação via Botões --- # Inicializa o estado de página (ANOVA ou REGRESSAO) if 'page' not in st.session_state: st.session_state.page = 'ANOVA' # Botões de navegação col1, col2 = st.columns([1, 1]) with col1: if st.button('📊 ANOVA'): st.session_state.page = 'ANOVA' with col2: if st.button('📈 Regressão'): st.session_state.page = 'REGRESSAO' st.markdown("---") # --- Página ANOVA --- if st.session_state.page == 'ANOVA': st.title("🏠 Dashboard de ANOVA Imobiliária") st.markdown(""" Esta seção permite realizar Análises de Variância (ANOVA) no Ames Housing Dataset, investigando como diferentes variáveis categóricas impactam o preço de venda dos imóveis. """) df_anova, coluna_preco_anova, colunas_categoricas_selecionaveis, todas_colunas_anova = load_data_anova() if df_anova is not None and coluna_preco_anova is not None: st.header("1. Visão Geral dos Dados (ANOVA)") if st.checkbox("Mostrar amostra dos dados"): st.dataframe(df_anova.head()) st.write(f"Total de registros carregados: {len(df_anova)}") st.write(f"Coluna alvo (preço): `{coluna_preco_anova}`") st.sidebar.header("⚙️ Configurações ANOVA") variaveis_selecionadas = st.sidebar.multiselect( "Escolha 1 a 3 variáveis categóricas para ANOVA:", options=colunas_categoricas_selecionaveis, max_selections=3 ) if variaveis_selecionadas: st.header("2. Resultados da Análise ANOVA") st.markdown(f"Analisando **{', '.join(variaveis_selecionadas)}** sobre **{coluna_preco_anova}**.") for var_analisada in variaveis_selecionadas: st.subheader(f"Análise para: `{var_analisada}`") df_analise_var = df_anova[[var_analisada, coluna_preco_anova]].copy() df_analise_var.dropna(subset=[var_analisada, coluna_preco_anova], inplace=True) if df_analise_var.empty or df_analise_var[var_analisada].nunique() < 2: st.warning(f"Dados insuficientes ou poucos níveis para '{var_analisada}'. Pulando.") continue resultados_var = perform_anova_for_variable(df_analise_var, var_analisada, coluna_preco_anova) if "error" in resultados_var: st.error(f"Erro ao analisar '{var_analisada}': {resultados_var['error']}") continue # Tabela ANOVA if "anova_table" in resultados_var: st.markdown("**Tabela ANOVA:**") st.dataframe(resultados_var["anova_table"]) p_anova = resultados_var.get("p_valor_anova") if p_anova is not None: if p_anova < 0.05: st.success(f"✅ Diferença significativa (p-valor: {p_anova:.4e}).") else: st.info(f"ℹ️ Sem diferença significativa (p-valor: {p_anova:.4e}).") # Pressupostos e Testes Alternativos with st.expander("Verificar Pressupostos e Testes Alternativos"): st.markdown("**Normalidade dos Resíduos:**") if "shapiro_test" in resultados_var: stat, p_val = resultados_var["shapiro_test"] st.write(f"Shapiro-Wilk: Estatística={stat:.4f}, P-valor={p_val:.4e}") elif "anderson_test" in resultados_var: ad_res = resultados_var["anderson_test"] st.write(f"Anderson-Darling: Estatística={ad_res.statistic:.4f}") if resultados_var.get("normalidade_ok"): st.success("✅ Resíduos parecem normalmente distribuídos.") else: st.warning("⚠️ Resíduos NÃO parecem normalmente distribuídos.") if "normalidade" in resultados_var["plots"]: st.pyplot(resultados_var["plots"]["normalidade"]) st.markdown("**Homogeneidade das Variâncias (Levene):**") if "levene_test" in resultados_var: stat_l, p_l = resultados_var["levene_test"] st.write(f"Levene: Estatística={stat_l:.4f}, P-valor={p_l:.4e}") if resultados_var.get("homocedasticidade_ok"): st.success("✅ Variâncias homogêneas.") else: st.warning("⚠️ Variâncias NÃO homogêneas.") else: st.write("Levene não pôde ser realizado (insuficiente).") if "kruskal_test" in resultados_var: st.markdown("**Kruskal-Wallis (Não Paramétrico):**") stat_k, p_k = resultados_var["kruskal_test"] st.write(f"Kruskal-Wallis: Estatística={stat_k:.4f}, P-valor={p_k:.4e}") if p_k < 0.05: st.success("✅ Diferença significativa nas medianas.") else: st.info("ℹ️ Sem diferença significativa nas medianas.") # Boxplot if "boxplot" in resultados_var["plots"]: st.markdown("**Distribuição de Preços por Categoria:**") st.pyplot(resultados_var["plots"]["boxplot"]) st.markdown("---") elif not variaveis_selecionadas: st.sidebar.warning("Selecione ao menos uma variável para executar a ANOVA.") st.sidebar.markdown("---") st.sidebar.markdown("Desenvolvido para análise imobiliária.") if variaveis_selecionadas: st.header("3. Insights Gerais e Recomendações ANOVA") with st.expander("Ver Recomendações"): st.markdown(""" ### Como interpretar: - ANOVA ajuda a entender se variáveis categóricas (ex: estilo da casa, ano, telhado) têm associação significativa com o preço. - **Variáveis com p-valor < 0.05**: sugerem diferença estatisticamente significativa. - Use essas informações para ajustar estratégia de precificação e marketing. - Para análise multivariada, prossiga para Regressão Linear Múltipla. """) # NOVO: Inserir campo para a análise fornecida dentro da seção ANOVA st.header("🔎 Análise de Modelo e Recomendações Exemplo") st.markdown(""" Foram escolhidas 6 variáveis, incluindo variáveis contínuas e categóricas: **Contínuas**: - **grlivarea**: Área construída (acima do solo) em pés quadrados. - **overallqual**: Qualidade geral do imóvel (escala de 1 a 10). - **garagecars**: Capacidade da garagem (número de carros). **Categóricas (convertidas em dummies)**: - **neighborhood**: Bairro. - **area_faixa**: Faixas da área construída. 📊 **Resultados do Modelo (Regressão Linear Múltipla)**: - **R² = 0,7535** → O modelo explica aproximadamente 75,35% da variabilidade do preço de venda dos imóveis, o que é considerado muito bom para dados econômicos/sociais. - **RMSE = 39.665** → Erro médio quadrático (desvio padrão dos erros). - **MAE = 27.558** → Erro médio absoluto — em média, o modelo erra cerca de 27 mil dólares por previsão. 🏗️ **Coeficientes Estimados (Impacto das Variáveis)**: - **grlivarea = 52,32** → Cada aumento de 1 pé² na área construída eleva o preço em 52,32 dólares. Ex.: 100 pés² = +5.232 dólares. - **overallqual = 28.190** → Cada ponto a mais na qualidade geral do imóvel (1 a 10) aumenta o preço em 28.190 dólares. Impacto muito significativo. - **garagecars = 19.700** → Cada vaga adicional na garagem eleva o preço em 19.700 dólares. 📏 **Análise dos Pressupostos**: 1. **Linearidade**: ✅ - O gráfico de resíduos vs valores ajustados não apresentou padrões severos, embora haja leve tendência nas caudas, o que é aceitável. 2. **Normalidade dos resíduos**: ❌ - Teste de Shapiro-Wilk: p < 0.0001 → Os resíduos não são normais. - Contudo, com amostras grandes (> 2000 observações), a normalidade dos resíduos não é um pressuposto crítico para a validade dos coeficientes (teorema central do limite). Impacta mais a construção de intervalos de confiança. 3. **Homocedasticidade**: ❌ - O teste de Levene nas ANOVAs sugere heterocedasticidade (variância dos resíduos não é constante). 4. **Multicolinearidade**: ✅ - VIF (Fator de Inflação da Variância) está baixo para as variáveis: - grlivarea: 1,56 - overallqual: 1,85 - garagecars: 1,64 - Sem risco de multicolinearidade. 🔥 **Principais Insights**: - A variável de maior impacto absoluto é **overallqual** (qualidade geral). → Imóveis de melhor acabamento, materiais, design e estado de conservação são fortemente valorizados. - **Garagem** também tem peso elevado: cada vaga adicional vale quase 20 mil dólares. - A **área construída** tem impacto linear relevante: quanto maior o imóvel, maior o preço. - As variáveis de localização (**neighborhood**) e faixa de área (**area_faixa**) também são importantes, mas os coeficientes não foram mostrados diretamente na tabela (porque são muitas dummies). Sabemos, porém, que bairros premium têm preços bem mais altos. 🔄 **Transformações Logarítmicas (Seriam Necessárias?)**: - Dado que há: - Não normalidade dos resíduos; - Heterocedasticidade; - ➡️ Seria adequado testar um modelo log-log (`log(preço) ~ log(área) + outras`) para melhorar os pressupostos. - Vantagem do modelo log-log: - Os coeficientes passam a ser interpretados como variações percentuais. - Ex.: Um coeficiente de 0,15 indica que um aumento de 1% na área resulta em um aumento de 0,15% no preço. **Recomendações**: - Foque em imóveis com alta qualidade de construção. → A cada ponto a mais na qualidade, você valoriza o imóvel em quase 30 mil dólares. - Invista em melhorar vagas de garagem. → Acrescentar uma vaga pode agregar aproximadamente 20 mil dólares ao valor. - Área construída é importante, mas cresce de forma linear. → Estratégia: Ampliações moderadas são rentáveis até certo ponto. - Atenção à localização (**Neighborhood**). → Embora os coeficientes individuais não estejam visíveis no resumo, é sabido que bairros mais valorizados impactam fortemente o preço. Você deve usar essa informação para selecionar imóveis em regiões de maior demanda. - Para modelagem mais robusta: → Recomenda-se testar modelos logarítmicos que podem entregar previsões mais estáveis. - Cuidado com imóveis fora do padrão: → O modelo tem maiores erros nos extremos — imóveis muito caros ou muito baratos. 📌 **Decisão com Confiança**: - Priorize imóveis bem construídos, em bairros consolidados, com boa metragem e pelo menos 2 vagas de garagem. - Foque sua argumentação nesses atributos para justificar o preço do imóvel aos clientes. """) else: if df_anova is None: st.warning("Aguardando carregamento dos dados ou verifique as URLs.") elif coluna_preco_anova is None: st.error(f"Coluna de preço não encontrada. Verifique as colunas: {todas_colunas_anova}") # --- Página REGRESSÃO --- elif st.session_state.page == 'REGRESSAO': st.title("🏠 Dashboard de Regressão Imobiliária") st.markdown(""" Esta seção permite realizar Modelagem Preditiva com Regressão Linear Múltipla no Ames Housing Dataset para entender o impacto de variáveis contínuas e categóricas no preço. """) df_reg, coluna_preco_reg, colunas_categoricas_reg, colunas_continuas_reg, todas_colunas_reg = load_data_reg() if df_reg is not None and coluna_preco_reg is not None: st.header("1. Visão Geral dos Dados (Regressão)") if st.checkbox("Mostrar amostra aleatória dos dados"): st.dataframe(df_reg.sample(min(5, len(df_reg)))) st.write(f"Total registros: {len(df_reg)}") st.write(f"Coluna alvo (preço): `{coluna_preco_reg}`") # Configurações no sidebar st.sidebar.title("⚙️ Configurações Regressão") st.sidebar.header("Seleção de Variáveis") st.sidebar.markdown("Escolha 4 a 6 variáveis (≥1 contínua e ≥1 categórica).") default_cont = ['grlivarea', 'overallqual', 'yearbuilt', 'totalbsmtsf'] valid_default_cont = [v for v in default_cont if v in colunas_continuas_reg] if not valid_default_cont and colunas_continuas_reg: valid_default_cont = colunas_continuas_reg[:1] reg_continuous_vars = st.sidebar.multiselect( "Variáveis Contínuas:", options=colunas_continuas_reg, default=valid_default_cont, key="reg_cont_vars" ) default_cat = ['neighborhood', 'housestyle'] valid_default_cat = [v for v in default_cat if v in colunas_categoricas_reg] if not valid_default_cat and colunas_categoricas_reg: valid_default_cat = colunas_categoricas_reg[:1] reg_categorical_vars = st.sidebar.multiselect( "Variáveis Categóricas:", options=colunas_categoricas_reg, default=valid_default_cat, key="reg_cat_vars" ) total_vars = len(reg_continuous_vars) + len(reg_categorical_vars) valid_selection = True if not (4 <= total_vars <= 6): st.sidebar.warning(f"Selecione entre 4 e 6 variáveis (total atual: {total_vars}).") valid_selection = False if not reg_continuous_vars: st.sidebar.warning("Selecione ao menos 1 variável contínua.") valid_selection = False if not reg_categorical_vars: st.sidebar.warning("Selecione ao menos 1 variável categórica.") valid_selection = False st.markdown("---") st.header("2. Análise Exploratória das Variáveis Selecionadas") if reg_continuous_vars or reg_categorical_vars: if st.checkbox("Mostrar Distribuições das Variáveis Selecionadas", value=False): st.markdown("##### Distribuições das Variáveis Contínuas") for var_cont in reg_continuous_vars: if var_cont in df_reg.columns: fig, ax = plt.subplots(figsize=(6, 3)) sns.histplot(df_reg[var_cont], kde=True, ax=ax, bins=30) ax.set_title(f"Distribuição de {var_cont}") st.pyplot(fig) else: st.warning(f"'{var_cont}' não encontrada para plotar.") st.markdown("##### Contagem das Categorias das Variáveis Categóricas") for var_cat in reg_categorical_vars: if var_cat in df_reg.columns: fig, ax = plt.subplots(figsize=(7, 4)) if df_reg[var_cat].nunique() > 5: sns.countplot(y=df_reg[var_cat], ax=ax, order=df_reg[var_cat].value_counts().index, palette="viridis") else: sns.countplot(x=df_reg[var_cat], ax=ax, order=df_reg[var_cat].value_counts().index, palette="viridis") plt.xticks(rotation=45, ha="right") ax.set_title(f"Contagem de {var_cat}") plt.tight_layout() st.pyplot(fig) else: st.warning(f"'{var_cat}' não encontrada para plotar contagem.") if st.checkbox("Mostrar Mapa de Correlação das Contínuas + Preço", value=False): st.markdown("##### Mapa de Correlação") vars_corr = [var for var in reg_continuous_vars if var in df_reg.columns] + [coluna_preco_reg] if len(vars_corr) > 1: corr_matrix = df_reg[vars_corr].corr() fig_corr, ax_corr = plt.subplots( figsize=(min(10, len(vars_corr) * 1.5), min(8, len(vars_corr) * 1.2)) ) sns.heatmap(corr_matrix, annot=True, cmap="coolwarm", fmt=".2f", linewidths=.5, ax=ax_corr) ax_corr.set_title("Mapa de Correlação") st.pyplot(fig_corr) else: st.info("Selecione ao menos duas variáveis numéricas para o mapa de correlação.") st.markdown("---") if st.button("Executar Regressão Linear", disabled=not valid_selection): with st.spinner("Executando regressão..."): output_reg = run_linear_regression_analysis(df_reg, coluna_preco_reg, reg_continuous_vars, reg_categorical_vars) if "error" in output_reg: st.error(output_reg["error"]) else: st.subheader("Resultados do Modelo de Regressão") model_summary_obj = output_reg.get('model_summary_obj') if model_summary_obj: st.markdown("##### Sumário Geral do Modelo:") sum_table0 = pd.read_html(model_summary_obj.tables[0].as_html(), header=None, index_col=None)[0] st.table(sum_table0.iloc[:, :2].rename(columns={0: "Métrica", 1: "Valor"})) st.table(sum_table0.iloc[:, 2:].rename(columns={2: "Métrica", 3: "Valor"})) st.markdown("##### Coeficientes do Modelo:") sum_table1 = pd.read_html(model_summary_obj.tables[1].as_html(), header=0, index_col=0)[0] st.dataframe(sum_table1.style.format({ "coef": "{:.4f}", "std err": "{:.4f}", "t": "{:.3f}", "P>|t|": "{:.3e}", "[0.025": "{:.4f}", "0.975]": "{:.4f}" })) if len(model_summary_obj.tables) > 2: st.markdown("##### Outras Estatísticas e Notas:") notes_html = model_summary_obj.tables[2].as_html() notes_df = pd.read_html(notes_html, header=None, index_col=None)[0] for i in range(len(notes_df)): line = notes_df.iloc[i].tolist() st.text(" ".join([str(x) for x in line if pd.notna(x)])) st.subheader("Métricas de Desempenho") if 'performance_metrics' in output_reg: metrics_df = pd.DataFrame.from_dict(output_reg['performance_metrics'], orient='index', columns=['Valor']) st.table(metrics_df.style.format("{:.4f}")) st.markdown(""" * **R-squared / R-squared Ajustado:** Variância explicada pelo modelo. * **RMSE (log) / MAE (log):** Erros médios na escala logarítmica. """) st.subheader("Interpretação dos Coeficientes") if 'coefficients_interpretation_notes' in output_reg: st.markdown(output_reg['coefficients_interpretation_notes']) st.subheader("Recomendações Práticas") if 'practical_recommendations_table_data' in output_reg: recom_df = pd.DataFrame(output_reg['practical_recommendations_table_data']) if not recom_df.empty: st.dataframe(recom_df) else: st.info("Nenhuma recomendação gerada (verifique significância).") st.sidebar.markdown("---") st.sidebar.info("Dashboard de Regressão Imobiliária") else: if df_reg is None: st.error("Falha ao carregar dados. Verifique a conexão ou a URL.") elif coluna_preco_reg is None: st.error(f"Coluna de preço não identificada. Colunas disponíveis: {todas_colunas_reg}") else: if not colunas_categoricas_reg and not colunas_continuas_reg: st.error("Nenhuma coluna adequada identificada para regressão.")