teehr 0.5.0__tar.gz → 0.5.1.dev10__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {teehr-0.5.0 → teehr-0.5.1.dev10}/PKG-INFO +3 -2
- {teehr-0.5.0 → teehr-0.5.1.dev10}/pyproject.toml +2 -1
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/__init__.py +2 -2
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/examples/03-metric_queries.ipynb +1 -1
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/fetching/usgs/usgs.py +16 -3
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/metrics/bootstrap_funcs.py +82 -5
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/metrics/deterministic_funcs.py +166 -34
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/metrics/signature_funcs.py +65 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/models/calculated_fields/row_level.py +47 -2
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/models/calculated_fields/timeseries_aware.py +453 -13
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/models/metrics/deterministic_models.py +130 -19
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/models/metrics/metric_attributes.py +16 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/models/metrics/signature_models.py +92 -11
- {teehr-0.5.0 → teehr-0.5.1.dev10}/LICENSE.md +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/README.md +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/const.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/evaluation/__init__.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/evaluation/evaluation.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/evaluation/fetch.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/evaluation/generate.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/evaluation/metrics.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/evaluation/tables/attribute_table.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/evaluation/tables/base_table.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/evaluation/tables/configuration_table.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/evaluation/tables/domain_table.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/evaluation/tables/joined_timeseries_table.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/evaluation/tables/location_attribute_table.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/evaluation/tables/location_crosswalk_table.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/evaluation/tables/location_table.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/evaluation/tables/primary_timeseries_table.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/evaluation/tables/secondary_timeseries_table.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/evaluation/tables/timeseries_table.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/evaluation/tables/unit_table.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/evaluation/tables/variable_table.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/evaluation/utils.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/example_data/__init__.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/example_data/ensemble_example_data.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/example_data/nwm_gridded_example_data.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/example_data/nwm_streamflow_example_data.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/example_data/two_locations.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/example_data/v0_3_test_study.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/examples/01-evaluation_setup.ipynb +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/examples/02-table_queries.ipynb +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/examples/04-table_filters.ipynb +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/examples/05-2_site_setup.ipynb +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/examples/06-2_site_query.ipynb +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/examples/07-2_site_plotting.ipynb +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/examples/08-sql.ipynb +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/examples/09-ensemble_metric_queries.ipynb +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/examples/__init__.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/examples/clone_from_s3.ipynb +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/examples/load_netcdf.ipynb +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/examples/ngen_example.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/examples/partition_by_reference_time.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/examples/setup_ensemble_example.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/examples/setup_evaluation.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/examples/setup_nwm_grid_example.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/examples/setup_nwm_streamflow_example.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/examples/two_site_debug_script.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/fetching/__init__.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/fetching/const.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/fetching/nwm/__init__.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/fetching/nwm/grid_utils.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/fetching/nwm/nwm_grids.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/fetching/nwm/nwm_points.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/fetching/nwm/point_utils.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/fetching/nwm/retrospective_grids.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/fetching/nwm/retrospective_points.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/fetching/readme.md +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/fetching/usgs/__init__.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/fetching/utils.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/generate/utils.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/loading/__init__.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/loading/location_attributes.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/loading/location_crosswalks.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/loading/locations.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/loading/readme.md +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/loading/s3/clone_from_s3.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/loading/timeseries.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/loading/utils.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/metrics/__init__.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/metrics/gumboot_bootstrap.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/metrics/probabilistic_funcs.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/metrics/readme.md +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/models/__init__.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/models/calculated_fields/base.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/models/fetching/__init__.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/models/fetching/nwm12_grid.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/models/fetching/nwm12_point.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/models/fetching/nwm20_grid.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/models/fetching/nwm20_point.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/models/fetching/nwm22_grid.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/models/fetching/nwm22_point.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/models/fetching/nwm30_grid.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/models/fetching/nwm30_point.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/models/fetching/utils.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/models/filters.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/models/generate/base.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/models/generate/timeseries_generator_models.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/models/metrics/__init__.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/models/metrics/basemodels.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/models/metrics/bootstrap_models.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/models/metrics/probabilistic_models.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/models/pandera_dataframe_schemas.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/models/pydantic_table_models.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/models/readme.md +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/models/str_enum.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/models/table_enums.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/querying/__init__.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/querying/filter_format.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/querying/metric_format.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/querying/readme.md +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/querying/utils.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/template/__init__.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/template/cache/readme.md +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/template/dataset/attributes/_readme.md +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/template/dataset/attributes/attributes.csv +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/template/dataset/configurations/_readme.md +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/template/dataset/configurations/configurations.csv +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/template/dataset/joined_timeseries/_readme.md +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/template/dataset/location_attributes/_readme.md +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/template/dataset/location_crosswalks/_readme.md +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/template/dataset/locations/_readme.md +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/template/dataset/primary_timeseries/_readme.md +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/template/dataset/secondary_timeseries/_readme.md +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/template/dataset/units/_readme.md +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/template/dataset/units/units.csv +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/template/dataset/variables/_readme.md +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/template/dataset/variables/variables.csv +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/template/gitignore_template +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/template/readme.md +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/template/scripts/__init__.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/template/scripts/user_defined_fields.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/utilities/__init__.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/utilities/generate_weights.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/utils/remove_spark_jars.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/utils/s3path.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/utils/utils.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/visualization/__init__.py +0 -0
- {teehr-0.5.0 → teehr-0.5.1.dev10}/src/teehr/visualization/dataframe_accessor.py +0 -0
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: teehr
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.1.dev10
|
|
4
4
|
Summary: Tools for Exploratory Evaluation in Hydrologic Research
|
|
5
5
|
License: GNU v3
|
|
6
|
+
License-File: LICENSE.md
|
|
6
7
|
Author: RTI International
|
|
7
8
|
Requires-Python: >=3.10,<3.14
|
|
8
9
|
Classifier: License :: Other/Proprietary License
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "teehr"
|
|
3
|
-
version = "0.5.
|
|
3
|
+
version = "0.5.1dev10"
|
|
4
4
|
description = "Tools for Exploratory Evaluation in Hydrologic Research"
|
|
5
5
|
authors = [
|
|
6
6
|
"RTI International",
|
|
7
7
|
"Matthew Denno <mdenno@rti.org>",
|
|
8
8
|
"Katie van Werkhoven <kvanwerkhoven@rti.org>",
|
|
9
9
|
"Sam Lamont <slamont@rti.org>",
|
|
10
|
+
"Sam Landsteiner <slandsteiner@rti.org>",
|
|
10
11
|
]
|
|
11
12
|
license = "GNU v3"
|
|
12
13
|
readme = "README.md"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Initialize the TEEHR package."""
|
|
2
2
|
import warnings
|
|
3
3
|
|
|
4
|
-
__version__ = "0.5.
|
|
4
|
+
__version__ = "0.5.1dev10"
|
|
5
5
|
|
|
6
6
|
with warnings.catch_warnings():
|
|
7
7
|
warnings.simplefilter("ignore", UserWarning)
|
|
@@ -10,7 +10,7 @@ with warnings.catch_warnings():
|
|
|
10
10
|
from teehr.evaluation.evaluation import Evaluation # noqa
|
|
11
11
|
from teehr.models.metrics.deterministic_models import DeterministicMetrics # noqa
|
|
12
12
|
from teehr.models.metrics.probabilistic_models import ProbabilisticMetrics # noqa
|
|
13
|
-
from teehr.models.metrics.signature_models import
|
|
13
|
+
from teehr.models.metrics.signature_models import Signatures # noqa
|
|
14
14
|
from teehr.models.metrics.bootstrap_models import Bootstrappers # noqa
|
|
15
15
|
from teehr.models.metrics.basemodels import Operators # noqa
|
|
16
16
|
from teehr.models.pydantic_table_models import ( # noqa
|
|
@@ -44,7 +44,9 @@ from teehr.fetching.const import (
|
|
|
44
44
|
USGS_VARIABLE_MAPPER
|
|
45
45
|
)
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
HOURLY_DATETIME_STR_FMT = "%Y-%m-%dT%H:%M:00+0000"
|
|
48
|
+
DAILY_DATETIME_STR_FMT = "%Y-%m-%d"
|
|
49
|
+
|
|
48
50
|
DAYLIGHT_SAVINGS_PAD = timedelta(hours=2)
|
|
49
51
|
|
|
50
52
|
pd.options.mode.copy_on_write = True
|
|
@@ -211,11 +213,22 @@ def _fetch_usgs_streamflow(
|
|
|
211
213
|
) -> pd.DataFrame:
|
|
212
214
|
"""Fetch USGS gage data and format to TEEHR format."""
|
|
213
215
|
logger.debug("Fetching USGS streamflow data from NWIS.")
|
|
214
|
-
|
|
216
|
+
|
|
217
|
+
if service == "iv":
|
|
218
|
+
datetime_str_format = HOURLY_DATETIME_STR_FMT
|
|
219
|
+
elif service == "dv":
|
|
220
|
+
datetime_str_format = DAILY_DATETIME_STR_FMT
|
|
221
|
+
else:
|
|
222
|
+
err_msg = f"Service '{service}' is not supported. Valid options are 'iv' and 'dv'."
|
|
223
|
+
logger.error(err_msg)
|
|
224
|
+
raise ValueError(err_msg)
|
|
225
|
+
|
|
226
|
+
start_dt_str = start_date.strftime(datetime_str_format)
|
|
227
|
+
|
|
215
228
|
end_dt_str = (
|
|
216
229
|
end_date
|
|
217
230
|
- timedelta(minutes=1)
|
|
218
|
-
).strftime(
|
|
231
|
+
).strftime(datetime_str_format)
|
|
219
232
|
|
|
220
233
|
# Parse out list of sites.
|
|
221
234
|
parsed_sites = _parse_site_id_list(sites)
|
|
@@ -4,13 +4,8 @@ import logging
|
|
|
4
4
|
|
|
5
5
|
import pandas as pd
|
|
6
6
|
import numpy as np
|
|
7
|
-
# from arch.bootstrap import (
|
|
8
|
-
# StationaryBootstrap,
|
|
9
|
-
# CircularBlockBootstrap,
|
|
10
|
-
# )
|
|
11
7
|
|
|
12
8
|
from teehr.models.metrics.basemodels import MetricsBasemodel
|
|
13
|
-
# from teehr.metrics.gumboot_bootstrap import GumbootBootstrap
|
|
14
9
|
|
|
15
10
|
logger = logging.getLogger(__name__)
|
|
16
11
|
|
|
@@ -57,6 +52,33 @@ def create_circularblock_func(model: MetricsBasemodel) -> Callable:
|
|
|
57
52
|
)
|
|
58
53
|
else:
|
|
59
54
|
return results.ravel()
|
|
55
|
+
|
|
56
|
+
def signature_bootstrap_func(p: pd.Series) -> Dict:
|
|
57
|
+
"""Bootstrap function."""
|
|
58
|
+
bs = CircularBlockBootstrap(
|
|
59
|
+
model.bootstrap.block_size,
|
|
60
|
+
p,
|
|
61
|
+
seed=model.bootstrap.seed,
|
|
62
|
+
random_state=model.bootstrap.random_state
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
results = bs.apply(
|
|
66
|
+
model.func(model),
|
|
67
|
+
model.bootstrap.reps
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
if model.bootstrap.quantiles is not None:
|
|
71
|
+
return _calculate_quantiles(
|
|
72
|
+
model.output_field_name,
|
|
73
|
+
results,
|
|
74
|
+
model.bootstrap.quantiles,
|
|
75
|
+
)
|
|
76
|
+
else:
|
|
77
|
+
return results.ravel()
|
|
78
|
+
|
|
79
|
+
if len(model.input_field_names) == 1:
|
|
80
|
+
return signature_bootstrap_func
|
|
81
|
+
|
|
60
82
|
return bootstrap_func
|
|
61
83
|
|
|
62
84
|
|
|
@@ -91,6 +113,34 @@ def create_gumboot_func(model: MetricsBasemodel) -> Callable:
|
|
|
91
113
|
)
|
|
92
114
|
else:
|
|
93
115
|
return results.ravel()
|
|
116
|
+
|
|
117
|
+
def signature_bootstrap_func(p: pd.Series, vt: pd.Series) -> Dict:
|
|
118
|
+
"""Bootstrap function."""
|
|
119
|
+
bs = GumbootBootstrap(
|
|
120
|
+
p,
|
|
121
|
+
value_time=vt,
|
|
122
|
+
seed=model.bootstrap.seed,
|
|
123
|
+
water_year_month=model.bootstrap.water_year_month,
|
|
124
|
+
boot_year_file=model.bootstrap.boot_year_file
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
results = bs.apply(
|
|
128
|
+
model.func(model),
|
|
129
|
+
model.bootstrap.reps
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
if model.bootstrap.quantiles is not None:
|
|
133
|
+
return _calculate_quantiles(
|
|
134
|
+
model.output_field_name,
|
|
135
|
+
results,
|
|
136
|
+
model.bootstrap.quantiles,
|
|
137
|
+
)
|
|
138
|
+
else:
|
|
139
|
+
return results.ravel()
|
|
140
|
+
|
|
141
|
+
if len(model.input_field_names) == 1:
|
|
142
|
+
return signature_bootstrap_func
|
|
143
|
+
|
|
94
144
|
return bootstrap_func
|
|
95
145
|
|
|
96
146
|
|
|
@@ -124,4 +174,31 @@ def create_stationary_func(model: MetricsBasemodel) -> Callable:
|
|
|
124
174
|
)
|
|
125
175
|
else:
|
|
126
176
|
return results.ravel()
|
|
177
|
+
|
|
178
|
+
def signature_bootstrap_func(p: pd.Series) -> Dict:
|
|
179
|
+
"""Bootstrap function."""
|
|
180
|
+
bs = StationaryBootstrap(
|
|
181
|
+
model.bootstrap.block_size,
|
|
182
|
+
p,
|
|
183
|
+
seed=model.bootstrap.seed,
|
|
184
|
+
random_state=model.bootstrap.random_state
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
results = bs.apply(
|
|
188
|
+
model.func(model),
|
|
189
|
+
model.bootstrap.reps
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
if model.bootstrap.quantiles is not None:
|
|
193
|
+
return _calculate_quantiles(
|
|
194
|
+
model.output_field_name,
|
|
195
|
+
results,
|
|
196
|
+
model.bootstrap.quantiles,
|
|
197
|
+
)
|
|
198
|
+
else:
|
|
199
|
+
return results.ravel()
|
|
200
|
+
|
|
201
|
+
if len(model.input_field_names) == 1:
|
|
202
|
+
return signature_bootstrap_func
|
|
203
|
+
|
|
127
204
|
return bootstrap_func
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import numpy as np
|
|
3
3
|
import numpy.typing as npt
|
|
4
4
|
import pandas as pd
|
|
5
|
+
from scipy.stats import rankdata
|
|
5
6
|
|
|
6
7
|
from teehr.models.metrics.basemodels import MetricsBasemodel
|
|
7
8
|
from teehr.models.metrics.basemodels import TransformEnum
|
|
@@ -10,6 +11,8 @@ from typing import Callable, Optional
|
|
|
10
11
|
import logging
|
|
11
12
|
logger = logging.getLogger(__name__)
|
|
12
13
|
|
|
14
|
+
EPSILON = 1e-6 # Small constant to avoid division by zero
|
|
15
|
+
|
|
13
16
|
|
|
14
17
|
def _transform(
|
|
15
18
|
p: pd.Series,
|
|
@@ -22,6 +25,12 @@ def _transform(
|
|
|
22
25
|
if model.transform is not None:
|
|
23
26
|
match model.transform:
|
|
24
27
|
case TransformEnum.log:
|
|
28
|
+
if model.add_epsilon:
|
|
29
|
+
logger.debug(
|
|
30
|
+
"Applying epsilon before log transform"
|
|
31
|
+
)
|
|
32
|
+
p = p + EPSILON
|
|
33
|
+
s = s + EPSILON
|
|
25
34
|
logger.debug("Applying log transform")
|
|
26
35
|
p = np.log(p)
|
|
27
36
|
s = np.log(s)
|
|
@@ -42,6 +51,12 @@ def _transform(
|
|
|
42
51
|
p = np.exp(p)
|
|
43
52
|
s = np.exp(s)
|
|
44
53
|
case TransformEnum.inv:
|
|
54
|
+
if model.add_epsilon:
|
|
55
|
+
logger.debug(
|
|
56
|
+
"Applying epsilon before inverse transform"
|
|
57
|
+
)
|
|
58
|
+
p = p + EPSILON
|
|
59
|
+
s = s + EPSILON
|
|
45
60
|
logger.debug("Applying inverse transform")
|
|
46
61
|
p = 1.0 / p
|
|
47
62
|
s = 1.0 / s
|
|
@@ -125,7 +140,12 @@ def relative_bias(model: MetricsBasemodel) -> Callable:
|
|
|
125
140
|
"""Relative Bias."""
|
|
126
141
|
p, s = _transform(p, s, model)
|
|
127
142
|
difference = s - p
|
|
128
|
-
|
|
143
|
+
if model.add_epsilon:
|
|
144
|
+
result = np.sum(difference)/(np.sum(p) + EPSILON)
|
|
145
|
+
else:
|
|
146
|
+
result = np.sum(difference)/np.sum(p)
|
|
147
|
+
|
|
148
|
+
return result
|
|
129
149
|
|
|
130
150
|
return relative_bias_inner
|
|
131
151
|
|
|
@@ -142,7 +162,12 @@ def mean_absolute_relative_error(model: MetricsBasemodel) -> Callable:
|
|
|
142
162
|
"""Absolute Relative Error."""
|
|
143
163
|
p, s = _transform(p, s, model)
|
|
144
164
|
absolute_difference = np.abs(s - p)
|
|
145
|
-
|
|
165
|
+
if model.add_epsilon:
|
|
166
|
+
result = np.sum(absolute_difference)/(np.sum(p) + EPSILON)
|
|
167
|
+
else:
|
|
168
|
+
result = np.sum(absolute_difference)/np.sum(p)
|
|
169
|
+
|
|
170
|
+
return result
|
|
146
171
|
|
|
147
172
|
return mean_absolute_relative_error_inner
|
|
148
173
|
|
|
@@ -157,7 +182,12 @@ def multiplicative_bias(model: MetricsBasemodel) -> Callable:
|
|
|
157
182
|
def multiplicative_bias_inner(p: pd.Series, s: pd.Series) -> float:
|
|
158
183
|
"""Multiplicative Bias."""
|
|
159
184
|
p, s = _transform(p, s, model)
|
|
160
|
-
|
|
185
|
+
if model.add_epsilon:
|
|
186
|
+
result = np.mean(s)/(np.mean(p) + EPSILON)
|
|
187
|
+
else:
|
|
188
|
+
result = np.mean(s)/np.mean(p)
|
|
189
|
+
|
|
190
|
+
return result
|
|
161
191
|
|
|
162
192
|
return multiplicative_bias_inner
|
|
163
193
|
|
|
@@ -172,11 +202,45 @@ def pearson_correlation(model: MetricsBasemodel) -> Callable:
|
|
|
172
202
|
def pearson_correlation_inner(p: pd.Series, s: pd.Series) -> float:
|
|
173
203
|
"""Pearson Correlation Coefficient."""
|
|
174
204
|
p, s = _transform(p, s, model)
|
|
175
|
-
|
|
205
|
+
|
|
206
|
+
if model.add_epsilon:
|
|
207
|
+
# Calculate covariance between p and s
|
|
208
|
+
numerator = np.cov(p, s)[0, 1]
|
|
209
|
+
|
|
210
|
+
# Calculate standard deviations and multiply them
|
|
211
|
+
denominator = np.nanstd(p) * np.nanstd(s) + EPSILON
|
|
212
|
+
|
|
213
|
+
# Calculate correlation coefficient
|
|
214
|
+
result = numerator / denominator
|
|
215
|
+
|
|
216
|
+
else:
|
|
217
|
+
result = np.corrcoef(s, p)[0][1]
|
|
218
|
+
|
|
219
|
+
return result
|
|
176
220
|
|
|
177
221
|
return pearson_correlation_inner
|
|
178
222
|
|
|
179
223
|
|
|
224
|
+
def variability_ratio(model: MetricsBasemodel) -> Callable:
|
|
225
|
+
"""Create the Variability Ratio metric function.
|
|
226
|
+
|
|
227
|
+
:math:`VR=\\frac{\\sigma_{sec}}{\\sigma_{prim}}`
|
|
228
|
+
""" # noqa
|
|
229
|
+
logger.debug("Building the variability_ratio metric function")
|
|
230
|
+
|
|
231
|
+
def variability_ratio_inner(p: pd.Series, s: pd.Series) -> float:
|
|
232
|
+
"""Variability Ratio."""
|
|
233
|
+
p, s = _transform(p, s, model)
|
|
234
|
+
if model.add_epsilon:
|
|
235
|
+
result = (np.std(s))/(np.std(p) + EPSILON)
|
|
236
|
+
else:
|
|
237
|
+
result = np.std(s)/np.std(p)
|
|
238
|
+
|
|
239
|
+
return result
|
|
240
|
+
|
|
241
|
+
return variability_ratio_inner
|
|
242
|
+
|
|
243
|
+
|
|
180
244
|
def r_squared(model: MetricsBasemodel) -> Callable:
|
|
181
245
|
"""Create the R-squared metric function.
|
|
182
246
|
|
|
@@ -187,8 +251,23 @@ def r_squared(model: MetricsBasemodel) -> Callable:
|
|
|
187
251
|
def r_squared_inner(p: pd.Series, s: pd.Series) -> float:
|
|
188
252
|
"""R-squared."""
|
|
189
253
|
p, s = _transform(p, s, model)
|
|
190
|
-
|
|
191
|
-
|
|
254
|
+
|
|
255
|
+
if model.add_epsilon:
|
|
256
|
+
# Calculate covariance between p and s
|
|
257
|
+
numerator = np.cov(p, s)[0, 1]
|
|
258
|
+
|
|
259
|
+
# Calculate standard deviations and multiply them
|
|
260
|
+
denominator = np.nanstd(p) * np.nanstd(s) + EPSILON
|
|
261
|
+
|
|
262
|
+
# Calculate correlation coefficient and square it
|
|
263
|
+
pearson_correlation_coefficient = numerator / denominator
|
|
264
|
+
result = np.power(pearson_correlation_coefficient, 2)
|
|
265
|
+
|
|
266
|
+
else:
|
|
267
|
+
pearson_correlation_coefficient = np.corrcoef(s, p)[0][1]
|
|
268
|
+
result = np.power(pearson_correlation_coefficient, 2)
|
|
269
|
+
|
|
270
|
+
return result
|
|
192
271
|
|
|
193
272
|
return r_squared_inner
|
|
194
273
|
|
|
@@ -235,10 +314,18 @@ def annual_peak_relative_bias(model: MetricsBasemodel) -> Callable:
|
|
|
235
314
|
secondary_yearly_max_values = df.groupby(
|
|
236
315
|
df.value_time.dt.year
|
|
237
316
|
).secondary_value.max()
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
317
|
+
if model.add_epsilon:
|
|
318
|
+
result = np.sum(
|
|
319
|
+
secondary_yearly_max_values
|
|
320
|
+
- primary_yearly_max_values
|
|
321
|
+
) / (np.sum(primary_yearly_max_values) + EPSILON)
|
|
322
|
+
else:
|
|
323
|
+
result = np.sum(
|
|
324
|
+
secondary_yearly_max_values
|
|
325
|
+
- primary_yearly_max_values
|
|
326
|
+
) / np.sum(primary_yearly_max_values)
|
|
327
|
+
|
|
328
|
+
return result
|
|
242
329
|
|
|
243
330
|
return annual_peak_relative_bias_inner
|
|
244
331
|
|
|
@@ -254,13 +341,23 @@ def spearman_correlation(model: MetricsBasemodel) -> Callable:
|
|
|
254
341
|
"""Spearman Rank Correlation Coefficient."""
|
|
255
342
|
p, s = _transform(p, s, model)
|
|
256
343
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
344
|
+
# calculate ranks (average method for ties)
|
|
345
|
+
primary_ranks = rankdata(p, method='average')
|
|
346
|
+
secondary_ranks = rankdata(s, method='average')
|
|
347
|
+
|
|
348
|
+
# calculate covariance between p_rank and s_rank
|
|
349
|
+
covariance = np.cov(primary_ranks, secondary_ranks)[0, 1]
|
|
350
|
+
|
|
351
|
+
# calculate standard deviations of ranks
|
|
352
|
+
std_primary = np.std(primary_ranks)
|
|
353
|
+
std_secondary = np.std(secondary_ranks)
|
|
354
|
+
|
|
355
|
+
if model.add_epsilon:
|
|
356
|
+
result = covariance / (std_primary * std_secondary + EPSILON)
|
|
357
|
+
else:
|
|
358
|
+
result = covariance / (std_primary * std_secondary)
|
|
359
|
+
|
|
360
|
+
return result
|
|
264
361
|
|
|
265
362
|
return spearman_correlation_inner
|
|
266
363
|
|
|
@@ -282,7 +379,10 @@ def nash_sutcliffe_efficiency(model: MetricsBasemodel) -> Callable:
|
|
|
282
379
|
p, s = _transform(p, s, model)
|
|
283
380
|
|
|
284
381
|
numerator = np.sum(np.subtract(p, s) ** 2)
|
|
285
|
-
|
|
382
|
+
if model.add_epsilon:
|
|
383
|
+
denominator = np.sum(np.subtract(p, np.mean(p)) ** 2) + EPSILON
|
|
384
|
+
else:
|
|
385
|
+
denominator = np.sum(np.subtract(p, np.mean(p)) ** 2)
|
|
286
386
|
if numerator == np.nan or denominator == np.nan:
|
|
287
387
|
return np.nan
|
|
288
388
|
if denominator == 0:
|
|
@@ -313,7 +413,10 @@ def nash_sutcliffe_efficiency_normalized(model: MetricsBasemodel) -> Callable:
|
|
|
313
413
|
p, s = _transform(p, s, model)
|
|
314
414
|
|
|
315
415
|
numerator = np.sum(np.subtract(p, s) ** 2)
|
|
316
|
-
|
|
416
|
+
if model.add_epsilon:
|
|
417
|
+
denominator = np.sum(np.subtract(p, np.mean(p)) ** 2) + EPSILON
|
|
418
|
+
else:
|
|
419
|
+
denominator = np.sum(np.subtract(p, np.mean(p)) ** 2)
|
|
317
420
|
if numerator == np.nan or denominator == np.nan:
|
|
318
421
|
return np.nan
|
|
319
422
|
if denominator == 0:
|
|
@@ -343,10 +446,16 @@ def kling_gupta_efficiency(model: MetricsBasemodel) -> Callable:
|
|
|
343
446
|
linear_correlation = np.corrcoef(s, p)[0, 1]
|
|
344
447
|
|
|
345
448
|
# Relative variability
|
|
346
|
-
|
|
449
|
+
if model.add_epsilon:
|
|
450
|
+
relative_variability = np.std(s) / (np.std(p) + EPSILON)
|
|
451
|
+
else:
|
|
452
|
+
relative_variability = np.std(s) / np.std(p)
|
|
347
453
|
|
|
348
454
|
# Relative mean
|
|
349
|
-
|
|
455
|
+
if model.add_epsilon:
|
|
456
|
+
relative_mean = np.mean(s) / (np.mean(p) + EPSILON)
|
|
457
|
+
else:
|
|
458
|
+
relative_mean = np.mean(s) / np.mean(p)
|
|
350
459
|
|
|
351
460
|
# Scaled Euclidean distance
|
|
352
461
|
euclidean_distance = np.sqrt(
|
|
@@ -379,13 +488,21 @@ def kling_gupta_efficiency_mod1(model: MetricsBasemodel) -> Callable:
|
|
|
379
488
|
linear_correlation = np.corrcoef(s, p)[0, 1]
|
|
380
489
|
|
|
381
490
|
# Variability_ratio
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
491
|
+
if model.add_epsilon:
|
|
492
|
+
variability_ratio = (
|
|
493
|
+
(np.std(s) / (np.mean(s) + EPSILON))
|
|
494
|
+
/ (np.std(p) / (np.mean(p) + EPSILON))
|
|
495
|
+
)
|
|
496
|
+
else:
|
|
497
|
+
variability_ratio = (
|
|
498
|
+
(np.std(s) / np.mean(s))
|
|
499
|
+
/ (np.std(p) / np.mean(p))
|
|
500
|
+
)
|
|
387
501
|
# Relative mean (same as kge)
|
|
388
|
-
|
|
502
|
+
if model.add_epsilon:
|
|
503
|
+
relative_mean = (np.mean(s) / (np.mean(p) + EPSILON))
|
|
504
|
+
else:
|
|
505
|
+
relative_mean = (np.mean(s) / np.mean(p))
|
|
389
506
|
|
|
390
507
|
# Scaled Euclidean distance
|
|
391
508
|
euclidean_distance = np.sqrt(
|
|
@@ -417,14 +534,24 @@ def kling_gupta_efficiency_mod2(model: MetricsBasemodel) -> Callable:
|
|
|
417
534
|
linear_correlation = np.corrcoef(s, p)[0, 1]
|
|
418
535
|
|
|
419
536
|
# Relative variability (same as kge)
|
|
420
|
-
|
|
537
|
+
if model.add_epsilon:
|
|
538
|
+
relative_variability = (np.std(s) / (np.std(p) + EPSILON))
|
|
539
|
+
else:
|
|
540
|
+
relative_variability = (np.std(s) / np.std(p))
|
|
421
541
|
|
|
422
542
|
# bias component
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
543
|
+
if model.add_epsilon:
|
|
544
|
+
bias_component = (
|
|
545
|
+
((np.mean(s) - np.mean(p)) ** 2)
|
|
546
|
+
/
|
|
547
|
+
((np.std(p) ** 2) + EPSILON)
|
|
548
|
+
)
|
|
549
|
+
else:
|
|
550
|
+
bias_component = (
|
|
551
|
+
((np.mean(s) - np.mean(p)) ** 2)
|
|
552
|
+
/
|
|
553
|
+
(np.std(p) ** 2)
|
|
554
|
+
)
|
|
428
555
|
|
|
429
556
|
# Scaled Euclidean distance
|
|
430
557
|
euclidean_distance = np.sqrt(
|
|
@@ -499,7 +626,12 @@ def root_mean_standard_deviation_ratio(model: MetricsBasemodel) -> Callable:
|
|
|
499
626
|
p, s = _transform(p, s, model)
|
|
500
627
|
rmse = _root_mean_squared_error(p, s)
|
|
501
628
|
obs_std_dev = np.std(p)
|
|
502
|
-
|
|
629
|
+
if model.add_epsilon:
|
|
630
|
+
result = rmse / (obs_std_dev + EPSILON)
|
|
631
|
+
else:
|
|
632
|
+
result = rmse / obs_std_dev
|
|
633
|
+
|
|
634
|
+
return result
|
|
503
635
|
|
|
504
636
|
return root_mean_standard_deviation_ratio_inner
|
|
505
637
|
|
|
@@ -8,6 +8,8 @@ from typing import Callable, Optional
|
|
|
8
8
|
import logging
|
|
9
9
|
logger = logging.getLogger(__name__)
|
|
10
10
|
|
|
11
|
+
EPSILON = 1e-6 # Small constant to avoid division by zero
|
|
12
|
+
|
|
11
13
|
|
|
12
14
|
def _transform(
|
|
13
15
|
p: pd.Series,
|
|
@@ -19,6 +21,9 @@ def _transform(
|
|
|
19
21
|
if model.transform is not None:
|
|
20
22
|
match model.transform:
|
|
21
23
|
case TransformEnum.log:
|
|
24
|
+
if model.add_epsilon:
|
|
25
|
+
logger.debug("Adding epsilon to avoid log(0)")
|
|
26
|
+
p = p + EPSILON
|
|
22
27
|
logger.debug("Applying log transform")
|
|
23
28
|
p = np.log(p)
|
|
24
29
|
case TransformEnum.sqrt:
|
|
@@ -34,6 +39,9 @@ def _transform(
|
|
|
34
39
|
logger.debug("Applying exponential transform")
|
|
35
40
|
p = np.exp(p)
|
|
36
41
|
case TransformEnum.inv:
|
|
42
|
+
if model.add_epsilon:
|
|
43
|
+
logger.debug("Adding epsilon to avoid division by zero")
|
|
44
|
+
p = p + EPSILON
|
|
37
45
|
logger.debug("Applying inverse transform")
|
|
38
46
|
p = 1.0 / p
|
|
39
47
|
case TransformEnum.abs:
|
|
@@ -151,3 +159,60 @@ def sum(model: MetricsBasemodel) -> Callable:
|
|
|
151
159
|
return np.sum(p)
|
|
152
160
|
|
|
153
161
|
return sum_inner
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def flow_duration_curve_slope(model: MetricsBasemodel) -> Callable:
|
|
165
|
+
"""Create flow duration curve slope metric function."""
|
|
166
|
+
logger.debug("Building the flow duration curve slope metric function")
|
|
167
|
+
|
|
168
|
+
def flow_duration_curve_slope_inner(
|
|
169
|
+
p: pd.Series,
|
|
170
|
+
) -> float:
|
|
171
|
+
"""Flow duration curve slope."""
|
|
172
|
+
# ensure percentiles are within valid range
|
|
173
|
+
if not (0 <= model.lower_quantile <= 1):
|
|
174
|
+
raise ValueError(
|
|
175
|
+
"Lower quantile must be between 0 and 1"
|
|
176
|
+
)
|
|
177
|
+
if not (0 <= model.upper_quantile <= 1):
|
|
178
|
+
raise ValueError(
|
|
179
|
+
"Upper quantile must be between 0 and 1"
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
# ensure lower quantile is less than upper quantile
|
|
183
|
+
if model.lower_quantile >= model.upper_quantile:
|
|
184
|
+
raise ValueError(
|
|
185
|
+
"Lower quantile must be less than upper quantile"
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
# apply any specified transform
|
|
189
|
+
p = _transform(p, model)
|
|
190
|
+
|
|
191
|
+
# sort the streamflow values in descending order
|
|
192
|
+
p_sorted = p.sort_values(ascending=False).reset_index(drop=True)
|
|
193
|
+
|
|
194
|
+
# calculate exceedance probabilities
|
|
195
|
+
n = len(p_sorted)
|
|
196
|
+
fdc_probs = (p_sorted.index/(n+1))
|
|
197
|
+
|
|
198
|
+
# determine indices for the specified quantiles
|
|
199
|
+
lower_idx = np.argmin(np.abs(fdc_probs - model.lower_quantile))
|
|
200
|
+
upper_idx = np.argmin(np.abs(fdc_probs - model.upper_quantile))
|
|
201
|
+
|
|
202
|
+
# check for as_percentile flag
|
|
203
|
+
if model.as_percentile:
|
|
204
|
+
fdc_probs = fdc_probs * 100
|
|
205
|
+
|
|
206
|
+
# calculate slope between the two quantiles
|
|
207
|
+
if model.add_epsilon:
|
|
208
|
+
slope = (p_sorted.iloc[upper_idx] - p_sorted.iloc[lower_idx]) / ((
|
|
209
|
+
fdc_probs[upper_idx] - fdc_probs[lower_idx]
|
|
210
|
+
) + EPSILON)
|
|
211
|
+
else:
|
|
212
|
+
slope = (p_sorted.iloc[upper_idx] - p_sorted.iloc[lower_idx]) / (
|
|
213
|
+
fdc_probs[upper_idx] - fdc_probs[lower_idx]
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
return slope
|
|
217
|
+
|
|
218
|
+
return flow_duration_curve_slope_inner
|
|
@@ -267,7 +267,7 @@ class ThresholdValueExceeded(CalculatedFieldABC, CalculatedFieldBaseModel):
|
|
|
267
267
|
Default: "primary_value"
|
|
268
268
|
- threshold_field_name:
|
|
269
269
|
The name of the column containing the threshold value.
|
|
270
|
-
Default:
|
|
270
|
+
Default: "secondary_value"
|
|
271
271
|
- output_field_name:
|
|
272
272
|
The name of the column to store the boolean value.
|
|
273
273
|
Default: "threshold_value_exceeded"
|
|
@@ -290,7 +290,51 @@ class ThresholdValueExceeded(CalculatedFieldABC, CalculatedFieldBaseModel):
|
|
|
290
290
|
def func(input_value: pd.Series,
|
|
291
291
|
threshold_value: pd.Series
|
|
292
292
|
) -> pd.Series:
|
|
293
|
-
mask = input_value > threshold_value
|
|
293
|
+
mask = input_value.astype(float) > threshold_value.astype(float)
|
|
294
|
+
return mask
|
|
295
|
+
|
|
296
|
+
sdf = sdf.withColumn(
|
|
297
|
+
self.output_field_name,
|
|
298
|
+
func(self.input_field_name,
|
|
299
|
+
self.threshold_field_name)
|
|
300
|
+
)
|
|
301
|
+
return sdf
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
class ThresholdValueNotExceeded(CalculatedFieldABC, CalculatedFieldBaseModel):
|
|
305
|
+
"""Adds boolean column indicating if the input value is less than or equal to a threshold.
|
|
306
|
+
|
|
307
|
+
Properties
|
|
308
|
+
----------
|
|
309
|
+
- input_field_name:
|
|
310
|
+
The name of the column containing the primary value.
|
|
311
|
+
Default: "primary_value"
|
|
312
|
+
- threshold_field_name:
|
|
313
|
+
The name of the column containing the threshold value.
|
|
314
|
+
Default: "secondary_value"
|
|
315
|
+
- output_field_name:
|
|
316
|
+
The name of the column to store the boolean value.
|
|
317
|
+
Default: "threshold_value_not_exceeded"
|
|
318
|
+
|
|
319
|
+
""" # noqa
|
|
320
|
+
|
|
321
|
+
input_field_name: str = Field(
|
|
322
|
+
default="primary_value"
|
|
323
|
+
)
|
|
324
|
+
threshold_field_name: str = Field(
|
|
325
|
+
default="secondary_value"
|
|
326
|
+
)
|
|
327
|
+
output_field_name: str = Field(
|
|
328
|
+
default="threshold_value_not_exceeded"
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
def apply_to(self, sdf: ps.DataFrame) -> ps.DataFrame:
|
|
332
|
+
"""Apply the calculated field to the Spark DataFrame."""
|
|
333
|
+
@pandas_udf(returnType=T.BooleanType())
|
|
334
|
+
def func(input_value: pd.Series,
|
|
335
|
+
threshold_value: pd.Series
|
|
336
|
+
) -> pd.Series:
|
|
337
|
+
mask = input_value.astype(float) <= threshold_value.astype(float)
|
|
294
338
|
return mask
|
|
295
339
|
|
|
296
340
|
sdf = sdf.withColumn(
|
|
@@ -429,5 +473,6 @@ class RowLevelCalculatedFields:
|
|
|
429
473
|
Seasons = Seasons
|
|
430
474
|
ForecastLeadTime = ForecastLeadTime
|
|
431
475
|
ThresholdValueExceeded = ThresholdValueExceeded
|
|
476
|
+
ThresholdValueNotExceeded = ThresholdValueNotExceeded
|
|
432
477
|
DayOfYear = DayOfYear
|
|
433
478
|
HourOfYear = HourOfYear
|