Skip to content

Commit

Permalink
Fixed various issues for binned data
Browse files Browse the repository at this point in the history
  • Loading branch information
philippjfr committed Nov 29, 2017
1 parent 5b9193d commit 1fedff8
Show file tree
Hide file tree
Showing 8 changed files with 137 additions and 91 deletions.
69 changes: 58 additions & 11 deletions holoviews/core/data/grid.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,18 @@ def init(cls, eltype, data, kdims, vdims):
d for d in kdims + vdims]
if isinstance(data, tuple):
data = {d: v for d, v in zip(dimensions, data)}
elif isinstance(data, list) and data == []:
data = OrderedDict([(d, []) for d in dimensions])
elif not any(isinstance(data, tuple(t for t in interface.types if t is not None))
for interface in cls.interfaces.values()):
data = {k: v for k, v in zip(dimensions, zip(*data))}
elif isinstance(data, np.ndarray):
if data.ndim == 1:
if eltype._auto_indexable_1d and len(kdims)+len(vdims)>1:
data = np.column_stack([np.arange(len(data)), data])
else:
data = np.atleast_2d(data).T
data = {k: data[:,i] for i,k in enumerate(dimensions)}
elif isinstance(data, list) and data == []:
data = {d: np.array([]) for d in dimensions[:ndims]}
data.update({d: np.empty((0,) * ndims) for d in dimensions[ndims:]})
Expand All @@ -75,8 +87,14 @@ def init(cls, eltype, data, kdims, vdims):
for vdim in vdim_names:
shape = data[vdim].shape
error = DataError if len(shape) > 1 else ValueError
if (all((s!=e and (s+1)!=e) for s, e in zip(shape, expected[::-1])) and
not (not expected and shape == (1,))):
if (not expected and shape == (1,)):
pass
elif len(shape) != len(expected):
raise error('The shape of the %s value array does not '
'match the expected dimensionality indicated '
'by the key dimensions. Expected %d-D array, '
'found %d-D array.' % (vdim, len(expected), len(shape)))
elif any((s!=e and (s+1)!=e) for s, e in zip(shape, expected[::-1])):
raise error('Key dimension values and value array %s '
'shapes do not match. Expected shape %s, '
'actual shape: %s' % (vdim, expected[::-1], shape), cls)
Expand Down Expand Up @@ -104,15 +122,16 @@ def dimension_type(cls, dataset, dim):

@classmethod
def shape(cls, dataset, gridded=False):
if gridded:
return dataset.data[dataset.vdims[0].name].shape
shape = dataset.data[dataset.vdims[0].name].shape
if gridded or len(shape) == 1:
return shape
else:
return (cls.length(dataset), len(dataset.dimensions()))
return (np.product(shape),)


@classmethod
def length(cls, dataset):
return np.product([len(dataset.data[d.name]) for d in dataset.kdims])
return cls.shape(dataset)[0]


@classmethod
Expand All @@ -127,8 +146,11 @@ def coords(cls, dataset, dim, ordered=False, expanded=False, edges=False):
if expanded:
return util.expand_grid_coords(dataset, dim)
data = dataset.data[dim.name]
if ordered and np.all(data[1:] < data[:-1]):
data = data[::-1]
shape = cls.shape(dataset, True)
isedges = dim in dataset.kdims and len(data) == (shape[dataset.ndims-idx-1]+1)
isedges = (dim in dataset.kdims and len(shape) == dataset.ndims
and len(data) == (shape[dataset.ndims-idx-1]+1))
if edges and not isedges:
data = util.compute_edges(data)
elif not edges and isedges:
Expand Down Expand Up @@ -309,9 +331,9 @@ def key_select_mask(cls, dataset, values, ind):
mask = None
else:
index_mask = values == ind
if dataset.ndims == 1 and np.sum(index_mask) == 0:
if (dataset.ndims == 1 or dataset._binned) and np.sum(index_mask) == 0:
data_index = np.argmin(np.abs(values - ind))
mask = np.zeros(len(dataset), dtype=np.bool)
mask = np.zeros(len(values), dtype=np.bool)
mask[data_index] = True
else:
mask = index_mask
Expand All @@ -331,11 +353,36 @@ def select(cls, dataset, selection_mask=None, **selection):
for d in dimensions]
data = {}
value_select = []
for dim, ind in selection:
values = cls.values(dataset, dim, False)
for (dim, ind) in selection:
values = cls.coords(dataset, dim, False)
mask = cls.key_select_mask(dataset, values, ind)
if mask is None:
mask = np.ones(values.shape, dtype=bool)
if dataset._binned:
edges = cls.coords(dataset, dim, False, edges=True)
inds = np.argwhere(mask)
if np.isscalar(ind):
emin, emax = edges.min(), edges.max()
if ind < emin:
raise IndexError("Index %s less than lower bound "
"of %s for %s dimension." % (ind, emin, dim))
elif ind >= emax:
raise IndexError("Index %s more than or equal to upper bound "
"of %s for %s dimension." % (ind, emax, dim))
idx = max([np.digitize([ind], edges)[0]-1, 0])
mask = np.zeros(len(values), dtype=np.bool)
if np.sum(ind == values):
mask[idx] = True
values = edges[idx:idx+2]
elif len(inds):
mask[idx] = True
values = edges[idx: idx+2]
else:
values = edges[:0]
elif len(inds):
values = edges[inds.min(): inds.max()+2]
else:
values = edges[0:0]
else:
values = values[mask]
value_select.append(mask)
Expand Down
9 changes: 2 additions & 7 deletions holoviews/element/comparison.py
Original file line number Diff line number Diff line change
Expand Up @@ -536,9 +536,7 @@ def compare_trisurface(cls, el1, el2, msg='Trisurface'):

