Skip to content

Commit 051b649

Browse files
fixed zero devision bug which occured while testing with fake data; extended tests
1 parent 87277de commit 051b649

File tree

8 files changed

+417
-180
lines changed

8 files changed

+417
-180
lines changed

Diff for: .github/workflows/python-package.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
22
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python
33

4-
name: Python package
4+
name: Python-Package
55

66
on:
77
push:
@@ -36,4 +36,4 @@ jobs:
3636
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
3737
- name: Test with pytest
3838
run: |
39-
pytest cmethods/tests/testing.py
39+
pytest

Diff for: .gitignore

+134-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,139 @@
1-
.DS_Store
2-
__pycache__
3-
.ipynb_checkpoints
1+
# Byte-compiled / optimized / DLL files
2+
__pycache__/
3+
*.py[cod]
4+
*$py.class
5+
# C extensions
6+
*.so
7+
*.zip
48

9+
# Distribution / packaging
10+
.Python
511
build/
12+
develop-eggs/
613
dist/
14+
downloads/
15+
eggs/
16+
.eggs/
17+
lib/
18+
lib64/
19+
parts/
20+
sdist/
21+
var/
22+
wheels/
23+
pip-wheel-metadata/
24+
share/python-wheels/
25+
*.egg-info/
26+
.installed.cfg
27+
*.egg
28+
MANIFEST
29+
30+
# PyInstaller
31+
# Usually these files are written by a python script from a template
32+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
33+
*.manifest
34+
*.spec
35+
36+
# Installer logs
37+
pip-log.txt
38+
pip-delete-this-directory.txt
39+
40+
# Unit test / coverage reports
41+
htmlcov/
42+
.tox/
43+
.nox/
44+
.coverage
45+
.coverage.*
46+
.cache
47+
nosetests.xml
48+
coverage.xml
49+
*.cover
50+
*.py,cover
51+
.hypothesis/
52+
.pytest_cache/
53+
54+
# Translations
55+
*.mo
56+
*.pot
57+
58+
# Django stuff:
59+
*.log
60+
local_settings.py
61+
db.sqlite3
62+
db.sqlite3-journal
63+
64+
# Flask stuff:
65+
instance/
66+
.webassets-cache
67+
68+
# Scrapy stuff:
69+
.scrapy
70+
71+
# Sphinx documentation
72+
docs/_build/
73+
74+
# PyBuilder
75+
target/
76+
77+
# Jupyter Notebook
78+
.ipynb_checkpoints
79+
80+
# IPython
81+
profile_default/
82+
ipython_config.py
83+
84+
# pyenv
85+
.python-version
86+
87+
# pipenv
88+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
89+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
90+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
91+
# install all needed dependencies.
92+
#Pipfile.lock
93+
94+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
95+
__pypackages__/
96+
97+
# Celery stuff
98+
celerybeat-schedule
99+
celerybeat.pid
100+
101+
# SageMath parsed files
102+
*.sage.py
103+
104+
# Environments
105+
.env
106+
.venv
107+
env/
108+
venv/
109+
ENV/
110+
env.bak/
111+
venv.bak/
112+
113+
# Spyder project settings
114+
.spyderproject
115+
.spyproject
116+
117+
# Rope project settings
118+
.ropeproject
119+
120+
# mkdocs documentation
121+
/site
122+
123+
# mypy
124+
.mypy_cache/
125+
.dmypy.json
126+
dmypy.json
127+
del*.py
128+
129+
# Pyre type checker
130+
.pyre/
131+
132+
# misc
133+
.DS_Store
134+
*.csv
135+
*.log
136+
*.zip
137+
7138
*.egg-info/
8139
conda.stuff/

Diff for: README.md

+11-11
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,14 @@ These programs and data structures are designed to help minimize discrepancies b
4343
<figcaption>Figure 1: Schematic representation of a bias adjustment procedure</figcaption>
4444
</figure>
4545

