pastastore 1.10.2__py3-none-any.whl → 1.12.0__py3-none-any.whl
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.
- docs/conf.py +10 -97
- pastastore/__init__.py +5 -1
- pastastore/base.py +875 -272
- pastastore/connectors.py +359 -816
- pastastore/datasets.py +23 -33
- pastastore/extensions/__init__.py +7 -3
- pastastore/extensions/hpd.py +39 -17
- pastastore/plotting.py +71 -38
- pastastore/store.py +205 -186
- pastastore/styling.py +4 -2
- pastastore/typing.py +12 -0
- pastastore/util.py +322 -88
- pastastore/validator.py +524 -0
- pastastore/version.py +2 -3
- pastastore/yaml_interface.py +37 -39
- {pastastore-1.10.2.dist-info → pastastore-1.12.0.dist-info}/METADATA +17 -11
- pastastore-1.12.0.dist-info/RECORD +31 -0
- {pastastore-1.10.2.dist-info → pastastore-1.12.0.dist-info}/WHEEL +1 -1
- tests/conftest.py +156 -59
- tests/test_001_import.py +2 -1
- tests/test_002_connectors.py +40 -3
- tests/test_003_pastastore.py +60 -29
- tests/test_005_maps_plots.py +12 -0
- tests/test_006_benchmark.py +1 -1
- tests/test_007_hpdextension.py +46 -8
- tests/test_009_parallel.py +393 -0
- pastastore-1.10.2.dist-info/RECORD +0 -28
- {pastastore-1.10.2.dist-info → pastastore-1.12.0.dist-info}/licenses/LICENSE +0 -0
- {pastastore-1.10.2.dist-info → pastastore-1.12.0.dist-info}/top_level.txt +0 -0
pastastore/datasets.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Module containing example dataset."""
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
from pathlib import Path
|
|
4
4
|
|
|
5
5
|
import pandas as pd
|
|
6
6
|
from hydropandas import Obs, ObsCollection
|
|
@@ -30,8 +30,8 @@ def example_pastastore(conn="DictConnector"):
|
|
|
30
30
|
PastaStore containing example dataset
|
|
31
31
|
"""
|
|
32
32
|
# check it test dataset is available
|
|
33
|
-
datadir =
|
|
34
|
-
if not
|
|
33
|
+
datadir = Path(__file__).parent / "../tests/data"
|
|
34
|
+
if not datadir.exists():
|
|
35
35
|
raise FileNotFoundError(
|
|
36
36
|
"Test datasets not available! Clone repository from GitHub."
|
|
37
37
|
)
|
|
@@ -46,63 +46,57 @@ def example_pastastore(conn="DictConnector"):
|
|
|
46
46
|
# add data
|
|
47
47
|
|
|
48
48
|
# oseries 1
|
|
49
|
-
o = pd.read_csv(
|
|
49
|
+
o = pd.read_csv(datadir / "obs.csv", index_col=0, parse_dates=True)
|
|
50
50
|
pstore.add_oseries(o, "oseries1", metadata={"x": 165000, "y": 424000})
|
|
51
51
|
# oseries 2
|
|
52
|
-
o = pd.read_csv(
|
|
53
|
-
os.path.join(datadir, "head_nb1.csv"), index_col=0, parse_dates=True
|
|
54
|
-
)
|
|
52
|
+
o = pd.read_csv(datadir / "head_nb1.csv", index_col=0, parse_dates=True)
|
|
55
53
|
pstore.add_oseries(o, "oseries2", metadata={"x": 164000, "y": 423000})
|
|
56
54
|
|
|
57
55
|
# oseries 3
|
|
58
|
-
o = pd.read_csv(
|
|
56
|
+
o = pd.read_csv(datadir / "gw_obs.csv", index_col=0, parse_dates=True)
|
|
59
57
|
pstore.add_oseries(o, "oseries3", metadata={"x": 165554, "y": 422685})
|
|
60
58
|
|
|
61
59
|
# prec 1
|
|
62
|
-
s = pd.read_csv(
|
|
60
|
+
s = pd.read_csv(datadir / "rain.csv", index_col=0, parse_dates=True)
|
|
63
61
|
pstore.add_stress(s, "prec1", kind="prec", metadata={"x": 165050, "y": 424050})
|
|
64
62
|
|
|
65
63
|
# prec 2
|
|
66
|
-
s = pd.read_csv(
|
|
67
|
-
os.path.join(datadir, "rain_nb1.csv"), index_col=0, parse_dates=True
|
|
68
|
-
)
|
|
64
|
+
s = pd.read_csv(datadir / "rain_nb1.csv", index_col=0, parse_dates=True)
|
|
69
65
|
pstore.add_stress(s, "prec2", kind="prec", metadata={"x": 164010, "y": 423000})
|
|
70
66
|
|
|
71
67
|
# evap 1
|
|
72
|
-
s = pd.read_csv(
|
|
68
|
+
s = pd.read_csv(datadir / "evap.csv", index_col=0, parse_dates=True)
|
|
73
69
|
pstore.add_stress(s, "evap1", kind="evap", metadata={"x": 164500, "y": 424000})
|
|
74
70
|
|
|
75
71
|
# evap 2
|
|
76
|
-
s = pd.read_csv(
|
|
77
|
-
os.path.join(datadir, "evap_nb1.csv"), index_col=0, parse_dates=True
|
|
78
|
-
)
|
|
72
|
+
s = pd.read_csv(datadir / "evap_nb1.csv", index_col=0, parse_dates=True)
|
|
79
73
|
pstore.add_stress(s, "evap2", kind="evap", metadata={"x": 164000, "y": 423030})
|
|
80
74
|
|
|
81
75
|
# well 1
|
|
82
|
-
s = pd.read_csv(
|
|
76
|
+
s = pd.read_csv(datadir / "well.csv", index_col=0, parse_dates=True)
|
|
83
77
|
s = timestep_weighted_resample(s, pd.date_range(s.index[0], s.index[-1], freq="D"))
|
|
84
78
|
pstore.add_stress(s, "well1", kind="well", metadata={"x": 164691, "y": 423579})
|
|
85
79
|
|
|
86
80
|
# river notebook data (nb5)
|
|
87
81
|
oseries = pd.read_csv(
|
|
88
|
-
|
|
82
|
+
datadir / "nb5_head.csv", parse_dates=True, index_col=0
|
|
89
83
|
).squeeze("columns")
|
|
90
84
|
pstore.add_oseries(oseries, "head_nb5", metadata={"x": 200_000, "y": 450_000.0})
|
|
91
85
|
|
|
92
|
-
rain = pd.read_csv(
|
|
93
|
-
|
|
94
|
-
)
|
|
86
|
+
rain = pd.read_csv(datadir / "nb5_prec.csv", parse_dates=True, index_col=0).squeeze(
|
|
87
|
+
"columns"
|
|
88
|
+
)
|
|
95
89
|
pstore.add_stress(
|
|
96
90
|
rain, "prec_nb5", kind="prec", metadata={"x": 200_000, "y": 450_000.0}
|
|
97
91
|
)
|
|
98
|
-
evap = pd.read_csv(
|
|
99
|
-
|
|
100
|
-
)
|
|
92
|
+
evap = pd.read_csv(datadir / "nb5_evap.csv", parse_dates=True, index_col=0).squeeze(
|
|
93
|
+
"columns"
|
|
94
|
+
)
|
|
101
95
|
pstore.add_stress(
|
|
102
96
|
evap, "evap_nb5", kind="evap", metadata={"x": 200_000, "y": 450_000.0}
|
|
103
97
|
)
|
|
104
98
|
waterlevel = pd.read_csv(
|
|
105
|
-
|
|
99
|
+
datadir / "nb5_riv.csv", parse_dates=True, index_col=0
|
|
106
100
|
).squeeze("columns")
|
|
107
101
|
pstore.add_stress(
|
|
108
102
|
waterlevel,
|
|
@@ -110,11 +104,7 @@ def example_pastastore(conn="DictConnector"):
|
|
|
110
104
|
kind="riv",
|
|
111
105
|
metadata={"x": 200_000, "y": 450_000.0},
|
|
112
106
|
)
|
|
113
|
-
|
|
114
|
-
# read Menyanthes time series names correctly.
|
|
115
|
-
# multiwell notebook data
|
|
116
|
-
fname = os.path.join(datadir, "MenyanthesTest.men")
|
|
117
|
-
# meny = ps.read.MenyData(fname)
|
|
107
|
+
fname = datadir / "MenyanthesTest.men"
|
|
118
108
|
meny = ObsCollection.from_menyanthes(fname, Obs)
|
|
119
109
|
|
|
120
110
|
oseries = meny.loc["Obsevation well", "obs"]
|
|
@@ -184,12 +174,12 @@ def _default_connector(conntype: str):
|
|
|
184
174
|
default Connector based on type.
|
|
185
175
|
"""
|
|
186
176
|
Conn = getattr(pst, conntype)
|
|
187
|
-
if Conn.
|
|
177
|
+
if Conn._conn_type == "arcticdb":
|
|
188
178
|
uri = "lmdb://./arctic_db"
|
|
189
179
|
conn = Conn("my_db", uri)
|
|
190
|
-
elif Conn.
|
|
180
|
+
elif Conn._conn_type == "dict":
|
|
191
181
|
conn = Conn("my_db")
|
|
192
|
-
elif Conn.
|
|
182
|
+
elif Conn._conn_type == "pas":
|
|
193
183
|
conn = Conn("my_db", "./pas_db")
|
|
194
184
|
else:
|
|
195
185
|
raise ValueError(f"Unrecognized connector type! '{conntype}'")
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
# ruff: noqa: D104 F401
|
|
2
|
+
import logging
|
|
3
|
+
|
|
2
4
|
from pastastore.extensions.accessor import (
|
|
3
5
|
register_pastastore_accessor as register_pastastore_accessor,
|
|
4
6
|
)
|
|
5
7
|
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
6
10
|
|
|
7
11
|
def activate_hydropandas_extension():
|
|
8
|
-
"""Register
|
|
12
|
+
"""Register HydroPandas extension for downloading time series data."""
|
|
9
13
|
from pastastore.extensions.hpd import HydroPandasExtension as _
|
|
10
14
|
|
|
11
|
-
|
|
12
|
-
"Registered HydroPandas extension in PastaStore
|
|
15
|
+
logger.info(
|
|
16
|
+
"Registered HydroPandas extension in PastaStore, "
|
|
13
17
|
"e.g. `pstore.hpd.download_bro_gmw()`."
|
|
14
18
|
)
|
pastastore/extensions/hpd.py
CHANGED
|
@@ -18,6 +18,7 @@ from pastas.timeseries_utils import timestep_weighted_resample
|
|
|
18
18
|
from tqdm.auto import tqdm
|
|
19
19
|
|
|
20
20
|
from pastastore.extensions.accessor import register_pastastore_accessor
|
|
21
|
+
from pastastore.typing import TimeSeriesLibs
|
|
21
22
|
|
|
22
23
|
logger = logging.getLogger("hydropandas_extension")
|
|
23
24
|
|
|
@@ -68,7 +69,7 @@ class HydroPandasExtension:
|
|
|
68
69
|
|
|
69
70
|
def add_obscollection(
|
|
70
71
|
self,
|
|
71
|
-
libname:
|
|
72
|
+
libname: TimeSeriesLibs,
|
|
72
73
|
oc: hpd.ObsCollection,
|
|
73
74
|
kind: Optional[str] = None,
|
|
74
75
|
data_column: Optional[str] = None,
|
|
@@ -113,7 +114,7 @@ class HydroPandasExtension:
|
|
|
113
114
|
|
|
114
115
|
def add_observation(
|
|
115
116
|
self,
|
|
116
|
-
libname:
|
|
117
|
+
libname: TimeSeriesLibs,
|
|
117
118
|
obs: hpd.Obs,
|
|
118
119
|
name: Optional[str] = None,
|
|
119
120
|
kind: Optional[str] = None,
|
|
@@ -173,7 +174,7 @@ class HydroPandasExtension:
|
|
|
173
174
|
)
|
|
174
175
|
|
|
175
176
|
# gather metadata from obs object
|
|
176
|
-
metadata = {key: getattr(obs, key) for key in obs._metadata}
|
|
177
|
+
metadata = {key: getattr(obs, key) for key in obs._metadata} # noqa: SLF001
|
|
177
178
|
|
|
178
179
|
# convert np dtypes to builtins
|
|
179
180
|
for k, v in metadata.items():
|
|
@@ -200,7 +201,9 @@ class HydroPandasExtension:
|
|
|
200
201
|
action_msg = "added to"
|
|
201
202
|
|
|
202
203
|
if libname == "oseries":
|
|
203
|
-
self._store.upsert_oseries(
|
|
204
|
+
self._store.upsert_oseries(
|
|
205
|
+
o.squeeze(axis=1), name, metadata=metadata, force=True
|
|
206
|
+
)
|
|
204
207
|
logger.info(
|
|
205
208
|
"%sobservation '%s' %s oseries library.", source, name, action_msg
|
|
206
209
|
)
|
|
@@ -208,7 +211,11 @@ class HydroPandasExtension:
|
|
|
208
211
|
if kind is None:
|
|
209
212
|
raise ValueError("`kind` must be specified for stresses!")
|
|
210
213
|
self._store.upsert_stress(
|
|
211
|
-
(o * unit_multiplier).squeeze(axis=1),
|
|
214
|
+
(o * unit_multiplier).squeeze(axis=1),
|
|
215
|
+
name,
|
|
216
|
+
kind,
|
|
217
|
+
metadata=metadata,
|
|
218
|
+
force=True,
|
|
212
219
|
)
|
|
213
220
|
logger.info(
|
|
214
221
|
"%sstress '%s' (kind='%s') %s stresses library.",
|
|
@@ -242,10 +249,10 @@ class HydroPandasExtension:
|
|
|
242
249
|
tmintmax = self._store.get_tmin_tmax(
|
|
243
250
|
"oseries", names=[oseries] if oseries else None
|
|
244
251
|
)
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
252
|
+
if tmin is None:
|
|
253
|
+
tmin = tmintmax.loc[:, "tmin"].min() - Timedelta(days=10 * 365)
|
|
254
|
+
if tmax is None:
|
|
255
|
+
tmax = tmintmax.loc[:, "tmax"].max()
|
|
249
256
|
return tmin, tmax
|
|
250
257
|
|
|
251
258
|
@staticmethod
|
|
@@ -263,12 +270,13 @@ class HydroPandasExtension:
|
|
|
263
270
|
observation series with normalized datetime index
|
|
264
271
|
"""
|
|
265
272
|
if isinstance(obs, hpd.Obs):
|
|
266
|
-
metadata = {k: getattr(obs, k) for k in obs._metadata}
|
|
273
|
+
metadata = {k: getattr(obs, k) for k in obs._metadata} # noqa: SLF001
|
|
267
274
|
else:
|
|
268
275
|
metadata = {}
|
|
269
276
|
return obs.__class__(
|
|
270
277
|
timestep_weighted_resample(
|
|
271
|
-
|
|
278
|
+
# force series, see https://github.com/pastas/pastas/issues/1020
|
|
279
|
+
obs.squeeze(),
|
|
272
280
|
obs.index.normalize(),
|
|
273
281
|
).rename(obs.name),
|
|
274
282
|
**metadata,
|
|
@@ -585,6 +593,17 @@ class HydroPandasExtension:
|
|
|
585
593
|
):
|
|
586
594
|
"""Update meteorological data from KNMI in PastaStore.
|
|
587
595
|
|
|
596
|
+
Warning
|
|
597
|
+
-------
|
|
598
|
+
When fill_missing_obs is True, time series will be filled with observations
|
|
599
|
+
from nearest stations with data. If certain stations are generally more up to
|
|
600
|
+
date than others, this can lead to time series being permanently filled with
|
|
601
|
+
data from another station. To overwrite these filled values with data from the
|
|
602
|
+
actual station in a subsequent update, ensure that tmin is set to a date prior
|
|
603
|
+
to the first update call, or to be very safe, set tmin to the beginning of the
|
|
604
|
+
time series. This will force re-downloading of all data from the actual
|
|
605
|
+
station.
|
|
606
|
+
|
|
588
607
|
Parameters
|
|
589
608
|
----------
|
|
590
609
|
names : list of str, optional
|
|
@@ -596,7 +615,7 @@ class HydroPandasExtension:
|
|
|
596
615
|
end time, by default None, which defaults to today
|
|
597
616
|
fill_missing_obs : bool, optional
|
|
598
617
|
if True, fill missing observations by getting observations from nearest
|
|
599
|
-
station with data.
|
|
618
|
+
station with data. Default is True.
|
|
600
619
|
normalize_datetime_index : bool, optional
|
|
601
620
|
if True, normalize the datetime so stress value at midnight represents
|
|
602
621
|
the daily total, by default True.
|
|
@@ -625,7 +644,7 @@ class HydroPandasExtension:
|
|
|
625
644
|
|
|
626
645
|
if tmax is not None:
|
|
627
646
|
if tmintmax["tmax"].min() >= Timestamp(tmax):
|
|
628
|
-
logger.info(
|
|
647
|
+
logger.info("All KNMI stresses are up to date till %s.", tmax)
|
|
629
648
|
return
|
|
630
649
|
|
|
631
650
|
try:
|
|
@@ -639,7 +658,7 @@ class HydroPandasExtension:
|
|
|
639
658
|
maxtmax_rd = maxtmax_ev24 = Timestamp.today() - Timedelta(days=28)
|
|
640
659
|
logger.info(
|
|
641
660
|
"Using 28 days (4 weeks) prior to today as maxtmax: %s."
|
|
642
|
-
%
|
|
661
|
+
% maxtmax_rd.strftime("%Y-%m-%d")
|
|
643
662
|
)
|
|
644
663
|
|
|
645
664
|
for name in tqdm(names, desc="Updating KNMI meteo stresses"):
|
|
@@ -671,11 +690,14 @@ class HydroPandasExtension:
|
|
|
671
690
|
# fix for duplicate station entry in metadata:
|
|
672
691
|
stress_station = (
|
|
673
692
|
self._store.stresses.at[name, "station"]
|
|
674
|
-
if
|
|
693
|
+
if (
|
|
694
|
+
"station" in self._store.stresses.columns
|
|
695
|
+
and np.isfinite(self._store.stresses.at[name, "station"])
|
|
696
|
+
)
|
|
675
697
|
else None
|
|
676
698
|
)
|
|
677
699
|
if stress_station is not None and not isinstance(
|
|
678
|
-
stress_station, (int, np.integer)
|
|
700
|
+
stress_station, (int, np.integer, float)
|
|
679
701
|
):
|
|
680
702
|
stress_station = stress_station.squeeze().unique().item()
|
|
681
703
|
|
|
@@ -700,7 +722,7 @@ class HydroPandasExtension:
|
|
|
700
722
|
elif unit == "mm":
|
|
701
723
|
unit_multiplier = 1e3
|
|
702
724
|
elif unit.count("m") == 1 and unit.endswith("m"):
|
|
703
|
-
unit_multiplier = float(unit.replace("m", ""))
|
|
725
|
+
unit_multiplier = float(unit.replace("m", "").replace("*", ""))
|
|
704
726
|
else:
|
|
705
727
|
unit_multiplier = 1.0
|
|
706
728
|
logger.warning(
|
pastastore/plotting.py
CHANGED
|
@@ -15,6 +15,9 @@ follows::
|
|
|
15
15
|
pstore.maps.add_background_map(ax) # for adding a background map
|
|
16
16
|
"""
|
|
17
17
|
|
|
18
|
+
import logging
|
|
19
|
+
import warnings
|
|
20
|
+
|
|
18
21
|
import matplotlib.pyplot as plt
|
|
19
22
|
import numpy as np
|
|
20
23
|
import pandas as pd
|
|
@@ -25,6 +28,8 @@ from matplotlib.colors import BoundaryNorm, LogNorm
|
|
|
25
28
|
from matplotlib.lines import Line2D
|
|
26
29
|
from mpl_toolkits.axes_grid1 import make_axes_locatable
|
|
27
30
|
|
|
31
|
+
logger = logging.getLogger(__name__)
|
|
32
|
+
|
|
28
33
|
|
|
29
34
|
class Plots:
|
|
30
35
|
"""Plot class for Pastastore.
|
|
@@ -101,7 +106,7 @@ class Plots:
|
|
|
101
106
|
split=True is only supported if there are less than 20 time series
|
|
102
107
|
to plot.
|
|
103
108
|
"""
|
|
104
|
-
names = self.pstore.conn.
|
|
109
|
+
names = self.pstore.conn.parse_names(names, libname)
|
|
105
110
|
|
|
106
111
|
if len(names) > 20 and split:
|
|
107
112
|
raise ValueError(
|
|
@@ -116,7 +121,7 @@ class Plots:
|
|
|
116
121
|
else:
|
|
117
122
|
axes = ax
|
|
118
123
|
|
|
119
|
-
tsdict = self.pstore.conn._get_series(
|
|
124
|
+
tsdict = self.pstore.conn._get_series( # noqa: SLF001
|
|
120
125
|
libname, names, progressbar=progressbar, squeeze=False
|
|
121
126
|
)
|
|
122
127
|
for i, (n, ts) in enumerate(tsdict.items()):
|
|
@@ -236,7 +241,7 @@ class Plots:
|
|
|
236
241
|
ax : matplotlib.Axes
|
|
237
242
|
axes handle
|
|
238
243
|
"""
|
|
239
|
-
names = self.pstore.conn.
|
|
244
|
+
names = self.pstore.conn.parse_names(names, "stresses")
|
|
240
245
|
masknames = self.pstore.stresses.index.isin(names)
|
|
241
246
|
stresses = self.pstore.stresses.loc[masknames]
|
|
242
247
|
|
|
@@ -315,7 +320,7 @@ class Plots:
|
|
|
315
320
|
ax : matplotlib Axes
|
|
316
321
|
The axes in which the data-availability is plotted
|
|
317
322
|
"""
|
|
318
|
-
names = self.pstore.conn.
|
|
323
|
+
names = self.pstore.conn.parse_names(names, libname)
|
|
319
324
|
|
|
320
325
|
if libname == "stresses":
|
|
321
326
|
masknames = self.pstore.stresses.index.isin(names)
|
|
@@ -324,7 +329,7 @@ class Plots:
|
|
|
324
329
|
mask = stresses["kind"] == kind
|
|
325
330
|
names = stresses.loc[mask].index.to_list()
|
|
326
331
|
|
|
327
|
-
series = self.pstore.conn._get_series(
|
|
332
|
+
series = self.pstore.conn._get_series( # noqa: SLF001
|
|
328
333
|
libname, names, progressbar=progressbar, squeeze=False
|
|
329
334
|
).values()
|
|
330
335
|
|
|
@@ -434,6 +439,7 @@ class Plots:
|
|
|
434
439
|
cmap = plt.get_cmap(cmap, 256)
|
|
435
440
|
cmap.set_over((1.0, 1.0, 1.0))
|
|
436
441
|
|
|
442
|
+
pc = None
|
|
437
443
|
for i, s in enumerate(series):
|
|
438
444
|
if not s.empty:
|
|
439
445
|
if dropna:
|
|
@@ -450,10 +456,14 @@ class Plots:
|
|
|
450
456
|
|
|
451
457
|
# make a colorbar in an ax on the
|
|
452
458
|
# right side, then set the current axes to ax again
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
459
|
+
if pc is not None:
|
|
460
|
+
cb = fig.colorbar(pc, ax=ax, cax=cax, extend="both")
|
|
461
|
+
cb.set_ticks(bounds)
|
|
462
|
+
cb.ax.set_yticklabels(labels)
|
|
463
|
+
cb.ax.minorticks_off()
|
|
464
|
+
else:
|
|
465
|
+
# nothing was plotted; skip colorbar to avoid UnboundLocalError
|
|
466
|
+
cb = None
|
|
457
467
|
|
|
458
468
|
if set_yticks:
|
|
459
469
|
ax.set_yticks(np.arange(0.5, len(series) + 0.5), minor=False)
|
|
@@ -600,6 +610,8 @@ class Plots:
|
|
|
600
610
|
elif len(np.unique(onames)) > 1:
|
|
601
611
|
names = modelnames
|
|
602
612
|
cm = ps.CompareModels(models, names=names)
|
|
613
|
+
if ax is not None:
|
|
614
|
+
kwargs.setdefault("ax", ax)
|
|
603
615
|
cm.plot(**kwargs)
|
|
604
616
|
return cm
|
|
605
617
|
|
|
@@ -684,7 +696,7 @@ class Maps:
|
|
|
684
696
|
--------
|
|
685
697
|
self.add_background_map
|
|
686
698
|
"""
|
|
687
|
-
names = self.pstore.conn.
|
|
699
|
+
names = self.pstore.conn.parse_names(names, "stresses")
|
|
688
700
|
if extent is not None:
|
|
689
701
|
names = self.pstore.within(extent, names=names, libname="stresses")
|
|
690
702
|
df = self.pstore.stresses.loc[names]
|
|
@@ -707,7 +719,7 @@ class Maps:
|
|
|
707
719
|
kind_to_color = {k: f"C{i}" for i, k in enumerate(c.unique())}
|
|
708
720
|
c = c.apply(lambda k: kind_to_color[k])
|
|
709
721
|
|
|
710
|
-
r = self.
|
|
722
|
+
r = self.dataframe_scatter(stresses.loc[mask0], c=c, figsize=figsize, **kwargs)
|
|
711
723
|
if "ax" in kwargs:
|
|
712
724
|
ax = kwargs.pop("ax")
|
|
713
725
|
else:
|
|
@@ -768,12 +780,12 @@ class Maps:
|
|
|
768
780
|
--------
|
|
769
781
|
self.add_background_map
|
|
770
782
|
"""
|
|
771
|
-
names = self.pstore.conn.
|
|
783
|
+
names = self.pstore.conn.parse_names(names, "oseries")
|
|
772
784
|
if extent is not None:
|
|
773
785
|
names = self.pstore.within(extent, names=names)
|
|
774
786
|
oseries = self.pstore.oseries.loc[names]
|
|
775
787
|
mask0 = (oseries["x"] != 0.0) | (oseries["y"] != 0.0)
|
|
776
|
-
r = self.
|
|
788
|
+
r = self.dataframe_scatter(oseries.loc[mask0], figsize=figsize, **kwargs)
|
|
777
789
|
if "ax" in kwargs:
|
|
778
790
|
ax = kwargs["ax"]
|
|
779
791
|
else:
|
|
@@ -829,7 +841,7 @@ class Maps:
|
|
|
829
841
|
|
|
830
842
|
# mask out 0.0 coordinates
|
|
831
843
|
mask0 = (models["x"] != 0.0) | (models["y"] != 0.0)
|
|
832
|
-
r = self.
|
|
844
|
+
r = self.dataframe_scatter(models.loc[mask0], figsize=figsize, **kwargs)
|
|
833
845
|
if "ax" in kwargs:
|
|
834
846
|
ax = kwargs["ax"]
|
|
835
847
|
else:
|
|
@@ -842,7 +854,7 @@ class Maps:
|
|
|
842
854
|
|
|
843
855
|
return ax
|
|
844
856
|
|
|
845
|
-
def
|
|
857
|
+
def dataframe(
|
|
846
858
|
self,
|
|
847
859
|
df,
|
|
848
860
|
column,
|
|
@@ -856,7 +868,7 @@ class Maps:
|
|
|
856
868
|
backgroundmap=False,
|
|
857
869
|
**kwargs,
|
|
858
870
|
):
|
|
859
|
-
"""
|
|
871
|
+
"""Plot dataframe on a map.
|
|
860
872
|
|
|
861
873
|
Parameters
|
|
862
874
|
----------
|
|
@@ -905,11 +917,12 @@ class Maps:
|
|
|
905
917
|
}
|
|
906
918
|
scatter_kwargs.update(kwargs)
|
|
907
919
|
|
|
908
|
-
ax = self.
|
|
920
|
+
ax = self.dataframe_scatter(
|
|
909
921
|
df, column=column, figsize=figsize, **scatter_kwargs
|
|
910
922
|
)
|
|
911
923
|
if label:
|
|
912
|
-
|
|
924
|
+
if "index" in df:
|
|
925
|
+
df.set_index("index", inplace=True)
|
|
913
926
|
self.add_labels(df, ax, adjust=adjust)
|
|
914
927
|
|
|
915
928
|
if backgroundmap:
|
|
@@ -982,7 +995,7 @@ class Maps:
|
|
|
982
995
|
statsdf = statsdf.reset_index().set_index("oseries")
|
|
983
996
|
df = statsdf.join(self.pstore.oseries, how="left")
|
|
984
997
|
|
|
985
|
-
return self.
|
|
998
|
+
return self.dataframe(
|
|
986
999
|
df,
|
|
987
1000
|
column=statistic,
|
|
988
1001
|
label=label,
|
|
@@ -999,6 +1012,7 @@ class Maps:
|
|
|
999
1012
|
def modelparam(
|
|
1000
1013
|
self,
|
|
1001
1014
|
parameter,
|
|
1015
|
+
param_value="optimal",
|
|
1002
1016
|
modelnames=None,
|
|
1003
1017
|
label=True,
|
|
1004
1018
|
adjust=False,
|
|
@@ -1017,6 +1031,9 @@ class Maps:
|
|
|
1017
1031
|
----------
|
|
1018
1032
|
parameter: str
|
|
1019
1033
|
name of the parameter, e.g. "rech_A" or "river_a"
|
|
1034
|
+
param_value: str, optional
|
|
1035
|
+
which parameter value to plot, by default "optimal", other options
|
|
1036
|
+
are "initial", "pmin", "pmax"
|
|
1020
1037
|
modelnames : list of str, optional
|
|
1021
1038
|
list of modelnames to include
|
|
1022
1039
|
label: bool, optional
|
|
@@ -1052,6 +1069,7 @@ class Maps:
|
|
|
1052
1069
|
"""
|
|
1053
1070
|
paramdf = self.pstore.get_parameters(
|
|
1054
1071
|
[parameter],
|
|
1072
|
+
param_value=param_value,
|
|
1055
1073
|
modelnames=modelnames,
|
|
1056
1074
|
progressbar=progressbar,
|
|
1057
1075
|
ignore_errors=True,
|
|
@@ -1064,7 +1082,7 @@ class Maps:
|
|
|
1064
1082
|
paramdf = paramdf.reset_index().set_index("oseries")
|
|
1065
1083
|
df = paramdf.join(self.pstore.oseries, how="left")
|
|
1066
1084
|
|
|
1067
|
-
return self.
|
|
1085
|
+
return self.dataframe(
|
|
1068
1086
|
df,
|
|
1069
1087
|
column=parameter,
|
|
1070
1088
|
label=label,
|
|
@@ -1133,14 +1151,14 @@ class Maps:
|
|
|
1133
1151
|
self.add_background_map
|
|
1134
1152
|
"""
|
|
1135
1153
|
signature_df = self.pstore.get_signatures(
|
|
1136
|
-
[signature],
|
|
1137
1154
|
names=names,
|
|
1155
|
+
signatures=[signature],
|
|
1138
1156
|
progressbar=progressbar,
|
|
1139
1157
|
ignore_errors=True,
|
|
1140
|
-
)
|
|
1158
|
+
).transpose()
|
|
1141
1159
|
df = signature_df.join(self.pstore.oseries, how="left")
|
|
1142
1160
|
|
|
1143
|
-
return self.
|
|
1161
|
+
return self.dataframe(
|
|
1144
1162
|
df,
|
|
1145
1163
|
column=signature,
|
|
1146
1164
|
label=label,
|
|
@@ -1154,8 +1172,17 @@ class Maps:
|
|
|
1154
1172
|
**kwargs,
|
|
1155
1173
|
)
|
|
1156
1174
|
|
|
1175
|
+
def _plotmap_dataframe(self, *args, **kwargs):
|
|
1176
|
+
"""Deprecated, use dataframe method.""" # noqa: D401
|
|
1177
|
+
warnings.warn(
|
|
1178
|
+
"maps._plotmap_dataframe is deprecated, use maps.dataframe_scatter instead",
|
|
1179
|
+
DeprecationWarning,
|
|
1180
|
+
stacklevel=2,
|
|
1181
|
+
)
|
|
1182
|
+
return self.dataframe_scatter(*args, **kwargs)
|
|
1183
|
+
|
|
1157
1184
|
@staticmethod
|
|
1158
|
-
def
|
|
1185
|
+
def dataframe_scatter(
|
|
1159
1186
|
df,
|
|
1160
1187
|
x="x",
|
|
1161
1188
|
y="y",
|
|
@@ -1165,9 +1192,7 @@ class Maps:
|
|
|
1165
1192
|
figsize=(10, 8),
|
|
1166
1193
|
**kwargs,
|
|
1167
1194
|
):
|
|
1168
|
-
"""Plot dataframe
|
|
1169
|
-
|
|
1170
|
-
Can be called directly for more control over plot characteristics.
|
|
1195
|
+
"""Plot dataframe.
|
|
1171
1196
|
|
|
1172
1197
|
Parameters
|
|
1173
1198
|
----------
|
|
@@ -1310,10 +1335,12 @@ class Maps:
|
|
|
1310
1335
|
"metadata_source must be either 'model' or 'store'!"
|
|
1311
1336
|
)
|
|
1312
1337
|
if np.isnan(xi) or np.isnan(yi):
|
|
1313
|
-
|
|
1338
|
+
logger.warning("No x,y-data for %s!", istress.name)
|
|
1314
1339
|
continue
|
|
1315
1340
|
if xi == 0.0 or yi == 0.0:
|
|
1316
|
-
|
|
1341
|
+
logger.warning(
|
|
1342
|
+
"x,y-data is 0.0 for %s, not plotting!", istress.name
|
|
1343
|
+
)
|
|
1317
1344
|
continue
|
|
1318
1345
|
|
|
1319
1346
|
stresses.loc[istress.name, :] = (xi, yi, name, f"C{count % 10}")
|
|
@@ -1476,7 +1503,7 @@ class Maps:
|
|
|
1476
1503
|
m_idx = self.pstore.search(libname="models", s=model_names)
|
|
1477
1504
|
else:
|
|
1478
1505
|
m_idx = self.pstore.model_names
|
|
1479
|
-
struct = self.pstore.
|
|
1506
|
+
struct = self.pstore.get_model_time_series_names(progressbar=False).loc[m_idx]
|
|
1480
1507
|
|
|
1481
1508
|
oseries = self.pstore.oseries
|
|
1482
1509
|
stresses = self.pstore.stresses
|
|
@@ -1614,23 +1641,27 @@ class Maps:
|
|
|
1614
1641
|
ctx.add_basemap(ax, source=providers[map_provider], crs=proj.srs, **kwargs)
|
|
1615
1642
|
|
|
1616
1643
|
@staticmethod
|
|
1617
|
-
def add_labels(
|
|
1644
|
+
def add_labels(
|
|
1645
|
+
df, ax, adjust=False, objects=None, adjust_text_kwargs=None, **kwargs
|
|
1646
|
+
):
|
|
1618
1647
|
"""Add labels to points on plot.
|
|
1619
1648
|
|
|
1620
1649
|
Uses dataframe index to label points.
|
|
1621
1650
|
|
|
1622
1651
|
Parameters
|
|
1623
1652
|
----------
|
|
1624
|
-
df: pd.DataFrame
|
|
1653
|
+
df : pd.DataFrame
|
|
1625
1654
|
DataFrame containing x, y - data. Index is used as label
|
|
1626
|
-
ax: matplotlib.Axes
|
|
1655
|
+
ax : matplotlib.Axes
|
|
1627
1656
|
axes object to label points on
|
|
1628
|
-
adjust: bool
|
|
1657
|
+
adjust : bool
|
|
1629
1658
|
automated smart label placement using adjustText
|
|
1630
1659
|
objects : list of matplotlib objects
|
|
1631
1660
|
use to avoid labels overlapping markers
|
|
1632
|
-
|
|
1633
|
-
keyword arguments to
|
|
1661
|
+
adjust_text_kwargs
|
|
1662
|
+
keyword arguments to adjust_text function, only used if adjust=True
|
|
1663
|
+
**kwargs
|
|
1664
|
+
keyword arguments to ax.annotate or ax.text
|
|
1634
1665
|
"""
|
|
1635
1666
|
stroke = [patheffects.withStroke(linewidth=3, foreground="w")]
|
|
1636
1667
|
fontsize = kwargs.pop("fontsize", 10)
|
|
@@ -1647,14 +1678,15 @@ class Maps:
|
|
|
1647
1678
|
name,
|
|
1648
1679
|
fontsize=fontsize,
|
|
1649
1680
|
**{"path_effects": stroke},
|
|
1681
|
+
**kwargs,
|
|
1650
1682
|
)
|
|
1651
1683
|
)
|
|
1652
|
-
|
|
1684
|
+
if adjust_text_kwargs is None:
|
|
1685
|
+
adjust_text_kwargs = {}
|
|
1653
1686
|
adjust_text(
|
|
1654
1687
|
texts,
|
|
1655
1688
|
objects=objects,
|
|
1656
1689
|
force_text=(0.05, 0.10),
|
|
1657
|
-
**kwargs,
|
|
1658
1690
|
**{
|
|
1659
1691
|
"arrowprops": {
|
|
1660
1692
|
"arrowstyle": "-",
|
|
@@ -1662,6 +1694,7 @@ class Maps:
|
|
|
1662
1694
|
"alpha": 0.5,
|
|
1663
1695
|
}
|
|
1664
1696
|
},
|
|
1697
|
+
**adjust_text_kwargs,
|
|
1665
1698
|
)
|
|
1666
1699
|
|
|
1667
1700
|
else:
|