Skip to content

chc.frames

Stability tier: evolving.

Accept a columnar frame -- pandas, polars, or a plain mapping -- wherever chc takes named data.

Neither pandas nor polars is a chc dependency, so a frame is recognised structurally rather than by import: both expose .columns and frame[name], and every column converts through np.asarray. Estimators call as_columns once on entry and index a plain dict afterwards.

The mapping branch is guarded by an isinstance rather than by duck-typing, because a frame that is not a Mapping still survives the mapping idioms with the wrong answer instead of an error: dict(polars_frame) and for name in polars_frame both iterate columns as values, handing a caller column data where it asked for column names.

A column read as numbers holds real numbers. NumPy's cast to float64 takes far more: it reads the text "1.5" as 1.5, a date as its count of days since 1970, and a Decimal of 1E+400 as an infinity. So a reader of numbers reads a caller's column through one check, which refuses text, dates, times, durations, complex numbers and any other object that is not a real number, and a real number that is finite but past float64's range, naming the column and the row. An entry point that takes a caller's data as arrays, not as named columns, reads each through the same check as it enters (_real_numbers), naming the argument.

ColumnData = Mapping[str, Any] | ColumnFrame module-attribute

What every chc entry point that takes named data accepts.

ColumnFrame

Bases: Protocol

A frame with named columns, indexable by name: pandas, polars, pyarrow, and friends.

as_columns(data)

Normalise data to {name: column} from a mapping or any columnar frame.

The values are Any rather than ArrayLike and that is the honest type, not a shortcut: a mapping's columns come back exactly as they went in, so their type is the caller's, and every consumer here re-asserts it with the asarray its own precision contract requires.

A mapping passes through unconverted, which is what keeps this cheap enough to sit on every entry point: the library's own callers hand over JAX arrays, and np.asarray on those would force a device-to-host copy and raise outright on a tracer inside jax.jit. Only the frame branch materialises, and it stops at NumPy -- precision is the caller's decision, and the two consumers disagree on purpose (chc.gmethods is float64 by contract, the estimators follow JAX's x64 flag).

A NumPy masked array is read through its mask, in either branch. A masked cell is a missing value, and no reader of named columns here takes one --- a panel refuses a nan, and a fit would read what lies under the mask as data --- so a column with one is refused. A masked array that masks no cell is its data.

Raises:

Type Description
TypeError

when data is neither a mapping nor a frame with .columns.

ValueError

when two columns go by one name as text -- 0 and "0", or a label a pandas frame repeats -- so that one would replace the other; or when a column is a masked array that masks a cell, naming the column, the row and how many are masked.