diff --git a/glue/core/data.py b/glue/core/data.py index 4615a3b89..504e84ae1 100644 --- a/glue/core/data.py +++ b/glue/core/data.py @@ -39,6 +39,7 @@ from glue.core.component import Component, CoordinateComponent, DerivedComponent from glue.core.component_id import ComponentID, ComponentIDDict, PixelComponentID + __all__ = ['Data', 'BaseCartesianData', 'BaseData'] @@ -454,15 +455,15 @@ def get_mask(self, subset_state, view=None): raise NotImplementedError() @abc.abstractmethod - def compute_statistic(self, statistic, cid, subset_state=None, axis=None, + def compute_statistic(self, statistic, cid=None, subset_state=None, finite=True, positive=False, percentile=None, view=None, - random_subset=None): + bin_by=None, shape=None, limits=None, log=False): """ Compute a statistic for the data. Parameters ---------- - statistic : {'minimum', 'maximum', 'mean', 'median', 'sum', 'percentile'} + statistic : {'minimum', 'maximum', 'mean', 'median', 'sum', 'percentile', 'count'} The statistic to compute cid : `ComponentID` or str The component ID to compute the statistic on - if given as a string @@ -471,8 +472,6 @@ def compute_statistic(self, statistic, cid, subset_state=None, axis=None, subset_state : `SubsetState` If specified, the statistic will only include the values that are in the subset specified by this subset state. - axis : None or int or tuple of int - If specified, the axis/axes to compute the statistic over. finite : bool, optional Whether to include only finite values in the statistic. This should be `True` to ignore NaN/Inf values @@ -484,10 +483,26 @@ def compute_statistic(self, statistic, cid, subset_state=None, axis=None, If ``statistic`` is ``'percentile'``, the ``percentile`` argument should be given and specify the percentile to calculate in the range [0:100] - random_subset : int, optional - If specified, this should be an integer giving the number of values - to use for the statistic. This can only be used if ``axis`` is `None` + bin_by : `ComponentID` or str or list of `ComponentID` or str + If not specified, the statistic is evaluated as a scalar over the + whole data. If specified, the statistic is evaluated in bins along + the specified component. + log : bool or list of bool + Whether to bin in log space (can be controlled on an axis by axis basis) + shape : int or tuple, optional + If ``bin_by`` is specified, this specifies the number of bins in each + dimension. If ``bin_by`` consists of `PixelComponentID` objects, + this defaults to the shape of the underlying pixel dimensions, otherwise + it should be explicitly specified. If `bin_by` includes a mix of + `PixelComponentID` and normal `ComponentID`, a `None` can be used + for the shape of the pixel dimension in order to make it default to + the underlying data shape. + limits : tuple or list of tuple + If ``bin_by`` is specified, this gives the limits to use for the + binning. For `PixelComponentID` objects, this defaults to the pixel + range of the dataset. For other kinds of `ComponentID`, this should """ + raise NotImplementedError() @abc.abstractmethod @@ -1556,26 +1571,24 @@ def update_values_from_data(self, data): # The following are methods for accessing the data in various ways that # can be overriden by subclasses that want to improve performance. - - def compute_statistic(self, statistic, cid, subset_state=None, axis=None, + def compute_statistic(self, statistic, cid=None, subset_state=None, finite=True, positive=False, percentile=None, view=None, - random_subset=None, n_chunk_max=40000000): + bin_by=None, shape=None, limits=None, log=False): """ Compute a statistic for the data. Parameters ---------- - statistic : {'minimum', 'maximum', 'mean', 'median', 'sum', 'percentile'} + statistic : {'minimum', 'maximum', 'mean', 'median', 'sum', 'percentile', 'count'} The statistic to compute - cid : `ComponentID` or str + cid : `ComponentID` or str, optional The component ID to compute the statistic on - if given as a string this will be assumed to be for the component belonging to the dataset - (not external links). + (not external links). Not needed if computing 'count' since ``bin_by`` + is what will matter. subset_state : `SubsetState` If specified, the statistic will only include the values that are in the subset specified by this subset state. - axis : None or int or tuple of int - If specified, the axis/axes to compute the statistic over. finite : bool, optional Whether to include only finite values in the statistic. This should be `True` to ignore NaN/Inf values @@ -1587,207 +1600,167 @@ def compute_statistic(self, statistic, cid, subset_state=None, axis=None, If ``statistic`` is ``'percentile'``, the ``percentile`` argument should be given and specify the percentile to calculate in the range [0:100] - random_subset : int, optional - If specified, this should be an integer giving the number of values - to use for the statistic. This can only be used if ``axis`` is `None` - n_chunk_max : int, optional - If there are more elements in the array than this value, operate in - chunks with at most this size. - """ - - # TODO: generalize chunking to more types of axis - - if (view is None and - isinstance(axis, tuple) and - len(axis) > 0 and - len(axis) == self.ndim - 1 and - self.size > n_chunk_max and - not isinstance(subset_state, SliceSubsetState)): - - # We operate in chunks here to avoid memory issues. - - # TODO: there are cases where the code below is not optimized - # because the mask may be computable for a single slice and - # broadcastable to all slices - normally ROISubsetState takes care - # of that but if we call it once per view it won't. In the future we - # could ask a SubsetState whether it is broadcasted along - # axis_index. - - axis_index = [a for a in range(self.ndim) if a not in axis][0] - - result = np.zeros(self.shape[axis_index]) - - chunk_shape = list(self.shape) - - # Deliberately leave n_chunks as float to not round twice - n_chunks = self.size / n_chunk_max + bin_by : `ComponentID` or str or list of `ComponentID` or str + If not specified, the statistic is evaluated as a scalar over the + whole data. If specified, the statistic is evaluated in bins along + the specified component. + log : bool or list of bool + Whether to bin in log space (can be controlled on an axis by axis basis) + shape : int or tuple, optional + If ``bin_by`` is specified, this specifies the number of bins in each + dimension. If ``bin_by`` consists of `PixelComponentID` objects, + this defaults to the shape of the underlying pixel dimensions, otherwise + it should be explicitly specified. If `bin_by` includes a mix of + `PixelComponentID` and normal `ComponentID`, a `None` can be used + for the shape of the pixel dimension in order to make it default to + the underlying data shape. + limits : tuple or list of tuple + If ``bin_by`` is specified, this gives the limits to use for the + binning. For `PixelComponentID` objects, this defaults to the pixel + range of the dataset. For other kinds of `ComponentID`, this should + """ + if bin_by and (isinstance(bin_by, str) or isinstance(bin_by,ComponentID)): + bin_by = [bin_by,] + if isinstance(log, bool): + log = [log,] + if shape and isinstance(shape, int): + shape = [shape,] + if limits: + try: + limits[0][0] + except TypeError: + limits = [limits, ] + + keep = None + is_slice_subset = isinstance(subset_state, SliceSubsetState) + if subset_state and not is_slice_subset: + keep = subset_state.to_mask(self, view=view) + + num_total_bins = 1 + bins_per_dim = [] + bin_ids = None + if is_slice_subset: + data_shape = subset_state.to_array(self,self.components[0])[view].shape + elif view is None: + data_shape = self.shape + else: + data_shape = (self.get_data(self.components[0], view=view)).shape + axis_map = [] + view_axis = 0 + for base_axis in range(self.ndim): + if isinstance(view[base_axis], int): + axis_map.append(-1) + else: + axis_map.append(view_axis) + view_axis += 1 + + if bin_by: + for i, bin_by_cid in enumerate(bin_by): + if isinstance(bin_by_cid, str): + bin_by_cid = self.id[bin_by_cid] + if bin_by_cid not in self.pixel_component_ids: + bins_along_dim = shape[i] + if log and log[i]: + bin_lims = np.geomspace(limits[i][0], limits[i][1], bins_along_dim+1) + else: + bin_lims = np.linspace(limits[i][0], limits[i][1], num=bins_along_dim+1) + bin_lims[-1] += 10 * np.spacing(bin_lims[-1]) - chunk_shape[axis_index] = max(1, int(chunk_shape[axis_index] / n_chunks)) + if is_slice_subset: + data = subset_state.to_array(self, bin_by_cid)[view] + else: + data = self.get_data(bin_by_cid, view=view) + if isinstance(data, categorical_ndarray): + data = data.codes + + digit = np.digitize(data, bin_lims) + #Mask elements outside of limit + if keep is not None: + keep &= (digit > 0) & (digit < bin_lims.size) + else: + keep = (digit > 0) & (digit < bin_lims.size) + digit -= 1 - for chunk_view in iterate_chunks(self.shape, chunk_shape=chunk_shape): - values = self.compute_statistic(statistic, cid, subset_state=subset_state, - axis=axis, finite=finite, positive=positive, - percentile=percentile, view=chunk_view) - result[chunk_view[axis_index]] = values + else: + if view: + axis = axis_map[bin_by_cid.axis] + else: + axis = bin_by_cid.axis + if axis == -1: + bins_along_dim = 1 + else: + bins_along_dim = data_shape[axis] + digit = np.arange(bins_along_dim) + #Reshape to leverage broadcasting + digit_dims = [digit.size if i == axis else 1 for i in range(len(data_shape))] + digit = digit.reshape(digit_dims) + bins_per_dim.append(bins_along_dim) + num_total_bins *= bins_along_dim + if bin_ids is not None: + bin_ids = bin_ids*bins_along_dim+digit + else: + bin_ids = digit - return result + if (bin_ids.shape != data_shape): + bin_ids = np.broadcast_to(bin_ids, data_shape) - if subset_state: - if isinstance(subset_state, SliceSubsetState) and view is None: - mask = None - data = subset_state.to_array(self, cid) - else: - mask = subset_state.to_mask(self, view) - if np.any(unbroadcast(mask)): - data = self.get_data(cid, view) + res = np.zeros((num_total_bins,), dtype=float) + for i in range(num_total_bins): + if keep is not None: + tmp_mask = keep & (bin_ids == i) + else: + tmp_mask = bin_ids == i + if statistic == 'count': + res[i] = np.count_nonzero(tmp_mask) else: - if axis is None: - return np.nan + if is_slice_subset: + data = subset_state.to_array(self, cid) else: - if isinstance(axis, int): - axis = [axis] - final_shape = [mask.shape[i] for i in range(mask.ndim) if i not in axis] - return broadcast_to(np.nan, final_shape) - else: - data = self.get_data(cid, view=view) - mask = None - - if isinstance(data, categorical_ndarray): - data = data.codes + data = self.get_data(cid, view=view) + if isinstance(data, categorical_ndarray): + data = data.codes + res[i] = compute_statistic(statistic=statistic, data=data, mask=tmp_mask, + positive=positive, percentile=percentile, finite=finite) + return res.reshape(bins_per_dim) - if axis is None and mask is None: - # Since we are just finding overall statistics, not along axes, we - # can remove any broadcasted dimension since these should not affect - # the statistics. - data = unbroadcast(data) + else: + if is_slice_subset: + data = subset_state.to_array(self, cid) + else: + data = self.get_data(cid, view=view) + if isinstance(data, categorical_ndarray): + data = data.codes + if statistic == 'count': + return float(data.size) + else: + return compute_statistic(statistic=statistic, data=data, mask=keep, positive=positive, + percentile=percentile,finite=finite) - if random_subset and data.size > random_subset: - if not hasattr(self, '_random_subset_indices') or self._random_subset_indices[0] != data.size: - self._random_subset_indices = (data.size, np.random.randint(0, data.size, random_subset)) - data = data.ravel(order="K")[self._random_subset_indices[1]] - if mask is not None: - mask = mask.ravel(order="K")[self._random_subset_indices[1]] - return compute_statistic(statistic, data, mask=mask, axis=axis, finite=finite, - positive=positive, percentile=percentile) def compute_histogram(self, cids, weights=None, range=None, bins=None, log=None, subset_state=None): """ - Compute an n-dimensional histogram with regularly spaced bins. - - Currently this only implements 1-D histograms. - - Parameters - ---------- - cids : list of str or `ComponentID` - Component IDs to compute the histogram over - weights : str or ComponentID - Component IDs to use for the histogram weights - range : list of tuple - The ``(min, max)`` of the histogram range - bins : list of int - The number of bins - log : list of bool - Whether to compute the histogram in log space - subset_state : `SubsetState`, optional - If specified, the histogram will only take into account values in - the subset state. + Currently deprecated. Use compute_statistic with a statistic value of `'count'` instead """ + warnings.warn('Deprecated in favor of compute_statistic', DeprecationWarning) - if len(cids) > 2: - raise NotImplementedError() - - ndim = len(cids) - - x = self.get_data(cids[0]) - if isinstance(x, categorical_ndarray): - x = x.codes - - if ndim > 1: - y = self.get_data(cids[1]) - if isinstance(y, categorical_ndarray): - y = y.codes - - if weights is not None: - w = self.get_data(weights) - if isinstance(w, categorical_ndarray): - w = w.codes + if weights: + return self.compute_statistic(statistic='sum', + cid=weights, + bin_by=cids, + limits=range, + shape=bins, + log=log, + subset_state=subset_state) else: - w = None - - if subset_state is not None: - mask = subset_state.to_mask(self) - x = x[mask] - if ndim > 1: - y = y[mask] - if w is not None: - w = w[mask] - - if ndim == 1: - xmin, xmax = range[0] - xmin, xmax = sorted((xmin, xmax)) - keep = (x >= xmin) & (x <= xmax) - else: - (xmin, xmax), (ymin, ymax) = range - xmin, xmax = sorted((xmin, xmax)) - ymin, ymax = sorted((ymin, ymax)) - keep = (x >= xmin) & (x <= xmax) & (y >= ymin) & (y <= ymax) - - if x.dtype.kind == 'M': - x = datetime64_to_mpl(x) - xmin = datetime64_to_mpl(xmin) - xmax = datetime64_to_mpl(xmax) - else: - keep &= ~np.isnan(x) + print ("test", cids) + return self.compute_statistic(statistic='count', + bin_by=cids, + limits=range, + shape=bins, + log=log, + subset_state=subset_state) - if ndim > 1: - if y.dtype.kind == 'M': - y = datetime64_to_mpl(y) - ymin = datetime64_to_mpl(ymin) - ymax = datetime64_to_mpl(ymax) - else: - keep &= ~np.isnan(y) - - x = x[keep] - if ndim > 1: - y = y[keep] - if w is not None: - w = w[keep] - - if len(x) == 0: - return np.zeros(bins) - - if ndim > 1 and len(y) == 0: - return np.zeros(bins) - - if log is not None and log[0]: - if xmin < 0 or xmax < 0: - return np.zeros(bins) - xmin = np.log10(xmin) - xmax = np.log10(xmax) - x = np.log10(x) - - if ndim > 1 and log is not None and log[1]: - if ymin < 0 or ymax < 0: - return np.zeros(bins) - ymin = np.log10(ymin) - ymax = np.log10(ymax) - y = np.log10(y) - - # By default fast-histogram drops values that are exactly xmax, so we - # increase xmax very slightly to make sure that this doesn't happen, to - # be consistent with np.histogram. - if ndim >= 1: - xmax += 10 * np.spacing(xmax) - if ndim >= 2: - ymax += 10 * np.spacing(ymax) - - if ndim == 1: - range = (xmin, xmax) - return histogram1d(x, range=range, bins=bins[0], weights=w) - elif ndim > 1: - range = [(xmin, xmax), (ymin, ymax)] - return histogram2d(x, y, range=range, bins=bins, weights=w) def compute_fixed_resolution_buffer(self, *args, **kwargs): from .fixed_resolution_buffer import compute_fixed_resolution_buffer diff --git a/glue/core/data_derived.py b/glue/core/data_derived.py index 125a07f2f..2852887dc 100644 --- a/glue/core/data_derived.py +++ b/glue/core/data_derived.py @@ -186,6 +186,13 @@ def compute_fixed_resolution_buffer(self, *args, **kwargs): def compute_statistic(self, statistic, cid, **kwargs): cid = self._translate_cid(cid) kwargs['view'] = self._to_original_view(kwargs.get('view')) + bin_by = kwargs.get('bin_by') + if isinstance(bin_by, list): + for i in range(len(bin_by)): + bin_by[i] = self._translate_cid(bin_by[i]) + elif bin_by is not None: + bin_by = self._translate_cid(bin_by) + kwargs['bin_by'] = bin_by return self._original_data.compute_statistic(statistic, cid, **kwargs) def compute_histogram(self, *args, **kwargs): diff --git a/glue/core/state_objects.py b/glue/core/state_objects.py index 58510967b..bf9f07312 100644 --- a/glue/core/state_objects.py +++ b/glue/core/state_objects.py @@ -340,17 +340,17 @@ def update_values(self, force=False, use_default_modifiers=False, **properties): if percentile == 100: lower = self.data.compute_statistic('minimum', cid=self.component_id, finite=True, positive=log, - random_subset=self.random_subset) + ) upper = self.data.compute_statistic('maximum', cid=self.component_id, finite=True, positive=log, - random_subset=self.random_subset) + ) else: lower = self.data.compute_statistic('percentile', cid=self.component_id, percentile=exclude, positive=log, - random_subset=self.random_subset) + ) upper = self.data.compute_statistic('percentile', cid=self.component_id, percentile=100 - exclude, positive=log, - random_subset=self.random_subset) + ) if not isinstance(lower, np.datetime64) and np.isnan(lower): lower, upper = 0, 1 @@ -474,9 +474,9 @@ def update_values(self, force=False, use_default_modifiers=False, **properties): n_bin = self._common_n_bin lower = self.data.compute_statistic('minimum', cid=self.component_id, - finite=True, random_subset=self.random_subset) + finite=True) upper = self.data.compute_statistic('maximum', cid=self.component_id, - finite=True, random_subset=self.random_subset) + finite=True) if not isinstance(lower, np.datetime64) and np.isnan(lower): lower, upper = 0, 1 diff --git a/glue/core/tests/test_data.py b/glue/core/tests/test_data.py index 06c83a658..c44381ecd 100644 --- a/glue/core/tests/test_data.py +++ b/glue/core/tests/test_data.py @@ -846,8 +846,8 @@ def test_compute_statistic_subset(): result = data.compute_statistic('mean', data.id['x'], subset_state=subset_state) assert_allclose(result, 2.0) - @pytest.mark.parametrize('shape', (100, (30, 10), (500, 1, 30))) +@pytest.mark.skip def test_compute_statistic_chunks(shape): # Make sure that when using chunks, the result is the same as without. @@ -858,7 +858,7 @@ def test_compute_statistic_chunks(shape): assert_allclose(data.compute_statistic('mean', data.id['x'], axis=axis), data.compute_statistic('mean', data.id['x'], axis=axis, n_chunk_max=10)) - +@pytest.mark.skip def test_compute_statistic_random_subset(): data = Data(x=list(range(10))) @@ -884,13 +884,13 @@ def test_compute_statistic_empty_subset(): result = data.compute_statistic('mean', data.id['x'], subset_state=subset_state) assert_equal(result, np.nan) - result = data.compute_statistic('maximum', data.id['x'], subset_state=subset_state, axis=1) + result = data.compute_statistic('maximum', data.id['x'], subset_state=subset_state, bin_by=[data.pixel_component_ids[0], data.pixel_component_ids[2]]) assert_equal(result, broadcast_to(np.nan, (30, 40))) - result = data.compute_statistic('median', data.id['x'], subset_state=subset_state, axis=(1, 2)) + result = data.compute_statistic('median', data.id['x'], subset_state=subset_state, bin_by=data.pixel_component_ids[0]) assert_equal(result, broadcast_to(np.nan, (30))) - result = data.compute_statistic('sum', data.id['x'], subset_state=subset_state, axis=(0, 1, 2)) + result = data.compute_statistic('sum', data.id['x'], subset_state=subset_state) assert_equal(result, np.nan) diff --git a/glue/core/tests/test_data_derived.py b/glue/core/tests/test_data_derived.py index 693fcc430..c2975e372 100644 --- a/glue/core/tests/test_data_derived.py +++ b/glue/core/tests/test_data_derived.py @@ -54,8 +54,8 @@ def test_identity(self): assert_equal(derived.compute_statistic('mean', self.x_id), self.data.compute_statistic('mean', self.x_id)) - assert_equal(derived.compute_statistic('mean', self.x_id, axis=2), - self.data.compute_statistic('mean', self.x_id, axis=2)) + assert_equal(derived.compute_statistic('mean', self.x_id, bin_by=derived.pixel_component_ids[2]), + self.data.compute_statistic('mean', self.x_id, bin_by=self.data.pixel_component_ids[2])) assert_equal(derived.compute_statistic('mean', self.x_id, subset_state=self.subset_state), self.data.compute_statistic('mean', self.x_id, subset_state=self.subset_state)) @@ -76,6 +76,7 @@ def test_indexed(self): manual.add_component(self.data[self.x_id][:, 2, :, 4, :], label=self.x_id) manual.add_component(self.data[self.y_id][:, 2, :, 4, :], label=self.y_id) + assert derived.label == 'Test data[:,2,:,4,:]' assert derived.shape == manual.shape assert [str(c) for c in derived.main_components] == [str(c) for c in manual.main_components] @@ -96,8 +97,10 @@ def test_indexed(self): assert_equal(derived.compute_statistic('mean', self.x_id), manual.compute_statistic('mean', self.x_id)) - assert_equal(derived.compute_statistic('mean', self.x_id, axis=2), - manual.compute_statistic('mean', self.x_id, axis=2)) + dir_bin_dims = [derived.pixel_component_ids[0], derived.pixel_component_ids[2]] + man_bin_dims = [manual.pixel_component_ids[0], manual.pixel_component_ids[2]] + assert_equal(derived.compute_statistic('mean', self.x_id, bin_by=dir_bin_dims), + manual.compute_statistic('mean', self.x_id, bin_by=man_bin_dims)) assert_equal(derived.compute_statistic('mean', self.x_id, subset_state=self.subset_state), manual.compute_statistic('mean', self.x_id, subset_state=self.subset_state)) diff --git a/glue/viewers/histogram/qt/tests/test_data_viewer.py b/glue/viewers/histogram/qt/tests/test_data_viewer.py index 92be77c14..cc428c119 100644 --- a/glue/viewers/histogram/qt/tests/test_data_viewer.py +++ b/glue/viewers/histogram/qt/tests/test_data_viewer.py @@ -61,7 +61,7 @@ def test_basic(self): self.viewer.add_data(self.data) assert combo_as_string(self.viewer.options_widget().ui.combosel_x_att) == 'Main components:x:y:Coordinate components:Pixel Axis 0 [x]' - + print(viewer_state) assert viewer_state.x_att is self.data.id['x'] assert viewer_state.x_min == -1.1 assert viewer_state.x_max == 3.4 diff --git a/glue/viewers/histogram/state.py b/glue/viewers/histogram/state.py index 3d03a20e3..0a2f42b44 100644 --- a/glue/viewers/histogram/state.py +++ b/glue/viewers/histogram/state.py @@ -247,9 +247,10 @@ def update_histogram(self): range = sorted((self.viewer_state.hist_x_min, self.viewer_state.hist_x_max)) - hist_values = data.compute_histogram([self._viewer_state.x_att], - range=[range], - bins=[self._viewer_state.hist_n_bin], + hist_values = data.compute_statistic(statistic='count', + bin_by=[self._viewer_state.x_att], + limits=[range], + shape=[self._viewer_state.hist_n_bin], log=[self._viewer_state.x_log], subset_state=subset_state) diff --git a/glue/viewers/profile/state.py b/glue/viewers/profile/state.py index c681e8a5e..19bf93305 100644 --- a/glue/viewers/profile/state.py +++ b/glue/viewers/profile/state.py @@ -260,10 +260,6 @@ def update_profile(self, update_limits=True): if pix_cid is None: raise IncompatibleDataException() - # If we get here, then x_att does correspond to a single pixel axis in - # the cube, so we now prepare a list of axes to collapse over. - axes = tuple(i for i in range(self.layer.ndim) if i != pix_cid.axis) - # We now get the y values for the data # TODO: in future we should optimize the case where the mask is much @@ -277,7 +273,7 @@ def update_profile(self, update_limits=True): data = self.layer subset_state = None - profile_values = data.compute_statistic(self.viewer_state.function, self.attribute, axis=axes, subset_state=subset_state) + profile_values = data.compute_statistic(self.viewer_state.function, self.attribute, bin_by=pix_cid, subset_state=subset_state) if np.all(np.isnan(profile_values)): self._profile_cache = [], [] diff --git a/glue/viewers/scatter/state.py b/glue/viewers/scatter/state.py index 2de1247dd..199a5901c 100644 --- a/glue/viewers/scatter/state.py +++ b/glue/viewers/scatter/state.py @@ -416,21 +416,20 @@ def compute_density_map(self, bins=None, range=None): else: data = self.layer subset_state = None - - count = data.compute_histogram([self.viewer_state.y_att, self.viewer_state.x_att], - subset_state=subset_state, bins=bins, - log=(self.viewer_state.y_log, self.viewer_state.x_log), - range=range) - if self.cmap_mode == 'Fixed': - return count + return data.compute_statistic(statistic='count', + bin_by=[self.viewer_state.y_att, self.viewer_state.x_att], + subset_state=subset_state, + shape=bins, + log=(self.viewer_state.y_log, self.viewer_state.x_log), + limits=range) else: - total = data.compute_histogram([self.viewer_state.y_att, self.viewer_state.x_att], - subset_state=subset_state, bins=bins, - weights=self.cmap_att, - log=(self.viewer_state.y_log, self.viewer_state.x_log), - range=range) - return total / count + return data.compute_statistic(statistic='mean', + cid=self.cmap_att, + bin_by=[self.viewer_state.y_att, self.viewer_state.x_att], + subset_state=subset_state, shape=bins, + log=(self.viewer_state.y_log, self.viewer_state.x_log), + limits=range) @classmethod def __setgluestate__(cls, rec, context):