Spaces:
Sleeping
Sleeping
| import streamlit as st | |
| import pandas as pd | |
| import numpy as np | |
| import scipy.stats as stats | |
| import statsmodels.api as sm | |
| from statsmodels.formula.api import ols | |
| import matplotlib.pyplot as plt | |
| import seaborn as sns | |
| # --- 1. Configuração da Página Streamlit --- | |
| st.set_page_config( | |
| page_title="Análise ANOVA Interativa - Ames Housing", | |
| page_icon="📊", | |
| layout="wide", # Usa a largura total da tela | |
| initial_sidebar_state="expanded" # Sidebar expandida por padrão | |
| ) | |
| from PIL import Image | |
| import os | |
| # ... outros elementos do Streamlit ... | |
| image_path="Logo/logo_ppca.png" | |
| if os.path.exists(image_path): | |
| try: | |
| logo = Image.open(image_path) | |
| st.image(logo, caption='ANÁLISE ESTATÍSTICA DE DADOS E INFORMAÇÕES', width=350) | |
| except Exception as e: | |
| st.error(f"Erro ao carregar a imagem '{image_path}': {e}") | |
| st.write("Por favor, verifique se o arquivo é uma imagem válida (ex: .png, .jpg).") | |
| st.header("""📊 Análise de Variância (ANOVA) Interativa | |
| Programa de Pós-Graduação em Computação Aplicada - UNB - PPCA | |
| Mestrado Profissional""") | |
| st.subheader("Departamento de Ciência da Computação") | |
| #st.write("""Prof. João Gabriel de Moraes Souza.""") | |
| st.info("""Tarefa Avaliativa 3 – Análise com ANOVA no Mercado Imobiliário! 🏠📊""") | |
| st.markdown( | |
| """ | |
| Bem-vindos!! | |
| Eu sou Éder Marcelo Ponte Cunha, Mestrando do PPCA da UNB!\n | |
| Esta aplicação interativa foi desenvolvida para explorar a relação | |
| entre diferentes características de imóveis (categóricas) e seus preços\n | |
| de venda no dataset **Ames Housing** através da Análise de Variância (ANOVA).\n | |
| A ANOVA nos ajuda a entender se as médias de um grupo | |
| (neste caso, preços de venda para diferentes tipos de garagem, vizinhança, Qualidade Geral, etc) | |
| são significativamente diferentes entre si.\n | |
| Aqui, você pode: | |
| - **Visualizar as distribuições** de preços de venda por categoria. | |
| - **Revisar os resultados da ANOVA** para cada característica. | |
| - **Validar os pressupostos da ANOVA** (Normalidade, Homocedasticidade, Independência) | |
| para garantir a validade dos resultados. | |
| """ | |
| ) | |
| st.sidebar.header("Configurações e Dados") | |
| # --- 2. Carregamento dos Dados --- | |
| # Armazena em cache o carregamento e pré-processamento dos dados | |
| def load_and_preprocess_data(): | |
| """ | |
| Carrega o dataset AmesHousing.csv e realiza o pré-processamento necessário. | |
| Retorna o DataFrame limpo. | |
| """ | |
| data_path = 'AmesHousing.csv' # Assumindo que o arquivo está no mesmo diretório | |
| df = None | |
| if os.path.exists(data_path): | |
| try: | |
| df = pd.read_csv(data_path) | |
| st.sidebar.success("Base de dados 'AmesHousing.csv' carregada com sucesso.") | |
| except Exception as e: | |
| st.sidebar.error(f"Erro ao carregar 'AmesHousing.csv': {e}. Usando DataFrame de exemplo.") | |
| else: | |
| st.sidebar.warning(f"Arquivo '{data_path}' não encontrado. Usando DataFrame de exemplo.") | |
| if df is None: | |
| # DataFrame de exemplo para garantir que a aplicação funcione mesmo sem o arquivo | |
| data = { | |
| 'Garage Type': ['Attchd', 'Detchd', 'BuiltIn', 'Attchd', 'Detchd', 'CarPort', np.nan, 'Attchd', 'No_Garage'], | |
| 'Neighborhood': ['CollgCr', 'Veenker', 'Crawfor', 'NoRidge', 'Mitchel', 'Somerst', 'MeadowV', 'StoneBr', 'IDYLWOD'], | |
| 'Overall Qual': [7, 8, 7, 9, 5, 6, 4, 8, 5], | |
| 'SalePrice': [200000, 250000, 180000, 350000, 120000, 150000, 90000, 300000, 110000] | |
| } | |
| df = pd.DataFrame(data) | |
| # Renomear colunas para remover espaços e caracteres especiais | |
| df.rename(columns={'Garage Type': 'Garage_Type', 'Overall Qual': 'Overall_Qual'}, inplace=True) | |
| # Pré-processamento dos dados para ANOVA | |
| # Preencher NaNs na coluna 'Garage_Type' com 'No_Garage' | |
| df['Garage_Type'].fillna('No_Garage', inplace=True) | |
| # Remover linhas com NaNs nas colunas críticas para a análise | |
| df_clean = df.dropna(subset=['SalePrice', 'Neighborhood', 'Overall_Qual', 'Garage_Type']) | |
| # Converter 'Overall_Qual' para tipo categórico (importante para ANOVA) | |
| df_clean['Overall_Qual'] = df_clean['Overall_Qual'].astype('category') | |
| return df_clean | |
| df_clean = load_and_preprocess_data() | |
| #selected_feature_for_code = next(key for key, value in feature_display_names.items() if value == selected_display_name) | |
| st.sidebar.subheader("Visão Geral dos Dados") | |
| if st.sidebar.checkbox("Mostrar as primeiras linhas do dataset"): | |
| st.sidebar.dataframe(df_clean.head()) | |
| st.sidebar.info(f"Dataset carregado com {df_clean.shape[0]} linhas e {df_clean.shape[1]} colunas após a limpeza.") | |
| # Lista de características para análise ANOVA | |
| features_for_anova = ['Garage_Type', 'Neighborhood', 'Overall_Qual'] | |
| selected_feature = st.sidebar.selectbox( | |
| "Selecione a Característica para Análise Detalhada:", | |
| features_for_anova | |
| ) | |
| # --- 3. Análise ANOVA e Visualizações (Seção Principal) --- | |
| st.header("🔍 Análise ANOVA por Característica") | |
| st.markdown( | |
| f"Selecione uma característica na barra lateral para ver a análise ANOVA e os box plots. " | |
| f"Atualmente, a análise detalhada está focada em **'{selected_feature.replace('_', ' ')}'**." | |
| ) | |
| st.subheader(f"Distribuição de Preços de Venda por {selected_feature.replace('_', ' ')}") | |
| st.markdown( | |
| f"O box plot abaixo mostra a distribuição do Preço de Venda ('SalePrice') para cada categoria de '{selected_feature.replace('_', ' ')}'. " | |
| "Isso ajuda a visualizar as diferenças nas medianas e na dispersão dos preços entre os grupos, " | |
| "sugerindo se há diferenças significativas que a ANOVA pode confirmar." | |
| ) | |
| fig_box = plt.figure(figsize=(12, 5)) | |
| sns.boxplot(x=selected_feature, y='SalePrice', data=df_clean) | |
| plt.title(f'Distribuição de Preços de Venda por {selected_feature.replace("_", " ")}') | |
| plt.xlabel(selected_feature.replace('_', ' ')) | |
| plt.ylabel('Preço de Venda') | |
| plt.xticks(rotation=45, ha='right') | |
| plt.grid(axis='y', linestyle='--', alpha=0.7) | |
| st.pyplot(fig_box) | |
| st.subheader(f"Tabela ANOVA para {selected_feature.replace('_', ' ')}") | |
| st.markdown( | |
| f"A tabela ANOVA abaixo resume os resultados estatísticos para a característica '{selected_feature.replace('_', ' ')}'. " | |
| "O valor P ('PR(>F)') indica a probabilidade de observar diferenças tão grandes ou maiores entre as médias dos grupos " | |
| "se não houvesse diferença real. Um P-valor < 0.05 (geralmente) sugere que há uma diferença estatisticamente significativa " | |
| "entre pelo menos duas médias de grupo." | |
| ) | |
| # Realizando a ANOVA usando statsmodels | |
| formula = f'SalePrice ~ C({selected_feature})' | |
| model = ols(formula, data=df_clean).fit() | |
| anova_table = sm.stats.anova_lm(model, typ=2) | |
| st.dataframe(anova_table) | |
| # Interpretação básica do resultado da ANOVA | |
| p_value_anova = anova_table['PR(>F)'][0] # P-valor da característica | |
| st.info( | |
| f"**Conclusão da ANOVA para '{selected_feature.replace('_', ' ')}':**\n\n" | |
| f"- O P-valor para '{selected_feature.replace('_', ' ')}' é **{p_value_anova:.4f}**.\n" | |
| f"- Se {p_value_anova:.4f} < 0.05, podemos rejeitar a hipótese nula (H0), indicando que há diferenças estatisticamente significativas " | |
| f"nas médias de 'SalePrice' entre as categorias de '{selected_feature.replace('_', ' ')}'." | |
| f"- Caso contrário, não há evidência estatística suficiente para afirmar que as médias são diferentes." | |
| ) | |
| # --- 4. Validação dos Pressupostos da ANOVA (para a característica selecionada) --- | |
| st.header("✔️ Validação dos Pressupostos da ANOVA") | |
| st.markdown( | |
| f"Para que os resultados da ANOVA sejam válidos, alguns pressupostos precisam ser atendidos. " | |
| f"Vamos verificar esses pressupostos para a análise de 'SalePrice' por **'{selected_feature.replace('_', ' ')}'**." | |
| ) | |
| # Calcular resíduos e valores ajustados para a característica selecionada | |
| residuals = model.resid | |
| fitted_values = model.fittedvalues | |
| # --- 4.1. Teste de Normalidade dos Resíduos --- | |
| st.subheader("1. Normalidade dos Resíduos") | |
| st.markdown( | |
| "A ANOVA assume que os resíduos (diferenças entre valores observados e previstos) " | |
| "são normalmente distribuídos. Isso pode ser verificado visualmente e por testes estatísticos." | |
| ) | |
| st.markdown("### 1.1. Histograma dos Resíduos") | |
| st.markdown( | |
| "Um histograma que se assemelha a uma curva em forma de sino (distribuição normal) " | |
| "sugere que os resíduos são normalmente distribuídos." | |
| ) | |
| fig_hist = plt.figure(figsize=(6, 3)) | |
| sns.histplot(residuals, kde=True, bins=50) | |
| plt.title(f'Histograma dos Resíduos para {selected_feature.replace("_", " ")}') | |
| plt.xlabel('Resíduos') | |
| plt.ylabel('Frequência') | |
| plt.grid(axis='y', linestyle='--', alpha=0.7) | |
| st.pyplot(fig_hist) | |
| st.markdown("### 1.2. Q-Q Plot dos Resíduos") | |
| st.markdown( | |
| "No Q-Q Plot, se os pontos seguirem de perto a linha diagonal, isso indica " | |
| "que os resíduos são normalmente distribuídos." | |
| ) | |
| fig_qq = sm.qqplot(residuals, line='s') # 's' para linha padronizada | |
| plt.title(f'Q-Q Plot dos Resíduos para {selected_feature.replace("_", " ")}') | |
| st.pyplot(fig_qq) | |
| st.markdown("### 1.3. Teste Estatístico: Shapiro-Wilk Test") | |
| st.markdown( | |
| "**H0 (Hipótese Nula):** Os resíduos são normalmente distribuídos.\n" | |
| "**Ha (Hipótese Alternativa):** Os resíduos NÃO são normalmente distribuídos.\n" | |
| "Um P-valor menor que 0.05 (geralmente) sugere que devemos rejeitar H0, \n" | |
| "indicando falta de normalidade. Este teste é sensível a grandes amostras." | |
| ) | |
| shapiro_test_statistic, shapiro_p_value = stats.shapiro(residuals) | |
| st.write(f"Estatística de Shapiro-Wilk: `{shapiro_test_statistic:.4f}`") | |
| st.write(f"P-valor de Shapiro-Wilk: `{shapiro_p_value:.4f}`") | |
| if shapiro_p_value < 0.05: | |
| st.warning("Conclusão: Rejeitamos H0. Os resíduos **NÃO** são normalmente distribuídos. Isso pode afetar a validade da ANOVA.") | |
| else: | |
| st.success("Conclusão: Não rejeitamos H0. Os resíduos são normalmente distribuídos.") | |
| # --- 4.2. Teste de Homocedasticidade --- | |
| st.subheader("2. Homocedasticidade (Homogeneidade das Variâncias)") | |
| st.markdown( | |
| "A homocedasticidade significa que a variância dos resíduos é aproximadamente igual " | |
| "para todos os grupos. A heterocedasticidade (variâncias desiguais) pode levar a " | |
| "conclusões errôneas na ANOVA." | |
| ) | |
| st.markdown("### 2.1. Visualização: Resíduos vs. Valores Ajustados") | |
| st.markdown( | |
| "Neste gráfico, esperamos ver uma 'nuvem' de pontos distribuída aleatoriamente em torno " | |
| "da linha zero, sem padrões distintos (como um funil, o que indicaria heterocedasticidade)." | |
| ) | |
| fig_res_vs_fit = plt.figure(figsize=(6, 3)) | |
| plt.scatter(fitted_values, residuals, alpha=0.5) | |
| plt.axhline(y=0, color='r', linestyle='--') | |
| plt.title(f'Resíduos vs. Valores Ajustados para {selected_feature.replace("_", " ")}') | |
| plt.xlabel('Valores Ajustados (Preço de Venda Previsto)') | |
| plt.ylabel('Resíduos') | |
| plt.grid(True, linestyle='--', alpha=0.7) | |
| st.pyplot(fig_res_vs_fit) | |
| st.markdown("### 2.2. Teste Estatístico: Levene's Test") | |
| st.markdown( | |
| "**H0 (Hipótese Nula):** As variâncias dos resíduos são iguais entre os grupos (homocedasticidade).\n" | |
| "**Ha (Hipótese Alternativa):** As variâncias dos resíduos **NÃO** são iguais entre os grupos (heterocedasticidade).\n" | |
| "Um P-valor menor que 0.05 (geralmente) sugere que devemos rejeitar H0, " | |
| "indicando heterocedasticidade." | |
| ) | |
| # Agrupamos os resíduos por categoria da característica selecionada | |
| groups_for_levene = [residuals[df_clean[selected_feature] == g] for g in df_clean[selected_feature].unique()] | |
| levene_test_statistic, levene_p_value = stats.levene(*groups_for_levene) | |
| st.write(f"Estatística de Levene: `{levene_test_statistic:.4f}`") | |
| st.write(f"P-valor de Levene: `{levene_p_value:.4f}`") | |
| if levene_p_value < 0.05: | |
| st.warning("Conclusão: Rejeitamos H0. Há evidências de **heterocedasticidade** (variâncias desiguais). Isso pode afetar a validade da ANOVA.") | |
| else: | |
| st.success("Conclusão: Não rejeitamos H0. Há evidências de **homocedasticidade** (variâncias iguais).") | |
| # --- 4.3. Independência dos Erros --- | |
| st.subheader("3. Independência dos Erros") | |
| st.markdown( | |
| "Os erros (resíduos) devem ser independentes uns dos outros. Isso significa que " | |
| "o erro de uma observação não deve estar relacionado ao erro de outra. " | |
| "Este pressuposto é geralmente garantido por um bom desenho experimental e coleta de dados aleatória." | |
| ) | |
| st.markdown( | |
| "Para dados de imóveis como este, coletados como um 'snapshot' e não em série temporal, " | |
| "a independência é geralmente uma suposição razoável, a menos que haja alguma ordem " | |
| "espacial ou temporal não modelada. Visualmente, a falta de padrões no gráfico " | |
| "de resíduos vs. valores ajustados também pode sugerir independência." | |
| ) | |
| st.markdown("### Estatística Durbin-Watson") | |
| st.markdown( | |
| "A estatística Durbin-Watson testa a autocorrelação dos resíduos. Valores próximos de 2 " | |
| "sugerem que não há autocorrelação. Valores abaixo de 1 ou acima de 3 podem indicar problemas." | |
| ) | |
| durbin_watson_stat = sm.stats.durbin_watson(residuals) | |
| st.write(f"Estatística Durbin-Watson: `{durbin_watson_stat:.4f}`") | |
| if durbin_watson_stat > 1.5 and durbin_watson_stat < 2.5: | |
| st.success("Conclusão: A estatística Durbin-Watson sugere que os resíduos são independentes (sem autocorrelação).") | |
| else: | |
| st.warning("Conclusão: A estatística Durbin-Watson pode indicar problemas de independência dos resíduos (autocorrelação).") | |
| st.markdown("---") | |
| st.markdown( | |
| "**Importante:** A violação de um ou mais pressupostos da ANOVA não invalida " | |
| "automaticamente a análise, mas pode exigir cautela na interpretação dos resultados " | |
| "ou o uso de métodos alternativos (e.g., testes não paramétricos, transformações de dados)." | |
| ) | |
| # --- 5. Otimização para Deploy no Hugging Face Spaces --- | |
| #st.sidebar.markdown("---") | |
| #st.sidebar.subheader("Informações para Deploy") | |
| #st.sidebar.markdown( | |
| #)""" | |
| # --- Fim do Código --- |