Skip to content

Commit 5efd2e3

Browse files
authored
Merge branch 'main' into hf_x_object
2 parents 31ad52e + 71b4efe commit 5efd2e3

File tree

4 files changed

+114
-18
lines changed

4 files changed

+114
-18
lines changed

plotly_resampler/figure_resampler/figure_resampler_interface.py

Lines changed: 40 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -595,6 +595,7 @@ def _parse_get_trace_props(
595595
hf_y: Iterable = None,
596596
hf_text: Iterable = None,
597597
hf_hovertext: Iterable = None,
598+
check_nans: bool = True,
598599
) -> _hf_data_container:
599600
"""Parse and capture the possibly high-frequency trace-props in a datacontainer.
600601
@@ -603,14 +604,19 @@ def _parse_get_trace_props(
603604
trace : BaseTraceType
604605
The trace which will be parsed.
605606
hf_x : Iterable, optional
606-
high-frequency trace "x" data, overrides the current trace its x-data.
607+
High-frequency trace "x" data, overrides the current trace its x-data.
607608
hf_y : Iterable, optional
608-
high-frequency trace "y" data, overrides the current trace its y-data.
609+
High-frequency trace "y" data, overrides the current trace its y-data.
609610
hf_text : Iterable, optional
610-
high-frequency trace "text" data, overrides the current trace its text-data.
611+
High-frequency trace "text" data, overrides the current trace its text-data.
611612
hf_hovertext : Iterable, optional
612-
high-frequency trace "hovertext" data, overrides the current trace its
613+
High-frequency trace "hovertext" data, overrides the current trace its
613614
hovertext data.
615+
check_nans: bool, optional
616+
Whether the `hf_y` should be checked for NaNs, by default True.
617+
As checking for NaNs is expensive, this can be disabled when the `hf_y` is
618+
already known to contain no NaNs (or when the downsampler can handle NaNs,
619+
e.g., EveryNthPoint).
614620
615621
Returns
616622
-------
@@ -680,7 +686,7 @@ def _parse_get_trace_props(
680686
# Remove NaNs for efficiency (storing less meaningless data)
681687
# NaNs introduce gaps between enclosing non-NaN data points & might distort
682688
# the resampling algorithms
683-
if pd.isna(hf_y).any():
689+
if check_nans and pd.isna(hf_y).any():
684690
not_nan_mask = ~pd.isna(hf_y)
685691
hf_x = hf_x[not_nan_mask]
686692
hf_y = hf_y[not_nan_mask]
@@ -843,6 +849,7 @@ def add_trace(
843849
hf_y: Iterable = None,
844850
hf_text: Union[str, Iterable] = None,
845851
hf_hovertext: Union[str, Iterable] = None,
852+
check_nans: bool = True,
846853
**trace_kwargs,
847854
):
848855
"""Add a trace to the figure.
@@ -870,7 +877,7 @@ def add_trace(
870877
.. note::
871878
If this variable is not set, ``_global_downsampler`` will be used.
872879
limit_to_view: boolean, optional
873-
If set to True the trace's datapoints will be cut to the corresponding
880+
If set to True, the trace's datapoints will be cut to the corresponding
874881
front-end view, even if the total number of samples is lower than
875882
``max_n_samples``, By default False.\n
876883
Remark that setting this parameter to True ensures that low frequency traces
@@ -888,6 +895,13 @@ def add_trace(
888895
hf_hovertext: Iterable, optional
889896
The original high frequency hovertext. If set, this has priority over the
890897
trace its ```hovertext`` argument.
898+
check_nans: boolean, optional
899+
If set to True, the trace's data will be checked for NaNs - which will be
900+
removed. By default True.
901+
As this is a costly operation, it is recommended to set this parameter to
902+
False if you are sure that your data does not contain NaNs (or when the
903+
downsampler can handle NaNs, e.g., EveryNthPoint). This should considerably
904+
speed up the graph construction time.
891905
**trace_kwargs: dict
892906
Additional trace related keyword arguments.
893907
e.g.: row=.., col=..., secondary_y=...
@@ -959,7 +973,7 @@ def add_trace(
959973

960974
# construct the hf_data_container
961975
# TODO in future version -> maybe regex on kwargs which start with `hf_`
962-
dc = self._parse_get_trace_props(trace, hf_x, hf_y, hf_text, hf_hovertext)
976+
dc = self._parse_get_trace_props(trace, hf_x, hf_y, hf_text, hf_hovertext, check_nans)
963977

964978
# These traces will determine the autoscale its RANGE!
965979
# -> so also store when `limit_to_view` is set.
@@ -1018,6 +1032,7 @@ def add_traces(
10181032
| List[AbstractSeriesAggregator]
10191033
| AbstractFigureAggregator = None,
10201034
limit_to_views: List[bool] | bool = False,
1035+
check_nans: List[bool] | bool = True,
10211036
**traces_kwargs,
10221037
):
10231038
"""Add traces to the figure.
@@ -1052,13 +1067,22 @@ def add_traces(
10521067
aggregator is passed, all traces will use this aggregator.
10531068
If this variable is not set, ``_global_downsampler`` will be used.
10541069
limit_to_views : None | List[bool] | bool, optional
1055-
List of limit_to_view booleans for the added traces. If set to True
1056-
the trace's datapoints will be cut to the corresponding front-end view,
1057-
even if the total number of samples is lower than ``max_n_samples``. If a
1058-
single boolean is passed, all to be added traces will use this value,
1070+
List of limit_to_view booleans for the added traces. If set to True the
1071+
trace's datapoints will be cut to the corresponding front-end view, even if
1072+
the total number of samples is lower than ``max_n_samples``.
1073+
If a single boolean is passed, all to be added traces will use this value,
10591074
by default False.\n
10601075
Remark that setting this parameter to True ensures that low frequency traces
10611076
are added to the ``hf_data`` property.
1077+
check_nans : None | List[bool] | bool, optional
1078+
List of check_nans booleans for the added traces. If set to True, the
1079+
trace's datapoints will be checked for NaNs. If a single boolean is passed,
1080+
all to be added traces will use this value, by default True.\n
1081+
As this is a costly operation, it is recommended to set this parameter to
1082+
False if the data is known to contain no NaNs (or when the downsampler can
1083+
handle NaNs, e.g., EveryNthPoint). This will considerably speed up the graph
1084+
construction time.
1085+
10621086
**traces_kwargs: dict
10631087
Additional trace related keyword arguments.
10641088
e.g.: rows=.., cols=..., secondary_ys=...
@@ -1098,9 +1122,11 @@ def add_traces(
10981122
downsamplers = [downsamplers] * len(data)
10991123
if isinstance(limit_to_views, bool):
11001124
limit_to_views = [limit_to_views] * len(data)
1125+
if isinstance(check_nans, bool):
1126+
check_nans = [check_nans] * len(data)
11011127

1102-
for i, (trace, max_out, downsampler, limit_to_view) in enumerate(
1103-
zip(data, max_n_samples, downsamplers, limit_to_views)
1128+
for i, (trace, max_out, downsampler, limit_to_view, check_nan) in enumerate(
1129+
zip(data, max_n_samples, downsamplers, limit_to_views, check_nans)
11041130
):
11051131
if (
11061132
trace.type.lower() not in self._high_frequency_traces
@@ -1112,7 +1138,7 @@ def add_traces(
11121138
if not limit_to_view and (trace.y is None or len(trace.y) <= max_out_s):
11131139
continue
11141140

1115-
dc = self._parse_get_trace_props(trace)
1141+
dc = self._parse_get_trace_props(trace, check_nans=check_nan)
11161142
self._hf_data[trace.uid] = self._construct_hf_data_dict(
11171143
dc,
11181144
trace=trace,

plotly_resampler/registering.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,10 +98,10 @@ def register_plotly_resampler(mode="auto", **aggregator_kwargs):
9898
The mode of the plotly-resampler.
9999
Possible values are: 'auto', 'figure', 'widget', None.
100100
If 'auto' is used, the mode is determined based on the environment; if it is in
101-
an iPython environment, the mode is 'widget', otherwise it is 'figure'.
101+
an IPython environment, the mode is 'widget', otherwise it is 'figure'.
102102
If 'figure' is used, all plotly figures are wrapped as FigureResampler objects.
103103
If 'widget' is used, all plotly figure widgets are wrapped as
104-
FigureWidgetResampler objects (we advise to use this mode in iPython environment
104+
FigureWidgetResampler objects (we advise to use this mode in IPython environment
105105
with a kernel).
106106
If None is used, wrapping is done as expected (go.Figure -> FigureResampler,
107107
go.FigureWidget -> FigureWidgetResampler).

tests/test_figure_resampler.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -346,14 +346,17 @@ def test_nan_removed_input(float_series):
346346
)
347347

348348
float_series = float_series.copy()
349-
float_series.iloc[np.random.choice(len(float_series), 100)] = np.nan
349+
float_series.iloc[np.random.choice(len(float_series), 100, replace=False)] = np.nan
350350
fig.add_trace(
351351
go.Scatter(x=float_series.index, y=float_series, name="float_series"),
352352
row=1,
353353
col=1,
354354
hf_text="text",
355355
hf_hovertext="hovertext",
356356
)
357+
# Check the desired behavior
358+
assert len(fig.hf_data[0]["y"]) == len(float_series) - 100
359+
assert ~pd.isna(fig.hf_data[0]["y"]).any()
357360

358361
# here we test whether we are able to deal with not-nan output
359362
float_series.iloc[np.random.choice(len(float_series), 100)] = np.nan
@@ -377,6 +380,37 @@ def test_nan_removed_input(float_series):
377380
col=2,
378381
)
379382

383+
def test_nan_removed_input_check_nans_false(float_series):
384+
# see: https://plotly.com/python/subplots/#custom-sized-subplot-with-subplot-titles
385+
base_fig = make_subplots(
386+
rows=2,
387+
cols=2,
388+
specs=[[{}, {}], [{"colspan": 2}, None]],
389+
)
390+
391+
fig = FigureResampler(
392+
base_fig,
393+
default_n_shown_samples=1000,
394+
resampled_trace_prefix_suffix=(
395+
'<b style="color:sandybrown">[R]</b>',
396+
'<b style="color:sandybrown">[R]</b>',
397+
),
398+
)
399+
400+
float_series = float_series.copy()
401+
float_series.iloc[np.random.choice(len(float_series), 100)] = np.nan
402+
fig.add_trace(
403+
go.Scatter(x=float_series.index, y=float_series, name="float_series"),
404+
row=1,
405+
col=1,
406+
hf_text="text",
407+
hf_hovertext="hovertext",
408+
check_nans=False
409+
)
410+
# Check the undesired behavior
411+
assert len(fig.hf_data[0]["y"]) == len(float_series)
412+
assert pd.isna(fig.hf_data[0]["y"]).any()
413+
380414

381415
def test_hf_text():
382416
y = np.arange(10_000)

tests/test_figurewidget_resampler.py

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,14 +262,17 @@ def test_nan_removed_input(float_series):
262262
)
263263

264264
float_series = float_series.copy()
265-
float_series.iloc[np.random.choice(len(float_series), 100)] = np.nan
265+
float_series.iloc[np.random.choice(len(float_series), 100, replace=False)] = np.nan
266266
fig.add_trace(
267267
go.Scatter(x=float_series.index, y=float_series, name="float_series"),
268268
row=1,
269269
col=1,
270270
hf_text="text",
271271
hf_hovertext="hovertext",
272272
)
273+
# Check the desired behavior
274+
assert len(fig.hf_data[0]["y"]) == len(float_series) - 100
275+
assert ~pd.isna(fig.hf_data[0]["y"]).any()
273276

274277
# here we test whether we are able to deal with not-nan output
275278
float_series.iloc[np.random.choice(len(float_series), 100)] = np.nan
@@ -294,6 +297,38 @@ def test_nan_removed_input(float_series):
294297
)
295298

296299

300+
def test_nan_removed_input_check_nans_false(float_series):
301+
# see: https://plotly.com/python/subplots/#custom-sized-subplot-with-subplot-titles
302+
base_fig = make_subplots(
303+
rows=2,
304+
cols=2,
305+
specs=[[{}, {}], [{"colspan": 2}, None]],
306+
)
307+
308+
fig = FigureWidgetResampler(
309+
base_fig,
310+
default_n_shown_samples=1000,
311+
resampled_trace_prefix_suffix=(
312+
'<b style="color:sandybrown">[R]</b>',
313+
'<b style="color:sandybrown">[R]</b>',
314+
),
315+
)
316+
317+
float_series = float_series.copy()
318+
float_series.iloc[np.random.choice(len(float_series), 100)] = np.nan
319+
fig.add_trace(
320+
go.Scatter(x=float_series.index, y=float_series, name="float_series"),
321+
row=1,
322+
col=1,
323+
hf_text="text",
324+
hf_hovertext="hovertext",
325+
check_nans=False
326+
)
327+
# Check the undesired behavior
328+
assert len(fig.hf_data[0]["y"]) == len(float_series)
329+
assert pd.isna(fig.hf_data[0]["y"]).any()
330+
331+
297332
def test_hf_text():
298333
y = np.arange(10_000)
299334

@@ -932,6 +967,7 @@ def test_hf_data_subplots_non_shared_xaxes_row_col_none():
932967
assert 40_000 <= x_1[0] <= 40_000 + (20_000 / 1000)
933968
assert (60_000 - 20_000 / 1_000) <= x_1[-1] <= 60_000
934969

970+
935971
def test_updates_two_traces():
936972
n = 1_000_000
937973
X = np.arange(n)

0 commit comments

Comments
 (0)