Testing your own data
Yes: the intended workflow is to provide a labeled table, select models, and get a paired comparison plus an explanation of the measured results. You do not need to modify Python code or register an OpenML ID.
Quibble evaluates classification or regression with one target column. Its localhost workbench can use datasets saved in local folders, but it does not upload arbitrary files through the browser or provide a prediction API. "Upload" here means copying a file into a local folder.
Already have a generated dataset? Use the same workflow below, with the target included for scoring but excluded from predictive inputs. Keep its generation code, assumptions, seed, and synthetic label alongside your experiment records, outside the feature table. Plausible column names do not establish realistic predictive relationships.
The runnable synthetic example later in this guide is a demonstration, not an application benchmark. The experiment workbench also supports reviewed, bounded synthetic specifications and optional provider-configured scenario drafting. Real store/customer histories still need a suitable evaluation design; the current random splits do not provide temporal or grouped validation.
1. Prepare the environment
Follow Getting started first. From the project root:
& .\.venv\Scripts\Activate.ps1
python -m pip check
python -m quibble doctor
The unified requirements include pyarrow for Parquet. CSV needs no additional package
beyond the core dependencies. For a minimal package install, the optional [data] extra
provides Parquet support.
2. Create a named dataset folder
New-Item -ItemType Directory -Force .\data\payments | Out-Null
Place your table and its declaration together:
data/
payments/
data.csv Your labeled table, or data.parquet
dataset.json Target, task, and optional column rules
Use the folder name, payments, in commands, not the file path.
- Names may contain letters, digits, hyphens, and underscores, beginning with a letter
or digit. Do not use built-in/OpenML names such as
wineoradult. report,runs,smoke, andartifactsare reserved output names.- Names should be unique, meaningful, and stable while you compare models.
- Local folders default to
<project-root>\data, not.cache: these are your inputs, not downloaded datasets. - Set
QUIBBLE_DATA_DIRin.envto relocate the folder collection. Relative paths resolve from the project root; absolute paths can point to another local drive. - Each dataset needs its own
dataset.json. A CSV alone is not discovered.
For example, to use your existing file, replace the source path before running:
Copy-Item 'C:\path\to\your-file.csv' .\data\payments\data.csv
No separate upload step, service account, or network dataset registration is required.
3. Check the table format
Each row must be one observation. Each column must have one scalar value per observation. The target must already be known for every row.
Example classification CSV, showing structure only:
customer_id,amount,days_open,region,paid_late
C001,120.50,45,east,yes
C002,80.00,10,west,no
C003,230.00,38,east,yes
C004,60.00,8,west,no
Four rows illustrate the schema; they are not enough for meaningful model comparison. Use representative data, preferably hundreds or more labeled rows when available.
| Requirement | Behavior |
|---|---|
| Format | UTF-8 comma-separated CSV, optionally with a UTF-8 BOM, or .parquet |
| Header | First CSV row contains unique, nonempty column names without surrounding spaces |
| CSV records | Every nonblank row has the same number of fields as the header; use standard quoting for commas |
| One target | Exact case-sensitive column name declared in the manifest |
| Target completeness | Missing/empty target values are rejected, not imputed or silently dropped |
| Classification | At least two classes and at least two rows per class for basic loading |
| Regression | Target must be finite numeric values |
| Features | At least one feature remains after removing the target and excluded columns |
| Numeric features | Numeric values or missing values; infinity is rejected |
| Categorical features | Strings, booleans, or declared categorical columns; train-fitted adapters handle encoding |
| Unsupported types | Nested lists/objects and undeclared typed datetimes are rejected; engineer or exclude them first |
| Minimum size | At least four labeled rows to load; usable split sizes and model limits still apply |
Additional preparation rules:
- Exclude row/customer IDs, names, email addresses, addresses, free-form sensitive text, and columns that are not valid predictive inputs.
- Exclude information available only after the outcome. For example, a final collection status may reveal whether an invoice was late.
- Do not assume numeric category codes are continuous measurements. Declare postal codes,
product codes, and similar columns categorical. Declared CSV categories are read as
text, preserving leading zeros such as
001. - CSV uses pandas' standard missing-value tokens, including empty cells and common tokens
such as
NA. If those are legitimate category names, recode them before importing. - Missing feature values are retained. Quibble does not promise that every backend can handle every missingness pattern or mixed schema.
- CSV date-looking strings are treated as categories unless you engineer numeric features or exclude them. Parquet datetime columns need exclusion or an explicit categorical declaration. A categorical timestamp does not make a random split temporally valid.
- This version has no group/time split, automatic leakage detection, deduplication, train/test-file pairing, or automatic schema repair. Repeated observations from one person/account or future-dependent features can invalidate random-split results.
The file is loaded into memory, not streamed. Size RAM/VRAM expectations accordingly.
4. Declare the target, task, and column rules
Create data\payments\dataset.json:
{
"file": "data.csv",
"target": "paid_late",
"task": "classification",
"drop_columns": ["customer_id"],
"categorical_columns": ["region"]
}
Or create that declaration from PowerShell:
@'
{
"file": "data.csv",
"target": "paid_late",
"task": "classification",
"drop_columns": ["customer_id"],
"categorical_columns": ["region"]
}
'@ | Set-Content -Encoding utf8 .\data\payments\dataset.json
| Key | Required | Meaning |
|---|---|---|
file |
yes | Relative path to .csv or .parquet, inside this dataset folder |
target |
yes | Column to predict; it is automatically removed from features |
task |
yes | Exactly classification or regression; never guessed from numeric labels |
drop_columns |
no | Columns excluded before splitting/training; defaults to [] |
categorical_columns |
no | Extra categorical declarations, including numeric codes; defaults to [] |
All declared columns must exist. Column lists must be unique and must not overlap each other or the target. Unknown manifest keys are rejected so a typo cannot silently change the experiment. Absolute file paths and paths escaping the dataset folder are rejected.
For regression, use a numeric target and change the declaration, for example:
{
"file": "data.parquet",
"target": "days_until_payment",
"task": "regression",
"drop_columns": ["customer_id"],
"categorical_columns": ["region"]
}
Do not include both paid_late and days_until_payment as mutually revealing predictors.
Drop unused outcome columns too.
Optional: generate a runnable synthetic example
If you do not have a file ready, this creates 180 synthetic observations matching the classification manifest above. It is a workflow exercise, not evidence about real payment data. It replaces the example input file, so do not run it over your real table.
python -c "from pathlib import Path; import numpy as np; import pandas as pd; rng=np.random.default_rng(42); n=180; days=rng.integers(1,61,n); df=pd.DataFrame({'customer_id':['C'+str(i) for i in range(n)], 'amount':rng.uniform(20,500,n).round(2), 'days_open':days, 'region':rng.choice(['east','west'],n), 'paid_late':np.where(days>30,'yes','no')}); df.to_csv(Path('data')/'payments'/'data.csv',index=False)"
5. Discover and validate before training
python -m quibble datasets --list
python -m quibble datasets --verify --names payments
Expected outcomes:
payments: localappears in the list.- Verification prints rows, features, task, missingness, categorical share, class balance for classification, and local-source metadata.
- Metadata is saved in
datasets_meta.jsonin the working directory. - Verification returns 0 if all requested entries load and names match; 1 if any fail
or an OpenML name mismatches. Inspect messages as well as
$LASTEXITCODE. - Invalid input produces an explicit
ERROR, including missing target/columns, unsupported format, incomplete labels, or a malformed manifest.
Always use --names payments here. Omitting it verifies all registered and discovered
datasets, including OpenML entries that may download data.
Verification checks basic loading, not leakage, representative sampling, full model capacity, or whether your chosen split is valid.
6. Run a bounded smoke check
Begin with inexpensive local models:
python -m quibble benchmark --datasets payments --backends histgb,lightgbm --device cpu --jobs 1 --smoke --out results_payments
This uses one holdout split, model seed 0, at most 128 training and 32 test rows. All selected backends receive the same sampled rows. Outputs are isolated:
results_payments/
smoke/<unique-id>/
payments/ Records, arrays, and analysis
report/report.md Interpretation of this setup check
Read the report's Interpretation, Model participation, device labels, and failure details. Smoke interpretation explains what completed, the observed scores/resources, and any disagreements. It does not choose a deployment winner or issue a swap verdict. Perfect accuracy on a tiny sample is not proof of equivalence or real-world quality.
To check foundation-model setup after the trees work:
python -m quibble benchmark --datasets payments --preset foundations --device auto --jobs 1 --smoke --out results_payments
This can download large pretrained checkpoints into .cache. Small smoke inputs do not
make model weights smaller. Check package/model licenses and access requirements first.
7. Run the full paired comparison
python -m quibble benchmark --datasets payments --backends histgb,lightgbm --device cpu --jobs 1 --cv 3x2 --model-seeds 0 --out results_payments
Full results go to results_payments\payments, not its smoke subtree.
Each model receives the full training split. CV 3x2 means six fits per model;
each row is held out once per repeat.
- For local classification, every class needs at least K rows for K-fold CV. Use fewer folds or more labeled data if the split is rejected. Holdout must also have enough training and test rows to represent all classes.
--device cpupins CPU.cudarequires CUDA;autoresolves once per sweep. CPU-only models stay on CPU, and mixed-device reports say so.- Resource failures are recorded without a hidden CPU retry or downsampling. Other fits continue, but the benchmark returns 1 for failed/refused/missing/incompatible cells.
- More splits/models can be expensive. Start with trees, then select foundation models or
--preset alldeliberately. AutoGluon has a per-fit training budget. - Optional
--keep-modelsretains supported tree/AutoGluon fits; foundation fitted estimators are not serialized.
Benchmark also analyzes results, checks swaps from HistGB (or the first selected backend), and writes the report. You do not have to run each stage separately.
8. Read the report and inspect evidence
Open results_payments\report\report.md.
| Section | What to check |
|---|---|
| Model participation | Were all requested fits completed, compatible, and actually used? |
| Device policy | Is this CPU-only or mixed CPU/GPU, and are any measurements cached? |
| Interpretation | Which measured quality/resource tradeoffs warrant further evaluation, and why? |
| Local data source | Exact input path, target, SHA-256, manifest/data fingerprint, and classification label mapping |
| Quality and cost | Accuracy by default for classification, RMSE for regression; intervals, other metrics, time/memory |
| Disagreements | Which rows differ, who is right, and links to disputed-row CSVs |
| Slices/calibration | Weak subgroups and probability reliability where available |
| Swap checks | PASS / FAIL / INCONCLUSIVE against your chosen limits |
| Failures | Full errors/refusals and any resource problems |
Primary classification accuracy can hide imbalance. Inspect AUC, Brier/ECE, class balance, and row-level errors too. You can explicitly use log loss or another supported metric:
python -m quibble analyze --dataset payments --out results_payments --metric log_loss
Report regeneration uses its default primary metric again; it does not inherit the previous
analysis command's --metric. To change the swap quality metric, set primary in a thresholds file.
To customize replacement limits, copy the example and edit it before evaluating:
Copy-Item .\swap_thresholds.example.json .\payments_limits.json
python -m quibble swap --dataset payments --incumbent histgb --candidate lightgbm --out results_payments --thresholds payments_limits.json
Swap returns 0 for PASS, 1 for FAIL, and 2 for INCONCLUSIVE. A policy FAIL does not mean training failed. PASS is not a production-safety guarantee or proof that the data has no leakage. Review limits against the actual cost of changing predictions.
9. Reuse, retry, and reproduce
-
Repeating the same benchmark reuses saved cells, including errors.
--forceretries selected cells; it does not force another checkpoint download. -
Local records save input SHA-256, a fingerprint of file content plus manifest settings, task, target, column rules, and the original classification labels.
-
Source changes are not silently accepted as the old experiment. If file content, target, task, or column rules change, choose a new output root, for example
results_payments_v2. Changes to rows/features can invalidate predictions even if the labels are unchanged. -
Restoring the original file/manifest lets you analyze the old experiment. Moving an unchanged input collection using
QUIBBLE_DATA_DIRdoes not itself alter its content fingerprint. -
Keep original inputs and manifests securely if you want to reproduce analysis. Reports reload features for slices/traits and reject source mismatches; fingerprints do not reconstruct the original table.
-
Regenerate the named smoke report by passing the entire smoke-run root, not its
reportfolder:python -m quibble report --out results_payments\smoke\<unique-id> -
Regenerate the full report without fitting:
python -m quibble report --out results_payments --swap histgb:lightgbm --thresholds payments_limits.json -
Dated history preserves generated records, arrays, and report evidence. Original input feature files and downloaded weights are not copied into history. Retained models live separately under the output root's
artifacts. -
Use
--jobs 1, keep split/CV/model seeds fixed, record package versions, and avoid changing files or installing packages while workers run.
Privacy and publication
The default data/ folder and results/ are ignored by git. An explicitly chosen root
such as results_payments or an external data directory needs its own ignore/protection
rules; .gitignore is not access control.
Quibble's current adapters fit locally, not through hosted prediction APIs. Model downloads can require network access; review optional libraries' telemetry and policies separately. Do not pass data you are not permitted to process.
Results are not anonymous: labels, row indices, label names, file paths, fitted models, and disputed-row CSVs can disclose input information. Disagreement exports include up to the first 40 feature columns for disputed rows. Before sharing reports, snapshots, metadata, or model artifacts, inspect them and remove sensitive material under your data policy.
For temporal/grouped deployment problems, get an appropriate evaluation design before treating these random-split comparisons as decision evidence.