@classmethod
def compare_histogram(cls, el1, el2, msg='Histogram'):
cls.compare_dimensioned(el1, el2)
cls.compare_arrays(el1.edges, el2.edges, ' '.join([msg, 'edges']))
cls.compare_arrays(el1.values, el2.values, ' '.join([msg, 'values']))
cls.compare_dataset(el1, el2, msg)

@classmethod
def compare_points(cls, el1, el2, msg='Points'):
Expand Down Expand Up @@ -593,10 +591,7 @@ def compare_raster(cls, el1, el2, msg='Raster'):

@classmethod
def compare_quadmesh(cls, el1, el2, msg='QuadMesh'):
cls.compare_dimensioned(el1, el2)
cls.compare_arrays(el1.data[0], el2.data[0], ' '.join([msg, 'x-data']))
cls.compare_arrays(el1.data[1], el2.data[1], ' '.join([msg, 'y-data']))
cls.compare_arrays(el1.data[2], el2.data[2], ' '.join([msg, 'z-data']))
cls.compare_dataset(el1, el2, msg)

@classmethod
def compare_heatmap(cls, el1, el2, msg='HeatMap'):
Expand Down
4 changes: 2 additions & 2 deletions holoviews/plotting/bokeh/raster.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,8 +185,8 @@ def get_data(self, element, ranges, style):
widths = np.diff(element.interface.coords(element, x, edges=True))
heights = np.diff(element.interface.coords(element, y, edges=True))
if self.invert_axes:
xvals, yvals, zvals = xvals.T.flatten(), yvals.T.flatten(), zdata.flatten()
xvals, yvals, widths, heights = yvals, xvals, heights, widths
xs, ys, zvals = xvals.T.flatten(), yvals.T.flatten(), zdata.flatten()
xs, ys, widths, heights = ys, xs, heights, widths
else:
xs, ys, zvals = xvals.flatten(), yvals.flatten(), zdata.T.flatten()
ws, hs = cartesian_product([widths, heights], copy=True)
Expand Down
25 changes: 24 additions & 1 deletion tests/testcoreutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from holoviews.core.util import (
sanitize_identifier_fn, find_range, max_range, wrap_tuple_streams,
deephash, merge_dimensions, get_path, make_path_unique, compute_density,
date_range, dt_to_int
date_range, dt_to_int, compute_edges
)
from holoviews import Dimension, Element
from holoviews.streams import PointerXY
Expand Down Expand Up @@ -591,3 +591,26 @@ def test_date_range_1_sec(self):
drange = date_range(start, end, 10)
self.assertEqual(drange[0], start+np.timedelta64(50, 'ms'))
self.assertEqual(drange[-1], end-np.timedelta64(50, 'ms'))


class TestComputeEdges(ComparisonTestCase):
"""
Tests for compute_edges function.
"""

def setUp(self):
self.array1 = [.5, 1.5, 2.5]
self.array2 = [.5, 1.0000001, 1.5]
self.array3 = [1, 2, 4]

