pastastore 1.3.0__py3-none-any.whl → 1.5.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.
- pastastore/__init__.py +3 -5
- pastastore/base.py +204 -58
- pastastore/connectors.py +51 -30
- pastastore/datasets.py +3 -2
- pastastore/plotting.py +136 -30
- pastastore/store.py +201 -35
- pastastore/styling.py +67 -0
- pastastore/util.py +48 -16
- pastastore/version.py +32 -1
- pastastore/yaml_interface.py +33 -25
- {pastastore-1.3.0.dist-info → pastastore-1.5.0.dist-info}/METADATA +12 -10
- pastastore-1.5.0.dist-info/RECORD +15 -0
- {pastastore-1.3.0.dist-info → pastastore-1.5.0.dist-info}/WHEEL +1 -1
- pastastore-1.3.0.dist-info/RECORD +0 -14
- {pastastore-1.3.0.dist-info → pastastore-1.5.0.dist-info}/LICENSE +0 -0
- {pastastore-1.3.0.dist-info → pastastore-1.5.0.dist-info}/top_level.txt +0 -0
pastastore/store.py
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
"""Module containing the PastaStore object for managing time series and models."""
|
|
2
|
+
|
|
1
3
|
import json
|
|
2
4
|
import os
|
|
3
5
|
import warnings
|
|
4
|
-
from typing import List, Optional, Tuple, Union
|
|
6
|
+
from typing import List, Literal, Optional, Tuple, Union
|
|
5
7
|
|
|
6
8
|
import numpy as np
|
|
7
9
|
import pandas as pd
|
|
@@ -14,6 +16,7 @@ from pastastore.base import BaseConnector
|
|
|
14
16
|
from pastastore.connectors import DictConnector
|
|
15
17
|
from pastastore.plotting import Maps, Plots
|
|
16
18
|
from pastastore.util import _custom_warning
|
|
19
|
+
from pastastore.version import PASTAS_GEQ_150
|
|
17
20
|
from pastastore.yaml_interface import PastastoreYAML
|
|
18
21
|
|
|
19
22
|
FrameorSeriesUnion = Union[pd.DataFrame, pd.Series]
|
|
@@ -72,7 +75,7 @@ class PastaStore:
|
|
|
72
75
|
self.yaml = PastastoreYAML(self)
|
|
73
76
|
|
|
74
77
|
def _register_connector_methods(self):
|
|
75
|
-
"""
|
|
78
|
+
"""Register connector methods (internal method)."""
|
|
76
79
|
methods = [
|
|
77
80
|
func
|
|
78
81
|
for func in dir(self.conn)
|
|
@@ -83,26 +86,70 @@ class PastaStore:
|
|
|
83
86
|
|
|
84
87
|
@property
|
|
85
88
|
def oseries(self):
|
|
89
|
+
"""
|
|
90
|
+
Returns the oseries metadata as dataframe.
|
|
91
|
+
|
|
92
|
+
Returns
|
|
93
|
+
-------
|
|
94
|
+
oseries
|
|
95
|
+
oseries metadata as dataframe
|
|
96
|
+
"""
|
|
86
97
|
return self.conn.oseries
|
|
87
98
|
|
|
88
99
|
@property
|
|
89
100
|
def stresses(self):
|
|
101
|
+
"""
|
|
102
|
+
Returns the stresses metadata as dataframe.
|
|
103
|
+
|
|
104
|
+
Returns
|
|
105
|
+
-------
|
|
106
|
+
stresses
|
|
107
|
+
stresses metadata as dataframe
|
|
108
|
+
"""
|
|
90
109
|
return self.conn.stresses
|
|
91
110
|
|
|
92
111
|
@property
|
|
93
112
|
def models(self):
|
|
113
|
+
"""Return list of model names.
|
|
114
|
+
|
|
115
|
+
Returns
|
|
116
|
+
-------
|
|
117
|
+
list
|
|
118
|
+
list of model names
|
|
119
|
+
"""
|
|
94
120
|
return self.conn.models
|
|
95
121
|
|
|
96
122
|
@property
|
|
97
123
|
def oseries_names(self):
|
|
124
|
+
"""Return list of oseries names.
|
|
125
|
+
|
|
126
|
+
Returns
|
|
127
|
+
-------
|
|
128
|
+
list
|
|
129
|
+
list of oseries names
|
|
130
|
+
"""
|
|
98
131
|
return self.conn.oseries_names
|
|
99
132
|
|
|
100
133
|
@property
|
|
101
134
|
def stresses_names(self):
|
|
135
|
+
"""Return list of streses names.
|
|
136
|
+
|
|
137
|
+
Returns
|
|
138
|
+
-------
|
|
139
|
+
list
|
|
140
|
+
list of stresses names
|
|
141
|
+
"""
|
|
102
142
|
return self.conn.stresses_names
|
|
103
143
|
|
|
104
144
|
@property
|
|
105
145
|
def model_names(self):
|
|
146
|
+
"""Return list of model names.
|
|
147
|
+
|
|
148
|
+
Returns
|
|
149
|
+
-------
|
|
150
|
+
list
|
|
151
|
+
list of model names
|
|
152
|
+
"""
|
|
106
153
|
return self.conn.model_names
|
|
107
154
|
|
|
108
155
|
@property
|
|
@@ -111,22 +158,57 @@ class PastaStore:
|
|
|
111
158
|
|
|
112
159
|
@property
|
|
113
160
|
def n_oseries(self):
|
|
161
|
+
"""Return number of oseries.
|
|
162
|
+
|
|
163
|
+
Returns
|
|
164
|
+
-------
|
|
165
|
+
int
|
|
166
|
+
number of oseries
|
|
167
|
+
"""
|
|
114
168
|
return self.conn.n_oseries
|
|
115
169
|
|
|
116
170
|
@property
|
|
117
171
|
def n_stresses(self):
|
|
172
|
+
"""Return number of stresses.
|
|
173
|
+
|
|
174
|
+
Returns
|
|
175
|
+
-------
|
|
176
|
+
int
|
|
177
|
+
number of stresses
|
|
178
|
+
"""
|
|
118
179
|
return self.conn.n_stresses
|
|
119
180
|
|
|
120
181
|
@property
|
|
121
182
|
def n_models(self):
|
|
183
|
+
"""Return number of models.
|
|
184
|
+
|
|
185
|
+
Returns
|
|
186
|
+
-------
|
|
187
|
+
int
|
|
188
|
+
number of models
|
|
189
|
+
"""
|
|
122
190
|
return self.conn.n_models
|
|
123
191
|
|
|
124
192
|
@property
|
|
125
193
|
def oseries_models(self):
|
|
194
|
+
"""Return dictionary of models per oseries.
|
|
195
|
+
|
|
196
|
+
Returns
|
|
197
|
+
-------
|
|
198
|
+
dict
|
|
199
|
+
dictionary containing list of models (values) for each oseries (keys).
|
|
200
|
+
"""
|
|
126
201
|
return self.conn.oseries_models
|
|
127
202
|
|
|
128
203
|
@property
|
|
129
204
|
def oseries_with_models(self):
|
|
205
|
+
"""Return list of oseries for which models are contained in the database.
|
|
206
|
+
|
|
207
|
+
Returns
|
|
208
|
+
-------
|
|
209
|
+
list
|
|
210
|
+
list of oseries names for which models are contained in the database.
|
|
211
|
+
"""
|
|
130
212
|
return self.conn.oseries_with_models
|
|
131
213
|
|
|
132
214
|
def __repr__(self):
|
|
@@ -136,7 +218,7 @@ class PastaStore:
|
|
|
136
218
|
def get_oseries_distances(
|
|
137
219
|
self, names: Optional[Union[list, str]] = None
|
|
138
220
|
) -> FrameorSeriesUnion:
|
|
139
|
-
"""
|
|
221
|
+
"""Get the distances in meters between the oseries.
|
|
140
222
|
|
|
141
223
|
Parameters
|
|
142
224
|
----------
|
|
@@ -175,7 +257,7 @@ class PastaStore:
|
|
|
175
257
|
n: int = 1,
|
|
176
258
|
maxdist: Optional[float] = None,
|
|
177
259
|
) -> FrameorSeriesUnion:
|
|
178
|
-
"""
|
|
260
|
+
"""Get the nearest (n) oseries.
|
|
179
261
|
|
|
180
262
|
Parameters
|
|
181
263
|
----------
|
|
@@ -191,7 +273,6 @@ class PastaStore:
|
|
|
191
273
|
oseries:
|
|
192
274
|
list with the names of the oseries.
|
|
193
275
|
"""
|
|
194
|
-
|
|
195
276
|
distances = self.get_oseries_distances(names)
|
|
196
277
|
if maxdist is not None:
|
|
197
278
|
distances = distances.where(distances <= maxdist, np.nan)
|
|
@@ -214,8 +295,7 @@ class PastaStore:
|
|
|
214
295
|
stresses: Optional[Union[list, str]] = None,
|
|
215
296
|
kind: Optional[Union[str, List[str]]] = None,
|
|
216
297
|
) -> FrameorSeriesUnion:
|
|
217
|
-
"""
|
|
218
|
-
stresses.
|
|
298
|
+
"""Get the distances in meters between the oseries and stresses.
|
|
219
299
|
|
|
220
300
|
Parameters
|
|
221
301
|
----------
|
|
@@ -274,7 +354,7 @@ class PastaStore:
|
|
|
274
354
|
n: int = 1,
|
|
275
355
|
maxdist: Optional[float] = None,
|
|
276
356
|
) -> FrameorSeriesUnion:
|
|
277
|
-
"""
|
|
357
|
+
"""Get the nearest (n) stresses of a specific kind.
|
|
278
358
|
|
|
279
359
|
Parameters
|
|
280
360
|
----------
|
|
@@ -295,7 +375,6 @@ class PastaStore:
|
|
|
295
375
|
stresses:
|
|
296
376
|
list with the names of the stresses.
|
|
297
377
|
"""
|
|
298
|
-
|
|
299
378
|
distances = self.get_distances(oseries, stresses, kind)
|
|
300
379
|
if maxdist is not None:
|
|
301
380
|
distances = distances.where(distances <= maxdist, np.nan)
|
|
@@ -317,8 +396,9 @@ class PastaStore:
|
|
|
317
396
|
progressbar=False,
|
|
318
397
|
ignore_errors=False,
|
|
319
398
|
):
|
|
320
|
-
"""Get groundwater signatures.
|
|
321
|
-
|
|
399
|
+
"""Get groundwater signatures.
|
|
400
|
+
|
|
401
|
+
NaN-values are returned when the signature cannot be computed.
|
|
322
402
|
|
|
323
403
|
Parameters
|
|
324
404
|
----------
|
|
@@ -380,11 +460,16 @@ class PastaStore:
|
|
|
380
460
|
i_signatures.append(sign_val)
|
|
381
461
|
else:
|
|
382
462
|
raise e
|
|
383
|
-
signatures_df.loc[name, signatures] = i_signatures
|
|
463
|
+
signatures_df.loc[name, signatures] = i_signatures.squeeze()
|
|
384
464
|
|
|
385
465
|
return signatures_df
|
|
386
466
|
|
|
387
|
-
def get_tmin_tmax(
|
|
467
|
+
def get_tmin_tmax(
|
|
468
|
+
self,
|
|
469
|
+
libname: Literal["oseries", "stresses", "models"],
|
|
470
|
+
names: Union[str, List[str], None] = None,
|
|
471
|
+
progressbar: bool = False,
|
|
472
|
+
):
|
|
388
473
|
"""Get tmin and tmax for time series.
|
|
389
474
|
|
|
390
475
|
Parameters
|
|
@@ -403,22 +488,48 @@ class PastaStore:
|
|
|
403
488
|
tmintmax : pd.dataframe
|
|
404
489
|
Dataframe containing tmin and tmax per time series
|
|
405
490
|
"""
|
|
406
|
-
|
|
407
491
|
names = self.conn._parse_names(names, libname=libname)
|
|
408
492
|
tmintmax = pd.DataFrame(
|
|
409
493
|
index=names, columns=["tmin", "tmax"], dtype="datetime64[ns]"
|
|
410
494
|
)
|
|
411
495
|
desc = f"Get tmin/tmax {libname}"
|
|
412
496
|
for n in tqdm(names, desc=desc) if progressbar else names:
|
|
413
|
-
if libname == "
|
|
414
|
-
|
|
497
|
+
if libname == "models":
|
|
498
|
+
mld = self.conn.get_models(
|
|
499
|
+
n,
|
|
500
|
+
return_dict=True,
|
|
501
|
+
)
|
|
502
|
+
tmintmax.loc[n, "tmin"] = mld["settings"]["tmin"]
|
|
503
|
+
tmintmax.loc[n, "tmax"] = mld["settings"]["tmax"]
|
|
415
504
|
else:
|
|
416
|
-
s =
|
|
417
|
-
|
|
418
|
-
|
|
505
|
+
s = (
|
|
506
|
+
self.conn.get_oseries(n)
|
|
507
|
+
if libname == "oseries"
|
|
508
|
+
else self.conn.get_stresses(n)
|
|
509
|
+
)
|
|
510
|
+
tmintmax.loc[n, "tmin"] = s.first_valid_index()
|
|
511
|
+
tmintmax.loc[n, "tmax"] = s.last_valid_index()
|
|
512
|
+
|
|
419
513
|
return tmintmax
|
|
420
514
|
|
|
421
515
|
def get_extent(self, libname, names=None, buffer=0.0):
|
|
516
|
+
"""Get extent [xmin, xmax, ymin, ymax] from library.
|
|
517
|
+
|
|
518
|
+
Parameters
|
|
519
|
+
----------
|
|
520
|
+
libname : str
|
|
521
|
+
name of the library containing the time series
|
|
522
|
+
('oseries', 'stresses', 'models')
|
|
523
|
+
names : str, list of str, or None, optional
|
|
524
|
+
list of names to include for computing the extent
|
|
525
|
+
buffer : float, optional
|
|
526
|
+
add this distance to the extent, by default 0.0
|
|
527
|
+
|
|
528
|
+
Returns
|
|
529
|
+
-------
|
|
530
|
+
extent : list
|
|
531
|
+
extent [xmin, xmax, ymin, ymax]
|
|
532
|
+
"""
|
|
422
533
|
names = self.conn._parse_names(names, libname=libname)
|
|
423
534
|
if libname in ["oseries", "stresses"]:
|
|
424
535
|
df = getattr(self, libname)
|
|
@@ -443,8 +554,10 @@ class PastaStore:
|
|
|
443
554
|
progressbar: Optional[bool] = False,
|
|
444
555
|
ignore_errors: Optional[bool] = False,
|
|
445
556
|
) -> FrameorSeriesUnion:
|
|
446
|
-
"""Get model parameters.
|
|
447
|
-
|
|
557
|
+
"""Get model parameters.
|
|
558
|
+
|
|
559
|
+
NaN-values are returned when the parameters are not present in the model or the
|
|
560
|
+
model is not optimized.
|
|
448
561
|
|
|
449
562
|
Parameters
|
|
450
563
|
----------
|
|
@@ -526,7 +639,6 @@ class PastaStore:
|
|
|
526
639
|
-------
|
|
527
640
|
s : pandas.DataFrame
|
|
528
641
|
"""
|
|
529
|
-
|
|
530
642
|
modelnames = self.conn._parse_names(modelnames, libname="models")
|
|
531
643
|
|
|
532
644
|
# if statistics is str
|
|
@@ -558,6 +670,7 @@ class PastaStore:
|
|
|
558
670
|
name: str,
|
|
559
671
|
modelname: str = None,
|
|
560
672
|
add_recharge: bool = True,
|
|
673
|
+
add_ar_noisemodel: bool = False,
|
|
561
674
|
recharge_name: str = "recharge",
|
|
562
675
|
) -> ps.Model:
|
|
563
676
|
"""Create a pastas Model.
|
|
@@ -572,6 +685,8 @@ class PastaStore:
|
|
|
572
685
|
add recharge to the model by looking for the closest
|
|
573
686
|
precipitation and evaporation time series in the stresses
|
|
574
687
|
library, by default True
|
|
688
|
+
add_ar1_noisemodel : bool, optional
|
|
689
|
+
add AR(1) noise model to the model, by default False
|
|
575
690
|
recharge_name : str
|
|
576
691
|
name of the RechargeModel
|
|
577
692
|
|
|
@@ -598,6 +713,8 @@ class PastaStore:
|
|
|
598
713
|
ml = ps.Model(ts, name=modelname, metadata=meta)
|
|
599
714
|
if add_recharge:
|
|
600
715
|
self.add_recharge(ml, recharge_name=recharge_name)
|
|
716
|
+
if add_ar_noisemodel and PASTAS_GEQ_150:
|
|
717
|
+
ml.add_noisemodel(ps.ArNoiseModel())
|
|
601
718
|
return ml
|
|
602
719
|
else:
|
|
603
720
|
raise ValueError("Empty time series!")
|
|
@@ -709,9 +826,9 @@ class PastaStore:
|
|
|
709
826
|
for var in ("prec", "evap"):
|
|
710
827
|
try:
|
|
711
828
|
name = self.get_nearest_stresses(ml.oseries.name, kind=var).iloc[0, 0]
|
|
712
|
-
except AttributeError:
|
|
829
|
+
except AttributeError as e:
|
|
713
830
|
msg = "No precipitation or evaporation time series found!"
|
|
714
|
-
raise Exception(msg)
|
|
831
|
+
raise Exception(msg) from e
|
|
715
832
|
if isinstance(name, float):
|
|
716
833
|
if np.isnan(name):
|
|
717
834
|
raise ValueError(
|
|
@@ -828,8 +945,8 @@ class PastaStore:
|
|
|
828
945
|
"""
|
|
829
946
|
try:
|
|
830
947
|
from art_tools import pastas_get_model_results
|
|
831
|
-
except Exception:
|
|
832
|
-
raise ModuleNotFoundError("You need 'art_tools' to use this method!")
|
|
948
|
+
except Exception as e:
|
|
949
|
+
raise ModuleNotFoundError("You need 'art_tools' to use this method!") from e
|
|
833
950
|
|
|
834
951
|
if mls is None:
|
|
835
952
|
mls = self.conn.models
|
|
@@ -870,7 +987,7 @@ class PastaStore:
|
|
|
870
987
|
"File already exists! " "Use 'overwrite=True' to " "force writing file."
|
|
871
988
|
)
|
|
872
989
|
elif os.path.exists(fname):
|
|
873
|
-
warnings.warn(f"Overwriting file '{os.path.basename(fname)}'")
|
|
990
|
+
warnings.warn(f"Overwriting file '{os.path.basename(fname)}'", stacklevel=1)
|
|
874
991
|
|
|
875
992
|
with ZipFile(fname, "w", compression=ZIP_DEFLATED) as archive:
|
|
876
993
|
# oseries
|
|
@@ -991,8 +1108,9 @@ class PastaStore:
|
|
|
991
1108
|
libname: str,
|
|
992
1109
|
s: Optional[Union[list, str]] = None,
|
|
993
1110
|
case_sensitive: bool = True,
|
|
1111
|
+
sort=True,
|
|
994
1112
|
):
|
|
995
|
-
"""Search for names of time series or models starting with s
|
|
1113
|
+
"""Search for names of time series or models starting with `s`.
|
|
996
1114
|
|
|
997
1115
|
Parameters
|
|
998
1116
|
----------
|
|
@@ -1002,19 +1120,20 @@ class PastaStore:
|
|
|
1002
1120
|
find names with part of this string or strings in list
|
|
1003
1121
|
case_sensitive : bool, optional
|
|
1004
1122
|
whether search should be case sensitive, by default True
|
|
1123
|
+
sort : bool, optional
|
|
1124
|
+
sort list of names
|
|
1005
1125
|
|
|
1006
1126
|
Returns
|
|
1007
1127
|
-------
|
|
1008
1128
|
matches : list
|
|
1009
1129
|
list of names that match search result
|
|
1010
1130
|
"""
|
|
1011
|
-
|
|
1012
1131
|
if libname == "models":
|
|
1013
|
-
lib_names =
|
|
1132
|
+
lib_names = self.model_names
|
|
1014
1133
|
elif libname == "stresses":
|
|
1015
|
-
lib_names =
|
|
1134
|
+
lib_names = self.stresses_names
|
|
1016
1135
|
elif libname == "oseries":
|
|
1017
|
-
lib_names =
|
|
1136
|
+
lib_names = self.oseries_names
|
|
1018
1137
|
else:
|
|
1019
1138
|
raise ValueError("Provide valid libname: 'models', 'stresses' or 'oseries'")
|
|
1020
1139
|
|
|
@@ -1031,7 +1150,8 @@ class PastaStore:
|
|
|
1031
1150
|
else:
|
|
1032
1151
|
m = np.append(m, [n for n in lib_names if sub.lower() in n.lower()])
|
|
1033
1152
|
matches = list(np.unique(m))
|
|
1034
|
-
|
|
1153
|
+
if sort:
|
|
1154
|
+
matches.sort()
|
|
1035
1155
|
return matches
|
|
1036
1156
|
|
|
1037
1157
|
def get_model_timeseries_names(
|
|
@@ -1060,7 +1180,6 @@ class PastaStore:
|
|
|
1060
1180
|
indicating whether a stress is contained within a time series
|
|
1061
1181
|
model.
|
|
1062
1182
|
"""
|
|
1063
|
-
|
|
1064
1183
|
model_names = self.conn._parse_names(modelnames, libname="models")
|
|
1065
1184
|
structure = pd.DataFrame(
|
|
1066
1185
|
index=model_names, columns=["oseries"] + self.stresses_names
|
|
@@ -1127,6 +1246,53 @@ class PastaStore:
|
|
|
1127
1246
|
"'libname' must be one of ['oseries', 'stresses', 'models']!"
|
|
1128
1247
|
)
|
|
1129
1248
|
getter = getattr(self.conn, f"get_{libname}")
|
|
1130
|
-
for n in
|
|
1249
|
+
for n in (
|
|
1250
|
+
tqdm(names, desc=f"Applying {func.__name__}") if progressbar else names
|
|
1251
|
+
):
|
|
1131
1252
|
result[n] = func(getter(n))
|
|
1132
1253
|
return result
|
|
1254
|
+
|
|
1255
|
+
def within(self, extent, names=None, libname="oseries"):
|
|
1256
|
+
"""Get names of items within extent.
|
|
1257
|
+
|
|
1258
|
+
Parameters
|
|
1259
|
+
----------
|
|
1260
|
+
extent : list
|
|
1261
|
+
list with [xmin, xmax, ymin, ymax]
|
|
1262
|
+
names : str, list of str, optional
|
|
1263
|
+
list of names to include, by default None
|
|
1264
|
+
libname : str, optional
|
|
1265
|
+
name of library, must be one of ('oseries', 'stresses', 'models'), by
|
|
1266
|
+
default "oseries"
|
|
1267
|
+
|
|
1268
|
+
Returns
|
|
1269
|
+
-------
|
|
1270
|
+
list
|
|
1271
|
+
list of items within extent
|
|
1272
|
+
"""
|
|
1273
|
+
xmin, xmax, ymin, ymax = extent
|
|
1274
|
+
names = self.conn._parse_names(names, libname)
|
|
1275
|
+
if libname == "oseries":
|
|
1276
|
+
df = self.oseries.loc[names]
|
|
1277
|
+
elif libname == "stresses":
|
|
1278
|
+
df = self.stresses.loc[names]
|
|
1279
|
+
elif libname == "models":
|
|
1280
|
+
onames = np.unique(
|
|
1281
|
+
[
|
|
1282
|
+
self.get_models(modelname, return_dict=True)["oseries"]["name"]
|
|
1283
|
+
for modelname in names
|
|
1284
|
+
]
|
|
1285
|
+
)
|
|
1286
|
+
df = self.oseries.loc[onames]
|
|
1287
|
+
else:
|
|
1288
|
+
raise ValueError(
|
|
1289
|
+
"libname must be one of ['oseries', 'stresses', 'models']"
|
|
1290
|
+
f", got '{libname}'"
|
|
1291
|
+
)
|
|
1292
|
+
mask = (
|
|
1293
|
+
(df["x"] <= xmax)
|
|
1294
|
+
& (df["x"] >= xmin)
|
|
1295
|
+
& (df["y"] >= ymin)
|
|
1296
|
+
& (df["y"] <= ymax)
|
|
1297
|
+
)
|
|
1298
|
+
return df.loc[mask].index.tolist()
|
pastastore/styling.py
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""Module containing dataframe styling functions."""
|
|
2
|
+
|
|
3
|
+
import matplotlib as mpl
|
|
4
|
+
import matplotlib.pyplot as plt
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def float_styler(val, norm, cmap=None):
|
|
9
|
+
"""Style float values in DataFrame.
|
|
10
|
+
|
|
11
|
+
Parameters
|
|
12
|
+
----------
|
|
13
|
+
val : float
|
|
14
|
+
value in cell
|
|
15
|
+
norm : matplotlib.colors.Normalize
|
|
16
|
+
normalizer to map values to range(0, 1)
|
|
17
|
+
cmap : colormap, optional
|
|
18
|
+
colormap to use, by default None, which uses RdYlBu
|
|
19
|
+
|
|
20
|
+
Returns
|
|
21
|
+
-------
|
|
22
|
+
str
|
|
23
|
+
css value pairs for styling dataframe
|
|
24
|
+
|
|
25
|
+
Usage
|
|
26
|
+
-----
|
|
27
|
+
Given some dataframe
|
|
28
|
+
|
|
29
|
+
>>> df.map(float_styler, subset=["some column"], norm=norm, cmap=cmap)
|
|
30
|
+
"""
|
|
31
|
+
if cmap is None:
|
|
32
|
+
cmap = plt.get_cmap("RdYlBu")
|
|
33
|
+
bg = cmap(norm(val))
|
|
34
|
+
color = mpl.colors.rgb2hex(bg)
|
|
35
|
+
c = "White" if np.mean(bg[:3]) < 0.4 else "Black"
|
|
36
|
+
return f"background-color: {color}; color: {c}"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def boolean_styler(b):
|
|
40
|
+
"""Style boolean values in DataFrame.
|
|
41
|
+
|
|
42
|
+
Parameters
|
|
43
|
+
----------
|
|
44
|
+
b : bool
|
|
45
|
+
value in cell
|
|
46
|
+
|
|
47
|
+
Returns
|
|
48
|
+
-------
|
|
49
|
+
str
|
|
50
|
+
css value pairs for styling dataframe
|
|
51
|
+
|
|
52
|
+
Usage
|
|
53
|
+
-----
|
|
54
|
+
Given some dataframe
|
|
55
|
+
|
|
56
|
+
>>> df.map(boolean_styler, subset=["some column"])
|
|
57
|
+
"""
|
|
58
|
+
if b:
|
|
59
|
+
return (
|
|
60
|
+
f"background-color: {mpl.colors.rgb2hex((231/255, 255/255, 239/255))}; "
|
|
61
|
+
"color: darkgreen"
|
|
62
|
+
)
|
|
63
|
+
else:
|
|
64
|
+
return (
|
|
65
|
+
f"background-color: {mpl.colors.rgb2hex((255/255, 238/255, 238/255))}; "
|
|
66
|
+
"color: darkred"
|
|
67
|
+
)
|