Source code for actuarialpy.periods
"""Date and period helper primitives."""
from __future__ import annotations
import pandas as pd
[docs]
def to_period(values, freq: str):
"""Convert scalar or array-like date values to pandas Period values."""
return pd.to_datetime(values).to_period(freq)
[docs]
def add_period_column(
df: pd.DataFrame,
date_col: str,
freq: str,
period_col: str | None = None,
*,
copy: bool = True,
) -> pd.DataFrame:
"""Add a pandas Period column from a date column.
Common frequencies include ``M``, ``Q``, and ``Y``.
"""
if date_col not in df.columns:
raise ValueError(f"Missing required column: {date_col}")
result = df.copy() if copy else df
name = period_col or f"{date_col}_{freq.lower()}"
result[name] = pd.to_datetime(result[date_col]).dt.to_period(freq)
return result
def period_label(period, *, fmt: str | None = None) -> str:
"""Format a pandas Period or date-like value as a string label."""
if isinstance(period, pd.Period):
return str(period)
value = pd.to_datetime(period)
return value.strftime(fmt) if fmt else str(value.date())
def months_between(start, end) -> int:
"""Number of whole month boundaries between two date-like values."""
s = pd.to_datetime(start)
e = pd.to_datetime(end)
return (e.year - s.year) * 12 + (e.month - s.month)
def add_duration_column(
df: pd.DataFrame,
start_col: str,
date_col: str,
duration_col: str = "duration_month",
*,
one_based: bool = True,
copy: bool = True,
) -> pd.DataFrame:
"""Add elapsed duration in months between a start date and an observation date."""
for col in [start_col, date_col]:
if col not in df.columns:
raise ValueError(f"Missing required column: {col}")
result = df.copy() if copy else df
start = pd.to_datetime(result[start_col])
date = pd.to_datetime(result[date_col])
duration = (date.dt.year - start.dt.year) * 12 + (date.dt.month - start.dt.month)
if one_based:
duration = duration + 1
result[duration_col] = duration
return result