Calibration of potential evapotranspiration methods#

M. Vremec, R.A. Collenteur University of Graz, 2021

In this notebook it is shown how to calibrate various (potential) evapotranspiration (PET) equations, using a linear regression relationship between daily potential evapotranspiration obtained with the FAO-56 equation and daily PET estimates obtained with the alternative methods. This notebook requires Scipy and SkLearn python packages to run.

import pandas as pd
import matplotlib.pyplot as plt
from scipy.optimize import least_squares
from sklearn.metrics import mean_squared_error

import pyet as pyet

1. Load KNMI Data#

data = pd.read_csv("data/example_3/etmgeg_260.txt", skiprows=46, delimiter=",", 
                   skipinitialspace=True, index_col="YYYYMMDD", parse_dates=True).loc["2018",:]
data.head()
# STN DDVEC FHVEC FG FHX FHXH FHN FHNH FXX FXXH ... VVNH VVX VVXH NG UG UX UXH UN UNH EV24
YYYYMMDD
2018-01-01 260 225 45 50 90 2 10 18 190.0 2.0 ... 2.0 75.0 3.0 7.0 84 96 17 73 1 3
2018-01-02 260 216 39 45 80 24 20 1 140.0 24.0 ... 19.0 75.0 8.0 7.0 88 96 3 80 8 2
2018-01-03 260 257 82 88 120 11 70 4 290.0 3.0 ... 1.0 75.0 4.0 8.0 73 95 1 65 9 1
2018-01-04 260 238 51 56 90 20 30 14 180.0 20.0 ... 14.0 80.0 21.0 8.0 82 97 14 67 21 2
2018-01-05 260 225 38 40 60 1 20 17 150.0 16.0 ... 15.0 75.0 1.0 6.0 87 96 17 71 3 2

5 rows × 40 columns

2.Estimation of potential evapotranspiration#

# define meteorological variables needed for PE estimation
meteo = pd.DataFrame({"tmean":data.TG/10, "tmax":data.TX/10, "tmin":data.TN/10, "rh":data.UG, "wind":data.FG/10, "rs":data.Q/100})
tmean, tmax, tmin, rh, wind, rs = [meteo[col] for col in meteo.columns]
pressure =  data.PG / 100 # to kPa
wind = data.FG/10  # to m/s
lat = 0.91  # [rad]
elevation = 4 

pet_fao56 = pyet.pm_fao56(tmean, wind, rs=rs, elevation=elevation, lat=lat, 
                         tmax=tmax, tmin=tmin, rh=rh)  # FAO-56 method
pet_romanenko = pyet.romanenko(tmean, rh)  # Romanenko method
pet_abtew = pyet.abtew(tmean, rs)  # Abtew method

The model performance of the Abtew and Romanenko method will be evaluated using the root mean square error (RMSE), as implemented in SkLearn’s mean_squared_error method.

pet_fao56.plot()
pet_romanenko.plot()
pet_abtew.plot()
plt.ylabel("PE [mm/day]")
print("RMSE(Romanenko) = {} mm/d".format(mean_squared_error(pet_fao56, pet_romanenko, squared=False)))
print("RMSE(Abtew) = {} mm/d".format(mean_squared_error(pet_fao56, pet_abtew, squared=False)))
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[4], line 5
      1 pet_fao56.plot()
      2 pet_romanenko.plot()
      3 pet_abtew.plot()
      4 plt.ylabel("PE [mm/day]")
----> 5 print("RMSE(Romanenko) = {} mm/d".format(mean_squared_error(pet_fao56, pet_romanenko, squared=False)))
      6 print("RMSE(Abtew) = {} mm/d".format(mean_squared_error(pet_fao56, pet_abtew, squared=False)))

File ~/checkouts/readthedocs.org/user_builds/pyet/envs/dev/lib/python3.11/site-packages/sklearn/utils/_param_validation.py:196, in validate_params.<locals>.decorator.<locals>.wrapper(*args, **kwargs)
    193 func_sig = signature(func)
    195 # Map *args/**kwargs to the function signature
--> 196 params = func_sig.bind(*args, **kwargs)
    197 params.apply_defaults()
    199 # ignore self/cls and positional/keyword markers

File ~/.asdf/installs/python/3.11.14/lib/python3.11/inspect.py:3195, in Signature.bind(self, *args, **kwargs)
   3190 def bind(self, /, *args, **kwargs):
   3191     """Get a BoundArguments object, that maps the passed `args`
   3192     and `kwargs` to the function's signature.  Raises `TypeError`
   3193     if the passed arguments can not be bound.
   3194     """
-> 3195     return self._bind(args, kwargs)

File ~/.asdf/installs/python/3.11.14/lib/python3.11/inspect.py:3184, in Signature._bind(self, args, kwargs, partial)
   3182         arguments[kwargs_param.name] = kwargs
   3183     else:
-> 3184         raise TypeError(
   3185             'got an unexpected keyword argument {arg!r}'.format(
   3186                 arg=next(iter(kwargs))))
   3188 return self._bound_arguments_cls(self, arguments)

TypeError: got an unexpected keyword argument 'squared'
../_images/c586e6f383827d36b785ff11ebd809ed906ad9f54ef54dc906e106f3d087f5b2.png

3. Calibration of different PE equations#

