Skip to content

Projection

gaspatchio_core.accessors.projection.ProjectionColumnAccessor

Bases: BaseColumnAccessor

Actuarial projection operations for time-series calculations.

This accessor provides methods for transforming rates and probabilities into cumulative values over projection periods. Complex operations like cumulative products use these methods, while simple operations like multiplication should use standard operators.

Design Philosophy
  • Complex cumulative operations: Use projection methods
  • Simple arithmetic: Use operators (*, +, -, /)
  • Terminal/aggregate values: Use Polars (.list.last())

Accessed via .projection on a column or expression, e.g., af["mortality_rate"].projection.cumulative_survival().

Examples:

Cumulative survival from mortality rates:

from gaspatchio_core import ActuarialFrame

data = {"qx": [[0.001, 0.0011, 0.0012], [0.002, 0.0022, 0.0024]]}
af = ActuarialFrame(data)

# Complex cumulative product - use projection method
af.survival_to_t = af.qx.projection.cumulative_survival()

# Simple multiplication - use operators
af.death_benefit = af.face_amount * af.survival_to_t * af.qx
af.premium = af.annual_premium * af.survival_to_t

# Terminal value - use Polars
af.maturity_benefit = af.face_amount.list.last()

Premium holiday modeling:

from gaspatchio_core import ActuarialFrame

data = {"premium": [[1000, 1000, 1000, 1000, 1000]]}
af = ActuarialFrame(data)

# Period override - use projection method
af.premium_with_holiday = af.premium.projection.with_period(3, value=0)
# Result: [1000, 1000, 1000, 0, 1000]

at_period(relative_period, fill_value=0.0)

Get value at relative period offset.

Access values from other time periods using mathematical t notation. Negative values reference prior periods (t-1, t-2), positive values reference future periods (t+1, t+2).

This method provides flexible time-shifting for arbitrary period offsets, complementing the convenience methods previous_period() (t-1) and next_period() (t+1).

For list columns, shifts values within each list. For scalar columns, shifts across rows (use .over() for grouping).

When to use

  • Multi-Period Lag Analysis: Access values from multiple periods back (t-2, t-3) for trend analysis and smoothing calculations.
  • Reserve Rollforward: Reference reserves from specific prior periods in complex reserve formulas requiring multiple lag periods.
  • Experience Studies: Compare values across multiple time periods to analyze experience trends and validate assumptions.
  • Flexible Time-Shifting: Use when previous_period() and next_period() don't provide the specific offset needed for your calculation.
Parameters

relative_period : int Period offset from current time using mathematical notation: - Negative values: prior periods (e.g., -1 for t-1, -2 for t-2) - Positive values: future periods (e.g., 1 for t+1, 2 for t+2) - Zero: current period (no shift) fill_value : scalar, optional Value to use for missing entries at boundaries. Default is 0.

Returns

ExpressionProxy Expression with values from specified relative period

Examples

Previous Period: t-1

from gaspatchio_core import ActuarialFrame

data = {"reserve": [[1000, 1100, 1200]]}
af = ActuarialFrame(data)

# at_period(-1) is equivalent to previous_period()
af.reserve_t1 = af.reserve.projection.at_period(-1)

print(af.collect())
shape: (1, 2)
┌──────────────────┬──────────────────┐
│ reserve          ┆ reserve_t1       │
│ ---              ┆ ---              │
│ list[i64]        ┆ list[i64]        │
╞══════════════════╪══════════════════╡
│ [1000, 1100, ...] ┆ [0, 1000, 1100]  │
└──────────────────┴──────────────────┘

Two Periods Back: t-2

from gaspatchio_core import ActuarialFrame

data = {"value": [[100, 110, 120, 130, 140]]}
af = ActuarialFrame(data)

af.value_t2 = af.value.projection.at_period(-2)

