import streamlit as st import pandas as pd import numpy as np import matplotlib.pyplot as plt import scipy.stats as stats import statsmodels.api as sm import matplotlib.patches as mpatches from statsmodels.formula.api import ols from sklearn.model_selection import train_test_split from statsmodels.api import OLS, add_constant from sklearn.metrics import r2_score, mean_squared_error, mean_absolute_error from statsmodels.stats.outliers_influence import variance_inflation_factor from statsmodels.stats.diagnostic import het_breuschpagan from scipy.stats import shapiro from scipy.stats import anderson from scipy.stats import kruskal import plotly.express as px import plotly.graph_objects as go from plotly.subplots import make_subplots import seaborn as sns from datetime import datetime import base64 # === CONFIGURAÇÃO DA PÁGINA === st.set_page_config( page_title="📊 Análise Estatística Avançada", page_icon="📊", layout="wide", initial_sidebar_state="expanded" ) # === CSS CUSTOMIZADO === st.markdown(""" """, unsafe_allow_html=True) # === HEADER PRINCIPAL === st.markdown("""

📊 Análise Estatística Avançada

ANOVA & Regressão Linear com Diagnósticos Completos

""", unsafe_allow_html=True) # === CARREGAMENTO DO DATASET === @st.cache_data def load_data(): try: file_path = "src/AmesHousing.csv" return pd.read_csv(file_path) except: st.error("📂 Arquivo não encontrado. Verifique o caminho do dataset.") return None df = load_data() if df is not None: # === INFORMAÇÕES DO DATASET === col1, col2, col3, col4 = st.columns(4) with col1: st.markdown("""

📏 Linhas

{}

""".format(len(df)), unsafe_allow_html=True) with col2: st.markdown("""

📊 Colunas

{}

""".format(len(df.columns)), unsafe_allow_html=True) with col3: st.markdown("""

💰 Preço Médio

${:,.0f}

""".format(df['SalePrice'].mean()), unsafe_allow_html=True) with col4: st.markdown("""

🏠 Preço Mediano

${:,.0f}

""".format(df['SalePrice'].median()), unsafe_allow_html=True) # === PRÉ-VISUALIZAÇÃO DOS DADOS === with st.expander("🔍 Pré-visualização dos Dados", expanded=False): st.dataframe(df.head(10), use_container_width=True) # Gráfico de distribuição do preço fig = px.histogram(df, x='SalePrice', nbins=50, title='📈 Distribuição dos Preços de Venda', color_discrete_sequence=['#667eea']) fig.update_layout( plot_bgcolor='rgba(0,0,0,0)', paper_bgcolor='rgba(0,0,0,0)', font=dict(color='#2c3e50') ) st.plotly_chart(fig, use_container_width=True) # === SELEÇÃO DE VARIÁVEIS === qualitativas = df.select_dtypes(include='object').columns.tolist() quantitativas = df.select_dtypes(include=['float64', 'int64']).drop(columns=['SalePrice'], errors='ignore').columns.tolist() st.sidebar.markdown("""

⚙️ Configurações

""", unsafe_allow_html=True) st.sidebar.markdown("### 📊 Variáveis para ANOVA") anova_vars = st.sidebar.multiselect( "Selecione variáveis qualitativas:", qualitativas, help="Escolha variáveis categóricas para análise ANOVA" ) st.sidebar.markdown('### 🔧 Variáveis independentes para regressão') regressao_vars = st.sidebar.multiselect( "Selecione as variáveis:", options=df.columns.drop('SalePrice'), help="Escolha quatro a seis variáveis explicativas, sendo obrigatório incluir ao menos uma variável contínua e uma categórica " ) # === CONFIGURAÇÕES AVANÇADAS === st.sidebar.markdown("---") st.sidebar.markdown("### ⚙️ Configurações Avançadas") # Opções de visualização show_advanced_plots = st.sidebar.checkbox("📊 Gráficos Avançados", value=True) show_correlations = st.sidebar.checkbox("🔗 Matriz de Correlação", value=False) auto_refresh = st.sidebar.checkbox("🔄 Atualização Automática", value=False) # Informações do sistema st.sidebar.markdown("---") st.sidebar.markdown("### ℹ️ Informações") st.sidebar.info(f"Última atualização: {datetime.now().strftime('%H:%M:%S')}") st.sidebar.info(f"Registros carregados: {len(df):,}") # === MATRIZ DE CORRELAÇÃO === if show_correlations and regressao_vars: st.markdown("""