def test_simple_edges(self):
self.assertEqual(compute_edges(self.array1),
np.array([0, 1, 2, 3]))

def test_close_edges(self):
self.assertEqual(compute_edges(self.array2),
np.array([0.25, 0.75, 1.25, 1.75]))

def test_uneven_edges(self):
self.assertEqual(compute_edges(self.array3),
np.array([0.5, 1.5, 3.0, 5.0]))
8 changes: 0 additions & 8 deletions tests/testdataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -1264,14 +1264,6 @@ def setUp(self):
self.init_grid_data()
self.init_column_data()

def test_dataset_array_init_hm(self):
"Tests support for arrays (homogeneous)"
exception = "None of the available storage backends "\
"were able to support the supplied data format."
with self.assertRaisesRegexp(Exception, exception):
Dataset(np.column_stack([self.xs, self.xs_2]),
kdims=['x'], vdims=['x2'])

def test_dataset_dataframe_init_hm(self):
"Tests support for homogeneous DataFrames"
if pd is None:
Expand Down
12 changes: 8 additions & 4 deletions tests/testelementconstructors.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,17 @@ def test_hist_yvalues_construct(self):

def test_hist_curve_construct(self):
hist = Histogram(Curve(([0.1, 0.3, 0.5], [2.1, 2.2, 3.3])))
self.assertEqual(hist.data[0], np.array([2.1, 2.2, 3.3]))
self.assertEqual(hist.data[1], np.array([0, 0.2, 0.4, 0.6]))
values = hist.dimension_values(1)
edges = hist.interface.coords(hist, hist.kdims[0], edges=True)
self.assertEqual(values, np.array([2.1, 2.2, 3.3]))
self.assertEqual(edges, np.array([0, 0.2, 0.4, 0.6]))

def test_hist_curve_int_edges_construct(self):
hist = Histogram(Curve(range(3)))
self.assertEqual(hist.data[0], np.arange(3))
self.assertEqual(hist.data[1], np.array([-.5, .5, 1.5, 2.5]))
values = hist.dimension_values(1)
edges = hist.interface.coords(hist, hist.kdims[0], edges=True)
self.assertEqual(values, np.arange(3))
self.assertEqual(edges, np.array([-.5, .5, 1.5, 2.5]))

def test_heatmap_construct(self):
hmap = HeatMap([('A', 'a', 1), ('B', 'b', 2)])
Expand Down
75 changes: 43 additions & 32 deletions tests/testelementindexing.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,58 +9,76 @@
class HistogramIndexingTest(ComparisonTestCase):

def setUp(self):
self.values = [i for i in range(10)]
self.edges = [i for i in range(11)]
self.hist=Histogram(self.values, self.edges)
self.values = np.arange(10)
self.edges = np.arange(11)
self.hist=Histogram((self.edges, self.values))

def test_slice_all(self):
sliced = self.hist[:]
self.assertEqual(np.all(sliced.values == self.values), True)
self.assertEqual(np.all(sliced.edges == self.edges), True)
values = sliced.dimension_values(1)
edges = sliced.interface.coords(sliced, sliced.kdims[0], edges=True)
self.assertEqual(values, self.values)
self.assertEqual(edges, self.edges)

def test_slice_exclusive_upper(self):
"Exclusive upper boundary semantics for bin centers"
sliced = self.hist[:6.5]
self.assertEqual(np.all(sliced.values == [0, 1, 2, 3, 4, 5]), True)
self.assertEqual(np.all(sliced.edges == [0, 1, 2, 3, 4, 5, 6]), True)
values = sliced.dimension_values(1)
edges = sliced.interface.coords(sliced, sliced.kdims[0], edges=True)
self.assertEqual(values, np.arange(6))
self.assertEqual(edges, np.arange(7))

def test_slice_exclusive_upper_exceeded(self):
"Slightly above the boundary in the previous test"
sliced = self.hist[:6.55]
self.assertEqual(np.all(sliced.values == [0, 1, 2, 3, 4, 5, 6]), True)
self.assertEqual(np.all(sliced.edges == [0, 1, 2, 3, 4, 5, 6, 7]), True)
values = sliced.dimension_values(1)
edges = sliced.interface.coords(sliced, sliced.kdims[0], edges=True)
self.assertEqual(values, np.arange(7))
self.assertEqual(edges, np.arange(8))

