Description
(Title to update when the root cause is identified...)
When attempting to modernize the IEX Trading example on examples.holoviz.org (holoviz-topics/examples#398) we encountered the error I report below. Sorry for the long code to reproduce the bug...
import datetime
import pandas as pd
import holoviews as hv
import datashader as ds
from holoviews.operation.datashader import spikes_aggregate
hv.config.image_rtol = 10e-3 # Suppresses datetime issue at high zoom level
hv.extension('bokeh')
df = pd.read_csv('/Users/mliquet/dev/examples/iex_trading/data/IEX_2019-10-21.csv')
# df = pd.read_csv('https://s3.amazonaws.com/datashader-data/IEX_2019-10-21.csv')
df['timestamp'] = df['timestamp'].astype('datetime64[ns]')
df['timestamp'] -= datetime.timedelta(hours=4)
symbol_volumes = df.groupby('symbol')['size'].sum()
top_symbol_volumes = symbol_volumes.sort_values()[-10:]
top_symbols = ', '.join(list(top_symbol_volumes.index))
top_volume_percent = top_symbol_volumes.sum() / symbol_volumes.sum() * 100
symbol_info = {
"PInterest":'PINS',
'Chesapeake Energy Corporation': 'CHK',
"Snap Inc": 'SNAP',
"NIO Inc": 'NIO',
"McDermott International": 'MDR',
"Teva Pharmaceutical Industries": 'TEVA',
"Hewlett Packard Enterprise":'HPE',
"Bank of America": 'BAC',
"GE": 'General Electric',
"Infosys":'INFY',
}
spikes = hv.Spikes(df, ['timestamp'], ['symbol', 'size', 'price'])
raster_opts = hv.opts.Image(
min_height=400, responsive=True, colorbar=True,
cmap='blues', xrotation=90, default_tools=['xwheel_zoom', 'xpan', 'xbox_zoom']
)
range_stream = hv.streams.RangeX()
def xrange_filter(spikes, x_range):
low, high = (None, None) if x_range is None else x_range
ranged = spikes[pd.to_datetime(low):pd.to_datetime(high)]
total_displayed = len(ranged)
if total_displayed >= 200:
ranged = ranged.iloc[:0]
return ranged.opts(spike_length=1, alpha=0)
def visualize_symbol(symbol, offset):
selection = spikes.select(symbol=symbol)
range_stream.source = selection
rasterized = spikes_aggregate(selection, streams=[range_stream],
offset=offset, expand=False,
aggregator=ds.sum('size'),
spike_length=1).opts(raster_opts)
filtered = selection.apply(xrange_filter, streams=[range_stream])
return rasterized * filtered.opts(tools=['hover'], position=offset)
overlay = visualize_symbol('PINS', 0) * visualize_symbol('CHK', 1)
hv.render(overlay)
print(range_stream.x_range)
Output (displayed, but with the traceback too...):
Traceback:
WARNING:param.dynamic_operation: Callable raised "UFuncTypeError(<ufunc 'less_equal'>, (<class 'numpy.dtypes.DateTime64DType'>, <class 'numpy.dtypes._PyFloatDType'>, None))".
Invoked as dynamic_operation(x_range=(np.float64(nan), np.float64(nan)))
WARNING:param.dynamic_operation: Callable raised "UFuncTypeError(<ufunc 'less_equal'>, (<class 'numpy.dtypes.DateTime64DType'>, <class 'numpy.dtypes._PyFloatDType'>, None))".
Invoked as dynamic_operation(x_range=(np.float64(nan), np.float64(nan)))
numpy.exceptions.DTypePromotionError: The DType <class 'numpy.dtypes.DateTime64DType'> could not be promoted by <class 'numpy.dtypes._PyFloatDType'>. This means that no common DType exists for the given inputs. For example they cannot be stored in a single array unless the dtype is `object`. The full list of DTypes is: (<class 'numpy.dtypes.DateTime64DType'>, <class 'numpy.dtypes._PyFloatDType'>)
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/Users/mliquet/dev/holoviews/holoviews/plotting/util.py", line 300, in get_plot_frame
return map_obj[key]
File "/Users/mliquet/dev/holoviews/holoviews/core/spaces.py", line 1216, in __getitem__
val = self._execute_callback(*tuple_key)
File "/Users/mliquet/dev/holoviews/holoviews/core/spaces.py", line 983, in _execute_callback
retval = self.callback(*args, **kwargs)
File "/Users/mliquet/dev/holoviews/holoviews/core/spaces.py", line 552, in __call__
return self.callable()
File "/Users/mliquet/dev/holoviews/holoviews/util/__init__.py", line 1052, in dynamic_operation
key, obj = resolve(key, kwargs)
File "/Users/mliquet/dev/holoviews/holoviews/util/__init__.py", line 1041, in resolve
return key, map_obj[key]
File "/Users/mliquet/dev/holoviews/holoviews/core/spaces.py", line 1216, in __getitem__
val = self._execute_callback(*tuple_key)
File "/Users/mliquet/dev/holoviews/holoviews/core/spaces.py", line 983, in _execute_callback
retval = self.callback(*args, **kwargs)
File "/Users/mliquet/dev/holoviews/holoviews/core/spaces.py", line 552, in __call__
return self.callable()
File "/Users/mliquet/dev/holoviews/holoviews/core/spaces.py", line 197, in dynamic_mul
self_el = self.select(HoloMap, **key_map) if self.kdims else self[()]
File "/Users/mliquet/dev/holoviews/holoviews/core/spaces.py", line 1216, in __getitem__
val = self._execute_callback(*tuple_key)
File "/Users/mliquet/dev/holoviews/holoviews/core/spaces.py", line 983, in _execute_callback
retval = self.callback(*args, **kwargs)
File "/Users/mliquet/dev/holoviews/holoviews/core/spaces.py", line 552, in __call__
return self.callable()
File "/Users/mliquet/dev/holoviews/holoviews/core/spaces.py", line 197, in dynamic_mul
self_el = self.select(HoloMap, **key_map) if self.kdims else self[()]
File "/Users/mliquet/dev/holoviews/holoviews/core/spaces.py", line 1216, in __getitem__
val = self._execute_callback(*tuple_key)
File "/Users/mliquet/dev/holoviews/holoviews/core/spaces.py", line 983, in _execute_callback
retval = self.callback(*args, **kwargs)
File "/Users/mliquet/dev/holoviews/holoviews/core/spaces.py", line 581, in __call__
ret = self.callable(*args, **kwargs)
File "/Users/mliquet/dev/holoviews/holoviews/util/__init__.py", line 1052, in dynamic_operation
key, obj = resolve(key, kwargs)
File "/Users/mliquet/dev/holoviews/holoviews/util/__init__.py", line 1041, in resolve
return key, map_obj[key]
File "/Users/mliquet/dev/holoviews/holoviews/core/spaces.py", line 1216, in __getitem__
val = self._execute_callback(*tuple_key)
File "/Users/mliquet/dev/holoviews/holoviews/core/spaces.py", line 983, in _execute_callback
retval = self.callback(*args, **kwargs)
File "/Users/mliquet/dev/holoviews/holoviews/core/spaces.py", line 581, in __call__
ret = self.callable(*args, **kwargs)
File "/Users/mliquet/dev/holoviews/holoviews/util/__init__.py", line 1053, in dynamic_operation
return apply(obj, *key, **kwargs)
File "/Users/mliquet/dev/holoviews/holoviews/util/__init__.py", line 1045, in apply
processed = self._process(element, key, kwargs)
File "/Users/mliquet/dev/holoviews/holoviews/util/__init__.py", line 1027, in _process
return self.p.operation.process_element(element, key, **kwargs)
File "/Users/mliquet/dev/holoviews/holoviews/core/operation.py", line 192, in process_element
return self._apply(element, key)
File "/Users/mliquet/dev/holoviews/holoviews/core/operation.py", line 141, in _apply
ret = self._process(element, key)
File "/Users/mliquet/dev/holoviews/holoviews/operation/datashader.py", line 694, in _process
info = self._get_sampling(element, x, y, ndim=1, default=default)
File "/Users/mliquet/dev/holoviews/holoviews/operation/resample.py", line 142, in _get_sampling
x_range = (np.nanmin([np.nanmax([x0, ex0]), ex1]),
File "/Users/mliquet/dev/holoviews/.pixi/envs/test-39/lib/python3.9/site-packages/numpy/lib/_nanfunctions_impl.py", line 500, in nanmax
res = np.amax(a, axis=axis, out=out, **kwargs)
File "/Users/mliquet/dev/holoviews/.pixi/envs/test-39/lib/python3.9/site-packages/numpy/_core/fromnumeric.py", line 2916, in amax
return _wrapreduction(a, np.maximum, 'max', axis, None, out,
File "/Users/mliquet/dev/holoviews/.pixi/envs/test-39/lib/python3.9/site-packages/numpy/_core/fromnumeric.py", line 86, in _wrapreduction
return ufunc.reduce(obj, axis, dtype, out, **passkwargs)
numpy._core._exceptions._UFuncNoLoopError: ufunc 'less_equal' did not contain a loop with signature matching types (<class 'numpy.dtypes.DateTime64DType'>, <class 'numpy.dtypes._PyFloatDType'>) -> None
I managed to avoid when making this change, as self.p.x_range
is equal to (np.nan, np.nan)
as this stage and causes the error.
diff --git a/holoviews/operation/resample.py b/holoviews/operation/resample.py
index bb482a79c..b858ae49e 100644
--- a/holoviews/operation/resample.py
+++ b/holoviews/operation/resample.py
@@ -130,7 +130,7 @@ class ResampleOperation2D(ResampleOperation1D):
else:
if x is None:
x_range = self.p.x_range or (-0.5, 0.5)
- elif self.p.expand or not self.p.x_range:
+ elif self.p.expand or (self.p.x_range is None or all(not isfinite(v) for v in self.p.x_range)):
if self.p.x_range and all(isfinite(v) for v in self.p.x_range):
x_range = self.p.x_range
else:
I was wondering why this changed. When looking into it, I saw that spikes_aggregate._process
is called 4 times, while I'd expect it to be only called 2 times. Running the same snippet in an older environment (python 3.6, holoviews 1.13.0), I see it is only called 2 times. I also noticed that, after rendering, range_stream.x_range
is (np.nan, np.nan)
in the newer code (with the change applied), while it was None
before. Printing in Stream.update
and in spikes_aggregate._process
, I can see the stream's x_range
is updated a bunch of times, from GenericElementPlot.get_extents()
with (np.nan, np.nan)
after spikes_aggregate._process
is called the first 2 times. Things were much different in the old code, with the first four lines only being printed.
Stream update 5179861584 {'x_range': None}
Stream update 5179861584 {'x_range': None}
SKIPES AGGREGATE
SKIPES AGGREGATE
Stream update 5179861584 {'x_range': (nan, nan)}
Stream update 5179861584 {'x_range': (nan, nan)}
Stream update 5179861584 {'x_range': (numpy.datetime64('2019-10-21T09:30:09.432334336'), numpy.datetime64('2019-10-21T15:59:58.745244160'))}
Stream update 5179861584 {'x_range': (nan, nan)}
Stream update 5179861584 {'x_range': (nan, nan)}
Stream update 5179861584 {'x_range': (nan, nan)}
Stream update 5179861584 {'x_range': (nan, nan)}
Stream update 5179861584 {'x_range': (nan, nan)}
Stream update 5179861584 {'x_range': (numpy.datetime64('2019-10-21T09:30:09.432334336'), numpy.datetime64('2019-10-21T15:59:58.745244160'))}
Stream update 5179861584 {'x_range': (nan, nan)}
Stream update 5179861584 {'x_range': (nan, nan)}
Stream update 5179861584 {'x_range': (nan, nan)}
Stream update 5179861584 {'x_range': (nan, nan)}
Stream update 5179861584 {'x_range': (nan, nan)}
Stream update 5179861584 {'x_range': (numpy.datetime64('2019-10-21T09:30:09.432334336'), numpy.datetime64('2019-10-21T15:59:58.745244160'))}
Stream update 5179861584 {'x_range': (nan, nan)}
Stream update 5179861584 {'x_range': (nan, nan)}
Stream update 5179861584 {'x_range': (nan, nan)}
Stream update 5179861584 {}
SKIPES AGGREGATE
SKIPES AGGREGATE