pandas-dev/pandas · error · ValueError

Invalid side: . Side must be one of 'left', 'right', 'both

Error message

Invalid side: {side}. Side must be one of 'left', 'right', 'both'

What it means

ValueError raised in _str_pad of the ArrowStringArray mixin when the side argument is not one of 'left', 'right', or 'both'. The pyarrow backend uses 'both' for center-padding; 'center' is not accepted.

Solutions

  1. Use side='both' for center padding: s.str.pad(10, side='both').
  2. Use the higher-level s.str.center(10) which maps to side='both' internally.
  3. Confirm the Series dtype; the same call works on object dtype but pyarrow enforces the documented enum.

Example fix

# before
s.str.pad(10, side='center')
# after
s.str.center(10)  # or s.str.pad(10, side='both')
Defensive patterns

Strategy: validation

Validate before calling

VALID_SIDES = {'left', 'right', 'both'}
def safe_pad(s, width, side, fillchar=' '):
    if side not in VALID_SIDES:
        side = 'both' if side == 'center' else 'left'
    return s.str.pad(width, side=side, fillchar=fillchar)

Try / catch

try:
    s.str.pad(10, side=side)
except ValueError as e:
    if 'Invalid side' in str(e):
        s.str.center(10)  # map center->both
    else:
        raise

Prevention

When it happens

Trigger: s.str.pad(width=10, side='center') on a pyarrow-backed string Series (dtype StringDtype(storage='pyarrow')).

Common situations: Code that worked on object dtype strings and used 'center' verbatim; users assuming pandas mirrors Python's str.center naming.

Related errors


AI-assisted analysis of pandas-dev/pandas@3b7651241d (2026-08-11). Data as JSON: /api/errors/8fdc20005d7b61a6. Report an issue: GitHub.

Appendix: source

Thrown at pandas/core/arrays/_arrow_string_mixins.py:166

            pa_pad = pc.utf8_lpad
        elif side == "right":
            pa_pad = pc.utf8_rpad
        elif side == "both":
            if pa_version_under17p0:
                # GH#59624 fall back to object dtype
                from pandas import array

                obj_arr = self.astype(object, copy=False)  # type: ignore[attr-defined]
                obj = array(obj_arr, dtype=object)
                result = obj._str_pad(width, side, fillchar)  # type: ignore[attr-defined]
                return type(self)._from_sequence(result, dtype=self.dtype)  # type: ignore[attr-defined]
            else:
                # GH#54792
                # https://github.com/apache/arrow/issues/15053#issuecomment-2317032347
                lean_left = (width % 2) == 0
                pa_pad = partial(pc.utf8_center, lean_left_on_odd_padding=lean_left)
        else:
            raise ValueError(
                f"Invalid side: {side}. Side must be one of 'left', 'right', 'both'"
            )
        return self._from_pyarrow_array(
            pa_pad(self._pa_array, width=width, padding=fillchar)
        )

    def _str_zfill(self, width: int) -> Self:
        if pa_version_under21p0:
            predicate = lambda val: val.zfill(width)
            result = self._apply_elementwise(predicate)
            return self._from_pyarrow_array(pa.chunked_array(result))
        return self._from_pyarrow_array(pc.utf8_zfill(self._pa_array, width))

    def _str_normalize(self, form: Literal["NFC", "NFD", "NFKC", "NFKD"]) -> Self:
        if form not in ("NFC", "NFD", "NFKC", "NFKD"):
            raise ValueError("invalid normalization form")
        if form in ("NFC", "NFKC"):
            # GH#64359 pc.utf8_normalize only decomposes; it skips the canonical

View on GitHub (pinned to 3b7651241d)