From e4753634a5745e662b74d43df04f5151a7de526d Mon Sep 17 00:00:00 2001 From: btobers Date: Mon, 31 Mar 2025 10:39:03 -0400 Subject: [PATCH 1/2] use dictionary with lambda functions to compile stats, ignore all-nan warnings --- pygem/output.py | 55 +++++++++++++++++++++++-------------------------- 1 file changed, 26 insertions(+), 29 deletions(-) diff --git a/pygem/output.py b/pygem/output.py index 579947dd..38500473 100644 --- a/pygem/output.py +++ b/pygem/output.py @@ -17,7 +17,7 @@ import numpy as np import pandas as pd import xarray as xr -import os, types, json, cftime, collections +import os, types, json, cftime, collections, warnings import pygem from pygem.setup.config import ConfigManager # instantiate ConfigManager @@ -735,41 +735,38 @@ def _update_dicts(self): 'temporal_resolution': 'annual', 'comment': 'climatic mass balance is computed before dynamics so can theoretically exceed ice thickness'} + def calc_stats_array(data, stats_cns=pygem_prms['sim']['out']['sim_stats']): """ - Calculate stats for a given variable + Calculate stats for a given variable. Parameters ---------- - vn : str - variable name - ds : xarray dataset - dataset of output with all ensemble simulations + data : np.array + 2D array with ensemble simulations (shape: [n_samples, n_ensembles]) + stats_cns : list, optional + List of statistics to compute (e.g., ['mean', 'std', 'median']) Returns ------- stats : np.array - Statistics related to a given variable + Statistics related to a given variable. """ - stats = None - if 'mean' in stats_cns: - if stats is None: - stats = np.nanmean(data,axis=1)[:,np.newaxis] - if 'std' in stats_cns: - stats = np.append(stats, np.nanstd(data,axis=1)[:,np.newaxis], axis=1) - if '2.5%' in stats_cns: - stats = np.append(stats, np.nanpercentile(data, 2.5, axis=1)[:,np.newaxis], axis=1) - if '25%' in stats_cns: - stats = np.append(stats, np.nanpercentile(data, 25, axis=1)[:,np.newaxis], axis=1) - if 'median' in stats_cns: - if stats is None: - stats = np.nanmedian(data, axis=1)[:,np.newaxis] - else: - stats = np.append(stats, np.nanmedian(data, axis=1)[:,np.newaxis], axis=1) - if '75%' in stats_cns: - stats = np.append(stats, np.nanpercentile(data, 75, axis=1)[:,np.newaxis], axis=1) - if '97.5%' in stats_cns: - stats = np.append(stats, np.nanpercentile(data, 97.5, axis=1)[:,np.newaxis], axis=1) - if 'mad' in stats_cns: - stats = np.append(stats, median_abs_deviation(data, axis=1, nan_policy='omit')[:,np.newaxis], axis=1) - return stats \ No newline at end of file + + stat_funcs = { + 'mean': lambda x: np.nanmean(x, axis=1), + 'std': lambda x: np.nanstd(x, axis=1), + '2.5%': lambda x: np.nanpercentile(x, 2.5, axis=1), + '25%': lambda x: np.nanpercentile(x, 25, axis=1), + 'median': lambda x: np.nanmedian(x, axis=1), + '75%': lambda x: np.nanpercentile(x, 75, axis=1), + '97.5%': lambda x: np.nanpercentile(x, 97.5, axis=1), + 'mad': lambda x: median_abs_deviation(x, axis=1, nan_policy='omit') + } + + # aggregate model bin thicknesses as desired + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) # Suppress All-NaN Slice Warnings + stats_list = [stat_funcs[stat](data) for stat in stats_cns if stat in stat_funcs] + + return np.column_stack(stats_list) if stats_list else None \ No newline at end of file From 89c19a40e58ce68611d6863be0fd1a959da728ac Mon Sep 17 00:00:00 2001 From: btobers Date: Mon, 31 Mar 2025 10:43:43 -0400 Subject: [PATCH 2/2] added comments --- pygem/output.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pygem/output.py b/pygem/output.py index 38500473..1d428720 100644 --- a/pygem/output.py +++ b/pygem/output.py @@ -749,10 +749,11 @@ def calc_stats_array(data, stats_cns=pygem_prms['sim']['out']['sim_stats']): Returns ------- - stats : np.array + stats : np.array, or None Statistics related to a given variable. """ + # dictionary of functions to call for each stat in `stats_cns` stat_funcs = { 'mean': lambda x: np.nanmean(x, axis=1), 'std': lambda x: np.nanstd(x, axis=1), @@ -764,9 +765,10 @@ def calc_stats_array(data, stats_cns=pygem_prms['sim']['out']['sim_stats']): 'mad': lambda x: median_abs_deviation(x, axis=1, nan_policy='omit') } - # aggregate model bin thicknesses as desired + # calculate statustics for each stat in `stats_cns` with warnings.catch_warnings(): warnings.simplefilter("ignore", RuntimeWarning) # Suppress All-NaN Slice Warnings stats_list = [stat_funcs[stat](data) for stat in stats_cns if stat in stat_funcs] - + + # stack stats_list to numpy array return np.column_stack(stats_list) if stats_list else None \ No newline at end of file