Repository: pandas-dev/pandas

Issue:
BUG: .extractall() throws AssertionError if capture group length > 1
Code to replicate error:

```
import pandas as pd
s = pd.Series(["a13a23", "b13", "c13"], index=["A", "B", "C"])
s.str.extractall("[ab](\d\d)")
```

Note that the regex `[ab](\d)` from the documentation page works, whereas `[ab](\d\d)` above doesn't. It seems that any captured group with a length of > 1 causes this error.

Though playing with this a bit more, the following regex's all seem to work correctly without error:

```
([ab])(\d\d)
()[ab](\d+)
(a13)(\d\d)
```

I've reproduced the issue in both versions 0.18.0 and 0.18.1. I'll admit I've not checked against the master branch though.

Note: I posted this to the mailing list, but haven't had any responses - thus I assume this is a bug.
I'm unsure what the underlying cause is here (maybe it doesn't like the first regex character not being within a capture group?).



Discussion:
cc @tdhock

yeah this looks buggy. welcome to have you step thru and see where the error is / pull-request.

I've narrowed it down to the list comprehension on [this](https://github.com/pydata/pandas/blob/master/pandas/core/strings.py#L711) line. When the tuple is of length 1, it separates out the _string_ into individual chars, rather than iterating over the (single) tuple value(s).

```
>>> [x for x in ('ab', 'xy')]
['ab', 'xy']
>>> [x for x in ('ab')]
['a', 'b']
```

I suppose code can be added beforehand to check for this particular case (though checking the length has the same issue - both examples above are length 2), but is there a cleaner fix?

Checking the total number of groups in the regex match might be an alternative. 

you can just make a list of the capture groups i think

```
In [9]: [ x for x in ('ab')]
Out[9]: ['a', 'b']

In [10]: [ x for x in ['ab']]
Out[10]: ['ab']
```


Resolving patch:
diff --git a/pandas/core/strings.py b/pandas/core/strings.py
--- a/pandas/core/strings.py
+++ b/pandas/core/strings.py
@@ -708,6 +708,8 @@ def str_extractall(arr, pat, flags=0):
                 subject_key = (subject_key, )
 
             for match_i, match_tuple in enumerate(regex.findall(subject)):
+                if isinstance(match_tuple, compat.string_types):
+                    match_tuple = (match_tuple,)
                 na_tuple = [np.NaN if group == "" else group
                             for group in match_tuple]
                 match_list.append(na_tuple)


Test patch:
diff --git a/pandas/tests/test_strings.py b/pandas/tests/test_strings.py
--- a/pandas/tests/test_strings.py
+++ b/pandas/tests/test_strings.py
@@ -977,6 +977,20 @@ def test_extractall_single_group(self):
         e = DataFrame(['a', 'b', 'd', 'c'], i)
         tm.assert_frame_equal(r, e)
 
+    def test_extractall_single_group_with_quantifier(self):
+        # extractall(one un-named group with quantifier) returns
+        # DataFrame with one un-named column (GH13382).
+        s = Series(['ab3', 'abc3', 'd4cd2'], name='series_name')
+        r = s.str.extractall(r'([a-z]+)')
+        i = MultiIndex.from_tuples([
+            (0, 0),
+            (1, 0),
+            (2, 0),
+            (2, 1),
+        ], names=(None, "match"))
+        e = DataFrame(['ab', 'abc', 'd', 'cd'], i)
+        tm.assert_frame_equal(r, e)
+
     def test_extractall_no_matches(self):
         s = Series(['a3', 'b3', 'd4c2'], name='series_name')
         # one un-named group.