The least squares approach is applied to estimate the parameters in the PE equations, by minimizing the sum of the residuals between the simulated (Abtew and Romanenko) and observed (FAO-56) data. The minimization of the objective function is done using the Trust Region Reflective algorithm, as implemented in Scipy’s least squares method.

3.1 Romanenko method#

# Define function for computing residuals
def cal_romanenko(k, obs):
    return pyet.romanenko(tmean, rh, k)-obs
# estimate k in the Romanenko method
x0 = 4.5  # initial estimate of parameter
res_1 = least_squares(cal_romanenko, x0, args=[pet_fao56])
res_1.x
array([3.87304623])
# Compute RMSE using the calibrated value of k
pet_romanenko_cal = pyet.romanenko(tmean, rh, k=res_1.x)
print("RMSE(Romanenko) = {} mm/d".format(mean_squared_error(pet_fao56, pet_romanenko_cal, squared=False)))
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[7], line 3
      1 # Compute RMSE using the calibrated value of k
      2 pet_romanenko_cal = pyet.romanenko(tmean, rh, k=res_1.x)
----> 3 print("RMSE(Romanenko) = {} mm/d".format(mean_squared_error(pet_fao56, pet_romanenko_cal, squared=False)))

File ~/checkouts/readthedocs.org/user_builds/pyet/envs/dev/lib/python3.11/site-packages/sklearn/utils/_param_validation.py:196, in validate_params.<locals>.decorator.<locals>.wrapper(*args, **kwargs)
    193 func_sig = signature(func)
    195 # Map *args/**kwargs to the function signature
--> 196 params = func_sig.bind(*args, **kwargs)
    197 params.apply_defaults()
    199 # ignore self/cls and positional/keyword markers

File ~/.asdf/installs/python/3.11.14/lib/python3.11/inspect.py:3195, in Signature.bind(self, *args, **kwargs)
   3190 def bind(self, /, *args, **kwargs):
   3191     """Get a BoundArguments object, that maps the passed `args`
   3192     and `kwargs` to the function's signature.  Raises `TypeError`
   3193     if the passed arguments can not be bound.
   3194     """
-> 3195     return self._bind(args, kwargs)

File ~/.asdf/installs/python/3.11.14/lib/python3.11/inspect.py:3184, in Signature._bind(self, args, kwargs, partial)
   3182         arguments[kwargs_param.name] = kwargs
   3183     else:
-> 3184         raise TypeError(
   3185             'got an unexpected keyword argument {arg!r}'.format(
   3186                 arg=next(iter(kwargs))))
   3188 return self._bound_arguments_cls(self, arguments)

TypeError: got an unexpected keyword argument 'squared'

RMSE (calibrated) = 0.546 < RMSE (uncalibrated) = 0.694

3.2 Abtew method#

# Define function for computing residuals and initial estimate of parameters
def cal_abtew(k,obs):
    return pyet.abtew(tmean, rs, k)-obs
x0 = 0.53
# estimate k in the Romanenko method
res_2 = least_squares(cal_abtew, x0, args=[pet_fao56])
res_2.x
array([0.45748986])
pet_abtew_cali = pyet.abtew(tmean, rs, res_2.x)
print("RMSE(Abtew) = {} mm/d".format(mean_squared_error(pet_fao56, pet_abtew_cali, squared=False)))
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[10], line 2
      1 pet_abtew_cali = pyet.abtew(tmean, rs, res_2.x)
----> 2 print("RMSE(Abtew) = {} mm/d".format(mean_squared_error(pet_fao56, pet_abtew_cali, squared=False)))

File ~/checkouts/readthedocs.org/user_builds/pyet/envs/dev/lib/python3.11/site-packages/sklearn/utils/_param_validation.py:196, in validate_params.<locals>.decorator.<locals>.wrapper(*args, **kwargs)
    193 func_sig = signature(func)
    195 # Map *args/**kwargs to the function signature
--> 196 params = func_sig.bind(*args, **kwargs)
    197 params.apply_defaults()
    199 # ignore self/cls and positional/keyword markers

File ~/.asdf/installs/python/3.11.14/lib/python3.11/inspect.py:3195, in Signature.bind(self, *args, **kwargs)
   3190 def bind(self, /, *args, **kwargs):
   3191     """Get a BoundArguments object, that maps the passed `args`
   3192     and `kwargs` to the function's signature.  Raises `TypeError`
   3193     if the passed arguments can not be bound.
   3194     """
-> 3195     return self._bind(args, kwargs)

File ~/.asdf/installs/python/3.11.14/lib/python3.11/inspect.py:3184, in Signature._bind(self, args, kwargs, partial)
   3182         arguments[kwargs_param.name] = kwargs
   3183     else:
-> 3184         raise TypeError(
   3185             'got an unexpected keyword argument {arg!r}'.format(
   3186                 arg=next(iter(kwargs))))
   3188 return self._bound_arguments_cls(self, arguments)

TypeError: got an unexpected keyword argument 'squared'

RMSE (calibrated) = 0.613 < RMSE (uncalibrated) = 0.741

pet_fao56.plot()
pet_romanenko_cal.plot()
pet_abtew_cali.plot()
plt.ylabel("PET [mm/d]");
../_images/e4a8261a071f065f2d2da2dbd630ba43c7d5e60665ee6e7e24fcd25c2726a219.png