Repository: pandas-dev/pandas

Issue:
qcut raising TypeError for boolean Series
#### Code Sample, a copy-pastable example if possible

```python
import pandas as pd
pd.qcut(pd.Series([True, False, False, False, False, False, True]), 6, duplicates="drop", precision=2)
```
#### Problem description

Pandas throws a TypeError:
```
Traceback (most recent call last):
  File "/tmp/pandas/env/lib/python3.5/site-packages/numpy/core/fromnumeric.py", line 52, in _wrapfunc
    return getattr(obj, method)(*args, **kwds)
TypeError: Cannot cast ufunc multiply output from dtype('float64') to dtype('bool') with casting rule 'same_kind'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/tmp/pandas/env/src/pandas/pandas/core/reshape/tile.py", line 210, in qcut
    dtype=dtype, duplicates=duplicates)
  File "/tmp/pandas/env/src/pandas/pandas/core/reshape/tile.py", line 254, in _bins_to_cuts
    dtype=dtype)
  File "/tmp/pandas/env/src/pandas/pandas/core/reshape/tile.py", line 351, in _format_labels
    precision = _infer_precision(precision, bins)
  File "/tmp/pandas/env/src/pandas/pandas/core/reshape/tile.py", line 429, in _infer_precision
    levels = [_round_frac(b, precision) for b in bins]
  File "/tmp/pandas/env/src/pandas/pandas/core/reshape/tile.py", line 429, in <listcomp>
    levels = [_round_frac(b, precision) for b in bins]
  File "/tmp/pandas/env/src/pandas/pandas/core/reshape/tile.py", line 422, in _round_frac
    return np.around(x, digits)
  File "/tmp/pandas/env/lib/python3.5/site-packages/numpy/core/fromnumeric.py", line 2837, in around
    return _wrapfunc(a, 'round', decimals=decimals, out=out)
  File "/tmp/pandas/env/lib/python3.5/site-packages/numpy/core/fromnumeric.py", line 62, in _wrapfunc
    return _wrapit(obj, method, *args, **kwds)
  File "/tmp/pandas/env/lib/python3.5/site-packages/numpy/core/fromnumeric.py", line 42, in _wrapit
    result = getattr(asarray(obj), method)(*args, **kwds)
TypeError: Cannot cast ufunc multiply output from dtype('float64') to dtype('bool') with casting rule 'same_kind'
```

If the second parameter for qcut is changed from 6 to 7, a different TypeError is raised:

```
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/tmp/pandas/env/src/pandas/pandas/core/reshape/tile.py", line 207, in qcut
    bins = algos.quantile(x, quantiles)
  File "/tmp/pandas/env/src/pandas/pandas/core/algorithms.py", line 903, in quantile
    return algos.arrmap_float64(q, _get_score)
  File "pandas/_libs/algos_common_helper.pxi", line 416, in pandas._libs.algos.arrmap_float64
  File "/tmp/pandas/env/src/pandas/pandas/core/algorithms.py", line 888, in _get_score
    idx % 1)
  File "/tmp/pandas/env/src/pandas/pandas/core/algorithms.py", line 876, in _interpolate
    return a + (b - a) * fraction
TypeError: numpy boolean subtract, the `-` operator, is deprecated, use the bitwise_xor, the `^` operator, or the logical_xor function instead.
```

#### Expected Output

Something like

```
0      (0.29, 1.0]
1    (-0.01, 0.29]
2    (-0.01, 0.29]
3    (-0.01, 0.29]
4    (-0.01, 0.29]
5    (-0.01, 0.29]
6      (0.29, 1.0]
dtype: category
Categories (2, interval[float64]): [(-0.01, 0.29] < (0.29, 1.0]]
```

#### Output of ``pd.show_versions()``

Using Pandas 0.23.0.dev0+516.g74e6c78, also reproducable with 0.22.0. In Pandas 0.20.3, the first TypeError is also reproducable, but the second command (with 7 instead of 6) works.

<details>

INSTALLED VERSIONS
------------------
commit: None
python: 3.5.2.final.0
python-bits: 64
OS: Linux
OS-release: 4.13.0-36-generic
machine: x86_64
processor: x86_64
byteorder: little
LC_ALL: None
LANG: de_DE.UTF-8
LOCALE: de_DE.UTF-8

pandas: 0.23.0.dev0+516.g74e6c78
pytest: None
pip: 9.0.1
setuptools: 38.5.2
Cython: 0.27.3
numpy: 1.14.1
scipy: None
pyarrow: None
xarray: None
IPython: None
sphinx: None
patsy: None
dateutil: 2.7.0
pytz: 2018.3
blosc: None
bottleneck: None
tables: None
numexpr: None
feather: None
matplotlib: None
openpyxl: None
xlrd: None
xlwt: None
xlsxwriter: None
lxml: None
bs4: None
html5lib: None
sqlalchemy: None
pymysql: None
psycopg2: None
jinja2: None
s3fs: None
fastparquet: None
pandas_gbq: None
pandas_datareader: None

