FIX raise an error in CountVectorizer with a custom token pattern that captures several group
diff --git a/doc/whats_new/v0.24.rst b/doc/whats_new/v0.24.rst
index 9db5535415af5..15fc5ddfa5331 100644
--- a/doc/whats_new/v0.24.rst
+++ b/doc/whats_new/v0.24.rst
@@ -252,6 +252,11 @@ Changelog
   values for one categorical feature. :pr:`17367` by :user:`Peng Yu <yupbank>`
   and :user:`Chiara Marmo <cmarmo>`.
 
+- |Fix| :class:`feature_extraction.CountVectorizer` raises an issue if a
+  custom token pattern which capture more than one group is provided.
+  :pr:`15427` by :user:`Gangesh Gudmalwar <ggangesh>` and
+  :user:`Erin R Hoffman <hoffm386>`.
+
 :mod:`sklearn.feature_selection`
 ................................
 
diff --git a/sklearn/feature_extraction/tests/test_text.py b/sklearn/feature_extraction/tests/test_text.py
index 733785476a66c..6f22e70f1827a 100644
--- a/sklearn/feature_extraction/tests/test_text.py
+++ b/sklearn/feature_extraction/tests/test_text.py
@@ -341,6 +341,43 @@ def test_fit_countvectorizer_twice():
     assert X1.shape[1] != X2.shape[1]
 
 
+def test_countvectorizer_custom_token_pattern():
+    """Check `get_feature_names()` when a custom token pattern is passed.
+    Non-regression test for:
+    https://github.com/scikit-learn/scikit-learn/issues/12971
+    """
+    corpus = [
+        'This is the 1st document in my corpus.',
+        'This document is the 2nd sample.',
+        'And this is the 3rd one.',
+        'Is this the 4th document?',
+    ]
+    token_pattern = r"[0-9]{1,3}(?:st|nd|rd|th)\s\b(\w{2,})\b"
+    vectorizer = CountVectorizer(token_pattern=token_pattern)
+    vectorizer.fit_transform(corpus)
+    expected = ['document', 'one', 'sample']
+    assert vectorizer.get_feature_names() == expected
+
+
+def test_countvectorizer_custom_token_pattern_with_several_group():
+    """Check that we raise an error if token pattern capture several groups.
+    Non-regression test for:
+    https://github.com/scikit-learn/scikit-learn/issues/12971
+    """
+    corpus = [
+        'This is the 1st document in my corpus.',
+        'This document is the 2nd sample.',
+        'And this is the 3rd one.',
+        'Is this the 4th document?',
+    ]
+
+    token_pattern = r"([0-9]{1,3}(?:st|nd|rd|th))\s\b(\w{2,})\b"
+    err_msg = "More than 1 capturing group in token pattern"
+    vectorizer = CountVectorizer(token_pattern=token_pattern)
+    with pytest.raises(ValueError, match=err_msg):
+        vectorizer.fit(corpus)
+
+
 def test_tf_idf_smoothing():
     X = [[1, 1, 1],
          [1, 1, 0],
diff --git a/sklearn/feature_extraction/text.py b/sklearn/feature_extraction/text.py
index 4189125705d51..c982f86b3dd3a 100644
--- a/sklearn/feature_extraction/text.py
+++ b/sklearn/feature_extraction/text.py
@@ -340,6 +340,13 @@ def build_tokenizer(self):
         if self.tokenizer is not None:
             return self.tokenizer
         token_pattern = re.compile(self.token_pattern)
+
+        if token_pattern.groups > 1:
+            raise ValueError(
+                "More than 1 capturing group in token pattern. Only a single "
+                "group should be captured."
+            )
+
         return token_pattern.findall
 
     def get_stop_words(self):
@@ -605,6 +612,10 @@ class HashingVectorizer(TransformerMixin, _VectorizerMixin, BaseEstimator):
         or more alphanumeric characters (punctuation is completely ignored
         and always treated as a token separator).
 
+        If there is a capturing group in token_pattern then the
+        captured group content, not the entire match, becomes the token.
+        At most one capturing group is permitted.
+
     ngram_range : tuple (min_n, max_n), default=(1, 1)
         The lower and upper boundary of the range of n-values for different
         n-grams to be extracted. All values of n such that min_n <= n <= max_n
@@ -872,6 +883,10 @@ class CountVectorizer(_VectorizerMixin, BaseEstimator):
         or more alphanumeric characters (punctuation is completely ignored
         and always treated as a token separator).
 
+        If there is a capturing group in token_pattern then the
+        captured group content, not the entire match, becomes the token.
+        At most one capturing group is permitted.
+
     ngram_range : tuple (min_n, max_n), default=(1, 1)
         The lower and upper boundary of the range of n-values for different
         word n-grams or char n-grams to be extracted. All values of n such
@@ -1596,6 +1611,10 @@ class TfidfVectorizer(CountVectorizer):
         or more alphanumeric characters (punctuation is completely ignored
         and always treated as a token separator).
 
+        If there is a capturing group in token_pattern then the
+        captured group content, not the entire match, becomes the token.
+        At most one capturing group is permitted.
+
     ngram_range : tuple (min_n, max_n), default=(1, 1)
         The lower and upper boundary of the range of n-values for different
         n-grams to be extracted. All values of n such that min_n <= n <= max_n

<!--
Thanks for contributing a pull request! Please ensure you have taken a look at
the contribution guidelines: https://github.com/scikit-learn/scikit-learn/blob/master/CONTRIBUTING.md#pull-request-checklist
-->

#### Reference Issues/PRs
<!--
Example: Fixes #1234. See also #3456.
Please use keywords (e.g., Fixes) to create link to the issues or pull requests
you resolved, so that they will automatically be closed when your pull request
is merged. See https://github.com/blog/1506-closing-issues-via-pull-requests
-->
Fixes #12971 

#### What does this implement/fix? Explain your changes.
1. Merges in changes from #13229, which raises a ValueError if the token pattern regex has more than one capturing group and documents the general behavior of capturing groups in the token pattern regex
2. Improves the doc for token_pattern per requested changes on #13229
3. Changes the test spec from testing 0 capturing groups to testing 1 capturing group per requested changes on #13229
4. Modifies the test corpus and regexes for `test_countvectorizer_custom_token_pattern` so they represent a more "realistic" type of custom token pattern than used in #13229.  The previous test used an example from #12971 where the custom token pattern behaved the same as the default token pattern on that particular corpus.  My new test is an adapted version of those examples that uses some of the same selectors but also (a) behaves differently than the default and (b) gives an example of what you might actually use a capturing group for.  I have modified both regexes so they meet these requirements and are almost identical, but the first has 1 capturing group and the second has 2 capturing groups.

#### Any other comments?
Same as #13229, this does not address deprecation or backward compatibility

<!--
Please be aware that we are a loose team of volunteers so patience is
necessary; assistance handling other issues is very welcome. We value
all user contributions, no matter how minor they are. If we are slow to
review, either the pull request needs some benchmarking, tinkering,
convincing, etc. or more likely the reviewers are simply busy. In either
case, we ask for your understanding during the review process.
For more information, see our FAQ on this topic:
http://scikit-learn.org/dev/faq.html#why-is-my-pull-request-not-getting-any-attention.

Thanks for contributing!
-->
