|
1 | 1 | """HydroErr metrics and helpers.""" |
2 | 2 |
|
3 | 3 | import warnings |
4 | | -from collections.abc import Sequence |
5 | 4 | from typing import Any |
6 | 5 |
|
7 | 6 | import numpy as np |
8 | | -from numpy.typing import NDArray |
9 | 7 | from scipy.stats import gmean, rankdata |
10 | 8 |
|
11 | | -# Typing aliases |
12 | | -InputArray = NDArray[np.floating | np.integer] | Sequence[int | float] |
13 | | -FloatArray = NDArray[np.floating | np.integer] |
| 9 | +from .typing_aliases import InputArray |
| 10 | +from .util import treat_values |
14 | 11 |
|
15 | 12 | __all__: list[str] = [ |
16 | 13 | "acc", |
@@ -6287,11 +6284,6 @@ def mean_var( |
6287 | 6284 | return np.var(np.log1p(observed_array) - np.log1p(simulated_array)) |
6288 | 6285 |
|
6289 | 6286 |
|
6290 | | -#################################################################################################### |
6291 | | -# HELPER FUNCTIONS AND API INFORMATION # |
6292 | | -#################################################################################################### |
6293 | | - |
6294 | | - |
6295 | 6287 | metric_names = [ |
6296 | 6288 | "Mean Error", |
6297 | 6289 | "Mean Absolute Error", |
@@ -6531,130 +6523,3 @@ def mean_var( |
6531 | 6523 | for i in range(len(function_list)): |
6532 | 6524 | function_list[i].name = metric_names[i] # type: ignore[invalid-assignment] |
6533 | 6525 | function_list[i].abbr = metric_abbr[i] # type: ignore[invalid-assignment] |
6534 | | - |
6535 | | - |
6536 | | -def treat_values( # noqa: C901 |
6537 | | - simulated_array: InputArray, |
6538 | | - observed_array: InputArray, |
6539 | | - replace_nan: float | None = None, |
6540 | | - replace_inf: float | None = None, |
6541 | | - remove_neg: bool = False, |
6542 | | - remove_zero: bool = False, |
6543 | | -) -> tuple[FloatArray, FloatArray]: |
6544 | | - """Remove the nan, negative, and inf values in two numpy arrays.""" |
6545 | | - sim_copy: FloatArray = np.copy(simulated_array) |
6546 | | - obs_copy: FloatArray = np.copy(observed_array) |
6547 | | - |
6548 | | - # Checking to see if the vectors are the same length |
6549 | | - if not sim_copy.ndim == 1: |
6550 | | - raise ValueError("The simulated array is not one dimensional.") |
6551 | | - if not obs_copy.ndim == 1: |
6552 | | - raise ValueError("The observed array is not one dimensional.") |
6553 | | - |
6554 | | - if sim_copy.size != obs_copy.size: |
6555 | | - raise ValueError("The two ndarrays are not the same size.") |
6556 | | - |
6557 | | - # Treat missing data in observed_array and simulated_array, rows in simulated_array or |
6558 | | - # observed_array that contain nan values |
6559 | | - all_treatment_array = np.ones(obs_copy.size, dtype=bool) |
6560 | | - |
6561 | | - if np.any(np.isnan(obs_copy)) or np.any(np.isnan(sim_copy)): |
6562 | | - if replace_nan is not None: |
6563 | | - # Finding the NaNs |
6564 | | - sim_nan = np.isnan(sim_copy) |
6565 | | - obs_nan = np.isnan(obs_copy) |
6566 | | - # Replacing the NaNs with the input |
6567 | | - sim_copy[sim_nan] = replace_nan |
6568 | | - obs_copy[obs_nan] = replace_nan |
6569 | | - |
6570 | | - warnings.warn( |
6571 | | - f"Elements(s) {np.where(sim_nan)[0]} contained NaN values in the simulated array" |
6572 | | - f" and elements(s) {np.where(obs_nan)[0]} contained NaN values in the observed" |
6573 | | - " array and have been replaced (Elements are zero indexed).", |
6574 | | - UserWarning, |
6575 | | - stacklevel=2, |
6576 | | - ) |
6577 | | - else: |
6578 | | - # Getting the indices of the nan values, combining them, and informing user. |
6579 | | - nan_indices_fcst = ~np.isnan(sim_copy) |
6580 | | - nan_indices_obs = ~np.isnan(obs_copy) |
6581 | | - all_nan_indices = np.logical_and(nan_indices_fcst, nan_indices_obs) |
6582 | | - all_treatment_array = np.logical_and(all_treatment_array, all_nan_indices) |
6583 | | - |
6584 | | - warnings.warn( |
6585 | | - f"Row(s) {np.where(~all_nan_indices)[0]} contained NaN values and the row(s) have" |
6586 | | - f" been removed (Rows are zero indexed).", |
6587 | | - UserWarning, |
6588 | | - stacklevel=2, |
6589 | | - ) |
6590 | | - |
6591 | | - if np.any(np.isinf(obs_copy)) or np.any(np.isinf(sim_copy)): |
6592 | | - if replace_nan is not None: |
6593 | | - # Finding the NaNs |
6594 | | - sim_inf = np.isinf(sim_copy) |
6595 | | - obs_inf = np.isinf(obs_copy) |
6596 | | - # Replacing the NaNs with the input |
6597 | | - sim_copy[sim_inf] = replace_inf |
6598 | | - obs_copy[obs_inf] = replace_inf |
6599 | | - |
6600 | | - warnings.warn( |
6601 | | - f"Elements(s) {np.where(sim_inf)[0]} contained Inf values in the simulated array" |
6602 | | - f" and elements(s) {np.where(obs_inf)[0]} contained Inf values in the observed" |
6603 | | - " array and have been replaced (Elements are zero indexed).", |
6604 | | - UserWarning, |
6605 | | - stacklevel=2, |
6606 | | - ) |
6607 | | - else: |
6608 | | - inf_indices_fcst = ~(np.isinf(sim_copy)) |
6609 | | - inf_indices_obs = ~np.isinf(obs_copy) |
6610 | | - all_inf_indices = np.logical_and(inf_indices_fcst, inf_indices_obs) |
6611 | | - all_treatment_array = np.logical_and(all_treatment_array, all_inf_indices) |
6612 | | - |
6613 | | - warnings.warn( |
6614 | | - f"Row(s) {np.where(~all_inf_indices)[0]} contained Inf or -Inf values and" |
6615 | | - " the row(s) have been removed (Rows are zero indexed).", |
6616 | | - UserWarning, |
6617 | | - stacklevel=2, |
6618 | | - ) |
6619 | | - |
6620 | | - # Treat zero data in observed_array and simulated_array, rows in simulated_array or |
6621 | | - # observed_array that contain zero values |
6622 | | - if remove_zero and ((obs_copy == 0).any() or (sim_copy == 0).any()): |
6623 | | - zero_indices_fcst = ~(sim_copy == 0) |
6624 | | - zero_indices_obs = ~(obs_copy == 0) |
6625 | | - all_zero_indices = np.logical_and(zero_indices_fcst, zero_indices_obs) |
6626 | | - all_treatment_array = np.logical_and(all_treatment_array, all_zero_indices) |
6627 | | - |
6628 | | - warnings.warn( |
6629 | | - f"Row(s) {np.where(~all_zero_indices)[0]} contained zero values and the row(s)" |
6630 | | - " have been removed (Rows are zero indexed).", |
6631 | | - UserWarning, |
6632 | | - stacklevel=2, |
6633 | | - ) |
6634 | | - |
6635 | | - # Treat negative data in observed_array and simulated_array, rows in simulated_array or |
6636 | | - # observed_array that contain negative values |
6637 | | - |
6638 | | - # Ignore runtime warnings from comparing |
6639 | | - if remove_neg: |
6640 | | - with np.errstate(invalid="ignore"): |
6641 | | - obs_copy_bool = obs_copy < 0 |
6642 | | - sim_copy_bool = sim_copy < 0 |
6643 | | - |
6644 | | - if obs_copy_bool.any() or sim_copy_bool.any(): |
6645 | | - neg_indices_fcst = ~sim_copy_bool |
6646 | | - neg_indices_obs = ~obs_copy_bool |
6647 | | - all_neg_indices = np.logical_and(neg_indices_fcst, neg_indices_obs) |
6648 | | - all_treatment_array = np.logical_and(all_treatment_array, all_neg_indices) |
6649 | | - |
6650 | | - warnings.warn( |
6651 | | - f"Row(s) {np.where(~all_neg_indices)[0]} contained negative values and the row(s)" |
6652 | | - f" have been removed (Rows are zero indexed).", |
6653 | | - UserWarning, |
6654 | | - stacklevel=2, |
6655 | | - ) |
6656 | | - |
6657 | | - obs_copy = obs_copy[all_treatment_array] |
6658 | | - sim_copy = sim_copy[all_treatment_array] |
6659 | | - |
6660 | | - return sim_copy, obs_copy |
0 commit comments