Skip to content

Commit fecdd7f

Browse files
removed xclim functions; added setup.py; minor change in project structure
1 parent f3d00c9 commit fecdd7f

File tree

10 files changed

+201
-173
lines changed

10 files changed

+201
-173
lines changed

.gitignore

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,7 @@
11
.DS_Store
2-
__pycache__
2+
__pycache__
3+
.ipynb_checkpoints
4+
5+
build/
6+
dist/
7+
*.egg-info/

MANIFEST.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
include README.md LICENSE

README.md

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
# Bias-Adjustment-Python
22

3-
Collection of different scale- and distribution-based bias adjustments for climatic research. This methods are part of the bachelor thesis of Benjamin T. Schwertfeger.
4-
During this thesis, many of these methods have also been implemented in C++.
5-
This can be found here: [https://github.com/btschwertfeger/Bias-Adjustment-Cpp](https://github.com/btschwertfeger/Bias-Adjustment-Cpp).
3+
Collection of different scale- and distribution-based bias adjustment techniques for climatic research. (see `examples.ipynb` for help)
64

7-
There is also a Jupyter Notebook that serves as example.
5+
Bias adjustment procedures in Python are very slow, so they should not be used on large data sets.
6+
A C++ Implementation that works way faster can be found here: [https://github.com/btschwertfeger/Bias-Adjustment-Cpp](https://github.com/btschwertfeger/Bias-Adjustment-Cpp).
87
____
98
## Run adjustment:
109
```bash
@@ -28,14 +27,6 @@ ____
2827
|Quantile Mapping|quantile_mapping|
2928
|Quantile Delta Mapping|quantile_delta_mapping|
3029

31-
## Methods adapted from [xclim](https://xclim.readthedocs.io/en/stable/sdba.html):
32-
|Method| `--method` parameter|
33-
|-----|-----|
34-
|Empirical Quantile Mapping|xclim_eqm|
35-
|Detrended Quantile Mapping|xclim_dqm|
36-
|Quantile Delta Mapping|xclime_qdm|
37-
38-
3930
____
4031
# Notes:
4132
- Linear and variance, as well as delta change method require `--group time.month` as argument.

CMethods.py renamed to cmethods/CMethods.py

Lines changed: 14 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,11 @@
11
#!/bin/python3
22

33
import multiprocessing
4-
import xclim as xc
5-
from xclim import sdba
64
import xarray as xr
75
import numpy as np
8-
from scipy.stats.mstats import mquantiles
9-
from scipy.interpolate import interp1d
10-
116
from tqdm import tqdm
127

13-
14-
__descrption__ = 'Script to adjust climate biases in 3D Climate data'
8+
__descrption__ = 'Script to adjust climate bias in climate data'
159
__author__ = 'Benjamin Thomas Schwertfeger'
1610
__copyright__ = __author__
1711
__email__ = '[email protected]'
@@ -52,10 +46,9 @@ def __init__(self, method: str, available_methods: list):
5246

5347
CUSTOM_METHODS = [
5448
'linear_scaling', 'variance_scaling', 'delta_method',
55-
'quantile_mapping', 'empirical_quantile_mapping', 'quantile_delta_mapping'
49+
'quantile_mapping', 'quantile_delta_mapping'
5650
]
57-
XCLIM_SDBA_METHODS = ['xclim_eqm', 'xclim_dqm', 'xclim_qdm']
58-
METHODS = CUSTOM_METHODS + XCLIM_SDBA_METHODS
51+
METHODS = CUSTOM_METHODS #+ XCLIM_SDBA_METHODS
5952

6053
def __init__(self):
6154
pass
@@ -67,10 +60,7 @@ def get_available_methods(cls) -> list:
6760

6861
@classmethod
6962
def get_function(cls, method: str):
70-
if method == 'xclim_dqm': return sdba.adjustment.DetrendedQuantileMapping.train
71-
elif method == 'xclim_eqm': return sdba.adjustment.EmpiricalQuantileMapping.train
72-
elif method == 'xclim_qdm': return sdba.adjustment.QuantileDeltaMapping.train
73-
elif method == 'linear_scaling': return cls.linear_scaling
63+
if method == 'linear_scaling': return cls.linear_scaling
7464
elif method == 'variance_scaling': return cls.variance_scaling
7565
elif method == 'delta_method': return cls.delta_method
7666
elif method == 'quantile_mapping': return cls.quantile_mapping
@@ -140,40 +130,7 @@ def adjust_2d(cls,
140130
result = simp.copy(deep=True).load()
141131
len_lat, len_lon = len(obs.lat), len(obs.lon)
142132

143-
if method in cls.XCLIM_SDBA_METHODS:
144-
if n_jobs == 1:
145-
method = cls.get_function(method)
146-
for lat in tqdm(range(len_lat)):
147-
for lon in range(len_lon):
148-
result[lat,lon] = method(
149-
obs = obs[lat,lon],
150-
simh = simh[lat,lon],
151-
simp = simp[lat,lon],
152-
tslice_adjust = tslice_adjust,
153-
n_quantiles = n_quantiles,
154-
kind = kind,
155-
group = group,
156-
window = window,
157-
**kwargs
158-
)
159-
else:
160-
with multiprocessing.Pool(processes=n_jobs) as pool:
161-
params: [dict] = [{
162-
'method': method,
163-
'obs': obs[lat],
164-
'simh': simh[lat],
165-
'simp': simp[lat],
166-
'tslice_adjust': tslice_adjust,
167-
'kind': kind,
168-
'group': group,
169-
'window': window,
170-
'kwargs': kwargs
171-
} for lat in range(len_lat)]
172-
for lat, corrected in enumerate(pool.map(cls.pool_adjust, params)):
173-
result[lat] = corrected
174-
return result.transpose('time', 'lat', 'lon')
175-
176-
elif method in cls.CUSTOM_METHODS:
133+
if method in cls.CUSTOM_METHODS:
177134
if n_jobs == 1:
178135
method = cls.get_function(method)
179136
for lat in tqdm(range(len_lat)):
@@ -226,25 +183,7 @@ def pool_adjust(cls, params) -> xr.core.dataarray.DataArray:
226183
len_lon = len(obs.lon)
227184

228185
func_adjustment = None
229-
if method in cls.XCLIM_SDBA_METHODS:
230-
func_adjustment = cls.get_function(method)
231-
for lon in range(len_lon):
232-
result[lon] = cls.xclim_sdba_adjustment(
233-
method = func_adjustment,
234-
method_name = method,
235-
obs = obs,
236-
simh = simh,
237-
simp = simp,
238-
tslice_adjust = tslice_adjust,
239-
n_quantiles = n_quantiles,
240-
group = group,
241-
window = window,
242-
save_model = save_model,
243-
**kwargs
244-
)
245-
return result
246-
247-
elif method in cls.CUSTOM_METHODS:
186+
if method in cls.CUSTOM_METHODS:
248187
func_adjustment = cls.get_function(method)
249188
kwargs['n_quantiles'] = n_quantiles
250189
kwargs['kind'] = kind
@@ -255,57 +194,7 @@ def pool_adjust(cls, params) -> xr.core.dataarray.DataArray:
255194
return result
256195

257196
else: raise UnknownMethodError(method, cls.METHODS)
258-
259-
@staticmethod
260-
def xclim_sdba_adjustment(
261-
method,
262-
method_name: str,
263-
obs: xr.core.dataarray.DataArray,
264-
simh: xr.core.dataarray.DataArray,
265-
simp: xr.core.dataarray.DataArray,
266-
tslice_adjust: slice=None,
267-
n_quantiles: int=100,
268-
kind: str='+',
269-
group: str='time.month',
270-
window: int=1,
271-
save_model: bool=False
272-
) -> xr.core.dataarray.DataArray:
273-
'''Method to adjust 1 dimensional climate data using the xclim.sdba library
274-
275-
Note: This Method should only be called by the method adjust_2d.
276-
277-
----- P A R A M E T E R S -----
278-
279-
method (method): adjustment method (once of the xclim.sdba library)
280-
method_name (str): Name of the method to use in filename of the model
281-
282-
obs (xarray.core.dataarray.DataArray): observed / obserence Data
283-
simh (xarray.core.dataarray.DataArray): simulated historical Data
284-
simp (xarray.core.dataarray.DataArray): future simulated Data to adjust
285-
286-
tslice_adjust (slice): Timespan to adjust, default: None
287-
n_quantiles (int): Number of quantiles to involve
288-
kind (str): Kind of adjustment ('+' or '*'), default: '+'
289-
group (str): Group data by, default: 'time.month'
290-
window (int): Grouping window, default: 1
291-
292-
----- R E T U R N -----
293-
294-
xarray.core.dataarray.DataArray: Adjusted data
295-
296-
'''
297-
298-
grouper = xc.sdba.Grouper(group=group, window=window)
299-
model = method(obs, simh, nquantiles=n_quantiles, kind=kind, group=grouper)
300-
301-
if save_model: model.ds.to_netcdf(f'{method_name}_model_nquantiles-{n_quantiles}_kind-{kind}_group-{group}_window-{window}.nc')
302-
if tslice_adjust != None: simp = simp.sel(time=tslice_adjust)
303-
304-
bias = model.adjust(simp)
305-
bias.attrs = simp.attrs
306-
307-
return bias
308-
197+
309198
@classmethod
310199
def grouped_correction(cls,
311200
method: str,
@@ -458,7 +347,6 @@ def variance_scaling(cls,
458347
'''
459348
if group != None: return cls.grouped_correction(method='variance_scaling', obs=obs, simh=simh, simp=simp, group=group, kind=kind, **kwargs)
460349
else:
461-
obs, simh, simp = np.array(obs), np.array(simh), np.array(simp)
462350
LS_simh = cls.linear_scaling(obs, simh, simh) # Eq. 1
463351
LS_simp = cls.linear_scaling(obs, simh, simp) # Eq. 2
464352

@@ -544,19 +432,20 @@ def quantile_mapping(cls,
544432

545433
if group != None: return cls.grouped_correction(method='quantile_mapping', obs=obs, simh=simh, simp=simp, group=group, n_quantiles=n_quantiles, kind=kind, **kwargs)
546434
elif kind == '+':
547-
# make np.array to achieve higher accuracy (idk why)
435+
# create np.array to achieve higher accuracy (idk why)
548436
obs, simh, simp = np.array(obs), np.array(simh), np.array(simp)
549-
# define quantile boundaries (xbins)
437+
550438
global_max = max(np.amax(obs), np.amax(simh))
551-
global_min = min(np.amin(obs), np.amin(simh)) # change to 0.0 if precipitation
552-
wide = abs(global_max - global_min) / n_quantiles # change to global_max/n_quantiles if precipitation
439+
global_min = min(np.amin(obs), np.amin(simh))
440+
wide = abs(global_max - global_min) / n_quantiles
553441
xbins = np.arange(global_min, global_max + wide, wide)
554442

555443
cdf_obs = cls.get_cdf(obs, xbins)
556444
cdf_simh = cls.get_cdf(simh, xbins)
557445
epsilon = np.interp(simp, xbins, cdf_simh) # Eq. 1
558446

559447
return cls.get_inverse_of_cdf(cdf_obs, epsilon, xbins) # Eq. 1
448+
560449
elif kind == '*':
561450
''' Inspired by Adrian Tompkins [email protected] posted here:
562451
https://www.researchgate.net/post/Does-anyone-know-about-bias-correction-and-quantile-mapping-in-PYTHON
@@ -625,7 +514,7 @@ def empirical_quantile_mapping(cls,
625514
https://svn.oss.deltares.nl/repos/openearthtools/trunk/python/applications/hydrotools/hydrotools/statistics/bias_correction.py
626515
627516
'''
628-
raise ValueError('idk if it is allowed to use this so please have a look at:https://svn.oss.deltares.nl/repos/openearthtools/trunk/python/applications/hydrotools/hydrotools/statistics/bias_correction.py ')
517+
raise ValueError('idk if it is allowed to use this so please have a look at: https://svn.oss.deltares.nl/repos/openearthtools/trunk/python/applications/hydrotools/hydrotools/statistics/bias_correction.py ')
629518
# if group != None:
630519
# return cls.grouped_correction(
631520
# method = 'empirical_quantile_mapping',
@@ -689,7 +578,6 @@ def quantile_delta_mapping(cls,
689578
# calculate exact cdf values of $F_{sim,p}[T_{sim,p}(t)]$
690579
epsilon = np.interp(simp, xbins, cdf_simp) # Eq. 1
691580

692-
# invert F_{obs,h}
693581
QDM1 = cls.get_inverse_of_cdf(cdf_obs, epsilon, xbins) # Eq. 2
694582
delta = simp - cls.get_inverse_of_cdf(cdf_simh, epsilon, xbins) # Eq. 3
695583

@@ -712,7 +600,7 @@ def get_cdf(a, xbins: list) -> np.array:
712600

713601
@staticmethod
714602
def get_inverse_of_cdf(base_cdf, insert_cdf, xbins) -> np.array:
715-
''' returns the inverse cummulative distribution function of base_cdf ($$F_{base_cdf}\left[insert_cdf\right])$$'''
603+
''' returns the inverse cummulative distribution function of base_cdf ($F_{base_cdf}\left[insert_cdf\right])$'''
716604
return np.interp(insert_cdf, base_cdf, xbins)
717605

718606
@staticmethod

cmethods/__init__.py

Whitespace-only changes.

cmethods/__version__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
VERSION = (0,0,0,0,1,15)
2+
__version__ = '.'.join(map(str, VERSION))

cmethods/requirements.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
xarray
2+
numpy
3+
tqdm
4+
matplotlib

do_bias_correction.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import logging, sys
1212
import xarray as xr
1313

14-
from CMethods import CMethods
14+
from cmethods.CMethods import CMethods
1515

1616
# * ----- L O G G I N G -----
1717
formatter = logging.Formatter(

examples.ipynb

Lines changed: 25 additions & 33 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)