Source code for linumpy.metrics.aggregate
"""Aggregation utilities for pipeline metrics files."""
import logging
from collections import defaultdict
from pathlib import Path
from typing import Any
import numpy as np
from linumpy.metrics.core import load_metrics
[docs]
logger = logging.getLogger(__name__)
[docs]
def aggregate_metrics(metrics_dir: Path, pattern: str = "*_metrics.json") -> dict[str, list[dict]]:
"""
Aggregate all metrics files from a directory.
Parameters
----------
metrics_dir : str or Path
Directory containing metrics files.
pattern : str
Glob pattern to match metrics files.
Returns
-------
dict
Dictionary with step names as keys and lists of metrics as values.
"""
metrics_dir = Path(metrics_dir)
aggregated: dict[str, list[dict]] = defaultdict(list)
for metrics_file in sorted(metrics_dir.rglob(pattern)):
try:
metrics = load_metrics(metrics_file)
except Exception as e:
logger.warning("Could not load %s: %s", metrics_file, e)
continue
step_name = metrics.get("step_name", "unknown")
metrics["source_file"] = str(metrics_file)
aggregated[step_name].append(metrics)
return dict(aggregated)
[docs]
def compute_summary_statistics(metrics_list: list[dict]) -> dict:
"""
Compute summary statistics for a list of metrics from the same step.
Parameters
----------
metrics_list : list
List of metrics dictionaries from the same step.
Returns
-------
dict
Summary statistics for numerical metrics.
"""
if not metrics_list:
return {}
# Collect all numerical values per metric name
numerical_values: dict[str, list[float]] = defaultdict(list)
statuses: list[str] = []
for m in metrics_list:
statuses.append(m.get("overall_status", "unknown"))
for name, data in m.get("metrics", {}).items():
value = data.get("value")
if isinstance(value, (int, float)) and not isinstance(value, bool):
numerical_values[name].append(float(value))
summary: dict[str, Any] = {
"count": len(metrics_list),
"status_counts": {
"ok": statuses.count("ok"),
"warning": statuses.count("warning"),
"error": statuses.count("error"),
},
}
for name, values in numerical_values.items():
if values:
summary[name] = {
"mean": float(np.mean(values)),
"std": float(np.std(values)),
"min": float(np.min(values)),
"max": float(np.max(values)),
"median": float(np.median(values)),
}
return summary