From b4ad00bf2704d1f64f4590ca536d398725331876 Mon Sep 17 00:00:00 2001 From: Gregory Lee Date: Fri, 4 Sep 2020 13:00:53 -0400 Subject: [PATCH 1/6] ENH: add histogram2d and histogramdd --- cupy/__init__.py | 2 + cupy/_statistics/histogram.py | 209 +++++++++++++++++- .../statics_tests/test_histogram.py | 63 ++++++ 3 files changed, 272 insertions(+), 2 deletions(-) diff --git a/cupy/__init__.py b/cupy/__init__.py index da2e330b7fd..d300318efd5 100644 --- a/cupy/__init__.py +++ b/cupy/__init__.py @@ -720,6 +720,8 @@ def isscalar(element): from cupy._statistics.histogram import bincount # NOQA from cupy._statistics.histogram import digitize # NOQA from cupy._statistics.histogram import histogram # NOQA +from cupy._statistics.histogram import histogram2d # NOQA +from cupy._statistics.histogram import histogramdd # NOQA # ----------------------------------------------------------------------------- # Undocumented functions diff --git a/cupy/_statistics/histogram.py b/cupy/_statistics/histogram.py index cd0f4ec493c..75e6a20f9d6 100644 --- a/cupy/_statistics/histogram.py +++ b/cupy/_statistics/histogram.py @@ -9,6 +9,9 @@ from cupy.cuda import cub from cupy.cuda import common +# rename builtin range for use in functions that take a range argument +_range = range + # TODO(unno): use searchsorted _histogram_kernel = core.ElementwiseKernel( @@ -271,10 +274,212 @@ def histogram(x, bins=10, range=None, weights=None, density=False): return y, bin_edges -# TODO(okuta): Implement histogram2d +def histogramdd(sample, bins=10, range=None, weights=None, density=False): + """Compute the multidimensional histogram of some data. + + Args: + sample (cupy.ndarray): The data to be histogrammed. (N, D) or (D, N) + array + + Note the unusual interpretation of sample when an array_like: + + * When an array, each row is a coordinate in a D-dimensional space - + such as ``histogramdd(cupy.array([p1, p2, p3]))``. + * When an array_like, each element is the list of values for single + coordinate - such as ``histogramdd((X, Y, Z))``. + + The first form should be preferred. + bins (int or tuple of int or cupy.ndarray): The bin specification: + + * A sequence of arrays describing the monotonically increasing bin + edges along each dimension. + * The number of bins for each dimension (nx, ny, ... =bins) + * The number of bins for all dimensions (nx=ny=...=bins). + range (sequence, optional): A sequence of length D, each an optional + (lower, upper) tuple giving the outer bin edges to be used if the + edges are not given explicitly in `bins`. An entry of None in the + sequence results in the minimum and maximum values being used for + the corresponding dimension. The default, None, is equivalent to + passing a tuple of D None values. + weights (cupy.ndarray): An array of values `w_i` weighing each sample + `(x_i, y_i, z_i, ...)`. The values of the returned histogram are + equal to the sum of the weights belonging to the samples falling + into each bin. + density (bool, optional): If False, the default, returns the number of + samples in each bin. If True, returns the probability *density* + function at the bin, ``bin_count / sample_count / bin_volume``. + + Returns: + H (cupy.ndarray): The multidimensional histogram of sample x. See + normed and weights for the different possible semantics. + edges (list of cupy.ndarray): A list of D arrays describing the bin + edges for each dimension. + + .. warning:: + + This function may synchronize the device. + + .. seealso:: :func:`numpy.histogramdd` + """ + if isinstance(sample, cupy.ndarray): + # Sample is an ND-array. + if sample.ndim == 1: + sample = sample[:, cupy.newaxis] + nsamples, ndim = sample.shape + else: + sample = cupy.stack(sample, axis=-1) + nsamples, ndim = sample.shape + + nbin = numpy.empty(ndim, int) + edges = ndim * [None] + dedges = ndim * [None] + if weights is not None: + weights = cupy.asarray(weights) + + try: + nbins = len(bins) + if nbins != ndim: + raise ValueError( + "The dimension of bins must be equal to the dimension of the " + " sample x." + ) + except TypeError: + # bins is an integer + bins = ndim * [bins] + + # normalize the range argument + if range is None: + range = (None,) * ndim + elif len(range) != ndim: + raise ValueError("range argument must have one entry per dimension") + + # Create edge arrays + for i in _range(ndim): + if cupy.ndim(bins[i]) == 0: + if bins[i] < 1: + raise ValueError( + "`bins[{}]` must be positive, when an integer".format(i) + ) + smin, smax = _get_outer_edges(sample[:, i], range[i]) + num = int(bins[i] + 1) # synchronize! + edges[i] = cupy.linspace(smin, smax, num) + elif cupy.ndim(bins[i]) == 1: + edges[i] = cupy.asarray(bins[i]) + if (edges[i][:-1] > edges[i][1:]).any(): + raise ValueError( + "`bins[{}]` must be monotonically increasing, when an array".format( + i + ) + ) + else: + raise ValueError( + "`bins[{}]` must be a scalar or 1d array".format(i) + ) + + nbin[i] = len(edges[i]) + 1 # includes an outlier on each end + dedges[i] = cupy.diff(edges[i]) + + # Compute the bin number each sample falls into. + ncount = tuple( + # avoid cupy.digitize to work around NumPy issue gh-11022 + cupy.searchsorted(edges[i], sample[:, i], side="right") + for i in _range(ndim) + ) + + # Using digitize, values that fall on an edge are put in the right bin. + # For the rightmost bin, we want values equal to the right edge to be + # counted in the last bin, and not as an outlier. + for i in _range(ndim): + # Find which points are on the rightmost edge. + on_edge = sample[:, i] == edges[i][-1] + # Shift these points one bin to the left. + ncount[i][on_edge] -= 1 + + # Compute the sample indices in the flattened histogram matrix. + # This raises an error if the array is too large. + xy = cupy.ravel_multi_index(ncount, nbin) + + # Compute the number of repetitions in xy and assign it to the + # flattened histmat. + hist = cupy.bincount(xy, weights, minlength=numpy.prod(nbin)) + + # Shape into a proper matrix + hist = hist.reshape(nbin) + + # This preserves the (bad) behavior observed in NumPy gh-7845, for now. + hist = hist.astype(float) # Note: NumPy uses casting='safe' here too + + # Remove outliers (indices 0 and -1 for each dimension). + core = ndim * (slice(1, -1),) + hist = hist[core] + if density: + # calculate the probability density function + s = hist.sum() + for i in _range(ndim): + shape = [1] * ndim + shape[i] = nbin[i] - 2 + hist = hist / dedges[i].reshape(shape) + hist /= s + + if any(hist.shape != numpy.asarray(nbin) - 2): + raise RuntimeError("Internal Shape Error") + return hist, edges + + +def histogram2d(x, y, bins=10, range=None, weights=None, density=None): + """Compute the bi-dimensional histogram of two data samples. + + Args: + x (cupy.ndarray): The first array of samples to be histogrammed. + y (cupy.ndarray): The second array of samples to be histogrammed. + bins (int or tuple of int or cupy.ndarray): The bin specification: + + * A sequence of arrays describing the monotonically increasing bin + edges along each dimension. + * The number of bins for each dimension (nx, ny) + * The number of bins for all dimensions (nx=ny=bins). + range (sequence, optional): A sequence of length two, each an optional + (lower, upper) tuple giving the outer bin edges to be used if the + edges are not given explicitly in `bins`. An entry of None in the + sequence results in the minimum and maximum values being used for + the corresponding dimension. The default, None, is equivalent to + passing a tuple of two None values. + weights (cupy.ndarray): An array of values `w_i` weighing each sample + `(x_i, y_i)`. The values of the returned histogram are equal to the + sum of the weights belonging to the samples falling into each bin. + density (bool, optional): If False, the default, returns the number of + samples in each bin. If True, returns the probability *density* + function at the bin, ``bin_count / sample_count / bin_volume``. + + Returns: + H (cupy.ndarray): The multidimensional histogram of sample x. See + normed and weights for the different possible semantics. + edges0 (tuple of cupy.ndarray): A list of D arrays describing the bin + edges for the first dimension. + edges1 (tuple of cupy.ndarray): A list of D arrays describing the bin + edges for the second dimension. + + .. warning:: + + This function may synchronize the device. + + .. seealso:: :func:`numpy.histogram2d` + """ + try: + n = len(bins) + except TypeError: + n = 1 + + if n != 1 and n != 2: + if isinstance(bins, cupy.ndarray): + xedges = yedges = bins + bins = [xedges, yedges] + else: + raise ValueError("array-like bins not supported in CuPy") -# TODO(okuta): Implement histogramdd + hist, edges = histogramdd([x, y], bins, range, weights, density) + return hist, edges[0], edges[1] _bincount_kernel = core.ElementwiseKernel( diff --git a/tests/cupy_tests/statics_tests/test_histogram.py b/tests/cupy_tests/statics_tests/test_histogram.py index 8f9ae9c65c8..cef67919fec 100644 --- a/tests/cupy_tests/statics_tests/test_histogram.py +++ b/tests/cupy_tests/statics_tests/test_histogram.py @@ -483,3 +483,66 @@ def test_digitize_nd_bins(self): bins = xp.array([[1], [2]]) with pytest.raises(ValueError): xp.digitize(x, bins) + + +@testing.parameterize( + *testing.product( + {'weights': [None, 1, 2], + 'weights_dtype': [numpy.int32, numpy.float64], + 'density': [True, False], + 'bins': [10, (8, 16, 12), (16, 8, 12), (16, 12, 8), (12, 8, 16), + 'array'], + 'range': [None, ((20, 50), (10, 100), (0, 40))]} + ) +) +@testing.gpu +class TestHistogramdd(unittest.TestCase): + + @testing.for_all_dtypes(no_bool=True, no_complex=True) + @testing.numpy_cupy_allclose(atol=1e-7, rtol=1e-7) + def test_histogramdd(self, xp, dtype): + x = testing.shaped_random((100, 3), xp, dtype, scale=100) + if self.bins == 'array': + bins = [xp.arange(0, 100, 4), + xp.arange(0, 100, 10), + xp.arange(25)] + else: + bins = self.bins + if self.weights is not None: + weights = xp.ones((x.shape[0],), dtype=self.weights_dtype) + else: + weights = None + y, bin_edges = xp.histogramdd(x, bins=bins, range=self.range, + weights=weights, density=self.density) + return [y, ] + [e for e in bin_edges] + + +@testing.parameterize( + *testing.product( + {'weights': [None, 1, 2], + 'weights_dtype': [numpy.int32, numpy.float64], + 'density': [True, False], + 'bins': [10, (8, 16), (16, 8), 'array'], + 'range': [None, ((20, 50), (10, 100))]} + ) +) +@testing.gpu +class TestHistogram2d(unittest.TestCase): + + @testing.for_all_dtypes(no_bool=True, no_complex=True) + @testing.numpy_cupy_allclose(atol=1e-7, rtol=1e-7) + def test_histogram2d(self, xp, dtype): + x = testing.shaped_random((100, ), xp, dtype, scale=100) + y = testing.shaped_random((100, ), xp, dtype, scale=100) + if self.bins == 'array': + bins = [xp.arange(0, 100, 4), xp.arange(0, 100, 10)] + else: + bins = self.bins + if self.weights is not None: + weights = xp.ones((x.shape[0],), dtype=self.weights_dtype) + else: + weights = None + y, edges0, edges1 = xp.histogram2d(x, y, bins=bins, + range=self.range, weights=weights, + density=self.density) + return y, edges0, edges1 From e5fcdb0fb06a9b89836c4df207959faad2df1517 Mon Sep 17 00:00:00 2001 From: Gregory Lee Date: Fri, 4 Sep 2020 13:02:22 -0400 Subject: [PATCH 2/6] rename folder: test_statistics --- tests/cupy_tests/{statics_tests => statistics_tests}/__init__.py | 0 .../{statics_tests => statistics_tests}/test_correlation.py | 0 .../{statics_tests => statistics_tests}/test_histogram.py | 0 .../{statics_tests => statistics_tests}/test_meanvar.py | 0 .../cupy_tests/{statics_tests => statistics_tests}/test_order.py | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename tests/cupy_tests/{statics_tests => statistics_tests}/__init__.py (100%) rename tests/cupy_tests/{statics_tests => statistics_tests}/test_correlation.py (100%) rename tests/cupy_tests/{statics_tests => statistics_tests}/test_histogram.py (100%) rename tests/cupy_tests/{statics_tests => statistics_tests}/test_meanvar.py (100%) rename tests/cupy_tests/{statics_tests => statistics_tests}/test_order.py (100%) diff --git a/tests/cupy_tests/statics_tests/__init__.py b/tests/cupy_tests/statistics_tests/__init__.py similarity index 100% rename from tests/cupy_tests/statics_tests/__init__.py rename to tests/cupy_tests/statistics_tests/__init__.py diff --git a/tests/cupy_tests/statics_tests/test_correlation.py b/tests/cupy_tests/statistics_tests/test_correlation.py similarity index 100% rename from tests/cupy_tests/statics_tests/test_correlation.py rename to tests/cupy_tests/statistics_tests/test_correlation.py diff --git a/tests/cupy_tests/statics_tests/test_histogram.py b/tests/cupy_tests/statistics_tests/test_histogram.py similarity index 100% rename from tests/cupy_tests/statics_tests/test_histogram.py rename to tests/cupy_tests/statistics_tests/test_histogram.py diff --git a/tests/cupy_tests/statics_tests/test_meanvar.py b/tests/cupy_tests/statistics_tests/test_meanvar.py similarity index 100% rename from tests/cupy_tests/statics_tests/test_meanvar.py rename to tests/cupy_tests/statistics_tests/test_meanvar.py diff --git a/tests/cupy_tests/statics_tests/test_order.py b/tests/cupy_tests/statistics_tests/test_order.py similarity index 100% rename from tests/cupy_tests/statics_tests/test_order.py rename to tests/cupy_tests/statistics_tests/test_order.py From e98329f7de4278e5019b4bc150bdbccaacd30da2 Mon Sep 17 00:00:00 2001 From: Gregory Lee Date: Fri, 4 Sep 2020 14:22:21 -0400 Subject: [PATCH 3/6] TST: test histogramdd and histogram2d exceptions --- cupy/_statistics/histogram.py | 11 ++-- .../statistics_tests/test_histogram.py | 66 +++++++++++++++++-- 2 files changed, 68 insertions(+), 9 deletions(-) diff --git a/cupy/_statistics/histogram.py b/cupy/_statistics/histogram.py index 75e6a20f9d6..7b59688f8eb 100644 --- a/cupy/_statistics/histogram.py +++ b/cupy/_statistics/histogram.py @@ -364,12 +364,13 @@ def histogramdd(sample, bins=10, range=None, weights=None, density=False): num = int(bins[i] + 1) # synchronize! edges[i] = cupy.linspace(smin, smax, num) elif cupy.ndim(bins[i]) == 1: - edges[i] = cupy.asarray(bins[i]) - if (edges[i][:-1] > edges[i][1:]).any(): + if not isinstance(bins[i], cupy.ndarray): + raise ValueError("array-like bins not supported") + edges[i] = bins[i] + if (edges[i][:-1] > edges[i][1:]).any(): # synchronize! raise ValueError( - "`bins[{}]` must be monotonically increasing, when an array".format( - i - ) + "`bins[{}]` must be monotonically increasing, when an " + "array".format(i) ) else: raise ValueError( diff --git a/tests/cupy_tests/statistics_tests/test_histogram.py b/tests/cupy_tests/statistics_tests/test_histogram.py index cef67919fec..9d1395259aa 100644 --- a/tests/cupy_tests/statistics_tests/test_histogram.py +++ b/tests/cupy_tests/statistics_tests/test_histogram.py @@ -491,7 +491,7 @@ def test_digitize_nd_bins(self): 'weights_dtype': [numpy.int32, numpy.float64], 'density': [True, False], 'bins': [10, (8, 16, 12), (16, 8, 12), (16, 12, 8), (12, 8, 16), - 'array'], + 'array_list'], 'range': [None, ((20, 50), (10, 100), (0, 40))]} ) ) @@ -502,7 +502,7 @@ class TestHistogramdd(unittest.TestCase): @testing.numpy_cupy_allclose(atol=1e-7, rtol=1e-7) def test_histogramdd(self, xp, dtype): x = testing.shaped_random((100, 3), xp, dtype, scale=100) - if self.bins == 'array': + if self.bins == 'array_list': bins = [xp.arange(0, 100, 4), xp.arange(0, 100, 10), xp.arange(25)] @@ -517,12 +517,57 @@ def test_histogramdd(self, xp, dtype): return [y, ] + [e for e in bin_edges] +@testing.gpu +class TestHistogramddErrors(unittest.TestCase): + + def test_histogramdd_invalid_bins(self): + for xp in (numpy, cupy): + x = testing.shaped_random((16, 2), xp, scale=100) + bins = [xp.arange(0, 100, 10), ] * 3 + with pytest.raises(ValueError): + y, bin_edges = xp.histogramdd(x, bins) + + def test_histogramdd_invalid_bins2(self): + for xp in (numpy, cupy): + x = testing.shaped_random((16, 2), xp, scale=100) + with pytest.raises(ValueError): + y, bin_edges = xp.histogramdd(x, bins=0) + + def test_histogramdd_invalid_bins3(self): + for xp in (numpy, cupy): + x = testing.shaped_random((16, 2), xp, scale=100) + bins = xp.arange(100) + bins[30] = 99 # non-ascending bins + with pytest.raises(ValueError): + y, bin_edges = xp.histogramdd(x, bins=bins) + + def test_histogramdd_invalid_bins4(self): + for xp in (numpy, cupy): + x = testing.shaped_random((16, 2), xp, scale=100) + bins = xp.arange(64).reshape((8, 8)) # too many dimensions + with pytest.raises(ValueError): + y, bin_edges = xp.histogramdd(x, bins=bins) + + def test_histogramdd_invalid_range(self): + for xp in (numpy, cupy): + x = testing.shaped_random((16, 2), xp, scale=100) + r = ((0, 100),) * 3 + with pytest.raises(ValueError): + y, bin_edges = xp.histogramdd(x, range=r) + + def test_histogramdd_disallow_arraylike_bins(self): + x = testing.shaped_random((16, 2), cupy, scale=100) + bins = [[0, 10, 20, 50, 90]] * 2 # too many dimensions + with pytest.raises(ValueError): + y, bin_edges = cupy.histogramdd(x, bins=bins) + + @testing.parameterize( *testing.product( {'weights': [None, 1, 2], 'weights_dtype': [numpy.int32, numpy.float64], 'density': [True, False], - 'bins': [10, (8, 16), (16, 8), 'array'], + 'bins': [10, (8, 16), (16, 8), 'array_list', 'array'], 'range': [None, ((20, 50), (10, 100))]} ) ) @@ -534,8 +579,10 @@ class TestHistogram2d(unittest.TestCase): def test_histogram2d(self, xp, dtype): x = testing.shaped_random((100, ), xp, dtype, scale=100) y = testing.shaped_random((100, ), xp, dtype, scale=100) - if self.bins == 'array': + if self.bins == 'array_list': bins = [xp.arange(0, 100, 4), xp.arange(0, 100, 10)] + elif self.bins == 'array': + bins = xp.arange(0, 100, 4) else: bins = self.bins if self.weights is not None: @@ -546,3 +593,14 @@ def test_histogram2d(self, xp, dtype): range=self.range, weights=weights, density=self.density) return y, edges0, edges1 + + +@testing.gpu +class TestHistogram2dErrors(unittest.TestCase): + + def test_histogram2d_disallow_arraylike_bins(self): + x = testing.shaped_random((16, ), cupy, scale=100) + y = testing.shaped_random((16, ), cupy, scale=100) + bins = [0, 10, 20, 50, 90] + with pytest.raises(ValueError): + y, bin_edges = cupy.histogram2d(x, y, bins=bins) From 5d01b2ab603b8f3f874edfffc668c8daeeed9aba Mon Sep 17 00:00:00 2001 From: Gregory Lee Date: Wed, 9 Sep 2020 08:31:45 -0400 Subject: [PATCH 4/6] pep8 fix --- cupy/_statistics/histogram.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cupy/_statistics/histogram.py b/cupy/_statistics/histogram.py index 7b59688f8eb..350ba572d08 100644 --- a/cupy/_statistics/histogram.py +++ b/cupy/_statistics/histogram.py @@ -283,8 +283,8 @@ def histogramdd(sample, bins=10, range=None, weights=None, density=False): Note the unusual interpretation of sample when an array_like: - * When an array, each row is a coordinate in a D-dimensional space - - such as ``histogramdd(cupy.array([p1, p2, p3]))``. + * When an array, each row is a coordinate in a D-dimensional + space - such as ``histogramdd(cupy.array([p1, p2, p3]))``. * When an array_like, each element is the list of values for single coordinate - such as ``histogramdd((X, Y, Z))``. From 2d9226d4c4f030fe207a5cc60605e1de24b58634 Mon Sep 17 00:00:00 2001 From: Gregory Lee Date: Wed, 9 Sep 2020 08:32:00 -0400 Subject: [PATCH 5/6] DOC: update statistics API docs --- docs/source/reference/statistics.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/source/reference/statistics.rst b/docs/source/reference/statistics.rst index 5354b105de9..78168123e1b 100644 --- a/docs/source/reference/statistics.rst +++ b/docs/source/reference/statistics.rst @@ -15,6 +15,7 @@ Order statistics cupy.nanmin cupy.nanmax cupy.percentile + cupy.ptp Means and variances @@ -41,7 +42,10 @@ Histograms :nosignatures: cupy.histogram + cupy.histogram2d + cupy.histogramdd cupy.bincount + cupy.digitize Correlations From 7aaff5f6fa9b6943c53e868d8fa8536cbce56bd0 Mon Sep 17 00:00:00 2001 From: Gregory Lee Date: Wed, 9 Sep 2020 11:30:35 -0400 Subject: [PATCH 6/6] consistently use single quotes in histogram.py --- cupy/_statistics/histogram.py | 38 +++++++++++++++++------------------ 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/cupy/_statistics/histogram.py b/cupy/_statistics/histogram.py index 350ba572d08..2c26b47a217 100644 --- a/cupy/_statistics/histogram.py +++ b/cupy/_statistics/histogram.py @@ -63,14 +63,14 @@ def _ravel_and_check_weights(a, weights): # Ensure that the array is a "subtractable" dtype if a.dtype == cupy.bool_: - warnings.warn("Converting input from {} to {} for compatibility." + warnings.warn('Converting input from {} to {} for compatibility.' .format(a.dtype, cupy.uint8), RuntimeWarning, stacklevel=3) a = a.astype(cupy.uint8) if weights is not None: if not isinstance(weights, cupy.ndarray): - raise ValueError("weights must be a cupy.ndarray") + raise ValueError('weights must be a cupy.ndarray') if weights.shape != a.shape: raise ValueError( 'weights should have the same shape as a.') @@ -91,7 +91,7 @@ def _get_outer_edges(a, range): 'max must be larger than min in range parameter.') if not (numpy.isfinite(first_edge) and numpy.isfinite(last_edge)): raise ValueError( - "supplied range of [{}, {}] is not finite".format( + 'supplied range of [{}, {}] is not finite'.format( first_edge, last_edge)) elif a.size == 0: first_edge = 0.0 @@ -101,7 +101,7 @@ def _get_outer_edges(a, range): last_edge = float(a.max()) if not (cupy.isfinite(first_edge) and cupy.isfinite(last_edge)): raise ValueError( - "autodetected range of [{}, {}] is not finite".format( + 'autodetected range of [{}, {}] is not finite'.format( first_edge, last_edge)) # expand empty range to avoid divide by zero @@ -130,7 +130,7 @@ def _get_bin_edges(a, bins, range): if isinstance(bins, str): raise NotImplementedError( - "only integer and array bins are implemented") + 'only integer and array bins are implemented') elif isinstance(bins, cupy.ndarray) or numpy.ndim(bins) == 1: # TODO(okuta): After #3060 is merged, `if cupy.ndim(bins) == 1:`. if isinstance(bins, cupy.ndarray): @@ -209,7 +209,7 @@ def histogram(x, bins=10, range=None, weights=None, density=False): raise NotImplementedError('complex number is not supported') if not isinstance(x, cupy.ndarray): - raise ValueError("x must be a cupy.ndarray") + raise ValueError('x must be a cupy.ndarray') x, weights = _ravel_and_check_weights(x, weights) bin_edges = _get_bin_edges(x, bins, range) @@ -252,8 +252,8 @@ def histogram(x, bins=10, range=None, weights=None, density=False): if not simple_weights: # object dtype such as Decimal are supported in NumPy, but not here raise NotImplementedError( - "only weights with dtype that can be cast to float or complex " - "are supported") + 'only weights with dtype that can be cast to float or complex ' + 'are supported') if weights.dtype.kind == 'c': y = cupy.zeros(bin_edges.size - 1, dtype=cupy.complex128) _weighted_histogram_kernel( @@ -340,8 +340,8 @@ def histogramdd(sample, bins=10, range=None, weights=None, density=False): nbins = len(bins) if nbins != ndim: raise ValueError( - "The dimension of bins must be equal to the dimension of the " - " sample x." + 'The dimension of bins must be equal to the dimension of the ' + ' sample x.' ) except TypeError: # bins is an integer @@ -351,30 +351,30 @@ def histogramdd(sample, bins=10, range=None, weights=None, density=False): if range is None: range = (None,) * ndim elif len(range) != ndim: - raise ValueError("range argument must have one entry per dimension") + raise ValueError('range argument must have one entry per dimension') # Create edge arrays for i in _range(ndim): if cupy.ndim(bins[i]) == 0: if bins[i] < 1: raise ValueError( - "`bins[{}]` must be positive, when an integer".format(i) + '`bins[{}]` must be positive, when an integer'.format(i) ) smin, smax = _get_outer_edges(sample[:, i], range[i]) num = int(bins[i] + 1) # synchronize! edges[i] = cupy.linspace(smin, smax, num) elif cupy.ndim(bins[i]) == 1: if not isinstance(bins[i], cupy.ndarray): - raise ValueError("array-like bins not supported") + raise ValueError('array-like bins not supported') edges[i] = bins[i] if (edges[i][:-1] > edges[i][1:]).any(): # synchronize! raise ValueError( - "`bins[{}]` must be monotonically increasing, when an " - "array".format(i) + '`bins[{}]` must be monotonically increasing, when an ' + 'array'.format(i) ) else: raise ValueError( - "`bins[{}]` must be a scalar or 1d array".format(i) + '`bins[{}]` must be a scalar or 1d array'.format(i) ) nbin[i] = len(edges[i]) + 1 # includes an outlier on each end @@ -383,7 +383,7 @@ def histogramdd(sample, bins=10, range=None, weights=None, density=False): # Compute the bin number each sample falls into. ncount = tuple( # avoid cupy.digitize to work around NumPy issue gh-11022 - cupy.searchsorted(edges[i], sample[:, i], side="right") + cupy.searchsorted(edges[i], sample[:, i], side='right') for i in _range(ndim) ) @@ -424,7 +424,7 @@ def histogramdd(sample, bins=10, range=None, weights=None, density=False): hist /= s if any(hist.shape != numpy.asarray(nbin) - 2): - raise RuntimeError("Internal Shape Error") + raise RuntimeError('Internal Shape Error') return hist, edges @@ -477,7 +477,7 @@ def histogram2d(x, y, bins=10, range=None, weights=None, density=None): xedges = yedges = bins bins = [xedges, yedges] else: - raise ValueError("array-like bins not supported in CuPy") + raise ValueError('array-like bins not supported in CuPy') hist, edges = histogramdd([x, y], bins, range, weights, density) return hist, edges[0], edges[1]