Files
prog-team-proj/utils/stats.py
2025-12-13 18:19:38 -01:00

498 lines
13 KiB
Python

import os
import sys
from typing import Any, Iterable
import numpy as np
import pandas as pd
from utils.utils import extract_mag_depth
STAT_HEADER = """=== Terramotos ===
== Estatísticas ==
"""
STAT_MENU = """[1] Média
[2] Variância
[3] Desvio padrão
[4] Máximo
[5] Mínimo
[6] Moda
[7] Print de todas as estatísticas
[T] Estatísticas Temporais (T5)
[Q] Voltar ao menu principal
"""
FILTER_CHOICES = """[1] Magnitudes
[2] Distância
[3] Profundidade
"""
CHOICE = {"1": "Magnitudes", "2": "Distancia", "3": "Profundidade"}
def filter_submenu(type: str):
"""[summary]
[description]
Args:
type (str): [description]
Returns:
[type]: [description]
"""
os.system("cls" if sys.platform == "windows" else "clear")
print(f"{STAT_HEADER}\n = {type} = ")
print(FILTER_CHOICES)
choice = input("Qual dos valores: ")
try:
usrChoice = CHOICE[choice]
return usrChoice
except KeyError:
return None
# -- t5 funcs
def _get_unique_events(df: pd.DataFrame) -> pd.DataFrame:
"""Função privada que retorna os eventos únicos
(Ler docstring do `parser.py` para o porquê de se fazer isto)
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')
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) -> pd.DataFrame:
"""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)
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):
"""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
events = _get_unique_events(df)
def _get_max_mag(mags: pd.Series):
"""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
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):
"""Menu de estatisticas das magnitudes e profundidades por mes
Args:
df (pd.DataFrame): Dataframe com os eventos
"""
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):
"""Menu de estatísticas
Args:
df (pd.DataFrame): Dataframe com eventos
"""
inStats = True
while inStats:
os.system("cls" if sys.platform == "windows" else "clear")
print(STAT_HEADER + "\n" + STAT_MENU)
usrIn = input("Opção: ").lower()
match usrIn:
case "t":
t5_menu(df)
continue
case "1":
c = filter_submenu("Média")
if c is not None:
retValue = average(df, c)
if retValue:
print(f"A média de {c} é {retValue}")
else:
print("Um erro aconteceu. Nada a apresentar de momento.")
else:
continue
case "2":
c = filter_submenu("Variância")
if c is not None:
retValue = variance(df, c)
if retValue:
print(f"A variância dos dados de {c} é {retValue}")
else:
print("Um erro aconteceu. Nada a apresentar de momento.")
else:
continue
case "3":
c = filter_submenu("Desvio Padrão")
if c is not None:
retValue = std_dev(df, c)
if retValue:
print(f"O desvio padrão de {c} é {retValue}")
else:
print("Um erro aconteceu. Nada a apresentar de momento.")
else:
continue
case "4":
c = filter_submenu("Máximo")
if c is not None:
retValue = max_v(df, c)
print(f"O valor máximo em {c} é {retValue}")
else:
continue
case "5":
c = filter_submenu("Mínimo")
if c is not None:
retValue = min_v(df, c)
print(f"O valor mínimo em {c} é {retValue}")
else:
continue
case "6":
c = filter_submenu("Moda")
if c is not None:
retValue = moda(df, c)
print(f"O valor moda em {c} é {retValue}")
else:
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":
inStats = False
continue
case _:
pass
input("Clica `Enter` para continuar")
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)
values = events[filter_by].to_numpy()
if filter_by == "Magnitudes":
values = _unpack_mags(values)
try:
return np.average(values)
except Exception:
return None
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)
values = events[filter_by].to_numpy()
if filter_by == "Magnitudes":
values = _unpack_mags(values)
try:
return np.var(values)
except Exception:
return None
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)
values = events[filter_by].to_numpy()
if filter_by == "Magnitudes":
values = _unpack_mags(values)
try:
return np.std(values)
except Exception:
return None
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)
values = events[filter_by].to_numpy()
if filter_by == "Magnitudes":
values = _unpack_mags(values)
return np.max(values)
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)
values = events[filter_by].to_numpy()
if filter_by == "Magnitudes":
values = _unpack_mags(values)
return np.min(values)
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)
values = events[filter_by].to_numpy()
if filter_by == "Magnitudes":
values = _unpack_mags(values)
uniques, count = np.unique(values, return_counts=True)
uniques_list = list(zip(uniques, count))
return sorted(uniques_list, reverse=True, key=lambda x: x[1])[0][0]
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)
for v in arr:
for m in v:
newVals = np.append(newVals, float(m["Magnitude"]))
return newVals