chc.panel¶
Stability tier: stable.
A validated long-format panel: one place where the shape assumptions are checked and recorded.
The estimators in this library take panel data in two incompatible shapes. The DiD and synthetic
control routines want a dense (n_units, n_periods) matrix of one outcome
(chc.did, chc.scm); everything else wants flat named columns
(chc.frames). Callers currently pivot between the two by hand, and the pivot is where the
quiet failures live: a duplicated (unit, time) row silently keeps whichever value NumPy wrote
last, a missing one silently becomes a zero, and neither shows up until an effect estimate is
already in a slide.
Panel does that pivot once, refuses to do it when the data cannot support it, and says
which unit and which period were responsible. It holds a read-only copy of the columns it was
given, made once, and in an object column only values that cannot change in place, so that the
data cannot change under its hash; passing one around costs what passing a dict does.
A panel holds text, dates and times as well as numbers: a unit's name, a period's date. Which
columns are numbers is the reader's question, so a column is checked when it is read as numbers,
by Panel.wide and by every routine here that reads a panel's column as float64, and
refused there, naming the column, the unit and the time, unless it holds real numbers.
Provenance travels with it. A number is reproducible only together with the bytes it came
from and the precision it was computed in, and this library has already been bitten by the second:
JAX's x64 flag changes which sample a seed draws, so a seed alone does not name a dataset. The
hash is over the column bytes, so two panels that agree numerically but differ in dtype hash
differently --- which is the honest answer, because they will not produce the same numbers. An
object column's bytes are pointers, which no two runs share, so it is hashed by its values: each
value's type and text, each with its length.
A panel reads its periods on a calendar only where the caller declares one, as frequency:
calendar months stamped as dates lie 28 to 31 days apart, on no grid a library could infer, and
read in the order logged a month no unit logged is not seen. The declared frequency is part of
the dataset's identity, so it enters the hash.
PanelError
¶
Bases: ValueError
A frame could not be read as a panel; the message names the column and the entity.
Provenance
dataclass
¶
What a number would need beside it to be reproduced: the bytes, the version, the precision.
x64 is not decoration. JAX's double-precision flag changes how many bits a threefry key
spends per element, so the same seed draws a different sample at the two settings; a run
recorded without it cannot be repeated. See docs/concepts/dtype-policy.md.
to_json()
¶
A plain dict, ready for json.dumps beside the result it describes.
Panel
dataclass
¶
Long-format panel data whose (unit, time) index has been checked exactly once.
eq=False because the fields hold arrays: a generated __eq__ would compare them
elementwise and return an array where a caller expects a bool, so panel_a == panel_b would
raise inside any if. Identity of a panel is provenance and its hash.
names
property
¶
Every column name, in the frame's own order.
units
property
¶
The distinct unit labels, sorted --- the row order of wide.
periods
property
¶
The distinct period labels, sorted --- the column order of wide.
is_balanced
property
¶
Whether every unit is observed in every period, which wide requires.
from_frame(data, *, unit, time, frequency=None, cluster=None, seed=None, require_balanced=False)
classmethod
¶
Read a pandas/polars frame or a column mapping as a panel, or say exactly why not.
An object column, which is how pandas hands over text, holds values of one type that
cannot change in place and whose text names them: str, bytes, bool, int,
float, complex, Decimal, Fraction, UUID, date, time,
datetime or timedelta, a subclass of one (a pandas Timestamp is a
datetime), or NumPy's scalar of one of these kinds. Each value is hashed by its type and
its text. A float, a complex or a Decimal, NumPy's among them, is refused when
it is nan or infinite, as in a float column. A value that can change in place --- a dict, a
list, a set, a bytearray, an array, an object of the caller's own class --- is refused
rather than copied, since a copy would still change through panel[name][row]. So are
None and pandas' NA, which is how a frame hands over a missing value, and any other
type, a tuple or a pandas Period among them: fill or drop a missing value, and convert
any other value to text, or a Period to a timestamp. A masked array's masked cell is a
missing value too, refused by chc.frames.as_columns.
A column of text, dates, times, durations or complex numbers is held, as a label is, and
so is a number past float64's range. Each is refused where it is read as numbers, by
wide and by chc.decision.prescribe among others: a column is read as
numbers only where its dtype is boolean, integer or floating, or its values are bool,
int, float, Decimal or Fraction, NumPy's among them, and each is finite in
float64 where it is finite in its own type.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
unit, time
|
the column names holding the entity and the period. Values may be any
sortable type; periods are ranked, not assumed to be |
required | |
frequency
|
str | float | None
|
the calendar the periods are read on, which says which two periods are
consecutive. For dates, |
None
|
cluster
|
str | None
|
an optional column naming the group cluster-robust inference should use --- declared here rather than at the call site, because it is a property of the sampling design and not of the estimator. |
None
|
seed
|
int | None
|
the generator seed, when the data is simulated. Recorded in |
None
|
require_balanced
|
bool
|
fail at construction if some unit is missing some period, rather than
at the first |
False
|
Raises:
| Type | Description |
|---|---|
PanelError
|
for a missing index column, one column named as both the unit and the
time, a non-1-D or ragged column, a non-finite number in a float, a complex or an
object column, a missing date, a duplicated |
ValueError
|
from |
codes()
¶
Row-aligned integer codes into units and periods.
What the estimators that group by entity want: chc.regret's fold builders and the
cluster-robust variance routines index by position, and deriving the positions here means
one ranking convention rather than one per caller.
wide(name)
¶
Column name as the dense (n_units, n_periods) float64 matrix DiD and SCM take.
Float64 regardless of the input dtype and of JAX's x64 flag, matching
chc.did and chc.scm, which are NumPy-float64 by contract so that an estimate
does not move with a global flag.
Raises:
| Type | Description |
|---|---|
PanelError
|
if the column does not hold real numbers --- text, dates, times,
durations, complex numbers, any other object --- or holds one that is finite in its
own type and an infinity in float64, naming the first such value's unit and time;
or if the panel is unbalanced, naming the first missing |
installed_version()
¶
The version of the installed distribution, which is the only one that can be reproduced.
Read from package metadata rather than from a literal in the source, because a literal is a
second copy of pyproject.toml's version and second copies drift: chc.__version__ sat at
0.3.0 through the 0.4.0 and 0.5.0 releases, so every Provenance written by a caller reading
it named a version that did not produce the numbers beside it.