46-
In this way, for example, modeled data, which on average represent values that are too cold, can be adjusted by applying an adjustment procedure. The following figure shows the observed, the modeled and the adjusted values. It is directly visible that the delta adjusted time series ($T^{*DM}_{sim,p}$) are much more similar to the observed data ($T_{obs,p}$) than the raw modeled data ($T_{sim,p}$).
46+
In this way, for example, modeled data, which on average represent values that are too cold, can be adjusted by applying an adjustment procedure. The following figure shows the observed, the modeled, and the adjusted values. It is directly visible that the delta adjusted time series ($T^{*DM}_{sim,p}$) are much more similar to the observed data ($T_{obs,p}$) than the raw modeled data ($T_{sim,p}$).
4747

4848
<figure>
4949
<img
5050
src="images/dm-doy-plot.png?raw=true"
5151
alt="Temperature per day of year in modeled, observed and bias-adjusted climate data"
5252
style="background-color: white; border-radius: 7px">
53-
<figcaption>Figure 2: Temperature per day of year in observed, modeled and bias-adjusted climate data</figcaption>
53+
<figcaption>Figure 2: Temperature per day of year in observed, modeled, and bias-adjusted climate data</figcaption>
5454
</figure>
5555

5656
---
@@ -61,14 +61,14 @@ In this way, for example, modeled data, which on average represent values that a
6161

6262
All methods except the `adjust_3d` function requires the application on one time series.
6363

64-
| Function name | Description |
65-
| ------------------------ | ---------------------------------------------------------------------------------------------------------- |
66-
| `linear_scaling` | Linear Scaling (additive and multiplicative) |
67-
| `variance_scaling` | Variance Scaling (additive) |
68-
| `delta_method` | Delta (Change) Method (additive and multiplicative) |
69-
| `quantile_mapping` | Quantile Mapping (additive) and Detrended Quantile Mapping (additive and multiplicative) |
70-
| `quantile_delta_mapping` | Quantile Delta Mapping (additive and multiplicative) |
71-
| `adjust_3d` | requires a method name and the respective parameters to adjust all time series of a 3-dimensional data set |
64+
| Function name | Description |
65+
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
66+
| `linear_scaling` | Linear Scaling (additive and multiplicative) |
67+
| `variance_scaling` | Variance Scaling (additive) |
68+
| `delta_method` | Delta (Change) Method (additive and multiplicative) |
69+
| `quantile_mapping` | Quantile Mapping (additive and multiplicative) and Detrended Quantile Mapping (additive and multiplicative; to use DQM, set parameter `detrended` to `True`) |
70+
| `quantile_delta_mapping` | Quantile Delta Mapping (additive and multiplicative) |
71+
| `adjust_3d` | requires a method name and the respective parameters to adjust all time series of a 3-dimensional data set |
7272

7373
---
7474

@@ -148,7 +148,7 @@ python3 do_bias_correction.py \
148148

149149
## 5. Notes
150150

151-
- Computation in Python takes some time, so this is only for demonstration. When adjusting large datasets, its best to use the C++ implementation mentioned above.
151+
- Computation in Python takes some time, so this is only for demonstration. When adjusting large datasets, its best to use the C++ implementation mentioned earlier.
152152
- Formulas and references can be found in the implementations of the corresponding functions.
153153

154154
### Space for improvements:

Diff for: cmethods/CMethods.py

