pandas-dev/pandas · error · TypeError

cannot subtract {type(other).__name__} from {type(self).__na

Error message

cannot subtract {type(other).__name__} from {type(self).__name__}

What it means

Raised by _sub_periodlike when self.dtype is not PeriodDtype. Subtracting a Period (or PeriodArray) is only defined for PeriodArray operands (yielding an object ndarray of DateOffsets); doing it against DatetimeArray or TimedeltaArray is rejected.

Source

Thrown at pandas/core/arrays/datetimelike.py:1241

        # like a timedelta.
        # For datetime64 dtypes by convention we treat NaT as a datetime, so
        # this subtraction returns a timedelta64 dtype.
        # For period dtype, timedelta64 is a close-enough return dtype.
        result = np.empty(self.shape, dtype=np.int64)
        result.fill(iNaT)
        if self.dtype.kind in "mM":
            # We can retain unit in dtype
            self = cast("DatetimeArray| TimedeltaArray", self)
            return result.view(f"timedelta64[{self.unit}]")
        else:
            return result.view("timedelta64[ns]")

    @final
    def _sub_periodlike(self, other: Period | PeriodArray) -> npt.NDArray[np.object_]:
        # If the operation is well-defined, we return an object-dtype ndarray
        # of DateOffsets.  Null entries are filled with pd.NaT
        if not isinstance(self.dtype, PeriodDtype):
            raise TypeError(
                f"cannot subtract {type(other).__name__} from {type(self).__name__}"
            )

        self = cast("PeriodArray", self)
        self._check_compatible_with(other)

        other_i8, o_mask = self._get_i8_values_and_mask(other)
        new_i8_data = add_overflowsafe(self.asi8, np.asarray(-other_i8, dtype="i8"))
        new_data = np.array([self.freq.base * x for x in new_i8_data])

        if o_mask is None:
            # i.e. Period scalar
            mask = self._isnan
        else:
            # i.e. PeriodArray
            mask = self._isnan | o_mask
        new_data[mask] = NaT
        return new_data

View on GitHub (pinned to 71959b8cb9)

Solutions

  1. Convert the Period to a Timestamp first: idx - period.to_timestamp().
  2. For PeriodIndex, use idx - other_period to get a DateOffset result.
  3. Add the appropriate DateOffset (negative) instead of subtracting a Period from a DatetimeIndex.
  4. Check isinstance(idx.dtype, pd.PeriodDtype) before subtracting a Period.

Example fix

// before
out = datetime_idx - pd.Period('2020-01', 'M')  # TypeError
// after
out = datetime_idx - pd.Period('2020-01', 'M').to_timestamp()
Defensive patterns

Strategy: type-guard

Validate before calling

from pandas.api.types import is_period_dtype
if not is_period_dtype(idx.dtype):
    out = idx - period.to_timestamp()
else:
    out = idx - period

Type guard

def accepts_period_subtraction(idx) -> bool:
    from pandas.api.types import is_period_dtype
    return is_period_dtype(idx.dtype)

Try / catch

try:
    out = idx - period
except TypeError as e:
    if 'cannot subtract' in str(e) and 'Period' in str(e):
        out = idx - period.to_timestamp()
    else:
        raise

Prevention

When it happens

Trigger: DatetimeIndex - pd.Period(...) or TimedeltaIndex - PeriodArray, dispatched via __sub__ line 1398-1399 into _sub_periodlike at line 1237; the PeriodDtype check at line 1240 fires.

Common situations: Mixing Period and datetime columns in subtraction; assuming Period behaves like a datetime or offset.

Related errors


AI-assisted analysis of pandas-dev/pandas@71959b8cb9 (2026-08-07). Data as JSON: /api/errors/89cfe4c058eead9d. Report an issue: GitHub.