stats.comparison.complete_panel

stats.comparison.complete_panel(matrix, *, min_periods=None)

Largest complete panel obtainable under a coverage requirement.

A blocked design needs a complete matrix, but entries that joined a campaign late are scored on fewer periods than the rest. Admitting them costs periods for everyone, and excluding them costs entries. This helper makes that trade explicit and repeatable: keep the entries scored on at least min_periods periods, then keep the periods scored for all of them.

Raising min_periods admits fewer entries over a longer window, lowering it admits more entries over a shorter one. Neither direction dominates, so a campaign report is usually clearer with two or three panels bracketing the trade than with one.

This is pure: no logging, no plotting, no mutation.

Parameters

Name Type Description Default
matrix pd.DataFrame Wide period-by-entry frame, one row per period and one column per entry, with NaN where an entry was not scored. required
min_periods int | None Minimum number of scored periods an entry needs to enter the panel. Defaults to None, which admits every entry and therefore keeps only the periods scored for all of them. None

Returns

Name Type Description
pd.DataFrame pd.DataFrame: Complete sub-frame with no NaN, carrying the
pd.DataFrame admitted entries as columns and the surviving periods as rows.

Raises

Name Type Description
ValueError When min_periods is negative, or the requirement admits no entry or leaves no period.

Examples

import numpy as np
import pandas as pd
from spotforecast2_safe.stats.comparison import complete_panel

days = pd.date_range("2026-06-10", periods=6, freq="D")
matrix = pd.DataFrame(
    {
        "early": [500.0, 510, 505, 495, 515, 500],
        "late": [np.nan, np.nan, 520.0, 512, 530, 515],
    },
    index=days,
)
print(complete_panel(matrix).shape)                 # 2 entries, 4 days
print(complete_panel(matrix, min_periods=6).shape)  # 1 entry, 6 days
(4, 2)
(6, 1)