{"record":{"id":"39a606025cc08c5c","repo":"pandas-dev/pandas","slug":"pat-must-contain-a-symbolic-group-name","errorCode":null,"errorMessage":"{pat=} must contain a symbolic group name.","messagePattern":"(.+?) must contain a symbolic group name\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"pandas/core/arrays/arrow/array.py","lineNumber":3669,"sourceCode":"        result = self._apply_elementwise(predicate)\n        return self._from_pyarrow_array(pa.chunked_array(result))\n\n    def _str_casefold(self) -> Self:\n        predicate = lambda val: val.casefold()\n        result = self._apply_elementwise(predicate)\n        return self._from_pyarrow_array(pa.chunked_array(result))\n\n    def _str_encode(self, encoding: str, errors: str = \"strict\") -> Self:\n        predicate = lambda val: val.encode(encoding, errors)\n        result = self._apply_elementwise(predicate)\n        return self._from_pyarrow_array(pa.chunked_array(result))\n\n    def _str_extract(self, pat: str, flags: int = 0, expand: bool = True):\n        if flags:\n            raise NotImplementedError(\"Only flags=0 is implemented.\")\n        groups = re.compile(pat).groupindex.keys()\n        if len(groups) == 0:\n            raise ValueError(f\"{pat=} must contain a symbolic group name.\")\n        result = pc.extract_regex(self._pa_array, pat)\n        if expand:\n            return {\n                col: self._from_pyarrow_array(pc.struct_field(result, [i]))\n                for col, i in zip(groups, range(result.type.num_fields), strict=True)\n            }\n        else:\n            return type(self)(pc.struct_field(result, [0]))\n\n    def _str_findall(self, pat: str, flags: int = 0) -> Self:\n        regex = re.compile(pat, flags=flags)\n        predicate = lambda val: regex.findall(val)\n        result = self._apply_elementwise(predicate)\n        return self._from_pyarrow_array(pa.chunked_array(result))\n\n    def _str_get_dummies(self, sep: str = \"|\", dtype: NpDtype | None = None):\n        if dtype is None:\n            dtype = np.bool_","sourceCodeStart":3651,"sourceCodeEnd":3687,"githubUrl":"https://github.com/pandas-dev/pandas/blob/71959b8cb9b2459c16e14b34f28b178ccfe14735/pandas/core/arrays/arrow/array.py#L3651-L3687","documentation":"Raised by ArrowExtensionArray._str_extract when the compiled regex has no symbolic (named) groups. The ArrowExtensionArray implementation builds result columns keyed by `re.compile(pat).groupindex.keys()`, so at least one named capture group `(?P<name>...)` is mandatory. An unnamed group or a groupless pattern is rejected with ValueError before any extraction runs.","triggerScenarios":"Calling `s.str.extract(r\"(\\\\d+)\")` (unnamed group) or `s.str.extract(r\"\\\\d+\")` (no group at all) on a pyarrow-backed string Series. With expand=True (the default) on this backend, every capture must be named.","commonSituations":"Patterns authored for object/string[python] dtypes where unnamed groups returned numeric columns (0, 1, ...). Copy-pasting regex from another language/tool that uses positional groups. Using str.extract for boolean matching instead of str.contains.","solutions":["Add a symbolic name to the group: `s.str.extract(r\"(?P<num>\\\\d+)\")`.","If you only need a boolean/match, use `s.str.contains(pat)` or `s.str.match(pat)` instead of str.extract.","If positional columns are required, cast to string[python]/object: `s.astype(\"string[python]\").str.extract(r\"(\\\\d+)\")`.","Name every group when you have multiple captures so each column is addressable."],"exampleFix":"# before\ns = pd.Series([\"a1\",\"b2\"], dtype=\"string[pyarrow]\")\ns.str.extract(r\"(\\\\d+)\")  # ValueError\n\n# after\ns.str.extract(r\"(?P<digit>\\\\d+)\")","handlingStrategy":"validation","validationCode":"import re\n\ndef has_named_group(pat: str) -> bool:\n    return len(re.compile(pat).groupindex) > 0\n\ndef safe_extract(s, pat):\n    if not has_named_group(pat):\n        raise ValueError(f\"pattern {pat!r} needs at least one named group (?P<name>...)\")\n    return s.str.extract(pat)","typeGuard":"import re\n\ndef is_named_group_pattern(pat: str) -> bool:\n    try:\n        return len(re.compile(pat).groupindex) > 0\n    except re.error:\n        return False","tryCatchPattern":"try:\n    out = s.str.extract(pat)\nexcept ValueError as e:\n    if \"symbolic group name\" in str(e):\n        # add a default name and retry\n        out = s.str.extract(f\"(?P<g0>{pat})\")\n    else:\n        raise","preventionTips":["Always use (?P<name>...) for capture groups in str.extract patterns.","Lint regex patterns used with str.extract to require named groups.","Reserve str.contains/str.match for boolean matching, not str.extract."],"tags":["pyarrow","string-accessor","regex","validation"],"analyzedSha":"71959b8cb9b2459c16e14b34f28b178ccfe14735","analyzedAt":"2026-08-07T01:30:20.476Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}