doc: Comentários a cada função

fix: remover código morto ou desnecessário
This commit is contained in:
2025-12-13 12:16:20 -01:00
parent 96aaeed19f
commit 3e0814057f
10 changed files with 1287 additions and 468 deletions

View File

@@ -2,6 +2,7 @@
## Como utilizar ## Como utilizar
Correr o ficheiro `earthquakes.py` usando `python earthquakes.py` Correr o ficheiro `earthquakes.py` usando `python earthquakes.py`
Garantir que o ficheiro de dados está no mesmo diretório que o ficheiro `earthquakes.py`
## Objectivos ## Objectivos
@@ -36,7 +37,7 @@ First, let's represent the data using Python's Pandas module and implement CRUD
## Prazos ## Prazos
- T1 a T4 -> 10 de novembro - T1 a T4 -> 10 de novembro
- (a definir) - T5 a T7 -> 14 de dezembro
## Apontamentos ## Apontamentos
Dados parecem estar no formato [Nordic](https://seisan.info/v13/node259.html) Dados parecem estar no formato [Nordic](https://seisan.info/v13/node259.html)

View File

@@ -5,43 +5,63 @@ import json
import os import os
import sys import sys
from datetime import datetime from datetime import datetime
from typing import Any
import pandas as pd import pandas as pd
from utils import parser, crud, stats, utils, visuals, filters from utils import crud, filters, parser, stats, utils, visuals
HEADER = """=== Terramotos ===""" HEADER = """=== Terramotos ==="""
EVENT_COLS = ["Data", "Latitude", "Longitude", "Profundidade", "Tipo Evento", "Gap", "Magnitudes", "Regiao", "Sentido", "Pub", "SZ", "VZ"] EVENT_COLS = [
STATION_COLS = ["Estacao", "Hora", "Min", "Seg", "Componente", "Distancia Epicentro", "Tipo Onda"] "Data",
"Latitude",
"Longitude",
"Profundidade",
"Tipo Evento",
"Gap",
"Magnitudes",
"Regiao",
"Sentido",
"Pub",
"SZ",
"VZ",
]
STATION_COLS = [
"Estacao",
"Hora",
"Min",
"Seg",
"Componente",
"Distancia Epicentro",
"Tipo Onda",
]
MENU = """[1] Criar a base de dados MENU = """[1] Criar a base de dados
[3] Apagar um evento [2] Apagar um evento
[4] Apagar uma entrada de um evento [3] Apagar uma entrada de um evento
[5] Visualizar um evento [4] Visualizar um evento
[6] Guardar como JSON [5] Guardar como JSON
[7] Guardar como CSV [6] Guardar como CSV
[8] Estatísticas [7] Estatísticas
[9] Criar uma entrada [8] Criar uma entrada
[10] Gráficos [9] Gráficos
[11] Filtros (T7) [10] Filtros (T7)
[Q] Sair [Q] Sair
""" """
def guardar_json(df: pd.DataFrame, fname: str) -> bool:
_retValues = utils.create_dict_struct(df, EVENT_COLS, None)
with open(fname , "w") as fp:
try:
json.dump(_retValues, fp)
except:
return False
return True
def guardar_csv(df: pd.DataFrame, fname: str): def guardar_csv(df: pd.DataFrame, fname: str):
"""Guarda uma DataFrame num ficheiro csv
Args:
df (pd.DataFrame): Dataframe com os dados
fname (str): nome do ficheiro csv
Returns:
bool: Retorna se a operação foi bem sucedida ou não
"""
with open(fname, "w") as fp: with open(fname, "w") as fp:
try: try:
df.to_csv(fp, index=False) df.to_csv(fp, index=False)
@@ -51,6 +71,12 @@ def guardar_csv(df: pd.DataFrame, fname: str):
def main(): def main():
"""Ponto de entrada do programa.
Constituido por um while loop a correr um menu onde o utilizador pode
interagir com os vários módulos implementados.
"""
isRunning = True isRunning = True
db = None db = None
original_db = None original_db = None
@@ -79,10 +105,10 @@ def main():
else: else:
input("Base de dados não encontrada. Por favor tenta de novo.") input("Base de dados não encontrada. Por favor tenta de novo.")
case "3": case "2":
if db is not None: if db is not None:
crud.read_ids(db) crud.read_ids(db)
choice = _get_usr_input("Escolhe o ID para apagar: ", int) choice: int = _get_usr_input("Escolhe o ID para apagar: ", int)
if not _event_exists(db, choice): if not _event_exists(db, choice):
retInfo = "ID do event não encontrado!" retInfo = "ID do event não encontrado!"
@@ -94,11 +120,10 @@ def main():
else: else:
retInfo = "Base de dados não encontrada!" retInfo = "Base de dados não encontrada!"
case "3":
case "4":
if db is not None: if db is not None:
crud.read_ids(db) crud.read_ids(db)
eid_choice = _get_usr_input("Escolhe o ID: ", int) eid_choice: int = _get_usr_input("Escolhe o ID: ", int)
if not _event_exists(db, eid_choice): if not _event_exists(db, eid_choice):
retInfo = "ID do event não encontrado!" retInfo = "ID do event não encontrado!"
@@ -119,7 +144,7 @@ def main():
else: else:
retInfo = "Base de dados não encontrada!" retInfo = "Base de dados não encontrada!"
case "5": case "4":
if db is not None: if db is not None:
crud.read_ids(db) crud.read_ids(db)
choice = _get_usr_input("Escolhe o ID para ver os dados: ", int) choice = _get_usr_input("Escolhe o ID para ver os dados: ", int)
@@ -137,16 +162,16 @@ def main():
else: else:
retInfo = "Base de dados não encontrada!" retInfo = "Base de dados não encontrada!"
case "6": case "5":
if db is not None: if db is not None:
fname = _get_usr_input("Nome do ficheiro a guardar? ") fname = _get_usr_input("Nome do ficheiro a guardar? ")
if fname is None: if fname is None:
fname = "valores.json" fname = "valores.json"
utils.save_as_json(db, fname, EVENT_COLS, STATION_COLS) utils.save_as_json(db, fname, EVENT_COLS)
else: else:
retInfo = "Base de dados não encontrada!" retInfo = "Base de dados não encontrada!"
case "7": case "6":
if db is not None: if db is not None:
fname = _get_usr_input("Nome do ficheiro a guardar? ") fname = _get_usr_input("Nome do ficheiro a guardar? ")
if fname is None: if fname is None:
@@ -155,13 +180,13 @@ def main():
else: else:
retInfo = "Base de dados não encontrada!" retInfo = "Base de dados não encontrada!"
case "8": case "7":
if db is not None: if db is not None:
stats.stat_menu(db) stats.stat_menu(db)
else: else:
retInfo = "Base de dados não encontrada!" retInfo = "Base de dados não encontrada!"
case "9": case "8":
if db is not None: if db is not None:
crud.read_ids(db) crud.read_ids(db)
eid_choice = _get_usr_input("Escolhe o ID: ", int) eid_choice = _get_usr_input("Escolhe o ID: ", int)
@@ -176,7 +201,6 @@ def main():
crud.show_table(table) crud.show_table(table)
insertion_point = _get_usr_input("Posição da nova linha: ", int) insertion_point = _get_usr_input("Posição da nova linha: ", int)
# TODO: balizar a escolha para apenas as linhas do evento em questao
db, msg = crud.create_table_row(db, eid_choice, insertion_point) db, msg = crud.create_table_row(db, eid_choice, insertion_point)
new_table = crud.get_table(db, eid_choice) new_table = crud.get_table(db, eid_choice)
@@ -186,13 +210,13 @@ def main():
else: else:
retInfo = "Base de dados não encontrada!" retInfo = "Base de dados não encontrada!"
case "10": case "9":
if db is not None: if db is not None:
visuals.visual_menu(db) visuals.visual_menu(db)
else: else:
retInfo = "Base de dados não encontrada!" retInfo = "Base de dados não encontrada!"
case "11": case "10":
if db is not None: if db is not None:
# Passa db e original_db para o menu de filtros # Passa db e original_db para o menu de filtros
# Retorna a nova db ativa (filtrada ou redefinida) # Retorna a nova db ativa (filtrada ou redefinida)
@@ -213,30 +237,71 @@ def main():
def _file_exists(name: str) -> bool: def _file_exists(name: str) -> bool:
"""Verifica se um ficheiro existe no diretório onde o programa correntemente
corre, através de os.getcwd()
Args:
name (str): Nome do ficheiro a verificar
Returns:
bool: True se existe, False caso contrário
"""
currFiles = os.listdir(os.getcwd()) currFiles = os.listdir(os.getcwd())
if name in currFiles: if name in currFiles:
return True return True
return False return False
def _event_exists(df, eid) -> bool:
def _event_exists(df: pd.DataFrame, eid: int) -> bool:
"""Função privada de verificação de eventos
Verifica se um certo ID de evento existe ou não dentro de uma DataFrame
Args:
df (pd.DataFrame): DataFrame a pesquisar
eid (int): Evento específico a pesquisar
Returns:
bool: True se evento existe dentro da DataFrame, False caso contrário
"""
allEvents = set(df["ID"]) allEvents = set(df["ID"])
return eid in allEvents return eid in allEvents
def _get_usr_input(msg:str, asType=str): def _get_usr_input(msg: str, asType: Any = str) -> Any:
"""Modifica o stdin do utilizador para o tipo especificado. Por defeito retorna uma str.
Args:
msg (str): String a ser alterada
asType (Any): tipo no qual msg deverá ser intepretado como (default: `str`)
Returns:
[type]: [description]
"""
usrIn = input(msg) usrIn = input(msg)
if usrIn == "": if usrIn == "":
return None return None
return asType(usrIn) return asType(usrIn)
def _prettify_event(df):
preambleInfo = df.drop_duplicates(subset="ID", keep="first") def _prettify_event(df: pd.DataFrame) -> None:
stations = df[["Estacao", "Componente", "Tipo Onda", "Amplitude"]] """Função privada para utilização na visualização de um evento singular através
do menu de `Visualizar um evento`
Args:
df (pd.DataFrame): DataFrame com os dados do evento
"""
# preambleInfo = df.drop_duplicates(subset="ID", keep="first")
# stations = df[["Estacao", "Componente", "Tipo Onda", "Amplitude"]]
info = df.drop_duplicates(subset="Data", keep="first") info = df.drop_duplicates(subset="Data", keep="first")
data = datetime.fromisoformat(info.Data.values[0]).strftime("%c") data = datetime.fromisoformat(info.Data.values[0]).strftime("%c")
print(f"Região: {info["Regiao"].values[0]}\nData: {data}\nLatitude: {info['Latitude'].values[0]}\nLongitude: {info['Longitude'].values[0]}" print(
+ f"\nProfundidade: {info['Profundidade'].values[0]}\nTipo de evento: {info['Tipo Evento'].values[0]}\n") f"Região: {info['Regiao'].values[0]}\nData: {data}\nLatitude: {info['Latitude'].values[0]}\nLongitude: {info['Longitude'].values[0]}"
+ f"\nProfundidade: {info['Profundidade'].values[0]}\nTipo de evento: {info['Tipo Evento'].values[0]}\n"
)
if __name__ == '__main__':
# entry point
if __name__ == "__main__":
main() main()

View File

@@ -1,2 +1,5 @@
numpy==2.3.4 dash==3.3.0
matplotlib==3.10.8
numpy==2.3.5
pandas==2.3.3 pandas==2.3.3
plotly==6.5.0

View File

@@ -1,60 +1,338 @@
# pyright: basic # pyright: basic
from typing import Any
import pandas as pd import pandas as pd
pd.set_option('display.max_rows', 500) pd.set_option("display.max_rows", 500)
pd.set_option('display.max_columns', 500) pd.set_option("display.max_columns", 500)
pd.set_option('display.width', 150) pd.set_option("display.width", 150)
# -- globals # -- globals
HEADER_COLS = ["Data", "Distancia", "Tipo Evento", "Latitude", "Longitude", "Profundidade", "Magnitudes"] HEADER_COLS = [
"Data",
"Distancia",
"Tipo Evento",
"Latitude",
"Longitude",
"Profundidade",
"Magnitudes",
]
TABLE_READ_RET = ["Estacao", "Hora", "Min", "Seg", "Componente", "Amplitude"] TABLE_READ_RET = ["Estacao", "Hora", "Min", "Seg", "Componente", "Amplitude"]
# -- helper funcs # -- helper funcs
def _get_uniques(df) -> pd.DataFrame:
def _get_uniques(df: pd.DataFrame) -> pd.DataFrame:
"""Funcao privada que retorna os eventos unicos, removendo duplicados.
Mantem o primeiro evento de cada ID unico, removendo entradas com o ID igual
Args:
df (pd.DataFrame): DataFrame com os dados
Returns:
pd.DataFrame: Nova DataFrame com IDs duplicados removidos
"""
return df.get(["ID", "Data", "Regiao"]).drop_duplicates(subset="ID", keep="first") return df.get(["ID", "Data", "Regiao"]).drop_duplicates(subset="ID", keep="first")
def _show_events(df): def _show_events(df: pd.DataFrame) -> None:
for (_, row) in df.iterrows(): """Funcao privada para print de cada evendo e a respectiva Regiao
print(f"{row["ID"]}: {row["Regiao"]}")
Args:
df (pd.DataFrame): DataFrame com os dados
"""
for _, row in df.iterrows():
print(f"{row['ID']}: {row['Regiao']}")
# -- main # -- main
def read_ids(df):
def read_ids(df: pd.DataFrame) -> None:
"""Mostra, por print(), os eventos disponiveis em df.
Args:
df (pd.DataFrame): DataFrame com os dados
"""
ids = _get_uniques(df) ids = _get_uniques(df)
_show_events(ids) _show_events(ids)
def get_unique_events_table(df): def get_unique_events_table(df: pd.DataFrame) -> pd.DataFrame:
"""Retorna uma nova DataFrame com eventos de ID unico.
Remove, para cada ID unico, os duplicados, mantendo o primeiro.
Args:
df (pd.DataFrame): DataFrame com os dados
Returns:
pd.DataFrame: Nova DataFrame filtrada
"""
return df.drop_duplicates(subset="ID", keep="first") return df.drop_duplicates(subset="ID", keep="first")
def read_header(df, event_id): def read_header(df: pd.DataFrame, event_id: int) -> str:
# Obtém a informação da primeira linha do evento (cabeçalho) """Lê a primeira entrada com ID `event_id`
Obtém a informação da primeira linha do evento (cabeçalho) e
constrói a string formatada "<indice> <nome>: <valor>"
Args:
df (pd.DataFrame): DataFrame com os dados
event_id (int): ID do evento
Returns:
str: str com os dados do evento
"""
row = df[df["ID"] == event_id].iloc[0] row = df[df["ID"] == event_id].iloc[0]
cols = list(df.columns)
info = [] info = []
for (i, col) in enumerate(HEADER_COLS): for i, col in enumerate(HEADER_COLS):
# Constrói a string formatada "Índice Nome: Valor"
info.append(f"{i + 1} {col}: {row[col]}") info.append(f"{i + 1} {col}: {row[col]}")
infoString = f"Header do evento {event_id}:\n" + "\n".join(info) infoString = f"Header do evento {event_id}:\n" + "\n".join(info)
return infoString return infoString
def show_table(df, retCols=TABLE_READ_RET): def show_table(df: pd.DataFrame, retCols: list[str] = TABLE_READ_RET) -> None:
"""print() da DataFrame total, filtrada por colunas. Por defeito, faz print
de apenas da Estação, HMS, Componente e Amplitude registada
Args:
df (pd.DataFrame): DataFrame com os dados
retCols (list[str]): Filtro de colunas a fazer print() (default: `TABLE_READ_RET`)
"""
print(df.loc[:, retCols]) print(df.loc[:, retCols])
def get_table(df, event_id): def get_table(df: pd.DataFrame, event_id: int) -> pd.DataFrame:
rows = df[df["ID"] == event_id] """Retorna uma DataFrame apenas com o evento `event_id`
Args:
df (pd.DataFrame): DataFrame com os dados
event_id (int): ID do evento
Returns:
pd.DataFrame: Nova DataFrame com todos os dados do evento `event_id`
"""
rows: pd.DataFrame = df[df["ID"] == event_id] # type: ignore
return rows return rows
def read_table_row(df, event_id, row_number_1): def delete_event(df: pd.DataFrame, event_id: int) -> pd.DataFrame:
"""Apaga um evento da DataFrame, retornando a DataFrame atualizada
Args:
df (pd.DataFrame): DataFrame com os dados
event_id (int): ID do evento a apagar
Returns:
pd.DataFrame: DataFrame sem o evento.
"""
new_df = df.drop(df[df["ID"] == event_id].index)
print(f"Evento {event_id} apagado!")
return new_df
def delete_table_row(df: pd.DataFrame, event_id: int, row_number: int) -> pd.DataFrame:
"""Apaga uma linha específica relativa ao evento `event_id`
Args:
df (pd.DataFrame): DataFrame com os dados
event_id ([type]): [description]
row_number ([type]): [description]
Returns:
pd.DataFrame: [description]
"""
matching_indices = df.index[df["ID"] == event_id].tolist()
first_event_row = matching_indices[0]
last_event_row = matching_indices[-1]
# Garante que não estamos a apagar uma linha que pertence a outro evento
if row_number < first_event_row or row_number > last_event_row:
print(
f"Erro: A posição a apagar, {row_number} está fora do intervalo permitido para o evento {event_id}."
)
return df
new_df = df.drop([row_number]).reset_index(drop=True)
print(f"Linha {row_number} apagada com sucesso!")
return new_df
def create_table_row(
df: pd.DataFrame, event_id: int, insertion_point: int
) -> pd.DataFrame:
"""Insere uma nova linha vazia numa posição específica dentro do evento `event_id`
Args:
df (pd.DataFrame): DataFrame com os dados
event_id (int): ID do evento
insertion_point (int): [description]
Returns:
tuple: [description]
"""
#
# Encontra os limites (início e fim) do evento atual
matching_indices = df.index[df["ID"] == event_id].tolist()
first_event_row = matching_indices[0]
last_event_row = matching_indices[-1]
# Valida se o ponto de inserção é válido para este evento
if insertion_point < first_event_row or insertion_point > last_event_row + 1:
print(
f"Erro: A posição de inserção {insertion_point} está fora do intervalo permitido para o evento {event_id}"
)
return df
# Cria a nova linha
new_row_df = pd.DataFrame(columns=df.columns, index=[0])
new_row_df["ID"] = event_id
new_row_df = new_row_df.fillna(0)
new_row_df = new_row_df.astype(df.dtypes)
# Parte o dataframe em dois (antes e depois do ponto de inserção) e mete a nova linha no meio
df_before = df.iloc[:insertion_point]
df_after = df.iloc[insertion_point:]
new_df = pd.concat([df_before, new_row_df, df_after], ignore_index=True)
print(f"Linha inserida com sucesso na posição {insertion_point}")
return new_df
# -- Deprecated
def update_header(df, event_id, new_data):
""">OBSOLETO<
Atualiza o cabeçalho de um evento com os novos dados
Args:
df (pd.DataFrame): DataFrame com os dados
event_id (int): ID do evento
new_data (dict[str, Any]): Novos dados para substituir
Returns:
str: AWK de atualização do cabeçalho
"""
for key, value in new_data.items():
if key in df.columns:
# Atualiza todas as linhas deste evento (ID == event_id) com o novo valor
df.loc[(df["ID"] == event_id) | df.iloc[0], key] = value
return f"Header do evento {event_id} atualizado com sucesso."
def create_table_row_old(
df: pd.DataFrame, event_id: int, row_number_1: int
) -> pd.DataFrame:
""">OBSOLETO<
Cria uma linha na DataFrame
Args:
df (pd.DataFrame): DataFrame com os dados
event_id (int): ID do evento
row_number_1 (int): posição livre onde inserir
Returns:
pd.DataFrame: Nova DataFrame
"""
event_rows = df[df["ID"] == event_id]
if event_rows.empty:
print(f"Erro: Evento com ID {event_id} não encontrado.")
return df
header_idx: int = event_rows.index[0] # type: ignore
table_size = len(event_rows.index) - 1
# Validar posição da nova linha
if not (1 <= row_number_1 <= table_size + 1):
print(
f"Erro: Posição {row_number_1} inválida. Evento {event_id} tem {table_size} linha(s) na tabela."
)
return df
insertion_point = header_idx + row_number_1
new_row_df = pd.DataFrame(columns=df.columns, index=[0])
new_row_df["ID"] = event_id
new_row_df = new_row_df.astype(df.dtypes)
df_before = df.iloc[:insertion_point]
df_after = df.iloc[insertion_point:]
new_df = pd.concat([df_before, new_row_df, df_after], ignore_index=True)
print(f"Linha inserida com sucesso na posição {row_number_1} do evento {event_id}.")
return new_df
def create_blank_event(df: pd.DataFrame, event_id: int) -> pd.DataFrame:
""">OBSOLETO<
Criano um novo evento com valores vazios
Args:
df (pd.DataFrame): DataFrame com os dados
event_id (int): ID do novo evento
Returns:
pd.DataFrame: Nova DataFrame
"""
# Primeiro, avança os IDs de todos os eventos seguintes para arranjar espaço
df.loc[df["ID"] >= event_id, "ID"] += 1
# Cria 2 linhas novas: uma para o cabeçalho e outra vazia para dados
blank_row_df = pd.DataFrame(columns=df.columns, index=[0, 1])
blank_row_df["ID"] = event_id
blank_row_df = blank_row_df.astype(df.dtypes)
# Junta as novas linhas ao dataframe principal
new_df = pd.concat([df, blank_row_df], ignore_index=True)
# Ordena por ID para garantir que fica tudo na ordem certa (mergesort é estável)
new_df = new_df.sort_values(by="ID", kind="mergesort").reset_index(drop=True)
return new_df
def update_table_row(df: pd.DataFrame, row_line: int, new_data: dict[str, Any]) -> str:
"""Atualiza uma linha de `df` com novos dados
Args:
df (pd.DataFrame): DataFrame com os dados
row_line (int): linha a atualizar
new_data (dict[str, Any]): novos dados a substituir na linha
Returns:
str: AWK de atualização da linha
"""
for key, value in new_data.items():
if key in df.columns:
df.loc[row_line, key] = value
return f"Linha {row_line} do evento atualizada com sucesso."
def read_table_row(df: pd.DataFrame, event_id: int, row_number_1: int) -> str:
"""Retorna uma str com todos os valores de uma linha de `df`, relativa ao
evento `event_id`.
Args:
df (pd.DataFrame): DataFrame com os dados
event_id (int): ID do evento
row_number_1 (int): Linha a imprimir
Returns:
str: String formatada com os dados.
"""
# Retorna uma linha específica da tabela de estações # Retorna uma linha específica da tabela de estações
# row_number_1 é o índice dado pelo utilizador (começa em 1) # row_number_1 é o índice dado pelo utilizador (começa em 1)
# row_number_0 é o índice real da lista (começa em 0) # row_number_0 é o índice real da lista (começa em 0)
@@ -73,119 +351,6 @@ def read_table_row(df, event_id, row_number_1):
tableCols = cols[start:] tableCols = cols[start:]
info = [] info = []
for (i, col) in enumerate(tableCols): for i, col in enumerate(tableCols):
info.append(f"{i + 1} {col}: {row[col]}") info.append(f"{i + 1} {col}: {row[col]}")
return f"Linha {row_number_1:02d} do evento {event_id}:\n" + "\n".join(info) return f"Linha {row_number_1:02d} do evento {event_id}:\n" + "\n".join(info)
def update_table_row(df, row_line, new_data):
for key, value in new_data.items():
if key in df.columns:
df.loc[row_line, key] = value
return f"Linha {row_line} do evento atualizada com sucesso."
def update_header(df, event_id, new_data):
# Atualiza o cabeçalho de um evento com os novos dados
for key, value in new_data.items():
if key in df.columns:
# Atualiza todas as linhas deste evento (ID == event_id) com o novo valor
df.loc[(df["ID"] == event_id) | df.iloc[0], key] = value
return f"Header do evento {event_id} atualizado com sucesso."
def delete_event(df, event_id):
# Apaga um evento inteiro (header + tabela)
new_df = df.drop(df[df["ID"] == event_id].index)
print(f"Evento {event_id} apagado!")
return new_df
def delete_table_row(df, event_id, row_number):
# Apaga uma linha específica da tabela de estações de um evento
# Encontra todos os índices (números de linha no DataFrame que pertencem a este evento
matching_indices = df.index[df['ID'] == event_id].tolist()
first_event_row = matching_indices[0]
last_event_row = matching_indices[-1]
# Garante que não estamos a apagar uma linha que pertence a outro evento
if row_number < first_event_row or row_number > last_event_row:
return df, f"Erro: A posição a apagar, {row_number} está fora do intervalo permitido para o evento {event_id}."
new_df = df.drop([row_number]).reset_index(drop=True)
return new_df, f"Linha {row_number} apagada com sucesso!"
def create_blank_event(df, event_id):
# Cria um novo evento vazio
# Primeiro, avança os IDs de todos os eventos seguintes para arranjar espaço
df.loc[df["ID"] >= event_id, "ID"] += 1
# Cria 2 linhas novas: uma para o cabeçalho e outra vazia para dados
blank_row_df = pd.DataFrame(columns=df.columns, index=[0, 1])
blank_row_df["ID"] = event_id
blank_row_df = blank_row_df.astype(df.dtypes)
# Junta as novas linhas ao dataframe principal
new_df = pd.concat([df, blank_row_df], ignore_index=True)
# Ordena por ID para garantir que fica tudo na ordem certa (mergesort é estável)
new_df = new_df.sort_values(by="ID", kind="mergesort").reset_index(drop=True)
return new_df
def create_table_row(df, event_id, insertion_point):
# Insere uma nova linha vazia numa posição específica dentro do evento
# Encontra os limites (início e fim) do evento atual
matching_indices = df.index[df['ID'] == event_id].tolist()
first_event_row = matching_indices[0]
last_event_row = matching_indices[-1]
# Valida se o ponto de inserção é válido para este evento
if insertion_point < first_event_row or insertion_point > last_event_row + 1:
return df, f"Erro: A posição de inserção {insertion_point} está fora do intervalo permitido para o evento {event_id}"
# Cria a nova linha
new_row_df = pd.DataFrame(columns=df.columns, index=[0])
new_row_df['ID'] = event_id
new_row_df = new_row_df.fillna(0)
new_row_df = new_row_df.astype(df.dtypes)
# Parte o dataframe em dois (antes e depois do ponto de inserção) e mete a nova linha no meio
df_before = df.iloc[:insertion_point]
df_after = df.iloc[insertion_point:]
new_df = pd.concat([df_before, new_row_df, df_after], ignore_index=True)
return new_df, f"Linha inserida com sucesso na posição {insertion_point}"
def create_entire_database() -> pd.DataFrame:
pass
def create_table_row_old(df, event_id, row_number_1):
event_rows = df[df["ID"] == event_id]
if event_rows.empty:
return df, f"Erro: Evento com ID {event_id} não encontrado."
header_idx = event_rows.index[0]
table_size = len(event_rows.index) - 1
# Validar posição da nova linha
if not (1 <= row_number_1 <= table_size + 1):
return df, f"Erro: Posição {row_number_1} inválida. Evento {event_id} tem {table_size} linha(s) na tabela."
insertion_point = header_idx + row_number_1
new_row_df = pd.DataFrame(columns=df.columns, index=[0])
new_row_df['ID'] = event_id
new_row_df = new_row_df.astype(df.dtypes)
df_before = df.iloc[:insertion_point]
df_after = df.iloc[insertion_point:]
new_df = pd.concat([df_before, new_row_df, df_after], ignore_index=True)
return new_df, f"Linha inserida com sucesso na posição {row_number_1} do evento {event_id}."

View File

@@ -1,43 +1,117 @@
import pandas as pd
from datetime import datetime
import os import os
import sys import sys
from datetime import datetime
import pandas as pd
def filter_by_date(df: pd.DataFrame, start_date: str, end_date: str) -> pd.DataFrame: def filter_by_date(df: pd.DataFrame, start_date: str, end_date: str) -> pd.DataFrame:
# filtra o dataframe por intervalo de datas (strings em formato ISO) """Retorna uma nova DataFrame filtrada por datas de inicio e fim
mask = (df['Data'] >= start_date) & (df['Data'] <= end_date)
Args:
df (pd.DataFrame): DataFrame a filtrar
start_date (str): data de inicio, em formato ISO
end_date (str): data de fim, em formato ISO
Returns:
pd.DataFrame: DataFrame filtrada
"""
# FIX: filtragem por datas usando datetime
mask = (df["Data"] >= start_date) & (df["Data"] <= end_date)
return df.loc[mask] return df.loc[mask]
def filter_by_depth(df: pd.DataFrame, min_depth: float, max_depth: float) -> pd.DataFrame:
mask = (df['Profundidade'] >= min_depth) & (df['Profundidade'] <= max_depth) def filter_by_depth(
df: pd.DataFrame, min_depth: float, max_depth: float
) -> pd.DataFrame:
"""Retorna uma nova DataFrame, filtrada entre um intervalo de profundidades
Args:
df (pd.DataFrame): DataFrame a filtrar
min_depth (float): profundidade minima
max_depth (float): profundidade maxima
Returns:
pd.DataFrame: DataFrame filtrada
"""
mask = (df["Profundidade"] >= min_depth) & (df["Profundidade"] <= max_depth)
return df.loc[mask] return df.loc[mask]
def filter_by_magnitude(df: pd.DataFrame, min_mag: float, max_mag: float, mag_type: str = 'L') -> pd.DataFrame:
def filter_mag(mags): def filter_by_magnitude(
df: pd.DataFrame, min_mag: float, max_mag: float, mag_type: str = "L"
) -> pd.DataFrame:
"""Retorna uma nova DataFrame, filtrada entre um intervalo de magnitudes.
[description]
Args:
df (pd.DataFrame): DataFrame a filtrar
min_mag (float): magnitude minima
max_mag (float): magnitude maxima
mag_type (str): Tipo de magnitude a filtrar (default: `'L'`)
Returns:
pd.DataFrame: DataFrame filtrada
"""
def _filter_mag(mags):
# Filtrar por tipo de magnitude específico # Filtrar por tipo de magnitude específico
vals = [float(m['Magnitude']) for m in mags if m.get('Tipo') == mag_type] vals = [float(m["Magnitude"]) for m in mags if m.get("Tipo") == mag_type]
if not vals: if not vals:
return False return False
# Se houver múltiplas magnitudes do mesmo tipo, usa o máximo para filtragem # Se houver múltiplas magnitudes do mesmo tipo, usa o máximo para filtragem
mx = max(vals) mx = max(vals)
return min_mag <= mx <= max_mag return min_mag <= mx <= max_mag
mask = df['Magnitudes'].apply(filter_mag) mask = df["Magnitudes"].apply(_filter_mag)
return df.loc[mask] return df.loc[mask]
# -- t7 filters # -- t7 filters
def filter_by_gap(df: pd.DataFrame, max_gap: float) -> pd.DataFrame: def filter_by_gap(df: pd.DataFrame, max_gap: float) -> pd.DataFrame:
"""Retorna uma nova DataFrame, filtrada por valores do GAP inferiores a `max_gap`
Args:
df (pd.DataFrame): DataFrame a filtrar
max_gap (float): valor GAP maximo
Returns:
pd.DataFrame: DataFrame filtrada
"""
# Filtra onde Gap <= max_gap # Filtra onde Gap <= max_gap
return df[df['Gap'] <= max_gap] return df[df["Gap"] <= max_gap]
def filter_by_quality(df: pd.DataFrame, quality: str) -> pd.DataFrame: def filter_by_quality(df: pd.DataFrame, quality: str) -> pd.DataFrame:
return df[df['Pub'] == quality] """Retorna uma nova DataFrame para eventos apenas com qualidade especificada
Args:
df (pd.DataFrame): DataFrame a filtrar
quality (str): Qualidade a filtrar
Returns:
pd.DataFrame: DataFrame filtrada
"""
return df[df["Pub"] == quality]
def filter_by_zone(df: pd.DataFrame, zone_type: str, zone_val: str) -> pd.DataFrame: def filter_by_zone(df: pd.DataFrame, zone_type: str, zone_val: str) -> pd.DataFrame:
"""Retorna uma nova DataFrame para eventos de uma certa zona
Args:
df (pd.DataFrame): DataFrame a filtrar
zone_type (str): Tipo da zona, (ex: VZ, SZ)
zone_val (str): Valor da zona
Returns:
pd.DataFrame: DataFrame filtrada
"""
return df[df[zone_type] == zone_val] return df[df[zone_type] == zone_val]
FILTER_MENU = """[1] Filtrar por Data (Inicio:Fim) FILTER_MENU = """[1] Filtrar por Data (Inicio:Fim)
[2] Filtrar por Gap (< Valor) [2] Filtrar por Gap (< Valor)
[3] Filtrar por Qualidade (EPI) [3] Filtrar por Qualidade (EPI)
@@ -50,7 +124,18 @@ FILTER_MENU = """[1] Filtrar por Data (Inicio:Fim)
[Q] Voltar [Q] Voltar
""" """
def filter_menu(db: pd.DataFrame, original_db: pd.DataFrame):
def filter_menu(db: pd.DataFrame, original_db: pd.DataFrame) -> pd.DataFrame:
"""Menu de filtragem da DataFrame, com base em datas, magnitudes, profundidades, zonas, GAP e qualidades,
com opcao para reverter para a DataFrame original, para remocao dos filtros aplicados
Args:
db (pd.DataFrame): DataFrame a ser filtrada
original_db (pd.DataFrame): DataFrame de origem, para reversao
Returns:
pd.DataFrame: Retorna a DataFrame com os filtros aplicados, ou a original sem qualquer filtro aplicado.
"""
currDb = db currDb = db
while True: while True:
@@ -60,7 +145,6 @@ def filter_menu(db: pd.DataFrame, original_db: pd.DataFrame):
print(FILTER_MENU) print(FILTER_MENU)
usrIn = input("Opção: ").lower() usrIn = input("Opção: ").lower()
match usrIn: match usrIn:
case "1": case "1":
start = input("Data Inicio (YYYY-MM-DD): ") start = input("Data Inicio (YYYY-MM-DD): ")
@@ -71,10 +155,11 @@ def filter_menu(db: pd.DataFrame, original_db: pd.DataFrame):
val = float(input("Gap Máximo: ")) val = float(input("Gap Máximo: "))
currDb = filter_by_gap(currDb, val) currDb = filter_by_gap(currDb, val)
case "3": case "3":
confirm = input("Filtrar apenas eventos com Qualidade EPI? (s/n): ").lower() confirm = input(
if confirm == 's': "Filtrar apenas eventos com Qualidade EPI? (s/n): "
).lower()
if confirm == "s":
currDb = filter_by_quality(currDb, "EPI") currDb = filter_by_quality(currDb, "EPI")
else: else:
print("Filtro não aplicado.") print("Filtro não aplicado.")

View File

@@ -1,146 +1,261 @@
import io import io
from collections import defaultdict from collections import defaultdict
from datetime import datetime from datetime import datetime
from typing import Any
import pandas as pd import pandas as pd
"""Parser de dados
A dataframe retornada tera multiplas linhas referentes ao mesmo evento
visto que se esta a guardar por linha cada estacao que registou o evento em questa
logo cada linha tem sempre a mesma informacao duplicada que se encontra no preambulo
para cada estacao
"""
# --- variáveis globais --- # --- variáveis globais ---
DIST_IND = {"L": "Local", "R": "Regional", "D": "Distante"} DIST_IND = {"L": "Local", "R": "Regional", "D": "Distante"}
TYPE = {"Q": "Quake", "V": "Volcanic", "U": "Unknown", "E": "Explosion"} TYPE = {"Q": "Quake", "V": "Volcanic", "U": "Unknown", "E": "Explosion"}
# --- funções auxiliares --- # --- funções auxiliares ---
def is_blank(l: str) -> bool: def is_blank(_str: str) -> bool:
return len(l.strip(" ")) == 0 """Verifica se uma string tem ou nao conteudo
def parse_flt(v:str) -> float | None: Args:
_str (str): str a verificar se esta vazia
Returns:
bool: True se str tem conteudo, False caso contrario
"""
return len(_str.strip(" ")) == 0
def parse_flt(value: str) -> float | None:
"""Formata str como float
Args:
value (str): nro em string para ser formatado
Returns:
float | None: Retorna um float se bem sucedido, None se excepcao
"""
try: try:
t = float(v) return float(value)
return t
except ValueError: except ValueError:
return None return None
def parse_int(v:str) -> int | None:
def parse_int(value: str) -> int | None:
"""Formata str como int
Args:
value (str): nro em string para ser formatado
Returns:
int | None: Retorna um int se bem sucedido, None se excepcao
"""
try: try:
t = int(v) return int(value)
return t
except ValueError: except ValueError:
return None return None
def into_dataframe(data) -> pd.DataFrame:
def into_dataframe(data: dict[str, Any]) -> pd.DataFrame:
"""Transforma uma dict numa DataFrame
Args:
data (dict[str, Any]): [description]
Returns:
pd.DataFrame: DataFrame
"""
if len(data) == 0: if len(data) == 0:
return pd.DataFrame() return pd.DataFrame()
aux = {k: [] for k in data.keys()} aux = {k: [] for k in data.keys()}
for (k,v) in data.items(): for k, v in data.items():
aux[k].append(v) aux[k].append(v)
return pd.DataFrame(data=aux) return pd.DataFrame(data=aux)
def _concat(preamble, df: pd.DataFrame):
for (k,v) in preamble.items(): def _concat(preamble: dict[str, Any], df: pd.DataFrame) -> pd.DataFrame:
"""Junta o preambulo, uma dict, na DataFrame
Args:
preamble (dict[str, Any]): Preambulo do evento a inserir
df (pd.DataFrame): DataFrame com eventos
Returns:
[type]: Nova DataFrame com o preambulo adicionado
"""
for k, v in preamble.items():
df.insert(len(df.columns) - 1, k, [v for _ in range(len(df))]) df.insert(len(df.columns) - 1, k, [v for _ in range(len(df))])
return df return df
def validate_no_stations(expected:int , stationsDF:pd.DataFrame) -> bool:
uniqueStations = stationsDF["Estacao"].nunique()
return expected == uniqueStations
# --- principal --- # --- principal ---
def parse(fname): def parse(fname: str) -> pd.DataFrame:
"""Faz o parse de todos os eventos no ficheiro.
A funcao separa em eventos singulares, e transforma cada evento numa DataFrame,
que sera concatenada com uma DataFrame que contem todos os eventos existentes
Args:
fname (str): nome do ficheiro que contem os dados
Returns:
pd.DataFrame: DataFrame com os eventos formatados
"""
fp = open(fname) fp = open(fname)
data = [l for l in fp.read().split("\n")] data = [line for line in fp.read().split("\n")]
chunks = boundaries(data) chunks = boundaries(data)
df = pd.DataFrame() df = pd.DataFrame()
for (idx,c) in enumerate(chunks): for c in chunks:
a = parse_chunk(data[c[0] : c[1]]) a = parse_chunk(data[c[0] : c[1]])
aux = pd.concat([df, a], axis=0, ignore_index=True) aux = pd.concat([df, a], axis=0, ignore_index=True)
df = aux df = aux
fp.close() fp.close()
return df return df
def boundaries(data: list[str]):
def boundaries(data: list[str]) -> list[tuple[int, int]]:
"""Procura e guarda a posicao de cada evento.
O ficheiro tem os eventos separados por uma linha em branco
Args:
data (list[str]): lista dos dados
Returns:
list[tuple[int, int]]: lista com tuples dos indices de inicio
e fim de cada evento
"""
boundaries = [] boundaries = []
start = None eventStart = None
for (idx,l) in enumerate(data): for idx, line in enumerate(data):
if start is None: if eventStart is None:
if not is_blank(l): if not is_blank(line):
start = idx eventStart = idx
else: else:
if is_blank(l): if is_blank(line):
boundaries.append((start,idx)) boundaries.append((eventStart, idx))
start = None eventStart = None
return boundaries return boundaries
def parse_chunk(chunk_lines: list[str]):
hIdx = None
for (idx, l) in enumerate(chunk_lines):
if l[-1] == "7":
hIdx = idx
break
preambleRet = _parse_preamble(chunk_lines[:hIdx])
phaseRet = _parse_type_7(chunk_lines[hIdx:])
if not validate_no_stations(preambleRet["Estacoes"], phaseRet): def parse_chunk(chunk_lines: list[str]) -> pd.DataFrame:
pass """Parse de um evento no formato Nordic, separando num preambulo, e nas estacoes
Ambos sao enviados para as suas funcoes privadas para serem parsed
Args:
chunk_lines (list[str]): lista de str do evento, como slice da lista de todos os eventos
Returns:
pd.DataFrame: DataFrame do evento
"""
separatorIdx = None
for idx, line in enumerate(chunk_lines):
if line[-1] == "7":
separatorIdx = idx
break
preambleRet = _parse_preamble(chunk_lines[:separatorIdx])
phaseRet = _parse_type_7(chunk_lines[separatorIdx:])
return _concat(preambleRet, phaseRet) return _concat(preambleRet, phaseRet)
def _parse_preamble(hLines: list[str]):
aux = defaultdict(list) def _parse_preamble(hLines: list[str]) -> dict[str, Any]:
"""Transforma o preambulo numa dict com os valores que precisamos
Verifica cada linha e separa dentro de uma dict, com a chave sendo o tipo de linha
Args:
hLines (list[str]): slice da lista com apenas o preambulo
Returns:
dict[str, Any]: dict com os valores necessarios
"""
lineTypes = defaultdict(list)
for line in hLines: for line in hLines:
match line[-1]: match line[-1]:
case "1": case "1":
aux[1].append(line) lineTypes[1].append(line)
case "3": case "3":
aux[3].append(line) lineTypes[3].append(line)
case "6": case "6":
aux[6].append(line) lineTypes[6].append(line)
case "E": case "E":
aux["E"].append(line) lineTypes["E"].append(line)
case "I": case "I":
aux["I"].append(line) lineTypes["I"].append(line)
case "F":
pass
# aux["F"].append(line)
case _: case _:
pass pass
headerDict = dict() headerDict = dict()
for (k,v) in aux.items(): for k, v in lineTypes.items():
if len(v) != 0: if len(v) != 0:
# FUNCS[k] retorna o handle de cada funcao para cada tipo de linha
headerDict.update(FUNCS[k](v)) headerDict.update(FUNCS[k](v))
return headerDict return headerDict
def _parse_type_1(data: list[str]): def _parse_type_1(data: list[str]) -> dict[str, Any]:
aux = data[0] """Transforma linhas tipo 1 (data, hora, latitude, longitude, profundidade
y = int(aux[1:5]) agencia, magnitudes e tipos e nro de estacoes que registaram o evento)
mo = int(aux[6:8])
d = int(aux[8:10]) Args:
h = int(aux[11:13]) data (list[str]): lista de linhas tipo 1
m = int(aux[13:15])
s = int(aux[16:18]) Returns:
mil = int(aux[19]) * 10**5 dict[str, Any]: dict com os valores necessarios
"""
y = int(data[0][1:5])
mo = int(data[0][6:8])
d = int(data[0][8:10])
h = int(data[0][11:13])
m = int(data[0][13:15])
s = int(data[0][16:18])
mil = int(data[0][19]) * 10**5
dt = datetime(y, mo, d, h, m, s, mil) dt = datetime(y, mo, d, h, m, s, mil)
dist_ind = DIST_IND[aux[21]] dist_ind = DIST_IND[data[0][21]]
ev_type = TYPE[aux[22]] ev_type = TYPE[data[0][22]]
lat = float(aux[23:30]) lat = float(data[0][23:30])
long = float(aux[30:38]) long = float(data[0][30:38])
depth = float(aux[38:43]) depth = float(data[0][38:43])
no_stat = int(aux[48:51]) no_stat = int(data[0][48:51])
hypo = {"Data": dt.isoformat(), "Distancia": dist_ind, "Tipo Evento": ev_type, "Latitude": lat, "Longitude": long, "Profundidade": depth, "Estacoes": no_stat, "Magnitudes": list()} hypo = {
for l in data: # NOTE: ANTES ERA UMA STRING, AGORA E O OBJECTO DATETIME
hypo["Magnitudes"] = hypo["Magnitudes"] + _parse_mag(l) "Data": dt,
"Distancia": dist_ind,
"Tipo Evento": ev_type,
"Latitude": lat,
"Longitude": long,
"Profundidade": depth,
"Estacoes": no_stat,
"Magnitudes": [],
}
for line in data:
hypo["Magnitudes"] = hypo["Magnitudes"] + _parse_mag(line)
return hypo return hypo
def _parse_mag(line: str):
def _parse_mag(line: str) -> list[dict[str, Any]]:
"""Transforma nos varios tipos de magnitudes
Args:
line (str): str das linhas tipo 1
Returns:
list[dict[str, Any]]: dict com os valores das magnitudes e o seu tipo
"""
magnitudes = [] magnitudes = []
base = 55 base = 55
while base < 79: while base < 79:
@@ -152,14 +267,26 @@ def _parse_mag(line: str):
return magnitudes return magnitudes
def _parse_type_3(data: list[str]): def _parse_type_3(data: list[str]) -> dict[str, Any]:
"""Transforma linhas tipo 3 (observacoes)
Args:
data (list[str]): lista com linhas tipo 3
Returns:
dict[str, Any]: dict com valores necessarios
"""
comments = {} comments = {}
for line in data: for line in data:
if line.startswith(" SENTIDO") or line.startswith(" REGIAO") or line.startswith(" PUB"): if (
c, v = line[:-2].strip().split(": ", maxsplit=1) line.startswith(" SENTIDO")
or line.startswith(" REGIAO")
or line.startswith(" PUB")
):
chave, valor = line[:-2].strip().split(": ", maxsplit=1)
if c == "REGIAO": if chave == "REGIAO":
parts = v.split(",") parts = valor.split(",")
comments["Regiao"] = parts[0].strip() comments["Regiao"] = parts[0].strip()
for p in parts[1:]: for p in parts[1:]:
p = p.strip() p = p.strip()
@@ -167,38 +294,128 @@ def _parse_type_3(data: list[str]):
comments["SZ"] = p comments["SZ"] = p
elif "VZ" in p: elif "VZ" in p:
comments["VZ"] = p comments["VZ"] = p
elif c == "PUB": elif chave == "PUB":
comments["Pub"] = v.strip() comments["Pub"] = valor.strip()
else: else:
comments[c.capitalize()] = v.split(",")[0] comments[chave.capitalize()] = valor.split(",")[0]
return comments return comments
def _parse_type_6(data: list[str]): def _parse_type_6(data: list[str]) -> dict[str, list[str]]:
"""Transforma linhas tipo 6 (nome de onda)
[description]
Args:
data (list[str]): lista de linhas tipo 6
Returns:
dict[str, list[str]]: lista de nomes dos ficheiros das ondas
"""
waves = [] waves = []
for l in data: for line in data:
waves.append(l.strip().split(" ")[0]) waves.append(line.strip().split(" ")[0])
return {"Onda": waves} return {"Onda": waves}
def _parse_type_7(data: list[str]): def _parse_type_7(data: list[str]) -> pd.DataFrame:
"""Transforma linhas tipo 7 (estacoes)
Args:
data (list[str]): linhas tipo 7
Returns:
pd.DataFrame: DataFrame com as informacoes de cada estacao
"""
aux = io.StringIO("\n".join(data)) aux = io.StringIO("\n".join(data))
dados = pd.read_fwf(aux, colspecs=[(1,5), (6,8),(10,15), (18,20), (20,22), (23,28), (34,38), (71,75)]) dados = pd.read_fwf(
dados.rename(columns={'STAT': "Estacao", 'SP': "Componente" , 'PHASW': "Tipo Onda", 'HR': "Hora", 'MM': "Min", 'SECON': "Seg", 'AMPL': "Amplitude", " DIST": "Distancia Epicentro"}, inplace=True) aux,
colspecs=[
(1, 5),
(6, 8),
(10, 15),
(18, 20),
(20, 22),
(23, 28),
(34, 38),
(71, 75),
],
)
dados.rename(
columns={
"STAT": "Estacao",
"SP": "Componente",
"PHASW": "Tipo Onda",
"HR": "Hora",
"MM": "Min",
"SECON": "Seg",
"AMPL": "Amplitude",
" DIST": "Distancia Epicentro",
},
inplace=True,
)
return dados return dados
def _parse_type_e(data: list[str]): def _parse_type_e(data: list[str]) -> dict[str, Any]:
aux = data[0] """Transformar linhas tipo E (erros)
error = {"Gap": int(aux[5:8]), "Origin": float(aux[14:20]), "Error_lat": float(aux[24:30]), "Error_long": float(aux[32:38]), "Error_depth": float(aux[38:43]), "Cov_xy": float(aux[43:55]), "Cov_xz": float(aux[55:67]), "Cov_yz": float(aux[67:79])}
Args:
data (list[str]): linhas tipo E
Returns:
dict[str, Any]: dict com os valores necessarios
"""
error = {
"Gap": int(data[0][5:8]),
"Origin": float(data[0][14:20]),
"Error_lat": float(data[0][24:30]),
"Error_long": float(data[0][32:38]),
"Error_depth": float(data[0][38:43]),
"Cov_xy": float(data[0][43:55]),
"Cov_xz": float(data[0][55:67]),
"Cov_yz": float(data[0][67:79]),
}
return error return error
def _parse_type_i(data: list[str]): def _parse_type_i(data: list[str]) -> dict[str, int]:
"""Transforma linhas tipo I(ID do evento)
Args:
data (list[str]): linhas tipo I
Returns:
dict[str, int]: dict com o valor do ID
"""
aux = data[0] aux = data[0]
return {"ID": int(aux[60:74])} return {"ID": int(aux[60:74])}
FUNCS = {1: _parse_type_1, 3: _parse_type_3, 6: _parse_type_6, "E": _parse_type_e, "I": _parse_type_i} FUNCS = {
1: _parse_type_1,
3: _parse_type_3,
6: _parse_type_6,
"E": _parse_type_e,
"I": _parse_type_i,
}
# -- Deprecated
def validate_station_numbers(expected: int, stationsDF: pd.DataFrame) -> bool:
"""[summary]
[description]
Args:
expected (int): [description]
stationsDF (pd.DataFrame): [description]
Returns:
bool: [description]
"""
uniqueStations = stationsDF["Estacao"].nunique()
return expected == uniqueStations

View File

@@ -1,49 +0,0 @@
import collections
import datetime
import stats
from matplotlib import pyplot as plt
class Plotter:
def __init__(self, data):
self.raw_data = data
pass
def extract_info(self):
pass
def plot_events_day(self):
values = collections.Counter(stats._preprare_days(self.raw_data))
x = list(values.keys())
y = list(values.values())
fig, ax = plt.subplots(layout="constrained")
ax.bar(x, y)
plt.show()
def plot_events_month(self):
values = collections.Counter(stats._preprare_months(self.raw_data))
x = list(values.keys())
y = list(values.values())
fig, ax = plt.subplots(layout="constrained")
ax.bar(x, y)
plt.show()
if __name__ == "__main__":
import parser
asdf = parser.parse("../dados.txt")
a = Plotter(asdf)
# b = stats._filter_mags(a.raw_data, more_than=2.5, less_than=2.9)
c = stats.filter_date(
a.raw_data,
after=datetime.datetime(year=2014, month=1, day=6),
before=datetime.datetime(year=2014, month=1, day=12),
)
print(c)

View File

@@ -1,8 +1,11 @@
import os import os
import sys import sys
from typing import Any, Iterable, TypeAlias
import pandas as pd
import numpy as np import numpy as np
import pandas as pd
from utils.utils import extract_mag_depth
STAT_HEADER = """=== Terramotos === STAT_HEADER = """=== Terramotos ===
== Estatísticas == == Estatísticas ==
@@ -14,6 +17,7 @@ STAT_MENU = """[1] Média
[4] Máximo [4] Máximo
[5] Mínimo [5] Mínimo
[6] Moda [6] Moda
[7] Print de todas as estatísticas
[T] Estatísticas Temporais (T5) [T] Estatísticas Temporais (T5)
[Q] Voltar ao menu principal [Q] Voltar ao menu principal
@@ -29,6 +33,16 @@ CHOICE = {"1": "Magnitudes", "2": "Distancia","3": "Profundidade"}
def filter_submenu(type: str): def filter_submenu(type: str):
"""[summary]
[description]
Args:
type (str): [description]
Returns:
[type]: [description]
"""
os.system("cls" if sys.platform == "windows" else "clear") os.system("cls" if sys.platform == "windows" else "clear")
print(f"{STAT_HEADER}\n = {type} = ") print(f"{STAT_HEADER}\n = {type} = ")
print(FILTER_CHOICES) print(FILTER_CHOICES)
@@ -42,70 +56,110 @@ def filter_submenu(type: str):
return None return None
# -- t5 funcs # -- t5 funcs
def _get_unique_events(df: pd.DataFrame) -> pd.DataFrame: def _get_unique_events(df: pd.DataFrame) -> pd.DataFrame:
return df.drop_duplicates(subset="ID", keep='first') """Função privada que retorna os eventos únicos
def convert_to_datetime(df: pd.DataFrame) -> pd.DataFrame: (Ler docstring do `parser.py` para o porquê de se fazer isto)
# Converte coluna Data para objetos datetime
df = df.copy()
df['Data'] = pd.to_datetime(df['Data'], format='mixed')
return df
def events_per_period(df: pd.DataFrame, period: str): Args:
df (pd.DataFrame): Dataframe com todos os eventos
Returns:
pd.DataFrame: Dataframe com apenas uma linha por evento
"""
return df.drop_duplicates(subset="ID", keep="first")
def events_per_period(df: pd.DataFrame, period: str) -> tuple[Iterable, Iterable]:
"""Retorna os eventos por período, seja por dia, seja por mês
Args:
df (pd.DataFrame): Dataframe com valores
period (str): tipo de período. `D` para dia, `M` para mês
Returns:
tuple[Iterable, Iterable]: tuple com iteradores dos indices e valores
"""
# Calcula o número de eventos por dia ('D') ou mês ('M') # Calcula o número de eventos por dia ('D') ou mês ('M')
df = convert_to_datetime(df)
events = _get_unique_events(df) events = _get_unique_events(df)
if period == 'M': if period == "M":
period = 'ME' period = "ME"
res = events.set_index('Data').resample(period).size() res = events.set_index("Data").resample(period).size()
return res.index, res.values return (res.index, res.values)
def stats_depth_month(df: pd.DataFrame):
# Calcula estatísticas de Profundidade por Mês def stats_depth_month(df: pd.DataFrame) -> pd.DataFrame:
df = convert_to_datetime(df) """Estatisticas de profundidade de sismos, por mes
Args:
df (pd.DataFrame): DataFrame com eventos
Returns:
[type]: Dataframe com as estatisticas de profundidade, por mes
"""
events = _get_unique_events(df) events = _get_unique_events(df)
grouped = events.set_index('Data').resample('ME')['Profundidade'] grouped = events.set_index("Data").resample("ME")["Profundidade"]
stats_df = pd.DataFrame({ stats_df = pd.DataFrame(
'Mean': grouped.mean(), {
'Std': grouped.std(), "Mean": grouped.mean(),
'Median': grouped.median(), "Std": grouped.std(),
'Q1': grouped.quantile(0.25), "Median": grouped.median(),
'Q3': grouped.quantile(0.75), "Q1": grouped.quantile(0.25),
'Min': grouped.min(), "Q3": grouped.quantile(0.75),
'Max': grouped.max() "Min": grouped.min(),
}) "Max": grouped.max(),
}
)
return stats_df return stats_df
def stats_mag_month(df: pd.DataFrame): def stats_mag_month(df: pd.DataFrame):
"""Estatisticas de magnitude dos sismos, por mes
Args:
df (pd.DataFrame): DataFrame com eventos
Returns:
[type]: Dataframe com as estatisticas de magnitude, por mes
"""
# Calcula estatísticas de Magnitude por Mês # Calcula estatísticas de Magnitude por Mês
df = convert_to_datetime(df)
events = _get_unique_events(df) events = _get_unique_events(df)
def get_max_mag(mags): def _get_max_mag(mags: pd.Series):
vals = [float(m['Magnitude']) for m in mags if 'Magnitude' in m] """Funcao aplicadora à df, para encontrar a maior magnitude
Args:
mags (pd.Series): Serie com as magnitudes
Returns:
pd.Series: Serie com a magnitude maxima
"""
vals = [float(m["Magnitude"]) for m in mags if "Magnitude" in m]
return max(vals) if vals else np.nan return max(vals) if vals else np.nan
events = events.copy() events = events.copy()
events['MaxMag'] = events['Magnitudes'].apply(get_max_mag) events["MaxMag"] = events["Magnitudes"].apply(_get_max_mag)
grouped = events.set_index('Data').resample('ME')['MaxMag'] grouped = events.set_index("Data").resample("ME")["MaxMag"]
stats_df = pd.DataFrame({ stats_df = pd.DataFrame(
'Mean': grouped.mean(), {
'Std': grouped.std(), "Mean": grouped.mean(),
'Median': grouped.median(), "Std": grouped.std(),
'Q1': grouped.quantile(0.25), "Median": grouped.median(),
'Q3': grouped.quantile(0.75), "Q1": grouped.quantile(0.25),
'Min': grouped.min(), "Q3": grouped.quantile(0.75),
'Max': grouped.max() "Min": grouped.min(),
}) "Max": grouped.max(),
}
)
return stats_df return stats_df
@@ -119,7 +173,13 @@ T5_MENU = """[1] Número de eventos por dia
[Q] Voltar [Q] Voltar
""" """
def t5_menu(df: pd.DataFrame): def t5_menu(df: pd.DataFrame):
"""Menu de estatisticas das magnitudes e profundidades por mes
Args:
df (pd.DataFrame): Dataframe com os eventos
"""
while True: while True:
os.system("cls" if sys.platform == "windows" else "clear") os.system("cls" if sys.platform == "windows" else "clear")
print(STAT_HEADER + "\n" + " == T5: Estatísticas Temporais ==\n" + T5_MENU) print(STAT_HEADER + "\n" + " == T5: Estatísticas Temporais ==\n" + T5_MENU)
@@ -127,14 +187,22 @@ def t5_menu(df: pd.DataFrame):
match usrIn: match usrIn:
case "1": case "1":
dates, counts = events_per_period(df, 'D') dates, counts = events_per_period(df, "D")
print("\nEventos por Dia:") print("\nEventos por Dia:")
print(pd.DataFrame({'Data': dates, 'Contagem': counts}).to_string(index=False)) print(
pd.DataFrame({"Data": dates, "Contagem": counts}).to_string(
index=False
)
)
case "2": case "2":
dates, counts = events_per_period(df, 'M') dates, counts = events_per_period(df, "M")
print("\nEventos por Mês:") print("\nEventos por Mês:")
print(pd.DataFrame({'Data': dates, 'Contagem': counts}).to_string(index=False)) print(
pd.DataFrame({"Data": dates, "Contagem": counts}).to_string(
index=False
)
)
case "3": case "3":
st = stats_depth_month(df) st = stats_depth_month(df)
@@ -156,7 +224,14 @@ def t5_menu(df: pd.DataFrame):
# -- stat menu # -- stat menu
def stat_menu(df: pd.DataFrame): def stat_menu(df: pd.DataFrame):
"""Menu de estatísticas
Args:
df (pd.DataFrame): Dataframe com eventos
"""
inStats = True inStats = True
while inStats: while inStats:
os.system("cls" if sys.platform == "windows" else "clear") os.system("cls" if sys.platform == "windows" else "clear")
@@ -231,6 +306,13 @@ def stat_menu(df: pd.DataFrame):
else: else:
continue continue
case "7":
m, d = _mag_depth(df)
print("\t\tMagnitude\tProfundidade")
for a, b in zip(m, d):
print(f"{a[0]}\t{round(a[1], 4)}\t\t{round(b[1], 4)}")
case "q": case "q":
inStats = False inStats = False
continue continue
@@ -241,7 +323,47 @@ def stat_menu(df: pd.DataFrame):
input("Clica `Enter` para continuar") input("Clica `Enter` para continuar")
def average(df: pd.DataFrame, filter_by): type tuples = tuple[list[tuple[str, Any]], list[tuple[str, Any]]]
def _mag_depth(df: pd.DataFrame) -> tuples:
"""Cria uma lista com cada estatística para as magnitudes e profundidades,
de forma a ser possivel fazer print de tudo de uma só vez
Args:
df (pd.DataFrame): Dataframe com valores
Returns:
tuples: lista com estatisticas das magnitudes e profundidades
"""
data = extract_mag_depth(df)
mag_array = data.Magnitudes.values
depth_array = data.Profundidade.values
mags = []
dep = []
for a, b in zip(
["Media\t", "Desvio-Padrao", "Variancia", "Valor Maximo", "Valor Minimo"],
[np.average, np.std, np.var, np.max, np.min],
):
mags.append((a, b(mag_array)))
dep.append((a, b(depth_array)))
return (mags, dep)
def average(df: pd.DataFrame, filter_by) -> np.float64 | None:
"""Calculo da média para o tipo especifico
Args:
df (pd.DataFrame): Dataframe com valores
filter_by (str): Valor para calculo da media
Returns:
np.float64 | None: média
"""
events = _get_unique_events(df) events = _get_unique_events(df)
values = events[filter_by].to_numpy() values = events[filter_by].to_numpy()
@@ -249,11 +371,20 @@ def average(df: pd.DataFrame, filter_by):
values = _unpack_mags(values) values = _unpack_mags(values)
try: try:
return np.average(values) return np.average(values)
except: except Exception:
return None return None
def variance(df, filter_by): def variance(df: pd.DataFrame, filter_by: str) -> np.float64 | None:
"""calcula a variancia para o tipo especificado
Args:
df (pd.DataFrame): Dataframe com valores
filter_by (str): Valor para calculo da variancia
Returns:
np.float64 | None: variancia
"""
events = _get_unique_events(df) events = _get_unique_events(df)
values = events[filter_by].to_numpy() values = events[filter_by].to_numpy()
@@ -262,11 +393,20 @@ def variance(df, filter_by):
try: try:
return np.var(values) return np.var(values)
except: except Exception:
return None return None
def std_dev(df, filter_by): def std_dev(df: pd.DataFrame, filter_by: str) -> np.float64 | None:
"""calcula o desvio-padrao para o tipo especificado
Args:
df (pd.DataFrame): Dataframe com valores
filter_by (str): Valor para calculo do desvio-padrao
Returns:
np.float64 | None: desvio-padrao
"""
events = _get_unique_events(df) events = _get_unique_events(df)
values = events[filter_by].to_numpy() values = events[filter_by].to_numpy()
@@ -275,11 +415,20 @@ def std_dev(df, filter_by):
try: try:
return np.std(values) return np.std(values)
except: except Exception:
return None return None
def max_v(df, filter_by): def max_v(df: pd.DataFrame, filter_by: str) -> np.floating:
"""Retorna o valor maximo num array
Args:
df (pd.DataFrame): Dataframe com valores
filter_by (str): Coluna para o valor maximo
Returns:
np.floating: valor maximo
"""
events = _get_unique_events(df) events = _get_unique_events(df)
values = events[filter_by].to_numpy() values = events[filter_by].to_numpy()
@@ -289,7 +438,16 @@ def max_v(df, filter_by):
return np.max(values) return np.max(values)
def min_v(df, filter_by): def min_v(df, filter_by) -> np.floating:
"""Retorna o valor minimo num array
Args:
df (pd.DataFrame): Dataframe com valores
filter_by (str): Coluna para o valor minimo
Returns:
np.floating: valor minimo
"""
events = _get_unique_events(df) events = _get_unique_events(df)
values = events[filter_by].to_numpy() values = events[filter_by].to_numpy()
@@ -299,7 +457,17 @@ def min_v(df, filter_by):
return np.min(values) return np.min(values)
def moda(df, filter_by): def moda(df, filter_by) -> np.floating:
"""Calcula a moda para um array de valores
Args:
df (pd.DataFrame): Dataframe com valores
filter_by (str): Coluna para o calculo da moda
Returns:
np.floating: moda
"""
events = _get_unique_events(df) events = _get_unique_events(df)
values = events[filter_by].to_numpy() values = events[filter_by].to_numpy()
@@ -312,10 +480,18 @@ def moda(df, filter_by):
return sorted(uniques_list, reverse=True, key=lambda x: x[1])[0][0] return sorted(uniques_list, reverse=True, key=lambda x: x[1])[0][0]
def _unpack_mags(arr: np.ndarray): def _unpack_mags(arr: np.ndarray) -> np.ndarray:
"""Funcao privada para facilitar o calculo das magnitudes
Args:
arr (np.ndarray): Lista dos tipos de magnitudes
Returns:
np.ndarray: magnitudes
"""
newVals = np.empty(0) newVals = np.empty(0)
for v in arr: for v in arr:
for m in v: for m in v:
newVals = np.append(newVals, float(m["Magnitude"])) newVals = np.append(newVals, float(m["Magnitude"]))
return newVals return newVals

View File

@@ -1,24 +1,71 @@
#! /usr/bin/env python #! /usr/bin/env python
# pyright: basic # pyright: basic
from datetime import time
import json import json
from datetime import time
from math import modf from math import modf
from typing import Any from typing import Any
import pandas as pd import pandas as pd
def save_as_json(df: pd.DataFrame, fname, event_cols, station_cols) -> bool: def extract_mag_depth(df: pd.DataFrame) -> pd.DataFrame:
info = create_dict_struct(df, event_cols, station_cols) """Extrai as magnitudes e profundidades.
Nas magnitudes, apenas deixa o tipo L
Args:
df (pd.DataFrame): Dataframe com eventos
Returns:
pd.DataFrame: Dataframe com apenas magnitudes e profundidades
"""
_df = df.drop_duplicates(subset="ID", keep="first")[
["Magnitudes", "Profundidade"]
].reset_index(drop=True)
mags = []
for _, value in _df.iterrows():
for mag in value.Magnitudes:
if mag["Tipo"] == "L":
mags.append(float(mag["Magnitude"]))
break
_df = _df.drop(columns=["Magnitudes"])
aux = pd.DataFrame.from_dict({"Magnitudes": mags})
return pd.concat([aux, _df], axis=1)
def save_as_json(df: pd.DataFrame, fname: str, event_cols: list[str]) -> bool:
"""Guarda a dataframe como um ficheiro JSON
Args:
df (pd.DataFrame): Dataframe com eventos
fname (str): nome do ficheiro a guardar
event_cols (list[str]): lista com os nomes das colunas presentes em `df`
Returns:
bool: Sucesso da operacao
"""
info = _create_dict_struct(df, event_cols)
with open(fname, "w") as fp: with open(fname, "w") as fp:
json.dump(info, fp, indent=4) json.dump(info, fp, indent=4)
return True return True
# TODO: passar os nomes das colunas, para não haver problemas no futuro, caso se altere os nomes da dataframe
def create_dict_struct(df: pd.DataFrame, event_cols, station_cols) -> dict[str, Any]: def _create_dict_struct(df: pd.DataFrame, event_cols) -> dict[str, Any]:
# get all events by their id """Funcao privada para ajuda a guardar como ficheiro JSON
[description]
Args:
df (pd.DataFrame): [description]
event_cols ([type]): [description]
Returns:
dict[str, Any]: [description]
"""
uniqueIds = df["ID"].unique() uniqueIds = df["ID"].unique()
allEvents = {} allEvents = {}
@@ -26,17 +73,30 @@ def create_dict_struct(df: pd.DataFrame, event_cols, station_cols) -> dict[str,
for id in uniqueIds: for id in uniqueIds:
filteredDf = df.loc[df["ID"] == id] filteredDf = df.loc[df["ID"] == id]
first_row = filteredDf.head(1) first_row = filteredDf.head(1)
allEvents[int(id)] = create_event_info(first_row, event_cols) allEvents[int(id)] = _create_event_info(first_row, event_cols)
allEvents[int(id)].update(create_stations_info_1(filteredDf)) allEvents[int(id)].update(create_stations_info_1(filteredDf))
return allEvents return allEvents
def create_event_info(info: pd.DataFrame, cols) -> dict[str, Any]: def _create_event_info(info: pd.DataFrame, cols) -> dict[str, Any]:
"""Funcao privada para criar a estrutura dict pretendida
no ficheiro JSOn
Args:
info (pd.DataFrame): dataframe com eventos
cols ([type]): lista com nomes das colunas
Returns:
dict[str, Any]: dict com o formato pretendido
"""
informacoes = dict() informacoes = dict()
for v in cols: for v in cols:
if v == "Magnitudes": if v == "Data":
informacoes[v] = info.iloc[0][v].isoformat()
elif v == "Magnitudes":
informacoes[v] = create_mag_info(info.iloc[0][v]) informacoes[v] = create_mag_info(info.iloc[0][v])
elif v in {"Latitude", "Longitude", "Profundidade", "Gap"}: elif v in {"Latitude", "Longitude", "Profundidade", "Gap"}:
informacoes[v] = float(info.iloc[0][v]) informacoes[v] = float(info.iloc[0][v])
@@ -47,20 +107,33 @@ def create_event_info(info: pd.DataFrame, cols) -> dict[str, Any]:
def create_stations_info_1(info: pd.DataFrame) -> dict[str, Any]: def create_stations_info_1(info: pd.DataFrame) -> dict[str, Any]:
"""Funcao privada para ajuda de formatacao no guardar como JSON
Args:
info (pd.DataFrame): dataframe com eventos
Returns:
dict[str, Any]: dict com o formato pretendido
"""
stationsDict = {} stationsDict = {}
for idx in range(len(info)): for idx in range(len(info)):
aux = info.iloc[idx] aux = info.iloc[idx]
micro, sec = tuple(map(int, modf(aux["Seg"]))) micro, sec = tuple(map(int, modf(aux["Seg"])))
hms = time(hour=aux["Hora"],minute=aux["Min"], second=sec, microsecond=micro).strftime("%H:%M:%S.%f") hms = time(
station = {"Componente": aux["Componente"], "Hora": hms, "Distancia": float(aux["DIS"])} hour=aux["Hora"], minute=aux["Min"], second=sec, microsecond=micro
).strftime("%H:%M:%S.%f")
station = {
"Componente": aux["Componente"],
"Hora": hms,
"Distancia": float(aux["DIS"]),
}
if type(aux["Tipo Onda"]) != float: if type(aux["Tipo Onda"]) is float:
station.update({"Tipo Onda": aux["Tipo Onda"]}) station.update({"Tipo Onda": aux["Tipo Onda"]})
if aux["Tipo Onda"] == "IAML": if aux["Tipo Onda"] == "IAML":
station.update({"Amplitude": float(aux["Amplitude"])}) station.update({"Amplitude": float(aux["Amplitude"])})
if aux["Estacao"] not in stationsDict.keys(): if aux["Estacao"] not in stationsDict.keys():
stationsDict[aux["Estacao"]] = [station] stationsDict[aux["Estacao"]] = [station]
else: else:
@@ -68,16 +141,16 @@ def create_stations_info_1(info: pd.DataFrame) -> dict[str, Any]:
return {"Estacoes": stationsDict} return {"Estacoes": stationsDict}
def create_mag_info(magnitudes): def create_mag_info(magnitudes: list[dict[str, Any]]) -> dict[str, Any]:
"""Funcao privada para parsing das magnitudes
Args:
magnitudes (list[dict[str, Any]]): [description]
Returns:
dict[str, Any]: dict com o formato pretendido
"""
mags = {} mags = {}
for value in magnitudes: for value in magnitudes:
mags[value["Tipo"]] = value["Magnitude"] mags[value["Tipo"]] = value["Magnitude"]
return mags return mags
if __name__ == '__main__':
import parser
df = parser.parse("dados.txt")
a = create_dict_struct(df, None, None)
save_as_json(a)

View File

@@ -1,14 +1,25 @@
import matplotlib.pyplot as plt
import pandas as pd
import sys
import os import os
import sys
import matplotlib.pyplot as plt
import numpy as np import numpy as np
import pandas as pd
from utils import stats from utils import stats
# -- helpers # -- helpers
def plot_bar(x, y, xLabel, yLabel, title): def plot_bar(x, y, xLabel, yLabel, title):
"""Funcao para efetuar o plot de um grafico de barras
Args:
x ([]): valores em x
y ([type]): valor y correspondente a cada valor x
xLabel ([type]): [description]
yLabel ([type]): [description]
title ([type]): [description]
"""
plt.figure(figsize=(10, 6)) plt.figure(figsize=(10, 6))
plt.bar(x, y) plt.bar(x, y)
plt.xlabel(xLabel) plt.xlabel(xLabel)
@@ -18,9 +29,22 @@ def plot_bar(x, y, xLabel, yLabel, title):
plt.tight_layout() plt.tight_layout()
plt.show() plt.show()
def plot_linear_with_std(x, mean, std, xLabel, yLabel, title): def plot_linear_with_std(x, mean, std, xLabel, yLabel, title):
"""[summary]
[description]
Args:
x ([type]): [description]
mean ([type]): [description]
std ([type]): [description]
xLabel ([type]): [description]
yLabel ([type]): [description]
title ([type]): [description]
"""
plt.figure(figsize=(10, 6)) plt.figure(figsize=(10, 6))
plt.errorbar(x, mean, yerr=std, fmt='-o', capsize=5, ecolor='red') plt.errorbar(x, mean, yerr=std, fmt="-o", capsize=5, ecolor="red")
plt.xlabel(xLabel) plt.xlabel(xLabel)
plt.ylabel(yLabel) plt.ylabel(yLabel)
plt.title(title) plt.title(title)
@@ -29,11 +53,23 @@ def plot_linear_with_std(x, mean, std, xLabel, yLabel, title):
plt.tight_layout() plt.tight_layout()
plt.show() plt.show()
def plot_boxplot(dataList, labels, xLabel, yLabel, title): def plot_boxplot(dataList, labels, xLabel, yLabel, title):
"""[summary]
[description]
Args:
dataList ([type]): [description]
labels ([type]): [description]
xLabel ([type]): [description]
yLabel ([type]): [description]
title ([type]): [description]
"""
# dataList: lista de arrays/series, um para cada etiqueta # dataList: lista de arrays/series, um para cada etiqueta
# labels: lista de etiquetas correspondentes a dataList # labels: lista de etiquetas correspondentes a dataList
plt.figure(figsize=(10, 6)) plt.figure(figsize=(10, 6))
plt.boxplot(dataList, labels=labels) plt.boxplot(dataList, label=labels)
plt.xlabel(xLabel) plt.xlabel(xLabel)
plt.ylabel(yLabel) plt.ylabel(yLabel)
plt.title(title) plt.title(title)
@@ -41,55 +77,94 @@ def plot_boxplot(dataList, labels, xLabel, yLabel, title):
plt.tight_layout() plt.tight_layout()
plt.show() plt.show()
# -- t6 logic # -- t6 logic
def viz_events_per_period(df: pd.DataFrame, period: str, title_suffix: str): def viz_events_per_period(df: pd.DataFrame, period: str, title_suffix: str):
"""[summary]
[description]
Args:
df (pd.DataFrame): [description]
period (str): [description]
title_suffix (str): [description]
"""
dates, counts = stats.events_per_period(df, period) dates, counts = stats.events_per_period(df, period)
# Formatar datas para melhor leitura no gráfico # Formatar datas para melhor leitura no gráfico
if period == 'D': if period == "D":
# dates é um DatetimeIndex # dates é um DatetimeIndex
labels = [d.strftime('%Y-%m-%d') for d in dates] labels = [d.strftime("%Y-%m-%d") for d in dates]
else: else:
labels = [d.strftime('%Y-%m') for d in dates] labels = [d.strftime("%Y-%m") for d in dates]
plot_bar(labels, counts, "Data", "Número de Eventos", f"Eventos por {title_suffix}") plot_bar(labels, counts, "Data", "Número de Eventos", f"Eventos por {title_suffix}")
def viz_linear_stats(df: pd.DataFrame, target: str): def viz_linear_stats(df: pd.DataFrame, target: str):
"""[summary]
[description]
Args:
df (pd.DataFrame): [description]
target (str): [description]
"""
# Média +/- Desvio Padrão # Média +/- Desvio Padrão
if target == 'Profundidade': if target == "Profundidade":
st = stats.stats_depth_month(df) st = stats.stats_depth_month(df)
unit = "km" unit = "km"
else: # Magnitude else: # Magnitude
st = stats.stats_mag_month(df) st = stats.stats_mag_month(df)
unit = "Magn" unit = "Magn"
labels = [d.strftime('%Y-%m') for d in st.index] labels = [d.strftime("%Y-%m") for d in st.index]
plot_linear_with_std(
labels,
st["Mean"],
st["Std"],
"Mês",
f"{target} ({unit})",
f"Média e Desvio Padrão de {target} por Mês",
)
plot_linear_with_std(labels, st['Mean'], st['Std'], "Mês", f"{target} ({unit})", f"Média e Desvio Padrão de {target} por Mês")
def viz_boxplot(df: pd.DataFrame, target: str): def viz_boxplot(df: pd.DataFrame, target: str):
df = stats.convert_to_datetime(df) """[summary]
[description]
Args:
df (pd.DataFrame): [description]
target (str): [description]
Returns:
[type]: [description]
"""
events = stats._get_unique_events(df) events = stats._get_unique_events(df)
# Agrupar por mês # Agrupar por mês
grouped = events.set_index('Data').resample('ME') grouped = events.set_index("Data").resample("ME")
data_to_plot = [] data_to_plot = []
labels = [] labels = []
for name, group in grouped: for name, group in grouped:
if target == 'Profundidade': if target == "Profundidade":
vals = group['Profundidade'].dropna().values vals = group["Profundidade"].dropna().values
else: else:
# Extrair magnitudes máximas # Extrair magnitudes máximas
def get_max_mag(mags): def get_max_mag(mags):
vals = [float(m['Magnitude']) for m in mags if 'Magnitude' in m] vals = [float(m["Magnitude"]) for m in mags if "Magnitude" in m]
return max(vals) if vals else np.nan return max(vals) if vals else np.nan
vals = group['Magnitudes'].apply(get_max_mag).dropna().values
vals = group["Magnitudes"].apply(get_max_mag).dropna().values
if len(vals) > 0: if len(vals) > 0:
data_to_plot.append(vals) data_to_plot.append(vals)
labels.append(name.strftime('%Y-%m')) labels.append(name.strftime("%Y-%m"))
plot_boxplot(data_to_plot, labels, "Mês", target, f"Boxplot de {target} por Mês") plot_boxplot(data_to_plot, labels, "Mês", target, f"Boxplot de {target} por Mês")
@@ -108,7 +183,15 @@ VISUALS_MENU = """[1] Gráfico Barras: Eventos por Dia
HEADER = "=== T6: Representação Gráfica ===" HEADER = "=== T6: Representação Gráfica ==="
def visual_menu(df: pd.DataFrame): def visual_menu(df: pd.DataFrame):
"""
[description]
Args:
df (pd.DataFrame): [description]
"""
while True: while True:
os.system("cls" if sys.platform == "windows" else "clear") os.system("cls" if sys.platform == "windows" else "clear")
print(HEADER + "\n" + VISUALS_MENU) print(HEADER + "\n" + VISUALS_MENU)
@@ -116,9 +199,9 @@ def visual_menu(df: pd.DataFrame):
match usrIn: match usrIn:
case "1": case "1":
viz_events_per_period(df, 'D', "Dia") viz_events_per_period(df, "D", "Dia")
case "2": case "2":
viz_events_per_period(df, 'M', "Mês") viz_events_per_period(df, "M", "Mês")
case "3": case "3":
viz_linear_stats(df, "Profundidade") viz_linear_stats(df, "Profundidade")
case "4": case "4":