def test_slice_inclusive_lower(self):
"Inclusive lower boundary semantics for bin centers"
sliced = self.hist[3.5:]
self.assertEqual(np.all(sliced.values == [3, 4, 5, 6, 7, 8, 9]), True)
self.assertEqual(np.all(sliced.edges == [3, 4, 5, 6, 7, 8, 9, 10]), True)
values = sliced.dimension_values(1)
edges = sliced.interface.coords(sliced, sliced.kdims[0], edges=True)
self.assertEqual(values, np.arange(3, 10))
self.assertEqual(edges, np.arange(3, 11))

def test_slice_inclusive_lower_undershot(self):
"Inclusive lower boundary semantics for bin centers"
sliced = self.hist[3.45:]
self.assertEqual(np.all(sliced.values == [3, 4, 5, 6, 7, 8, 9]), True)
self.assertEqual(np.all(sliced.edges == [3, 4, 5, 6, 7, 8, 9, 10]), True)
values = sliced.dimension_values(1)
edges = sliced.interface.coords(sliced, sliced.kdims[0], edges=True)
self.assertEqual(values, np.arange(3, 10))
self.assertEqual(edges, np.arange(3, 11))

def test_slice_bounded(self):
sliced = self.hist[3.5:6.5]
self.assertEqual(np.all(sliced.values == [3, 4, 5]), True)
self.assertEqual(np.all(sliced.edges == [3, 4, 5, 6]), True)
values = sliced.dimension_values(1)
edges = sliced.interface.coords(sliced, sliced.kdims[0], edges=True)
self.assertEqual(values, np.arange(3, 6))
self.assertEqual(edges, np.arange(3, 7))

def test_slice_lower_out_of_bounds(self):
sliced = self.hist[-3:]
self.assertEqual(np.all(sliced.values == self.values), True)
self.assertEqual(np.all(sliced.edges == self.edges), True)
values = sliced.dimension_values(1)
edges = sliced.interface.coords(sliced, sliced.kdims[0], edges=True)
self.assertEqual(values, self.values)
self.assertEqual(edges, self.edges)

def test_slice_upper_out_of_bounds(self):
sliced = self.hist[:12]
self.assertEqual(np.all(sliced.values == self.values), True)
self.assertEqual(np.all(sliced.edges == self.edges), True)
values = sliced.dimension_values(1)
edges = sliced.interface.coords(sliced, sliced.kdims[0], edges=True)
self.assertEqual(values, self.values)
self.assertEqual(edges, self.edges)

def test_slice_both_out_of_bounds(self):
sliced = self.hist[-3:13]
self.assertEqual(np.all(sliced.values == self.values), True)
self.assertEqual(np.all(sliced.edges == self.edges), True)
values = sliced.dimension_values(1)
edges = sliced.interface.coords(sliced, sliced.kdims[0], edges=True)
self.assertEqual(values, self.values)
self.assertEqual(edges, self.edges)

def test_scalar_index(self):
self.assertEqual(self.hist[4.5], 4)
Expand All @@ -79,19 +97,12 @@ def test_scalar_lowest_index(self):
self.assertEqual(self.hist[0], 0)

def test_scalar_lowest_index_out_of_bounds(self):
try:
self.hist[-0.1]
except Exception as e:
if not str(e).startswith("'Key value -0.1 is out of the histogram bounds"):
raise AssertionError("Out of bound exception not generated")
with self.assertRaises(IndexError):
self.hist[-1]

def test_scalar_highest_index_out_of_bounds(self):
try:
with self.assertRaises(IndexError):
self.hist[10]
except Exception as e:
if not str(e).startswith("'Key value 10 is out of the histogram bounds"):
raise AssertionError("Out of bound exception not generated")


class QuadMeshIndexingTest(ComparisonTestCase):

Expand All @@ -104,7 +115,7 @@ def setUp(self):
self.qmesh = QuadMesh((self.xs, self.ys, self.zs))

def test_qmesh_index_lower_left(self):
self.assertEqual(self.qmesh[0, 0], 0)
self.assertEqual(self.qmesh[10, 1], 0)

def test_qmesh_index_lower_right(self):
self.assertEqual(self.qmesh[800, 3.9], 2)
Expand Down
26 changes: 0 additions & 26 deletions tests/testelementutils.py

This file was deleted.

0 comments on commit 1fedff8

Please sign in to comment.