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
2 changes: 1 addition & 1 deletion chainladder/adjustments/bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ def fit(self, X, y=None, sample_weight=None):
xp = X.get_array_module()
if len(X) != 1:
raise ValueError("Only single index triangles are supported")
if type(X.ddims) is not np.ndarray:
if type(X._ddims) is not np.ndarray:
raise ValueError("Triangle must be expressed with development lags")
obj = Development(
n_periods=self.n_periods,
Expand Down
6 changes: 3 additions & 3 deletions chainladder/core/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -692,12 +692,12 @@ def _auto_sparse(self) -> Triangle:

@property
def valuation(self):
ddims = self.ddims
ddims = self._ddims
if self.is_val_tri:
out = pd.DataFrame(np.repeat(self.ddims.values[None], len(self.odims), 0))
out = pd.DataFrame(np.repeat(self._ddims.values[None], len(self._odims), 0))
return pd.DatetimeIndex(out.unstack().values)
ddim_arr = ddims - ddims[0]
origin = np.minimum(self.odims, np.datetime64(self.valuation_date))
origin = np.minimum(self._odims, np.datetime64(self.valuation_date))
val_array = origin.astype("datetime64[M]") + np.timedelta64(ddims[0], "M")
val_array = val_array.astype(__dt64_dtype__) - np.timedelta64(1, __dt64_unit__)
val_array = val_array[:, None]
Expand Down
7 changes: 4 additions & 3 deletions chainladder/core/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,11 @@ def _get_full_triangle(X, ultimate, is_cumulative=True):
emergence.values = emergence.values.cumprod(axis=3) - 1

# Shifting the CDFs by development age, and renaming the last column as 9999
emergence.ddims = (
emergence.ddims + {"Y": 12, "Q": 3, "S": 6, "M": 1}[emergence.development_grain]
emergence._ddims = (
emergence._ddims
+ {"Y": 12, "Q": 3, "S": 6, "M": 1}[emergence.development_grain]
)
emergence.ddims[-1] = 9999
emergence._ddims[-1] = 9999
emergence.values = emergence.values / num_to_nan(emergence.values[..., -1:])
ld = X.incr_to_cum().latest_diagonal
cum_run_off = ld + emergence * (ultimate - ld)
Expand Down
4 changes: 2 additions & 2 deletions chainladder/core/correlation.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ def __init__(self, triangle, p_critical: float = 0.5):

# remove last column because it was not part of the comparison with m2
numerator.values = numerator.values[..., :-1]
numerator.ddims = numerator.ddims[:-1]
numerator._ddims = numerator._ddims[:-1]

# n_dev_periods is the number of development periods in the triangle ("I" in the Mack 97).
n_dev_periods = len(triangle.development)
Expand Down Expand Up @@ -333,7 +333,7 @@ def p_z_lower(z: int, n: int, p: float = 0.5) -> float:
z_critical = triangle.dev_to_val().sum("origin")
z_critical = z_critical[z_critical.valuation > triangle.valuation.min()] * 0
z_critical.values = np.array(self.probs) < p_critical
z_critical.odims = triangle.odims[0:1]
z_critical._odims = triangle._odims[0:1]
self.z_critical = z_critical
self.z = self.z_critical.copy()
self.z.values = z
Expand Down
38 changes: 19 additions & 19 deletions chainladder/core/dunders.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,36 +165,36 @@ def _prep_origin_development(self, obj, other):
(m == n) or (m == 1) or (n == 1)
for m, n in zip(obj.shape[-2:][::-1], other.shape[-2:][::-1])
)
if (len(obj.odims), len(obj.ddims)) == (len(other.odims), len(other.ddims)):
if np.all(obj.ddims != other.ddims) and len(obj.ddims) > 1:
if (len(obj._odims), len(obj._ddims)) == (len(other._odims), len(other._ddims)):
if np.all(obj._ddims != other._ddims) and len(obj._ddims) > 1:
is_broadcastable = False
if np.all(obj.odims != other.odims) and len(obj.odims) > 1:
if np.all(obj._odims != other._odims) and len(obj._odims) > 1:
is_broadcastable = False
if len(other.odims) == 1 and len(obj.odims) > 1:
other.odims = obj.odims
if len(other._odims) == 1 and len(obj._odims) > 1:
other._odims = obj._odims
other.origin_grain = obj.origin_grain
elif len(obj.odims) == 1 and len(other.odims) > 1:
obj.odims = other.odims
elif len(obj._odims) == 1 and len(other._odims) > 1:
obj._odims = other._odims
obj.origin_grain = other.origin_grain
if len(other.ddims) == 1 and len(obj.ddims) > 1:
other.ddims = obj.ddims
if len(other._ddims) == 1 and len(obj._ddims) > 1:
other._ddims = obj._ddims
other.development_grain = obj.development_grain
elif len(obj.ddims) == 1 and len(other.ddims) > 1:
obj.ddims = other.ddims
elif len(obj._ddims) == 1 and len(other._ddims) > 1:
obj._ddims = other._ddims
obj.development_grain = other.development_grain
if not is_broadcastable:
# If broadcasting doesn't work, union axes similar to pandas
ddims = pd.concat(
(
pd.Series(obj.ddims, index=obj.ddims),
pd.Series(other.ddims, index=other.ddims),
pd.Series(obj._ddims, index=obj._ddims),
pd.Series(other._ddims, index=other._ddims),
),
axis=1,
).sort_index()
odims = pd.concat(
(
pd.Series(obj.odims, index=obj.odims),
pd.Series(other.odims, index=other.odims),
pd.Series(obj._odims, index=obj._odims),
pd.Series(other._odims, index=other._odims),
),
axis=1,
sort=False,
Expand Down Expand Up @@ -240,11 +240,11 @@ def _prep_origin_development(self, obj, other):
len(ddims),
)
obj_arr.shape = (self.shape[0], self.shape[1], len(odims), len(ddims))
obj.odims = np.array(odims.index)
if isinstance(obj.ddims, pd.DatetimeIndex):
obj.ddims = pd.DatetimeIndex(ddims.index)
obj._odims = np.array(odims.index)
if isinstance(obj._ddims, pd.DatetimeIndex):
obj._ddims = pd.DatetimeIndex(ddims.index)
else:
obj.ddims = np.array(ddims.index)
obj._ddims = np.array(ddims.index)
obj.values = obj_arr
other.values = other_arr
return obj, other
Expand Down
20 changes: 10 additions & 10 deletions chainladder/core/pandas.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,10 @@ def to_frame(
out["columns"] = obj.columns[values.coords[1]]
missing_cols: list = list(set(self.columns) - set(out["columns"]))
if origin_as_datetime:
out["origin"] = obj.odims[values.coords[2]]
out["origin"] = obj._odims[values.coords[2]]
else:
out["origin"] = obj.origin[values.coords[2]]
out["development"] = obj.ddims[values.coords[3]]
out["development"] = obj._ddims[values.coords[3]]
out["values"] = values.data
out: DataFrame = pd.pivot_table(
out, index=obj.key_labels + ["origin", "development"], columns="columns"
Expand All @@ -121,8 +121,8 @@ def to_frame(

valuation_series = pd.DataFrame(
obj.valuation.values.reshape(obj.shape[-2:], order="F"),
index=obj.odims if origin_as_datetime else obj.origin,
columns=obj.ddims,
index=obj._odims if origin_as_datetime else obj.origin,
columns=obj._ddims,
).unstack()
valuation_series.name = "valuation"
valuation: DataFrame = valuation_series.reset_index().rename(
Expand Down Expand Up @@ -960,7 +960,7 @@ def drop(
keep = self._validate_contiguous_drop(
result.development, ax_labels, "development", errors
)
result = result._slice(keep, "ddims")
result = result._slice(keep, "_ddims")
if result.is_val_tri:
result.valuation_date = min(
result.valuation.max(), result.valuation_date
Expand Down Expand Up @@ -1313,11 +1313,11 @@ def agg_func(
)
if axis == 1 and obj.values.shape[axis] == 1 and len(obj.columns) > 1:
obj.columns = pd.Index([0], name="columns")
if axis == 2 and obj.values.shape[axis] == 1 and len(obj.odims) > 1:
obj.odims = obj.odims[0:1]
if axis == 2 and obj.values.shape[axis] == 1 and len(obj._odims) > 1:
obj._odims = obj._odims[0:1]
# If axis is development, set the ddims to be the valuation date.
if axis == 3 and obj.values.shape[axis] == 1 and len(obj.ddims) > 1:
obj.ddims = pd.DatetimeIndex(
if axis == 3 and obj.values.shape[axis] == 1 and len(obj._ddims) > 1:
obj._ddims = pd.DatetimeIndex(
[self.valuation_date], dtype=__dt64_dtype__, freq=None
)
obj._set_slicers()
Expand Down Expand Up @@ -1385,7 +1385,7 @@ def aggregate(i, obj, axis, v):
obj.origin_grain = self.obj._get_grain(odims)
split = obj.origin_grain.split("-")
obj.origin_grain = {"2Q": "S"}.get(split[0], split[0])
obj.odims = odims.values
obj._odims = odims.values
obj._set_slicers()
if auto_sparse:
obj = obj._auto_sparse()
Expand Down
15 changes: 8 additions & 7 deletions chainladder/core/slice.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ def get_idx(self, idx: tuple[_AxisKey, _AxisKey, _AxisKey, _AxisKey]) -> Triangl
# Set the new dimension values.
obj.index = obj.index.iloc[i_idx].reset_index(drop=True)
obj.columns = obj.columns[c_idx]
obj.odims, obj.ddims = obj.odims[o_idx], obj.ddims[d_idx]
obj._odims, obj._ddims = obj._odims[o_idx], obj._ddims[d_idx]
obj.valuation_date = cast(
pd.Timestamp, np.minimum(obj.valuation.max(), obj.valuation_date)
)
Expand Down Expand Up @@ -788,14 +788,14 @@ def __getitem__(

# Case development axis.
if isinstance(key, pd.Series) and key.name == "development":
return self._slice(key, "ddims")
return self._slice(key, "_ddims")
# Case ndarray, could be valuation or origin.
if isinstance(key, np.ndarray):
# Case valuation, inferred by size of ndarray obtained by filtering on valuation date.
if len(key) == np.prod(self.shape[-2:]) and self.shape[-1] > 1:
return self._slice_valuation(key)
# Otherwise, assume case origin.
return self._slice(key, "odims")
return self._slice(key, "_odims")
# Case index.
if isinstance(key, pd.Series):
if pd.api.types.is_bool_dtype(key):
Expand Down Expand Up @@ -990,7 +990,7 @@ def _slice_valuation(self: TriangleProtocol, key: np.ndarray) -> Triangle:
def _slice(
self: TriangleProtocol,
key: pd.Series | np.ndarray,
axis: Literal["ddims", "odims"],
axis: Literal["ddims", "odims", "_ddims", "_odims"],
) -> Triangle:
"""
Private method for handling of origin/development slicing.
Expand All @@ -999,16 +999,17 @@ def _slice(
----------
key: pd.Series | np.ndarray
Array specifying the slice to extract.
axis: Literal['ddims', 'odims']
axis: Literal['ddims', 'odims', '_ddims', '_odims']
The axis to which the slice would apply, `ddims` for development, `odims` for origin.
"""

# Update the axis, then the values.
obj = self.copy()
setattr(obj, axis, getattr(obj, axis)[key])
attr = f"_{axis}" if not axis.startswith("_") else axis
setattr(obj, attr, getattr(obj, attr)[key])
# noinspection PyProtectedMember
slicer = ..., _LocBase._contig_slice(np.arange(len(key))[key])
if axis == "odims":
if axis in ("odims", "_odims"):
slicer = tuple(list(slicer) + [slice(None)])
obj.values = obj.values[slicer]
return obj
Expand Down
2 changes: 1 addition & 1 deletion chainladder/core/tests/test_arithmetic.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ def test_arithmetic_union_val_tri(raa: Triangle) -> None:
a = val_raa[val_raa.valuation < "1987"]
b = val_raa[val_raa.valuation >= "1987"]
result = a + b
assert isinstance(result.ddims, pd.DatetimeIndex)
assert isinstance(result._ddims, pd.DatetimeIndex)
assert result.shape == val_raa.shape
assert result == val_raa

Expand Down
6 changes: 3 additions & 3 deletions chainladder/core/tests/test_grain.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,12 +98,12 @@ def test_annual_trailing(prism):
# (limit data to November)
tri = tri[tri.valuation < tri.valuation_date].incr_to_cum()
tri = tri.grain("OQDQ", trailing=True).grain("OYDY")
assert np.all(tri.ddims[:4] == np.array([12, 24, 36, 48]))
assert np.all(tri.development[:4] == np.array([12, 24, 36, 48]))


def test_development_age(raa: Triangle) -> None:
assert (raa.ddims == list(range(12, 121, 12))).all()
assert (raa.development == list(range(12, 121, 12))).all()


def test_development_age_quarterly(qtr: Triangle) -> None:
assert (qtr.ddims == list(range(3, 136, 3))).all()
assert (qtr.development == list(range(3, 136, 3))).all()
34 changes: 31 additions & 3 deletions chainladder/core/tests/test_triangle.py
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ def test_origin_and_value_setters(raa):
assert np.all((
np.all(raa2.origin == raa.origin),
np.all(raa2.development == raa.development),
np.all(raa2.odims == raa.odims),
np.all(raa2._odims == raa._odims),
np.all(raa2.columns == raa.columns),
))

Expand Down Expand Up @@ -667,17 +667,43 @@ def test_series_indexing(raa):
assert sliced.index.iloc[0]["Company"] == "A"


def test_odims_deprecation_warning(raa):
"""Accessing or setting odims should emit a FutureWarning."""
with pytest.warns(FutureWarning, match="'odims' attribute is deprecated"):
o = raa.odims
assert np.all(o == raa._odims)

raa2 = raa.copy()
with pytest.warns(FutureWarning, match="'odims' attribute is deprecated"):
raa2.odims = raa._odims
assert np.all(raa2._odims == raa._odims)


def test_ddims_deprecation_warning(raa):
"""Accessing or setting ddims should emit a FutureWarning."""
with pytest.warns(FutureWarning, match="'ddims' attribute is deprecated"):
d = raa.ddims
assert np.all(d == raa._ddims)

raa2 = raa.copy()
with pytest.warns(FutureWarning, match="'ddims' attribute is deprecated"):
raa2.ddims = raa._ddims
assert np.all(raa2._ddims == raa._ddims)


def test_legacy_pickle_compatibility(raa):
"""Pickles saved prior to kdims/vdims migration should unpickle cleanly with warning."""
"""Pickles saved prior to kdims/vdims/odims/ddims migration should unpickle cleanly with warning."""
import pickle

# 1. Simulate oldest serialized Triangle containing kdims and vdims
# 1. Simulate oldest serialized Triangle containing kdims, vdims, odims, and ddims
state = raa.__dict__.copy()
state.pop("_axes", None)
state.pop("_index", None)
state.pop("_columns", None)
state["kdims"] = raa.index.values
state["vdims"] = raa.columns.values
state["odims"] = state.pop("_odims")
state["ddims"] = state.pop("_ddims")

restored = cl.Triangle.__new__(cl.Triangle)
with pytest.warns(
Expand All @@ -689,6 +715,8 @@ def test_legacy_pickle_compatibility(raa):
assert isinstance(restored.index, pd.DataFrame)
assert restored.index.equals(raa.index)
assert list(restored.columns) == list(raa.columns)
assert restored.origin.equals(raa.origin)
assert (restored.development == raa.development).all()
assert restored.loc["Total"].shape == raa.loc["Total"].shape
assert restored == raa
assert 0 in restored._axes
Expand Down
Loading
Loading