Repository: cupy/cupy

Issue:
Support list-like bins argument in histogram
As noted in https://github.com/cupy/cupy/issues/3322 , `cupy.histogram` currently expects the `bins` argument to be a cupy array, rather than an iterable sequence. It might be a nice addition to support Python lists as input.

The `numpy.histogram` argument accepts a generic sequence. Perhaps `cupy.histogram` could be enhanced to accept a list-like iterable which is then appropriately coerced (if need be) to a `cupy.array`, which is necessary for this segment of the code:

https://github.com/cupy/cupy/blob/76c6daca665890a9482d5ff66a49dbd484eb77b3/cupy/statistics/histogram.py#L211-L214

`_get_bin_edges` currently fails silently if a Python list is passed as `bins`. Support for a list-like iterable could perhaps be done in this function.

```python
import cupy as cp

arr = cp.random.randint(5,10,(10,10))
x, weights = cp.statistics.histogram._ravel_and_check_weights(arr, None)
cp.statistics.histogram._get_bin_edges(x, [5,6], None) # returns None
```





Discussion:

Resolving patch:
diff --git a/cupy/statistics/histogram.py b/cupy/statistics/histogram.py
--- a/cupy/statistics/histogram.py
+++ b/cupy/statistics/histogram.py
@@ -125,7 +125,22 @@ def _get_bin_edges(a, bins, range):
     n_equal_bins = None
     bin_edges = None
 
-    if isinstance(bins, int):  # cupy.ndim(bins) == 0:
+    if isinstance(bins, str):
+        raise NotImplementedError(
+            "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):
+            bin_edges = bins
+        else:
+            bin_edges = numpy.asarray(bins)
+
+        if (bin_edges[:-1] > bin_edges[1:]).any():  # synchronize! when CuPy
+            raise ValueError(
+                '`bins` must increase monotonically, when an array')
+        if isinstance(bin_edges, numpy.ndarray):
+            bin_edges = cupy.asarray(bin_edges)
+    elif numpy.ndim(bins) == 0:
         try:
             n_equal_bins = operator.index(bins)
         except TypeError:
@@ -135,17 +150,8 @@ def _get_bin_edges(a, bins, range):
             raise ValueError('`bins` must be positive, when an integer')
 
         first_edge, last_edge = _get_outer_edges(a, range)
-
-    elif isinstance(bins, cupy.ndarray):
-        if bins.ndim == 1:    # cupy.ndim(bins) == 0:
-            bin_edges = cupy.asarray(bins)
-            if (bin_edges[:-1] > bin_edges[1:]).any():  # synchronize!
-                raise ValueError(
-                    '`bins` must increase monotonically, when an array')
-
-    elif isinstance(bins, str):
-        raise NotImplementedError(
-            "only integer and array bins are implemented")
+    else:
+        raise ValueError('`bins` must be 1d, when an array')
 
     if n_equal_bins is not None:
         # numpy's gh-10322 means that type resolution rules are dependent on


Test patch:
diff --git a/tests/cupy_tests/statics_tests/test_histogram.py b/tests/cupy_tests/statics_tests/test_histogram.py
--- a/tests/cupy_tests/statics_tests/test_histogram.py
+++ b/tests/cupy_tests/statics_tests/test_histogram.py
@@ -232,6 +232,22 @@ def test_histogram_array_bins(self, xp, dtype):
         y, bin_edges = xp.histogram(x, bins)
         return y, bin_edges
 
+    @testing.for_all_dtypes(no_bool=True, no_complex=True)
+    @testing.numpy_cupy_array_equal()
+    def test_histogram_numpy_bins(self, xp, dtype):
+        x = testing.shaped_arange((10,), xp, dtype)
+        bins = testing.shaped_arange((3,), numpy, dtype)
+        y, bin_edges = xp.histogram(x, bins)
+        return y, bin_edges
+
+    @testing.for_all_dtypes(no_bool=True, no_complex=True)
+    @testing.numpy_cupy_array_equal()
+    def test_histogram_list_bins(self, xp, dtype):
+        x = testing.shaped_arange((10,), xp, dtype)
+        bins = list(testing.shaped_arange((3,), numpy, dtype))
+        y, bin_edges = xp.histogram(x, bins)
+        return y, bin_edges
+
     # numpy 1.13.1 does not check this error correctly with unsigned int.
     @testing.for_all_dtypes(no_bool=True, no_complex=True)
     def test_histogram_bins_not_ordered(self, dtype):