Local Data Source (skeleton)#
local_datasource is the source skeleton that ships with CORR-Vars. It carries the
variable classes and the extraction pipeline, but no connection to any store: the query
that reaches a warehouse table, a folder of parquet files, or an HTTP service is
deployment-specific and is not part of the published package.
Two things follow from that:
The aggregation and derivation classes (
NativeStatic,DerivedStatic,DerivedDynamic,ComplexVariable) compute from data that is already in the cohort. They need no store and work on any cohort — see Aggregated Variables and Custom Variables Guide.Only
NativeDynamic, which fetches new rows, needs a store. That is what binding the skeleton is for.
The source is pinned as local under [local].sources in
src/corr_vars/concepts/routing.toml: its definitions are never fetched from the
Concepts API, so it works with no network and no API key. Definitions come from the
bundled mapping/vars.json, and py functions from mapping/variables.py, looked
up by variable name.
The two seams#
Binding the skeleton to a store means replacing exactly two things.
1. cohort.load_data — the observation frame#
load_data() raises
NotImplementedError. Replace it to build the cohort’s observation frame: one row per
observation, carrying at least the level’s primary key and its t_min / t_max anchor
columns (for icu_stay: icu_stay_id, icu_admission, icu_discharge).
2. NativeExtractor.extract — the raw rows#
NativeExtractor.extract() also raises NotImplementedError. Subclass it,
implement extract(cohort, id_column) to return rows carrying at least id_column,
recordtime and value, and point NativeDynamic.extractor_class at the
subclass. NativeDynamic wraps the extractor in the BaseDynamic pipeline:
cache → extract_from_db → time filter → cleaning → optional py post-processing.
Worked example: a folder of parquet files#
The following binds the skeleton to a directory in which each table is one parquet file,
keyed on icu_stay_id:
from pathlib import Path
import polars as pl
import corr_vars.sources.local_datasource.cohort as ld_cohort
from corr_vars.sources.local_datasource import NativeDynamic, NativeExtractor
DATA_DIR = Path("/path/to/my_store")
class MyExtractor(NativeExtractor):
"""Read one parquet file per table and restrict it to the cohort's ids."""
def extract(self, cohort, id_column):
frame = pl.scan_parquet(DATA_DIR / f"{self.table}.parquet")
# `where` is optional; when set it is an SQL-style boolean on the table.
if self.where:
frame = frame.filter(pl.sql_expr(self.where))
ids = cohort.obs.select(id_column).unique()
frame = frame.join(ids.lazy(), on=id_column, how="inner")
frame = frame.select(id_column, "recordtime", "value")
if self.value_dtype:
frame = frame.with_columns(
pl.col("value").cast(getattr(pl, self.value_dtype))
)
return frame.collect()
NativeDynamic.extractor_class = MyExtractor
def load_data(obs_level, source_kwargs):
"""Build the observation frame for `obs_level`."""
stays = pl.read_parquet(DATA_DIR / "icu_stays.parquet")
return stays.select("icu_stay_id", "icu_admission", "icu_discharge")
ld_cohort.load_data = load_data
With both seams filled, a NativeDynamic variable extracts from the store like any
other native variable:
from corr_vars import Cohort
cohort = Cohort(
obs_level="icu_stay",
sources={"local_datasource": {}},
project="my_project",
)
cohort.add_variable(NativeDynamic(
var_name="blood_lactate",
dynamic=True,
table="labs",
where="analyte = 'lactate'",
value_dtype="Float64",
cleaning={"value": {"low": 0.1, "high": 30.0}},
))
Configuration#
corr_vars.sources.local_datasource.config defines the SourceDict and
SourceDictPartial TypedDicts, plus DEFAULTS and MIGRATIONS. The published
schema is deliberately small — a database name and conn_args
(hostname, username, password, password_file):
DEFAULTS: SourceDict = {
"database": None,
"conn_args": {
"hostname": None,
"username": None,
"password": None,
"password_file": False,
},
}
A deployment extends these with whatever its store needs. MIGRATIONS maps legacy
top-level keys to their canonical nested position, so old configuration files keep
loading.
Variable definitions#
Once the store is bound, the source needs definitions. Either fill the bundled mapping, or
route the source to a Concepts API endpoint in concepts/routing.toml (which unpins it
from [local].sources).
mapping/vars.json#
The bundled file declares no variables. Add them under variables, and list the ones
that should load automatically under corr_defaults.default_vars per observation level.
The main definition fields are:
Field |
Meaning |
|---|---|
|
|
|
Table in the backing store (native variables only). |
|
Row filter for the extraction, in the store’s dialect (native variables only). |
|
Polars dtype name the |
|
Per-column plausibility bounds, e.g. |
|
Dependencies. Derived and complex variables take a |
|
Observation levels the definition is valid for. |
mapping/variables.py#
One module-level function per variable that needs a py transformation, named exactly
like its key in vars.json. The loader looks the function up by that name and injects
it into the config as "py". A variable with no transformation needs no entry.
Note
A variable published through the CORR Concepts API carries its py source with it,
so variables.py is only for the definitions a source bundles itself.
Class Reference#
- class corr_vars.sources.local_datasource.extract.BaseDynamic(var_name, dynamic, requires=[], tmin=None, tmax=None, py=None, py_ready_polars=False, cleaning=None, allow_caching=True, screened_obs_level=None)[source]#
Bases:
_ScreenedRequirements,Variable,ABCAbstract base for all dynamic variables of this source.
Provides the shared extraction pipeline: optimised obs-level selection, caching, attribute building, time-filtering, and cleaning. Subclasses implement
_extract_from_db()to supply the raw data.- Parameters:
var_name (
str) – Variable name.dynamic (
bool) – Whether the variable is dynamic (time-series).requires (
list[str] |dict[str,RequirementsDict]) – Dependency variables loaded before extraction.tmin (
str|tuple[str,str] |TimeAnchor|None) – Minimum time anchor.tmax (
str|tuple[str,str] |TimeAnchor|None) – Maximum time anchor.py (
VariableCallable|None) – Optional transformation function applied after raw extraction.py_ready_polars (
bool) – Whetherpyaccepts and returns polars DataFrames.cleaning (
dict[str,dict[Literal['low','high'],Any]] |None) – Value-range cleaning rules.allow_caching (
bool) – Whether to cache raw data in the cohort’s tmpdir.screened_obs_level (
Optional[Literal['patient','hospital_stay']]) – Default observation level used to scope DB queries when the time window is not bounded by hospital-stay bounds.
- screened_obs_level: Literal[ObsLevel.HOSPITAL_STAY, ObsLevel.PATIENT] | None#
- abstractmethod extract_from_db(cohort, id_column)[source]#
Return raw variable data; called by
extract()on a cache miss.The returned DataFrame must not yet have time-window columns or cleaning applied — those are handled by the shared pipeline.
- Parameters:
cohort (
Cohort)id_column (
Literal['case_id','patient_id'])
- Return type:
DataFrame
- extract(cohort)[source]#
Extracts data from the datasource. Usually follows this pattern.
# Load & Extract required variables self._get_required_vars(cohort) # This should change self.data either by returning or side effect self.data = self._custom_extraction(cohort) # Calls variable function to transform extracted data self._call_var_function(cohort) if self.dynamic: self._add_time_window(cohort) # Expects case_tmin, case_tmax for each primary key self._timefilter() self._apply_cleaning()
- Parameters:
cohort (
Cohort)- Return type:
DataFrame
- class corr_vars.sources.local_datasource.extract.NativeExtractor(var_name, *, time_window=None, table=None, where=None, columns=None, value_dtype=None)[source]#
Bases:
objectRaw extractor satisfying VariableProtocol.
Fetches a variable’s rows from the source’s backing store. No caching, time-filtering, or cleaning — those are handled by the wrapping
NativeDynamicpipeline, which is what makes this class worth separating: an extractor can be swapped without touching that pipeline.extract()is deliberately not implemented here. A deployment binds this source to its own store — a warehouse table, a set of parquet files, an HTTP service — and the query that reaches it is deployment-specific, so it is not part of the published package. Subclass this and implementextract(), then pointNativeDynamicat the subclass throughNativeDynamic.extractor_class.- Parameters:
var_name (
str) – Variable name.table (
str|None) – Table name in the backing store.where (
str|None) – Row filter, in whatever dialect the store speaks.columns (
dict[str,str|dict[str,str]] |None) – Column alias/cast mapping for the query.value_dtype (
str|None) – Polars dtype name thevaluecolumn is cast to.time_window (
TimeWindow|None) – Time window (stored to satisfy the protocol).
- value_dtype: str | None#
- extract(cohort, id_column)[source]#
Fetch this variable’s raw rows, restricted to the cohort’s ids.
- Parameters:
cohort (Cohort) – Cohort whose ids the extract is restricted to.
id_column (Literal["case_id", "patient_id"]) – Key the store is queried and joined on.
- Returns:
One row per record, carrying at least id_column,
recordtimeandvalue.- Return type:
pl.DataFrame
- Raises:
NotImplementedError – Always. See the class docstring — the query that reaches a deployment’s store is not part of this package.
- class corr_vars.sources.local_datasource.extract.NativeDynamic(var_name, dynamic, requires=[], tmin=None, tmax=None, py=None, py_ready_polars=False, cleaning=None, allow_caching=True, screened_obs_level=None, table=None, where=None, value_dtype=None, columns=None)[source]#
Bases:
BaseDynamicStore-backed dynamic variable with the full
BaseDynamicpipeline.Wraps a
NativeExtractorto provide caching, time-filtering, cleaning, and optionalpypost-processing.The extractor is the only part that touches a backing store, and the published
NativeExtractorraisesNotImplementedError. Bind this source to a store by subclassing it and settingextractor_class:class MyExtractor(NativeExtractor): def extract(self, cohort, id_column): ... # query your store, return one row per record NativeDynamic.extractor_class = MyExtractor
- Parameters:
table (
str|None) – Table name in the backing store.where (
str|None) – Row filter appended to the generated query.value_dtype (
str|None) – Optional polars dtype name applied to thevaluecolumn after extraction (e.g."Float64").columns (
dict[str,str|dict[str,str]] |None) – Column alias/cast mapping for the query.
All other args are forwarded to
BaseDynamic.Note
Only use this class for defining custom variables. For CORR-specified variables, use
cohort.add_variable()directly.- Parameters:
var_name (
str)dynamic (
bool)requires (
list[str] |dict[str,RequirementsDict])tmin (
str|tuple[str,str] |TimeAnchor|None)tmax (
str|tuple[str,str] |TimeAnchor|None)py (
VariableCallable|None)py_ready_polars (
bool)cleaning (
dict[str,dict[Literal['low','high'],Any]] |None)allow_caching (
bool)screened_obs_level (
Optional[Literal['patient','hospital_stay']])
- extractor_class#
Extractor the variable builds to reach the backing store. Rebind it on this class (or on a subclass) to plug in a deployment’s own query.
- Parameters:
var_name (
str)time_window (
TimeWindow|None)table (
str|None)where (
str|None)columns (
dict[str,str|dict[str,str]] |None)value_dtype (
str|None)
alias of
NativeExtractor
- extract_from_db(cohort, id_column)[source]#
Return raw variable data; called by
extract()on a cache miss.The returned DataFrame must not yet have time-window columns or cleaning applied — those are handled by the shared pipeline.
- Parameters:
cohort (
Cohort)id_column (
Literal['case_id','patient_id'])
- Return type:
DataFrame
- class corr_vars.sources.local_datasource.extract.ComplexVariable(var_name, dynamic, requires=[], tmin=None, tmax=None, py=None, py_ready_polars=False, cleaning=None, allow_caching=True, screened_obs_level=None)[source]#
Bases:
BaseDynamicVariable extracted entirely by a custom
py()function.No database query is issued; the variable function receives
required_varsand must return apl.DataFrame. Time-filtering is applied only when the function addscase_tmin/case_tmaxcolumns to the result.- Parameters:
var_name (
str) – Name of the variable.dynamic (
bool) – Whether the variable is dynamic.requires (
list[str] |dict[str,RequirementsDict]) – Variables loaded and passed to the function.tmin (
str|tuple[str,str] |TimeAnchor|None) – Minimum time anchor.tmax (
str|tuple[str,str] |TimeAnchor|None) – Maximum time anchor.py (
VariableCallable|None) – Variable function that produces the data.py_ready_polars (
bool) – Whetherpyaccepts and returns polars DataFrames.cleaning (
dict[str,dict[Literal['low','high'],Any]] |None) – Dictionary of cleaning rules.allow_caching (
bool) – Whether to allow caching.screened_obs_level (
Optional[Literal['patient','hospital_stay']]) – Observation level used for scoping the cache key.
- extract_from_db(cohort, id_column)[source]#
Return raw variable data; called by
extract()on a cache miss.The returned DataFrame must not yet have time-window columns or cleaning applied — those are handled by the shared pipeline.
- Parameters:
cohort (
Cohort)id_column (
Literal['case_id','patient_id'])
- Return type:
DataFrame
- extract(cohort)[source]#
Extracts data from the datasource. Usually follows this pattern.
# Load & Extract required variables self._get_required_vars(cohort) # This should change self.data either by returning or side effect self.data = self._custom_extraction(cohort) # Calls variable function to transform extracted data self._call_var_function(cohort) if self.dynamic: self._add_time_window(cohort) # Expects case_tmin, case_tmax for each primary key self._timefilter() self._apply_cleaning()
- Parameters:
cohort (
Cohort)- Return type:
DataFrame
- corr_vars.sources.local_datasource.extract.VariableLoader(*args, **kwargs)[source]#
Factory function to create the correct variable type based on the arguments.
- Return type:
- corr_vars.sources.local_datasource.cohort.load_data(obs_level, source_kwargs)[source]#
Build the cohort’s observation frame from the backing store.
- Parameters:
obs_level (ObsLevel) – Level of observation the frame is keyed at.
source_kwargs (SourceDict) – This source’s resolved configuration.
- Returns:
One row per observation, carrying at least the level’s primary key and its
t_min/t_maxanchor columns.- Return type:
pl.DataFrame
- Raises:
NotImplementedError – Always.
local_datasourceis a source skeleton; replace this function to bind it to a store.