print(af.collect())
shape: (1, 2)
┌───────────────────────┬──────────────────────┐
│ value                 ┆ value_t2             │
│ ---                   ┆ ---                  │
│ list[i64]             ┆ list[i64]            │
╞═══════════════════════╪══════════════════════╡
│ [100, 110, 120, 13... ┆ [0, 0, 100, 110, 120]│
└───────────────────────┴──────────────────────┘

Next Period: t+1

from gaspatchio_core import ActuarialFrame

data = {"cashflow": [[1000, 1100, 1200]]}
af = ActuarialFrame(data)

# at_period(1) is equivalent to next_period()
af.cf_tp1 = af.cashflow.projection.at_period(1)

print(af.collect())
shape: (1, 2)
┌──────────────────┬─────────────────┐
│ cashflow         ┆ cf_tp1          │
│ ---              ┆ ---             │
│ list[i64]        ┆ list[i64]       │
╞══════════════════╪═════════════════╡
│ [1000, 1100, ...] ┆ [1100, 1200, 0] │
└──────────────────┴─────────────────┘

Reserve Rollforward Formula

from gaspatchio_core import ActuarialFrame

data = {
    "reserve": [[0, 950, 1900, 2850]],
    "premium": [[1000, 1000, 1000, 1000]],
    "interest": [[50, 52, 55, 58]],
    "benefit": [[100, 102, 105, 108]],
}
af = ActuarialFrame(data)

# Reserve rollforward formula:
# Reserve(t) = Reserve(t-1) + Premium(t) + Interest(t) - Benefit(t)
af.reserve_t1 = af.reserve.projection.at_period(-1)
af.reserve_calc = af.reserve_t1 + af.premium + af.interest - af.benefit

print(af.collect())
shape: (1, 6)
┌─────────────────┬─────────────┬─────────┬─────────┬──────────┬──────────────┐
│ reserve         ┆ premium     ┆ intere.. ┆ benefit ┆ reserve..┆ reserve_calc │
│ ---             ┆ ---         ┆ ---     ┆ ---     ┆ ---      ┆ ---          │
│ list[i64]       ┆ list[i64]   ┆ list..  ┆ list..  ┆ list[i64]┆ list[i64]    │
╞═════════════════╪═════════════╪═════════╪═════════╪══════════╪══════════════╡
│ [0, 950, 19...┆ [1000, 10...┆ [50, 52...┆ [100, ...┆ [0, 0, 9...┆ [950, 19...│
└─────────────────┴─────────────┴─────────┴─────────┴──────────┴──────────────┘
See Also

previous_period : Convenience method for t-1 next_period : Convenience method for t+1

cumulative_survival(rate_timing=None, start_at=1.0)

Convert mortality rates to cumulative survival probabilities.

Transforms period mortality rates (qx) into cumulative survival probabilities using the formula tpx[t] = (1-qx[0]) * (1-qx[1]) * ... * (1-qx[t]). Essential for life insurance projections, reserve calculations, and any actuarial work requiring survival probabilities from mortality assumptions.

For list columns, applies element-wise cumulative product within each list. For scalar columns, applies cumulative product across rows (use .over() for grouping by policy).

When to use

  • Life Insurance Projections: Calculate the probability policies remain inforce for death benefit, premium, and cash value projections.
  • Reserve Calculations: Compute expected policy counts for reserve valuations and capital requirements.
  • Persistency Analysis: Model combined mortality and lapse decrements to project policy persistency over time.
  • Pricing Models: Calculate expected present values of benefits and premiums weighted by survival probabilities.
Timing Conventions

The rate_timing parameter controls when decrement rates are applied:

  • beginning_of_period (default): Rate at period t is NOT yet applied to P[IF][t]. The survival at t represents the probability of surviving TO the start of period t. Result: [1.0, tpx[0], tpx[0]*tpx[1], ...]

  • end_of_period: Rate at period t HAS been applied to P[IF][t]. The survival at t represents the probability of surviving THROUGH period t. This matches Excel-style timing. Result: [tpx[0], tpx[0]*tpx[1], ...]

With constant rates, both conventions give identical values. The difference only appears when rates change over time (e.g., at age boundaries).

Parameters

rate_timing : {"beginning_of_period", "end_of_period"}, optional When decrement rates are applied. Recommended for most users:

- ``"beginning_of_period"``: Rate at t NOT yet applied (default behavior)
- ``"end_of_period"``: Rate at t HAS been applied (Excel-style)

If not specified, falls back to `start_at` parameter behavior.

start_at : float, optional Lower-level control over timing. Only use if rate_timing is not set. Initial survival probability to prepend at t=0:

- 1.0 (default): Beginning-of-period [1.0, tpx[0], tpx[1], ...]
- None: End-of-period [tpx[0], tpx[1], ...]
- Other: Custom initial value (e.g., 0.95 for partial cohort)
Returns

ExpressionProxy Cumulative survival probabilities for each period

Raises

ValueError If both rate_timing and a non-default start_at are specified, or if rate_timing has an invalid value RuntimeError If the column is not part of an ActuarialFrame context

Examples

Beginning-of-Period Timing (Default)

from gaspatchio_core import ActuarialFrame

data = {
    "policy_id": ["P001", "P002"],
    "qx": [[0.001, 0.002, 0.003], [0.002, 0.003, 0.004]],
}
af = ActuarialFrame(data)

# Default: rate at t not yet applied
af.pols_if = af.qx.projection.cumulative_survival()
# Or explicitly:
af.pols_if = af.qx.projection.cumulative_survival(
    rate_timing="beginning_of_period"
)

print(af.collect())
shape: (2, 3)
┌───────────┬───────────────────────┬────────────────────────┐
│ policy_id ┆ qx                    ┆ pols_if                │
│ ---       ┆ ---                   ┆ ---                    │
│ str       ┆ list[f64]             ┆ list[f64]              │
╞═══════════╪═══════════════════════╪════════════════════════╡
│ P001      ┆ [0.001, 0.002, 0.003] ┆ [1.0, 0.999, 0.997002] │
│ P002      ┆ [0.002, 0.003, 0.004] ┆ [1.0, 0.998, 0.995006] │
└───────────┴───────────────────────┴────────────────────────┘

End-of-Period Timing (Excel-Style)

from gaspatchio_core import ActuarialFrame

data = {
    "qx": [[0.001, 0.002, 0.003]],
}
af = ActuarialFrame(data)

# Excel-style: rate at t has been applied
af.tpx = af.qx.projection.cumulative_survival(rate_timing="end_of_period")

print(af.collect())
shape: (1, 2)
┌───────────────────────┬─────────────────────────────┐
│ qx                    ┆ tpx                         │
│ ---                   ┆ ---                         │
│ list[f64]             ┆ list[f64]                   │
╞═══════════════════════╪═════════════════════════════╡
│ [0.001, 0.002, 0.003] ┆ [0.999, 0.997002, 0.994011] │
└───────────────────────┴─────────────────────────────┘

Custom Initial Value (Partial Cohort)

from gaspatchio_core import ActuarialFrame

data = {
    "qx": [[0.001, 0.002, 0.003]],
}
af = ActuarialFrame(data)

# 95% survived underwriting - use start_at for custom values
af.pols_if = af.qx.projection.cumulative_survival(start_at=0.95)

print(af.collect())
shape: (1, 2)
┌───────────────────────┬─────────────────────────┐
│ qx                    ┆ pols_if                 │
│ ---                   ┆ ---                     │
│ list[f64]             ┆ list[f64]               │
╞═══════════════════════╪═════════════════════════╡
│ [0.001, 0.002, 0.003] ┆ [0.95, 0.999, 0.997002] │
└───────────────────────┴─────────────────────────┘

next_period(fill_value=0.0)

Get value from next period (t+1).

Equivalent to shifting forward one period. Less common than previous_period() but useful for certain actuarial calculations requiring forward-looking values.

For list columns, shifts values within each list. For scalar columns, shifts across rows (use .over() for grouping).

When to use

  • Forward-Looking Calculations: Access next period values for calculations that require looking ahead in the projection timeline.
  • Period-Over-Period Growth: Calculate growth rates or changes by comparing current values to next period values.
  • Validation Checks: Verify that projected values follow expected patterns by comparing current and next period results.
  • Timing Adjustments: Reference future period values when modeling payment or benefit timing that leads the valuation period.
Parameters

fill_value : scalar, optional Value to use for last period where no next value exists. Default is 0.

Returns

ExpressionProxy Expression with values shifted from next period

Examples

Basic Usage: Next Period Values

from gaspatchio_core import ActuarialFrame

data = {"interest_rate": [[0.05, 0.06, 0.07]]}
af = ActuarialFrame(data)

af.rate_next = af.interest_rate.projection.next_period()

print(af.collect())
shape: (1, 2)
┌────────────────────┬───────────────────┐
│ interest_rate      ┆ rate_next         │
│ ---                ┆ ---               │
│ list[f64]          ┆ list[f64]         │
╞════════════════════╪═══════════════════╡
│ [0.05, 0.06, 0.07] ┆ [0.06, 0.07, 0.0] │
└────────────────────┴───────────────────┘

Forward-Looking Calculation Example

from gaspatchio_core import ActuarialFrame

data = {"cashflow": [[1000, 1100, 1200]]}
af = ActuarialFrame(data)

# Compare current period to next period
af.cf_next = af.cashflow.projection.next_period()
af.cf_growth = af.cf_next - af.cashflow

print(af.collect())
shape: (1, 3)
┌──────────────────┬─────────────────┬──────────────────┐
│ cashflow         ┆ cf_next         ┆ cf_growth        │
│ ---              ┆ ---             ┆ ---              │
│ list[i64]        ┆ list[i64]       ┆ list[i64]        │
╞══════════════════╪═════════════════╪══════════════════╡
│ [1000, 1100, ...] ┆ [1100, 1200, 0] ┆ [100, 100, -...] │
└──────────────────┴─────────────────┴──────────────────┘
See Also

previous_period : Get value from previous period (t-1) at_period : Get value at arbitrary period offset

previous_period(fill_value=0.0)

Get value from previous period (t-1).

Equivalent to shifting back one period. Most common case for actuarial projections when referencing prior period values.

For list columns, shifts values within each list. For scalar columns, shifts across rows (use .over() for grouping).

When to use

  • Inforce Rollforward: Calculate beginning-of-period inforce values using ending inforce from the previous period in life insurance models.
  • Reserve Calculations: Access prior period reserves for reserve rollforward formulas and cash flow testing.
  • Period Comparisons: Compare current period values against previous period for variance analysis and experience studies.
  • Dependent Calculations: Reference lagged values in formulas where current period depends on prior period results.
Parameters

fill_value : scalar, optional Value to use for first period where no previous value exists. Default is 0.

Returns

ExpressionProxy Expression with values shifted from previous period

Examples

Basic Usage: Previous Period Values

from gaspatchio_core import ActuarialFrame

data = {"pols_death": [[10, 15, 20]]}
af = ActuarialFrame(data)

af.pols_death_prev = af.pols_death.projection.previous_period()

print(af.collect())
shape: (1, 2)
┌──────────────┬──────────────────┐
│ pols_death   ┆ pols_death_prev  │
│ ---          ┆ ---              │
│ list[i64]    ┆ list[i64]        │
╞══════════════╪══════════════════╡
│ [10, 15, 20] ┆ [0, 10, 15]      │
└──────────────┴──────────────────┘

Custom Fill Value: Reserve Calculations

from gaspatchio_core import ActuarialFrame

data = {"reserve": [[1000, 1100, 1200]]}
af = ActuarialFrame(data)

# Use None to get null for missing values
af.reserve_prev = af.reserve.projection.previous_period(fill_value=None)

print(af.collect())
shape: (1, 2)
┌──────────────────┬──────────────────┐
│ reserve          ┆ reserve_prev     │
│ ---              ┆ ---              │
│ list[i64]        ┆ list[i64]        │
╞══════════════════╪══════════════════╡
│ [1000, 1100, ...] ┆ [null, 1000, ...] │
└──────────────────┴──────────────────┘

Actuarial Formula: Inforce Rollforward

from gaspatchio_core import ActuarialFrame

data = {
    "pols_if_after_death": [[1000, 990, 975]],
    "pols_lapse": [[5, 8, 10]],
}
af = ActuarialFrame(data)

# Calculate beginning-of-period inforce using previous period values
# pols_if_bop(t) = pols_if_after_death(t-1) - pols_lapse(t-1)
af.pols_if_prev = af.pols_if_after_death.projection.previous_period(
    fill_value=1000
)
af.pols_lapse_prev = af.pols_lapse.projection.previous_period()
af.pols_if_bop = af.pols_if_prev - af.pols_lapse_prev

print(af.collect())
shape: (1, 4)
┌─────────────────────┬─────────────┬────────────────┬─────────────┐
│ pols_if_after_death ┆ pols_lapse  ┆ pols_if_prev   ┆ pols_if_bop │
│ ---                 ┆ ---         ┆ ---            ┆ ---         │
│ list[i64]           ┆ list[i64]   ┆ list[i64]      ┆ list[i64]   │
╞═════════════════════╪═════════════╪════════════════╪═════════════╡
│ [1000, 990, 975]    ┆ [5, 8, 10]  ┆ [1000, 1000... ┆ [1000, 995..│
└─────────────────────┴─────────────┴────────────────┴─────────────┘
See Also

next_period : Get value from next period (t+1) at_period : Get value at arbitrary period offset

prospective_value(discount_rate=None, discount_factor=None, *, timing='end_of_period')

Calculate prospective (present) value of future cashflows from each time t.

Computes the present value of all future cashflows from each projection period onwards, using backward recursion: PV(t) = CF(t) + PV(t+1) * v(t).

This is the standard actuarial "prospective policy value" calculation, essential for reserve valuations, embedded value projections, profit testing, and asset adequacy testing. Replaces complex Polars list operations with a clean, actuarial-focused API.

When to use

  • Reserve Calculations: Compute present value of future benefits less premiums for statutory and GAAP reserve valuations.
  • Embedded Value: Calculate present value of future profits for embedded value and value of in-force business metrics.
  • Profit Testing: Project present value of cashflows at each duration for pricing validation and profitability analysis.
  • Asset Adequacy: Test sufficiency of assets to cover future liabilities under various interest rate scenarios.
Parameters

discount_rate : float or ExpressionProxy or ColumnProxy, optional Per-period discount rate for discounting future cashflows:

- Scalar float: Constant rate for all periods (e.g., 0.05 for 5%)
- List column: Per-period rates that may vary over time

Cannot be specified together with `discount_factor`.

discount_factor : ExpressionProxy or ColumnProxy, optional Pre-computed discount factors (v^t values). Use when you have yield curve or scenario-specific discount factors already calculated.

Cannot be specified together with `discount_rate`.

timing : {"beginning_of_period", "end_of_period"}, default "end_of_period" When cashflows occur within each period:

- ``"end_of_period"``: Cashflow at t paid at end of period (benefits)
- ``"beginning_of_period"``: Cashflow at t paid at start (premiums)
Returns

ExpressionProxy Present value of future cashflows at each projection period

Raises

ValueError If both discount_rate and discount_factor are specified, or if neither is specified

Examples

Death Benefit PV with Constant Discount Rate

from gaspatchio_core import ActuarialFrame

data = {
    "death_benefit": [[100.0, 100.0, 100.0]],
}
af = ActuarialFrame(data)

# Calculate prospective value at 5% discount rate
af.pv_benefits = af.death_benefit.projection.prospective_value(
    discount_rate=0.05
)

print(af.collect())
shape: (1, 2)
┌────────────────────┬─────────────────────────────┐
│ death_benefit      ┆ pv_benefits                 │
│ ---                ┆ ---                         │
│ list[f64]          ┆ list[f64]                   │
╞════════════════════╪═════════════════════════════╡
│ [100.0, 100.0, ... ┆ [285.94, 195.24, 100.0]     │
└────────────────────┴─────────────────────────────┘

Premium PV with Time-Varying Rates

from gaspatchio_core import ActuarialFrame

data = {
    "premium": [[1000.0, 1000.0, 1000.0]],
    "disc_rate": [[0.04, 0.05, 0.06]],
}
af = ActuarialFrame(data)

af.pv_premiums = af.premium.projection.prospective_value(
    discount_rate=af.disc_rate,
    timing="beginning_of_period"
)

print(af.collect())

With Pre-Computed Discount Factors

from gaspatchio_core import ActuarialFrame

data = {
    "benefit": [[100.0, 100.0, 100.0]],
    "v_t": [[1.0, 0.952381, 0.907029]],  # 5% discount factors
}
af = ActuarialFrame(data)

af.pv = af.benefit.projection.prospective_value(discount_factor=af.v_t)

print(af.collect())
Notes

Implementation Details:

The method internally performs:

  1. Compute discounted cashflows: CF(t) * v(t)
  2. Fill NaN values with 0 (handles cashflows beyond policy term)
  3. Apply reverse -> cumsum -> reverse pattern to get "sum from t to end"
  4. Adjust for timing convention

Replaces Ugly Pattern:

This method replaces verbose Polars list manipulation. The old pattern required 6+ lines of Polars list operations (reverse, cumsum, reverse), while the new API is a single clean method call.

See Also

cumulative_survival : Calculate cumulative survival probabilities previous_period : Access prior period values for reserve rollforward

with_period(period, value)

Override value at a specific period (zero-indexed).

Creates a modified version of a list column with a specific element set to a new value. Essential for modeling planned policy changes, premium holidays, benefit adjustments, and other known discontinuities in actuarial projections.

This method only works with list columns. For scalar columns, use conditional logic with .when() and .then().

When to use

  • Premium Holidays: Model scheduled breaks in premium payments, such as waiver of premium periods or contractual payment holidays.
  • Benefit Changes: Implement known benefit adjustments at specific durations, like step-up death benefits or maturity bonuses.
  • Policy Events: Model surrender charge schedules, conversion options, or guaranteed insurability riders that activate at specific times.
  • Assumption Overrides: Apply one-time adjustments to mortality rates, lapse rates, or expenses for specific policy anniversaries.
Parameters

period : int Zero-based index to modify. Negative indices supported (-1 = last period). value : float or str Value to set at that period

Returns
Modified list with value changed at specified period
Raises
RuntimeError: If proxy not associated with an ActuarialFrame
ValueError: If period is out of bounds for the list
Examples

Vector Example: Premium Holiday

from gaspatchio_core import ActuarialFrame

data = {"premium": [[1000, 1000, 1000]]}
af = ActuarialFrame(data)

af.premium_adj = af.premium.projection.with_period(1, value=0)

print(af.collect())
shape: (1, 2)
┌────────────────────┬─────────────────┐
│ premium            ┆ premium_adj     │
│ ---                ┆ ---             │
│ list[i64]          ┆ list[i64]       │
╞════════════════════╪═════════════════╡
│ [1000, 1000, 1000] ┆ [1000, 0, 1000] │
└────────────────────┴─────────────────┘

Vector Example: Negative Index (Last Period)

from gaspatchio_core import ActuarialFrame

data = {"benefit": [[1000, 1000, 1000]]}
af = ActuarialFrame(data)

af.benefit_adj = af.benefit.projection.with_period(-1, value=5000)

print(af.collect())
shape: (1, 2)
┌────────────────────┬────────────────────┐
│ benefit            ┆ benefit_adj        │
│ ---                ┆ ---                │
│ list[i64]          ┆ list[i64]          │
╞════════════════════╪════════════════════╡
│ [1000, 1000, 1000] ┆ [1000, 1000, 5000] │
└────────────────────┴────────────────────┘

Vector Example: Benefit Increase

from gaspatchio_core import ActuarialFrame

data = {"face_amount": [[100000, 100000, 100000]]}
af = ActuarialFrame(data)

af.face_adj = af.face_amount.projection.with_period(1, value=150000)

print(af.collect())
shape: (1, 2)
┌──────────────────────────┬──────────────────────────┐
│ face_amount              ┆ face_adj                 │
│ ---                      ┆ ---                      │
│ list[i64]                ┆ list[i64]                │
╞══════════════════════════╪══════════════════════════╡
│ [100000, 100000, 100000] ┆ [100000, 150000, 100000] │
└──────────────────────────┴──────────────────────────┘

with_periods(updates)

Override values at multiple specific periods.

Creates a modified version of a list column with multiple elements changed at once. More efficient and readable than chaining multiple with_period() calls. Essential for modeling complex benefit schedules, premium patterns, and assumption variations across policy durations.

When to use

  • Benefit Schedules: Model policies with multiple benefit changes, such as increasing term insurance or scheduled death benefit steps.
  • Premium Patterns: Implement complex premium schedules with multiple holidays, increases, or decreases at known policy anniversaries.
  • Surrender Charges: Define surrender charge schedules that decrease over time or change at specific durations.
  • Assumption Testing: Apply multiple one-time adjustments to test sensitivity to assumption changes at different policy durations.
Parameters

updates : dict[int, int | float | str] Dictionary mapping period indices (zero-based) to new values. Negative indices are supported (-1 = last period).

Returns
Modified list with values changed at specified periods
Raises
RuntimeError: If proxy not associated with an ActuarialFrame
ValueError: If any period is out of bounds for the list
Examples

Vector Example: Multiple Premium Holidays

from gaspatchio_core import ActuarialFrame

data = {"premium": [[500, 500, 500]]}
af = ActuarialFrame(data)

af.premium_adj = af.premium.projection.with_periods({0: 0, 2: 0})

print(af.collect())
shape: (1, 2)
┌─────────────────┬─────────────┐
│ premium         ┆ premium_adj │
│ ---             ┆ ---         │
│ list[i64]       ┆ list[i64]   │
╞═════════════════╪═════════════╡
│ [500, 500, 500] ┆ [0, 500, 0] │
└─────────────────┴─────────────┘

Vector Example: Benefit Schedule

from gaspatchio_core import ActuarialFrame

data = {"benefit": [[1000, 1000, 1000]]}
af = ActuarialFrame(data)

af.benefit_adj = af.benefit.projection.with_periods({0: 1500, -1: 5000})

print(af.collect())
shape: (1, 2)
┌────────────────────┬────────────────────┐
│ benefit            ┆ benefit_adj        │
│ ---                ┆ ---                │
│ list[i64]          ┆ list[i64]          │
╞════════════════════╪════════════════════╡
│ [1000, 1000, 1000] ┆ [1500, 1000, 5000] │
└────────────────────┴────────────────────┘