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