File size: 38,288 Bytes
32daecd
 
64eed68
5158316
32daecd
5158316
 
 
32daecd
 
5158316
 
 
32daecd
 
5158316
32daecd
5158316
32daecd
 
 
5158316
 
 
 
 
 
 
 
 
 
 
32daecd
5158316
 
 
 
33be9bb
5158316
 
 
 
 
 
 
 
 
 
 
 
32daecd
5158316
32daecd
 
 
 
5158316
 
32daecd
 
5158316
 
 
 
32daecd
 
 
 
 
 
 
33be9bb
32daecd
 
 
33be9bb
32daecd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33be9bb
32daecd
 
 
 
 
 
5158316
32daecd
5158316
 
 
 
 
 
32daecd
 
 
5158316
 
33be9bb
5158316
 
 
 
33be9bb
 
32daecd
 
5158316
 
 
 
 
 
 
 
 
32daecd
 
5158316
 
 
 
 
 
 
 
32daecd
5158316
 
 
 
33be9bb
5158316
 
 
 
 
 
 
33be9bb
5158316
33be9bb
32daecd
5158316
 
 
32daecd
 
5158316
 
33be9bb
5158316
 
 
 
 
 
 
 
 
32daecd
5158316
 
 
32daecd
5158316
 
 
 
 
 
 
 
 
 
 
 
 
32daecd
5158316
 
32daecd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5158316
32daecd
 
5158316
32daecd
 
 
5158316
32daecd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5158316
32daecd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4dce935
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32daecd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5158316
32daecd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5158316
32daecd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33be9bb
32daecd
 
 
 
33be9bb
32daecd
33be9bb
32daecd
 
 
 
 
 
33be9bb
32daecd
 
 
 
 
 
33be9bb
32daecd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5158316
32daecd
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
# 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.")