import pandas as pd import os import matplotlib.pyplot as plt import seaborn as sns # --- Configurações Globais --- # É recomendado que estes caminhos sejam configuráveis, talvez lidos de um arquivo de configuração # ou passados como argumentos, em vez de hardcoded. # Link original: https://cdn3.gnarususercontent.com.br/2927-pandas-selecao-agrupamento-dados/1-SEEG10_GERAL-BR_UF_2022.10.27-FINAL-SITE.xlsx EXCEL_FILE_PATH = 'D:\\Python para Data Science\\1-SEEG10_GERAL-BR_UF_2022.10.27-FINAL-SITE.xlsx' SHEET_NAME = 'GEE Estados' CSV_FILE_PATH = 'dados_transformados.csv' # --- Funções de Carregamento e Pré-processamento de Dados --- def load_excel_data(path: str, sheet_name: str) -> pd.DataFrame: """ Carrega dados de um arquivo Excel e lida com FileNotFoundError. """ print(f'Carregando dados do Excel de: {path} (aba: {sheet_name})...') try: return pd.read_excel(path, sheet_name=sheet_name) except FileNotFoundError: raise FileNotFoundError(f'Erro: O arquivo Excel não foi encontrado em: {path}. Verifique o caminho e o nome do arquivo.') except Exception as e: raise Exception(f'Erro inesperado ao carregar o Excel: {e}') def transform_dataframe_to_long_format(df: pd.DataFrame) -> pd.DataFrame: """ Transforma o DataFrame de formato wide (colunas de ano) para long, com 'Ano' e 'Emissões_Ano'. """ print('Transformando dados de formato wide para long...') # Identifica colunas de ano de forma mais robusta year_columns = [col for col in df.columns if isinstance(col, int) or (isinstance(col, str) and str(col).isdigit() and len(str(col)) == 4)] if not year_columns: raise ValueError('Nenhuma coluna de ano válida detectada para transformação.') # Define as colunas de identificação para o melt id_vars = [ 'Nível 1 - Setor', 'Nível 2', 'Nível 3', 'Nível 4', 'Nível 5', 'Nível 6', 'Emissão / Remoção / Bunker', 'Gás', 'Estado', 'Atividade Econômica', 'Produto' ] # Filtra id_vars para incluir apenas colunas existentes no DataFrame existing_id_vars = [col for col in id_vars if col in df.columns] df_long = df.melt( id_vars=existing_id_vars, value_vars=year_columns, var_name='Ano', value_name='Emissões_Ano' ) # Converte tipos de dados após o melt df_long['Ano'] = pd.to_numeric(df_long['Ano'], errors='coerce').astype('Int64') df_long['Emissões_Ano'] = pd.to_numeric(df_long['Emissões_Ano'], errors='coerce') return df_long def clean_and_optimize_dataframe(df: pd.DataFrame) -> pd.DataFrame: """ Seleciona colunas relevantes, remove linhas com nulos em colunas essenciais e otimiza tipos. """ print('Limpando e otimizando tipos de dados...') relevant_columns = [ 'Estado', 'Nível 1 - Setor', 'Atividade Econômica', 'Gás', 'Ano', 'Emissões_Ano' ] # Garante que apenas as colunas existentes sejam selecionadas df_clean = df[[col for col in relevant_columns if col in df.columns]].copy() original_rows = len(df_clean) required_subset = ['Ano', 'Emissões_Ano', 'Nível 1 - Setor', 'Atividade Econômica', 'Gás', 'Estado'] df_clean.dropna(subset=[col for col in required_subset if col in df_clean.columns], inplace=True) rows_removed = original_rows - len(df_clean) if rows_removed > 0: print(f'Removidas {rows_removed} linhas com valores nulos em colunas essenciais.') # Otimização de memória: converte strings para tipo 'category' quando apropriado for col in ['Estado', 'Nível 1 - Setor', 'Atividade Econômica', 'Gás']: if col in df_clean.columns: df_clean[col] = df_clean[col].astype('category') return df_clean def load_or_process_data(excel_path: str, sheet_name: str, csv_path: str) -> pd.DataFrame: """ Tenta carregar dados de um CSV cache. Se falhar, processa do Excel e salva em CSV. """ if os.path.exists(csv_path): try: print(f'Tentando carregar dados de: {csv_path} (cache)...') df_global = pd.read_csv(csv_path) # Re-aplica os tipos otimizados após carregar do CSV df_global['Ano'] = pd.to_numeric(df_global['Ano'], errors='coerce').astype('Int64') df_global['Emissões_Ano'] = pd.to_numeric(df_global['Emissões_Ano'], errors='coerce') for col in ['Estado', 'Nível 1 - Setor', 'Atividade Econômica', 'Gás']: if col in df_global.columns: df_global[col] = df_global[col].astype('category') print(f'Dados carregados com sucesso de: {csv_path}.') return df_global except (pd.errors.EmptyDataError, ValueError, FileNotFoundError, Exception) as e: print(f'Erro ao carregar ou validar o CSV existente ({e}). Recarregando e transformando do Excel.') # Cai para o processamento do Excel se o CSV estiver corrompido ou não encontrado print(f'Arquivo CSV \'{csv_path}\' não encontrado ou inválido. Processando do Excel pela primeira vez.') df_excel = load_excel_data(excel_path, sheet_name) df_long = transform_dataframe_to_long_format(df_excel) df_final = clean_and_optimize_dataframe(df_long) try: df_final.to_csv(csv_path, index=False) print(f'Dados transformados e salvos com sucesso como: {csv_path}.') except IOError as e: print(f'Erro de E/S ao tentar salvar o arquivo CSV: {e}. Verifique permissões ou se o arquivo está em uso.') raise # Levanta a exceção para interromper, já que o cache não foi salvo return df_final # --- Funções de Interação com o Usuário --- def get_unique_sorted_values(df: pd.DataFrame, column: str, display_name: str) -> list: """ Retorna e exibe valores únicos e ordenados de uma coluna. """ if column not in df.columns: print(f"Erro: Coluna '{column}' não encontrada no DataFrame.") return [] unique_values = sorted(df[column].dropna().unique().tolist()) if not unique_values: print(f'Nenhum(a) {display_name.lower()} disponível. Verifique os dados.') return [] print(f'\n--- {display_name}s disponíveis ---') for idx, val in enumerate(unique_values): print(f'{idx}: {val}') return unique_values def get_user_selection(available_values: list, prompt: str, allow_multiple: bool = True) -> list: """ Solicita ao usuário que selecione itens por índice(s). """ entrada = input(prompt).strip() if not entrada: # Retorna todos os valores se ENTER for pressionado e seleção múltipla permitida return available_values if allow_multiple else [] try: # Divide a entrada por vírgula e remove espaços em branco, convertendo para int indices = [int(i.strip()) for i in entrada.split(',') if i.strip()] selected_values = [] for i in indices: if 0 <= i < len(available_values): selected_values.append(available_values[i]) else: print(f'Aviso: Índice "{i}" fora do intervalo válido. Ignorando.') if not selected_values: print('Nenhum item válido selecionado. Digite índices válidos.') return [] if not allow_multiple and len(selected_values) > 1: print('Erro: Apenas um item pode ser selecionado para esta opção.') return [] return selected_values except ValueError: print('Erro: Entrada inválida. Digite números inteiros ou índices válidos separados por vírgula.') return [] def get_year_selection(available_years: list, prompt: str) -> list: """ Pede ao usuário para selecionar anos, permitindo índices, intervalo de índices ou anos literais. """ entrada = input(prompt).strip() if not entrada: return available_years # Seleciona todos os anos se ENTER try: selected_years = [] if ':' in entrada: # Tratamento de intervalo (ex: "2000:2010" ou "42:46") start_str, end_str = entrada.split(':') start_val = int(start_str.strip()) end_val = int(end_str.strip()) # Tenta interpretar como intervalo de índices if 0 <= start_val < len(available_years) and 0 <= end_val < len(available_years) and start_val <= end_val: print(f"Interpretando '{entrada}' como intervalo de ÍNDICES.") selected_years = [available_years[i] for i in range(start_val, end_val + 1)] # Tenta interpretar como intervalo de anos literais elif available_years and min(available_years) <= start_val <= max(available_years) and min(available_years) <= end_val <= max(available_years) and start_val <= end_val: print(f"Interpretando '{entrada}' como intervalo de ANOS LITERAIS.") selected_years = [year for year in available_years if start_val <= year <= end_val] else: print(f'Erro: Intervalo "{entrada}" inválido. Verifique os valores ou o formato.') return [] else: # Tratamento de anos individuais ou múltiplos (ex: "2021" ou "40,41") partes_entrada = [p.strip() for p in entrada.split(',') if p.strip()] potential_years_literal = [int(p) for p in partes_entrada if p.isdigit()] # Tenta converter tudo para int # Se todas as partes são números e correspondem a anos literais disponíveis if len(potential_years_literal) == len(partes_entrada) and all(y in available_years for y in potential_years_literal): print(f"Interpretando '{entrada}' como ANOS LITERAIS.") selected_years = potential_years_literal else: # Caso contrário, tenta como índices print(f"Interpretando '{entrada}' como ÍNDICES.") indices = [] for p in partes_entrada: try: idx = int(p) if 0 <= idx < len(available_years): indices.append(idx) else: print(f'Aviso: Índice "{p}" fora do intervalo válido. Ignorando.') except ValueError: print(f'Aviso: Entrada inválida "{p}". Ignorando.') selected_years = [available_years[i] for i in indices] if not selected_years: print(f'Nenhum ano válido encontrado para a seleção "{entrada}".') return [] return selected_years except ValueError as e: print(f'Erro na seleção de anos: {e}. Verifique o formato da entrada.') return [] except Exception as e: print(f'Ocorreu um erro inesperado ao processar a seleção de anos: {e}') return [] # --- Funções de Exibição de Dados e Gráficos --- def display_paginated_dataframe(df_to_display: pd.DataFrame, sort_column: str = 'Emissões_Ano'): """ Gerencia a paginação e exibição de um DataFrame. """ total_rows = len(df_to_display) if total_rows == 0: print("Não há linhas para exibir após os filtros.") return default_lines_to_show = 100 while True: try: prompt_lines = (f'Digite: ENTER para as primeiras {default_lines_to_show} linhas, ' f'um número para exibir as N primeiras, ou "inicio:fim" para um intervalo (ex: 1:100): ') entry_lines = input(prompt_lines).strip() start_row, end_row = 0, total_rows if not entry_lines: end_row = min(default_lines_to_show, total_rows) elif ':' in entry_lines: parts = entry_lines.split(':') if len(parts) == 2: start_input, end_input = parts[0].strip(), parts[1].strip() start_row = int(start_input) - 1 if start_input else 0 end_row = int(end_input) if end_input else total_rows else: print('Formato de intervalo inválido. Use "inicio:fim", "inicio:" ou ":fim".') continue else: num_lines = int(entry_lines) if num_lines <= 0: print('O número de linhas deve ser maior que zero.') continue end_row = min(num_lines, total_rows) if not (0 <= start_row < total_rows and start_row < end_row): print(f'Intervalo inválido. Linha inicial deve ser >= 1 e <= {total_rows}, e linha final maior que a inicial.') continue if end_row > total_rows: # Ajusta o final se exceder o total end_row = total_rows print(f'\n--- Exibindo linhas de {start_row + 1} a {end_row} (ordenadas por {sort_column} decrescente) ---') # Garante que o DataFrame esteja ordenado para a exibição paginada display_df_sorted = df_to_display.sort_values(by=sort_column, ascending=False).iloc[start_row:end_row] print(display_df_sorted.to_string(index=False, float_format="{:,.2f}".format)) break except ValueError: print('Entrada inválida. Digite números inteiros ou um formato de intervalo válido.') except Exception as e: print(f'Ocorreu um erro inesperado na paginação: {e}') def display_validity_stats(df: pd.DataFrame, column_for_validity: str = 'Emissões_Ano'): """ Exibe estatísticas de validade (contagem de valores nulos) para uma coluna específica. """ original_rows = len(df) valid_rows = df[column_for_validity].notna().sum() invalid_rows = original_rows - valid_rows print('\n--- Estatísticas de Validade Geral do DataFrame ---') print(f'Total de linhas carregadas: {original_rows}') print(f'Linhas com \'{column_for_validity}\' válidas: {valid_rows}') print(f'Linhas com \'{column_for_validity}\' nulas/inválidas (desconsideradas): {invalid_rows}') if original_rows > 0: perc_validas = (valid_rows / original_rows) * 100 print(f'Porcentagem de linhas válidas: {perc_validas:.2f}%') else: print('Não há dados para calcular porcentagens.') def _get_filtered_dataframe_segment(df: pd.DataFrame, filter_col: str, display_name: str, allow_multiple: bool = True) -> tuple[pd.DataFrame, list]: """ Função auxiliar para aplicar um filtro comum (setor, atividade, gás) a um DataFrame. Retorna o DataFrame filtrado e os valores escolhidos. """ available_values = get_unique_sorted_values(df, filter_col, display_name) if not available_values: return pd.DataFrame(), [] # Retorna DataFrame vazio se não houver valores disponíveis chosen_values = get_user_selection(available_values, f'Digite os índices d{"" if allow_multiple else "o"}s {display_name.lower()}s desejados (separados por vírgula), ou ENTER para {"todos" if allow_multiple else "este"}: ', allow_multiple=allow_multiple) if not chosen_values: return pd.DataFrame(), [] # Retorna DataFrame vazio se a seleção for inválida ou vazia df_filtered_result = df[df[filter_col].isin(chosen_values)].copy() print(f'\nDados filtrados para {display_name}s: {chosen_values}') return df_filtered_result, chosen_values # --- Opções do Menu Principal --- def visao_geral_estados(df: pd.DataFrame): """ Exibe a visão geral das emissões por Estado (soma e média), ordenada por soma decrescente. """ print('\n--- Visão Geral: Emissões por Estado ---') if 'Estado' not in df.columns: print("A coluna 'Estado' não foi encontrada no DataFrame. Verifique a fonte de dados.") return # Agrupa por Estado e calcula soma e média de Emissões_Ano emissions_by_state = df.groupby('Estado', observed=True)['Emissões_Ano'].agg( Soma_Emissoes='sum', Media_Emissoes='mean' ).reset_index() # Ordena do maior para o menor pela Soma_Emissoes emissions_by_state_sorted = emissions_by_state.sort_values(by='Soma_Emissoes', ascending=False) if emissions_by_state_sorted.empty: print("Nenhum dado de emissão encontrado para os Estados.") return print(emissions_by_state_sorted.to_string(index=False, float_format="{:,.2f}".format)) def consulta_detalhada(df: pd.DataFrame): """ Permite uma consulta detalhada de emissões por Estado, Setor, Ano e Gás. Apresenta a soma das emissões por Estado e as estatísticas descritivas. """ df_current = df.copy() # 1. Escolher 1 ou mais Estados df_current, chosen_states = _get_filtered_dataframe_segment(df_current, 'Estado', 'Estado') if df_current.empty: return # 2. Escolher Nível 1 - Setor df_current, chosen_sectors = _get_filtered_dataframe_segment(df_current, 'Nível 1 - Setor', 'Setor (Nível 1)') if df_current.empty: return # 3. Escolher o Ano available_years = get_unique_sorted_values(df_current, 'Ano', 'Ano') if not available_years: return chosen_years = get_year_selection(available_years, 'Digite os anos desejados (ex: "2000,2005" para anos específicos, "42:46" para intervalo de índices, ou "2000:2010" para intervalo de anos), ou ENTER para todos: ') if not chosen_years: print('Nenhum ano válido selecionado. Retornando ao menu.') return df_current = df_current[df_current['Ano'].isin(chosen_years)].copy() print(f'Dados filtrados para Anos: {chosen_years}') # 4. Escolher o tipo de Gás df_current, chosen_gases = _get_filtered_dataframe_segment(df_current, 'Gás', 'Gás') if df_current.empty: return df_current.dropna(subset=['Emissões_Ano'], inplace=True) if df_current.empty: print('Nenhum dado válido encontrado para esta combinação de filtros.') return print('\n--- Resultados Detalhados por Estado (Soma de Emissões e Describe()) ---') # Calcula a soma das emissões por Estado e ordena emissao_por_estado_sum = df_current.groupby('Estado', observed=True)['Emissões_Ano'].sum().reset_index() emissao_por_estado_sum.rename(columns={'Emissões_Ano': 'Emissões_Totais'}, inplace=True) emissao_por_estado_sum.sort_values(by='Emissões_Totais', ascending=False, inplace=True) # Coleta estatísticas descritivas para cada estado ordenado describe_results = [] for estado in emissao_por_estado_sum['Estado']: df_estado = df_current[df_current['Estado'] == estado].copy() if not df_estado.empty: desc = df_estado['Emissões_Ano'].describe().to_frame(name=estado).T describe_results.append(desc) if describe_results: df_describe_combined = pd.concat(describe_results) # Mescla a soma total com as estatísticas descritivas final_output_df = pd.merge(emissao_por_estado_sum, df_describe_combined.reset_index().rename(columns={'index': 'Estado'}), on='Estado', how='left') # Reordena colunas para ter 'Estado' e 'Emissões_Totais' no início cols = ['Estado', 'Emissões_Totais'] + [col for col in final_output_df.columns if col not in ['Estado', 'Emissões_Totais']] final_output_df = final_output_df[cols] print(final_output_df.to_string(index=False, float_format="{:,.2f}".format)) else: print("Nenhum dado para gerar estatísticas descritivas ou soma para os Estados selecionados.") def plot_general_evolution(df_state: pd.DataFrame, state_name: str): """ Gera o gráfico geral de evolução de emissões para um estado, com média e pico. """ emissao_total_por_ano = df_state.groupby('Ano', observed=True)['Emissões_Ano'].sum().reset_index() emissao_total_por_ano.sort_values(by='Ano', inplace=True) if emissao_total_por_ano.empty: print(f'Nenhuma emissão total encontrada para o Estado "{state_name}" ao longo dos anos.') return plt.figure(figsize=(12, 7)) sns.lineplot(x='Ano', y='Emissões_Ano', data=emissao_total_por_ano, marker='o', linewidth=2, color='blue', label='Emissão Total') # Linha da média media_emissao = emissao_total_por_ano['Emissões_Ano'].mean() plt.axhline(y=media_emissao, color='orange', linestyle='--', label=f'Média Geral: {media_emissao:,.2f}') # Marcador de pico pico_emissao_idx = emissao_total_por_ano['Emissões_Ano'].idxmax() pico_emissao_ano = emissao_total_por_ano.loc[pico_emissao_idx, 'Ano'] pico_emissao_valor = emissao_total_por_ano.loc[pico_emissao_idx, 'Emissões_Ano'] plt.plot(pico_emissao_ano, pico_emissao_valor, 'o', markersize=8, color='black', label=f'Pico: {pico_emissao_valor:,.2f} ({pico_emissao_ano})') plt.xlabel('Ano') plt.ylabel('Emissões (toneladas CO2e)') plt.title(f'Evolução Geral das Emissões no Estado: {state_name}') plt.xticks(emissao_total_por_ano['Ano'].unique(), rotation=45, ha='right') plt.grid(True, linestyle='--', alpha=0.6) plt.legend() plt.tight_layout() plt.show() def plot_activity_evolution(df_state: pd.DataFrame, state_name: str): """ Gera gráfico de evolução por atividade econômica para um estado, com média e pico por atividade. """ df_activities = df_state.dropna(subset=['Atividade Econômica']) if df_activities.empty: print(f'Nenhum dado de atividade econômica válido encontrado para o Estado "{state_name}".') return available_activities = get_unique_sorted_values(df_activities, 'Atividade Econômica', 'Atividade Econômica') if not available_activities: return chosen_activities = get_user_selection(available_activities, 'Digite os índices das Atividades Econômicas desejadas (separadas por vírgula), ou ENTER para selecionar TODAS: ', allow_multiple=True) if not chosen_activities: print('Nenhuma Atividade Econômica válida selecionada. Retornando ao menu de gráficos.') return df_plot = df_activities[df_activities['Atividade Econômica'].isin(chosen_activities)].copy() if df_plot.empty: print(f'Nenhum dado encontrado para as atividades selecionadas no Estado "{state_name}".') return emissao_por_ano_atividade = df_plot.groupby(['Ano', 'Atividade Econômica'], observed=True)['Emissões_Ano'].sum().reset_index() emissao_por_ano_atividade.sort_values(by='Ano', inplace=True) if emissao_por_ano_atividade.empty: print(f'Nenhuma emissão encontrada para as atividades selecionadas no Estado "{state_name}" ao longo dos anos.') return plt.figure(figsize=(15, 8)) sns.lineplot(x='Ano', y='Emissões_Ano', hue='Atividade Econômica', data=emissao_por_ano_atividade, marker='o', linewidth=2) # Adicionar média e pico para CADA ATIVIDADE selecionada # Criar listas separadas para handles e labels para uma legenda mais organizada custom_handles = [] custom_labels = [] # Primeiro, coletar as handles e labels padrão do sns.lineplot current_handles, current_labels = plt.gca().get_legend_handles_labels() custom_handles.extend(current_handles) custom_labels.extend(current_labels) for activity in chosen_activities: df_activity_single = emissao_por_ano_atividade[emissao_por_ano_atividade['Atividade Econômica'] == activity] if not df_activity_single.empty: # Média media_activity = df_activity_single['Emissões_Ano'].mean() # Adiciona um ponto para a média no primeiro ano disponível da atividade para a legenda mean_handle, = plt.plot(df_activity_single['Ano'].iloc[0], media_activity, 's', markersize=7, color='orange', alpha=0.7, label=f'Média {activity}') custom_handles.append(mean_handle) custom_labels.append(f'Média {activity}') # Linha tracejada da média plt.plot(df_activity_single['Ano'], [media_activity] * len(df_activity_single), linestyle=':', color='orange', alpha=0.5, zorder=0) # Zorder para ficar atrás da linha principal # Pico pico_activity_idx = df_activity_single['Emissões_Ano'].idxmax() pico_activity_ano = df_activity_single.loc[pico_activity_idx, 'Ano'] pico_activity_valor = df_activity_single.loc[pico_activity_idx, 'Emissões_Ano'] peak_handle, = plt.plot(pico_activity_ano, pico_activity_valor, 'D', markersize=7, color='black', alpha=0.7, label=f'Pico {activity}') custom_handles.append(peak_handle) custom_labels.append(f'Pico {activity}') # Criar um dicionário para remover rótulos duplicados mantendo a ordem de adição unique_legend_elements = {} for h, l in zip(custom_handles, custom_labels): if l not in unique_legend_elements: unique_legend_elements[l] = h plt.xlabel('Ano') plt.ylabel('Emissões (toneladas CO2e)') plt.title(f'Evolução das Emissões por Atividade Econômica no Estado: {state_name}') plt.xticks(emissao_por_ano_atividade['Ano'].unique(), rotation=45, ha='right') plt.grid(True, linestyle='--', alpha=0.6) plt.legend(unique_legend_elements.values(), unique_legend_elements.keys(), title='Atividade Econômica & Detalhes', bbox_to_anchor=(1.05, 1), loc='upper left') plt.tight_layout() plt.show() def graficos_por_estado(df: pd.DataFrame): """ Permite escolher um Estado e, em seguida, uma opção de gráfico (Geral ou por Atividade). """ if 'Estado' not in df.columns: print("A coluna 'Estado' não foi encontrada no DataFrame. Verifique a fonte de dados.") return available_states = get_unique_sorted_values(df, 'Estado', 'Estado') if not available_states: return state_chosen_list = get_user_selection(available_states, 'Digite o índice do Estado desejado para o gráfico (APENAS UM): ', allow_multiple=False) if not state_chosen_list: return state_chosen = state_chosen_list[0] df_state = df[df['Estado'] == state_chosen].copy() df_state.dropna(subset=['Emissões_Ano', 'Ano'], inplace=True) if df_state.empty: print(f'Nenhum dado válido encontrado para o Estado "{state_chosen}" para gerar o gráfico.') return while True: print(f'\n--- Opções de Gráfico para o Estado: {state_chosen} ---') print('1 - Gráfico Geral de Evolução (Média e Pico)') print('2 - Gráfico por Atividade Econômica (Média e Pico por Atividade)') print('0 - Voltar ao Menu Principal') opcao_grafico = input('Digite o número da opção desejada: ').strip() if opcao_grafico == '1': plot_general_evolution(df_state, state_chosen) elif opcao_grafico == '2': plot_activity_evolution(df_state, state_chosen) elif opcao_grafico == '0': print('Voltando ao Menu Principal.') break else: print('Opção inválida. Digite 1, 2 ou 0.') # --- Função de Menu Principal --- def display_menu(df: pd.DataFrame): """ Exibe o menu de opções principal para o usuário e gerencia a navegação. """ while True: print('\n====== MENU DE CONSULTA E VISUALIZAÇÃO DE EMISSÕES ======') print('1 - Visão geral (Estados: Média e Soma de Emissões)') print('2 - Consulta Detalhada') print('3 - Gráficos por Estado') print('0 - Sair do Programa') opcao = input('Digite o número da opção desejada: ').strip() if opcao == '1': visao_geral_estados(df) elif opcao == '2': consulta_detalhada(df) elif opcao == '3': graficos_por_estado(df) elif opcao == '0': print('Encerrando o programa. Até a próxima!') break else: print('Opção inválida. Digite um número entre 0 e 3.') # --- Bloco Principal de Execução --- if __name__ == '__main__': df_global = None try: df_global = load_or_process_data(EXCEL_FILE_PATH, SHEET_NAME, CSV_FILE_PATH) if df_global is not None: display_validity_stats(df_global) display_menu(df_global) except Exception as e: print(f'O programa foi encerrado devido a um erro crítico: {e}')