</details>


Discussion:
I suppose could coerce bool types to int in the code. a PR to do this would be great.
```
In [12]: pd.qcut(pd.Series([True, False, False, False, False, False, True]).astype(int), 7, duplicates="drop", precision=2)
Out[12]: 
0      (0.29, 1.0]
1    (-0.01, 0.29]
2    (-0.01, 0.29]
3    (-0.01, 0.29]
4    (-0.01, 0.29]
5    (-0.01, 0.29]
6      (0.29, 1.0]
dtype: category
Categories (2, interval[float64]): [(-0.01, 0.29] < (0.29, 1.0]]
```
I'd be willing to tackle this issue as my first contribution if no one else is already working on it 

@jreback does this still require  a PR - if so, im happy to contribute 
sure #20400 was almost there but didn’t get merged

Resolving patch:
diff --git a/pandas/core/reshape/tile.py b/pandas/core/reshape/tile.py
--- a/pandas/core/reshape/tile.py
+++ b/pandas/core/reshape/tile.py
@@ -11,6 +11,7 @@
 from pandas.core.dtypes.common import (
     _NS_DTYPE,
     ensure_int64,
+    is_bool_dtype,
     is_categorical_dtype,
     is_datetime64_dtype,
     is_datetime64tz_dtype,
@@ -423,8 +424,8 @@ def _bins_to_cuts(
 
 def _coerce_to_type(x):
     """
-    if the passed data is of datetime/timedelta type,
-    this method converts it to numeric so that cut method can
+    if the passed data is of datetime/timedelta or bool type,
+    this method converts it to numeric so that cut or qcut method can
     handle it
     """
     dtype = None
@@ -437,6 +438,9 @@ def _coerce_to_type(x):
     elif is_timedelta64_dtype(x):
         x = to_timedelta(x)
         dtype = np.dtype("timedelta64[ns]")
+    elif is_bool_dtype(x):
+        # GH 20303
+        x = x.astype(np.int64)
 
     if dtype is not None:
         # GH 19768: force NaT to NaN during integer conversion


Test patch:
diff --git a/pandas/tests/reshape/test_cut.py b/pandas/tests/reshape/test_cut.py
--- a/pandas/tests/reshape/test_cut.py
+++ b/pandas/tests/reshape/test_cut.py
@@ -585,3 +585,21 @@ def test_timedelta_cut_roundtrip():
         ["0 days 23:57:07.200000", "2 days 00:00:00", "3 days 00:00:00"]
     )
     tm.assert_index_equal(result_bins, expected_bins)
+
+
+@pytest.mark.parametrize("bins", [6, 7])
+@pytest.mark.parametrize(
+    "box, compare",
+    [
+        (Series, tm.assert_series_equal),
+        (np.array, tm.assert_categorical_equal),
+        (list, tm.assert_equal),
+    ],
+)
+def test_cut_bool_coercion_to_int(bins, box, compare):
+    # issue 20303
+    data_expected = box([0, 1, 1, 0, 1] * 10)
+    data_result = box([False, True, True, False, True] * 10)
+    expected = cut(data_expected, bins, duplicates="drop")
+    result = cut(data_result, bins, duplicates="drop")
+    compare(result, expected)
diff --git a/pandas/tests/reshape/test_qcut.py b/pandas/tests/reshape/test_qcut.py
--- a/pandas/tests/reshape/test_qcut.py
+++ b/pandas/tests/reshape/test_qcut.py
@@ -236,3 +236,21 @@ def test_date_like_qcut_bins(arg, expected_bins):
     ser = Series(arg)
     result, result_bins = qcut(ser, 2, retbins=True)
     tm.assert_index_equal(result_bins, expected_bins)
+
+
+@pytest.mark.parametrize("bins", [6, 7])
+@pytest.mark.parametrize(
+    "box, compare",
+    [
+        (Series, tm.assert_series_equal),
+        (np.array, tm.assert_categorical_equal),
+        (list, tm.assert_equal),
+    ],
+)
+def test_qcut_bool_coercion_to_int(bins, box, compare):
+    # issue 20303
+    data_expected = box([0, 1, 1, 0, 1] * 10)
+    data_result = box([False, True, True, False, True] * 10)
+    expected = qcut(data_expected, bins, duplicates="drop")
+    result = qcut(data_result, bins, duplicates="drop")
+    compare(result, expected)