+36-46
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,7 @@ def delta_method(cls,
409409
https://svn.oss.deltares.nl/repos/openearthtools/trunk/python/applications/hydrotools/hydrotools/statistics/bias_correction.py
410410
411411
'''
412+
412413
if group is not None: return cls.grouped_correction(method='delta_method', obs=obs, simh=simh, simp=simp, group=group, kind=kind, **kwargs)
413414
if kind in cls.ADDITIVE: return np.array(obs) + (np.nanmean(simp) - np.nanmean(simh)) # Eq. 1
414415
if kind in cls.MULTIPLICATIVE:
@@ -484,7 +485,7 @@ def quantile_mapping(cls,
484485
cdf_obs = cls.get_cdf(obs, xbins)
485486
cdf_simh = cls.get_cdf(simh, xbins)
486487

487-
if kwargs.get('detrended', False) or kind in cls.MULTIPLICATIVE:
488+
if kwargs.get('detrended', False):
488489
# detrended => shift mean of $X_{sim,p}$ to range of $X_{sim,h}$ to adjust extremes
489490
for _, idxs in res.groupby('time.month').groups.items():
490491
m_simh, m_simp = [], []
@@ -498,18 +499,34 @@ def quantile_mapping(cls,
498499
m_simp_mean = np.nanmean(m_simp)
499500

500501
if kind in cls.ADDITIVE:
501-
epsilon = np.interp(m_simp - m_simp_mean + m_simh_mean, xbins, cdf_simh) # Eq. 1
502+
epsilon = np.interp(m_simp - m_simp_mean + m_simh_mean, xbins, cdf_simh) # Eq. 1
502503
X = cls.get_inverse_of_cdf(cdf_obs, epsilon, xbins) + m_simp_mean - m_simh_mean # Eq. 1
503-
else:
504-
epsilon = np.interp((m_simh_mean * m_simp) / m_simp_mean, xbins, cdf_simh, left=.0, right=999.0) # Eq. 2
505-
X = np.interp(epsilon, cdf_obs, xbins, left=.0, right=999.9) * (m_simp_mean / m_simh_mean) # Eq. 2
506-
#x = cm.get_inverse_of_cdf(cdf_obs, epsilon, xbins) * (m_simp_mean / m_simh_mean)
504+
505+
elif kind in cls.MULTIPLICATIVE:
506+
epsilon = np.interp( # Eq. 2
507+
(m_simh_mean * m_simp) / m_simp_mean,
508+
xbins, cdf_simh,
509+
left=kwargs.get('val_min', 0.0),
510+
right=kwargs.get('val_max', None)
511+
)
512+
X = np.interp(epsilon, cdf_obs, xbins) * (m_simp_mean / m_simh_mean) # Eq. 2
507513
for i, idx in enumerate(idxs): res.values[idx] = X[i]
508514
return res
509515

510-
if kind in cls.ADDITIVE: # additive, no detrend
511-
epsilon = np.interp(simp, xbins, cdf_simh) # Eq. 1
512-
res.values = cls.get_inverse_of_cdf(cdf_obs, epsilon, xbins) # Eq. 1
516+
if kind in cls.ADDITIVE:
517+
epsilon = np.interp(simp, xbins, cdf_simh) # Eq. 1
518+
res.values = cls.get_inverse_of_cdf(cdf_obs, epsilon, xbins) # Eq. 1
519+
return res
520+
521+
if kind in cls.MULTIPLICATIVE:
522+
epsilon = np.interp( # Eq. 2
523+
simp,
524+
xbins,
525+
cdf_simh,
526+
left=kwargs.get('val_min', 0.0),
527+
right=kwargs.get('val_max', None)
528+
)
529+
res.values = cls.get_inverse_of_cdf(cdf_obs, epsilon, xbins) # Eq. 2
513530
return res
514531

515532
raise ValueError('Not implemented!')
@@ -527,37 +544,6 @@ def empirical_quantile_mapping(cls,
527544
) -> xr.core.dataarray.DataArray:
528545
''' Method to adjust 1 dimensional climate data by empirical quantile mapping
529546
530-
----- P A R A M E T E R S -----
531-
532-
obs (xarray.core.dataarray.DataArray): observed / obserence Data
533-
simh (xarray.core.dataarray.DataArray): simulated historical Data
534-
simp (xarray.core.dataarray.DataArray): future simulated Data
535-
n_quantiles (int): Quantiles to involve, default: 10
536-
extrapolate (str): [optional] 'linear' or 'constant', default: None
537-
group (str): [optional] Group / Period (e.g.: 'time.month')
538-
539-
----- R E T U R N -----
540-
541-
xarray.core.dataarray.DataArray: Adjusted data
542-
543-
----- E X A M P L E -----
544-
545-
> obs = xarray.open_dataset('path/to/observed/data.nc')
546-
> simh = xarray.open_dataset('path/to/simulated/data.nc')
547-
> simp = xarray.open_dataset('path/to/future/data.nc')
548-
> variable = 'tas'
549-
> result = CMethods().empirical_quantile_mapping(
550-
> obs=obs[variable],
551-
> simh=simh[variable],
552-
> simp=simp[variable],
553-
> group='time.dayofyear'
554-
>)
555-
556-
----- R E F E R E N C E S -----
557-
558-
(April 2022) Taken from:
559-
https://svn.oss.deltares.nl/repos/openearthtools/trunk/python/applications/hydrotools/hydrotools/statistics/bias_correction.py
560-
561547
'''
562548
raise ValueError('not implemented; please have a look at: https://svn.oss.deltares.nl/repos/openearthtools/trunk/python/applications/hydrotools/hydrotools/statistics/bias_correction.py ')
563549
# if group is not None:
@@ -631,8 +617,8 @@ def quantile_delta_mapping(cls,
631617
if kind in cls.ADDITIVE:
632618
res = simp.copy(deep=True)
633619
obs, simh, simp = np.array(obs), np.array(simh), np.array(simp) # to achieve higher accuracy
634-
global_max = max(np.amax(obs), np.amax(simh))
635-
global_min = min(np.amin(obs), np.amin(simh))
620+
global_max = kwargs.get('global_max', max(np.amax(obs), np.amax(simh)))
621+
global_min = kwargs.get('global_min', min(np.amin(obs), np.amin(simh)))
636622
wide = abs(global_max - global_min) / n_quantiles
637623
xbins = np.arange(global_min, global_max + wide, wide)
638624

@@ -650,17 +636,21 @@ def quantile_delta_mapping(cls,
650636
if kind in cls.MULTIPLICATIVE:
651637
res = simp.copy(deep=True)
652638
obs, simh, simp = np.array(obs), np.array(simh), np.array(simp)
653-
global_max = max(np.amax(obs), np.amax(simh))
639+
global_max = kwargs.get('global_max', max(np.amax(obs), np.amax(simh)))
654640
wide = global_max / n_quantiles
655641
xbins = np.arange(kwargs.get('global_min', .0), global_max + wide, wide)
656642

657-
cdf_obs = cls.get_cdf(obs,xbins)
658-
cdf_simh = cls.get_cdf(simh,xbins)
643+
cdf_obs = cls.get_cdf(obs, xbins)
644+
cdf_simh = cls.get_cdf(simh, xbins)
659645
cdf_simp = cls.get_cdf(simp, xbins)
660646

661647
epsilon = np.interp(simp, xbins, cdf_simp) # Eq. 1.1
662648
QDM1 = cls.get_inverse_of_cdf(cdf_obs, epsilon, xbins) # Eq. 1.2
663-
delta = simp / cls.get_inverse_of_cdf(cdf_simh, epsilon, xbins) # Eq. 2.3
649+
650+
with np.errstate(divide='ignore', invalid='ignore'):
651+
delta = simp / cls.get_inverse_of_cdf(cdf_simh, epsilon, xbins) # Eq. 2.3
652+
delta[np.isnan(delta)] = 0
653+
664654
res.values = QDM1 * delta # Eq. 2.4
665655
return res
666656
raise ValueError(f'Unknown kind {kind}!')

Diff for: cmethods/__version__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
'''version of this module'''
2-
VERSION = (0,5,4,2)
2+
VERSION = (0,6,1)
33
__version__ = '.'.join(map(str, VERSION))

0 commit comments

Comments
 (0)