Skip to content

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.