Configuration and CLI reference
Use this page when authoring an experiment file or automating a command. For a short runnable path, start with the experiment configuration guide.
DatasetSpec
spec names columns that are not ordinary model features.
| Field | Type | Default | Meaning |
|---|---|---|---|
target |
string | required | Aggregate claim amount column |
exposure |
string | required | Finite exposure in years, at least one day (1/366, with 0.1% tolerance), used as model and diagnostic weight |
claim_count |
string or null | null |
Aggregate claim-count column; required for frequency-severity |
time_col |
string or null | null |
Ordered period used by a temporal split |
protected_cols |
list of strings | [] |
Review-only subgroup columns retained for held-out calibration and excluded from features |
target, exposure, and every protected-column name must be non-empty.
protected_cols must be unique and cannot overlap target, exposure, claim count,
or time. When data loads, every named field must exist.
Unknown fields are rejected: use claim_count, not claim_count_col, in spec.
ExperimentConfig
| Field | Type | Default | Meaning |
|---|---|---|---|
name |
string | "experiment" |
Stable experiment label used in reports and MLflow |
data_path |
string | required | Local or s3:// Parquet path; relative paths resolve from the config file's directory |
spec |
DatasetSpec |
required | Special-column contract |
features |
list of strings or null | null |
Explicit model features; null means every non-special column |
preprocessing |
object or null | null |
Optional binner and grouper settings |
split |
random or temporal |
random |
Outer holdout strategy |
test_size |
float | 0.2 |
Requested test fraction |
random_state |
integer | 42 |
Random split seed; estimator seeds remain model parameters |
models |
mapping | required, at least one | Stable model name to ModelSpec |
tuning |
TuningSpec or null |
null |
Optional trial settings and per-model scalar search spaces |
Explicit features must exist, be unique, and exclude every named special column, including protected columns. Automatic feature discovery excludes them for the same reason.
Preprocessing settings
preprocessing.binner and preprocessing.grouper are parameter mappings.
The workflow injects exposure_col, claim_count_col, and target_col from
spec; do not repeat them.
Configured exposure and claim-count columns are required during preprocessing
fit. Values must be finite, exposure must meet the one-day floor (1/366 with
0.1% tolerance), and claim counts must be non-negative. Direct transformer calls
without exposure_col use unweighted binning or unit row weights for grouping.
Binner
| Setting | Type | Default | Meaning |
|---|---|---|---|
cols |
list or null | null |
Numeric columns to bin; null selects numeric non-special columns |
strategy |
quantile or tree |
quantile |
Exposure-balanced cut points or target-aware decision-tree cuts |
max_bins |
integer | 8 |
Maximum bins per feature; at least 2 |
min_exposure |
number or null | null |
Merge bins below aggregate exposure |
min_claims |
number or null | null |
Merge bins below aggregate claim count |
monotonic |
false, true, increasing, or decreasing |
false |
Isotonic smoothing and adjacent-bin merging |
random_state |
integer | 42 |
Decision-tree seed |
min_claims requires spec.claim_count. Missing numeric values get a stable
Missing category.
Grouper
| Setting | Type | Default | Meaning |
|---|---|---|---|
cols |
list or null | null |
Categorical columns to group; null selects non-numeric non-special columns |
strategy |
similarity or rare |
similarity |
Risk-similar grouping or credibility-only rare-level collapse |
max_groups |
integer | 10 |
Maximum groups for similarity grouping |
min_exposure |
number or null | null |
Collapse levels below aggregate exposure |
min_claims |
number or null | null |
Collapse levels below aggregate claim count |
other_label |
string | "Other" |
Output label for collapsed or unknown levels |
Similarity uses aggregate claim amount divided by aggregate exposure. Ordered categoricals only merge adjacent levels.
Model kinds
kind |
Required structure | Built estimator |
|---|---|---|
glm |
optional params |
RiskGLM |
gbm |
optional params |
RiskGBM |
frequency_severity |
frequency and severity sub-specs |
FrequencySeverityModel |
ModelSpec.kind defaults to glm and params defaults to an empty mapping.
For direct GLM and GBM models, the workflow derives exposure_col from
DatasetSpec.exposure (spec.exposure in YAML). Omit it from model params:
an omitted or null value uses the spec, a matching non-null value is accepted,
and a conflicting non-null value raises ValueError.
GLM parameters
| Parameter | Default | Notes |
|---|---|---|
family |
normal |
Set tweedie, poisson, or gamma explicitly for positive pricing responses |
link |
auto |
Use log for a multiplicative pricing model or tariff export |
tweedie_power |
null |
Variance power when family is Tweedie; use 1.5 unless portfolio evidence supports another value |
alpha |
0.001 |
Regularization strength |
l1_ratio |
0.0 |
Elastic-net mixing |
fit_intercept |
true |
Fit an intercept |
max_iter |
100 |
Solver iteration limit |
gradient_tol |
null |
Optional glum convergence tolerance |
random_state |
null |
Backend seed |
exposure_col |
derived from spec |
Exposure is removed from features and routed as weight |
GBM parameters
| Parameter | Default | Notes |
|---|---|---|
objective |
tweedie |
Common alternatives are poisson, gamma, and regression |
tweedie_variance_power |
1.5 |
Must satisfy \(1.0 \le p < 2.0\) for Tweedie |
num_leaves |
31 |
Maximum leaves per tree |
max_depth |
-1 |
No explicit depth limit |
learning_rate |
0.1 |
Shrinkage rate |
n_estimators |
100 |
Number of trees |
min_child_samples |
20 |
Minimum rows in a leaf |
subsample |
1.0 |
Row fraction per tree |
subsample_freq |
0 |
Subsampling frequency; zero disables it |
colsample_bytree |
1.0 |
Feature fraction per tree |
reg_alpha |
0.0 |
L1 regularization |
reg_lambda |
0.0 |
L2 regularization |
monotone_constraints |
null |
Name mapping or post-exposure sequence with values -1, 0, 1 |
random_state |
null |
Backend seed |
n_jobs |
null |
Backend worker count |
verbose |
-1 |
LightGBM verbosity |
exposure_col |
derived from spec |
Exposure is removed from features and routed as weight |
Unknown constructor parameters fail rather than being collected by **kwargs.
Nested frequency-severity YAML
spec.claim_count is required. Frequency and severity sub-specs must be direct
glm or gbm kinds and cannot nest another frequency-severity model.
models:
frequency-severity:
kind: frequency_severity
frequency:
kind: glm
params:
family: poisson
link: log
alpha: 0.001
severity:
kind: glm
params:
family: gamma
link: log
alpha: 0.001
The workflow derives exposure, claim-count, and claim-amount column names from
spec. Conflicting non-null exposure_col, claim_count_col, or
claim_amount_col values in the outer model's params raise ValueError;
omitted or null values use the spec. Frequency fits
\(y = \text{claim count}/\text{exposure}\) with exposure weight. Severity fits
only positive-claim rows on mean claim size with claim-count weight.
Tuning settings
TuningSpec is optional. Its complete YAML shape is:
tuning:
n_trials: 30
calibration_penalty: 2.0
search_space:
tweedie-glm:
alpha:
type: float
low: 1.0e-6
high: 0.1
log: true
l1_ratio:
type: float
low: 0.0
high: 1.0
tweedie-gbm:
num_leaves:
type: int
low: 8
high: 64
step: 8
max_depth:
type: categorical
choices: [-1, 4, 6]
| Field | Type | Default | Validation |
|---|---|---|---|
n_trials |
integer | 20 |
At least one |
calibration_penalty |
finite float | 1.0 |
Non-negative |
search_space |
model-name mapping | {} |
Names must exist in models; each supplied model space must be non-empty |
| Distribution | Required fields | Optional fields | Validation |
|---|---|---|---|
float |
finite low, finite high |
positive finite step, log: true |
low <= high; step and log are mutually exclusive; logarithmic bounds are positive |
int |
integer low, integer high |
positive integer step (default 1), log: true |
low <= high; logarithmic sampling requires positive bounds and step: 1 |
categorical |
non-empty choices |
none | Each choice is null, boolean, integer, finite float, or string |
A custom model entry replaces that model's built-in search space. It does not merge with the defaults. GLM or GBM siblings without a custom entry keep their existing built-in spaces exactly:
| Model kind | Parameter | Distribution | Bounds |
|---|---|---|---|
glm |
alpha |
float, log | 1e-6 to 1.0 |
glm |
l1_ratio |
float | 0.0 to 1.0 |
gbm |
num_leaves |
int | 4 to 64 |
gbm |
learning_rate |
float, log | 1e-3 to 0.3 |
gbm |
n_estimators |
int | 20 to 300 |
gbm |
min_child_samples |
int | 1 to 100 |
gbm |
reg_alpha |
float, log | 1e-8 to 10.0 |
gbm |
reg_lambda |
float, log | 1e-8 to 10.0 |
Search-space parameters must be exposed by that model kind's estimator.
Unknown model names, unsupported parameters, frequency-severity spaces, empty
spaces, empty choices, invalid distributions, and extra fields fail validation.
The fixed identity, routing, and seed parameters family, link,
objective, tweedie_power, tweedie_variance_power, exposure_col,
and random_state cannot appear in a search space.
Explicit Python n_trials= and calibration_penalty= arguments have highest
precedence. CLI --trials and --calibration-penalty supply those explicit
arguments. Omitted arguments defer first to tuning, then to 20 and 1.0.
The sampler's separate random_state=42 API is unchanged.
Split behavior
| Split | Behavior | Validation |
|---|---|---|
random |
Seeded row permutation; rounded len(data) * test_size rows go to test |
Test must contain between one and n - 1 rows |
temporal |
Sorts spec.time_col ascending and places the latest requested fraction in test |
time_col is required, missing times fail, and equal timestamps never cross the boundary |
Configuration exposes a fraction-based temporal split. For a direct cutoff or
integer test size in Python, use temporal_split itself.
Random seeds
Three independent seeds control an experiment; set each where it belongs.
| Seed | Default | Scope |
|---|---|---|
ExperimentConfig.random_state |
42 |
Outer split permutation only |
Model random_state parameter |
Backend default (unset) | Estimator fitting; a per-model constructor parameter, never tunable |
tune_experiment(random_state=) |
42 |
Optuna sampler; model index i samples with TPESampler(seed=random_state + i) |
Nested selection
Tuning creates an inner split of outer training data. The selected candidate is refit on all outer training rows, and the outer test is evaluated once. Custom search spaces do not change this leakage boundary.
Protected-group audit outputs
Each ModelResult.protected_calibration is a mapping from protected-column name
to a calibration_table built only from outer-test predictions for that model.
Each table keeps the raw subgroup label in group, retains missing labels as a
subgroup, and reports exposure, observed and predicted claim amount, pure
premiums, and O/P ratio. Multiple protected columns are audited independently,
not as intersections.
Provide review-ready categorical or pre-banded groups. Azoic does not infer bins for continuous protected values. These tables are descriptive evidence for review, not a fairness threshold, automated decision, or legal assessment.
Complete YAML shape
Relative data_path values resolve from the YAML file's directory. If this
config is saved in examples/, synthetic.parquet means
examples/synthetic.parquet.
name: motor-pricing-v1
data_path: synthetic.parquet
spec:
target: claim_amount
exposure: exposure
claim_count: claim_count
time_col: null
protected_cols:
- review_group
features:
- driver_age
- vehicle_age
- region
- vehicle_brand
preprocessing:
binner:
cols: [driver_age, vehicle_age]
strategy: tree
max_bins: 6
min_exposure: 50
min_claims: 5
monotonic: false
random_state: 42
grouper:
cols: [region, vehicle_brand]
strategy: similarity
max_groups: 6
min_exposure: 50
min_claims: 5
other_label: Other
split: random
test_size: 0.2
random_state: 42
tuning: null
models:
tweedie-glm:
kind: glm
params:
family: tweedie
link: log
tweedie_power: 1.5
alpha: 0.001
random_state: 42
tweedie-gbm:
kind: gbm
params:
objective: tweedie
tweedie_variance_power: 1.5
n_estimators: 100
num_leaves: 31
learning_rate: 0.05
random_state: 42
frequency-severity:
kind: frequency_severity
frequency:
kind: glm
params:
family: poisson
link: log
severity:
kind: glm
params:
family: gamma
link: log
CLI commands
| Command | Required input | Main options | Output |
|---|---|---|---|
azoic profile |
--data, --target, --exposure |
--claim-count, --time-col, --out |
Screening table to stdout or CSV; --data preserves local paths and s3:// URI strings |
azoic fit |
--config |
--out, --quiet |
Markdown model card to stdout and/or a file |
azoic compare |
One or more config paths | --out |
Comparison table to stdout or CSV |
azoic tune |
--config |
--trials (YAML or 20), --calibration-penalty (YAML or 1.0), --out, --quiet |
Best parameters plus Markdown model card |
azoic export-tariff |
--config, --model, --out |
--distill, --recalibrate/--no-recalibrate |
Three-sheet xlsx tariff |
Run azoic COMMAND --help for Typer's current option spellings.
Workflow outputs
| Object | Contents |
|---|---|
Run |
Configuration snapshot, SHA-256 data fingerprint including categorical metadata, row counts, exact train/test positions, feature names, and named ModelResult objects |
ModelResult |
Model kind, effective parameter snapshot, diagnostic metrics, held-out calibration table, and protected_calibration mapping |
| Metrics | gini_train, gini_test, op_ratio_test, exposure-weighted Tweedie deviance_test at fixed power 1.5, and d2_test |
| Optional estimator mapping | Returned by run_experiment(..., return_estimators=True) |
| Model card | Markdown |
| Comparison | pandas table; comparison_dashboard(runs) optionally returns standalone Plotly HTML from Python; both warn once on mixed evaluation contexts while retaining all rows |
| Tariff | base_rate, factors, and mappings workbook sheets |
d2_test is 1 minus deviance_test divided by the null deviance. The null
predicts the test set's observed mean rate (total claims / total exposure),
using the same exposure-weighted Tweedie deviance at power 1.5. A negative value
means worse deviance than that constant-rate null. It is NaN for a claim-free
test set or zero null deviance (including a constant positive observed rate).
Run.train_indices and Run.test_indices are immutable tuple[int, ...]
values containing the actual fit/evaluation row positions, in their original
partition order. All models within a run share those positions. For tuning,
TuneResult.run records the final outer partition, not an inner trial split.
Recover rows from the exact input frame without sorting, filtering, or changing its index. The fingerprint covers shape, columns, dtypes, index, values, and categorical vocabulary (including unused levels), category dtype/order, and the ordered flag. Workflow evaluation deep-copies configuration and nested model parameters; public dictionaries and DataFrames remain mutable. Category-aware fingerprints intentionally change, so older runs need fresh evaluation.
Matching seeds or row counts alone cannot establish matching partitions. Comparisons warn once on different fingerprints, target/exposure columns, or holdout membership, naming affected runs without dropping rows. Holdout positions are compared as sets, so a permutation alone does not warn.
train_frame = portfolio.iloc[list(run.train_indices)]
test_frame = portfolio.iloc[list(run.test_indices)]
Convert tuples to a list or integer array: pandas interprets a bare tuple as
row/column indexing. CLI export-tariff recalibrates direct GLMs and distilled
students on the returned run's training positions only. Student fits use those
same positions; diagnostic and distillation-fidelity frames use test positions.
The CLI preserves the fitted estimator and holdout and rejects a reloaded
dataset whose fingerprint changed after fitting. --no-recalibrate preserves
the structural tariff. Library callers choose their calibration frame and must
keep distillation fit/validation observations disjoint; distinct frame objects
alone do not establish independence (see
Reporting and operations).
Run metrics describe raw estimator predictions. If applying a training-derived scale factor later, label those adjusted diagnostics separately.
Validation rules
ExperimentConfig,ModelSpec,PreprocessingSpec,TuningSpec, and all parameter distributions reject extra fields.- Data must be non-empty; exposure must be finite and at least one day in years
(
1/366, with 0.1% tolerance:EXPOSURE_FLOOR = (1/366) * (1 - 0.001)); target and claim count must be non-negative and finite. - Claim-count and target rows must be zero or positive together.
- Features must exist, be unique, and exclude special columns. Protected columns must be unique, non-empty, present in the data, and distinct from other special columns.
- Temporal splits reject absent or missing time values and keep timestamp ties together.
- Tweedie LightGBM power outside \([1.0, 2.0)\) fails.
- Frequency-severity requires both nested sub-specs and
spec.claim_count. - Tuning rejects unknown models, empty model spaces, unsupported or fixed parameters, invalid scalar distributions, and frequency-severity spaces.
- Preprocessing credibility by claims requires a claim-count column.
- Model prediction frames may omit outcome columns; fitted workflow pipelines supply harmless placeholders and drop them before the final estimator.
Azoic CLI (Typer): profile / fit / compare / export-tariff /
tune.
Wraps the M5 + M6 + M7 pieces end-to-end:
* azoic profile -- runs profile_features + screen_features and
prints (or writes) the resulting screening table.
* azoic fit -- loads an ExperimentConfig YAML, run_experiments
it, prints a model card to stdout (and/or writes it to disk).
* azoic compare -- runs one or more configs and prints a side-by-side
per-model metrics table (CSV to disk if --out).
* azoic export-tariff -- runs a config, exports a named GLM or, with
--distill, an exportable GLM student of a positive-objective GBM.
* azoic tune -- optuna hyperparameter search per model
(tune extra, M7 / v0.2 part 1) then a model card of the best fit.
ponytail: the CLI is a thin shell over workflow + reporting +
tariff + tune; no business logic lives here.
compare(configs=typer.Argument(..., help='One or more ExperimentConfig YAML files to run and compare.'), out=typer.Option(None, '--out', help='Write the comparison table to CSV (default: print to stdout).'))
Run multiple configs and print a side-by-side per-model metric table.
export_tariff(config=typer.Option(..., '--config', help='ExperimentConfig YAML.'), model=typer.Option(..., '--model', help='Name of the model to export.'), out=typer.Option(..., '--out', help='Output xlsx path.'), distill=typer.Option(False, '--distill', help='Distill a positive-objective GBM to an exportable GLM on the existing split.'), recalibrate=typer.Option(True, '--recalibrate/--no-recalibrate', help='Shift the base so the tariff reproduces the observed training total claim amount (default: on).'))
Run a config and export a GLM or an explicitly distilled GBM tariff.
fit(config=typer.Option(..., '--config', help='ExperimentConfig YAML.'), out=typer.Option(None, '--out', help='Write the model card markdown here.'), quiet=typer.Option(False, '--quiet', '-q', help='Do not print the card to stdout.'))
Run all models in an ExperimentConfig YAML; produce a model card.
profile(data=typer.Option(..., '--data', help='Parquet file (or s3:// path) to profile.'), target=typer.Option(..., '--target', help='Target column name (e.g. claim_amount).'), exposure=typer.Option(..., '--exposure', help='Exposure column name.'), claim_count=typer.Option(None, '--claim-count', help='Claim-count column.'), time_col=typer.Option(None, '--time-col', help='Time column.'), out=typer.Option(None, '--out', help='Write the screening table to CSV (default: print to stdout).'))
Profile features in a portfolio and screen them for keep / bin / group / drop.
tune(config=typer.Option(..., '--config', help='ExperimentConfig YAML.'), trials=typer.Option(None, '--trials', help='Optuna trials per model; overrides YAML (default: YAML or 20).'), calibration_penalty=typer.Option(None, '--calibration-penalty', help='O/P penalty weight; overrides YAML (default: YAML or 1.0).'), out=typer.Option(None, '--out', help='Write the model card markdown here.'), quiet=typer.Option(False, '--quiet', '-q', help='Do not print the card to stdout.'))
Tune model hyperparameters with optuna, then write a model card of the best fit.