fhiiiofhweoi
aaaaaaaa
This commit is contained in:
@@ -8,11 +8,11 @@ from datetime import datetime
|
|||||||
|
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
|
||||||
from utils import parser, crud, stats, utils
|
from utils import parser, crud, stats, utils, visuals, filters
|
||||||
|
|
||||||
HEADER = """=== Terramotos ==="""
|
HEADER = """=== Terramotos ==="""
|
||||||
|
|
||||||
EVENT_COLS = ["Data", "Latitude", "Longitude", "Profundidade", "Tipo Evento", "Gap", "Magnitudes", "Regiao", "Sentido"]
|
EVENT_COLS = ["Data", "Latitude", "Longitude", "Profundidade", "Tipo Evento", "Gap", "Magnitudes", "Regiao", "Sentido", "Pub", "SZ", "VZ"]
|
||||||
STATION_COLS = ["Estacao", "Hora", "Min", "Seg", "Componente", "Distancia Epicentro", "Tipo Onda"]
|
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
|
||||||
@@ -23,6 +23,8 @@ MENU ="""[1] Criar a base de dados
|
|||||||
[7] Guardar como CSV
|
[7] Guardar como CSV
|
||||||
[8] Estatísticas
|
[8] Estatísticas
|
||||||
[9] Criar uma entrada
|
[9] Criar uma entrada
|
||||||
|
[10] Gráficos
|
||||||
|
[11] Filtros (T7)
|
||||||
|
|
||||||
[Q] Sair
|
[Q] Sair
|
||||||
"""
|
"""
|
||||||
@@ -51,6 +53,7 @@ def guardar_csv(df: pd.DataFrame, fname: str):
|
|||||||
def main():
|
def main():
|
||||||
isRunning = True
|
isRunning = True
|
||||||
db = None
|
db = None
|
||||||
|
original_db = None
|
||||||
|
|
||||||
retInfo = None
|
retInfo = None
|
||||||
|
|
||||||
@@ -67,9 +70,11 @@ def main():
|
|||||||
|
|
||||||
if _file_exists(fname) and fname.endswith(".json"):
|
if _file_exists(fname) and fname.endswith(".json"):
|
||||||
db = pd.read_json(fname)
|
db = pd.read_json(fname)
|
||||||
|
original_db = db.copy()
|
||||||
print("Base de dados populada.")
|
print("Base de dados populada.")
|
||||||
elif _file_exists(fname):
|
elif _file_exists(fname):
|
||||||
db = parser.parse(fname)
|
db = parser.parse(fname)
|
||||||
|
original_db = db.copy()
|
||||||
input("Base de dados populada. Enter para voltar ao menu inicial")
|
input("Base de dados populada. Enter para voltar ao menu inicial")
|
||||||
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.")
|
||||||
@@ -180,6 +185,21 @@ def main():
|
|||||||
input()
|
input()
|
||||||
else:
|
else:
|
||||||
retInfo = "Base de dados não encontrada!"
|
retInfo = "Base de dados não encontrada!"
|
||||||
|
|
||||||
|
case "10":
|
||||||
|
if db is not None:
|
||||||
|
visuals.visual_menu(db)
|
||||||
|
else:
|
||||||
|
retInfo = "Base de dados não encontrada!"
|
||||||
|
|
||||||
|
case "11":
|
||||||
|
if db is not None:
|
||||||
|
# Passa db e original_db para o menu de filtros
|
||||||
|
# Retorna a nova db ativa (filtrada ou redefinida)
|
||||||
|
db = filters.filter_menu(db, original_db)
|
||||||
|
else:
|
||||||
|
retInfo = "Base de dados não encontrada!"
|
||||||
|
|
||||||
case "q":
|
case "q":
|
||||||
isRunning = False
|
isRunning = False
|
||||||
continue
|
continue
|
||||||
@@ -215,8 +235,8 @@ def _prettify_event(df):
|
|||||||
stations = df[["Estacao", "Componente", "Tipo Onda", "Amplitude"]]
|
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.Lat.values[0]}\nLongitude: {info.Long.values[0]}"
|
print(f"Região: {info["Regiao"].values[0]}\nData: {data}\nLatitude: {info['Latitude'].values[0]}\nLongitude: {info['Longitude'].values[0]}"
|
||||||
+ f"\nProfundidade: {info.Prof.values[0]}\nTipo de evento: {info['Tipo Ev'].values[0]}\n")
|
+ f"\nProfundidade: {info['Profundidade'].values[0]}\nTipo de evento: {info['Tipo Evento'].values[0]}\n")
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|||||||
14
shell.nix
Normal file
14
shell.nix
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{ pkgs ? import <nixpkgs> {} }:
|
||||||
|
|
||||||
|
pkgs.mkShell {
|
||||||
|
buildInputs = with pkgs; [
|
||||||
|
python3
|
||||||
|
python3Packages.pandas
|
||||||
|
python3Packages.numpy
|
||||||
|
python3Packages.matplotlib
|
||||||
|
];
|
||||||
|
|
||||||
|
shellHook = ''
|
||||||
|
echo "Funcionou"
|
||||||
|
'';
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@ pd.set_option('display.width', 150)
|
|||||||
|
|
||||||
# -- globals
|
# -- globals
|
||||||
|
|
||||||
HEADER_COLS = ["Data", "Distancia", "Tipo Ev", "Lat", "Long", "Prof", "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
|
||||||
@@ -33,14 +33,13 @@ def get_unique_events_table(df):
|
|||||||
|
|
||||||
|
|
||||||
def read_header(df, event_id):
|
def read_header(df, event_id):
|
||||||
# Informações do header do evento
|
# Obtém a informação da primeira linha do evento (cabeçalho)
|
||||||
row = df[df["ID"] == event_id].iloc[0]
|
row = df[df["ID"] == event_id].iloc[0]
|
||||||
cols = list(df.columns)
|
cols = list(df.columns)
|
||||||
# end = cols.index("ID") - 1
|
|
||||||
# header_cols = cols[:end]
|
|
||||||
# Para selecionar todas as colunas em vez de só algumas
|
|
||||||
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
|
||||||
@@ -56,15 +55,23 @@ def get_table(df, event_id):
|
|||||||
|
|
||||||
|
|
||||||
def read_table_row(df, event_id, row_number_1):
|
def read_table_row(df, event_id, row_number_1):
|
||||||
# retorna uma linha específica da tabela
|
# Retorna uma linha específica da tabela de estações
|
||||||
|
# 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 = row_number_1 - 1
|
row_number_0 = row_number_1 - 1
|
||||||
table = get_table(df, event_id)
|
table = get_table(df, event_id)
|
||||||
|
|
||||||
|
# Verifica se a linha pedida existe dentro das linhas deste evento
|
||||||
if row_number_0 < 0 or row_number_0 >= len(table):
|
if row_number_0 < 0 or row_number_0 >= len(table):
|
||||||
return f"Linha {row_number_1} não pertence ao evento {event_id}."
|
return f"Linha {row_number_1} não pertence ao evento {event_id}."
|
||||||
|
|
||||||
row = table.iloc[row_number_0]
|
row = table.iloc[row_number_0]
|
||||||
cols = list(df.columns)
|
cols = list(df.columns)
|
||||||
|
|
||||||
|
# Encontra onde começam as colunas da estação para mostrar apenas os dados relevantes
|
||||||
start = cols.index("Estacao")
|
start = cols.index("Estacao")
|
||||||
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]}")
|
||||||
@@ -79,9 +86,10 @@ def update_table_row(df, row_line, new_data):
|
|||||||
|
|
||||||
|
|
||||||
def update_header(df, event_id, new_data):
|
def update_header(df, event_id, new_data):
|
||||||
# atualiza o header de um evento
|
# Atualiza o cabeçalho de um evento com os novos dados
|
||||||
for key, value in new_data.items():
|
for key, value in new_data.items():
|
||||||
if key in df.columns:
|
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
|
df.loc[(df["ID"] == event_id) | df.iloc[0], key] = value
|
||||||
return f"Header do evento {event_id} atualizado com sucesso."
|
return f"Header do evento {event_id} atualizado com sucesso."
|
||||||
|
|
||||||
@@ -94,48 +102,60 @@ def delete_event(df, event_id):
|
|||||||
|
|
||||||
|
|
||||||
def delete_table_row(df, event_id, row_number):
|
def delete_table_row(df, event_id, row_number):
|
||||||
# Apaga uma linha específica da tabela do evento
|
# Apaga uma linha específica da tabela de estações de um evento
|
||||||
# Cria uma nova linha vazia no dataframe na posição insertion_point
|
|
||||||
|
# Encontra todos os índices (números de linha no DataFrame que pertencem a este evento
|
||||||
matching_indices = df.index[df['ID'] == event_id].tolist()
|
matching_indices = df.index[df['ID'] == event_id].tolist()
|
||||||
|
|
||||||
first_event_row = matching_indices[0]
|
first_event_row = matching_indices[0]
|
||||||
last_event_row = matching_indices[-1]
|
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:
|
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}."
|
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)
|
new_df = df.drop([row_number]).reset_index(drop=True)
|
||||||
return new_df, f"Linha {row_choice} apagada com sucesso!"
|
return new_df, f"Linha {row_number} apagada com sucesso!"
|
||||||
|
|
||||||
|
|
||||||
def create_blank_event(df, event_id):
|
def create_blank_event(df, event_id):
|
||||||
# Criar um evento vazio com linha de header e 1 linha de coluna
|
# 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
|
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 = pd.DataFrame(columns=df.columns, index=[0, 1])
|
||||||
blank_row_df["ID"] = event_id
|
blank_row_df["ID"] = event_id
|
||||||
blank_row_df = blank_row_df.astype(df.dtypes)
|
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)
|
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)
|
new_df = new_df.sort_values(by="ID", kind="mergesort").reset_index(drop=True)
|
||||||
|
|
||||||
return new_df
|
return new_df
|
||||||
|
|
||||||
|
|
||||||
def create_table_row(df, event_id, insertion_point):
|
def create_table_row(df, event_id, insertion_point):
|
||||||
# Cria uma nova linha vazia no dataframe na posição 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()
|
matching_indices = df.index[df['ID'] == event_id].tolist()
|
||||||
|
|
||||||
first_event_row = matching_indices[0]
|
first_event_row = matching_indices[0]
|
||||||
last_event_row = matching_indices[-1]
|
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:
|
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}"
|
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 = pd.DataFrame(columns=df.columns, index=[0])
|
||||||
new_row_df['ID'] = event_id
|
new_row_df['ID'] = event_id
|
||||||
new_row_df = new_row_df.fillna(0)
|
new_row_df = new_row_df.fillna(0)
|
||||||
new_row_df = new_row_df.astype(df.dtypes)
|
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_before = df.iloc[:insertion_point]
|
||||||
df_after = df.iloc[insertion_point:]
|
df_after = df.iloc[insertion_point:]
|
||||||
|
|
||||||
|
|||||||
108
utils/filters.py
Normal file
108
utils/filters.py
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
import pandas as pd
|
||||||
|
from datetime import datetime
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
mask = (df['Data'] >= start_date) & (df['Data'] <= end_date)
|
||||||
|
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)
|
||||||
|
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):
|
||||||
|
# Filtrar por tipo de magnitude específico
|
||||||
|
vals = [float(m['Magnitude']) for m in mags if m.get('Tipo') == mag_type]
|
||||||
|
if not vals:
|
||||||
|
return False
|
||||||
|
# Se houver múltiplas magnitudes do mesmo tipo, usa o máximo para filtragem
|
||||||
|
mx = max(vals)
|
||||||
|
return min_mag <= mx <= max_mag
|
||||||
|
|
||||||
|
mask = df['Magnitudes'].apply(filter_mag)
|
||||||
|
return df.loc[mask]
|
||||||
|
|
||||||
|
# -- t7 filters
|
||||||
|
|
||||||
|
def filter_by_gap(df: pd.DataFrame, max_gap: float) -> pd.DataFrame:
|
||||||
|
# Filtra onde Gap <= max_gap
|
||||||
|
return df[df['Gap'] <= max_gap]
|
||||||
|
|
||||||
|
def filter_by_quality(df: pd.DataFrame, quality: str) -> pd.DataFrame:
|
||||||
|
return df[df['Pub'] == quality]
|
||||||
|
|
||||||
|
def filter_by_zone(df: pd.DataFrame, zone_type: str, zone_val: str) -> pd.DataFrame:
|
||||||
|
return df[df[zone_type] == zone_val]
|
||||||
|
|
||||||
|
FILTER_MENU = """[1] Filtrar por Data (Inicio:Fim)
|
||||||
|
[2] Filtrar por Gap (< Valor)
|
||||||
|
[3] Filtrar por Qualidade (EPI)
|
||||||
|
[4] Filtrar por Zona SZ
|
||||||
|
[5] Filtrar por Zona VZ
|
||||||
|
[6] Filtrar por Magnitude (Min:Max)
|
||||||
|
[7] Filtrar por Profundidade (Min:Max)
|
||||||
|
[R] Reset Filtros
|
||||||
|
|
||||||
|
[Q] Voltar
|
||||||
|
"""
|
||||||
|
|
||||||
|
def filter_menu(db: pd.DataFrame, original_db: pd.DataFrame):
|
||||||
|
currDb = db
|
||||||
|
|
||||||
|
while True:
|
||||||
|
os.system("cls" if sys.platform == "windows" else "clear")
|
||||||
|
print("=== T7: Filtros ===")
|
||||||
|
print(f"Linhas actuais: {len(currDb)}")
|
||||||
|
print(FILTER_MENU)
|
||||||
|
usrIn = input("Opção: ").lower()
|
||||||
|
|
||||||
|
|
||||||
|
match usrIn:
|
||||||
|
case "1":
|
||||||
|
start = input("Data Inicio (YYYY-MM-DD): ")
|
||||||
|
end = input("Data Fim (YYYY-MM-DD): ")
|
||||||
|
currDb = filter_by_date(currDb, start, end)
|
||||||
|
|
||||||
|
case "2":
|
||||||
|
val = float(input("Gap Máximo: "))
|
||||||
|
currDb = filter_by_gap(currDb, val)
|
||||||
|
|
||||||
|
|
||||||
|
case "3":
|
||||||
|
confirm = input("Filtrar apenas eventos com Qualidade EPI? (s/n): ").lower()
|
||||||
|
if confirm == 's':
|
||||||
|
currDb = filter_by_quality(currDb, "EPI")
|
||||||
|
else:
|
||||||
|
print("Filtro não aplicado.")
|
||||||
|
|
||||||
|
case "4":
|
||||||
|
val = input("Zona SZ (ex: SZ31): ")
|
||||||
|
currDb = filter_by_zone(currDb, "SZ", val)
|
||||||
|
|
||||||
|
case "5":
|
||||||
|
val = input("Zona VZ (ex: VZ14): ")
|
||||||
|
currDb = filter_by_zone(currDb, "VZ", val)
|
||||||
|
|
||||||
|
case "6":
|
||||||
|
print("Filtrar por Magnitude Tipo 'L'")
|
||||||
|
min_m = float(input("Min Mag L: "))
|
||||||
|
max_m = float(input("Max Mag L: "))
|
||||||
|
|
||||||
|
currDb = filter_by_magnitude(currDb, min_m, max_m, "L")
|
||||||
|
|
||||||
|
case "7":
|
||||||
|
min_d = float(input("Min Profundidade: "))
|
||||||
|
max_d = float(input("Max Profundidade: "))
|
||||||
|
currDb = filter_by_depth(currDb, min_d, max_d)
|
||||||
|
|
||||||
|
case "r":
|
||||||
|
currDb = original_db.copy()
|
||||||
|
|
||||||
|
case "q":
|
||||||
|
return currDb
|
||||||
|
case _:
|
||||||
|
pass
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
# pyright: basic
|
|
||||||
import io
|
import io
|
||||||
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
@@ -6,12 +5,12 @@ from datetime import datetime
|
|||||||
|
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
|
||||||
# --- globals ---
|
# --- 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"}
|
||||||
|
|
||||||
|
|
||||||
# --- helper funcs ---
|
# --- funções auxiliares ---
|
||||||
def is_blank(l: str) -> bool:
|
def is_blank(l: str) -> bool:
|
||||||
return len(l.strip(" ")) == 0
|
return len(l.strip(" ")) == 0
|
||||||
|
|
||||||
@@ -156,10 +155,22 @@ def _parse_mag(line: str):
|
|||||||
def _parse_type_3(data: list[str]):
|
def _parse_type_3(data: list[str]):
|
||||||
comments = {}
|
comments = {}
|
||||||
for line in data:
|
for line in data:
|
||||||
if line.startswith(" SENTIDO") or line.startswith(" REGIAO"):
|
if line.startswith(" SENTIDO") or line.startswith(" REGIAO") or line.startswith(" PUB"):
|
||||||
c, v = line[:-2].strip().split(": ", maxsplit=1)
|
c, v = line[:-2].strip().split(": ", maxsplit=1)
|
||||||
v = v.split(",")[0]
|
|
||||||
comments[c.capitalize()] = v
|
if c == "REGIAO":
|
||||||
|
parts = v.split(",")
|
||||||
|
comments["Regiao"] = parts[0].strip()
|
||||||
|
for p in parts[1:]:
|
||||||
|
p = p.strip()
|
||||||
|
if "SZ" in p:
|
||||||
|
comments["SZ"] = p
|
||||||
|
elif "VZ" in p:
|
||||||
|
comments["VZ"] = p
|
||||||
|
elif c == "PUB":
|
||||||
|
comments["Pub"] = v.strip()
|
||||||
|
else:
|
||||||
|
comments[c.capitalize()] = v.split(",")[0]
|
||||||
|
|
||||||
return comments
|
return comments
|
||||||
|
|
||||||
|
|||||||
136
utils/stats.py
136
utils/stats.py
@@ -1,5 +1,3 @@
|
|||||||
# pyright: basic
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
@@ -16,6 +14,7 @@ STAT_MENU = """[1] Média
|
|||||||
[4] Máximo
|
[4] Máximo
|
||||||
[5] Mínimo
|
[5] Mínimo
|
||||||
[6] Moda
|
[6] Moda
|
||||||
|
[T] Estatísticas Temporais (T5)
|
||||||
|
|
||||||
[Q] Voltar ao menu principal
|
[Q] Voltar ao menu principal
|
||||||
"""
|
"""
|
||||||
@@ -43,6 +42,120 @@ def filter_submenu(type: str):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# -- t5 funcs
|
||||||
|
|
||||||
|
def _get_unique_events(df: pd.DataFrame) -> pd.DataFrame:
|
||||||
|
return df.drop_duplicates(subset="ID", keep='first')
|
||||||
|
|
||||||
|
def convert_to_datetime(df: pd.DataFrame) -> pd.DataFrame:
|
||||||
|
# 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):
|
||||||
|
# Calcula o número de eventos por dia ('D') ou mês ('M')
|
||||||
|
df = convert_to_datetime(df)
|
||||||
|
events = _get_unique_events(df)
|
||||||
|
|
||||||
|
if period == 'M':
|
||||||
|
period = 'ME'
|
||||||
|
|
||||||
|
res = events.set_index('Data').resample(period).size()
|
||||||
|
return res.index, res.values
|
||||||
|
|
||||||
|
def stats_depth_month(df: pd.DataFrame):
|
||||||
|
# Calcula estatísticas de Profundidade por Mês
|
||||||
|
df = convert_to_datetime(df)
|
||||||
|
events = _get_unique_events(df)
|
||||||
|
|
||||||
|
grouped = events.set_index('Data').resample('ME')['Profundidade']
|
||||||
|
|
||||||
|
stats_df = pd.DataFrame({
|
||||||
|
'Mean': grouped.mean(),
|
||||||
|
'Std': grouped.std(),
|
||||||
|
'Median': grouped.median(),
|
||||||
|
'Q1': grouped.quantile(0.25),
|
||||||
|
'Q3': grouped.quantile(0.75),
|
||||||
|
'Min': grouped.min(),
|
||||||
|
'Max': grouped.max()
|
||||||
|
})
|
||||||
|
return stats_df
|
||||||
|
|
||||||
|
def stats_mag_month(df: pd.DataFrame):
|
||||||
|
# Calcula estatísticas de Magnitude por Mês
|
||||||
|
df = convert_to_datetime(df)
|
||||||
|
events = _get_unique_events(df)
|
||||||
|
|
||||||
|
def get_max_mag(mags):
|
||||||
|
vals = [float(m['Magnitude']) for m in mags if 'Magnitude' in m]
|
||||||
|
return max(vals) if vals else np.nan
|
||||||
|
|
||||||
|
events = events.copy()
|
||||||
|
events['MaxMag'] = events['Magnitudes'].apply(get_max_mag)
|
||||||
|
|
||||||
|
grouped = events.set_index('Data').resample('ME')['MaxMag']
|
||||||
|
|
||||||
|
stats_df = pd.DataFrame({
|
||||||
|
'Mean': grouped.mean(),
|
||||||
|
'Std': grouped.std(),
|
||||||
|
'Median': grouped.median(),
|
||||||
|
'Q1': grouped.quantile(0.25),
|
||||||
|
'Q3': grouped.quantile(0.75),
|
||||||
|
'Min': grouped.min(),
|
||||||
|
'Max': grouped.max()
|
||||||
|
})
|
||||||
|
return stats_df
|
||||||
|
|
||||||
|
|
||||||
|
# -- t5 menu
|
||||||
|
|
||||||
|
T5_MENU = """[1] Número de eventos por dia
|
||||||
|
[2] Número de eventos por mês
|
||||||
|
[3] Estatísticas Profundidade por mês
|
||||||
|
[4] Estatísticas Magnitude por mês
|
||||||
|
|
||||||
|
[Q] Voltar
|
||||||
|
"""
|
||||||
|
|
||||||
|
def t5_menu(df: pd.DataFrame):
|
||||||
|
while True:
|
||||||
|
os.system("cls" if sys.platform == "windows" else "clear")
|
||||||
|
print(STAT_HEADER + "\n" + " == T5: Estatísticas Temporais ==\n" + T5_MENU)
|
||||||
|
usrIn = input("Opção: ").lower()
|
||||||
|
|
||||||
|
match usrIn:
|
||||||
|
case "1":
|
||||||
|
dates, counts = events_per_period(df, 'D')
|
||||||
|
print("\nEventos por Dia:")
|
||||||
|
print(pd.DataFrame({'Data': dates, 'Contagem': counts}).to_string(index=False))
|
||||||
|
|
||||||
|
case "2":
|
||||||
|
dates, counts = events_per_period(df, 'M')
|
||||||
|
print("\nEventos por Mês:")
|
||||||
|
print(pd.DataFrame({'Data': dates, 'Contagem': counts}).to_string(index=False))
|
||||||
|
|
||||||
|
case "3":
|
||||||
|
st = stats_depth_month(df)
|
||||||
|
print("\nEstatísticas Profundidade por Mês:")
|
||||||
|
print(st.to_string())
|
||||||
|
|
||||||
|
case "4":
|
||||||
|
st = stats_mag_month(df)
|
||||||
|
print("\nEstatísticas Magnitude por Mês:")
|
||||||
|
print(st.to_string())
|
||||||
|
|
||||||
|
case "q":
|
||||||
|
return
|
||||||
|
case _:
|
||||||
|
pass
|
||||||
|
|
||||||
|
input("\n[Enter] para continuar...")
|
||||||
|
|
||||||
|
|
||||||
|
# -- stat menu
|
||||||
|
|
||||||
def stat_menu(df: pd.DataFrame):
|
def stat_menu(df: pd.DataFrame):
|
||||||
inStats = True
|
inStats = True
|
||||||
while inStats:
|
while inStats:
|
||||||
@@ -51,6 +164,10 @@ def stat_menu(df: pd.DataFrame):
|
|||||||
usrIn = input("Opção: ").lower()
|
usrIn = input("Opção: ").lower()
|
||||||
|
|
||||||
match usrIn:
|
match usrIn:
|
||||||
|
case "t":
|
||||||
|
t5_menu(df)
|
||||||
|
continue
|
||||||
|
|
||||||
case "1":
|
case "1":
|
||||||
c = filter_submenu("Média")
|
c = filter_submenu("Média")
|
||||||
|
|
||||||
@@ -106,7 +223,7 @@ def stat_menu(df: pd.DataFrame):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
case "6":
|
case "6":
|
||||||
c = filter_submenu("Mínimo")
|
c = filter_submenu("Moda")
|
||||||
|
|
||||||
if c is not None:
|
if c is not None:
|
||||||
retValue = moda(df, c)
|
retValue = moda(df, c)
|
||||||
@@ -120,11 +237,12 @@ def stat_menu(df: pd.DataFrame):
|
|||||||
|
|
||||||
case _:
|
case _:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
input("Clica `Enter` para continuar")
|
input("Clica `Enter` para continuar")
|
||||||
|
|
||||||
|
|
||||||
def average(df: pd.DataFrame, filter_by):
|
def average(df: pd.DataFrame, filter_by):
|
||||||
events = df.drop_duplicates(subset="ID", keep='first')
|
events = _get_unique_events(df)
|
||||||
values = events[filter_by].to_numpy()
|
values = events[filter_by].to_numpy()
|
||||||
|
|
||||||
if filter_by == "Magnitudes":
|
if filter_by == "Magnitudes":
|
||||||
@@ -136,7 +254,7 @@ def average(df: pd.DataFrame, filter_by):
|
|||||||
|
|
||||||
|
|
||||||
def variance(df, filter_by):
|
def variance(df, filter_by):
|
||||||
events = df.drop_duplicates(subset="ID", keep='first')
|
events = _get_unique_events(df)
|
||||||
values = events[filter_by].to_numpy()
|
values = events[filter_by].to_numpy()
|
||||||
|
|
||||||
if filter_by == "Magnitudes":
|
if filter_by == "Magnitudes":
|
||||||
@@ -149,7 +267,7 @@ def variance(df, filter_by):
|
|||||||
|
|
||||||
|
|
||||||
def std_dev(df, filter_by):
|
def std_dev(df, filter_by):
|
||||||
events = df.drop_duplicates(subset="ID", keep='first')
|
events = _get_unique_events(df)
|
||||||
values = events[filter_by].to_numpy()
|
values = events[filter_by].to_numpy()
|
||||||
|
|
||||||
if filter_by == "Magnitudes":
|
if filter_by == "Magnitudes":
|
||||||
@@ -162,7 +280,7 @@ def std_dev(df, filter_by):
|
|||||||
|
|
||||||
|
|
||||||
def max_v(df, filter_by):
|
def max_v(df, filter_by):
|
||||||
events = df.drop_duplicates(subset="ID", keep='first')
|
events = _get_unique_events(df)
|
||||||
values = events[filter_by].to_numpy()
|
values = events[filter_by].to_numpy()
|
||||||
|
|
||||||
if filter_by == "Magnitudes":
|
if filter_by == "Magnitudes":
|
||||||
@@ -172,7 +290,7 @@ def max_v(df, filter_by):
|
|||||||
|
|
||||||
|
|
||||||
def min_v(df, filter_by):
|
def min_v(df, filter_by):
|
||||||
events = df.drop_duplicates(subset="ID", keep='first')
|
events = _get_unique_events(df)
|
||||||
values = events[filter_by].to_numpy()
|
values = events[filter_by].to_numpy()
|
||||||
|
|
||||||
if filter_by == "Magnitudes":
|
if filter_by == "Magnitudes":
|
||||||
@@ -182,7 +300,7 @@ def min_v(df, filter_by):
|
|||||||
|
|
||||||
|
|
||||||
def moda(df, filter_by):
|
def moda(df, filter_by):
|
||||||
events = df.drop_duplicates(subset="ID", keep='first')
|
events = _get_unique_events(df)
|
||||||
values = events[filter_by].to_numpy()
|
values = events[filter_by].to_numpy()
|
||||||
|
|
||||||
if filter_by == "Magnitudes":
|
if filter_by == "Magnitudes":
|
||||||
|
|||||||
133
utils/visuals.py
Normal file
133
utils/visuals.py
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import pandas as pd
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
from utils import stats
|
||||||
|
|
||||||
|
# -- helpers
|
||||||
|
|
||||||
|
def plot_bar(x, y, xLabel, yLabel, title):
|
||||||
|
plt.figure(figsize=(10, 6))
|
||||||
|
plt.bar(x, y)
|
||||||
|
plt.xlabel(xLabel)
|
||||||
|
plt.ylabel(yLabel)
|
||||||
|
plt.title(title)
|
||||||
|
plt.xticks(rotation=45)
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.show()
|
||||||
|
|
||||||
|
def plot_linear_with_std(x, mean, std, xLabel, yLabel, title):
|
||||||
|
plt.figure(figsize=(10, 6))
|
||||||
|
plt.errorbar(x, mean, yerr=std, fmt='-o', capsize=5, ecolor='red')
|
||||||
|
plt.xlabel(xLabel)
|
||||||
|
plt.ylabel(yLabel)
|
||||||
|
plt.title(title)
|
||||||
|
plt.xticks(rotation=45)
|
||||||
|
plt.grid(True)
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.show()
|
||||||
|
|
||||||
|
def plot_boxplot(dataList, labels, xLabel, yLabel, title):
|
||||||
|
# dataList: lista de arrays/series, um para cada etiqueta
|
||||||
|
# labels: lista de etiquetas correspondentes a dataList
|
||||||
|
plt.figure(figsize=(10, 6))
|
||||||
|
plt.boxplot(dataList, labels=labels)
|
||||||
|
plt.xlabel(xLabel)
|
||||||
|
plt.ylabel(yLabel)
|
||||||
|
plt.title(title)
|
||||||
|
plt.xticks(rotation=90)
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.show()
|
||||||
|
|
||||||
|
# -- t6 logic
|
||||||
|
|
||||||
|
def viz_events_per_period(df: pd.DataFrame, period: str, title_suffix: str):
|
||||||
|
dates, counts = stats.events_per_period(df, period)
|
||||||
|
# Formatar datas para melhor leitura no gráfico
|
||||||
|
if period == 'D':
|
||||||
|
# dates é um DatetimeIndex
|
||||||
|
labels = [d.strftime('%Y-%m-%d') for d in dates]
|
||||||
|
else:
|
||||||
|
labels = [d.strftime('%Y-%m') for d in dates]
|
||||||
|
|
||||||
|
plot_bar(labels, counts, "Data", "Número de Eventos", f"Eventos por {title_suffix}")
|
||||||
|
|
||||||
|
def viz_linear_stats(df: pd.DataFrame, target: str):
|
||||||
|
# Média +/- Desvio Padrão
|
||||||
|
if target == 'Profundidade':
|
||||||
|
st = stats.stats_depth_month(df)
|
||||||
|
unit = "km"
|
||||||
|
else: # Magnitude
|
||||||
|
st = stats.stats_mag_month(df)
|
||||||
|
unit = "Magn"
|
||||||
|
|
||||||
|
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")
|
||||||
|
|
||||||
|
def viz_boxplot(df: pd.DataFrame, target: str):
|
||||||
|
df = stats.convert_to_datetime(df)
|
||||||
|
events = stats._get_unique_events(df)
|
||||||
|
|
||||||
|
# Agrupar por mês
|
||||||
|
grouped = events.set_index('Data').resample('ME')
|
||||||
|
|
||||||
|
data_to_plot = []
|
||||||
|
labels = []
|
||||||
|
|
||||||
|
for name, group in grouped:
|
||||||
|
if target == 'Profundidade':
|
||||||
|
vals = group['Profundidade'].dropna().values
|
||||||
|
else:
|
||||||
|
# Extrair magnitudes máximas
|
||||||
|
def get_max_mag(mags):
|
||||||
|
vals = [float(m['Magnitude']) for m in mags if 'Magnitude' in m]
|
||||||
|
return max(vals) if vals else np.nan
|
||||||
|
vals = group['Magnitudes'].apply(get_max_mag).dropna().values
|
||||||
|
|
||||||
|
if len(vals) > 0:
|
||||||
|
data_to_plot.append(vals)
|
||||||
|
labels.append(name.strftime('%Y-%m'))
|
||||||
|
|
||||||
|
plot_boxplot(data_to_plot, labels, "Mês", target, f"Boxplot de {target} por Mês")
|
||||||
|
|
||||||
|
|
||||||
|
# --- Menu ---
|
||||||
|
|
||||||
|
VISUALS_MENU = """[1] Gráfico Barras: Eventos por Dia
|
||||||
|
[2] Gráfico Barras: Eventos por Mês
|
||||||
|
[3] Gráfico Linear: Profundidade (Média +/- DP) por Mês
|
||||||
|
[4] Gráfico Linear: Magnitude (Média +/- DP) por Mês
|
||||||
|
[5] Boxplot: Profundidade por Mês
|
||||||
|
[6] Boxplot: Magnitude por Mês
|
||||||
|
|
||||||
|
[Q] Voltar
|
||||||
|
"""
|
||||||
|
|
||||||
|
HEADER = "=== T6: Representação Gráfica ==="
|
||||||
|
|
||||||
|
def visual_menu(df: pd.DataFrame):
|
||||||
|
while True:
|
||||||
|
os.system("cls" if sys.platform == "windows" else "clear")
|
||||||
|
print(HEADER + "\n" + VISUALS_MENU)
|
||||||
|
usrIn = input("Opção: ").lower()
|
||||||
|
|
||||||
|
match usrIn:
|
||||||
|
case "1":
|
||||||
|
viz_events_per_period(df, 'D', "Dia")
|
||||||
|
case "2":
|
||||||
|
viz_events_per_period(df, 'M', "Mês")
|
||||||
|
case "3":
|
||||||
|
viz_linear_stats(df, "Profundidade")
|
||||||
|
case "4":
|
||||||
|
viz_linear_stats(df, "Magnitude")
|
||||||
|
case "5":
|
||||||
|
viz_boxplot(df, "Profundidade")
|
||||||
|
case "6":
|
||||||
|
viz_boxplot(df, "Magnitude")
|
||||||
|
case "q":
|
||||||
|
return
|
||||||
|
case _:
|
||||||
|
pass
|
||||||
Reference in New Issue
Block a user