Repository: pandas-dev/pandas

Issue:
cut/qcut "retbins" broken in 0.15.0
Works in 0.14.1:

``` python
In [11]: s = pd.Series(np.random.randn(100))

In [12]: pd.cut(s, 10, retbins=True)
Out[12]: 
(   (0.993, 1.376]
  (-0.924, -0.541]
   (-0.158, 0.226]
  (-1.695, -1.308]
  (-0.924, -0.541]
  (-0.541, -0.158]
   (-0.158, 0.226]
  (-0.541, -0.158]
    (0.609, 0.993]
    (0.226, 0.609]
    (0.226, 0.609]
    (0.609, 0.993]
    (0.609, 0.993]
 ...
    (0.993, 1.376]
  (-0.541, -0.158]
    (0.226, 0.609]
    (0.226, 0.609]
   (-0.158, 0.226]
    (0.226, 0.609]
   (-0.158, 0.226]
  (-0.924, -0.541]
  (-1.695, -1.308]
    (0.993, 1.376]
   (-0.158, 0.226]
  (-0.924, -0.541]
    (0.993, 1.376]
 Levels (10): Index(['(-1.695, -1.308]', '(-1.308, -0.924]',
                     '(-0.924, -0.541]', '(-0.541, -0.158]',
                     '(-0.158, 0.226]', '(0.226, 0.609]',
                     '(0.609, 0.993]', '(0.993, 1.376]', '(1.376, 1.759]',
                     '(1.759, 2.143]'], dtype=object)
 Length: 100,
 array([-1.69479925, -1.30760325, -0.92424086, -0.54087848, -0.1575161 ,
         0.22584629,  0.60920867,  0.99257106,  1.37593344,  1.75929582,
         2.14265821]))
```

but does not work in 0.15.0/current master:

```
In [13]: pd.cut(s, 10, retbins=True)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-13-958e04d69222> in <module>()
----> 1 pd.cut(s, 10, retbins=True)

/home/andrew/git/pandas-rosnfeld/pandas/tools/tile.pyc in cut(x, bins, right, labels, retbins, precision, include_lowest)
    113                         include_lowest=include_lowest)
    114     if isinstance(x, Series):
--> 115         res = Series(res, index=x.index)
    116     return res
    117 

/home/andrew/git/pandas-rosnfeld/pandas/core/series.pyc in __init__(self, data, index, dtype, name, copy, fastpath)
    210                                        raise_cast_failure=True)
    211 
--> 212                 data = SingleBlockManager(data, index, fastpath=True)
    213 
    214         generic.NDFrame.__init__(self, data, fastpath=True)

/home/andrew/git/pandas-rosnfeld/pandas/core/internals.pyc in __init__(self, block, axis, do_integrity_check, fastpath)
   3324             block = make_block(block,
   3325                                placement=slice(0, len(axis)),
-> 3326                                ndim=1, fastpath=True)
   3327 
   3328         self.blocks = [block]

/home/andrew/git/pandas-rosnfeld/pandas/core/internals.pyc in make_block(values, placement, klass, ndim, dtype, fastpath)
   2058 
   2059     return klass(values, ndim=ndim, fastpath=fastpath,
-> 2060                  placement=placement)
   2061 
   2062 

/home/andrew/git/pandas-rosnfeld/pandas/core/internals.pyc in __init__(self, values, ndim, fastpath, placement)
   1372         super(ObjectBlock, self).__init__(values, ndim=ndim,
   1373                                           fastpath=fastpath,
-> 1374                                           placement=placement)
   1375 
   1376     @property

/home/andrew/git/pandas-rosnfeld/pandas/core/internals.pyc in __init__(self, values, placement, ndim, fastpath)
     73             raise ValueError('Wrong number of items passed %d,'
     74                              ' placement implies %d' % (
---> 75                                  len(self.values), len(self.mgr_locs)))
     76 
     77     @property

ValueError: Wrong number of items passed 2, placement implies 100
```

The same goes for qcut. These work if retbins=False.

I found this while testing out the new release, wanted to take a look at the Categorical type. I should have done this with the RC and caught it sooner...



Discussion:
hmm, prob not updated (and not well tested).

pull-request to fix?

I'll take a look at it. Interestingly pandas/tools/tests/test_tile.py looks at many similar cases, but it and the API docs always use numpy.ndarrays (which work) as the input, not Series.


Resolving patch:
diff --git a/pandas/tools/tile.py b/pandas/tools/tile.py
--- a/pandas/tools/tile.py
+++ b/pandas/tools/tile.py
@@ -109,11 +109,8 @@ def cut(x, bins, right=True, labels=None, retbins=False, precision=3,
         if (np.diff(bins) < 0).any():
             raise ValueError('bins must increase monotonically.')
 
-    res = _bins_to_cuts(x, bins, right=right, labels=labels,retbins=retbins, precision=precision,
-                        include_lowest=include_lowest)
-    if isinstance(x, Series):
-        res = Series(res, index=x.index)
-    return res
+    return _bins_to_cuts(x, bins, right=right, labels=labels,retbins=retbins, precision=precision,
+                         include_lowest=include_lowest)
 
 
 
@@ -168,18 +165,21 @@ def qcut(x, q, labels=None, retbins=False, precision=3):
     else:
         quantiles = q
     bins = algos.quantile(x, quantiles)
-    res = _bins_to_cuts(x, bins, labels=labels, retbins=retbins,precision=precision,
-                        include_lowest=True)
-    if isinstance(x, Series):
-        res = Series(res, index=x.index)
-    return res
+    return _bins_to_cuts(x, bins, labels=labels, retbins=retbins,precision=precision,
+                         include_lowest=True)
 
 
 
 def _bins_to_cuts(x, bins, right=True, labels=None, retbins=False,
                   precision=3, name=None, include_lowest=False):
-    if name is None and isinstance(x, Series):
-        name = x.name
+    x_is_series = isinstance(x, Series)
+    series_index = None
+
+    if x_is_series:
+        series_index = x.index
+        if name is None:
+            name = x.name
+
     x = np.asarray(x)
 
     side = 'left' if right else 'right'
@@ -224,6 +224,9 @@ def _bins_to_cuts(x, bins, right=True, labels=None, retbins=False,
             fac = fac.astype(np.float64)
             np.putmask(fac, na_mask, np.nan)
 
+    if x_is_series:
+        fac = Series(fac, index=series_index)
+
     if not retbins:
         return fac
 


Test patch:
diff --git a/pandas/tools/tests/test_tile.py b/pandas/tools/tests/test_tile.py
--- a/pandas/tools/tests/test_tile.py
+++ b/pandas/tools/tests/test_tile.py
@@ -248,6 +248,16 @@ def test_qcut_return_categorical(self):
                                             ordered=True))
         tm.assert_series_equal(res, exp)
 
+    def test_series_retbins(self):
+        # GH 8589
+        s = Series(np.arange(4))
+        result, bins = cut(s, 2, retbins=True)
+        assert_equal(result.cat.codes.values, [0, 0, 1, 1])
+        assert_almost_equal(bins, [-0.003, 1.5, 3])
+
+        result, bins = qcut(s, 2, retbins=True)
+        assert_equal(result.cat.codes.values, [0, 0, 1, 1])
+        assert_almost_equal(bins, [0, 1.5, 3])
 
 
 def curpath():