Skip to content

Commit 5bdf444

Browse files
jonasvddjvdd
andauthored
✨ add fill_value option to gap handlers (#218)
* ✨ add fill_value option to gap handlers * 💨 update basic example * 🖊️ review code --------- Co-authored-by: Jeroen Van Der Donckt <[email protected]>
1 parent ef0d4e9 commit 5bdf444

File tree

5 files changed

+149
-12
lines changed

5 files changed

+149
-12
lines changed

examples/README.md

+2
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ Additionally, this notebook also shows some more advanced functionalities, such
2222
* How to add (shaded) confidence bounds to your time series
2323
* The flexibility of configuring different aggregation-algorithms and number of shown samples per trace
2424
* How plotly-resampler can be used for logarithmic x-axes and an implementation of a logarithmic aggregation algorithm, i.e., [LogLTTB](example_utils/loglttb.py)
25+
* Using different `fill_value` for gap handling of filled area plots.
2526

27+
**Note**: the basic example notebook requires `plotly-resampler>=0.9.0rc3`.
2628

2729
### 1.2 Figurewidget example
2830

examples/basic_example.ipynb

+92-7
Large diffs are not rendered by default.

plotly_resampler/aggregation/gap_handler_interface.py

+19-4
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,19 @@
1111

1212

1313
class AbstractGapHandler(ABC):
14+
def __init__(self, fill_value: Optional[float] = None):
15+
"""Constructor of AbstractGapHandler.
16+
17+
Parameters
18+
----------
19+
fill_value: float, optional
20+
The value to fill the gaps with, by default None.
21+
Note that setting this value to 0 for filled area plots is particularly
22+
useful.
23+
24+
"""
25+
self.fill_value = fill_value
26+
1427
@abstractmethod
1528
def _get_gap_mask(self, x_agg: np.ndarray) -> Optional[np.ndarray]:
1629
"""Get a boolean mask indicating the indices where there are gaps.
@@ -32,13 +45,13 @@ def _get_gap_mask(self, x_agg: np.ndarray) -> Optional[np.ndarray]:
3245
"""
3346
pass
3447

35-
def insert_none_between_gaps(
48+
def insert_fill_value_between_gaps(
3649
self,
3750
x_agg: np.ndarray,
3851
y_agg: np.ndarray,
3952
idxs: np.ndarray,
4053
) -> Tuple[np.ndarray, np.ndarray]:
41-
"""Insert None values in the y_agg array when there are gaps.
54+
"""Insert the fill_value in the y_agg array where there are gaps.
4255
4356
Gaps are determined by the x_agg array. The `_get_gap_mask` method is used to
4457
determine a boolean mask indicating the indices where there are gaps.
@@ -48,7 +61,7 @@ def insert_none_between_gaps(
4861
x_agg: np.ndarray
4962
The x array. This is used to determine the gaps.
5063
y_agg: np.ndarray
51-
The y array. A copy of this array will be expanded with None values where
64+
The y array. A copy of this array will be expanded with fill_values where
5265
there are gaps.
5366
idxs: np.ndarray
5467
The index array. This is relevant aggregators that perform data point
@@ -83,6 +96,8 @@ def insert_none_between_gaps(
8396
# Set the NaN values
8497
# We add the gap index offset (via the np.arange) to the indices to account for
8598
# the repeats (i.e., expanded y_agg array).
86-
y_agg_exp_nan[np.where(gap_mask)[0] + np.arange(gap_mask.sum())] = None
99+
y_agg_exp_nan[
100+
np.where(gap_mask)[0] + np.arange(gap_mask.sum())
101+
] = self.fill_value
87102

88103
return y_agg_exp_nan, idx_exp_nan

plotly_resampler/aggregation/plotly_aggregator_parser.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ def aggregate(
183183
if np.issubdtype(xdt, np.timedelta64) or np.issubdtype(xdt, np.datetime64):
184184
agg_x_parsed = agg_x_parsed.view("int64")
185185

186-
agg_y, indices = gap_handler.insert_none_between_gaps(
186+
agg_y, indices = gap_handler.insert_fill_value_between_gaps(
187187
agg_x_parsed, agg_y, indices
188188
)
189189
if isinstance(downsampler, DataPointSelector):

tests/test_aggregators.py

+35
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,41 @@ def test_wrap_aggregate_x_gaps(downsampler, series):
174174
assert pd.Series(y_agg).isna().sum() == 3
175175

176176

177+
@pytest.mark.parametrize(
178+
"downsampler",
179+
[EveryNthPoint, LTTB, MinMaxAggregator, MinMaxLTTB, MinMaxOverlapAggregator],
180+
)
181+
@pytest.mark.parametrize("series", [lf("float_series")])
182+
def test_wrap_aggregate_x_gaps_float_fill_value(downsampler, series):
183+
idx = np.arange(len(series))
184+
idx[1000:] += 1000
185+
idx[2000:] += 1500
186+
idx[8000:] += 2500
187+
series.index = idx
188+
# 1. test with the default fill value (i.e., None)
189+
x_agg, y_agg, indices = wrap_aggregate(
190+
hf_x=series.index,
191+
# add a constant to the series to ensure that the fill value is not used
192+
hf_y=series.values + 1000,
193+
downsampler=downsampler(),
194+
gap_handler=MedDiffGapHandler(),
195+
n_out=100,
196+
)
197+
assert len(x_agg) == len(y_agg) == len(indices)
198+
assert pd.Series(y_agg).isnull().sum() == 3
199+
# 2. test with a custom default fill value (i.e., 0)
200+
x_agg, y_agg, indices = wrap_aggregate(
201+
hf_x=series.index,
202+
# add a constant to the series to ensure that the fill value is not used
203+
hf_y=series.values + 1000,
204+
downsampler=downsampler(),
205+
gap_handler=MedDiffGapHandler(fill_value=0),
206+
n_out=100,
207+
)
208+
assert len(x_agg) == len(y_agg) == len(indices)
209+
assert pd.Series(y_agg == 0).sum() == 3
210+
211+
177212
@pytest.mark.parametrize(
178213
"downsampler", [EveryNthPoint, LTTB, MinMaxLTTB, MinMaxOverlapAggregator]
179214
)

0 commit comments

Comments
 (0)