cleverly.longitudinal.LongitudinalData.from_long_panel#

classmethod LongitudinalData.from_long_panel(data: Any, *, unit: str, time: str, treatment: str | Sequence[str], baseline: Sequence[str], time_varying: Sequence[str] = (), censoring: str | None = None, outcome: str | None = None, event: str | Mapping[str, str] | None = None, times: Sequence[Any] | None = None, cluster: str | None = None, weights: str | None = None, weights_type: str = 'probability', weights_estimated: bool = False, family: str = 'auto', continuous_treatment: bool = False, treatment_probabilities: Mapping[Any, str] | None = None, censoring_probabilities: str | None = None) → LongitudinalData[source]#

Build from a long panel: one row per unit and decision time.

Each row holds the covariates measured before that row’s decision, the decision, and whether the unit is still observed after it. The rows of one unit form one trajectory, and the trajectory is one observation: repeated rows add no independent observations. Units that depend on each other, such as the customers of one account, declare cluster=.

The container is the one from_frame() builds from the equivalent wide frame. Node t is the t-th decision time. A column c measured at node t is named "c@t" in history_frame() and policy_frame(), and each_node() writes a rule that reads it. Rows may arrive in any order. The container’s rows follow the sorted unit identifiers.

Parameters:
dataDataFrame

A pandas or polars frame with one row per unit and decision time.

unitstr

The unit identifier column. Identifiers must sort, so use one type.

timestr

The decision-time column. Times must sort.

treatmentstr or sequence of str

The decision at each row. A list of columns is one vector node per time.

baselinesequence of str

Unit-level covariates measured before the first decision. Each is recorded on the unit’s first row and holds one value per unit.

time_varyingsequence of str

Covariates measured at each row before that row’s decision. A measurement taken after the decision belongs to the next row. A column with no value at the first nodes, such as a lag, starts at its first recorded node.

censoringstr or None

1 where the unit is still observed after the row’s decision, and 0 on the last row of a unit that left. None means every unit has a row at every decision time, or leaves through the event.

outcomestr or None

An end-of-study outcome: one value per unit, on any of its rows.

eventstr or mapping of str to str or None

A survival outcome: an event indicator on each row, 1 on the row of the event. A mapping of cause label to column declares competing risks. Declare exactly one of outcome and event.

timessequence or None

Every decision time, in time order; the order given is the node order. None uses the times the panel holds, sorted, and needs numbers, dates or durations.

clusterstr or None

The cluster identifier, constant within a unit.

weightsstr or None

A unit-level probability weight.

weights_type{“probability”}

How to read weights, as for from_frame().

weights_estimatedbool

Whether the weights came out of a fitted model.

family{“auto”, “gaussian”, “binomial”}

The outcome family.

continuous_treatmentbool

Whether the treatment column holds a continuous dose at every node.

treatment_probabilitiesmapping of level to str, or None

The known treatment mechanism: each level mapped to the column that holds P(A_t = level | past) on each row.

censoring_probabilitiesstr or None

The column that holds P(C_t = 1 | past) on each row.

Returns:
LongitudinalData

The container, with panel and unit_ids set.

Raises:
DataError

For a missing unit or time, a duplicated (unit, time) row, a unit that starts late or skips a decision time, rows after a unit left, a unit whose rows stop without leaving, a unit-level column that varies within a unit or is first recorded after the first row, a column in two roles, a covariate that copies the outcome or the event, an event recorded on a censored row, a time-varying column that stops being recorded, and a treatment that never changes within a unit. Every check of from_frame() applies as well.

See also

to_long_panel

The inverse.

cleverly.LongPanel

The same input as a study design.

cleverly.longitudinal.each_node

One rule for every node.

Examples

A panel of 20 customers over two weeks. Customer 3 leaves after week 1, so its censoring is 0 on its one row and its outcome is missing.

>>> import numpy as np
>>> import pandas as pd
>>> from cleverly.longitudinal import LongitudinalData
>>> rng = np.random.default_rng(0)
>>> panel = pd.DataFrame(
...     {
...         "customer": np.repeat(np.arange(20), 2),
...         "week": np.tile([1, 2], 20),
...         "age": np.repeat(rng.normal(size=20), 2),
...         "eng": rng.normal(size=40),
...         "offer": rng.choice(["none", "offer"], size=40),
...         "active": 1.0,
...         "bought": np.repeat(rng.integers(0, 2, size=20), 2).astype(float),
...     }
... )
>>> panel = panel.drop(index=7)
>>> panel.loc[6, ["active", "bought"]] = [0.0, np.nan]
>>> data = LongitudinalData.from_long_panel(
...     panel,
...     unit="customer",
...     time="week",
...     treatment="offer",
...     baseline=["age"],
...     time_varying=["eng"],
...     censoring="active",
...     outcome="bought",
... )
>>> data.n, data.n_times
(20, 2)
>>> data.history_names(2)
('age', 'eng@1', 'eng@2')