Spaces:
Sleeping
Sleeping
| # -*- coding: utf-8 -*- | |
| """Tarefa3_AEDI_Gradio.py | |
| Aplicação Gradio para Análise ANOVA Dataset Ames Housing | |
| """ | |
| import gradio as gr | |
| import pandas as pd | |
| import numpy as np | |
| import matplotlib.pyplot as plt | |
| import seaborn as sns | |
| import warnings | |
| from datetime import datetime | |
| import io | |
| import base64 | |
| from io import BytesIO | |
| import tempfile | |
| import os | |
| # Bibliotecas estatísticas | |
| from scipy import stats | |
| from scipy.stats import f_oneway, levene, shapiro, kruskal | |
| from statsmodels.stats.anova import anova_lm | |
| from statsmodels.formula.api import ols | |
| from statsmodels.stats.multicomp import pairwise_tukeyhsd | |
| warnings.filterwarnings('ignore') | |
| # Configurações de visualização | |
| plt.style.use('default') | |
| sns.set_palette("husl") | |
| plt.rcParams['figure.figsize'] = (10, 6) | |
| plt.rcParams['font.size'] = 10 | |
| # ======================================== | |
| # FUNÇÕES AUXILIARES | |
| # ======================================== | |
| def save_plot_to_temp(fig): | |
| """Salvar gráfico em arquivo temporário""" | |
| try: | |
| # Criar arquivo temporário | |
| temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.png') | |
| temp_path = temp_file.name | |
| temp_file.close() | |
| # Salvar figura | |
| fig.savefig(temp_path, dpi=100, bbox_inches='tight', facecolor='white') | |
| plt.close(fig) # Fechar figura para liberar memória | |
| return temp_path | |
| except Exception as e: | |
| print(f"Erro ao salvar gráfico: {e}") | |
| return None | |
| def criar_dataset_sintetico(): | |
| """Criar dataset sintético""" | |
| np.random.seed(42) | |
| n_samples = 1000 # Reduzido para melhor performance | |
| df = pd.DataFrame({ | |
| 'SalePrice': np.random.normal(180000, 50000, n_samples).astype(int), | |
| 'GrLivArea': np.random.normal(1500, 400, n_samples).astype(int), | |
| 'BedroomAbvGr': np.random.choice([1, 2, 3, 4, 5], n_samples, p=[0.05, 0.15, 0.35, 0.35, 0.1]), | |
| 'YearBuilt': np.random.randint(1900, 2010, n_samples), | |
| 'YearRemodAdd': np.random.randint(1950, 2010, n_samples), | |
| 'GarageType': np.random.choice(['Attchd', 'Detchd', 'BuiltIn', None], n_samples, p=[0.6, 0.2, 0.1, 0.1]), | |
| 'Neighborhood': np.random.choice(['NAmes', 'Edwards', 'BrkSide', 'OldTown'], n_samples) | |
| }) | |
| df['SalePrice'] = df['SalePrice'] + df['GrLivArea'] * 50 + df['BedroomAbvGr'] * 10000 | |
| df['SalePrice'] = np.clip(df['SalePrice'], 50000, 500000) | |
| return df | |
| def carregar_dados(): | |
| """Carregar dados""" | |
| try: | |
| # Dataset menor para melhor performance | |
| url = 'https://raw.githubusercontent.com/plotly/datasets/master/ames-housing.csv' | |
| df = pd.read_csv(url) | |
| return df, "Dataset Ames Housing carregado com sucesso!" | |
| except Exception as e: | |
| print(f"Erro ao carregar dataset: {e}") | |
| df = criar_dataset_sintetico() | |
| return df, "Dataset sintético criado para demonstração" | |
| def preparar_caracteristicas(df): | |
| """Preparar características para análise""" | |
| # 1. Presença de Garagem | |
| if 'GarageType' in df.columns: | |
| df['HasGarage'] = df['GarageType'].notna() | |
| df['HasGarage'] = df['HasGarage'].map({True: 'Com Garagem', False: 'Sem Garagem'}) | |
| else: | |
| df['HasGarage'] = np.random.choice(['Com Garagem', 'Sem Garagem'], len(df)) | |
| # 2. Categoria de Bairro | |
| if 'Neighborhood' in df.columns: | |
| neighborhood_prices = df.groupby('Neighborhood')['SalePrice'].mean() | |
| if len(neighborhood_prices) >= 3: | |
| low_price = neighborhood_prices.quantile(0.33) | |
| high_price = neighborhood_prices.quantile(0.67) | |
| def categorize_neighborhood(neighborhood): | |
| if neighborhood in neighborhood_prices: | |
| avg_price = neighborhood_prices[neighborhood] | |
| if avg_price <= low_price: | |
| return 'Econômico' | |
| elif avg_price <= high_price: | |
| return 'Intermediário' | |
| else: | |
| return 'Premium' | |
| return 'Intermediário' | |
| df['NeighborhoodCategory'] = df['Neighborhood'].apply(categorize_neighborhood) | |
| else: | |
| df['NeighborhoodCategory'] = 'Intermediário' | |
| else: | |
| df['NeighborhoodCategory'] = 'Intermediário' | |
| # 3. Condição da Casa | |
| if 'YearBuilt' in df.columns and 'YearRemodAdd' in df.columns: | |
| df['YearsOld'] = 2010 - df['YearBuilt'] | |
| df['YearsSinceRemodel'] = 2010 - df['YearRemodAdd'] | |
| def categorize_house_condition(row): | |
| years_old = row['YearsOld'] | |
| years_since_remodel = row.get('YearsSinceRemodel', years_old) | |
| if years_old <= 20: | |
| return 'Nova' | |
| elif years_since_remodel <= 30: | |
| return 'Reformada' | |
| else: | |
| return 'Antiga' | |
| df['HouseCondition'] = df.apply(categorize_house_condition, axis=1) | |
| else: | |
| df['HouseCondition'] = np.random.choice(['Nova', 'Reformada', 'Antiga'], len(df)) | |
| # 4. Número de Quartos | |
| if 'BedroomAbvGr' in df.columns: | |
| def categorize_bedrooms(bedrooms): | |
| if bedrooms <= 2: | |
| return '1-2 Quartos' | |
| elif bedrooms == 3: | |
| return '3 Quartos' | |
| elif bedrooms == 4: | |
| return '4 Quartos' | |
| else: | |
| return '5+ Quartos' | |
| df['BedroomCategory'] = df['BedroomAbvGr'].apply(categorize_bedrooms) | |
| else: | |
| df['BedroomCategory'] = np.random.choice(['1-2 Quartos', '3 Quartos', '4 Quartos', '5+ Quartos'], len(df)) | |
| # 5. Área da Casa | |
| if 'GrLivArea' in df.columns: | |
| df['AreaM2'] = df['GrLivArea'] * 0.092903 | |
| area_quartiles = df['AreaM2'].quantile([0.25, 0.5, 0.75]) | |
| def categorize_area(area_m2): | |
| if area_m2 <= area_quartiles[0.25]: | |
| return 'Pequena' | |
| elif area_m2 <= area_quartiles[0.5]: | |
| return 'Média-Pequena' | |
| elif area_m2 <= area_quartiles[0.75]: | |
| return 'Média-Grande' | |
| else: | |
| return 'Grande' | |
| df['AreaCategory'] = df['AreaM2'].apply(categorize_area) | |
| else: | |
| df['AreaCategory'] = np.random.choice(['Pequena', 'Média-Pequena', 'Média-Grande', 'Grande'], len(df)) | |
| # Log do preço | |
| df['LogSalePrice'] = np.log(df['SalePrice']) | |
| return df | |
| # ======================================== | |
| # FUNÇÕES DE ANÁLISE | |
| # ======================================== | |
| def analise_exploratoria(): | |
| """Análise exploratória dos dados""" | |
| try: | |
| df, mensagem = carregar_dados() | |
| df = preparar_caracteristicas(df) | |
| # Criar figura única mais simples | |
| fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(12, 8)) | |
| fig.suptitle('Análise Exploratória - Preço de Venda', fontsize=14) | |
| # Histograma | |
| ax1.hist(df['SalePrice'], bins=20, alpha=0.7, color='skyblue', edgecolor='black') | |
| ax1.set_title('Distribuição do Preço') | |
| ax1.set_xlabel('Preço ($)') | |
| ax1.set_ylabel('Frequência') | |
| ax1.ticklabel_format(style='plain', axis='x') | |
| # Boxplot | |
| ax2.boxplot(df['SalePrice'], patch_artist=True) | |
| ax2.set_title('Boxplot do Preço') | |
| ax2.set_ylabel('Preço ($)') | |
| # Scatter plot área vs preço | |
| if 'GrLivArea' in df.columns: | |
| ax3.scatter(df['GrLivArea'], df['SalePrice'], alpha=0.5) | |
| ax3.set_title('Área vs Preço') | |
| ax3.set_xlabel('Área (sqft)') | |
| ax3.set_ylabel('Preço ($)') | |
| else: | |
| ax3.text(0.5, 0.5, 'Dados de área\nnão disponíveis', | |
| ha='center', va='center', transform=ax3.transAxes) | |
| # Log price | |
| ax4.hist(df['LogSalePrice'], bins=20, alpha=0.7, color='lightcoral', edgecolor='black') | |
| ax4.set_title('Distribuição Log(Preço)') | |
| ax4.set_xlabel('Log(Preço)') | |
| ax4.set_ylabel('Frequência') | |
| plt.tight_layout() | |
| # Salvar em arquivo temporário | |
| temp_path = save_plot_to_temp(fig) | |
| # Estatísticas | |
| stats_desc = df['SalePrice'].describe() | |
| stats_html = f""" | |
| <div style="background: #f0f8ff; padding: 15px; border-radius: 8px; margin: 10px 0;"> | |
| <h3>📊 Estatísticas Descritivas</h3> | |
| <p><strong>Média:</strong> ${stats_desc['mean']:,.0f}</p> | |
| <p><strong>Mediana:</strong> ${stats_desc['50%']:,.0f}</p> | |
| <p><strong>Desvio Padrão:</strong> ${stats_desc['std']:,.0f}</p> | |
| <p><strong>Mínimo:</strong> ${stats_desc['min']:,.0f}</p> | |
| <p><strong>Máximo:</strong> ${stats_desc['max']:,.0f}</p> | |
| <p><strong>Amostras:</strong> {len(df):,}</p> | |
| </div> | |
| """ | |
| return temp_path, stats_html, mensagem | |
| except Exception as e: | |
| error_msg = f"Erro na análise exploratória: {str(e)}" | |
| return None, f"<div style='color: red; padding: 10px;'>{error_msg}</div>", error_msg | |
| def visualizar_caracteristica(caracteristica): | |
| """Visualizar preços por característica""" | |
| try: | |
| df, _ = carregar_dados() | |
| df = preparar_caracteristicas(df) | |
| # Mapeamento de características | |
| map_caracteristicas = { | |
| 'Presença de Garagem': 'HasGarage', | |
| 'Categoria de Bairro': 'NeighborhoodCategory', | |
| 'Condição da Casa': 'HouseCondition', | |
| 'Número de Quartos': 'BedroomCategory', | |
| 'Área da Casa': 'AreaCategory' | |
| } | |
| coluna = map_caracteristicas[caracteristica] | |
| if coluna not in df.columns: | |
| return None, f"<div style='color: red;'>Coluna {coluna} não encontrada</div>" | |
| # Criar gráfico simples | |
| fig, ax = plt.subplots(figsize=(10, 6)) | |
| # Boxplot | |
| df_boxplot = df.dropna(subset=[coluna, 'SalePrice']) | |
| if len(df_boxplot[coluna].unique()) > 0: | |
| sns.boxplot(data=df_boxplot, x=coluna, y='SalePrice', ax=ax) | |
| ax.set_title(f'Preço por {caracteristica}') | |
| ax.tick_params(axis='x', rotation=45) | |
| ax.ticklabel_format(style='plain', axis='y') | |
| else: | |
| ax.text(0.5, 0.5, 'Dados insuficientes', ha='center', va='center') | |
| plt.tight_layout() | |
| temp_path = save_plot_to_temp(fig) | |
| # Estatísticas | |
| stats_grupo = df.groupby(coluna)['SalePrice'].agg(['count', 'mean', 'std']).round(0) | |
| stats_html = f""" | |
| <div style="background: #f0fff0; padding: 15px; border-radius: 8px; margin: 10px 0;"> | |
| <h3>📈 Estatísticas por {caracteristica}</h3> | |
| {stats_grupo.to_html(classes='table table-bordered')} | |
| </div> | |
| """ | |
| return temp_path, stats_html | |
| except Exception as e: | |
| error_msg = f"Erro na visualização: {str(e)}" | |
| return None, f"<div style='color: red; padding: 10px;'>{error_msg}</div>" | |
| def executar_anova(caracteristica): | |
| """Executar análise ANOVA""" | |
| try: | |
| df, _ = carregar_dados() | |
| df = preparar_caracteristicas(df) | |
| map_caracteristicas = { | |
| 'Presença de Garagem': 'HasGarage', | |
| 'Categoria de Bairro': 'NeighborhoodCategory', | |
| 'Condição da Casa': 'HouseCondition', | |
| 'Número de Quartos': 'BedroomCategory', | |
| 'Área da Casa': 'AreaCategory' | |
| } | |
| coluna = map_caracteristicas[caracteristica] | |
| if coluna not in df.columns: | |
| return f"<div style='color: red;'>Coluna {coluna} não encontrada</div>" | |
| # Preparar grupos | |
| grupos = [] | |
| nomes_grupos = [] | |
| for nome, grupo in df.groupby(coluna): | |
| valores = grupo['SalePrice'].dropna() | |
| if len(valores) > 0: | |
| grupos.append(valores) | |
| nomes_grupos.append(nome) | |
| if len(grupos) < 2: | |
| return f"<div style='color: red;'>Necessário pelo menos 2 grupos (encontrados: {len(grupos)})</div>" | |
| # ANOVA | |
| f_stat, p_valor = f_oneway(*grupos) | |
| significativo = p_valor < 0.05 | |
| # Resultado | |
| cor = "#28a745" if significativo else "#dc3545" | |
| resultado_html = f""" | |
| <div style="border-left: 5px solid {cor}; padding: 15px; background: #f8f9fa; margin: 10px 0;"> | |
| <h3>🔬 Resultado ANOVA - {caracteristica}</h3> | |
| <p><strong>F-statistic:</strong> {f_stat:.4f}</p> | |
| <p><strong>p-value:</strong> {p_valor:.6f}</p> | |
| <p><strong>Significativo (α=0.05):</strong> {'✅ SIM' if significativo else '❌ NÃO'}</p> | |
| <p><strong>Grupos analisados:</strong> {len(grupos)}</p> | |
| </div> | |
| """ | |
| return resultado_html | |
| except Exception as e: | |
| error_msg = f"Erro na ANOVA: {str(e)}" | |
| return f"<div style='color: red; padding: 10px;'>{error_msg}</div>" | |
| def analise_completa(): | |
| """Análise completa""" | |
| try: | |
| df, mensagem = carregar_dados() | |
| df = preparar_caracteristicas(df) | |
| # Gráfico resumo | |
| fig, axes = plt.subplots(2, 2, figsize=(12, 8)) | |
| caracteristicas = ['HasGarage', 'NeighborhoodCategory', 'HouseCondition', 'AreaCategory'] | |
| titulos = ['Garagem', 'Bairro', 'Condição', 'Área'] | |
| for i, (col, titulo) in enumerate(zip(caracteristicas, titulos)): | |
| if i < 4 and col in df.columns: | |
| ax = axes[i//2, i%2] | |
| try: | |
| df_plot = df.dropna(subset=[col, 'SalePrice']) | |
| if len(df_plot[col].unique()) > 0: | |
| sns.boxplot(data=df_plot, x=col, y='SalePrice', ax=ax) | |
| ax.set_title(titulo, fontsize=10) | |
| ax.tick_params(axis='x', rotation=45, labelsize=8) | |
| ax.ticklabel_format(style='plain', axis='y') | |
| else: | |
| ax.text(0.5, 0.5, 'Sem dados', ha='center', va='center') | |
| except: | |
| ax.text(0.5, 0.5, 'Erro', ha='center', va='center') | |
| plt.tight_layout() | |
| temp_path = save_plot_to_temp(fig) | |
| # Relatório | |
| relatorio_html = f""" | |
| <div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 20px; border-radius: 10px; text-align: center;"> | |
| <h2>📋 Relatório Completo</h2> | |
| <p>Análise ANOVA - Ames Housing</p> | |
| </div> | |
| <div style="background: #f8f9fa; padding: 15px; border-radius: 8px; margin: 10px 0;"> | |
| <h3>📊 Resumo do Dataset</h3> | |
| <p><strong>Amostras:</strong> {len(df):,} propriedades</p> | |
| <p><strong>Preço médio:</strong> ${df['SalePrice'].mean():,.0f}</p> | |
| <p><strong>Características analisadas:</strong> 5</p> | |
| </div> | |
| <div style="background: #e8f5e8; padding: 15px; border-radius: 8px; margin: 10px 0;"> | |
| <h3>🎯 Metodologia</h3> | |
| <ul> | |
| <li>Análise de Variância (ANOVA)</li> | |
| <li>Teste de significância (α = 0.05)</li> | |
| <li>5 características estratégicas</li> | |
| <li>Dataset: Ames Housing</li> | |
| </ul> | |
| </div> | |
| """ | |
| return temp_path, relatorio_html, mensagem | |
| except Exception as e: | |
| error_msg = f"Erro na análise completa: {str(e)}" | |
| return None, f"<div style='color: red; padding: 10px;'>{error_msg}</div>", error_msg | |
| # ======================================== | |
| # INTERFACE GRADIO SIMPLIFICADA | |
| # ======================================== | |
| with gr.Blocks(theme=gr.themes.Soft(), title="ANOVA - Ames Housing") as demo: | |
| gr.Markdown(""" | |
| # 🏠 Análise ANOVA - Preços de Imóveis | |
| **Análise Estatística do Dataset Ames Housing** | |
| Explore como diferentes características influenciam os preços de venda de imóveis usando análise de variância (ANOVA). | |
| """) | |
| with gr.Tab("🔍 Análise Exploratória"): | |
| gr.Markdown("### Explore os dados básicos do dataset") | |
| btn_explorar = gr.Button("Executar Análise Exploratória", variant="primary") | |
| with gr.Row(): | |
| img_exploratoria = gr.Image(label="Gráficos", height=300) | |
| with gr.Row(): | |
| stats_exploratoria = gr.HTML() | |
| status_exploratoria = gr.Textbox(label="Status", interactive=False) | |
| btn_explorar.click( | |
| analise_exploratoria, | |
| outputs=[img_exploratoria, stats_exploratoria, status_exploratoria] | |
| ) | |
| with gr.Tab("📊 Visualizar Características"): | |
| gr.Markdown("### Compare preços por característica") | |
| with gr.Row(): | |
| dropdown_caracteristica = gr.Dropdown( | |
| choices=[ | |
| "Presença de Garagem", | |
| "Categoria de Bairro", | |
| "Condição da Casa", | |
| "Número de Quartos", | |
| "Área da Casa" | |
| ], | |
| label="Selecione a característica", | |
| value="Presença de Garagem" | |
| ) | |
| btn_visualizar = gr.Button("Visualizar", variant="primary") | |
| with gr.Row(): | |
| img_caracteristica = gr.Image(label="Gráfico", height=300) | |
| with gr.Row(): | |
| stats_caracteristica = gr.HTML() | |
| btn_visualizar.click( | |
| visualizar_caracteristica, | |
| inputs=dropdown_caracteristica, | |
| outputs=[img_caracteristica, stats_caracteristica] | |
| ) | |
| with gr.Tab("🔬 Teste ANOVA"): | |
| gr.Markdown("### Execute análise ANOVA") | |
| with gr.Row(): | |
| dropdown_anova = gr.Dropdown( | |
| choices=[ | |
| "Presença de Garagem", | |
| "Categoria de Bairro", | |
| "Condição da Casa", | |
| "Número de Quartos", | |
| "Área da Casa" | |
| ], | |
| label="Característica para ANOVA", | |
| value="Presença de Garagem" | |
| ) | |
| btn_anova = gr.Button("Executar ANOVA", variant="primary") | |
| resultado_anova = gr.HTML(label="Resultado") | |
| btn_anova.click( | |
| executar_anova, | |
| inputs=dropdown_anova, | |
| outputs=resultado_anova | |
| ) | |
| with gr.Tab("📋 Relatório"): | |
| gr.Markdown("### Relatório completo da análise") | |
| btn_relatorio = gr.Button("Gerar Relatório Completo", variant="primary") | |
| with gr.Row(): | |
| img_relatorio = gr.Image(label="Gráficos do Relatório", height=300) | |
| with gr.Row(): | |
| html_relatorio = gr.HTML() | |
| status_relatorio = gr.Textbox(label="Status", interactive=False) | |
| btn_relatorio.click( | |
| analise_completa, | |
| outputs=[img_relatorio, html_relatorio, status_relatorio] | |
| ) | |
| with gr.Tab("ℹ️ Sobre"): | |
| gr.Markdown(""" | |
| ## Sobre esta Aplicação | |
| **Objetivo:** Análise estatística de preços de imóveis usando ANOVA | |
| **Dataset:** Ames Housing (preços de imóveis em Ames, Iowa) | |
| **Características analisadas:** | |
| - Presença de garagem | |
| - Categoria do bairro | |
| - Condição da casa | |
| - Número de quartos | |
| - Área construída | |
| **Métodos:** ANOVA, análise exploratória, testes de hipótese | |
| **Desenvolvido para:** Demonstração educacional | |
| """) | |
| # ======================================== | |
| # CONFIGURAÇÃO FINAL | |
| # ======================================== | |
| if __name__ == "__main__": | |
| # Configurações para Hugging Face Spaces | |
| demo.launch( | |
| server_name="0.0.0.0" if os.getenv('SPACE_ID') else "127.0.0.1", | |
| server_port=7860, | |
| share=False, | |
| show_error=True | |
| ) |