🔗 Matriz de Correlação

""", unsafe_allow_html=True) # Selecionar apenas variáveis numéricas numeric_vars = [var for var in regressao_vars if df[var].dtype in ['float64', 'int64']] if len(numeric_vars) > 1: corr_matrix = df[numeric_vars + ['SalePrice']].corr() # Criar heatmap interativo fig = px.imshow( corr_matrix, title="🔥 Mapa de Calor - Correlações", color_continuous_scale="RdBu_r", aspect="auto", text_auto=True ) fig.update_layout( plot_bgcolor='rgba(0,0,0,0)', paper_bgcolor='rgba(0,0,0,0)', font=dict(color='#2c3e50'), height=600 ) st.plotly_chart(fig, use_container_width=True) else: st.info("🔍 Selecione pelo menos 2 variáveis numéricas para visualizar correlações.") # === ANOVA === if anova_vars: st.markdown("""

🔬 Análise ANOVA

""", unsafe_allow_html=True) for var in anova_vars: with st.container(): st.markdown(f"""

📋 ANOVA - {var}

""", unsafe_allow_html=True) df_anova = df[[var, 'SalePrice']].dropna() if df_anova[var].nunique() < 2: st.warning(f"⚠️ '{var}' não tem categorias suficientes para ANOVA.") continue formula = f'SalePrice ~ C(Q("{var}"))' modelo = ols(formula, data=df_anova).fit() anova_result = sm.stats.anova_lm(modelo, typ=2) # Resultados em formato mais bonito col1, col2 = st.columns(2) with col1: st.markdown("#### 📊 Resultados ANOVA") st.dataframe(anova_result.round(4), use_container_width=True) with col2: # Testes de normalidade dos resíduos shapiro_result = shapiro(modelo.resid) ad_result = anderson(modelo.resid) st.markdown("#### 🧪 Testes de Diagnóstico") if shapiro_result.pvalue > 0.05: st.markdown(f"""
✅ Shapiro-Wilk
p-valor: {shapiro_result.pvalue:.4f}
Normalidade confirmada
""", unsafe_allow_html=True) else: st.markdown(f"""
⚠️ Shapiro-Wilk
p-valor: {shapiro_result.pvalue:.4f}
Normalidade rejeitada
""", unsafe_allow_html=True) # Teste de homocedasticidade grupos = [g["SalePrice"].values for _, g in df_anova.groupby(var)] levene_result = stats.levene(*grupos) kruskal_result = stats.kruskal(*grupos) col1, col2 = st.columns(2) with col1: if levene_result.pvalue > 0.05: st.markdown(f"""
✅ Teste de Levene
p-valor: {levene_result.pvalue:.4f}
Homocedasticidade confirmada
""", unsafe_allow_html=True) else: st.markdown(f"""
⚠️ Teste de Levene
p-valor: {levene_result.pvalue:.4f}
Heterocedasticidade detectada
""", unsafe_allow_html=True) with col2: if kruskal_result.pvalue < 0.05: st.markdown(f"""
📊 Kruskal-Wallis
p-valor: {kruskal_result.pvalue:.4f}
Diferenças significativas
""", unsafe_allow_html=True) else: st.markdown(f"""
📊 Kruskal-Wallis
p-valor: {kruskal_result.pvalue:.4f}
Sem diferenças significativas
""", unsafe_allow_html=True) # Calcula as medianas e ordena medianas = df_anova.groupby(var)['SalePrice'].median().sort_values(ascending=False) col1, col2 = st.columns(2) with col1: st.markdown("#### 💰 Medianas por Categoria") medianas_df = pd.DataFrame({ 'Categoria': medianas.index, 'Mediana (US$)': [f"${x:,.0f}" for x in medianas.values] }) st.dataframe(medianas_df, use_container_width=True, hide_index=True) with col2: # Categoria mais vantajosa mediana_max = medianas.max() categorias_top = medianas[medianas == mediana_max].index.tolist() if len(categorias_top) == 1: st.markdown(f"""

🏆 Categoria Mais Vantajosa

{categorias_top[0]}
Mediana: ${mediana_max:,.0f}
""", unsafe_allow_html=True) else: categorias_str = ", ".join(categorias_top) st.markdown(f"""

🏆 Categorias Mais Vantajosas (Empate)

