Developed by R.A. Collenteur & D. Brakenhoff
In this example it is shown how to create a Pastas model that not only includes precipitation and evaporation, but also observed river levels. We will consider observed heads that are strongly influenced by river level, based on a visual interpretation of the raw data.
[1]:
import pandas as pd import pastas as ps import matplotlib.pyplot as plt %matplotlib notebook ps.show_versions() ps.set_log_level("INFO")
Python version: 3.7.8 | packaged by conda-forge | (default, Jul 31 2020, 02:37:09) [Clang 10.0.1 ] Numpy version: 1.18.5 Scipy version: 1.4.0 Pandas version: 1.1.2 Pastas version: 0.16.0b Matplotlib version: 3.1.3
Before a model is created, it is generally a good idea to try and visually interpret the raw data and think about possible relationship between the time series and hydrological variables. Below the different time series are plotted.
The top plot shows the observed heads, with different observation frequencies and some gaps in the data. Below that the observed river levels, precipitation and evaporation are shown. Especially the river level show a clear relationship with the observed heads. Note however how the range in the river levels is about twice the range in the heads. Based on these observations, we would expect the the final step response of the head to the river level to be around 0.5 [m/m].
[2]:
oseries = pd.read_csv("../data/nb5_head.csv", parse_dates=True, squeeze=True, index_col=0) rain = pd.read_csv("../data/nb5_prec.csv", parse_dates=True, squeeze=True, index_col=0) evap = pd.read_csv("../data/nb5_evap.csv", parse_dates=True, squeeze=True, index_col=0) waterlevel = pd.read_csv("../data/nb5_riv.csv", parse_dates=True, squeeze=True, index_col=0) fig, axes = plt.subplots(4,1, figsize=(10, 5), sharex=True) oseries.plot(ax=axes[0], x_compat=True, legend=True, marker=".", linestyle=" ") waterlevel.plot(ax=axes[1], x_compat=True, legend=True) rain.plot(ax=axes[2], x_compat=True, legend=True) evap.plot(ax=axes[3], x_compat=True, legend=True) plt.xlim("2000", "2020");
First we create a model with precipitation and evaporation as explanatory time series. The results show that precipitation and evaporation can explain part of the fluctuations in the observed heads, but not all of them.
[3]:
ml = ps.Model(oseries.resample("D").mean().dropna(), name="River") sm = ps.RechargeModel(rain, evap, rfunc=ps.Exponential, name="recharge") ml.add_stressmodel(sm) ml.solve(tmin="2000", tmax="2019-10-29") ml.plots.results(figsize=(12, 8));
INFO: Cannot determine frequency of series Head: freq=None. The time series is irregular. INFO: Inferred frequency for time series Prec: freq=D INFO: Inferred frequency for time series Evap: freq=D
Fit report River Fit Statistics ================================================== nfev 17 EVP 38.60 nobs 5963 R2 0.39 noise True RMSE 0.46 tmin 2000-01-27 00:00:00 AIC -4.31 tmax 2019-10-29 00:00:00 BIC 29.16 freq D Obj 18.34 warmup 3650 days 00:00:00 ___ solver LeastSquares Interpolated No Parameters (5 were optimized) ================================================== optimal stderr initial vary recharge_A 516.665718 ±39.48% 183.785267 True recharge_a 217.825010 ±38.80% 10.000000 True recharge_f -1.594862 ±19.43% -1.000000 True constant_d 8.536652 ±3.12% 8.547142 True noise_alpha 66.997992 ±14.22% 1.000000 True Parameter correlations |rho| > 0.5 ================================================== recharge_A recharge_a 0.98 recharge_f constant_d -0.97
Based on the analysis of the raw data, we expect that the river levels can help to explain the fluctuations in the observed heads. Here, we add a stress model (ps.StressModel) to add the rivers level as an explanatory time series to the model. The model fit is greatly improved, showing that the rivers help in explaining the observed fluctuations in the observed heads. It can also be observed how the response of the head to the river levels is a lot faster than the response to precipitation and evaporation.
ps.StressModel
[4]:
w = ps.StressModel(waterlevel, rfunc=ps.One, name="waterlevel", settings="waterlevel") ml.add_stressmodel(w) ml.solve(tmin="2000", tmax="2019-10-29") axes = ml.plots.results(figsize=(12, 8)); axes[-1].set_xlim(0,10); # By default, the axes between responses are shared.
INFO: Inferred frequency for time series River: freq=D
Fit report River Fit Statistics =================================================== nfev 20 EVP 93.39 nobs 5963 R2 0.93 noise True RMSE 0.15 tmin 2000-01-27 00:00:00 AIC 2.15 tmax 2019-10-29 00:00:00 BIC 42.31 freq D Obj 4.24 warmup 3650 days 00:00:00 ___ solver LeastSquares Interpolated No Parameters (6 were optimized) =================================================== optimal stderr initial vary recharge_A 206.754929 ±23.04% 183.785267 True recharge_a 167.139827 ±22.57% 10.000000 True recharge_f -1.264738 ±16.38% -1.000000 True waterlevel_d 0.422350 ±0.73% 1.000000 True constant_d 8.476929 ±1.00% 8.547142 True noise_alpha 32.222936 ±10.56% 1.000000 True Parameter correlations |rho| > 0.5 =================================================== recharge_A recharge_a 0.95 constant_d -0.67 recharge_a constant_d -0.51 recharge_f constant_d -0.94