Skip to content

cross_validate fails cleanup() with constant timeseries #2691

@landiinii

Description

@landiinii

TLDR: Missing Null check throwing uncaught error. Workaround provided. PR coming.

I discovered that when running cross_validation from prophet.diagnostics if you pass in a time series with a single constant value, or if there is both a sufficiently large enough sequence within the time series that is a constant value and a sufficiently small enough cross validation window to fit inside that sequence, then the following error will be thrown:

Traceback (most recent call last):
  File "test/test.py", line 16, in <module>
    cross_val = cross_validation(model, horizon='30 days')
  File "test/.venv/lib/python3.13/site-packages/prophet/diagnostics.py", line 207, in cross_validation
    single_cutoff_forecast(df, model, cutoff, horizon, predict_columns)
    ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "test/.venv/lib/python3.13/site-packages/prophet/diagnostics.py", line 263, in single_cutoff_forecast
    m.stan_backend.cleanup()
    ~~~~~~~~~~~~~~~~~~~~~~^^
  File "test/.venv/lib/python3.13/site-packages/prophet/models.py", line 180, in cleanup
    fit_result.runset.csv_files +
    ^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'runset'

If you want to recreate this error, this simple script will throw it (example of successful values and erroring values):

import pandas as pd
from prophet import Prophet
from prophet.diagnostics import cross_validation

dates = pd.date_range(start='2025-01-01', end='2025-12-31')
# values = [1 + i for i in range(len(dates))] # good values, will succeed
values = [i // 100 for i in range(len(dates))] # bad values, will error

model = Prophet().fit(pd.DataFrame({'ds': dates, 'y': values}))
cross_val = cross_validation(model, horizon='30 days')

This error is ultimately getting thrown in CmdStanPyBackend.cleanup() a function that was included in the most recent release (1.1.7) with #2671.

That function does a check to see if the stan_fit class attribute exists, but currently does not check if the attribute is non-null. That attribute will be None though whenever any time series (or cross validation windows within a time series) satisfies this condition: self.history['y'].min() == self.history['y'].max() and (self.growth == 'linear' or self.growth == 'flat') which will cause that neither the fit() nor the sampling() method be called on the CmdStanPyBackend instance resulting that stan_fit remain in its default state of None all the way through the cleanup step, causing this error.

As a work around to correct for this bug until a release can be made, this code block can overwrite the existing function from the user code and add the null safe check:

from prophet.models import CmdStanPyBackend
_original_cleanup = CmdStanPyBackend.cleanup
CmdStanPyBackend.cleanup = lambda self: _original_cleanup(self) if self.stan_fit is not None else None

But ultimately a patch should be merged to replace this line with:

if hasattr(self, "stan_fit") and self.stan_fit is not None:

I'll put up a PR for it.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions