Vintage & Smoothing API Reference
Vintage Curve Fitting and Analysis
Use the vintage facade when you have cohort-level cumulative loss observations
by month on book. Start with vintage.run() for comparison,
validation, and optional tail projection; use CurveFitter directly only for a
narrow parametric fit.
cranalytics.vintage
Vintage analysis compatibility re-export surface.
The deep entry point for the vintage workflow is cranalytics.vintage.run
(defined in :mod:cranalytics.vintage._session), which orchestrates triangle
construction, smoothing comparison, ranking, and validation behind one call.
Prefer it for end-to-end analysis; it returns a result with .summary() and
.plot().
This module carries no logic of its own — it only re-exports the focused
vintage_* submodules (transforms, fitting, smoothing, validation) so that
existing from cranalytics.vintage import ... call sites keep working. Its
pure re-export status is pinned by tests/test_vintage_reexport_guard.py;
add new behavior to the appropriate vintage_* submodule, not here.
CurveFitter
Bases: BaseEstimator, RegressorMixin
Fit a parametric cumulative-loss curve to vintage observations.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
method
|
str
|
Curve family. One of |
'weibull'
|
Examples:
>>> import numpy as np
>>> from cranalytics import CurveFitter
>>> mob = np.arange(1, 13)
>>> losses = 0.08 * (1.0 - np.exp(-mob / 6.0))
>>> fitter = CurveFitter("weibull").fit(mob, losses)
>>> fitter.predict(np.array([12, 18])).shape
(2,)
.. warning:: Macro-Level Approximation Only This estimator fits cumulative loss rates over time. Iteratively predicting loss extrapolates cumulative impact based on the original starting cohort size. It is blind to actual principal paydown, scheduled amortization, and constant prepayment rates (CPR). It should be used for high-level scenario approximation, not as a substitute for true loan-level cashflow modeling.
fit(X: ArrayLike, y: ArrayLike, sample_weight: ArrayLike | None = None) -> CurveFitter
Fit the selected curve family.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
X
|
ArrayLike
|
Months-on-book observations. |
required |
y
|
ArrayLike
|
Observed cumulative loss rates. |
required |
sample_weight
|
ArrayLike | None
|
Optional non-negative observation weights. |
None
|
Returns:
| Type | Description |
|---|---|
CurveFitter
|
Fitted estimator with |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the method is unsupported. |
RuntimeError
|
If numerical optimization fails. |
predict(X: ArrayLike) -> np.ndarray
Predict cumulative loss rates for months on book.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
X
|
ArrayLike
|
Months-on-book values to evaluate. |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
Predicted cumulative loss rates. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the estimator is not fitted or the method is unsupported. |
forecast(months: np.ndarray) -> np.ndarray
Alias for predict().
VintageAnalysisSessionResult
dataclass
Bases: _SessionResultMapping
summary() -> pd.DataFrame
Compact, best-first ranking of the smoothing methods considered.
plot(**kwargs: Any) -> Any
Plotly heatmap of the vintage triangle. Requires the viz extra.
BaseSmoother
Base class for smoothing methods.
run(df: pd.DataFrame, *, vintage_col: str = 'vintage_date', mob_col: str = 'months_on_book', loss_col: str = 'cumulative_loss_rate', balance_col: str | None = None, segment_col: str | None = None, vintage_name: str | None = None, smoothers: list[str | Any] | None = None, min_maturity_months: int | None = None, include_cv: bool = True, strict: bool = False, extrapolate_tails: bool = False, tail_max_mob: int | None = None) -> VintageAnalysisSessionResult
Run one representative vintage analysis workflow from raw data.
Parameters
extrapolate_tails:
When True and incomplete vintages are present, project each immature
vintage's tail forward using the best-ranked smoother. Results are
returned on tail_projections as a long-format DataFrame with an
is_projected bool column.
tail_max_mob:
Project tails to this month-on-book. Defaults to the maximum MOB
observed in the selected (complete) vintage.
smooth_curve(mob: np.ndarray, values: np.ndarray, method: str | BaseSmoother, weights: np.ndarray | None = None, **kwargs) -> SmoothedCurve
Functional wrapper to smooth a curve by name or smoother instance.
aggregate_by_dollar_weights(df: pd.DataFrame, vintage_col: str = 'vintage_date', mob_col: str = 'months_on_book', segment_col: str | None = 'fico_band', loss_col: str = 'charge_off_amount', balance_col: str = 'outstanding_balance') -> pd.DataFrame
Aggregate segmented data using dollar-weighted averaging.
Aggregation is always performed by vintage and months-on-book. When segment_col is provided and present in the DataFrame, results are further segmented by that column.
create_vintage_triangle(df: pd.DataFrame, vintage_col: str = 'vintage_date', mob_col: str = 'months_on_book', loss_col: str = 'cumulative_loss_rate', balance_col: str | None = None) -> pd.DataFrame
Create a vintage triangle from loan-level or aggregated data.
When balance_col is provided, loss rates are dollar-weighted (weighted average by balance). Otherwise a simple mean is used.
detect_incomplete_vintages(triangle: pd.DataFrame, min_maturity_months: int = 24) -> list[str]
Detect vintages that have dropped off (e.g., after charge-off).
normalize_vintage_data(df: pd.DataFrame, vintage_col: str = 'vintage_date', mob_col: str = 'months_on_book', loss_col: str = 'cumulative_loss_rate', segment_col: str | None = None) -> list[pd.DataFrame]
Normalize raw data into a list of standardized DataFrames for curve fitting. Each DataFrame corresponds to a unique vintage-segment and contains 'mob' and 'cumulative_loss_rate'.
project_incomplete_vintage_tails(df: pd.DataFrame, incomplete_vintages: list[str], smoother: BaseSmoother | str, max_mob: int, vintage_col: str = 'vintage_date', mob_col: str = 'months_on_book', loss_col: str = 'cumulative_loss_rate') -> pd.DataFrame
Return projected tail rows for incomplete vintages.
For each vintage in incomplete_vintages, fits smoother to the observed mob/loss pairs and forecasts forward to max_mob. Projected values are clipped to be non-decreasing and bounded by 1.0 (cumulative loss semantics). If a vintage has fewer than two observations, a constant tail (last observed value held flat) is used instead.
Parameters
df:
Long-format vintage data (same format passed to
cranalytics.vintage.run).
incomplete_vintages:
Vintage names as they appear in df[vintage_col].
smoother:
A :class:~cranalytics.vintage.BaseSmoother instance or a method name
string accepted by :func:~cranalytics.vintage.create_smoother
(e.g. "moving_average", "spline").
max_mob:
The final month-on-book to project to.
vintage_col, mob_col, loss_col:
Column names matching those used in df.
Returns
pd.DataFrame
Long-format DataFrame with synthetic rows for each
(vintage, mob) pair beyond the observed range. Includes an
is_projected bool column marking all returned rows.
cross_validate_smoother(smoother: Any, mob: np.ndarray, values: np.ndarray, weights: np.ndarray | None = None, n_folds: int = 5, strict: bool = True, cv_method: str = 'rolling_origin') -> tuple[float, float]
Cross-validate a smoother and return (mean_mse, std_mse).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
smoother
|
Any
|
A fitted smoother object with |
required |
mob
|
ndarray
|
Months-on-book array (time index). Must be monotonically non-decreasing
for |
required |
values
|
ndarray
|
Observed curve values. |
required |
weights
|
ndarray | None
|
Optional per-observation weights. |
None
|
n_folds
|
int
|
Number of CV folds. |
5
|
strict
|
bool
|
If True, raise on fold failure; if False, skip failed folds. |
True
|
cv_method
|
str
|
Fold strategy:
|
'rolling_origin'
|