hvplot 0.9.3a1__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.
Files changed (63) hide show
  1. hvplot/__init__.py +322 -0
  2. hvplot/_version.py +16 -0
  3. hvplot/backend_transforms.py +329 -0
  4. hvplot/converter.py +2855 -0
  5. hvplot/cudf.py +26 -0
  6. hvplot/dask.py +42 -0
  7. hvplot/data/crime.csv +56 -0
  8. hvplot/datasets.yaml +48 -0
  9. hvplot/fugue.py +64 -0
  10. hvplot/ibis.py +21 -0
  11. hvplot/intake.py +32 -0
  12. hvplot/interactive.py +968 -0
  13. hvplot/networkx.py +625 -0
  14. hvplot/pandas.py +30 -0
  15. hvplot/plotting/__init__.py +63 -0
  16. hvplot/plotting/andrews_curves.py +99 -0
  17. hvplot/plotting/core.py +2288 -0
  18. hvplot/plotting/lag_plot.py +34 -0
  19. hvplot/plotting/parallel_coordinates.py +85 -0
  20. hvplot/plotting/scatter_matrix.py +220 -0
  21. hvplot/polars.py +21 -0
  22. hvplot/sample_data.py +30 -0
  23. hvplot/streamz.py +21 -0
  24. hvplot/tests/__init__.py +0 -0
  25. hvplot/tests/conftest.py +44 -0
  26. hvplot/tests/data/README.md +5 -0
  27. hvplot/tests/data/RGB-red.byte.tif +0 -0
  28. hvplot/tests/plotting/__init__.py +0 -0
  29. hvplot/tests/plotting/testcore.py +108 -0
  30. hvplot/tests/plotting/testohlc.py +34 -0
  31. hvplot/tests/plotting/testscattermatrix.py +138 -0
  32. hvplot/tests/test_links.py +99 -0
  33. hvplot/tests/testbackend_transforms.py +89 -0
  34. hvplot/tests/testcharts.py +452 -0
  35. hvplot/tests/testfugue.py +46 -0
  36. hvplot/tests/testgeo.py +468 -0
  37. hvplot/tests/testgeowithoutgv.py +60 -0
  38. hvplot/tests/testgridplots.py +259 -0
  39. hvplot/tests/testhelp.py +75 -0
  40. hvplot/tests/testibis.py +17 -0
  41. hvplot/tests/testinteractive.py +1442 -0
  42. hvplot/tests/testnetworkx.py +26 -0
  43. hvplot/tests/testoperations.py +385 -0
  44. hvplot/tests/testoptions.py +596 -0
  45. hvplot/tests/testoverrides.py +74 -0
  46. hvplot/tests/testpanel.py +70 -0
  47. hvplot/tests/testpatch.py +135 -0
  48. hvplot/tests/testplotting.py +69 -0
  49. hvplot/tests/teststreaming.py +28 -0
  50. hvplot/tests/testtransforms.py +39 -0
  51. hvplot/tests/testui.py +383 -0
  52. hvplot/tests/testutil.py +378 -0
  53. hvplot/tests/util.py +82 -0
  54. hvplot/ui.py +1032 -0
  55. hvplot/util.py +677 -0
  56. hvplot/utilities.py +129 -0
  57. hvplot/xarray.py +62 -0
  58. hvplot-0.9.3a1.dist-info/LICENSE +29 -0
  59. hvplot-0.9.3a1.dist-info/METADATA +243 -0
  60. hvplot-0.9.3a1.dist-info/RECORD +63 -0
  61. hvplot-0.9.3a1.dist-info/WHEEL +5 -0
  62. hvplot-0.9.3a1.dist-info/entry_points.txt +2 -0
  63. hvplot-0.9.3a1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1442 @@
1
+ from packaging.version import Version
2
+
3
+ import holoviews as hv
4
+ import hvplot.pandas # noqa
5
+ import hvplot.xarray # noqa
6
+ import matplotlib
7
+ import numpy as np
8
+ import pandas as pd
9
+ import panel as pn
10
+ import pytest
11
+ import xarray as xr
12
+
13
+ from holoviews.util.transform import dim
14
+ from hvplot import bind
15
+ from hvplot.interactive import Interactive
16
+ from hvplot.tests.util import makeDataFrame, makeMixedDataFrame
17
+ from hvplot.xarray import XArrayInteractive
18
+ from hvplot.util import bokeh3, param2
19
+
20
+ is_bokeh2 = pytest.mark.skipif(bokeh3, reason='requires bokeh 2.x')
21
+ is_bokeh3 = pytest.mark.skipif(not bokeh3, reason='requires bokeh 3.x')
22
+
23
+
24
+ @pytest.fixture(scope='module')
25
+ def series():
26
+ return pd.Series(np.arange(5.0), name='A')
27
+
28
+
29
+ @pytest.fixture(scope='module')
30
+ def df():
31
+ return makeMixedDataFrame()
32
+
33
+
34
+ @pytest.fixture(scope='module')
35
+ def dataset():
36
+ return xr.tutorial.load_dataset('air_temperature')
37
+
38
+
39
+ @pytest.fixture(scope='module')
40
+ def dataarray(dataset):
41
+ return dataset.air
42
+
43
+
44
+ class CallCtxt:
45
+ def __init__(self, call_args, call_kwargs, **kwargs):
46
+ for k, v in kwargs.items():
47
+ if k in ['args', 'kwargs']:
48
+ raise ValueError("**kwargs passed to CallCtxt can't be named args or kwargs")
49
+ setattr(self, k, v)
50
+ self.args = call_args
51
+ self.kwargs = call_kwargs
52
+
53
+ def __repr__(self):
54
+ inner = ''
55
+ for attr in vars(self):
56
+ inner += f'{attr}={getattr(self, attr)!r}, '
57
+ return f'CallCtxt({inner}args={self.args!r}, kwargs={self.kwargs!r})'
58
+
59
+ def is_empty(self):
60
+ return not self.args and not self.kwargs
61
+
62
+
63
+ class Spy:
64
+ def __init__(self):
65
+ self.count = 0
66
+ self.calls = {}
67
+
68
+ def __repr__(self):
69
+ return f'Spy(count={self.count!r}, calls={self.calls!r})'
70
+
71
+ def register_call(self, called_args, called_kwargs, **kwargs):
72
+ self.calls[self.count] = CallCtxt(called_args, called_kwargs, **kwargs)
73
+ self.count += 1
74
+
75
+
76
+ @pytest.fixture
77
+ def clone_spy():
78
+ # In hindsight it might have been a better idea to spy __init__
79
+ # rather than _clone, to check exactly how new instances are created
80
+ # and not depend on the implementation of _clone.
81
+ spy = Spy()
82
+
83
+ _clone = Interactive._clone
84
+
85
+ def clone_bis(inst, *args, **kwargs):
86
+ cloned = _clone(inst, *args, **kwargs)
87
+ spy.register_call(args, kwargs, depth=cloned._depth)
88
+ return cloned
89
+
90
+ Interactive._clone = clone_bis
91
+
92
+ yield spy
93
+
94
+ Interactive._clone = _clone
95
+
96
+
97
+ def test_spy(clone_spy, series):
98
+ si = Interactive(series)
99
+ si._clone()
100
+
101
+ assert clone_spy.count == 1
102
+ assert clone_spy.calls[0].is_empty()
103
+
104
+ si._clone(x='X')
105
+
106
+ assert clone_spy.count == 2
107
+ assert not clone_spy.calls[1].is_empty()
108
+ assert clone_spy.calls[1].kwargs == dict(x='X')
109
+
110
+
111
+ def test_interactive_pandas_dataframe(df):
112
+ dfi = Interactive(df)
113
+
114
+ assert type(dfi) is Interactive
115
+ assert dfi._obj is df
116
+ assert dfi._fn is None
117
+ assert dfi._transform == dim('*')
118
+ assert dfi._method is None
119
+
120
+
121
+ def test_interactive_pandas_series(series):
122
+ si = Interactive(series)
123
+
124
+ assert type(si) is Interactive
125
+ assert si._obj is series
126
+ assert si._fn is None
127
+ assert si._transform == dim('*')
128
+ assert si._method is None
129
+
130
+
131
+ def test_interactive_xarray_dataarray(dataarray):
132
+ dai = Interactive(dataarray)
133
+
134
+ assert type(dai) is XArrayInteractive
135
+ assert (dai._obj == dataarray).all()
136
+ assert dai._fn is None
137
+ assert dai._transform == dim('air')
138
+ assert dai._method is None
139
+
140
+
141
+ def test_interactive_xarray_dataarray_no_name():
142
+ dataarray = xr.DataArray(np.random.rand(2, 2))
143
+ with pytest.raises(ValueError, match='Cannot use interactive API on DataArray without name'):
144
+ Interactive(dataarray)
145
+
146
+
147
+ def test_interactive_xarray_dataset(dataset):
148
+ dsi = Interactive(dataset)
149
+
150
+ assert type(dsi) is XArrayInteractive
151
+ assert dsi._obj is dataset
152
+ assert dsi._fn is None
153
+ assert dsi._transform == dim('*')
154
+ assert dsi._method is None
155
+
156
+
157
+ def test_interactive_pandas_function(df):
158
+ select = pn.widgets.Select(options=list(df.columns))
159
+
160
+ def sel_col(col):
161
+ return df[col]
162
+
163
+ dfi = Interactive(bind(sel_col, select))
164
+ assert type(dfi) is Interactive
165
+ assert dfi._obj is df.A
166
+ assert isinstance(dfi._fn, pn.param.ParamFunction)
167
+ assert dfi._transform == dim('*')
168
+ assert dfi._method is None
169
+
170
+ select.value = 'B'
171
+ assert dfi._obj is df.B
172
+
173
+
174
+ def test_interactive_xarray_function(dataset):
175
+ ds = dataset.copy()
176
+ ds['air2'] = ds.air * 2
177
+
178
+ select = pn.widgets.Select(options=list(ds))
179
+
180
+ def sel_col(sel):
181
+ return ds[sel]
182
+
183
+ dsi = Interactive(bind(sel_col, select))
184
+
185
+ assert type(dsi) is XArrayInteractive
186
+ assert isinstance(dsi._fn, pn.param.ParamFunction)
187
+ assert dsi._transform == dim('air')
188
+ assert dsi._method is None
189
+
190
+ select.value = 'air2'
191
+ assert (dsi._obj == ds.air2).all()
192
+ assert dsi._transform == dim('air2')
193
+
194
+
195
+ def test_interactive_nested_widgets():
196
+ df = makeDataFrame()
197
+ w = pn.widgets.RadioButtonGroup(value='A', options=list('ABC'))
198
+
199
+ idf = Interactive(df)
200
+ pipeline = idf.groupby(['D', w]).mean()
201
+ ioutput = pipeline.panel().object().object
202
+ iw = pipeline.widgets()
203
+
204
+ output = df.groupby(['D', 'A']).mean()
205
+
206
+ pd.testing.assert_frame_equal(ioutput, output)
207
+ assert len(iw) == 1
208
+ assert iw[0] == w
209
+
210
+
211
+ @pytest.mark.skipif(
212
+ Version(hv.__version__) < Version('1.15.1'),
213
+ reason='Needs holoviews 1.15.1',
214
+ )
215
+ def test_interactive_slice():
216
+ df = makeDataFrame()
217
+ w = pn.widgets.IntSlider(start=10, end=40)
218
+
219
+ idf = Interactive(df)
220
+ pipeline = idf.iloc[:w]
221
+ ioutput = pipeline.panel().object().object
222
+ iw = pipeline.widgets()
223
+
224
+ output = df.iloc[:10]
225
+
226
+ pd.testing.assert_frame_equal(ioutput, output)
227
+ assert len(iw) == 1
228
+ assert iw[0] == w
229
+
230
+ w.value = 15
231
+ ioutput = pipeline.panel().object().object
232
+ output = df.iloc[:15]
233
+ pd.testing.assert_frame_equal(ioutput, output)
234
+
235
+
236
+ def test_interactive_pandas_dataframe_hvplot_accessor(df):
237
+ dfi = df.interactive()
238
+
239
+ assert dfi.hvplot(kind='scatter')._transform == dfi.hvplot.scatter()._transform
240
+
241
+ with pytest.raises(TypeError):
242
+ dfi.hvplot.scatter(kind='area')
243
+
244
+
245
+ def test_interactive_xarray_dataset_hvplot_accessor(dataarray):
246
+ dai = dataarray.interactive
247
+
248
+ assert dai.hvplot(kind='line')._transform == dai.hvplot.line()._transform
249
+
250
+ with pytest.raises(TypeError):
251
+ dai.hvplot.line(kind='area')
252
+
253
+
254
+ def test_interactive_pandas_dataframe_hvplot_accessor_dmap(df):
255
+ dfi = df.interactive()
256
+ dfi = dfi.hvplot.line(y='A')
257
+
258
+ # TODO: Not sure about the logic
259
+ assert dfi._dmap is True
260
+
261
+
262
+ def test_interactive_pandas_dataframe_hvplot_accessor_dmap_kind_widget(df):
263
+ w = pn.widgets.Select(options=['line', 'scatter'])
264
+ dfi = df.interactive()
265
+ dfi = dfi.hvplot(kind=w, y='A')
266
+
267
+ assert dfi._dmap is False
268
+
269
+
270
+ def test_interactive_with_bound_function_calls():
271
+ df = pd.DataFrame({'species': [1, 1, 1, 2, 2, 2], 'sex': 3 * ['MALE', 'FEMALE']})
272
+
273
+ w_species = pn.widgets.Select(name='Species', options=[1, 2])
274
+ w_sex = pn.widgets.MultiSelect(name='Sex', value=['MALE'], options=['MALE', 'FEMALE'])
275
+
276
+ def load_data(species, watch=True):
277
+ if watch:
278
+ load_data.COUNT += 1
279
+ return df.loc[df['species'] == species]
280
+
281
+ load_data.COUNT = 0
282
+
283
+ # Setting up interactive with a function
284
+ dfi = bind(load_data, w_species).interactive()
285
+ dfi = dfi.loc[dfi['sex'].isin(w_sex)]
286
+
287
+ out = dfi.output()
288
+
289
+ assert isinstance(out, pn.param.ParamFunction)
290
+ assert isinstance(out._pane, pn.pane.DataFrame)
291
+ pd.testing.assert_frame_equal(
292
+ out._pane.object, load_data(w_species.value, watch=False).loc[df['sex'].isin(w_sex.value)]
293
+ )
294
+
295
+ (dfi.loc[dfi['sex'].isin(w_sex)])
296
+ assert load_data.COUNT == 1
297
+
298
+ w_species.value = 2
299
+
300
+ pd.testing.assert_frame_equal(
301
+ out._pane.object, load_data(w_species.value, watch=False).loc[df['sex'].isin(w_sex.value)]
302
+ )
303
+
304
+ assert load_data.COUNT == 2
305
+
306
+ dfi = dfi.head(1)
307
+
308
+ assert load_data.COUNT == 2
309
+
310
+ out = dfi.output()
311
+
312
+ pd.testing.assert_frame_equal(
313
+ out._pane.object,
314
+ load_data(w_species.value, watch=False).loc[df['sex'].isin(w_sex.value)].head(1),
315
+ )
316
+
317
+ w_species.value = 1
318
+
319
+ pd.testing.assert_frame_equal(
320
+ out._pane.object,
321
+ load_data(w_species.value, watch=False).loc[df['sex'].isin(w_sex.value)].head(1),
322
+ )
323
+
324
+ assert load_data.COUNT == 3
325
+
326
+
327
+ def test_interactive_pandas_series_init(series, clone_spy):
328
+ si = Interactive(series)
329
+
330
+ assert clone_spy.count == 0
331
+
332
+ assert si._obj is series
333
+ assert repr(si._transform) == "dim('*')"
334
+ assert isinstance(si._current, pd.DataFrame)
335
+ pd.testing.assert_series_equal(si._current.A, series)
336
+ assert si._depth == 0
337
+ assert si._method is None
338
+
339
+
340
+ def test_interactive_pandas_series_accessor(series, clone_spy):
341
+ si = series.interactive()
342
+
343
+ assert isinstance(si, Interactive)
344
+ assert clone_spy.count == 1
345
+ assert clone_spy.calls[0].is_empty()
346
+
347
+ assert si._obj is series
348
+ assert repr(si._transform) == "dim('*')"
349
+ assert isinstance(si._current, pd.DataFrame)
350
+ pd.testing.assert_series_equal(si._current.A, series)
351
+ assert si._depth == 1
352
+ assert si._method is None
353
+
354
+
355
+ def test_interactive_pandas_series_operator(series, clone_spy):
356
+ si = Interactive(series)
357
+ si = si + 2
358
+
359
+ assert isinstance(si, Interactive)
360
+ assert isinstance(si._current, pd.DataFrame)
361
+ pd.testing.assert_series_equal(si._current.A, series + 2)
362
+ assert si._obj is series
363
+ assert repr(si._transform) == "dim('*').pd+2"
364
+ assert si._depth == 2
365
+ assert si._method is None
366
+
367
+ assert clone_spy.count == 2
368
+
369
+ # _clone(copy=True) in _resolve_accessor in _apply_operator
370
+ assert clone_spy.calls[0].depth == 1
371
+ assert not clone_spy.calls[0].args
372
+ assert clone_spy.calls[0].kwargs == {'copy': True}
373
+
374
+ # _clone in _apply_operator
375
+ assert clone_spy.calls[1].depth == 2
376
+ assert len(clone_spy.calls[1].args) == 1
377
+ assert repr(clone_spy.calls[1].args[0]) == "dim('*').pd+2"
378
+ assert not clone_spy.calls[1].kwargs
379
+
380
+
381
+ def test_interactive_pandas_series_method_args(series, clone_spy):
382
+ si = Interactive(series)
383
+ si = si.head(2)
384
+
385
+ assert isinstance(si, Interactive)
386
+ assert isinstance(si._current, pd.DataFrame)
387
+ pd.testing.assert_series_equal(si._current.A, series.head(2))
388
+ assert si._obj is series
389
+ assert repr(si._transform) == "dim('*').pd.head(2)"
390
+ assert si._depth == 3
391
+ assert si._method is None
392
+
393
+ assert clone_spy.count == 3
394
+
395
+ # _clone in _resolve_accessor in __getattribute__(name='head')
396
+ assert clone_spy.calls[0].depth == 1
397
+ assert not clone_spy.calls[0].args
398
+ assert clone_spy.calls[0].kwargs == {'copy': True}
399
+
400
+ # 1st _clone(copy=True) in __call__
401
+ assert clone_spy.calls[1].depth == 2
402
+ assert not clone_spy.calls[1].args
403
+ assert clone_spy.calls[1].kwargs == {'copy': True}
404
+
405
+ # 2nd _clone in __call__
406
+ assert clone_spy.calls[2].depth == 3
407
+ assert len(clone_spy.calls[2].args) == 1
408
+ assert repr(clone_spy.calls[2].args[0]) == "dim('*').pd.head(2)"
409
+ assert clone_spy.calls[2].kwargs == {'plot': False}
410
+
411
+
412
+ def test_interactive_pandas_series_method_kwargs(series, clone_spy):
413
+ si = Interactive(series)
414
+ si = si.head(n=2)
415
+
416
+ assert isinstance(si, Interactive)
417
+ assert isinstance(si._current, pd.DataFrame)
418
+ pd.testing.assert_series_equal(si._current.A, series.head(2))
419
+ assert si._obj is series
420
+ assert repr(si._transform) == "dim('*').pd.head(n=2)"
421
+ assert si._depth == 3
422
+ assert si._method is None
423
+
424
+ assert clone_spy.count == 3
425
+
426
+ # _clone in _resolve_accessor in __getattribute__(name='head')
427
+ assert clone_spy.calls[0].depth == 1
428
+ assert not clone_spy.calls[0].args
429
+ assert clone_spy.calls[0].kwargs == {'copy': True}
430
+
431
+ # 1st _clone(copy=True) in __call__
432
+ assert clone_spy.calls[1].depth == 2
433
+ assert not clone_spy.calls[1].args
434
+ assert clone_spy.calls[1].kwargs == {'copy': True}
435
+
436
+ # 2nd _clone in __call__
437
+ assert clone_spy.calls[2].depth == 3
438
+ assert len(clone_spy.calls[2].args) == 1
439
+ assert repr(clone_spy.calls[2].args[0]) == "dim('*').pd.head(n=2)"
440
+ assert clone_spy.calls[2].kwargs == {'plot': False}
441
+
442
+
443
+ def test_interactive_pandas_series_method_not_called(series, clone_spy):
444
+ si = Interactive(series)
445
+ si = si.head
446
+
447
+ assert isinstance(si, Interactive)
448
+ assert isinstance(si._current, pd.DataFrame)
449
+ pd.testing.assert_series_equal(si._current.A, si._obj)
450
+ assert si._obj is series
451
+ assert repr(si._transform) == "dim('*')"
452
+ assert si._depth == 1
453
+ assert si._method == 'head'
454
+
455
+ assert clone_spy.count == 1
456
+
457
+ # _clone in _resolve_accessor in __getattribute__(name='head')
458
+ assert clone_spy.calls[0].depth == 1
459
+ assert not clone_spy.calls[0].args
460
+ assert clone_spy.calls[0].kwargs == {'copy': True}
461
+
462
+
463
+ def test_interactive_pandas_frame_attrib(df, clone_spy):
464
+ dfi = Interactive(df)
465
+ dfi = dfi.A
466
+
467
+ assert isinstance(dfi, Interactive)
468
+ assert isinstance(dfi._current, pd.DataFrame)
469
+ pd.testing.assert_frame_equal(dfi._current, dfi._obj)
470
+ assert dfi._obj is df
471
+ assert repr(dfi._transform) == "dim('*')"
472
+ assert dfi._depth == 1
473
+ assert dfi._method == 'A'
474
+
475
+ assert clone_spy.count == 1
476
+
477
+ # _clone in _resolve_accessor in __getattribute__(name='A')
478
+ assert clone_spy.calls[0].depth == 1
479
+ assert not clone_spy.calls[0].args
480
+ assert clone_spy.calls[0].kwargs == {'copy': True}
481
+
482
+
483
+ def test_interactive_pandas_series_operator_and_method(series, clone_spy):
484
+ si = Interactive(series)
485
+
486
+ si = (si + 2).head(2)
487
+
488
+ assert isinstance(si, Interactive)
489
+ assert isinstance(si._current, pd.DataFrame)
490
+ pd.testing.assert_series_equal(si._current.A, (series + 2).head(2))
491
+ assert si._obj is series
492
+ assert repr(si._transform) == "(dim('*').pd+2).head(2)"
493
+ assert si._depth == 5
494
+ assert si._method is None
495
+
496
+ assert clone_spy.count == 5
497
+
498
+ # _clone(copy=True) in _resolve_accessor in _apply_operator
499
+ assert clone_spy.calls[0].depth == 1
500
+ assert not clone_spy.calls[0].args
501
+ assert clone_spy.calls[0].kwargs == {'copy': True}
502
+
503
+ # _clone in _apply_operator
504
+ assert clone_spy.calls[1].depth == 2
505
+ assert len(clone_spy.calls[1].args) == 1
506
+ assert repr(clone_spy.calls[1].args[0]) == "dim('*').pd+2"
507
+ assert not clone_spy.calls[1].kwargs
508
+
509
+ # _clone in _resolve_accessor in __getattribute__(name='head')
510
+ assert clone_spy.calls[2].depth == 3
511
+ assert not clone_spy.calls[2].args
512
+ assert clone_spy.calls[2].kwargs == {'copy': True}
513
+
514
+ # 1st _clone(copy=True) in __call__
515
+ assert clone_spy.calls[3].depth == 4
516
+ assert not clone_spy.calls[3].args
517
+ assert clone_spy.calls[3].kwargs == {'copy': True}
518
+
519
+ # 2nd _clone in __call__
520
+ assert clone_spy.calls[4].depth == 5
521
+ assert len(clone_spy.calls[4].args) == 1
522
+ assert repr(clone_spy.calls[4].args[0]) == "(dim('*').pd+2).head(2)"
523
+ assert clone_spy.calls[4].kwargs == {'plot': False}
524
+
525
+
526
+ def test_interactive_pandas_series_operator_widget(series):
527
+ w = pn.widgets.FloatSlider(value=2.0, start=1.0, end=5.0)
528
+
529
+ si = Interactive(series)
530
+
531
+ si = si + w
532
+
533
+ assert isinstance(si, Interactive)
534
+ assert isinstance(si._current, pd.DataFrame)
535
+ pd.testing.assert_series_equal(si._current.A, series + w.value)
536
+ assert si._obj is series
537
+ assert repr(si._transform) == "dim('*').pd+FloatSlider(end=5.0, start=1.0, value=2.0)"
538
+ assert si._depth == 2
539
+ assert si._method is None
540
+
541
+ assert len(si._params) == 1
542
+ assert si._params[0] is w.param.value
543
+
544
+
545
+ def test_interactive_pandas_series_method_widget(series):
546
+ w = pn.widgets.IntSlider(value=2, start=1, end=5)
547
+
548
+ si = Interactive(series)
549
+
550
+ si = si.head(w)
551
+
552
+ assert isinstance(si, Interactive)
553
+ assert isinstance(si._current, pd.DataFrame)
554
+ pd.testing.assert_series_equal(si._current.A, series.head(w.value))
555
+ assert si._obj is series
556
+ assert repr(si._transform) == "dim('*').pd.head(IntSlider(end=5, start=1, value=2))"
557
+ assert si._depth == 3
558
+ assert si._method is None
559
+
560
+ assert len(si._params) == 1
561
+ assert si._params[0] is w.param.value
562
+
563
+
564
+ def test_interactive_pandas_series_operator_and_method_widget(series):
565
+ w1 = pn.widgets.FloatSlider(value=2.0, start=1.0, end=5.0)
566
+ w2 = pn.widgets.IntSlider(value=2, start=1, end=5)
567
+
568
+ si = Interactive(series)
569
+
570
+ si = (si + w1).head(w2)
571
+
572
+ assert isinstance(si, Interactive)
573
+ assert isinstance(si._current, pd.DataFrame)
574
+ pd.testing.assert_series_equal(si._current.A, (series + w1.value).head(w2.value))
575
+ assert si._obj is series
576
+ assert (
577
+ repr(si._transform)
578
+ == "(dim('*').pd+FloatSlider(end=5.0, start=1.0, value=2.0)).head(IntSlider(end=5, start=1, value=2))"
579
+ )
580
+ assert si._depth == 5
581
+ assert si._method is None
582
+
583
+ assert len(si._params) == 2
584
+ assert si._params[0] is w1.param.value
585
+ assert si._params[1] is w2.param.value
586
+
587
+
588
+ def test_interactive_pandas_series_operator_ipywidgets(series):
589
+ ipywidgets = pytest.importorskip('ipywidgets')
590
+
591
+ w = ipywidgets.FloatSlider(value=2.0, min=1.0, max=5.0)
592
+
593
+ si = Interactive(series)
594
+
595
+ si = si + w
596
+
597
+ assert isinstance(si, Interactive)
598
+ assert isinstance(si._current, pd.DataFrame)
599
+ pd.testing.assert_series_equal(si._current.A, series + w.value)
600
+ assert si._obj is series
601
+ assert repr(si._transform) == "dim('*').pd+FloatSlider(value=2.0, max=5.0, min=1.0)"
602
+ assert si._depth == 2
603
+ assert si._method is None
604
+
605
+ # TODO: Isn't that a bug?
606
+ assert len(si._params) == 0
607
+
608
+ widgets = si.widgets()
609
+
610
+ assert isinstance(widgets, pn.Column)
611
+ assert len(widgets) == 1
612
+ assert isinstance(widgets[0], pn.pane.IPyWidget)
613
+ assert widgets[0].object is w
614
+
615
+
616
+ def test_interactive_pandas_series_operator_out_widgets(series):
617
+ w = pn.widgets.FloatSlider(value=2.0, start=1.0, end=5.0)
618
+ si = Interactive(series)
619
+ si = si + w
620
+
621
+ widgets = si.widgets()
622
+
623
+ assert isinstance(widgets, pn.Column)
624
+ assert len(widgets) == 1
625
+ assert widgets[0] is w
626
+
627
+
628
+ def test_interactive_pandas_series_method_out_widgets(series):
629
+ w = pn.widgets.IntSlider(value=2, start=1, end=5)
630
+ si = Interactive(series)
631
+ si = si.head(w)
632
+
633
+ widgets = si.widgets()
634
+
635
+ assert isinstance(widgets, pn.Column)
636
+ assert len(widgets) == 1
637
+ assert widgets[0] is w
638
+
639
+
640
+ def test_interactive_pandas_series_operator_and_method_out_widgets(series):
641
+ w1 = pn.widgets.FloatSlider(value=2.0, start=1.0, end=5.0)
642
+ w2 = pn.widgets.IntSlider(value=2, start=1, end=5)
643
+ si = Interactive(series)
644
+
645
+ si = (si + w1).head(w2)
646
+
647
+ widgets = si.widgets()
648
+
649
+ assert isinstance(widgets, pn.Column)
650
+ assert len(widgets) == 2
651
+ assert widgets[0] is w1
652
+ assert widgets[1] is w2
653
+
654
+
655
+ def test_interactive_pandas_frame_bind_out_widgets(df):
656
+ select = pn.widgets.Select(options=list(df.columns))
657
+
658
+ def sel_col(col):
659
+ return df[col]
660
+
661
+ dfi = Interactive(bind(sel_col, select))
662
+
663
+ widgets = dfi.widgets()
664
+
665
+ assert isinstance(widgets, pn.Column)
666
+ assert len(widgets) == 1
667
+ assert widgets[0] is select
668
+
669
+
670
+ def test_interactive_pandas_frame_bind_operator_out_widgets(df):
671
+ select = pn.widgets.Select(value='A', options=list(df.columns))
672
+
673
+ def sel_col(col):
674
+ return df[col]
675
+
676
+ dfi = Interactive(bind(sel_col, select))
677
+
678
+ w = pn.widgets.FloatSlider(value=2.0, start=1.0, end=5.0)
679
+ dfi = dfi + w
680
+
681
+ widgets = dfi.widgets()
682
+
683
+ assert isinstance(widgets, pn.Column)
684
+ assert len(widgets) == 2
685
+ assert widgets[0] is select
686
+ assert widgets[1] is w
687
+
688
+
689
+ def test_interactive_reevaluate_uses_cached_value(series):
690
+ w = pn.widgets.FloatSlider(value=2.0, start=1.0, end=5.0)
691
+ si = Interactive(series)
692
+ si = si + w
693
+
694
+ w.value = 3.0
695
+ assert repr(si._transform) == "dim('*').pd+FloatSlider(end=5.0, start=1.0, value=3.0)"
696
+
697
+ assert si._callback().object is si._callback().object
698
+
699
+
700
+ def test_interactive_pandas_series_operator_widget_update(series):
701
+ w = pn.widgets.FloatSlider(value=2.0, start=1.0, end=5.0)
702
+ si = Interactive(series)
703
+ si = si + w
704
+
705
+ w.value = 3.0
706
+ assert repr(si._transform) == "dim('*').pd+FloatSlider(end=5.0, start=1.0, value=3.0)"
707
+
708
+ out = si._callback()
709
+ assert out.object is si.eval()
710
+ assert isinstance(out, pn.pane.DataFrame)
711
+ pd.testing.assert_series_equal(out.object.A, series + 3.0)
712
+
713
+
714
+ def test_interactive_pandas_series_method_widget_update(series):
715
+ w = pn.widgets.IntSlider(value=2, start=1, end=5)
716
+ si = Interactive(series)
717
+ si = si.head(w)
718
+
719
+ w.value = 3
720
+ assert repr(si._transform) == "dim('*').pd.head(IntSlider(end=5, start=1, value=3))"
721
+
722
+ out = si._callback()
723
+ assert out.object is si.eval()
724
+ assert isinstance(out, pn.pane.DataFrame)
725
+ pd.testing.assert_series_equal(out.object.A, series.head(3))
726
+
727
+
728
+ def test_interactive_pandas_series_operator_and_method_widget_update(series):
729
+ w1 = pn.widgets.FloatSlider(value=2.0, start=1.0, end=5.0)
730
+ w2 = pn.widgets.IntSlider(value=2, start=1, end=5)
731
+ si = Interactive(series)
732
+ si = (si + w1).head(w2)
733
+
734
+ w1.value = 3.0
735
+ w2.value = 3
736
+
737
+ assert (
738
+ repr(si._transform)
739
+ == "(dim('*').pd+FloatSlider(end=5.0, start=1.0, value=3.0)).head(IntSlider(end=5, start=1, value=3))"
740
+ )
741
+
742
+ out = si._callback()
743
+ assert out.object is si.eval()
744
+ assert isinstance(out, pn.pane.DataFrame)
745
+ pd.testing.assert_series_equal(out.object.A, (series + 3.0).head(3))
746
+
747
+
748
+ def test_interactive_pandas_frame_loc(df):
749
+ dfi = Interactive(df)
750
+
751
+ dfi = dfi.loc[:, 'A']
752
+
753
+ assert isinstance(dfi, Interactive)
754
+
755
+ assert dfi._obj is df
756
+ assert isinstance(dfi._current, pd.Series)
757
+ pd.testing.assert_series_equal(dfi._current, df.loc[:, 'A'])
758
+ assert repr(dfi._transform) == "dim('*').pd.loc, getitem, (slice(None, None, None), 'A')"
759
+ assert dfi._depth == 3
760
+ assert dfi._method is None
761
+
762
+
763
+ def test_interactive_pandas_frame_filtering(df, clone_spy):
764
+ dfi = Interactive(df)
765
+
766
+ dfi = dfi[dfi.A > 1]
767
+
768
+ assert isinstance(dfi, Interactive)
769
+
770
+ assert dfi._obj is df
771
+ assert isinstance(dfi._current, pd.DataFrame)
772
+ pd.testing.assert_frame_equal(dfi._current, df[df.A > 1])
773
+ assert repr(dfi._transform) == "dim('*', getitem, dim('*').pd.A)>1"
774
+ # The depth of that Interactive instance is 2 because the last part of
775
+ # the chain executed, i.e. dfi[], leads to two clones being created,
776
+ # incrementing the _depth up to 2.
777
+ assert dfi._depth == 2
778
+ assert dfi._method is None
779
+
780
+ assert clone_spy.count == 5
781
+
782
+ # _clone(True) in _resolve_accessor in __getattribute__(name='A')
783
+ assert clone_spy.calls[0].depth == 1
784
+ assert not clone_spy.calls[0].args
785
+ assert clone_spy.calls[0].kwargs == {'copy': True}
786
+
787
+ # _clone in _resolve_accessor in _apply_operator(__gt__)
788
+ assert clone_spy.calls[1].depth == 2
789
+ assert len(clone_spy.calls[1].args) == 1
790
+ assert repr(clone_spy.calls[1].args[0]) == "dim('*').pd.A()"
791
+ assert clone_spy.calls[1].kwargs == {'inherit_kwargs': {}}
792
+
793
+ # _clone in _apply_operator(__gt__)
794
+ assert clone_spy.calls[2].depth == 3
795
+ assert len(clone_spy.calls[2].args) == 1
796
+ assert repr(clone_spy.calls[2].args[0]) == "(dim('*').pd.A())>1"
797
+ assert not clone_spy.calls[2].kwargs
798
+
799
+ # _clone(True) in _resolve_accessor in _apply_operator(getitem)
800
+ assert clone_spy.calls[3].depth == 1
801
+ assert not clone_spy.calls[3].args
802
+ assert clone_spy.calls[3].kwargs == {'copy': True}
803
+
804
+ # _clone in _apply_operator(getitem)
805
+ assert clone_spy.calls[4].depth == 2
806
+ assert len(clone_spy.calls[4].args) == 1
807
+ assert repr(clone_spy.calls[4].args[0]) == "dim('*', getitem, (dim('*').pd.A())>1)"
808
+ assert not clone_spy.calls[4].kwargs
809
+
810
+
811
+ def test_interactive_pandas_frame_chained_attrs(df, clone_spy):
812
+ dfi = Interactive(df)
813
+
814
+ dfi = dfi.A.max()
815
+
816
+ assert isinstance(dfi, Interactive)
817
+
818
+ assert dfi._obj is df
819
+ assert isinstance(dfi._current, float)
820
+ assert dfi._current == pytest.approx(df.A.max())
821
+ # This is a weird repr! Bug?
822
+ assert repr(dfi._transform) == "dim('*').pd.A).max("
823
+ assert dfi._depth == 4
824
+ assert dfi._method is None
825
+
826
+ assert clone_spy.count == 4
827
+
828
+ # _clone(True) in _resolve_accessor in __getattribute__(name='A')
829
+ assert clone_spy.calls[0].depth == 1
830
+ assert not clone_spy.calls[0].args
831
+ assert clone_spy.calls[0].kwargs == {'copy': True}
832
+
833
+ # _clone(True) in _resolve_accessor in __getattribute__(name='max')
834
+ assert clone_spy.calls[1].depth == 2
835
+ assert len(clone_spy.calls[1].args) == 1
836
+ assert repr(clone_spy.calls[1].args[0]) == "dim('*').pd.A()"
837
+ assert clone_spy.calls[1].kwargs == {'inherit_kwargs': {}}
838
+
839
+ # 1st _clone(copy=True) in __call__
840
+ assert clone_spy.calls[2].depth == 3
841
+ assert not clone_spy.calls[2].args
842
+ assert clone_spy.calls[2].kwargs == {'copy': True}
843
+
844
+ # 2nd _clone in __call__
845
+ assert clone_spy.calls[3].depth == 4
846
+ assert len(clone_spy.calls[3].args) == 1
847
+ assert repr(clone_spy.calls[3].args[0]) == "(dim('*').pd.A()).max()"
848
+ assert clone_spy.calls[3].kwargs == {'plot': False}
849
+
850
+
851
+ def test_interactive_pandas_out_repr(series):
852
+ si = Interactive(series)
853
+ si = si.max()
854
+
855
+ assert isinstance(si, Interactive)
856
+ assert isinstance(si._current, pd.Series)
857
+ assert si._current.A == pytest.approx(series.max())
858
+ assert si._obj is series
859
+ assert repr(si._transform) == "dim('*').pd.max()"
860
+ # One _clone from _resolve_accessor, two from _clone
861
+ assert si._depth == 3
862
+ assert si._method is None
863
+
864
+ # Equivalent to eval
865
+ out = si._callback()
866
+
867
+ assert isinstance(out, pd.Series)
868
+ assert out.A == pytest.approx(series.max())
869
+
870
+
871
+ def test_interactive_xarray_dataarray_out_repr(dataarray):
872
+ dai = Interactive(dataarray)
873
+
874
+ assert isinstance(dai._current, xr.DataArray)
875
+ assert dai._obj is dataarray
876
+ assert repr(dai._transform) == "dim('air')"
877
+ assert dai._depth == 0
878
+ assert dai._method is None
879
+
880
+ # Equivalent to eval
881
+ out = dai._callback()
882
+
883
+ assert isinstance(out, xr.DataArray)
884
+
885
+
886
+ def test_interactive_pandas_out_frame(series):
887
+ si = Interactive(series)
888
+ si = si.head(2)
889
+
890
+ assert isinstance(si, Interactive)
891
+ assert isinstance(si._current, pd.DataFrame)
892
+ pd.testing.assert_series_equal(si._current.A, series.head(2))
893
+ assert si._obj is series
894
+ assert repr(si._transform) == "dim('*').pd.head(2)"
895
+ assert si._depth == 3
896
+ assert si._method is None
897
+
898
+ # Equivalent to eval
899
+ out = si._callback()
900
+
901
+ assert isinstance(out, pn.pane.DataFrame)
902
+ pd.testing.assert_frame_equal(out.object, si._current)
903
+
904
+
905
+ def test_interactive_pandas_out_frame_max_rows(series):
906
+ si = Interactive(series, max_rows=5)
907
+ si = si.head(2)
908
+
909
+ # Equivalent to eval
910
+ out = si._callback()
911
+
912
+ assert isinstance(out, pn.pane.DataFrame)
913
+ assert out.max_rows == 5
914
+
915
+
916
+ def test_interactive_pandas_out_frame_max_rows_accessor_called(series):
917
+ si = series.interactive(max_rows=5)
918
+ si = si.head(2)
919
+
920
+ # Equivalent to eval
921
+ out = si._callback()
922
+
923
+ assert isinstance(out, pn.pane.DataFrame)
924
+ assert out.max_rows == 5
925
+
926
+
927
+ def test_interactive_pandas_out_frame_kwargs(series):
928
+ si = Interactive(series, width=111)
929
+ si = si.head(2)
930
+
931
+ # Equivalent to eval
932
+ out = si._callback()
933
+
934
+ assert isinstance(out, pn.pane.DataFrame)
935
+ assert out.width == 111
936
+
937
+
938
+ def test_interactive_pandas_out_frame_kwargs_accessor_called(series):
939
+ si = series.interactive(width=111)
940
+ si = si.head(2)
941
+
942
+ # Equivalent to eval
943
+ out = si._callback()
944
+
945
+ assert isinstance(out, pn.pane.DataFrame)
946
+ assert out.width == 111
947
+
948
+
949
+ def test_interactive_pandas_out_frame_attrib(df):
950
+ dfi = Interactive(df)
951
+ dfi = dfi.A
952
+
953
+ assert dfi._method == 'A'
954
+
955
+ # Equivalent to eval
956
+ out = dfi._callback()
957
+
958
+ # In that case the default behavior is to return the object transformed
959
+ # and to which _method is applied
960
+ assert isinstance(out, pd.Series)
961
+ pd.testing.assert_series_equal(df.A, out)
962
+
963
+
964
+ @pytest.mark.parametrize(
965
+ 'op',
966
+ [
967
+ '-', # __neg__
968
+ # Doesn't any of the supported data implement __not__?
969
+ # e.g. `not series` raises an error.
970
+ # 'not', # __not__
971
+ '+', # _pos__
972
+ ],
973
+ )
974
+ def test_interactive_pandas_series_operator_unary(series, op):
975
+ if op == '~':
976
+ series = pd.Series([True, False, True], name='A')
977
+ si = Interactive(series)
978
+ si = eval(f'{op} si')
979
+
980
+ assert isinstance(si, Interactive)
981
+ assert isinstance(si._current, pd.DataFrame)
982
+ pd.testing.assert_series_equal(si._current.A, eval(f'{op} series'))
983
+ assert si._obj is series
984
+ assert repr(si._transform) == f"{op}dim('*')"
985
+ assert si._depth == 2
986
+ assert si._method is None
987
+
988
+
989
+ def test_interactive_pandas_series_operator_unary_invert(): # __invert__
990
+ series = pd.Series([True, False, True], name='A')
991
+ si = Interactive(series)
992
+ si = ~si
993
+
994
+ assert isinstance(si, Interactive)
995
+ assert isinstance(si._current, pd.DataFrame)
996
+ pd.testing.assert_series_equal(si._current.A, ~series)
997
+ assert si._obj is series
998
+ assert repr(si._transform) == "dim('*', inv)"
999
+ assert si._depth == 2
1000
+ assert si._method is None
1001
+
1002
+
1003
+ @pytest.mark.parametrize(
1004
+ 'op',
1005
+ [
1006
+ '+', # __add__
1007
+ '&', # __and__
1008
+ '/', # __div__
1009
+ '==', # __eq__
1010
+ '//', # __floordiv__
1011
+ '>=', # __ge__
1012
+ '>', # __gt__
1013
+ '<=', # __le__
1014
+ '<', # __lt__
1015
+ '<', # __lt__
1016
+ # '<<', # __lshift__
1017
+ '%', # __mod__
1018
+ '*', # __mul__
1019
+ '!=', # __ne__
1020
+ '|', # __or__
1021
+ # '>>', # __rshift__
1022
+ '**', # __pow__
1023
+ '-', # __sub__
1024
+ '/', # __truediv__
1025
+ ],
1026
+ )
1027
+ def test_interactive_pandas_series_operator_binary(series, op):
1028
+ if op in ['&', '|']:
1029
+ series = pd.Series([True, False, True], name='A')
1030
+ val = True
1031
+ else:
1032
+ val = 2.0
1033
+ si = Interactive(series)
1034
+ si = eval(f'si {op} {val}')
1035
+
1036
+ assert isinstance(si, Interactive)
1037
+ assert isinstance(si._current, pd.DataFrame)
1038
+ pd.testing.assert_series_equal(si._current.A, eval(f'series {op} {val}'))
1039
+ assert si._obj is series
1040
+ val_repr = '2.0' if isinstance(val, float) else 'True'
1041
+ assert repr(si._transform) == f"dim('*').pd{op}{val_repr}"
1042
+ assert si._depth == 2
1043
+ assert si._method is None
1044
+
1045
+
1046
+ @pytest.mark.parametrize(
1047
+ 'op',
1048
+ [
1049
+ '+', # __radd__
1050
+ '&', # __rand__
1051
+ '/', # __rdiv__
1052
+ '//', # __rfloordiv__
1053
+ # '<<', # __rlshift__
1054
+ '%', # __rmod__
1055
+ '*', # __rmul__
1056
+ '|', # __ror__
1057
+ '**', # __rpow__
1058
+ # '>>', # __rshift__
1059
+ '-', # __rsub__
1060
+ '/', # __rtruediv__
1061
+ ],
1062
+ )
1063
+ def test_interactive_pandas_series_operator_reverse_binary(op):
1064
+ if op in ['&', '|']:
1065
+ series = pd.Series([True, False, True], name='A')
1066
+ val = True
1067
+ else:
1068
+ series = pd.Series([1.0, 2.0, 3.0], name='A')
1069
+ val = 2.0
1070
+ si = Interactive(series)
1071
+ si = eval(f'{val} {op} si')
1072
+
1073
+ assert isinstance(si, Interactive)
1074
+ assert isinstance(si._current, pd.DataFrame)
1075
+ pd.testing.assert_series_equal(si._current.A, eval(f'{val} {op} series'))
1076
+ assert si._obj is series
1077
+ val_repr = '2.0' if isinstance(val, float) else 'True'
1078
+ assert repr(si._transform) == f"{val_repr}{op}dim('*')"
1079
+ assert si._depth == 2
1080
+ assert si._method is None
1081
+
1082
+
1083
+ def test_interactive_pandas_series_operator_abs(series):
1084
+ si = Interactive(series)
1085
+ si = abs(si)
1086
+
1087
+ assert isinstance(si, Interactive)
1088
+ assert isinstance(si._current, pd.DataFrame)
1089
+ pd.testing.assert_series_equal(si._current.A, abs(series))
1090
+ assert si._obj is series
1091
+ assert repr(si._transform) == "absdim('*')"
1092
+ assert si._depth == 2
1093
+ assert si._method is None
1094
+
1095
+
1096
+ def test_interactive_pandas_series_operator_round(series):
1097
+ si = Interactive(series)
1098
+ si = round(si)
1099
+
1100
+ assert isinstance(si, Interactive)
1101
+ assert isinstance(si._current, pd.DataFrame)
1102
+ pd.testing.assert_series_equal(si._current.A, round(series))
1103
+ assert si._obj is series
1104
+ assert repr(si._transform) == "dim('*', round)"
1105
+ assert si._depth == 2
1106
+ assert si._method is None
1107
+
1108
+
1109
+ def test_interactive_pandas_series_plot(series, clone_spy):
1110
+ si = Interactive(series)
1111
+
1112
+ si = si.plot()
1113
+
1114
+ assert isinstance(si, Interactive)
1115
+ assert isinstance(si._current, matplotlib.axes.Axes)
1116
+ assert si._obj is series
1117
+ assert "dim('*').pd.plot(ax=<function Interactive._get_ax_fn.<locals>.get_ax" in repr(
1118
+ si._transform
1119
+ )
1120
+ assert si._depth == 3
1121
+ assert si._method is None
1122
+
1123
+ assert clone_spy.count == 3
1124
+
1125
+ # _clone(True) in _resolve_accessor in __getattribute__(name='plot')
1126
+ assert clone_spy.calls[0].depth == 1
1127
+ assert not clone_spy.calls[0].args
1128
+ assert clone_spy.calls[0].kwargs == {'copy': True}
1129
+
1130
+ # 1st _clone(copy=True) in __call__
1131
+ assert clone_spy.calls[1].depth == 2
1132
+ assert not clone_spy.calls[1].args
1133
+ assert clone_spy.calls[1].kwargs == {'copy': True}
1134
+
1135
+ # 2nd _clone in __call__
1136
+ assert clone_spy.calls[2].depth == 3
1137
+ assert len(clone_spy.calls[2].args) == 1
1138
+ # Not the complete repr as the function doesn't have a nice repr,
1139
+ # its repr displays the memory address.
1140
+ assert "dim('*').pd.plot(ax=<function Interactive._get_ax_fn.<locals>.get_ax" in repr(
1141
+ clone_spy.calls[2].args[0]
1142
+ )
1143
+ assert clone_spy.calls[2].kwargs == {'plot': True}
1144
+
1145
+ assert not si._dmap
1146
+ assert isinstance(si._fig, matplotlib.figure.Figure)
1147
+
1148
+ # Just test that it doesn't raise any error.
1149
+ si.output()
1150
+
1151
+
1152
+ def test_interactive_pandas_series_plot_kind_attr(series, clone_spy):
1153
+ # TODO: Not checking the dim reprs in this test as that was
1154
+ # affecting the pipeline.
1155
+ si = Interactive(series)
1156
+
1157
+ si = si.plot.line()
1158
+
1159
+ assert isinstance(si, Interactive)
1160
+ assert isinstance(si._current, matplotlib.axes.Axes)
1161
+ assert si._obj is series
1162
+ # assert "dim('*').pd.plot).line(ax=<function Interactive._get_ax_fn.<locals>.get_ax" in repr(si._transform)
1163
+ assert si._depth == 4
1164
+ assert si._method is None
1165
+
1166
+ assert clone_spy.count == 4
1167
+
1168
+ # _clone(True) in _resolve_accessor in __getattribute__(name='plot')
1169
+ assert clone_spy.calls[0].depth == 1
1170
+ assert not clone_spy.calls[0].args
1171
+ assert clone_spy.calls[0].kwargs == {'copy': True}
1172
+
1173
+ # _clone in _resolve_accessor in __getattribute__(name='line')
1174
+ assert clone_spy.calls[1].depth == 2
1175
+ assert len(clone_spy.calls[1].args) == 1
1176
+ # assert repr(clone_spy.calls[1].args[0]) == "dim('*').pd.plot()"
1177
+ assert len(clone_spy.calls[1].kwargs) == 1
1178
+ assert 'inherit_kwargs' in clone_spy.calls[1].kwargs
1179
+ assert 'ax' in clone_spy.calls[1].kwargs['inherit_kwargs']
1180
+
1181
+ # 1st _clone(copy=True) in __call__
1182
+ assert clone_spy.calls[2].depth == 3
1183
+ assert not clone_spy.calls[2].args
1184
+ assert clone_spy.calls[2].kwargs == {'copy': True}
1185
+
1186
+ # 2nd _clone in __call__
1187
+ assert clone_spy.calls[3].depth == 4
1188
+ assert len(clone_spy.calls[3].args) == 1
1189
+ # assert "(dim('*').pd.plot()).line(ax=<function Interactive._get_ax_fn.<locals>.get_ax" in repr(clone_spy.calls[3].args[0])
1190
+ # TODO: Looks like a bug, 'plot' should be True?
1191
+ assert clone_spy.calls[3].kwargs == {'plot': False}
1192
+
1193
+ assert not si._dmap
1194
+ assert isinstance(si._fig, matplotlib.figure.Figure)
1195
+
1196
+ # Just test that it doesn't raise any error.
1197
+ si.output()
1198
+
1199
+
1200
+ def test_interactive_pandas_dir_no_type_change(df):
1201
+ dfi = Interactive(df)
1202
+ dfi = dfi.head()
1203
+
1204
+ attrs = dir(dfi)
1205
+
1206
+ assert all(col in attrs for col in list(df.columns))
1207
+ assert 'describe' in attrs
1208
+
1209
+
1210
+ def test_interactive_pandas_dir_with_type_change(df):
1211
+ dfi = Interactive(df)
1212
+ dfi = dfi.head().A.head()
1213
+
1214
+ attrs = dir(dfi)
1215
+
1216
+ assert not any(col in attrs for col in list(df.columns))
1217
+ assert 'T' in attrs
1218
+
1219
+
1220
+ @pytest.mark.xfail(
1221
+ reason='hvplot.util.check_library expects the obj to have __module__, which is not true for a float'
1222
+ )
1223
+ def test_interactive_pandas_dir_with_type_change_to_float(df):
1224
+ dfi = Interactive(df)
1225
+ dfi = dfi.head().A.max()
1226
+
1227
+ attrs = dir(dfi)
1228
+
1229
+ assert not any(col in attrs for col in list(df.columns))
1230
+ assert 'describe' not in attrs
1231
+ assert 'real' not in attrs
1232
+
1233
+
1234
+ def test_interactive_pandas_dir_attrib(df):
1235
+ dfi = Interactive(df)
1236
+ dfi = dfi.head().A
1237
+
1238
+ attrs = dir(dfi)
1239
+
1240
+ assert not any(col in attrs for col in list(df.columns))
1241
+ assert 'T' in attrs
1242
+
1243
+
1244
+ def test_interactive_pandas_layout_default_no_widgets(df):
1245
+ dfi = Interactive(df)
1246
+ dfi = dfi.head()
1247
+
1248
+ assert dfi._center is False
1249
+ assert dfi._loc == 'top_left'
1250
+
1251
+ layout = dfi.layout()
1252
+
1253
+ assert isinstance(layout, pn.Row)
1254
+ assert len(layout) == 1
1255
+
1256
+
1257
+ def test_interactive_pandas_layout_default_no_widgets_kwargs(df):
1258
+ dfi = Interactive(df)
1259
+ dfi = dfi.head()
1260
+
1261
+ layout = dfi.layout(width=200)
1262
+
1263
+ assert isinstance(layout, pn.Row)
1264
+ assert layout.width == 200
1265
+
1266
+
1267
+ @is_bokeh2
1268
+ def test_interactive_pandas_layout_default_with_widgets(df):
1269
+ w = pn.widgets.IntSlider(value=2, start=1, end=5)
1270
+ dfi = Interactive(df)
1271
+ dfi = dfi.head(w)
1272
+
1273
+ assert dfi._center is False
1274
+ assert dfi._loc == 'top_left'
1275
+
1276
+ layout = dfi.layout()
1277
+
1278
+ assert isinstance(layout, pn.Row)
1279
+ assert len(layout) == 1
1280
+ assert isinstance(layout[0], pn.Column)
1281
+ assert len(layout[0]) == 2
1282
+ assert isinstance(layout[0][0], pn.Row)
1283
+ assert isinstance(layout[0][1], pn.pane.PaneBase)
1284
+ assert len(layout[0][0]) == 2
1285
+ assert isinstance(layout[0][0][0], pn.Column)
1286
+ assert len(layout[0][0][0]) == 1
1287
+ assert isinstance(layout[0][0][0][0], pn.widgets.Widget)
1288
+ assert isinstance(layout[0][0][1], pn.layout.HSpacer)
1289
+
1290
+
1291
+ @is_bokeh3
1292
+ def test_interactive_pandas_layout_default_with_widgets_bk3(df):
1293
+ w = pn.widgets.IntSlider(value=2, start=1, end=5)
1294
+ dfi = Interactive(df)
1295
+ dfi = dfi.head(w)
1296
+
1297
+ assert dfi._center is False
1298
+ assert dfi._loc == 'top_left'
1299
+
1300
+ layout = dfi.layout()
1301
+
1302
+ assert isinstance(layout, pn.Row)
1303
+ assert len(layout) == 1
1304
+ assert isinstance(layout[0], pn.Column)
1305
+ assert len(layout[0]) == 2
1306
+ assert isinstance(layout[0][0], pn.Column)
1307
+ assert isinstance(layout[0][1], pn.pane.PaneBase)
1308
+ assert len(layout[0][0]) == 1
1309
+ assert isinstance(layout[0][0][0], pn.widgets.IntSlider)
1310
+
1311
+
1312
+ @is_bokeh2
1313
+ def test_interactive_pandas_layout_center_with_widgets(df):
1314
+ w = pn.widgets.IntSlider(value=2, start=1, end=5)
1315
+ dfi = df.interactive(center=True)
1316
+ dfi = dfi.head(w)
1317
+
1318
+ assert dfi._center is True
1319
+ assert dfi._loc == 'top_left'
1320
+
1321
+ layout = dfi.layout()
1322
+
1323
+ assert isinstance(layout, pn.Row)
1324
+ assert len(layout) == 3
1325
+ assert isinstance(layout[0], pn.layout.HSpacer)
1326
+ assert isinstance(layout[1], pn.Column)
1327
+ assert isinstance(layout[2], pn.layout.HSpacer)
1328
+ assert len(layout[1]) == 2
1329
+ assert isinstance(layout[1][0], pn.Row)
1330
+ assert isinstance(layout[1][1], pn.Row)
1331
+ assert len(layout[1][0]) == 2
1332
+ assert len(layout[1][1]) == 3
1333
+ assert isinstance(layout[1][0][0], pn.Column)
1334
+ assert len(layout[1][0][0]) == 1
1335
+ assert isinstance(layout[1][0][0][0], pn.widgets.Widget)
1336
+ assert isinstance(layout[1][1][0], pn.layout.HSpacer)
1337
+ assert isinstance(layout[1][1][1], pn.pane.PaneBase)
1338
+ assert isinstance(layout[1][1][2], pn.layout.HSpacer)
1339
+
1340
+
1341
+ @is_bokeh2
1342
+ def test_interactive_pandas_layout_loc_with_widgets(df):
1343
+ w = pn.widgets.IntSlider(value=2, start=1, end=5)
1344
+ dfi = df.interactive(loc='top_right')
1345
+ dfi = dfi.head(w)
1346
+
1347
+ assert dfi._center is False
1348
+ assert dfi._loc == 'top_right'
1349
+
1350
+ layout = dfi.layout()
1351
+
1352
+ assert isinstance(layout, pn.Row)
1353
+ assert len(layout) == 1
1354
+ assert isinstance(layout[0], pn.Column)
1355
+ assert len(layout[0]) == 2
1356
+ assert isinstance(layout[0][0], pn.Row)
1357
+ assert isinstance(layout[0][1], pn.pane.PaneBase)
1358
+ assert len(layout[0][0]) == 2
1359
+ assert isinstance(layout[0][0][0], pn.layout.HSpacer)
1360
+ assert isinstance(layout[0][0][1], pn.Column)
1361
+ assert len(layout[0][0][1]) == 1
1362
+ assert isinstance(layout[0][0][1][0], pn.widgets.Widget)
1363
+
1364
+
1365
+ def test_interactive_pandas_eval(df):
1366
+ dfi = Interactive(df)
1367
+ dfi = dfi.head(2)
1368
+
1369
+ pd.testing.assert_frame_equal(dfi.eval(), df.head(2))
1370
+
1371
+
1372
+ def test_interactive_pandas_eval_attrib(df):
1373
+ dfi = Interactive(df)
1374
+ dfi = dfi.head(2).A
1375
+
1376
+ pd.testing.assert_series_equal(dfi.eval(), df.head(2).A)
1377
+
1378
+
1379
+ def test_interactive_pandas_eval_hvplot(df):
1380
+ dfi = Interactive(df)
1381
+ dfi = dfi.head(2).hvplot(y='A')
1382
+
1383
+ evaled = dfi.eval()
1384
+
1385
+ assert isinstance(evaled, hv.Curve)
1386
+
1387
+
1388
+ def test_interactive_pandas_series_widget_value(series):
1389
+ w = pn.widgets.FloatSlider(value=2.0, start=1.0, end=5.0)
1390
+
1391
+ si = Interactive(series)
1392
+
1393
+ si = si + w.param.value
1394
+
1395
+ assert isinstance(si, Interactive)
1396
+ assert isinstance(si._current, pd.DataFrame)
1397
+ pd.testing.assert_series_equal(si._current.A, series + w.value)
1398
+ assert si._obj is series
1399
+ if param2:
1400
+ assert "dim('*').pd+<param.parameters.Number object" in repr(si._transform)
1401
+ else:
1402
+ assert "dim('*').pd+<param.Number object" in repr(si._transform)
1403
+ assert si._depth == 2
1404
+ assert si._method is None
1405
+
1406
+ assert len(si._params) == 1
1407
+ assert si._params[0] is w.param.value
1408
+
1409
+ widgets = si.widgets()
1410
+
1411
+ assert isinstance(widgets, pn.Column)
1412
+ assert len(widgets) == 1
1413
+ assert widgets[0] is w
1414
+
1415
+
1416
+ def test_clones_dont_reexecute_transforms():
1417
+ # Fixes https://github.com/holoviz/hvplot/issues/832
1418
+ df = pd.DataFrame()
1419
+ msgs = []
1420
+
1421
+ def piped(df, msg):
1422
+ msgs.append(msg)
1423
+ return df
1424
+
1425
+ df.interactive.pipe(piped, msg='1').pipe(piped, msg='2')
1426
+
1427
+ assert len(msgs) == 3
1428
+
1429
+
1430
+ def test_interactive_accept_non_str_columnar_data():
1431
+ df = pd.DataFrame(np.random.random((10, 2)))
1432
+ assert all(not isinstance(col, str) for col in df.columns)
1433
+ dfi = Interactive(df)
1434
+
1435
+ w = pn.widgets.FloatSlider(start=0, end=1, step=0.05)
1436
+
1437
+ # Column names converted as string so can no longer use dfi[1]
1438
+ dfi = dfi['1'] + w.param.value
1439
+
1440
+ w.value = 0.5
1441
+
1442
+ pytest.approx(dfi.eval().sum(), (df[1] + 0.5).sum())