{categorias_str}
Mediana: ${mediana_max:,.0f}
""", unsafe_allow_html=True) # Gráfico interativo com Plotly if show_advanced_plots: # Criar subplot com múltiplas visualizações fig = make_subplots( rows=2, cols=2, subplot_titles=( f'🎻 Distribuição por {var}', f'📊 Boxplot por {var}', f'📈 Médias por {var}', f'🎯 Contagem por {var}' ), specs=[[{"secondary_y": False}, {"secondary_y": False}], [{"secondary_y": False}, {"secondary_y": False}]] ) # Violin plot for i, category in enumerate(df_anova[var].unique()): data = df_anova[df_anova[var] == category]['SalePrice'] fig.add_trace( go.Violin(y=data, name=str(category), box_visible=True), row=1, col=1 ) # Box plot for i, category in enumerate(df_anova[var].unique()): data = df_anova[df_anova[var] == category]['SalePrice'] fig.add_trace( go.Box(y=data, name=str(category)), row=1, col=2 ) # Médias means = df_anova.groupby(var)['SalePrice'].mean().reset_index() fig.add_trace( go.Bar(x=means[var], y=means['SalePrice'], name='Médias', marker_color='lightblue'), row=2, col=1 ) # Contagens counts = df_anova[var].value_counts().reset_index() fig.add_trace( go.Bar(x=counts[var], y=counts['count'], name='Contagens', marker_color='lightgreen'), row=2, col=2 ) fig.update_layout( height=800, showlegend=False, plot_bgcolor='rgba(0,0,0,0)', paper_bgcolor='rgba(0,0,0,0)', font=dict(color='white') ) st.plotly_chart(fig, use_container_width=True) else: # Gráfico simples fig = px.violin(df_anova, x=var, y='SalePrice', title=f'🎻 Distribuição de Preços por {var}', color=var, color_discrete_sequence=px.colors.qualitative.Set3) fig.update_layout( plot_bgcolor='rgba(0,0,0,0)', paper_bgcolor='rgba(0,0,0,0)', font=dict(color='white'), height=500 ) st.plotly_chart(fig, use_container_width=True) st.markdown("---") # === REGRESSÃO LINEAR === if regressao_vars: # Header principal com ícone st.markdown("""

📊 Regressão Linear Múltipla

Análise Estatística Avançada

""", unsafe_allow_html=True) df_reg = df[['SalePrice'] + regressao_vars].dropna().copy() if df_reg.empty: st.error("⚠️ Não há dados suficientes para regressão com as variáveis selecionadas.") else: df_reg['LogSalePrice'] = np.log(df_reg['SalePrice']) # Progress bar simulado para engajamento progress_bar = st.progress(0) status_text = st.empty() status_text.text('🔄 Preparando dados...') progress_bar.progress(20) # Transformar variáveis categóricas em dummies df_reg = pd.get_dummies(df_reg, columns=[v for v in regressao_vars if df_reg[v].dtype == 'object'], drop_first=True) X = df_reg.drop(columns=['SalePrice', 'LogSalePrice']) y = df_reg['LogSalePrice'] # Garantir tipos numéricos X = X.astype(float) y = y.astype(float) status_text.text('🎯 Treinando modelo...') progress_bar.progress(50) X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) X_train_const = add_constant(X_train) X_test_const = add_constant(X_test) modelo = OLS(y_train, X_train_const).fit() status_text.text('✅ Modelo treinado!') progress_bar.progress(100) # Remove progress bar após completar import time time.sleep(1) progress_bar.empty() status_text.empty() # Tabs para organizar melhor o conteúdo tab1, tab2, tab3, tab4 = st.tabs(["📈 Resumo do Modelo", "🔍 Diagnósticos", "📊 Avaliação", "💡 Interpretação"]) with tab1: st.markdown("### 📋 Resumo Estatístico") # Caixa expansível para o resumo completo with st.expander("🔍 Ver Resumo Completo do Modelo", expanded=False): st.text(modelo.summary()) # Métricas principais em destaque col1, col2, col3 = st.columns(3) r2_adj = modelo.rsquared_adj f_stat = modelo.fvalue n_obs = int(modelo.nobs) with col1: st.metric( label="R² Ajustado", value=f"{r2_adj:.3f}", delta=f"{r2_adj*100:.1f}% da variância explicada" ) with col2: st.metric( label="Estatística F", value=f"{f_stat:.2f}", delta="Significância do modelo" ) with col3: st.metric( label="Observações", value=f"{n_obs}", delta="Tamanho da amostra" ) with tab2: st.markdown("### 🔍 Diagnóstico dos Resíduos") residuos = modelo.resid fitted = modelo.fittedvalues # Layout em colunas para os gráficos col1, col2 = st.columns(2) with col1: # Gráfico de resíduos vs valores ajustados (melhorado) fig, ax = plt.subplots(figsize=(8, 6)) scatter = ax.scatter(fitted, residuos, alpha=0.6, c='steelblue', edgecolor='white', s=50) ax.axhline(0, color='red', linestyle='--', linewidth=2, alpha=0.8) ax.set_xlabel("Valores Ajustados", fontsize=12) ax.set_ylabel("Resíduos", fontsize=12) ax.set_title("Resíduos vs Valores Ajustados", fontsize=14, fontweight='bold') ax.grid(True, alpha=0.3) plt.tight_layout() st.pyplot(fig) with col2: # Q-Q plot para normalidade from scipy import stats fig, ax = plt.subplots(figsize=(8, 6)) stats.probplot(residuos, dist="norm", plot=ax) ax.set_title("Q-Q Plot (Normalidade)", fontsize=14, fontweight='bold') ax.grid(True, alpha=0.3) plt.tight_layout() st.pyplot(fig) # Testes estatísticos em cards coloridos st.markdown("### 🧪 Testes Estatísticos") shapiro_test = shapiro(residuos) het_test = het_breuschpagan(residuos, X_train_const) col1, col2 = st.columns(2) with col1: normalidade_status = "✅ Normal" if shapiro_test.pvalue >= 0.05 else "❌ Não Normal" cor_normalidade = "success" if shapiro_test.pvalue >= 0.05 else "error" st.markdown(f"""

