Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions chainladder/core/axis.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from __future__ import annotations

from typing import TYPE_CHECKING, Any, Callable
import numpy as np
import pandas as pd

from chainladder.utils.utility_functions import _get_axis_name, _get_axis_number
Expand Down Expand Up @@ -162,3 +163,84 @@ def _set_index(obj: Triangle, value: Any) -> pd.DataFrame:
elif hasattr(obj, "_axes") and (0 in obj._axes or "index" in obj._axes):
obj._len_check(obj.index, value)
return value.copy().reset_index(drop=True)


def _get_origin(obj: Triangle, raw: Any) -> pd.PeriodIndex | pd.Series:
"""
Transform raw origin timestamp array into public PeriodIndex or Series.
"""
if obj.is_pattern and len(raw) == 1:
return pd.Series(["(All)"])
freq = {
"S": "2Q",
"H": "2Q",
}.get(obj.origin_grain, obj.origin_grain)
freq = freq if freq == "M" else freq + "-" + obj.origin_close
return pd.DatetimeIndex(raw, name="origin").to_period(freq=freq)


def _set_origin(obj: Triangle, value: Any) -> np.ndarray:
"""
Validate and transform assigned origin periods into internal timestamps array.
"""
if hasattr(obj, "values") and obj.values is not None:
obj._len_check(range(obj.values.shape[2]), value)
elif hasattr(obj, "_axes") and (2 in obj._axes or "origin" in obj._axes):
obj._len_check(obj.origin, value)
freq = {
"S": "2Q",
}.get(obj.origin_grain, obj.origin_grain)
freq = freq if freq == "M" else freq + "-" + obj.origin_close
value = pd.PeriodIndex(list(value), freq=freq)
return value.to_timestamp().values


def _get_development(obj: Triangle, raw: Any) -> pd.Series:
"""
Transform raw development lags/timestamps into public Series.
"""
ddims = raw.copy()
if obj.is_val_tri:
formats = {"Y": "%Y", "S": "%YQ%q", "Q": "%YQ%q", "M": "%Y-%m"}
ddims = ddims.to_period(freq=obj.development_grain.replace("S", "2Q")).strftime(
formats[obj.development_grain]
)
elif obj.is_pattern:
offset = obj._dstep()["M"][obj.development_grain]
if obj.is_ultimate:
ddims[-1] = ddims[-2] + offset
if obj.is_cumulative:
ddims = ["{}-Ult".format(ddims[i]) for i in range(len(ddims))]
else:
ddims = [
"{}-{}".format(ddims[i], ddims[i] + offset) for i in range(len(ddims))
]
return pd.Series(list(ddims), name="development")


def _set_development(obj: Triangle, value: Any) -> np.ndarray:
"""
Validate and transform assigned development periods into internal lag/timestamp array.
"""
if hasattr(obj, "values") and obj.values is not None:
obj._len_check(range(obj.values.shape[3]), value)
elif hasattr(obj, "_axes") and (3 in obj._axes or "development" in obj._axes):
obj._len_check(obj.development, value)
return np.array([value] if isinstance(value, str) else value)


def _set_axis(obj: Triangle, axis: int | str, value: Any) -> None:
"""
Set values on the specified axis of a Triangle instance.

Parameters
----------
obj : Triangle
The Triangle instance to update.
axis : int or str
Axis identifier (0..3 or 'index', 'columns', 'origin', 'development').
value : Any
New axis values.
"""
axis_name = _get_axis_name(axis)
setattr(obj, axis_name, value)
6 changes: 2 additions & 4 deletions chainladder/core/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from chainladder.utils.cupy import cp
from chainladder.utils.dask import dp
from chainladder.utils.sparse import sp
from chainladder.utils.utility_functions import _get_axis_name

from chainladder.adjustments.disposal import DisposalMixin

Expand Down Expand Up @@ -821,10 +822,7 @@ def compute(self, *args, **kwargs):
return self

def _get_axis_value(self, axis):
axis = self._get_axis(axis)
return {0: self.index, 1: self.columns, 2: self.origin, 3: self.development}[
axis
]
return getattr(self, _get_axis_name(axis))


def is_chainladder(estimator):
Expand Down
41 changes: 21 additions & 20 deletions chainladder/core/pandas.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
_deprecated_drop_argument,
_warn_dask_parallel_deprecated,
)
from chainladder.utils.utility_functions import concat, num_to_nan
from chainladder.utils.utility_functions import _get_axis_number, concat, num_to_nan

from typing import cast, TYPE_CHECKING

Expand Down Expand Up @@ -239,7 +239,7 @@ def hvplot(self, *args: Any, **kwargs: Any) -> Any:
return df.hvplot(*args, **kwargs)

@staticmethod
def _get_axis(
def _get_axis_number(
axis: Literal["index", "columns", "origin", "development"] | int | None,
) -> int:
"""
Expand All @@ -250,30 +250,31 @@ def _get_axis(
axis: Literal['index', 'columns', 'origin', 'development'] | int | None
String or integer representation of the requested axis. If
supplied as a string, returns the integer representation. If
supplied as an integer, returns the same integer.
supplied as an integer, returns the canonical positive integer.

Returns
-------
int
The integer representation of the requested axis
The integer representation of the requested axis (0..3).
"""

ax = {
**{0: 0, 1: 1, 2: 2, 3: 3},
**{-1: 3, -2: 2, -3: 1, -4: 0},
**{"index": 0, "columns": 1, "origin": 2, "development": 3},
}

if axis is None:
return 0
try:
return ax[axis]
except KeyError:
if axis is None:
return 0
else:
raise ValueError(
"Invalid axis specified. Please specify the correct string or "
"integer representation of the desired axis."
)
return _get_axis_number(axis)
except ValueError as err:
raise ValueError(
"Invalid axis specified. Please specify the correct string or "
"integer representation of the desired axis."
) from err

@staticmethod
def _get_axis(
axis: Literal["index", "columns", "origin", "development"] | int | None,
) -> int:
"""
Backward-compatible alias for _get_axis_number.
"""
return TrianglePandas._get_axis_number(axis)

def dropna(self) -> Triangle:
"""
Expand Down
Loading
Loading