Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow specifying Bivariate levels #2474

Merged
merged 2 commits into from Mar 26, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
18 changes: 10 additions & 8 deletions holoviews/operation/element.py
Expand Up @@ -439,7 +439,7 @@ def _process(self, element, key=None):
element.data[2])

if isinstance(self.p.levels, int):
levels = self.p.levels+1 if self.p.filled else self.p.levels
levels = self.p.levels+2 if self.p.filled else self.p.levels+3
zmin, zmax = element.range(2)
levels = np.linspace(zmin, zmax, levels)
else:
Expand All @@ -448,23 +448,25 @@ def _process(self, element, key=None):
xdim, ydim = element.dimensions('key', label=True)
fig = Figure()
ax = Axes(fig, [0, 0, 1, 1])
contour_set = QuadContourSet(ax, *data, filled=self.p.filled, extent=extent, levels=levels)
contour_set = QuadContourSet(ax, *data, filled=self.p.filled,
extent=extent, levels=levels)
if self.p.filled:
contour_type = Polygons
levels = np.convolve(levels, np.ones((2,))/2, mode='valid')
else:
contour_type = Contours
vdims = element.vdims[:1]

paths = []
for level, cset in zip(levels, contour_set.collections):
empty = np.full((1, 2), np.NaN)
for level, cset in zip(contour_set.get_array(), contour_set.collections):
subpaths = []
for path in cset.get_paths():
if path.codes is None:
subpaths = [path.vertices]
subpaths.append(path.vertices)
else:
subpaths = np.split(path.vertices, np.where(path.codes==1)[0][1:])
for p in subpaths:
paths.append({(xdim, ydim): p, element.vdims[0].name: level})
subpaths += np.split(path.vertices, np.where(path.codes==1)[0][1:])
subpath = np.concatenate([p for sp in subpaths for p in (sp, empty)][:-1])
paths.append({(xdim, ydim): subpath, element.vdims[0].name: level})
contours = contour_type(paths, label=element.label, kdims=element.kdims, vdims=vdims)
if self.p.overlaid:
contours = element * contours
Expand Down
5 changes: 4 additions & 1 deletion holoviews/operation/stats.py
Expand Up @@ -150,6 +150,9 @@ class bivariate_kde(Operation):
filled = param.Boolean(default=False, doc="""
Controls whether to return filled or unfilled contours.""")

levels = param.ClassSelector(default=10, class_=(list, int), doc="""
A list of scalar values used to specify the contour levels.""")

n_samples = param.Integer(default=100, doc="""
Number of samples to compute the KDE over.""")

Expand Down Expand Up @@ -219,6 +222,6 @@ def _process(self, element, key=None):

img = Image((xs, ys, f.T), kdims=element.dimensions()[:2], vdims=[vdim], **params)
if self.p.contours:
cntr = contours(img, filled=self.p.filled)
cntr = contours(img, filled=self.p.filled, levels=self.p.levels)
return cntr.clone(cntr.data[1:], **params)
return img
4 changes: 4 additions & 0 deletions holoviews/plotting/bokeh/stats.py
Expand Up @@ -49,6 +49,10 @@ class BivariatePlot(PolygonPlot):
filled = param.Boolean(default=False, doc="""
Whether the bivariate contours should be filled.""")

levels = param.ClassSelector(default=10, class_=(list, int), doc="""
A list of scalar values used to specify the contour levels.""")




class BoxWhiskerPlot(CompositeElementPlot, ColorbarPlot, LegendPlot):
Expand Down
4 changes: 4 additions & 0 deletions holoviews/plotting/mpl/stats.py
Expand Up @@ -38,6 +38,10 @@ class BivariatePlot(PolygonPlot):
filled = param.Boolean(default=False, doc="""
Whether the bivariate contours should be filled.""")

levels = param.ClassSelector(default=10, class_=(list, int), doc="""
A list of scalar values used to specify the contour levels.""")




class BoxPlot(ChartPlot):
Expand Down
9 changes: 5 additions & 4 deletions tests/operation/testoperation.py
Expand Up @@ -57,17 +57,18 @@ def test_image_gradient(self):
def test_image_contours(self):
img = Image(np.array([[0, 1, 0], [3, 4, 5.], [6, 7, 8]]))
op_contours = contours(img, levels=[0.5])
contour = Contours([[(-0.5, 0.416667, 0.5), (-0.25, 0.5, 0.5)],
[(0.25, 0.5, 0.5), (0.5, 0.45, 0.5)]],
contour = Contours([[(-0.5, 0.416667, 0.5), (-0.25, 0.5, 0.5),
(np.NaN, np.NaN, 0.5), (0.25, 0.5, 0.5),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't quite understand where the NaNs come from...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

contours now returns a Contours element with one array per level, if there's multiple contours at a level they will be NaN separated.

(0.5, 0.45, 0.5)]],
vdims=img.vdims)
self.assertEqual(op_contours, contour)

@attr(optional=1) # Requires matplotlib
def test_image_contours_filled(self):
img = Image(np.array([[0, 1, 0], [3, 4, 5.], [6, 7, 8]]))
op_contours = contours(img, filled=True, levels=[2, 2.5])
data = [[(0., 0.333333, 2.25), (0.5, 0.3, 2.25), (0.5, 0.25, 2.25), (0., 0.25, 2.25),
(-0.5, 0.08333333, 2.25), (-0.5, 0.16666667, 2.25), (0., 0.33333333, 2.25)]]
data = [[(0., 0.333333, 2), (0.5, 0.3, 2), (0.5, 0.25, 2), (0., 0.25, 2),
(-0.5, 0.08333333, 2), (-0.5, 0.16666667, 2), (0., 0.33333333, 2)]]
polys = Polygons(data, vdims=img.vdims)
self.assertEqual(op_contours, polys)

Expand Down
16 changes: 15 additions & 1 deletion tests/operation/teststatsoperations.py
Expand Up @@ -7,7 +7,7 @@

import numpy as np

from holoviews import Distribution, Bivariate, Area, Image
from holoviews import Distribution, Bivariate, Area, Image, Contours, Polygons
from holoviews.element.comparison import ComparisonTestCase
from holoviews.operation.stats import (univariate_kde, bivariate_kde)

Expand Down Expand Up @@ -48,6 +48,20 @@ def test_bivariate_kde(self):
bounds=(-2, -2, 6, 6), vdims=['Density'])
self.assertEqual(kde, img)

def test_bivariate_kde_contours(self):
bivariate = Bivariate(np.random.rand(100, 2))
kde = bivariate_kde(bivariate, n_samples=100, x_range=(0, 1),
y_range=(0, 1), contours=True, levels=10)
self.assertIsInstance(kde, Contours)
self.assertEqual(len(kde.data), 10)

def test_bivariate_kde_contours_filled(self):
bivariate = Bivariate(np.random.rand(100, 2))
kde = bivariate_kde(bivariate, n_samples=100, x_range=(0, 1),
y_range=(0, 1), contours=True, filled=True, levels=10)
self.assertIsInstance(kde, Polygons)
self.assertEqual(len(kde.data), 10)

def test_bivariate_kde_nans(self):
kde = bivariate_kde(self.bivariate_nans, n_samples=2, x_range=(0, 4),
y_range=(0, 4), contours=False)
Expand Down