pandas-dev/pandas · error · TypeError

cannot subtract from

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 to yield a frequency count (object-dtype ndarray of DateOffsets) is only defined when self is itself a PeriodArray; subtracting a Period from a DatetimeArray or TimedeltaArray is rejected.

Solutions

  1. Convert self to a PeriodArray (via .to_period(freq)) before subtracting a Period.
  2. If you want point-in-time differences, convert the Period to a Timestamp via .to_timestamp() and subtract Timestamps.
  3. Confirm dtype: only PeriodDtype - Period/PeriodArray is permitted in this path.

Example fix

// before
res = dta - pd.Period('2020', freq='D')  # TypeError

// after
res = dta.to_period('D') - pd.Period('2020', freq='D')
Defensive patterns

Strategy: type-guard

Validate before calling

import pandas as pd

def sub_period(arr, period):
    if not isinstance(arr.dtype, pd.PeriodDtype):
        raise TypeError('self must be a PeriodArray to subtract a Period')
    return arr - period

Type guard

import pandas as pd

def is_period_array(a) -> bool:
    return isinstance(getattr(a, 'dtype', None), pd.PeriodDtype)

Try / catch

try:
    res = arr - other
except TypeError as e:
    if 'cannot subtract' in str(e) and 'Period' in str(e):
        res = arr.to_period(other.freq) - other
    else:
        raise

Prevention

When it happens

Trigger: DatetimeArray - Period(...); TimedeltaArray - PeriodArray; non-Period datetimelike array minus a Period operand.

Common situations: Period vs datetime confusion in calendar/difference code; assuming Period can be subtracted from a Timestamp column to give a duration.

Related errors


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

Appendix: 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)
        # GH#66552 the difference is a count of periods, not an ordinal, so
        #  INT64_MIN is a legitimate answer here and not the NaT sentinel.
        new_i8_data = add_overflowsafe(
            self.asi8, np.asarray(-other_i8, dtype="i8"), sentinel_ok=True
        )
        # multiply by python ints: numpy's scalar multiply spuriously reports
        #  overflow for a np.int64 count of INT64_MIN on Windows
        counts = new_i8_data.ravel().tolist()
        new_data = np.array([self.freq.base * count for count in counts]).reshape(
            new_i8_data.shape
        )

View on GitHub (pinned to 3b7651241d)