pandas-dev/pandas · error · ValueError

periods must be an integer

Error message

periods must be an integer

What it means

The diff() and shift() functions require the periods argument (n) to be an integer. This check accepts Python ints, numpy integers, and floats that are whole numbers (e.g., 3.0), but rejects fractional floats, strings, None, and other non-integer types. The check was added in GH#56607 to catch invalid period values early before they cause confusing downstream behavior.

Solutions

  1. Explicitly cast n to int: df.diff(int(n)).
  2. Validate n is integer-valued before calling: if isinstance(n, float) and not n.is_integer(): raise ValueError(...).
  3. Ensure computed period values use integer division (//) rather than float division (/).

Example fix

# before
periods = len(df) / num_groups  # may produce a float
df.diff(periods)

# after
periods = len(df) // num_groups  # integer division
df.diff(periods)
Defensive patterns

Strategy: validation

Validate before calling

def safe_diff(series_or_df, periods, axis=0):
    if isinstance(periods, float):
        if not periods.is_integer():
            raise ValueError(f"periods must be integer, got {periods}")
        periods = int(periods)
    elif not isinstance(periods, (int, np.integer)):
        raise ValueError(f"periods must be integer, got {type(periods).__name__}")
    return series_or_df.diff(periods, axis=axis)

Type guard

def is_valid_periods(n) -> bool:
    if isinstance(n, (int, np.integer)):
        return True
    if isinstance(n, float) and n.is_integer():
        return True
    return False

Try / catch

try:
    result = df.diff(periods)
except ValueError as e:
    if "periods must be an integer" in str(e):
        result = df.diff(int(periods))
    else:
        raise

Prevention

When it happens

Trigger: Calling df.diff(n) or s.shift(n) where n is a float like 1.5, a string like "2", None, or NaN. Passing a periods value computed from a calculation that can produce non-integer results (e.g., division). Using a variable that is dynamically typed and may hold a non-integer.

Common situations: Computing shift periods from a formula (e.g., len(df) / num_groups) that yields a float, then passing it directly to diff(). Receiving a periods value from user input or configuration as a string. Passing np.nan as periods when a computed window size is undefined.

Related errors


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

Appendix: source

Thrown at pandas/core/algorithms.py:1521

    ----------
    arr : ndarray or ExtensionArray
    n : int
        number of periods
    axis : {0, 1}
        axis to shift on
    stacklevel : int, default 3
        The stacklevel for the lost dtype warning.

    Returns
    -------
    shifted
    """

    # added a check on the integer value of period
    # see https://github.com/pandas-dev/pandas/issues/56607
    if not lib.is_integer(n):
        if not (is_float(n) and n.is_integer()):
            raise ValueError("periods must be an integer")
        n = int(n)
    na = np.nan
    dtype = arr.dtype

    is_bool = is_bool_dtype(dtype)
    if is_bool:
        op = operator.xor
    else:
        op = operator.sub

    if isinstance(dtype, NumpyEADtype):
        # NumpyExtensionArray cannot necessarily hold shifted versions of itself.
        arr = arr.to_numpy()
        dtype = arr.dtype

    if not isinstance(arr, np.ndarray):
        # i.e ExtensionArray
        if hasattr(arr, f"__{op.__name__}__"):

View on GitHub (pinned to 3b7651241d)