Skip to content

meridian_serde.save_meridian() fails on non-uniform (e.g. calendar-monthly) time axes that InputData validation accepts #1675

Description

@farok-amo

Meridian version: 1.7.0 (also happens on older versions)
Python: 3.12

What happens

Calling save_meridian on a model with calendar-monthly time coordinates (1st of each month) raises:

ValueError: Interval length between selected times must be consistent.

The odd part is that the same model passes Meridian's own InputData validation and trains and analyses fine. Only saving to .binpb fails. So the input layer treats monthly data as valid but the serializer does not.

Reproduce

from meridian.schema.serde import meridian_serde

# mmm has monthly time coords: 2024-01-01, 2024-02-01, 2024-03-01, ...
# day gaps are 31, 29, 31, 30, ...
meridian_serde.save_meridian(mmm, "model.binpb")

Traceback (trimmed):

meridian/schema/serde/marketing_data.py in __call__(self)
--> times_to_date_intervals = time_record.convert_times_to_date_intervals(
        self._input_data.media_time.data)
meridian/schema/utils/time_record.py in convert_times_to_date_intervals(times)
--> raise ValueError("Interval length between selected times must be consistent.")

What I found

The error comes from convert_times_to_date_intervals in meridian/schema/utils/time_record.py. It takes the gap between the first two dates as the expected interval, then requires every following gap to match it exactly:

interval_length = _compute_interval_length(datetimes[0], datetimes[1])                                                                                                                                      for i, start_date in enumerate(datetimes):
    ...
    if current_interval_length != interval_length:                                                                                                                                                                  raise ValueError("Interval length between selected times must be consistent.")

Calendar months are 28/29/30/31 days, so the second gap already differs and it raises.

Meanwhile _is_regular_time_index in meridian/data/time_coordinates.py (used by InputData._validate_times) explicitly allows monthly (28 to 31), quarterly (90 to 92) and yearly (365 to 366) spacing. the two checks disagree.

As far as I can tell the format itself does not need uniform spacing. The serializer writes an explicit DateInterval (start, end) onto every data point from the actual dates, and deserialize reads each time back from its stored start date. The only place a single interval is actually neede of the last period, which has no next date to look at.

Possible fix

Drop the strict check and derive the last period's end from the final gap instead of the first one:

final_interval_length = _compute_interval_length(datetimes[-2], datetimes[-1])
for i, start_date in enumerate(datetimes):
    end_date = (start_date + timedelta(days=final_interval_length)
                if i == len(datetimes) - 1 else datetimes[i + 1])
    ...

That would accept any increasing time axis, which lines up with what InputData alreadyhe mean interval (interval_days) which already tolerates monthly, so it should be fine.

Question

Is monthly data meant to be supported here, or is the uniform interval requirement intentional for .binpb? If it is supposed to work, is this something that could be fixed? Happy to help or put up a PR if that would be useful.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions