Your Data
Almost everything in cranalytics takes a pandas DataFrame. Start with the
data you have, then choose the workflow-specific contract. There are three
source-data families; a labeled modeling frame and vintage observations are
derived inputs with their own requirements.
Start from your source data
| Source | Grain | Minimum identity/date columns | First step |
|---|---|---|---|
| Loan snapshot | one row per loan at an observation date | loan_id, fund_date, as_of_date |
loan_tape.validate_loan_tape(df, kind="snapshot") |
| Loan history | one row per loan per observation date | loan_id, fund_date, as_of_date |
loan_tape.validate_loan_tape(df, kind="history") |
| Rollforward aggregate | one row per segment per month-on-book | segment_id, month_on_book, payments, chargeoffs, outstanding_balance |
ca.check(df, workflow="rollforward") for the base schema; cranalytics rollforward check your_rollforward_data.csv --output-dir rollforward_readiness_out for readiness |
The loan-tape minimum identifies records and dates. Forecasts, targets and
survival analysis require additional balances, terms, outcomes or event fields.
See Input Data Contracts before selecting
a converter from cranalytics.loan_tape.
Check a workflow-ready frame
A forecast-ready portfolio snapshot uses loan_id, principal,
annual_rate, term_months, start_date and status. Here principal is
current outstanding balance and rates are decimals. Check it before forecasting:
import pandas as pd
import cranalytics as ca
portfolio = pd.DataFrame({
"loan_id": ["L1", "L2"],
"principal": [10000.0, 15000.0],
"annual_rate": [0.08, 0.10],
"term_months": [36, 36],
"start_date": pd.to_datetime(["2024-01-01", "2024-02-01"]),
"status": ["Current", "Delinquent"],
})
report = ca.check(portfolio, workflow="lifetime-loss")
print(report.ok, report.workflow) # True, "portfolio": the base contract checked
print(report.required_columns)
print(report.issues)
check() returns a not-ok report for contract failures by default. Use
strict=True to raise; unknown workflow names raise even without strict mode.
Without workflow=, it detects a contract from column names. Ambiguous or
unrecognized columns return ok=False with candidate contracts.
On the command line: cranalytics check your_data.csv.
Python accepts the same workflow names as the CLI. The report names the base contract actually checked:
| Workflow name | Base contract | Additional requirements checked at run time |
|---|---|---|
lifetime-loss, simulation |
portfolio |
Transition inputs, forecast assumptions |
segmentation |
segmentation |
Only fico_score is required for segmentation |
ml-modeling |
feature-analytics when no roles are supplied |
Pass feature_cols, target_col, split_col, and model configuration to check the selected modeling roles |
survival |
portfolio when no roles are supplied |
Pass date/status columns and event mappings to check the selected time-to-event roles |
vintage |
vintage |
Cohort selection, maturity and smoothing options |
rollforward |
rollforward |
History coverage, backtesting and readiness diagnostics |
A base-schema pass reports readiness="base-only" and does not establish full
workflow readiness. For ML, pass the same roles to check() that you will pass
to predictive.run(); for survival, pass the date/status columns and event
mapping used by survival.run(). Custom frames need not contain the fixed
feature-analytics or portfolio columns used by the discovery checks.
Derived modeling and vintage inputs
Modeling frame: features and labels at the model's intended grain. The
feature-analytics base contract requires loan_id, principal, annual_rate,
term_months, start_date, fico_score and fpf30_flag. A temporal model also
needs a split column. This frame is not the raw loan history used to derive its
targets. See the ML tutorial.
Vintage observations: long-format cumulative losses, one row per cohort and
MOB. Aggregate loan-level records to this grain before calling the workflow.
vintage.run() takes this long format and builds a wide vintage triangle
internally. history_to_triangle() also returns a wide grid for inspection or
plotting; do not pass that grid directly to vintage.run().
import pandas as pd
import cranalytics as ca
observations = pd.DataFrame({
"vintage_name": ["2024-Q1"] * 12,
"mob": range(1, 13),
"cumulative_loss_rate": [
0.001, 0.003, 0.006, 0.010, 0.014, 0.018,
0.022, 0.026, 0.030, 0.033, 0.036, 0.039,
],
})
report = ca.check(observations, workflow="vintage")
triangle = ca.history_to_triangle(observations)
result = ca.vintage.run(
observations, vintage_col="vintage_name", mob_col="mob"
)
print(report.ok, triangle.shape)
print(result.summary())
For compatibility, CheckReport.shape still groups vintage observations and
feature-analytics frames under "loan history". That label does not mean their
columns match the raw loan-tape history contract. Use report.workflow and
report.required_columns to identify the contract.
Select the latest observation per loan
snapshot_from_history() keeps the row with the greatest period value for each
loan. Pass your observation-date column explicitly for raw history:
import pandas as pd
import cranalytics as ca
history = pd.DataFrame({
"loan_id": ["L1", "L1", "L2"],
"fund_date": pd.to_datetime(["2024-01-01"] * 3),
"as_of_date": pd.to_datetime(["2024-02-01", "2024-03-01", "2024-02-01"]),
"current_balance": [10000.0, 9800.0, 15000.0],
})
snapshot = ca.snapshot_from_history(history, period_col="as_of_date")
print(snapshot[["loan_id", "as_of_date", "current_balance"]])
This selection preserves the input columns. It does not add the balances,
rates, terms and statuses required by a forecasting portfolio. Likewise,
history_to_triangle() expects a cohort and a precomputed cumulative loss rate;
it does not derive loss rates from raw chargeoff amounts. Derive those fields
first and supply explicit column names when they differ from the defaults.