Skip to content

Adding Age-to-Age Factor Smoothing #622

@Hendrik240298

Description

@Hendrik240298

Description

Hi, I would like to propose and discuss the inclusion of my current age-to-age factor smoothing implementation into the chainladder-python codebase.

Summary

Add age-to-age factor smoothing capability to the Development class, allowing to smooth erratic development patterns in specific origins using linear interpolation of cumulative loss values.

Use Cases

  • Smoothing accident years with unusual patterns due to abnormal claims movements
  • Addressing low-volume specialty lines with erratic development
  • Removing data quality issues in specific cells
  • Creating more stable reserve estimates for volatile lines

Proposed API

Find a more detailed example here.

import chainladder as cl

# Load triangle data
tri = cl.load_sample('quarterly')['incurred']

# Smooth specific origin and development range
dev = cl.Development(
    smooth=[('2002', 18, 30)]  # (origin, start_age, end_age) - both INCLUSIVE
).fit(tri)

# Multiple origins with different ranges
dev = cl.Development(
    smooth=[
        ('2002', 18, 30),  # Smooths LDFs starting at ages 18, 21, 24, 27, 30
        ('2003', 9, 12),   # Smooths LDFs starting at ages 9, 12 (minimum: 2 LDFs)
    ]
).fit(tri)

# Combine with other Development parameters
dev = cl.Development(
    smooth=[('2002', 18, 30)],
    n_periods=10,
    drop=[('2001', 12)]
).fit(tri)

Algorithm

Linear Interpolation of Cumulative Values:

  1. Extract cumulative loss values at the start and end boundaries of the smoothed block
  2. Linearly interpolate intermediate cumulative values: C_i = C_start + w_i × (C_end - C_start)
  3. Recalculate age-to-age factors from interpolated cumulatives: LDF_j = C_{j+1} / C_j

Properties:

  • Preserves boundary values exactly
  • Produces smoothly declining factors
  • No parametric assumptions required
  • Simple, transparent calculation

Implementation Details

New Parameter:

  • smooth: tuple or list of tuples specifying (origin, start_age, end_age)
  • Both start_age and end_age are inclusive (starting ages of first and last LDFs to smooth)
  • Minimum requirement: 2 link ratios (3 cumulative values) needed for interpolation
  • Applied to specific origins before averaging across all origins

Workflow Position:

Development.fit() pipeline:
  1. Convert to cumulative and development mode
  2. Get triangle arrays
  3. → Apply smoothing (modifies specific origins)
  4. Apply drop adjustments (sets weights for exclusion)
  5. Run weighted regression (averages across origins)
  6. Calculate LDFs, CDFs, sigmas, etc.

Important: If you smooth AND drop the same cell:

  • The smoothed value is calculated but then excluded from the weighted average
  • Best practice: Use smooth OR drop for a given cell, not both

Display Mechanism:

  • Smoothed values visible in triangle.link_ratio heatmaps
  • Uses smooth_weights_ attribute to scale displayed values
  • Existing triangle.py code applies these weights automatically

Implementation Status

  • Add smooth parameter to Development class with inclusive end_age behavior
  • Implement _smooth() method with linear interpolation
  • Add smooth_weights_ for heatmap display
  • Write tests for new feature
  • Update Development docstring
  • Create minimal example notebook demonstrating functionality
  • Verify integration with existing methods
  • Ensure backend compatibility (numpy, sparse)

Files Changed

Modified:

  1. chainladder/development/development.py - Updated docstring with smooth parameter
  2. chainladder/development/base.py - Added _smooth() method with inclusive end_age behavior
  3. chainladder/development/tests/test_smoothing.py - New test file (31 tests, all updated for inclusive API)

Tests

Current tests are covering:

  • Basic functionality (single/multiple tuples)
  • Error handling (invalid inputs, minimum periods)
  • Integration with other Development parameters (including drop)
  • Edge cases (first/last origins, boundary preservation)
  • Backend compatibility (numpy, sparse)
  • No unintended cascade beyond specified range

Is your feature request at odds with the scope of the package?

  • Yes, absolutely!
  • No, but it's still worth discussing.
  • N/A (this request is not a codebase enhancement).

Describe the solution you'd like, or your current workaround.

No response

Do you have any additional supporting notes?

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    Effort > Moderate 🐕Mid-sized tasks estimated to take a few days to a few weeks.Great First Contribution! 🌱Beginner friendly tickets with narrow scope and huge impact. Perfect to join our community!Impact > Moderate 🔶User-visible but non-breaking change. Treated like a minor version bump (e.g., 0.6.5 → 0.7.0).

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions