import pandas as pd import os import matplotlib.pyplot as plt import seaborn as sns # --- Configurações globais do sistema --- #Link arquivo 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' # Define os 5 setores principais para os gráficos SETORES_GRAFICO = [ 'Agropecuária', 'Energia', 'Mudança de Uso da Terra e Floresta', 'Processos Industriais', 'Resíduos' ] # --- Função: Carregamento e Transformação do DataFrame --- def load_and_transform_dataframe(path: str) -> pd.DataFrame: ''' Lê o arquivo Excel, transforma dados de wide para long format (Anos em linhas) e trata valores nulos nas colunas principais. ''' print('Carregando dados do Excel e realizando a transformação inicial (pode demorar na primeira vez)...') try: df = pd.read_excel(path, sheet_name=SHEET_NAME) except FileNotFoundError: print(f'Erro: O arquivo Excel não foi encontrado no caminho especificado: {path}') print('Por favor, verifique se o caminho e o nome do arquivo estão corretos.') raise year_columns = [col for col in df.columns if isinstance(col, int) or (isinstance(col, str) and str(col).isdigit())] if not year_columns: raise ValueError('Nenhuma coluna de ano detectada no arquivo Excel. Verifique se as colunas de ano estão como números inteiros ou strings numéricas.') 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' ] existing_id_vars = [col for col in id_vars if col in df.columns] if len(existing_id_vars) != len(id_vars): missing_ids = set(id_vars) - set(existing_id_vars) print(f'Atenção: As seguintes colunas de ID esperadas não foram encontradas no Excel: {missing_ids}') print('Continuando a transformação apenas com as colunas de ID existentes.') id_vars = existing_id_vars print(f'Anos detectados para transformação: {year_columns[:5]} ... {year_columns[-5:]}') df_long = df.melt( id_vars=id_vars, value_vars=year_columns, var_name='Ano', value_name='Emissões_Ano' ) 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') print('\nPrimeiras linhas após melt e conversão de tipo:') print(df_long.head()) print(f'Tipos de dados das colunas principais após melt: \n{df_long[['Ano', 'Emissões_Ano']].dtypes}') original_rows = len(df_long) df_long.dropna(subset=['Ano', 'Emissões_Ano'], inplace=True) rows_after_dropna = len(df_long) if original_rows > rows_after_dropna: print(f'Removidas {original_rows - rows_after_dropna} linhas com valores nulos em \'Ano\' ou \'Emissões_Ano\'.') print(f'Total de linhas válidas após limpeza: {rows_after_dropna}') else: print('Nenhuma linha com valores nulos em \'Ano\' ou \'Emissões_Ano\' encontrada para remoção.') print(f'Total de linhas válidas: {rows_after_dropna}') try: df_long.to_csv(CSV_FILE_PATH, index=False) print(f'\nDados transformados e salvos com sucesso como: {CSV_FILE_PATH}') except IOError as e: print(f'\nErro de E/S ao tentar salvar o arquivo CSV: {e}') print('Isso pode ocorrer se o arquivo estiver em uso por outro programa (ex: Excel).') print('Por favor, feche qualquer programa que possa estar usando o arquivo e tente novamente.') raise return df_long # --- Função: Obtém valores únicos e ordenados de uma coluna --- def get_unique_sorted_values(df: pd.DataFrame, column: str, display_name: str) -> list: ''' Retorna uma lista de valores únicos e ordenados de uma coluna, com tratamento de nulos, e exibe esses valores para o usuário. ''' unique_values = sorted(df[column].dropna().unique().tolist()) if not unique_values: print(f'Nenhum {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 # --- Função: Solicita e valida a seleção do usuário --- def get_user_selection(available_values: list, prompt: str) -> list: ''' Pede ao usuário para selecionar itens de uma lista e retorna os valores selecionados. Lida com entradas inválidas e retorna uma lista vazia em caso de erro ou sem seleção. ''' entrada = input(prompt).strip() if not entrada: return [] try: indices = [int(i.strip()) for i in entrada.split(',') if i.strip()] selected_values = [available_values[i] for i in indices if 0 <= i < len(available_values)] if not selected_values: raise ValueError('Nenhum item válido selecionado. Por favor, digite índices válidos.') return selected_values except (ValueError, IndexError) as e: print(f'Erro na seleção: {e}. Certifique-se de digitar números válidos e dentro do intervalo.') return [] # --- Função: Exibe dados filtrados e estatísticas de validade --- def display_filtered_data_and_stats( df_filtered: pd.DataFrame, title: str, num_linhas_default: int = 100, sort_column: str = 'Emissões_Ano' ): ''' Exibe uma prévia do DataFrame filtrado, ordenado por uma coluna específica, e apresenta estatísticas de linhas válidas e inválidas. ''' if df_filtered.empty: print(f'\n{title}: Nenhum dado encontrado para a sua seleção.') return df_display = df_filtered.copy() original_rows_in_selection = len(df_display) df_display.dropna(subset=[sort_column], inplace=True) valid_rows_for_display = len(df_display) invalid_rows_for_display = original_rows_in_selection - valid_rows_for_display if df_display.empty: print(f'\n{title}: Nenhum dado válido encontrado após a filtragem de nulos na coluna \'{sort_column}\'.') return df_display.sort_values(by=sort_column, ascending=False, inplace=True) num_linhas_str = input(f'Quantas linhas deseja exibir? (Padrão: {num_linhas_default}. Deixe vazio para o padrão): ').strip() num_linhas = num_linhas_default if num_linhas_str: try: num_linhas = int(num_linhas_str) if num_linhas <= 0: print(f'Número de linhas inválido. Exibindo {num_linhas_default} linhas por padrão.') num_linhas = num_linhas_default except ValueError: print(f'Entrada inválida. Exibindo {num_linhas_default} linhas por padrão.') print(f'\n--- {title}: Primeiras {num_linhas} linhas (ordenadas por \'{sort_column}\' decrescente) ---\n') print(df_display.head(num_linhas)) print('\n--- Estatísticas de Validade ---') print(f'Total de linhas na seleção inicial: {original_rows_in_selection}') print(f'Linhas válidas para exibição/análise na coluna \'{sort_column}\': {valid_rows_for_display}') print(f'Linhas nulas/inválidas em \'{sort_column}\' (desconsideradas): {invalid_rows_for_display}') if original_rows_in_selection > 0: perc_validas = (valid_rows_for_display / original_rows_in_selection) * 100 perc_invalidas = (invalid_rows_for_display / original_rows_in_selection) * 100 print(f'Porcentagem de linhas válidas: {perc_validas:.2f}%') print(f'Porcentagem de linhas nulas/inválidas: {perc_invalidas:.2f}%') else: print('Não há dados para calcular porcentagens.') # --- Opção 1 do Menu: Exibe grupos de setores --- def show_sector_groups(df: pd.DataFrame): ''' Exibe todos os grupos de setores (Nível 1 - Setor) disponíveis no DataFrame. ''' setores = get_unique_sorted_values(df, 'Nível 1 - Setor', 'Setor') if not setores: return # --- Opção 2 do Menu: Mostra dados detalhados por grupo de setor --- def dados_por_grupo(df: pd.DataFrame): ''' Filtra e exibe dados detalhados para os grupos de setor selecionados pelo usuário, incluindo ordenação e estatísticas de validade. ''' setores_disponiveis = get_unique_sorted_values(df, 'Nível 1 - Setor', 'Setor') if not setores_disponiveis: return setores_escolhidos = get_user_selection(setores_disponiveis, 'Digite os índices dos grupos desejados, separados por vírgula: ') if not setores_escolhidos: return dados_filtrados = df[df['Nível 1 - Setor'].isin(setores_escolhidos)] display_filtered_data_and_stats( dados_filtrados, f'Dados Detalhados para Setores: {setores_escolhidos}', num_linhas_default=100 ) # --- Opção 3 do Menu: Consulta emissões por ano e setor com describe() --- def consulta_por_ano(df: pd.DataFrame): ''' Permite ao usuário consultar emissões por ano e setor, exibindo estatísticas descritivas (describe()) e de validade. ''' anos_disponiveis = get_unique_sorted_values(df, 'Ano', 'Ano') if not anos_disponiveis: return anos_escolhidos = get_user_selection(anos_disponiveis, 'Digite os índices dos anos desejados, separados por vírgula: ') if not anos_escolhidos: return setores_disponiveis = get_unique_sorted_values(df, 'Nível 1 - Setor', 'Setor') if not setores_disponiveis: return setores_escolhidos = get_user_selection(setores_disponiveis, 'Digite os índices dos setores desejados, separados por vírgula: ') if not setores_escolhidos: return dados_filtrados = df[ (df['Ano'].isin(anos_escolhidos)) & (df['Nível 1 - Setor'].isin(setores_escolhidos)) ].copy() if dados_filtrados.empty: print('\nNenhum dado encontrado para os anos e setores selecionados. Por favor, tente outras combinações.') return original_rows_filtered = len(dados_filtrados) dados_para_describe = dados_filtrados.dropna(subset=['Emissões_Ano']).copy() valid_rows_count = len(dados_para_describe) invalid_rows_count = original_rows_filtered - valid_rows_count print(f'\nDados filtrados para Anos: {anos_escolhidos} e Setores: {setores_escolhidos}') if dados_para_describe.empty: print('Nenhum dado válido de \'Emissões_Ano\' encontrado para os anos e setores selecionados após a filtragem de nulos.') return dados_para_describe.sort_values(by='Emissões_Ano', ascending=False, inplace=True) print('\n--- Primeiras 10 linhas dos dados filtrados (ordenadas por \'Emissões_Ano\' decrescente) ---') print(dados_para_describe.head(10)) print('\n--- Estatísticas Descritivas das \'Emissões_Ano\' ---') print(dados_para_describe['Emissões_Ano'].describe()) print('\n--- Estatísticas de Validade ---') print(f'Total de linhas na seleção inicial: {original_rows_filtered}') print(f'Linhas com \'Emissões_Ano\' válidas: {valid_rows_count}') print(f'Linhas com \'Emissões_Ano\' nulas/inválidas (desconsideradas): {invalid_rows_count}') if original_rows_filtered > 0: perc_validas = (valid_rows_count / original_rows_filtered) * 100 perc_invalidas = (invalid_rows_count / original_rows_filtered) * 100 print(f'Porcentagem de linhas válidas: {perc_validas:.2f}%') print(f'Porcentagem de linhas nulas/inválidas: {perc_invalidas:.2f}%') else: print('Não há dados para calcular porcentagens.') # --- Opção 4 do Menu: Gera gráficos de barras de emissões por setor --- def gerar_graficos(df: pd.DataFrame): ''' Gera um gráfico de barras horizontais das Emissões_Ano para os 5 setores principais em um ano escolhido, mostrando porcentagens. ''' anos_disponiveis = get_unique_sorted_values(df, 'Ano', 'Ano') if not anos_disponiveis: return anos_escolhidos = get_user_selection(anos_disponiveis, 'Digite o índice do ano desejado para o gráfico (apenas um ano): ') if not anos_escolhidos or len(anos_escolhidos) > 1: print('Por favor, selecione *apenas um* ano para o gráfico.') return ano_escolhido = anos_escolhidos[0] print(f'\nGerando gráfico para o ano: {ano_escolhido}') df_ano_setores = df[ (df['Ano'] == ano_escolhido) & (df['Nível 1 - Setor'].isin(SETORES_GRAFICO)) ].copy() df_ano_setores['Emissões_Ano'] = pd.to_numeric(df_ano_setores['Emissões_Ano'], errors='coerce') df_ano_setores.dropna(subset=['Emissões_Ano'], inplace=True) if df_ano_setores.empty: print(f'Nenhum dado válido encontrado para os setores principais no ano {ano_escolhido}.') return emissao_por_setor = df_ano_setores.groupby('Nível 1 - Setor')['Emissões_Ano'].sum().reset_index() for setor_esperado in SETORES_GRAFICO: if setor_esperado not in emissao_por_setor['Nível 1 - Setor'].values: emissao_por_setor.loc[len(emissao_por_setor)] = {'Nível 1 - Setor': setor_esperado, 'Emissões_Ano': 0} emissao_por_setor.sort_values(by='Emissões_Ano', ascending=False, inplace=True) total_emissao_ano = emissao_por_setor['Emissões_Ano'].sum() if total_emissao_ano == 0: print(f'Total de emissões para o ano {ano_escolhido} é zero. Não é possível gerar o gráfico de porcentagens.') return emissao_por_setor['Porcentagem'] = (emissao_por_setor['Emissões_Ano'] / total_emissao_ano) * 100 plt.figure(figsize=(12, 8)) sns.barplot( x='Emissões_Ano', y='Nível 1 - Setor', data=emissao_por_setor, palette='viridis', order=emissao_por_setor['Nível 1 - Setor'] ) plt.xlabel('Emissões (toneladas CO2e)') plt.ylabel('Setor') plt.title(f'Emissões por Setor (Principais) no Ano {ano_escolhido}') plt.grid(axis='x', linestyle='--', alpha=0.7) for index, row in emissao_por_setor.iterrows(): plt.text( row['Emissões_Ano'] + (emissao_por_setor['Emissões_Ano'].max() * 0.01), index, f'{row["Porcentagem"]:.1f}%', color='black', ha='left', va='center' ) plt.tight_layout() plt.show() # --- Função: Exibe o menu principal e gerencia a interação do usuário --- def display_menu(df: pd.DataFrame): ''' Exibe o menu de opções para o usuário e chama a função correspondente à escolha. ''' while True: print('\n====== MENU DE CONSULTA E VISUALIZAÇÃO DE EMISSÕES ======') print('1 - Ver Grupos de Setores (Nível 1 - Setor)') print('2 - Ver Dados Detalhados por Grupo de Setor') print('3 - Realizar Consulta de Emissões por Ano e Setor') print('4 - Gerar Gráfico de Emissões por Setor (Ano Específico)') print('0 - Sair do Programa') opcao = input('Digite o número da opção desejada: ').strip() if opcao == '1': show_sector_groups(df) elif opcao == '2': dados_por_grupo(df) elif opcao == '3': consulta_por_ano(df) elif opcao == '4': gerar_graficos(df) elif opcao == '0': print('Encerrando o programa. Até a próxima!') break else: print('Opção inválida. Por favor, digite um número entre 0 e 4.') # --- Bloco Principal de Execução --- if __name__ == '__main__': ''' Ponto de entrada do programa. Tenta carregar dados de um CSV otimizado, ou processa o Excel original se o CSV não existir ou for inválido. Inicia o menu interativo com o DataFrame carregado. ''' df_global = None try: if os.path.exists(CSV_FILE_PATH): try: print(f'Tentando carregar dados de: {CSV_FILE_PATH} (carregamento rápido)...') df_global = pd.read_csv(CSV_FILE_PATH) if 'Ano' not in df_global.columns or 'Emissões_Ano' not in df_global.columns or 'Nível 1 - Setor' not in df_global.columns: raise ValueError('CSV inválido ou incompleto.') 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') original_rows_loaded = len(df_global) df_global.dropna(subset=['Ano', 'Emissões_Ano', 'Nível 1 - Setor'], inplace=True) if len(df_global) < original_rows_loaded: print(f'Removidas {original_rows_loaded - len(df_global)} linhas com nulos essenciais no CSV carregado.') print(f'Dados carregados com sucesso de: {CSV_FILE_PATH}') except (pd.errors.EmptyDataError, ValueError, FileNotFoundError) as e: print(f'Erro ao carregar ou validar o CSV existente ({e}). Recarregando e transformando do Excel.') df_global = load_and_transform_dataframe(EXCEL_FILE_PATH) except Exception as e: print(f'Erro inesperado ao tentar carregar o CSV: {e}. Recarregando e transformando do Excel.') df_global = load_and_transform_dataframe(EXCEL_FILE_PATH) else: print(f'Arquivo CSV \'{CSV_FILE_PATH}\' não encontrado. Carregando e transformando do Excel pela primeira vez.') df_global = load_and_transform_dataframe(EXCEL_FILE_PATH) if df_global is None or df_global.empty: print('Não foi possível carregar ou processar os dados. Encerrando o programa.') exit() display_menu(df_global) except Exception as e: print(f'\nOcorreu um erro crítico e inesperado durante a execução principal do programa: {e}') print('Por favor, verifique o erro acima e as configurações do script.')