Repository: pandas-dev/pandas

Issue:
API: boolean dtype upsets cumsum
cumsum seems to require skipna=False otherwise it sulks here. (Not investigate which others are also affected, cumprod is though).

```
In [10]: b = pd.Series([False, False, False, True, True, False, False])

In [11]: b
Out[11]:
0    False
1    False
2    False
3     True
4     True
5    False
6    False
dtype: bool

In [12]: b.cumsum()
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-12-f3f684a93525> in <module>()
----> 1 b.cumsum()

/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/pandas/core/series.pyc in cumsum(self, axis, dtype, out, skipna)
   1626
   1627         if do_mask:
-> 1628             np.putmask(result, mask, pa.NA)
   1629
   1630         return Series(result, index=self.index)

ValueError: cannot convert float NaN to integer

In [13]: b.cumsum(skipna=False)
Out[13]:
0    0
1    0
2    0
3    1
4    2
5    2
6    2
dtype: int64
```

If it has nans or you int or object it works as expected:

```
In [21]: b.astype(int).cumsum()
In [22]: b.astype(object).cumsum()  # False at the beginning is expected
In [23]: b.astype(int).astype(object).cumsum()
```

Also, if you try and inset an nan it doesn't work nor raise (!):

```
In [31]: b.loc[0] = np.nan

In [32]: b
Out[32]:
0     True
1    False
2    False
3     True
4     True
5    False
6    False
dtype: bool
```



Discussion:
silently converts `nan` to `True`, i guess that's to preserve dtypes...is it the convention to treat `nan` as "truthy"?


Resolving patch:
diff --git a/pandas/core/series.py b/pandas/core/series.py
--- a/pandas/core/series.py
+++ b/pandas/core/series.py
@@ -1654,7 +1654,8 @@ def cumsum(self, axis=0, dtype=None, out=None, skipna=True):
         """
         arr = self.values.copy()
 
-        do_mask = skipna and not issubclass(self.dtype.type, np.integer)
+        do_mask = skipna and not issubclass(self.dtype.type,
+                                            (np.integer, np.bool_))
         if do_mask:
             mask = isnull(arr)
             np.putmask(arr, mask, 0.)
@@ -1683,7 +1684,8 @@ def cumprod(self, axis=0, dtype=None, out=None, skipna=True):
         """
         arr = self.values.copy()
 
-        do_mask = skipna and not issubclass(self.dtype.type, np.integer)
+        do_mask = skipna and not issubclass(self.dtype.type,
+                                            (np.integer, np.bool_))
         if do_mask:
             mask = isnull(arr)
             np.putmask(arr, mask, 1.)


Test patch:
diff --git a/pandas/tests/test_series.py b/pandas/tests/test_series.py
--- a/pandas/tests/test_series.py
+++ b/pandas/tests/test_series.py
@@ -3963,6 +3963,39 @@ def test_bfill(self):
         ts[2] = np.NaN
         assert_series_equal(ts.bfill(), ts.fillna(method='bfill'))
 
+    def test_cummethods_bool(self):
+        def cummin(x):
+            return np.minimum.accumulate(x)
+
+        def cummax(x):
+            return np.maximum.accumulate(x)
+
+        from itertools import product
+        a = pd.Series([False, False, False, True, True, False, False])
+        b = ~a
+        c = pd.Series([False] * len(b))
+        d = ~c
+        methods = {'cumsum': np.cumsum, 'cumprod': np.cumprod,
+                   'cummin': cummin, 'cummax': cummax}
+        args = product((a, b, c, d), methods)
+        for s, method in args:
+            expected = Series(methods[method](s.values))
+            result = getattr(s, method)()
+            assert_series_equal(result, expected)
+
+        e = pd.Series([False, True, nan, False])
+        cse = pd.Series([0, 1, nan, 1], dtype=object)
+        cpe = pd.Series([False, 0, nan, 0])
+        cmin = pd.Series([False, False, nan, False])
+        cmax = pd.Series([False, True, nan, True])
+        expecteds = {'cumsum': cse, 'cumprod': cpe, 'cummin': cmin,
+                     'cummax': cmax}
+
+        for method in methods:
+            res = getattr(e, method)()
+            assert_series_equal(res, expecteds[method])
+
+
     def test_replace(self):
         N = 100
         ser = Series(np.random.randn(N))