API Reference¶
Catalog¶
Data loading¶
- pamir.load_dataset(dataset_id, max_rows=None, auto_download=True)[source]¶
Load a single dataset from the local cache.
- Parameters:
dataset_id (
str) – One of the PaMIR dataset ids (seepamir.list_datasets()).max_rows (
Optional[int]) – Truncate to at most this many rows (in stream order).auto_download (
bool) – If the dataset is not cached, fetch and harmonize it from source (default True). Set False to require an explicitpamir.download().
- Return type:
- Returns:
X (DataFrame) – Feature columns exactly as stored:
int64,float64,boolandobjectall occur, and most of the datasets carry at least one non-numeric column. Nothing is encoded or imputed for you — seepamir.encode_features()for the glue most models need.y (ndarray of int) – Binary target (1 = default, 0 = non-default).
meta (dict) – Dataset metadata from the catalog, plus
n_rows,n_defaults,n_features. The catalog’snoteskey is present only on the datasets that required harmonization, so read it with.get().
Downloading data¶
PaMIR ships no data. These fetch each dataset from its original source and harmonize it locally (see Datasets).
- pamir.download(dataset_id, force=False, quiet=False)[source]¶
Fetch and harmonize one dataset; return the path to the cached parquet.
- Parameters:
dataset_id (
str) – A PaMIR dataset id (seepamir.list_datasets()).force (
bool) – Re-fetch and re-harmonize even if a cached parquet exists.quiet (
bool) – Suppress progress / validation messages.
- Return type:
Baselines¶
- pamir.encode_features(X_train, X_test)[source]¶
Ordinal-encode non-numeric columns against one shared level set.
Both frames are returned as float, so a level present in only one of them still maps to the same code in both. Numeric missing values are left as
NaN— that is information, and filling it is the caller’s decision.- Parameters:
- Returns:
(train, test) – New frames; the inputs are not modified.
- Return type:
- Raises:
ValueError – If the two frames do not carry the same columns in the same order.
- pamir.logistic_baseline(X_train, y_train, X_test)[source]¶
Median-imputed, standardized logistic regression.
The cheap reference point: fast enough to run the whole fleet under the default protocol parameters.
- pamir.gbdt_baseline(X_train, y_train, X_test, max_iter=150, learning_rate=0.1, seed=42)[source]¶
Histogram gradient boosting, handling missing values natively.
Uses scikit-learn rather than XGBoost so it needs no optional dependency, and so it cannot hit the OpenMP clash documented in the installation notes.
i.i.d. evaluation¶
- pamir.evaluate_iid(predict_fn, datasets=None, train_frac=0.7, max_n=None, n_seeds=5, verbose=True, on_error='warn')[source]¶
Run the i.i.d. protocol across the PaMIR fleet.
- Return type:
- Returns:
DataFrame with one row per dataset and the columns in
RESULT_COLUMNS.Pass it to
pamir.fleet_summary()for the headline numbers.
- Parameters:
- pamir.evaluate_iid_one(predict_fn, dataset_id, train_frac=0.7, max_n=None, n_seeds=5, on_error='warn')[source]¶
Evaluate one dataset with a standard i.i.d. train/test split.
- Parameters:
predict_fn (
Callable) –predict_fn(X_train, y_train, X_test) -> np.ndarrayof scores (higher = more likely to default).dataset_id (
str) – PaMIR dataset id.train_frac (
float) – Fraction of data used for training (default 0.7).n_seeds (
int) – Number of random shuffles (default 5). The reported AUC is the mean.on_error (
str) – What to do whenpredict_fnraises. Seepamir.failures.
- Return type:
- Returns:
dict with keys (dataset, n_rows, n_defaults, DR, train_frac, n_seeds,)
n_calls, n_failures, errors, auc_mean, auc_std, aucs.
Streaming evaluation¶
- pamir.evaluate(predict_fn, datasets=None, lag=1000, k_refit=10, max_n=20000, min_defaults=3, verbose=True, on_error='warn')[source]¶
Run the streaming protocol across the full PaMIR fleet.
- Parameters:
predict_fn (
Callable) – Same signature as inevaluate_one().datasets (
Optional[Sequence[str]]) – Subset of dataset ids. Default: all 19.lag (
int) – Protocol parameters (seeevaluate_one()).k_refit (
int) – Protocol parameters (seeevaluate_one()).max_n (
Optional[int]) – Protocol parameters (seeevaluate_one()).min_defaults (
int) – Protocol parameters (seeevaluate_one()).verbose (
bool) – Print per-dataset progress and the fleet summary.on_error (
str) – What to do whenpredict_fnraises.
- Return type:
- Returns:
DataFrame with one row per dataset and the columns in
RESULT_COLUMNS.Pass it to
pamir.fleet_summary()for the headline numbers — a meantaken by hand over this frame silently excludes the datasets the model
failed on.
- pamir.evaluate_one(predict_fn, dataset_id, lag=1000, k_refit=10, max_n=20000, min_defaults=3, on_error='warn')[source]¶
Run the streaming protocol on one dataset.
- Parameters:
predict_fn (
Callable) –predict_fn(X_train, y_train, X_test) -> np.ndarrayof scores (higher = more likely to default). Called once per refit point.X_traincontains only rows whose outcome is resolved;X_testis the remaining unscored stream, and the returned array must have one score per row of it.dataset_id (
str) – PaMIR dataset id.lag (
int) – Label-maturation delay. Outcome of application at position t is revealed only at position t + lag.k_refit (
int) – Refit every k_refit newly resolved defaults. This changes the score as well as the cost — see the protocol documentation — so report it.max_n (
Optional[int]) – Truncate the stream to at most this many rows.min_defaults (
int) – Minimum resolved defaults before the first refit.on_error (
str) – What to do whenpredict_fnraises. Seepamir.failures.
- Return type:
- Returns:
dict with keys (dataset, n_rows, n_defaults, DR, n_refits, n_calls,)
n_failures, errors, auc_final, refit_points (list of dicts with pos,
resolved_n, cum_defaults, cum_auc).
Results¶
- pamir.fleet_summary(results)[source]¶
Summarize a fleet run, withholding the mean when coverage is incomplete.
- Parameters:
results (
DataFrame) – As returned bypamir.evaluate()orpamir.evaluate_iid().- Return type:
- Returns:
dict with keys
n_datasets,n_scored,coverage,complete– How much of the fleet the model actually scored.auc_mean,gini_mean– The headline numbers —Noneunless every dataset was scored.auc_mean_scored_only– The average over the datasets that did score. Diagnostic only: it is an average over a subset the model chose by failing, so it is not comparable across models.n_failures,failed_datasets– What went wrong and where.
Synthetic augmentation¶
- class pamir.synthetic.SyntheticMixer(generators, weights=None, synthetic_share=None, n_synthetic=None, target=None, sizing='keep_real', n_total=None, stratify=True, synthetic_target_rate=None, oversample=1.5, allow_replacement=False, fidelity=True, fidelity_kwargs=None, pooled_fidelity=True, leakage_probes=True, shuffle=True, random_state=42)[source]¶
Build a training frame from real rows plus several synthetic engines.
- Parameters:
generators (
Mapping[str,Any]) –{name: generator}. Each value may be aGeneratorAdapter, an SDV synthesizer class or instance (zGAN is one), a pre-generated pool, or any object withfit/sample—as_adapter()wraps it.weights (
Optional[Mapping[str,float]]) – How the synthetic part splits across generators, e.g.{"zgan": 0.5, "zedge": 0.5}. Defaults to equal weights.synthetic_share (
Optional[float]) – Share of synthetic rows in the mixed frame, in [0, 1). Defaults to0.5when neither this norn_syntheticis given.n_synthetic (
Optional[int]) – Exact number of synthetic rows, in place of a share. Under cross-validation this is per fold, so the count is identical in every fold, whereas a share resolves against each fold’s train split and therefore varies with it. Mutually exclusive withsynthetic_share.target (
Optional[str]) – Target column. Enables stratification, class-balance control and the ML-efficacy fidelity arm.sizing (
str) –keep_realadds synthetic rows on top of every real row.fixed_totalpins the frame ton_totalrows.stratify (
bool) – Preserve the target distribution when subsampling real or synthetic rows.synthetic_target_rate (
Optional[float]) – Force the synthetic part to this positive rate (e.g.0.5to rebalance a 3%-default book). Requires oversampling; raises if the pool cannot supply enough rows of a class.oversample (
float) – Multiplier on how many rows each generator is asked for, so that class-balance selection has slack. Skipped for a fixed pool, which cannot generate more rows than it holds.fidelity (
bool) – Compute fidelity metrics for each generator’s rows against the real training rows. A full report costs roughly 11 s per generator per fold on a 1k x 20 table, so switch it off for a large fleet sweep and measure fidelity separately. The leakage probes are not switched off with it — seeleakage_probes.leakage_probes (
bool) – Run the exact-overlap probes whenever a holdout frame is given, whateverfidelityis set to. They cost milliseconds, and a delta reported without them cannot be checked, so switching the battery off for a fleet sweep leaves them running. Setting this to False is the only way to stop them, in either mode.pooled_fidelity (
bool) – Also score the concatenation of every generator’s rows as__pooled__. Switch off to save one report per fold.random_state (
int) – Seed for every selection step; generators receive derived seeds.n_total (Optional[int])
allow_replacement (bool)
fidelity_kwargs (Optional[Dict])
shuffle (bool)
- fit(train, fold=None, only=None)[source]¶
Fit the generators on
train— and only ontrain.onlyrestricts the work to the named generators.build()passes the ones its plan actually draws from, so a generator weighted to zero rows is not trained for nothing: for a GAN or a diffusion model that is a full training run per fold, thrown away.- Return type:
- Parameters:
- build(train, holdout=None, fold=None, fit=True)[source]¶
Fit (optionally), generate, mix, and score.
- Parameters:
train (
DataFrame) – Real training rows including the target column. This is the only data any generator ever sees.holdout (
Optional[DataFrame]) – Held-out rows, passed to the fidelity report for leakage probes only — never used to fit, generate or score fidelity itself.fold (
Optional[int]) – Fold index, forwarded to pool-based adapters so they pick the pool that belongs to this fold.fit (
bool) – Set False to reuse generators already fitted on this same frame.
- Return type:
MixResult
- class pamir.synthetic.CrossValidatedAugmentation(mixer, n_splits=5, seed=42, predict_fn=None, baseline=True, target='__target__', verbose=True)[source]¶
Run a
SyntheticMixerinside stratified k-fold, without leakage.- Parameters:
mixer (
SyntheticMixer) – Template. It is cloned per fold; the original is never fitted.n_splits (
int) – Folds. 5 matches the upstream ceiling protocol.seed (
int) – Seed for the fold split and for the default model arm, so changing it moves the delta on unchanged data — state which seed a run used before comparing two runs. 42 matchespublic_datasets/ceiling_cv.py; the audit’sscripts/03_ceiling_compare.pyuses 0. Apredict_fnof your own receives no seed; seed it yourself if it needs one.predict_fn (
Optional[Callable]) –predict_fn(X_train, y_train, X_test) -> scores, the same convention aspamir.evaluate(). Defaults to upstream’s XGBoost arm.baseline (
bool) – Also fit on the real training rows alone, to report a delta rather than a bare number.target (str)
verbose (bool)
- class pamir.synthetic.CVResult(folds, oof_augmented, oof_baseline, y, dataset=None)[source]¶
Pooled out-of-fold outcome of an augmentation run.
- Parameters:
- fidelity_headline()[source]¶
The few fidelity columns worth reading first, per fold and generator.
fidelity_framereturns everything the battery computed — 66 columns on a full SDV run. This is the documented shortlist fromHEADLINE_FIDELITY_COLUMNS, narrowed to the columns actually present, so it is also safe on a dependency-free run where most of the battery did not execute.- Return type:
- pamir.synthetic.run_fleet(mixer, datasets=None, max_rows=20000, **kwargs)[source]¶
Run the augmentation across PaMIR datasets and return one summary row each.
- Return type:
- Parameters:
mixer (SyntheticMixer)
max_rows (int | None)
- pamir.synthetic.plan_mixture(n_real, synthetic_share=None, weights=None, sizing='keep_real', n_total=None, n_synthetic=None)[source]¶
Resolve a proportion — or a row count — into exact row counts.
- Parameters:
n_real (
int) – Real training rows available (one fold’s train split, not the table).synthetic_share (
Optional[float]) – Target share of synthetic rows in the mixed frame, in [0, 1).0.7means 70% synthetic / 30% real. Defaults to0.5when neither this norn_syntheticis given.weights (
Optional[Mapping[str,float]]) – Relative weights per generator, e.g.{"zgan": 0.5, "zedge": 0.5}. Need not sum to 1; they are normalized.sizing (
str) –keep_real(default) keeps every real row and adds synthetic rows on top until the share is met — the mix never throws real data away.fixed_totalpins the mixed frame ton_totalrows and subsamples the real part, which is what an ablation at constant training size needs.n_synthetic (
Optional[int]) – Exact number of synthetic rows, in place of a share. Under CV this is per fold, so the count is identical in every fold whereas a share is not. Mutually exclusive withsynthetic_share: naming both would mean one of them silently losing.
- Return type:
MixturePlan
Generator adapters¶
- class pamir.synthetic.GeneratorAdapter(name)[source]¶
Uniform interface every generator is wrapped in.
- Parameters:
name (str)
- fit(data, target=None, discrete_columns=None)[source]¶
Train on real rows — the fold’s training split, target column included.
- Return type:
- Parameters:
- native_fidelity(real, synthetic)[source]¶
Metrics shipped by the engine’s own library. Empty when it ships none.
- class pamir.synthetic.ZganAdapter(repo_path=None, name='zgan', module='app.zgan.zgan_lib', utils_module='app.utils.zgan_utils', synthesizer=None, **params)[source]¶
zGAN (
zyplGANSynthesizer) from the zypl zgan repository.repo_pathis the checkout root; the class is imported fromapp.zgan.zgan_libunlessmodulesays otherwise. zGAN’s own fidelity helpers inapp.utils.zgan_utilsare reported throughnative_fidelity().
- class pamir.synthetic.ZedgeAdapter(repo_path=None, artifact_dir=None, fold_artifacts=None, name='zedge', mode='pipeline', device='cpu', temperature=2.0, num_steps=50, work_dir=None, oversample=1.2, allow_shared_artifact=False, **pipeline_kwargs)[source]¶
zEDGE / zGN-LatentDiff (latent diffusion) from the zgn_latdiff repository.
Two ways to run it:
mode="pipeline"Train on the fold’s split in-process: the training frame is written to a temporary CSV,
run_full_pipelinebuilds the artifact, andgenerate_samplesdraws the pool. Needs torch and, realistically, a GPU.mode="artifact"Reuse an artifact directory already trained on this fold’s split and only sample from it.
fitthen verifies nothing but the artifact’s presence, so the caller carries the burden of the leakage contract — which is whyfold_artifactsis the keyed form to prefer.
- class pamir.synthetic.SDVAdapter(synthesizer, name='sdv', max_categories=20, **params)[source]¶
Any SDV
BaseSingleTableSynthesizer— which is what zGAN subclasses.Metadata is detected from the fold’s training frame, so nothing about the held-out rows reaches the generator, not even a column’s value range.
- class pamir.synthetic.PoolAdapter(pool=None, name='pool', fold_pools=None, allow_shared_pool=False, allow_replacement=False)[source]¶
A pool of rows generated elsewhere — the offline path.
Use it when the engine runs outside this process (a GPU box, the zEDGE service, a nightly job) and all that comes back is a CSV or a DataFrame.
The pool must have been generated from the same fold’s training split.
fold_poolskeyed by fold index enforces that per fold; a single pool reused across folds is a leak and is rejected unlessallow_shared_pool.
- class pamir.synthetic.CallableAdapter(model, name='generator', init_kwargs=None, fit_kwargs=None, sample_kwargs=None, pass_discrete=False)[source]¶
Wrap any object exposing
fit(frame)andsample(n).This is the escape hatch for a model class that already follows the usual synthesizer convention but is neither zGAN nor zEDGE.
Each dictionary has exactly one destination, whatever is passed and however many times the adapter is fitted:
init_kwargsThe model’s constructor. Only meaningful when
modelis a class; given alongside an already-built instance they cannot be applied, so they are refused rather than ignored.fit_kwargsmodel.fit(frame, **fit_kwargs).sample_kwargsmodel.sample(n, **sample_kwargs).
- pamir.synthetic.as_adapter(obj, name='generator', **kwargs)[source]¶
Wrap whatever the caller has into a
GeneratorAdapter.Accepts an adapter (returned unchanged), a DataFrame or CSV path (pool), an SDV synthesizer class or instance, or any object with
fitandsample.- Return type:
- Parameters:
Fidelity¶
- pamir.synthetic.fidelity_report(real, synthetic, metadata=None, target=None, holdout=None, heavy=False, max_pairs=60, max_category_cardinality=200, max_contingency_cells=20000, seed=0)[source]¶
Score synthetic rows against the real rows they were trained on.
- Parameters:
real (
DataFrame) – The real training rows the generator saw — never a test split.synthetic (
DataFrame) – Generated rows with the same schema.metadata (
Optional[Dict]) – SDV-style{"columns": {name: {"sdtype": ...}}}; inferred when absent.target (
Optional[str]) – Target column name. Enables ML-efficacy metrics and the target-rate check.holdout (
Optional[DataFrame]) – Held-out real rows, used only for leakage probes (exact-row overlap).heavy (
bool) – Also run the slow arms (SVC detection, MLP efficacy, GMLogLikelihood).max_pairs (
int) – Cap on the number of column pairs scored, to bound O(p^2) work.max_category_cardinality (
int) – A categorical column whose synthetic cardinality exceeds this (and ten times the real cardinality) is reported as a type violation and scored as numerical by the pair, table and aggregate arms. Seecardinality_violations().max_contingency_cells (
int) – Skip a categorical pair whose contingency table would exceed this many cells. A bound on cost, not a statistical choice.seed (int)
- Return type:
- pamir.synthetic.leakage_report(real, synthetic, holdout)[source]¶
The leakage probes alone, with no distributional metric computed.
The probes cost milliseconds while the full battery costs seconds, so they are deliberately available on their own: switching fidelity off for a fleet sweep must never switch off the check that says whether to believe the resulting delta.
- Return type:
- Parameters:
- class pamir.synthetic.FidelityReport(overall=<factory>, columns=<factory>, pairs=<factory>, table=<factory>, leakage=<factory>, native=<factory>, type_violations=<factory>, errors=<factory>, n_real=0, n_synthetic=0)[source]¶
Everything measured about one (real, synthetic) pair.
- Parameters:
- pamir.synthetic.c2st_auc(real, synth, metadata, seed=0)[source]¶
Classifier two-sample test: AUC of telling synthetic from real.
The scale is two-sided and runs over [0, 1], not [0.5, 1]:
≈ 0.5— indistinguishable to a strong learner, which is the goal.> 0.5— the samples differ; above roughly 0.8 the synthetic rows are trivially identifiable and their value as training data is doubtful.< 0.5— duplicates, not quality. When synthetic rows repeat real ones, cross-validation puts a row’s twin in the training fold carrying the opposite label, the classifier is systematically wrong on the held-out twin, and the AUC falls below chance. A pure memoriser scores near 0.1. Read it withdcr_zero_shareandNewRowSynthesis, and never as “better than indistinguishable”.
fidelity_report()therefore also recordsC2ST_deviation, the distance|AUC - 0.5|, which is the number to rank generators by: it is 0 for an ideal generator and rises for both failure modes.