Spaces:
Running
Running
| # -*- coding: utf-8 -*- | |
| """ | |
| app.py - Interface Gradio para elaboração de modelos estatísticos | |
| Aplicativo para avaliação de imóveis | |
| """ | |
| import gradio as gr | |
| import pandas as pd | |
| import numpy as np | |
| import os | |
| from datetime import datetime | |
| # Importa módulos locais | |
| from core import ( | |
| detectar_abas_excel, | |
| carregar_arquivo, | |
| identificar_coluna_y_padrao, | |
| identificar_colunas_coords, | |
| obter_colunas_numericas, | |
| calcular_estatisticas_variaveis, | |
| calcular_metricas_outliers, | |
| sugerir_outliers, | |
| detectar_dicotomicas, | |
| ajustar_modelo, | |
| buscar_melhores_transformacoes, | |
| testar_micronumerosidade, | |
| exportar_modelo_dai, | |
| exportar_base_csv, | |
| TRANSFORMACOES | |
| ) | |
| from charts import ( | |
| criar_graficos_dispersao, | |
| criar_painel_diagnostico, | |
| criar_matriz_correlacao, | |
| criar_mapa | |
| ) | |
| # ============================================================ | |
| # CONSTANTES | |
| # ============================================================ | |
| TITULO = """ | |
| # ELABORAÇÃO DE MODELOS ESTATÍSTICOS | |
| ### Divisão de Avaliação de Imóveis | |
| --- | |
| """ | |
| # ============================================================ | |
| # FUNÇÕES AUXILIARES | |
| # ============================================================ | |
| def arredondar_df(df, decimais=4): | |
| """Arredonda apenas colunas numéricas de um DataFrame.""" | |
| if df is None: | |
| return None | |
| df_copy = df.copy() | |
| colunas_numericas = df_copy.select_dtypes(include=[np.number]).columns | |
| df_copy[colunas_numericas] = df_copy[colunas_numericas].round(decimais) | |
| return df_copy | |
| def carregar_css(): | |
| """Carrega CSS externo.""" | |
| css_path = os.path.join(os.path.dirname(__file__), "styles.css") | |
| try: | |
| with open(css_path, "r", encoding="utf-8") as f: | |
| return f.read() | |
| except FileNotFoundError: | |
| return "" | |
| def criar_header_secao(numero: int, titulo: str, timestamp: str = "") -> str: | |
| """Cria um header HTML estilizado para seções, opcionalmente com timestamp.""" | |
| timestamp_html = f' <span class="section-timestamp">(Atualizado às {timestamp})</span>' if timestamp else "" | |
| return f''' | |
| <div class="section-header"> | |
| <span class="section-number">{numero}</span> | |
| <h2 class="section-title">{titulo}{timestamp_html}</h2> | |
| </div> | |
| ''' | |
| def formatar_diagnosticos_html(diagnosticos): | |
| """Formata diagnósticos como HTML.""" | |
| if not diagnosticos: | |
| return "<p>Nenhum modelo ajustado.</p>" | |
| html = '<div class="diagnosticos-container">' | |
| # Estatísticas gerais | |
| html += '<div class="section-title-orange">Estatísticas Gerais</div>' | |
| html += f''' | |
| <div class="stats-grid"> | |
| <div class="stat-item"><span class="stat-label">n (observações)</span><span class="stat-value">{diagnosticos["n"]}</span></div> | |
| <div class="stat-item"><span class="stat-label">k (variáveis)</span><span class="stat-value">{diagnosticos["k"]}</span></div> | |
| <div class="stat-item"><span class="stat-label">R²</span><span class="stat-value">{diagnosticos["r2"]:.4f}</span></div> | |
| <div class="stat-item"><span class="stat-label">R² ajustado</span><span class="stat-value">{diagnosticos["r2_ajustado"]:.4f}</span></div> | |
| <div class="stat-item"><span class="stat-label">Correlação Pearson</span><span class="stat-value">{diagnosticos["r_pearson"]:.4f}</span></div> | |
| <div class="stat-item"><span class="stat-label">Desvio Padrão Resíduos</span><span class="stat-value">{diagnosticos["desvio_padrao_residuos"]:.4f}</span></div> | |
| </div> | |
| ''' | |
| # Testes | |
| html += '<div class="section-title-orange">Testes Estatísticos</div>' | |
| html += f''' | |
| <div class="teste-item"> | |
| <span class="teste-nome">Teste F:</span> | |
| <span class="teste-valor">F = {diagnosticos["Fc"]:.4f}, p = {diagnosticos["p_valor_F"]:.4f}</span> | |
| <span class="teste-interp">{diagnosticos["interp_F"]}</span> | |
| </div> | |
| <div class="teste-item"> | |
| <span class="teste-nome">Kolmogorov-Smirnov:</span> | |
| <span class="teste-valor">KS = {diagnosticos["ks_stat"]:.4f}, p = {diagnosticos["ks_p"]:.4f}</span> | |
| <span class="teste-interp">{diagnosticos["interp_KS"]}</span> | |
| </div> | |
| <div class="teste-item"> | |
| <span class="teste-nome">Curva Normal (%):</span> | |
| <span class="teste-valor">{diagnosticos["perc_resid"]}</span> | |
| <span class="teste-interp">Ideal: 68%, 90%, 95%</span> | |
| </div> | |
| <div class="teste-item"> | |
| <span class="teste-nome">Durbin-Watson:</span> | |
| <span class="teste-valor">DW = {diagnosticos["dw"]:.4f}</span> | |
| <span class="teste-interp">{diagnosticos["interp_DW"]}</span> | |
| </div> | |
| <div class="teste-item"> | |
| <span class="teste-nome">Breusch-Pagan:</span> | |
| <span class="teste-valor">LM = {diagnosticos["bp_lm"]:.4f}, p = {diagnosticos["bp_p"]:.4f}</span> | |
| <span class="teste-interp">{diagnosticos["interp_BP"]}</span> | |
| </div> | |
| ''' | |
| # Equação | |
| html += '<div class="section-title-orange">Equação do Modelo</div>' | |
| html += f'<div class="equation-box">{diagnosticos["equacao"]}</div>' | |
| html += '</div>' | |
| return html | |
| def formatar_busca_html(resultados_busca): | |
| """Formata resultados da busca automática como HTML.""" | |
| if not resultados_busca: | |
| return "<p>Execute a busca automática para ver resultados.</p>" | |
| html = '<div class="busca-container">' | |
| for r in resultados_busca: | |
| html += f''' | |
| <div class="modelo-card" style="border: 1px solid #ddd; border-radius: 8px; padding: 12px; margin: 8px 0; background: #f9f9f9;"> | |
| <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;"> | |
| <span class="modelo-rank" style="font-weight: bold; font-size: 1.1em;">#{r["rank"]}</span> | |
| <span class="modelo-r2" style="background: #e3f2fd; padding: 4px 8px; border-radius: 4px;">R² = {r["r2"]:.4f}</span> | |
| </div> | |
| <div class="modelo-transf" style="font-size: 0.95em;"> | |
| <b>y:</b> {r["transformacao_y"]}<br> | |
| ''' | |
| for col, transf in r["transformacoes_x"].items(): | |
| html += f"<b>{col}:</b> {transf}<br>" | |
| html += ''' | |
| </div> | |
| </div> | |
| ''' | |
| html += '</div>' | |
| return html | |
| # ============================================================ | |
| # CALLBACKS PRINCIPAIS | |
| # ============================================================ | |
| def ao_carregar_arquivo(arquivo): | |
| """Callback quando arquivo é carregado. Detecta se há múltiplas abas no Excel.""" | |
| if arquivo is None: | |
| return ( | |
| None, # estado_df | |
| "Nenhum arquivo enviado.", # status | |
| gr.update(choices=[], value=None, visible=False), # dropdown_aba | |
| gr.update(choices=[], value=None), # dropdown_y | |
| None, # tabela_dados | |
| None, # tabela_estatisticas | |
| gr.update(choices=[]), # checkboxes_x | |
| "<p>Carregue um arquivo para ver o mapa.</p>", # mapa | |
| None, # estado_df_filtrado | |
| criar_header_secao(4, "Estatísticas por Variável"), # header_estatisticas | |
| [], # estado_outliers_anteriores | |
| 1, # estado_iteracao | |
| gr.update(visible=False), # accordion_outliers_anteriores | |
| "Iteração: 1", # txt_iteracao_atual | |
| "0 outliers excluídos", # txt_n_outliers_anteriores | |
| "", # txt_lista_outliers_anteriores | |
| None, # estado_arquivo_temp | |
| ) | |
| # Obtém o caminho do arquivo (para poder reabrir depois) | |
| caminho_arquivo = arquivo.name if hasattr(arquivo, 'name') else str(arquivo) | |
| # Detecta abas do Excel | |
| abas, msg_abas, sucesso_abas = detectar_abas_excel(caminho_arquivo) | |
| # Se há múltiplas abas, mostra dropdown e carrega a primeira aba | |
| if sucesso_abas and len(abas) > 1: | |
| # Carrega a primeira aba automaticamente | |
| resultado = carregar_dados_do_arquivo(caminho_arquivo, abas[0], manter_dropdown=True, lista_abas=abas) | |
| return resultado | |
| # Se não há múltiplas abas, carrega diretamente | |
| return carregar_dados_do_arquivo(caminho_arquivo, None) | |
| def carregar_dados_do_arquivo(caminho_arquivo, nome_aba, manter_dropdown=False, lista_abas=None): | |
| """Carrega dados de um arquivo, opcionalmente de uma aba específica. | |
| Args: | |
| caminho_arquivo: Caminho do arquivo a carregar | |
| nome_aba: Nome da aba a carregar (apenas para Excel) | |
| manter_dropdown: Se True, mantém o dropdown de abas visível | |
| lista_abas: Lista de abas para popular o dropdown (quando manter_dropdown=True) | |
| """ | |
| df, msg, sucesso = carregar_arquivo(caminho_arquivo, nome_aba) | |
| if not sucesso: | |
| return ( | |
| None, # estado_df | |
| msg, # status | |
| gr.update(visible=False), # dropdown_aba (esconde) | |
| gr.update(choices=[], value=None), # dropdown_y | |
| None, # tabela_dados | |
| None, # tabela_estatisticas | |
| gr.update(choices=[]), # checkboxes_x | |
| "<p>Carregue um arquivo para ver o mapa.</p>", # mapa | |
| None, # estado_df_filtrado | |
| criar_header_secao(4, "Estatísticas por Variável"), # header_estatisticas | |
| [], # estado_outliers_anteriores | |
| 1, # estado_iteracao | |
| gr.update(visible=False), # accordion_outliers_anteriores | |
| "Iteração: 1", # txt_iteracao_atual | |
| "0 outliers excluídos", # txt_n_outliers_anteriores | |
| "", # txt_lista_outliers_anteriores | |
| None, # estado_arquivo_temp | |
| ) | |
| # Identifica colunas | |
| colunas_numericas = obter_colunas_numericas(df) | |
| coluna_y_padrao = identificar_coluna_y_padrao(df) | |
| # Variáveis X padrão: todas exceto Y | |
| colunas_x_padrao = [col for col in colunas_numericas if col != coluna_y_padrao] | |
| # Estatísticas iniciais | |
| estatisticas = calcular_estatisticas_variaveis(df, coluna_y_padrao) | |
| # Mapa | |
| mapa_html = criar_mapa(df) | |
| # Timestamp inicial | |
| timestamp = datetime.now().strftime("%H:%M:%S") | |
| # Configura dropdown de aba | |
| if manter_dropdown and lista_abas: | |
| dropdown_aba_update = gr.update(choices=lista_abas, value=nome_aba, visible=True) | |
| arquivo_temp = caminho_arquivo # Mantém o caminho para trocar de aba depois | |
| else: | |
| dropdown_aba_update = gr.update(visible=False) | |
| arquivo_temp = None | |
| return ( | |
| df, # estado_df | |
| msg, # status | |
| dropdown_aba_update, # dropdown_aba | |
| gr.update(choices=colunas_numericas, value=coluna_y_padrao), # dropdown_y | |
| arredondar_df(df), # tabela_dados | |
| estatisticas.round(4), # tabela_estatisticas | |
| gr.update(choices=colunas_numericas, value=colunas_x_padrao), # checkboxes_x (todas marcadas) | |
| mapa_html, # mapa | |
| df, # estado_df_filtrado (inicia com dados completos) | |
| criar_header_secao(4, "Estatísticas por Variável", timestamp), # header_estatisticas | |
| [], # estado_outliers_anteriores | |
| 1, # estado_iteracao | |
| gr.update(visible=False), # accordion_outliers_anteriores | |
| "Iteração: 1", # txt_iteracao_atual | |
| "0 outliers excluídos", # txt_n_outliers_anteriores | |
| "", # txt_lista_outliers_anteriores | |
| arquivo_temp, # estado_arquivo_temp | |
| ) | |
| def ao_selecionar_aba(caminho_arquivo, nome_aba): | |
| """Callback quando uma aba é selecionada no dropdown.""" | |
| if caminho_arquivo is None or nome_aba is None: | |
| return ( | |
| None, "Selecione uma aba.", gr.update(), gr.update(choices=[], value=None), | |
| None, None, gr.update(choices=[]), "<p>Selecione a aba.</p>", | |
| None, "", [], 1, gr.update(visible=False), "Iteração: 1", | |
| "0 outliers excluídos", "", None | |
| ) | |
| # Detecta abas novamente para manter o dropdown | |
| abas, _, _ = detectar_abas_excel(caminho_arquivo) | |
| return carregar_dados_do_arquivo(caminho_arquivo, nome_aba, manter_dropdown=True, lista_abas=abas) | |
| def ao_mudar_y(df, coluna_y): | |
| """Callback quando variável y é alterada.""" | |
| if df is None or coluna_y is None: | |
| return None, gr.update(choices=[]) | |
| # Lista de X disponíveis (exclui y) - todas marcadas por padrão | |
| colunas_x = [col for col in obter_colunas_numericas(df) if col != coluna_y] | |
| # Calcula estatísticas apenas para colunas selecionadas | |
| colunas_para_stats = [coluna_y] + colunas_x | |
| estatisticas = calcular_estatisticas_variaveis(df, coluna_y, colunas=colunas_para_stats) | |
| return estatisticas.round(4), gr.update(choices=colunas_x, value=colunas_x) | |
| def ajustar_modelo_callback(df, coluna_y, colunas_x, transformacao_y, outliers_anteriores, *valores_dropdowns): | |
| """Callback para ajustar o modelo e calcular métricas de outliers.""" | |
| if df is None or coluna_y is None or not colunas_x: | |
| return ( | |
| None, # estado_modelo | |
| criar_header_secao(7, "Diagnóstico de Modelo"), # header_modelo | |
| "<p>Selecione as variáveis para ajustar o modelo.</p>", # diagnosticos_html | |
| None, # tabela_coef | |
| None, # tabela_obs_calc | |
| None, None, None, None, None, # gráficos | |
| criar_header_secao(8, "Gráficos de Diagnóstico"), # header_graficos | |
| None, # tabela_metricas | |
| None, # estado_metricas | |
| criar_header_secao(9, "Analisar Outliers"), # header_outliers | |
| f"Outliers anteriores: {len(outliers_anteriores) if outliers_anteriores else 0} | Novos: 0 | Total após iteração: {len(outliers_anteriores) if outliers_anteriores else 0}" # txt_resumo_outliers | |
| ) | |
| # Extrai transformações dos dropdowns | |
| transformacoes_x = obter_transformacoes_dos_dropdowns(colunas_x, *valores_dropdowns) | |
| # Ajusta modelo | |
| resultado = ajustar_modelo( | |
| df, coluna_y, colunas_x, | |
| transformacao_y, transformacoes_x | |
| ) | |
| if resultado is None: | |
| return ( | |
| None, | |
| criar_header_secao(7, "Diagnóstico de Modelo"), | |
| "<p>Erro ao ajustar modelo. Verifique os dados.</p>", | |
| None, None, | |
| None, None, None, None, None, | |
| criar_header_secao(8, "Gráficos de Diagnóstico"), | |
| None, None, criar_header_secao(9, "Analisar Outliers"), | |
| f"Outliers anteriores: {len(outliers_anteriores) if outliers_anteriores else 0} | Novos: 0 | Total: {len(outliers_anteriores) if outliers_anteriores else 0}" | |
| ) | |
| # Formata diagnósticos | |
| diagnosticos_html = formatar_diagnosticos_html(resultado["diagnosticos"]) | |
| timestamp = datetime.now().strftime("%H:%M:%S") | |
| # Gráficos | |
| graficos = criar_painel_diagnostico(resultado) | |
| # Correlação | |
| colunas_corr = [coluna_y] + colunas_x | |
| fig_corr = criar_matriz_correlacao(df, colunas_corr) | |
| # Extrai métricas de outliers da tabela_obs_calc (já contém resíduos, Cook, etc.) | |
| tabela_metricas = resultado["tabela_obs_calc"].copy() | |
| # Resumo de outliers | |
| n_anteriores = len(outliers_anteriores) if outliers_anteriores else 0 | |
| resumo = f"Outliers anteriores: {n_anteriores} | Novos: 0 | Total após iteração: {n_anteriores}" | |
| return ( | |
| resultado, | |
| criar_header_secao(7, "Diagnóstico de Modelo", timestamp), # header_modelo | |
| diagnosticos_html, | |
| resultado["tabela_coef"].round(4), | |
| resultado["tabela_obs_calc"].round(4), | |
| graficos.get("obs_calc"), | |
| graficos.get("residuos"), | |
| graficos.get("histograma"), | |
| graficos.get("cook"), | |
| fig_corr, | |
| criar_header_secao(8, "Gráficos de Diagnóstico", timestamp), # header_graficos | |
| tabela_metricas.round(4), # tabela_metricas | |
| tabela_metricas, # estado_metricas | |
| criar_header_secao(9, "Analisar Outliers", timestamp), # header_outliers | |
| resumo # txt_resumo_outliers | |
| ) | |
| def buscar_transformacoes_callback(df, coluna_y, colunas_x): | |
| """Callback para busca automática de transformações.""" | |
| # Retorna: (html, resultados, timestamp, btn1_visible, btn2_visible, btn3_visible, btn4_visible, btn5_visible) | |
| btn_hidden = [gr.update(visible=False)] * 5 | |
| if df is None or coluna_y is None or not colunas_x: | |
| return ("<p>Selecione as variáveis primeiro.</p>", [], "", *btn_hidden) | |
| # Verifica se colunas têm dados válidos (não 100% NaN) | |
| colunas_vazias = [] | |
| for col in colunas_x: | |
| if col in df.columns and df[col].isna().all(): | |
| colunas_vazias.append(col) | |
| if colunas_vazias: | |
| msg = f"<p style='color: red;'><b>Erro:</b> As seguintes colunas estão completamente vazias (sem dados): <b>{', '.join(colunas_vazias)}</b></p>" | |
| msg += "<p>Selecione apenas variáveis que contenham dados válidos.</p>" | |
| return (msg, [], "", *btn_hidden) | |
| # Verifica se Y tem dados válidos | |
| if coluna_y in df.columns and df[coluna_y].isna().all(): | |
| msg = f"<p style='color: red;'><b>Erro:</b> A variável dependente <b>{coluna_y}</b> está completamente vazia.</p>" | |
| return (msg, [], "", *btn_hidden) | |
| # Detecta dicotômicas e fixa em (x) | |
| transformacoes_fixas = {} | |
| dicotomicas = detectar_dicotomicas(df, colunas_x) | |
| for col in dicotomicas: | |
| transformacoes_fixas[col] = "(x)" | |
| # Busca melhores transformações | |
| resultados = buscar_melhores_transformacoes( | |
| df, coluna_y, colunas_x, | |
| transformacoes_fixas=transformacoes_fixas, | |
| top_n=5 | |
| ) | |
| if not resultados: | |
| msg = "<p style='color: orange;'><b>Aviso:</b> Não foi possível encontrar combinações válidas de transformações.</p>" | |
| msg += "<p>Verifique se os dados contêm valores válidos (sem NaN ou Inf após transformações).</p>" | |
| return (msg, [], "", *btn_hidden) | |
| html = formatar_busca_html(resultados) | |
| timestamp = datetime.now().strftime("%H:%M:%S") | |
| # Atualiza visibilidade dos botões "Adotar" | |
| btn_updates = [gr.update(visible=(i < len(resultados))) for i in range(5)] | |
| return (html, resultados, criar_header_secao(5, "Transformações Sugeridas", timestamp), *btn_updates) | |
| def exportar_modelo_callback(resultado_modelo, df, estatisticas, nome_arquivo): | |
| """Callback para exportar modelo.""" | |
| if resultado_modelo is None: | |
| return "Nenhum modelo para exportar. Ajuste o modelo primeiro." | |
| if not nome_arquivo or not nome_arquivo.strip(): | |
| return "Informe o nome do arquivo." | |
| # Gera gráficos para incluir no arquivo | |
| graficos = criar_painel_diagnostico(resultado_modelo) | |
| caminho, msg = exportar_modelo_dai(resultado_modelo, df, estatisticas, nome_arquivo.strip(), graficos) | |
| return msg | |
| def ao_clicar_tabela(df, evt: gr.SelectData): | |
| """Callback quando clica em linha da tabela.""" | |
| if df is None or evt is None: | |
| return "<p>Carregue dados para ver o mapa.</p>" | |
| # Pega o índice da linha clicada | |
| indice = evt.index[0] + 1 # Ajusta para índice baseado em 1 | |
| return criar_mapa(df, indice_destacado=indice) | |
| def calcular_metricas_callback(df, coluna_y, colunas_x): | |
| """Callback para calcular métricas de outliers.""" | |
| if df is None or coluna_y is None or not colunas_x: | |
| return None, None | |
| # Calcula métricas usando todos os dados | |
| metricas = calcular_metricas_outliers(df, coluna_y, colunas_x) | |
| if metricas is None: | |
| return None, None | |
| return metricas.reset_index().round(4), metricas | |
| def simular_filtro_callback(metricas_estado, coluna, valor): | |
| """Callback para simular filtro de outliers (coluna <= -valor OU coluna >= +valor).""" | |
| if metricas_estado is None: | |
| return "" | |
| indices = sugerir_outliers(metricas_estado, coluna, valor) | |
| return ", ".join(map(str, indices)) | |
| def limpar_filtro_callback(): | |
| """Callback para limpar filtro de outliers.""" | |
| return "" | |
| def aplicar_exclusao_callback(df, indices_texto): | |
| """Callback para criar DataFrame filtrado (sem outliers).""" | |
| if df is None: | |
| return None | |
| if not indices_texto or not indices_texto.strip(): | |
| return arredondar_df(df) | |
| try: | |
| indices_excluir = [int(x.strip()) for x in indices_texto.split(",") if x.strip()] | |
| df_filtrado = df.drop(index=indices_excluir, errors='ignore') | |
| return arredondar_df(df_filtrado) | |
| except: | |
| return arredondar_df(df) | |
| def atualizar_estatisticas_callback(df_filtrado, coluna_y): | |
| """Callback para recalcular estatísticas com dados filtrados.""" | |
| if df_filtrado is None or coluna_y is None: | |
| return None | |
| # Usa índices do df_filtrado | |
| indices_usar = df_filtrado.index.tolist() if df_filtrado is not None else None | |
| estatisticas = calcular_estatisticas_variaveis(df_filtrado, coluna_y) | |
| return estatisticas.round(4) | |
| # Máximo de variáveis X suportadas na interface | |
| MAX_VARS_X = 20 | |
| def atualizar_campos_transformacoes(df, colunas_x): | |
| """ | |
| Atualiza visibilidade e valores dos campos de transformação. | |
| Retorna: [row_updates (5)] + [column_updates (20)] + [label_updates (20)] + [dropdown_updates (20)] | |
| """ | |
| n_rows = MAX_VARS_X // 4 # 5 rows (4 cards por linha) | |
| if df is None or not colunas_x: | |
| # Esconde todos | |
| row_updates = [gr.update(visible=False)] * n_rows | |
| column_updates = [gr.update(visible=False)] * MAX_VARS_X | |
| label_updates = [gr.update(value="", visible=False)] * MAX_VARS_X | |
| dropdown_updates = [gr.update(value="(x)", interactive=True, visible=False)] * MAX_VARS_X | |
| return row_updates + column_updates + label_updates + dropdown_updates | |
| dicotomicas = detectar_dicotomicas(df, colunas_x) | |
| # Updates para rows (visibilidade) - 4 por linha | |
| row_updates = [] | |
| for i in range(n_rows): | |
| idx_base = i * 4 | |
| visivel = idx_base < len(colunas_x) | |
| row_updates.append(gr.update(visible=visivel)) | |
| # Updates para columns, labels e dropdowns | |
| column_updates = [] | |
| label_updates = [] | |
| dropdown_updates = [] | |
| for i in range(MAX_VARS_X): | |
| if i < len(colunas_x): | |
| col = colunas_x[i] | |
| eh_dicotomica = col in dicotomicas | |
| column_updates.append(gr.update(visible=True)) | |
| label_updates.append(gr.update(value=col, visible=True)) | |
| dropdown_updates.append(gr.update( | |
| value="(x)", | |
| interactive=not eh_dicotomica, | |
| visible=True | |
| )) | |
| else: | |
| column_updates.append(gr.update(visible=False)) | |
| label_updates.append(gr.update(value="", visible=False)) | |
| dropdown_updates.append(gr.update(value="(x)", interactive=True, visible=False)) | |
| return row_updates + column_updates + label_updates + dropdown_updates | |
| def obter_transformacoes_dos_dropdowns(colunas_x, *valores_dropdowns): | |
| """Coleta transformações dos valores dos dropdowns.""" | |
| transformacoes = {} | |
| for i, col in enumerate(colunas_x): | |
| if i < len(valores_dropdowns) and valores_dropdowns[i]: | |
| transformacoes[col] = valores_dropdowns[i] | |
| else: | |
| transformacoes[col] = "(x)" | |
| return transformacoes | |
| def adotar_sugestao(indice, resultados, colunas_x): | |
| """Preenche os dropdowns com a sugestão selecionada.""" | |
| if not resultados or indice >= len(resultados): | |
| return [gr.update()] * (1 + MAX_VARS_X) | |
| sugestao = resultados[indice] | |
| # Update para transformação de Y | |
| updates = [gr.update(value=sugestao["transformacao_y"])] | |
| # Updates para transformações de X | |
| transf_x = sugestao["transformacoes_x"] | |
| for i in range(MAX_VARS_X): | |
| if i < len(colunas_x): | |
| col = colunas_x[i] | |
| updates.append(gr.update(value=transf_x.get(col, "(x)"))) | |
| else: | |
| updates.append(gr.update()) | |
| return updates | |
| def atualizar_estatisticas_auto(df_filtrado, coluna_y, colunas_x): | |
| """Recalcula estatísticas automaticamente e retorna header com timestamp.""" | |
| if df_filtrado is None or coluna_y is None: | |
| return None, criar_header_secao(4, "Estatísticas das Variáveis Selecionadas") | |
| # Filtra apenas colunas selecionadas + Y | |
| colunas_usar = [coluna_y] + list(colunas_x) if colunas_x else [coluna_y] | |
| colunas_disponiveis = [c for c in colunas_usar if c in df_filtrado.columns] | |
| if not colunas_disponiveis: | |
| return None, criar_header_secao(4, "Estatísticas das Variáveis Selecionadas") | |
| estatisticas = calcular_estatisticas_variaveis(df_filtrado, coluna_y, colunas=colunas_disponiveis) | |
| timestamp = datetime.now().strftime("%H:%M:%S") | |
| return estatisticas.round(4), criar_header_secao(4, "Estatísticas das Variáveis Selecionadas", timestamp) | |
| def reiniciar_iteracao_callback(df_original, outliers_anteriores, outliers_texto, iteracao_atual, coluna_y, colunas_x): | |
| """ | |
| Combina outliers anteriores com novos, atualiza estado e reinicia análise. | |
| """ | |
| # Parse dos novos outliers | |
| novos_outliers = [] | |
| if outliers_texto and outliers_texto.strip(): | |
| try: | |
| novos_outliers = [int(x.strip()) for x in outliers_texto.split(",") if x.strip()] | |
| except: | |
| pass | |
| # Combina listas (sem duplicatas) | |
| outliers_combinados = list(set((outliers_anteriores or []) + novos_outliers)) | |
| outliers_combinados.sort() | |
| # Incrementa iteração | |
| nova_iteracao = (iteracao_atual or 1) + 1 | |
| # Cria DataFrame filtrado | |
| df_filtrado = df_original.copy() | |
| if outliers_combinados: | |
| df_filtrado = df_filtrado.drop(index=outliers_combinados, errors='ignore') | |
| # Recalcula estatísticas | |
| estatisticas, header_estatisticas = atualizar_estatisticas_auto(df_filtrado, coluna_y, colunas_x) | |
| # Atualiza textos | |
| txt_iteracao = f"Iteração: {nova_iteracao}" | |
| txt_n_outliers = f"{len(outliers_combinados)} outliers excluídos" | |
| txt_lista = ", ".join(map(str, outliers_combinados)) if outliers_combinados else "Nenhum" | |
| # Visibilidade do accordion | |
| accordion_visivel = len(outliers_combinados) > 0 | |
| # Mapa atualizado | |
| mapa_html = criar_mapa(df_filtrado) | |
| return ( | |
| outliers_combinados, # estado_outliers_anteriores | |
| nova_iteracao, # estado_iteracao | |
| df_filtrado, # estado_df_filtrado | |
| arredondar_df(df_filtrado), # tabela_dados | |
| estatisticas, # tabela_estatisticas | |
| header_estatisticas, # header_estatisticas | |
| txt_iteracao, # txt_iteracao_atual | |
| txt_n_outliers, # txt_n_outliers_anteriores | |
| txt_lista, # txt_lista_outliers_anteriores | |
| gr.update(visible=accordion_visivel), # accordion_outliers_anteriores | |
| "", # outliers_texto (limpo) | |
| None, # tabela_metricas (limpa) | |
| None, # estado_metricas (limpo) | |
| criar_header_secao(9, "Analisar Outliers"), # header_outliers | |
| f"Outliers anteriores: {len(outliers_combinados)} | Novos: 0 | Total após iteração: {len(outliers_combinados)}", # txt_resumo_outliers | |
| mapa_html, # mapa_html atualizado | |
| ) | |
| def limpar_historico_callback(df_original, coluna_y, colunas_x): | |
| """ | |
| Limpa o histórico de outliers e reinicia do zero. | |
| """ | |
| # Recalcula estatísticas com dados originais | |
| estatisticas, header_estatisticas = atualizar_estatisticas_auto(df_original, coluna_y, colunas_x) | |
| # Mapa com dados originais | |
| mapa_html = criar_mapa(df_original) | |
| return ( | |
| [], # estado_outliers_anteriores (vazio) | |
| 1, # estado_iteracao (reinicia) | |
| df_original, # estado_df_filtrado (dados originais) | |
| arredondar_df(df_original), # tabela_dados | |
| estatisticas, # tabela_estatisticas | |
| header_estatisticas, # header_estatisticas | |
| "Iteração: 1", # txt_iteracao_atual | |
| "0 outliers excluídos", # txt_n_outliers_anteriores | |
| "", # txt_lista_outliers_anteriores | |
| gr.update(visible=False), # accordion_outliers_anteriores (esconde) | |
| "", # outliers_texto (limpo) | |
| None, # tabela_metricas (limpa) | |
| None, # estado_metricas (limpo) | |
| criar_header_secao(9, "Analisar Outliers"), # header_outliers | |
| "Outliers anteriores: 0 | Novos: 0 | Total após iteração: 0", # txt_resumo_outliers | |
| mapa_html, # mapa_html | |
| ) | |
| def atualizar_resumo_outliers(outliers_anteriores, outliers_texto): | |
| """Atualiza o resumo de outliers quando o usuário edita o campo.""" | |
| n_anteriores = len(outliers_anteriores) if outliers_anteriores else 0 | |
| novos_outliers = [] | |
| if outliers_texto and outliers_texto.strip(): | |
| try: | |
| novos_outliers = [int(x.strip()) for x in outliers_texto.split(",") if x.strip()] | |
| except: | |
| pass | |
| n_novos = len(novos_outliers) | |
| n_total = len(set((outliers_anteriores or []) + novos_outliers)) | |
| return f"Outliers anteriores: {n_anteriores} | Novos: {n_novos} | Total após iteração: {n_total}" | |
| def aplicar_selecao_callback(df, coluna_y, colunas_x, outliers_anteriores): | |
| """Aplica seleção de variáveis, atualiza estatísticas e busca transformações automaticamente. | |
| NÃO calcula métricas de outliers aqui - isso é feito ao ajustar o modelo. | |
| Filtra dados excluindo outliers de iterações anteriores. | |
| """ | |
| n_rows = MAX_VARS_X // 4 # Agora são 4 por linha | |
| # Valores padrão quando não há dados | |
| campos_vazios = ( | |
| [gr.update(visible=False)] * n_rows + | |
| [gr.update(value="", visible=False)] * MAX_VARS_X + | |
| [gr.update(value="(x)", interactive=True, visible=False)] * MAX_VARS_X | |
| ) | |
| # Botões de adotar escondidos por padrão | |
| btn_hidden = [gr.update(visible=False)] * 5 | |
| if df is None or coluna_y is None: | |
| return ( | |
| None, # estado_df_filtrado | |
| gr.update(value=None, visible=False), # tabela_estatisticas | |
| criar_header_secao(4, "Estatísticas das Variáveis Selecionadas"), # header_estatisticas | |
| criar_header_secao(5, "Transformações Sugeridas"), # header_busca | |
| "<p>Selecione as variáveis primeiro.</p>", # busca_html | |
| [], # estado_resultados_busca | |
| *btn_hidden, # botões adotar | |
| *campos_vazios # campos de transformação | |
| ) | |
| if not colunas_x: | |
| return ( | |
| df, | |
| gr.update(value=None, visible=False), | |
| criar_header_secao(4, "Estatísticas das Variáveis Selecionadas"), | |
| criar_header_secao(5, "Transformações Sugeridas"), | |
| "<p>Selecione pelo menos uma variável independente.</p>", | |
| [], | |
| *btn_hidden, | |
| *campos_vazios | |
| ) | |
| # Filtra dados excluindo outliers de iterações anteriores | |
| df_filtrado = df.copy() | |
| if outliers_anteriores: | |
| df_filtrado = df_filtrado.drop(index=outliers_anteriores, errors='ignore') | |
| # Calcula estatísticas com dados filtrados | |
| estatisticas, header_estatisticas = atualizar_estatisticas_auto(df_filtrado, coluna_y, colunas_x) | |
| # Atualiza campos de transformação | |
| campos_updates = atualizar_campos_transformacoes(df, colunas_x) | |
| # Busca automática de transformações | |
| busca_html, resultados_busca, header_busca_html, *btn_updates = buscar_transformacoes_callback( | |
| df_filtrado, coluna_y, colunas_x | |
| ) | |
| return ( | |
| df_filtrado, # estado_df_filtrado | |
| gr.update(value=estatisticas, visible=True), # tabela_estatisticas (mostra após carregar) | |
| header_estatisticas, # header_estatisticas (já é HTML com timestamp) | |
| header_busca_html, # header_busca (já é HTML com timestamp) | |
| busca_html, # busca_html | |
| resultados_busca, # estado_resultados_busca | |
| *btn_updates, # botões adotar | |
| *campos_updates # campos de transformação | |
| ) | |
| # ============================================================ | |
| # INTERFACE GRADIO | |
| # ============================================================ | |
| def criar_interface(): | |
| """Cria e retorna a interface Gradio.""" | |
| # Script JavaScript para forçar o Light Mode removendo a classe 'dark' do body | |
| js_func = """ | |
| function refresh() { | |
| const url = new URL(window.location); | |
| if (url.searchParams.get('__theme') !== 'light') { | |
| url.searchParams.set('__theme', 'light'); | |
| window.location.href = url.href; | |
| } | |
| } | |
| """ | |
| # Configura o tema base para combinar com o seu CSS (Laranja) | |
| tema = gr.themes.Default( | |
| primary_hue="orange", | |
| secondary_hue="neutral", | |
| ).set( | |
| body_background_fill="#f5f5f5", | |
| block_background_fill="#FFFFFF" | |
| ) | |
| # Carrega o CSS aqui (passando para o Blocks é mais robusto no Hugging Face) | |
| css_content = carregar_css() | |
| with gr.Blocks(title="Elaboração de Modelos", theme=tema, css=css_content, js=js_func) as app: | |
| gr.Markdown(TITULO) | |
| # Estados | |
| estado_df = gr.State(None) | |
| estado_modelo = gr.State(None) | |
| estado_estatisticas = gr.State(None) | |
| estado_outliers_anteriores = gr.State([]) # Lista de índices excluídos em iterações anteriores | |
| estado_iteracao = gr.State(1) # Contador de iterações | |
| # ======================================== | |
| # SEÇÃO 1: IMPORTAR DADOS | |
| # ======================================== | |
| # Estado para armazenar o arquivo temporariamente (usado quando há múltiplas abas) | |
| estado_arquivo_temp = gr.State(None) | |
| gr.HTML(criar_header_secao(1, "Importar Dados")) | |
| with gr.Group(elem_classes="section-container"): | |
| with gr.Row(): | |
| upload = gr.File( | |
| label="Carregar arquivo (Excel ou CSV)", | |
| file_types=[".xlsx", ".xls", ".csv"], | |
| scale=2 | |
| ) | |
| dropdown_aba = gr.Dropdown( | |
| label="Selecionar Aba", | |
| choices=[], | |
| interactive=True, | |
| visible=False, | |
| scale=1 | |
| ) | |
| dropdown_y = gr.Dropdown( | |
| label="Variável Dependente (y)", | |
| choices=[], | |
| interactive=True, | |
| scale=1 | |
| ) | |
| status = gr.Textbox( | |
| label="Status", | |
| interactive=False, | |
| scale=2 | |
| ) | |
| with gr.Accordion("Outliers Excluídos (Iterações Anteriores)", open=True, visible=False) as accordion_outliers_anteriores: | |
| with gr.Row(): | |
| txt_iteracao_atual = gr.Textbox( | |
| value="Iteração: 1", | |
| label="", | |
| interactive=False, | |
| scale=1 | |
| ) | |
| txt_n_outliers_anteriores = gr.Textbox( | |
| value="0 outliers excluídos", | |
| label="", | |
| interactive=False, | |
| scale=2 | |
| ) | |
| txt_lista_outliers_anteriores = gr.Textbox( | |
| label="Índices excluídos em iterações anteriores", | |
| value="", | |
| interactive=False, | |
| max_lines=3 | |
| ) | |
| btn_limpar_historico = gr.Button("Limpar Histórico e Reiniciar do Zero", variant="secondary") | |
| # ======================================== | |
| # SEÇÃO 2: VISUALIZAR DADOS | |
| # ======================================== | |
| gr.HTML(criar_header_secao(2, "Visualizar Dados")) | |
| with gr.Group(elem_classes="section-container"): | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| with gr.Accordion("Dados de Mercado", open=True): | |
| tabela_dados = gr.Dataframe( | |
| label="", | |
| interactive=False, | |
| max_height=400 | |
| ) | |
| with gr.Column(scale=1): | |
| with gr.Accordion("Mapa", open=True): | |
| mapa_html = gr.HTML( | |
| value="<p>Carregue dados para ver o mapa.</p>", | |
| label="" | |
| ) | |
| # ======================================== | |
| # SEÇÃO 3: SELECIONAR VARIÁVEIS | |
| # ======================================== | |
| gr.HTML(criar_header_secao(3, "Selecionar Variáveis")) | |
| with gr.Group(elem_classes="section-container"): | |
| with gr.Row(elem_classes="checkbox-selecionar-todos"): | |
| checkbox_selecionar_todos = gr.Checkbox( | |
| label="Marcar ou desmarcar todas as variáveis", | |
| value=True, | |
| interactive=True | |
| ) | |
| with gr.Row(): | |
| checkboxes_x = gr.CheckboxGroup( | |
| label="Variáveis Independentes (X)", | |
| choices=[], | |
| interactive=True | |
| ) | |
| with gr.Row(): | |
| btn_aplicar_selecao = gr.Button("Aplicar Seleção", variant="primary", scale=1) | |
| # Estados para outliers (usados na seção de Análise de Outliers após o modelo) | |
| estado_metricas = gr.State(None) | |
| estado_df_filtrado = gr.State(None) | |
| # ======================================== | |
| # SEÇÃO 4: ESTATÍSTICAS DAS VARIÁVEIS SELECIONADAS | |
| # ======================================== | |
| header_estatisticas = gr.HTML(criar_header_secao(4, "Estatísticas das Variáveis Selecionadas")) | |
| with gr.Group(elem_classes="section-container"): | |
| tabela_estatisticas = gr.Dataframe( | |
| label="", | |
| interactive=False, | |
| value=None, | |
| visible=False | |
| ) | |
| # ======================================== | |
| # SEÇÃO 5: TRANSFORMAÇÕES SUGERIDAS | |
| # ======================================== | |
| # Estado para armazenar resultados da busca | |
| estado_resultados_busca = gr.State([]) | |
| header_busca = gr.HTML(criar_header_secao(5, "Transformações Sugeridas")) | |
| with gr.Group(elem_classes="section-container") as grupo_busca: | |
| busca_html = gr.HTML( | |
| value="<p>Clique em 'Aplicar Seleção' para buscar as melhores combinações de transformações.</p>" | |
| ) | |
| # ======================================== | |
| # SEÇÃO 6: APLICAR TRANSFORMAÇÕES E AJUSTAR MODELO | |
| # ======================================== | |
| gr.HTML(criar_header_secao(6, "Aplicar Transformações e Ajustar Modelo")) | |
| with gr.Group(elem_classes="section-container"): | |
| # Botões "Adotar" (5 fixos, visibilidade controlada) | |
| gr.Markdown("*Adote uma das sugestões da busca ou configure manualmente:*") | |
| with gr.Row(): | |
| btn_adotar_1 = gr.Button("Adotar #1", visible=False, elem_classes="btn-adotar", size="sm") | |
| btn_adotar_2 = gr.Button("Adotar #2", visible=False, elem_classes="btn-adotar", size="sm") | |
| btn_adotar_3 = gr.Button("Adotar #3", visible=False, elem_classes="btn-adotar", size="sm") | |
| btn_adotar_4 = gr.Button("Adotar #4", visible=False, elem_classes="btn-adotar", size="sm") | |
| btn_adotar_5 = gr.Button("Adotar #5", visible=False, elem_classes="btn-adotar", size="sm") | |
| with gr.Row(): | |
| transformacao_y = gr.Dropdown( | |
| label="Transformação de y", | |
| choices=TRANSFORMACOES, | |
| value="(x)", | |
| interactive=True, | |
| scale=1 | |
| ) | |
| gr.Markdown("*Selecione a transformação para cada variável (dicotômicas ficam fixas em (x))*") | |
| # Criar componentes fixos: 20 pares (Label + Dropdown), 4 por linha | |
| transf_x_rows = [] | |
| transf_x_columns = [] | |
| transf_x_labels = [] | |
| transf_x_dropdowns = [] | |
| for i in range(MAX_VARS_X // 4): # 5 rows (20 / 4) | |
| row = gr.Row(visible=False) | |
| with row: | |
| for j in range(4): | |
| col = gr.Column(scale=1, min_width=180, elem_classes="transf-card", visible=False) | |
| with col: | |
| label = gr.Textbox( | |
| value="", | |
| label="Variável", | |
| interactive=False, | |
| visible=False, | |
| max_lines=1 | |
| ) | |
| dropdown = gr.Dropdown( | |
| choices=TRANSFORMACOES, | |
| value="(x)", | |
| label="Transformação", | |
| interactive=True, | |
| visible=False | |
| ) | |
| transf_x_columns.append(col) | |
| transf_x_labels.append(label) | |
| transf_x_dropdowns.append(dropdown) | |
| transf_x_rows.append(row) | |
| with gr.Row(): | |
| btn_ajustar = gr.Button("Aplicar transformações e ajustar modelo", variant="primary", scale=1) | |
| # ======================================== | |
| # SEÇÃO 7: DIAGNÓSTICO DE MODELO | |
| # ======================================== | |
| header_modelo = gr.HTML(criar_header_secao(7, "Diagnóstico de Modelo")) | |
| with gr.Group(elem_classes="section-container"): | |
| diagnosticos_html = gr.HTML( | |
| value="<p>Ajuste o modelo para ver os diagnósticos.</p>" | |
| ) | |
| with gr.Row(): | |
| with gr.Accordion("Tabela de Coeficientes", open=True): | |
| tabela_coef = gr.Dataframe( | |
| label="", | |
| interactive=False, | |
| max_height=300 | |
| ) | |
| with gr.Accordion("Valores Observados vs Calculados", open=True): | |
| tabela_obs_calc = gr.Dataframe( | |
| label="", | |
| interactive=False, | |
| max_height=400 | |
| ) | |
| # ======================================== | |
| # SEÇÃO 8: GRÁFICOS DE DIAGNÓSTICO | |
| # ======================================== | |
| header_graficos = gr.HTML(criar_header_secao(8, "Gráficos de Diagnóstico")) | |
| with gr.Group(elem_classes="section-container"): | |
| with gr.Row(): | |
| plot_obs_calc = gr.Plot(label="Observados vs Calculados") | |
| plot_residuos = gr.Plot(label="Resíduos") | |
| with gr.Row(): | |
| plot_hist = gr.Plot(label="Histograma dos Resíduos") | |
| plot_cook = gr.Plot(label="Distância de Cook") | |
| with gr.Row(): | |
| plot_corr = gr.Plot(label="Matriz de Correlação") | |
| # ======================================== | |
| # SEÇÃO 9: ANALISAR OUTLIERS | |
| # ======================================== | |
| header_outliers = gr.HTML(criar_header_secao(9, "Analisar Outliers")) | |
| with gr.Group(elem_classes="section-container"): | |
| gr.Markdown("*Métricas calculadas com base no modelo ajustado (resíduos com transformações aplicadas)*") | |
| tabela_metricas = gr.Dataframe( | |
| label="Métricas para identificação de outliers", | |
| interactive=False, | |
| max_height=300 | |
| ) | |
| gr.Markdown("### Filtrar Outliers") | |
| gr.Markdown("*Filtra valores onde: coluna ≤ -X OU coluna ≥ +X*") | |
| with gr.Row(): | |
| col_filtro = gr.Dropdown( | |
| label="Coluna", | |
| choices=["Resíduo Pad.", "Resíduo Stud.", "Cook"], | |
| value="Resíduo Pad.", | |
| scale=1 | |
| ) | |
| valor_filtro = gr.Number(label="Valor X", value=2.0, scale=1) | |
| btn_simular = gr.Button("Simular Filtro", variant="secondary", scale=1) | |
| btn_limpar = gr.Button("Limpar Filtro", variant="secondary", scale=1) | |
| with gr.Row(): | |
| outliers_texto = gr.Textbox( | |
| label="Índices a Excluir nesta Iteração", | |
| placeholder="Ex: 5, 12, 23", | |
| scale=3 | |
| ) | |
| with gr.Row(): | |
| txt_resumo_outliers = gr.Textbox( | |
| label="Resumo", | |
| value="Outliers anteriores: 0 | Novos: 0 | Total após iteração: 0", | |
| interactive=False | |
| ) | |
| with gr.Row(): | |
| btn_reiniciar_iteracao = gr.Button( | |
| "Aplicar Exclusões e Reiniciar Iteração", | |
| variant="primary", | |
| scale=2 | |
| ) | |
| btn_download_base = gr.Button("Baixar Base Tratada (CSV)", variant="secondary", scale=1) | |
| download_base_file = gr.File(label="", visible=False) | |
| # ======================================== | |
| # SEÇÃO 10: EXPORTAR MODELO | |
| # ======================================== | |
| gr.HTML(criar_header_secao(10, "Exportar Modelo")) | |
| with gr.Group(elem_classes="section-container"): | |
| with gr.Row(): | |
| nome_arquivo = gr.Textbox( | |
| label="Nome do arquivo", | |
| placeholder="modelo_01", | |
| scale=2 | |
| ) | |
| btn_exportar = gr.Button("Exportar .dai", variant="primary", scale=1) | |
| status_exportar = gr.Textbox( | |
| label="Status da exportação", | |
| interactive=False | |
| ) | |
| # ======================================== | |
| # EVENTOS | |
| # ======================================== | |
| # Upload de arquivo | |
| upload.upload( | |
| ao_carregar_arquivo, | |
| inputs=[upload], | |
| outputs=[ | |
| estado_df, | |
| status, | |
| dropdown_aba, | |
| dropdown_y, | |
| tabela_dados, | |
| tabela_estatisticas, | |
| checkboxes_x, | |
| mapa_html, | |
| estado_df_filtrado, | |
| header_estatisticas, | |
| estado_outliers_anteriores, | |
| estado_iteracao, | |
| accordion_outliers_anteriores, | |
| txt_iteracao_atual, | |
| txt_n_outliers_anteriores, | |
| txt_lista_outliers_anteriores, | |
| estado_arquivo_temp | |
| ] | |
| ) | |
| # Seleção de aba (quando há múltiplas abas no Excel) | |
| dropdown_aba.change( | |
| ao_selecionar_aba, | |
| inputs=[estado_arquivo_temp, dropdown_aba], | |
| outputs=[ | |
| estado_df, | |
| status, | |
| dropdown_aba, | |
| dropdown_y, | |
| tabela_dados, | |
| tabela_estatisticas, | |
| checkboxes_x, | |
| mapa_html, | |
| estado_df_filtrado, | |
| header_estatisticas, | |
| estado_outliers_anteriores, | |
| estado_iteracao, | |
| accordion_outliers_anteriores, | |
| txt_iteracao_atual, | |
| txt_n_outliers_anteriores, | |
| txt_lista_outliers_anteriores, | |
| estado_arquivo_temp | |
| ] | |
| ) | |
| # Mudança de y | |
| dropdown_y.change( | |
| ao_mudar_y, | |
| inputs=[estado_df, dropdown_y], | |
| outputs=[tabela_estatisticas, checkboxes_x] | |
| ) | |
| # Selecionar/Desselecionar todos via checkbox | |
| def toggle_selecionar_todos(selecionar, df, coluna_y): | |
| """Marca ou desmarca todas as variáveis independentes.""" | |
| if selecionar: | |
| # Marcar todos | |
| colunas_x = [col for col in obter_colunas_numericas(df) if col != coluna_y] if df is not None and coluna_y else [] | |
| return gr.update(value=colunas_x) | |
| else: | |
| # Desmarcar todos | |
| return gr.update(value=[]) | |
| checkbox_selecionar_todos.change( | |
| toggle_selecionar_todos, | |
| inputs=[checkbox_selecionar_todos, estado_df, dropdown_y], | |
| outputs=[checkboxes_x] | |
| ) | |
| # Clique na tabela -> atualiza mapa | |
| tabela_dados.select( | |
| ao_clicar_tabela, | |
| inputs=[estado_df], | |
| outputs=[mapa_html] | |
| ) | |
| # Seleção de X -> atualiza campos de transformação (preview) | |
| checkboxes_x.change( | |
| atualizar_campos_transformacoes, | |
| inputs=[estado_df, checkboxes_x], | |
| outputs=transf_x_rows + transf_x_columns + transf_x_labels + transf_x_dropdowns | |
| ) | |
| # Aplicar seleção de variáveis -> atualiza estatísticas, busca transformações e campos | |
| btn_aplicar_selecao.click( | |
| aplicar_selecao_callback, | |
| inputs=[estado_df, dropdown_y, checkboxes_x, estado_outliers_anteriores], | |
| outputs=[ | |
| estado_df_filtrado, | |
| tabela_estatisticas, | |
| header_estatisticas, | |
| header_busca, | |
| busca_html, | |
| estado_resultados_busca, | |
| btn_adotar_1, btn_adotar_2, btn_adotar_3, btn_adotar_4, btn_adotar_5 | |
| ] + transf_x_rows + transf_x_columns + transf_x_labels + transf_x_dropdowns | |
| ) | |
| # Simular filtro de outliers | |
| btn_simular.click( | |
| simular_filtro_callback, | |
| inputs=[estado_metricas, col_filtro, valor_filtro], | |
| outputs=[outliers_texto] | |
| ) | |
| # Limpar filtro | |
| btn_limpar.click( | |
| limpar_filtro_callback, | |
| inputs=[], | |
| outputs=[outliers_texto] | |
| ) | |
| # Atualizar resumo de outliers quando usuário edita o campo | |
| outliers_texto.change( | |
| atualizar_resumo_outliers, | |
| inputs=[estado_outliers_anteriores, outliers_texto], | |
| outputs=[txt_resumo_outliers] | |
| ) | |
| # Simular filtro também atualiza o resumo | |
| btn_simular.click( | |
| atualizar_resumo_outliers, | |
| inputs=[estado_outliers_anteriores, outliers_texto], | |
| outputs=[txt_resumo_outliers] | |
| ) | |
| # Download da base tratada (CSV) | |
| def download_base_callback(df_filtrado): | |
| """Callback para download da base tratada.""" | |
| if df_filtrado is None: | |
| return None | |
| caminho = exportar_base_csv(df_filtrado) | |
| return caminho | |
| btn_download_base.click( | |
| download_base_callback, | |
| inputs=[estado_df_filtrado], | |
| outputs=[download_base_file] | |
| ) | |
| # Botões "Adotar Sugestão" | |
| btn_adotar_1.click( | |
| lambda res, cols_x: adotar_sugestao(0, res, cols_x), | |
| inputs=[estado_resultados_busca, checkboxes_x], | |
| outputs=[transformacao_y] + transf_x_dropdowns | |
| ) | |
| btn_adotar_2.click( | |
| lambda res, cols_x: adotar_sugestao(1, res, cols_x), | |
| inputs=[estado_resultados_busca, checkboxes_x], | |
| outputs=[transformacao_y] + transf_x_dropdowns | |
| ) | |
| btn_adotar_3.click( | |
| lambda res, cols_x: adotar_sugestao(2, res, cols_x), | |
| inputs=[estado_resultados_busca, checkboxes_x], | |
| outputs=[transformacao_y] + transf_x_dropdowns | |
| ) | |
| btn_adotar_4.click( | |
| lambda res, cols_x: adotar_sugestao(3, res, cols_x), | |
| inputs=[estado_resultados_busca, checkboxes_x], | |
| outputs=[transformacao_y] + transf_x_dropdowns | |
| ) | |
| btn_adotar_5.click( | |
| lambda res, cols_x: adotar_sugestao(4, res, cols_x), | |
| inputs=[estado_resultados_busca, checkboxes_x], | |
| outputs=[transformacao_y] + transf_x_dropdowns | |
| ) | |
| # Ajustar modelo (usa dados filtrados se disponíveis) e calcula métricas de outliers | |
| btn_ajustar.click( | |
| lambda df_filt, df_orig, col_y, cols_x, transf_y, outliers_ant, *dd_vals: ajustar_modelo_callback( | |
| df_filt if df_filt is not None else df_orig, col_y, cols_x, transf_y, outliers_ant, *dd_vals | |
| ), | |
| inputs=[ | |
| estado_df_filtrado, estado_df, dropdown_y, checkboxes_x, | |
| transformacao_y, estado_outliers_anteriores | |
| ] + transf_x_dropdowns, | |
| outputs=[ | |
| estado_modelo, | |
| header_modelo, | |
| diagnosticos_html, | |
| tabela_coef, | |
| tabela_obs_calc, | |
| plot_obs_calc, | |
| plot_residuos, | |
| plot_hist, | |
| plot_cook, | |
| plot_corr, | |
| header_graficos, | |
| tabela_metricas, | |
| estado_metricas, | |
| header_outliers, | |
| txt_resumo_outliers | |
| ] | |
| ) | |
| # Reiniciar iteração (combina outliers e recomeça) e depois ajusta o modelo | |
| btn_reiniciar_iteracao.click( | |
| reiniciar_iteracao_callback, | |
| inputs=[ | |
| estado_df, estado_outliers_anteriores, outliers_texto, | |
| estado_iteracao, dropdown_y, checkboxes_x | |
| ], | |
| outputs=[ | |
| estado_outliers_anteriores, | |
| estado_iteracao, | |
| estado_df_filtrado, | |
| tabela_dados, | |
| tabela_estatisticas, | |
| header_estatisticas, | |
| txt_iteracao_atual, | |
| txt_n_outliers_anteriores, | |
| txt_lista_outliers_anteriores, | |
| accordion_outliers_anteriores, | |
| outliers_texto, | |
| tabela_metricas, | |
| estado_metricas, | |
| header_outliers, | |
| txt_resumo_outliers, | |
| mapa_html | |
| ] | |
| ).then( | |
| # Após reiniciar, ajusta modelo automaticamente para calcular métricas de outliers | |
| lambda df_filt, df_orig, col_y, cols_x, transf_y, outliers_ant, *dd_vals: ajustar_modelo_callback( | |
| df_filt if df_filt is not None else df_orig, col_y, cols_x, transf_y, outliers_ant, *dd_vals | |
| ), | |
| inputs=[ | |
| estado_df_filtrado, estado_df, dropdown_y, checkboxes_x, | |
| transformacao_y, estado_outliers_anteriores | |
| ] + transf_x_dropdowns, | |
| outputs=[ | |
| estado_modelo, | |
| header_modelo, | |
| diagnosticos_html, | |
| tabela_coef, | |
| tabela_obs_calc, | |
| plot_obs_calc, | |
| plot_residuos, | |
| plot_hist, | |
| plot_cook, | |
| plot_corr, | |
| header_graficos, | |
| tabela_metricas, | |
| estado_metricas, | |
| header_outliers, | |
| txt_resumo_outliers | |
| ] | |
| ) | |
| # Limpar histórico de outliers | |
| btn_limpar_historico.click( | |
| limpar_historico_callback, | |
| inputs=[estado_df, dropdown_y, checkboxes_x], | |
| outputs=[ | |
| estado_outliers_anteriores, | |
| estado_iteracao, | |
| estado_df_filtrado, | |
| tabela_dados, | |
| tabela_estatisticas, | |
| header_estatisticas, | |
| txt_iteracao_atual, | |
| txt_n_outliers_anteriores, | |
| txt_lista_outliers_anteriores, | |
| accordion_outliers_anteriores, | |
| outliers_texto, | |
| tabela_metricas, | |
| estado_metricas, | |
| header_outliers, | |
| txt_resumo_outliers, | |
| mapa_html | |
| ] | |
| ) | |
| # Exportar (usa dados filtrados se disponíveis) | |
| btn_exportar.click( | |
| lambda res_modelo, df_filt, df_orig, stats, nome: exportar_modelo_callback( | |
| res_modelo, df_filt if df_filt is not None else df_orig, stats, nome | |
| ), | |
| inputs=[estado_modelo, estado_df_filtrado, estado_df, tabela_estatisticas, nome_arquivo], | |
| outputs=[status_exportar] | |
| ) | |
| return app | |
| # ============================================================ | |
| # MAIN | |
| # ============================================================ | |
| if __name__ == "__main__": | |
| css = carregar_css() | |
| app = criar_interface() | |
| app.launch() | |