🎯 Teste Shapiro-Wilk

Status: {normalidade_status}
P-valor: {shapiro_test.pvalue:.4f}

""", unsafe_allow_html=True) with col2: hetero_status = "✅ Homocedasticidade" if het_test[1] >= 0.05 else "❌ Heterocedasticidade" st.markdown(f"""

📊 Teste Breusch-Pagan

Status: {hetero_status}
P-valor: {het_test[1]:.4f}

""", unsafe_allow_html=True) with tab3: st.markdown("### 📊 Avaliação do Modelo") # VIF em uma tabela mais bonita vif_data = pd.DataFrame() vif_data["Variável"] = X.columns vif_data["VIF"] = [variance_inflation_factor(X.values, i) for i in range(X.shape[1])] vif_data["Status"] = vif_data["VIF"].apply(lambda x: "⚠️ Alto" if x > 10 else "✅ OK" if x < 5 else "🔶 Moderado") st.markdown("#### 🔗 Análise de Multicolinearidade (VIF)") # Estilizar a tabela VIF def highlight_vif(val): if val > 10: return 'background-color: #ffebee; color: #c62828' elif val > 5: return 'background-color: #fff3e0; color: #ef6c00' else: return 'background-color: #e8f5e8; color: #2e7d32' st.dataframe( vif_data.style.applymap(highlight_vif, subset=['VIF']).format({'VIF': '{:.2f}'}), use_container_width=True ) # Métricas de performance y_pred = modelo.predict(X_test_const) r2 = r2_score(y_test, y_pred) rmse = np.sqrt(mean_squared_error(y_test, y_pred)) mae = mean_absolute_error(y_test, y_pred) st.markdown("#### 🎯 Métricas de Performance") col1, col2, col3 = st.columns(3) with col1: st.metric( label="R² (Teste)", value=f"{r2:.4f}", delta=f"{r2*100:.1f}% da variância explicada", delta_color="normal" ) with col2: st.metric( label="RMSE", value=f"{rmse:.3f}", delta="Erro médio quadrático", delta_color="off" ) with col3: st.metric( label="MAE", value=f"{mae:.3f}", delta="Erro médio absoluto", delta_color="off" ) # Gráfico de Valores Reais vs Preditos st.markdown("#### 📈 Valores Reais vs Preditos") fig, ax = plt.subplots(figsize=(10, 6)) ax.scatter(y_test, y_pred, alpha=0.6, c='steelblue', edgecolor='white', s=50) # Linha de referência perfeita min_val = min(min(y_test), min(y_pred)) max_val = max(max(y_test), max(y_pred)) ax.plot([min_val, max_val], [min_val, max_val], 'r--', linewidth=2, alpha=0.8, label='Predição Perfeita') ax.set_xlabel("Valores Reais (Log)", fontsize=12) ax.set_ylabel("Valores Preditos (Log)", fontsize=12) ax.set_title("Valores Reais vs Preditos", fontsize=14, fontweight='bold') ax.legend() ax.grid(True, alpha=0.3) plt.tight_layout() st.pyplot(fig) with tab4: st.markdown("### 💡 Interpretação Crítica do Modelo") # Análise automática do modelo com cards coloridos # Qualidade geral do modelo if r2 >= 0.7: st.success(f""" **🎉 Modelo Excelente!** O modelo explica aproximadamente **{r2*100:.1f}%** da variância logarítmica do preço. Isso indica um excelente ajuste aos dados. """) elif 0.4 <= r2 < 0.7: st.warning(f""" **⚠️ Modelo Moderado** O modelo explica cerca de **{r2*100:.1f}%** da variância. Pode haver variáveis importantes faltando ou relações não lineares não capturadas. """) else: st.error(f""" **❌ Modelo Fraco** R² de apenas **{r2*100:.1f}%**. O modelo tem baixo poder explicativo. Considere revisar variáveis ou abordagem. """) # Análise dos pressupostos st.markdown("#### 🔍 Análise dos Pressupostos") # Normalidade dos resíduos if shapiro_test.pvalue < 0.05: st.warning(""" **📊 Normalidade dos Resíduos**: Os resíduos **não seguem distribuição normal** (p < 0.05). Isso pode afetar a validade de intervalos de confiança e testes t. """) else: st.info(""" **📊 Normalidade dos Resíduos**: Os resíduos seguem uma distribuição aproximadamente normal (p ≥ 0.05). ✅ """) # Heterocedasticidade if het_test[1] < 0.05: st.warning(""" **📈 Homocedasticidade**: Evidência de **heterocedasticidade** (Breusch-Pagan, p < 0.05). O erro padrão das estimativas pode estar distorcido. """) else: st.info(""" **📈 Homocedasticidade**: Não há evidência de heterocedasticidade (p ≥ 0.05). ✅ """) # Multicolinearidade (VIF) high_vif = vif_data[vif_data["VIF"] > 10] if not high_vif.empty: st.warning(f""" **🔗 Multicolinearidade**: As seguintes variáveis apresentam **alta multicolinearidade** (VIF > 10): """) st.dataframe(high_vif[['Variável', 'VIF']], use_container_width=True) else: st.info("**🔗 Multicolinearidade**: Nenhuma variável apresenta multicolinearidade severa (VIF > 10). ✅") # Conclusão final em um card especial st.markdown("#### 🎯 Conclusão Final") if r2 > 0.7 and shapiro_test.pvalue > 0.05 and het_test[1] > 0.05 and high_vif.empty: st.success(""" **🌟 MODELO RECOMENDADO** O modelo é bem ajustado, estatisticamente confiável e pode ser usado para prever preços com confiança. Todos os pressupostos estatísticos foram atendidos. """) elif r2 < 0.4: st.error(""" **❌ MODELO NÃO RECOMENDADO** O modelo explica pouco da variação nos preços. Sugere-se revisar as variáveis e considerar modelos não-lineares ou técnicas de machine learning mais avançadas. """) else: st.warning(""" **⚠️ USAR COM CAUTELA** O modelo é razoável, mas há indícios de violações em alguns pressupostos. Use com cautela e considere melhorias no modelo. """) # Recomendações interativas with st.expander("💡 Recomendações para Melhorar o Modelo"): recomendacoes = [] if r2 < 0.6: recomendacoes.append("• **Adicionar mais variáveis explicativas** relevantes") recomendacoes.append("• **Considerar transformações não-lineares** das variáveis") recomendacoes.append("• **Explorar interações** entre variáveis") if shapiro_test.pvalue < 0.05: recomendacoes.append("• **Aplicar transformações** nos dados para normalizar resíduos") recomendacoes.append("• **Considerar modelos robustos** que não assumem normalidade") if not high_vif.empty: recomendacoes.append("• **Remover variáveis altamente correlacionadas**") recomendacoes.append("• **Usar técnicas de regularização** (Ridge, Lasso)") if recomendacoes: for rec in recomendacoes: st.markdown(rec) else: st.success("🎉 Seu modelo está bem ajustado! Parabéns!")