Skip to content

Commit

Permalink
Merge pull request #440 from casact/#438-bug
Browse files Browse the repository at this point in the history
#438 bug
  • Loading branch information
jbogaardt authored Jun 17, 2023
2 parents d365b07 + cba398a commit 5510c94
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 44 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -122,3 +122,6 @@ settings.json
.asv

coverage_html_report

# Random python test workbooks
Untitled.ipynb
46 changes: 34 additions & 12 deletions chainladder/core/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,10 +253,32 @@ def _to_datetime(data, fields, period_end=False, format=None):
return target

@staticmethod
def _development_lag(origin, development):
def _development_lag(origin, valuation):
"""For tabular format, this will convert the origin/development
difference to a development lag"""
return ((development - origin) / np.timedelta64(1, "M")).round(0).astype(int)

temp = pd.DataFrame()
temp["valuation"] = (valuation + pd.DateOffset(1)).dt.strftime("%m/%d/%Y")
temp["origin"] = origin.dt.strftime("%m/%d/%Y")

temp["age_old"] = (
((valuation - origin) / np.timedelta64(1, "M")).round(0).astype(int)
)

temp["age_temp_div"] = (
(valuation.dt.year * 12 + valuation.dt.month)
- (origin.dt.year * 12 + origin.dt.month)
+ 1
)

# print(temp.tail(10))

# return ((valuation - origin) / np.timedelta64(1, "Y")).round(0).astype(int) * 12
return (
(valuation.dt.year * 12 + valuation.dt.month)
- (origin.dt.year * 12 + origin.dt.month)
+ 1
)

@staticmethod
def _get_grain(dates, trailing=False, kind="origin"):
Expand Down Expand Up @@ -285,8 +307,12 @@ def _get_grain(dates, trailing=False, kind="origin"):
if trailing and grain != "M":
if kind == "origin":
end = (dates.min() - pd.DateOffset(days=1)).strftime("%b").upper()
end = 'DEC' if end in ['MAR', 'JUN', 'SEP', 'DEC'] and grain == 'Q' else end
end = 'DEC' if end in ['JUN', 'DEC'] and grain == '2Q' else end
end = (
"DEC"
if end in ["MAR", "JUN", "SEP", "DEC"] and grain == "Q"
else end
)
end = "DEC" if end in ["JUN", "DEC"] and grain == "2Q" else end
else:
# If inferred to beginning of calendar period, 1/1 from YYYY, 4/1 from YYYYQQ
if (
Expand Down Expand Up @@ -428,16 +454,12 @@ def compute(self, *args, **kwargs):
obj.array_backend = "numpy"
return obj
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 {0: self.index, 1: self.columns, 2: self.origin, 3: self.development}[
axis
]


def is_chainladder(estimator):
Expand Down
122 changes: 93 additions & 29 deletions chainladder/core/tests/test_grain.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,22 @@ def test_grain(qtr):
actual = qtr.iloc[0, 0].grain("OYDY")
xp = actual.get_array_module()
nan = xp.nan
expected = xp.array([
[44, 621, 950, 1020, 1070, 1069, 1089, 1094, 1097, 1099, 1100, 1100],
[42, 541, 1052, 1169, 1238, 1249, 1266, 1269, 1296, 1300, 1300, nan],
[17, 530, 966, 1064, 1100, 1128, 1155, 1196, 1201, 1200, nan, nan],
[10, 393, 935, 1062, 1126, 1209, 1243, 1286, 1298, nan, nan, nan],
[13, 481, 1021, 1267, 1400, 1476, 1550, 1583, nan, nan, nan, nan],
[2, 380, 788, 953, 1001, 1030, 1066, nan, nan, nan, nan, nan],
[4, 777, 1063, 1307, 1362, 1411, nan, nan, nan, nan, nan, nan],
[2, 472, 1617, 1818, 1820, nan, nan, nan, nan, nan, nan, nan],
[3, 597, 1092, 1221, nan, nan, nan, nan, nan, nan, nan, nan],
[4, 583, 1212, nan, nan, nan, nan, nan, nan, nan, nan, nan],
[21, 422, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan],
[13, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan]])
expected = xp.array(
[
[44, 621, 950, 1020, 1070, 1069, 1089, 1094, 1097, 1099, 1100, 1100],
[42, 541, 1052, 1169, 1238, 1249, 1266, 1269, 1296, 1300, 1300, nan],
[17, 530, 966, 1064, 1100, 1128, 1155, 1196, 1201, 1200, nan, nan],
[10, 393, 935, 1062, 1126, 1209, 1243, 1286, 1298, nan, nan, nan],
[13, 481, 1021, 1267, 1400, 1476, 1550, 1583, nan, nan, nan, nan],
[2, 380, 788, 953, 1001, 1030, 1066, nan, nan, nan, nan, nan],
[4, 777, 1063, 1307, 1362, 1411, nan, nan, nan, nan, nan, nan],
[2, 472, 1617, 1818, 1820, nan, nan, nan, nan, nan, nan, nan],
[3, 597, 1092, 1221, nan, nan, nan, nan, nan, nan, nan, nan],
[4, 583, 1212, nan, nan, nan, nan, nan, nan, nan, nan, nan],
[21, 422, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan],
[13, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan],
]
)
xp.testing.assert_array_equal(actual.values[0, 0, :, :], expected)


Expand Down Expand Up @@ -54,41 +57,102 @@ def test_commutative(qtr, atol):
assert abs(a - b).max().max().max() < atol


@pytest.mark.parametrize('grain',
['OYDY', 'OYDQ', 'OYDM', 'OSDS', 'OSDQ', 'OSDM', 'OQDQ', 'OQDM'])
@pytest.mark.parametrize('alt', [0, 1, 2])
@pytest.mark.parametrize('trailing', [False, True])
@pytest.mark.parametrize(
"grain", ["OYDY", "OYDQ", "OYDM", "OSDS", "OSDQ", "OSDM", "OQDQ", "OQDM"]
)
@pytest.mark.parametrize("alt", [0, 1, 2])
@pytest.mark.parametrize("trailing", [False, True])
def test_different_forms_of_grain(prism_dense, grain, trailing, alt, atol):
t = prism_dense["Paid"]
if alt == 1:
t = t.dev_to_val().copy()
if alt == 2:
t = t.val_to_dev().copy()
t = t[t.valuation < "2017-09"]
a = t.grain(grain, trailing=trailing)
b = t.incr_to_cum().grain(grain, trailing=trailing).cum_to_incr()
assert abs(a-b).sum().sum() < atol

a = t.incr_to_cum().grain(grain, trailing=trailing)
b = t.grain(grain, trailing=trailing).incr_to_cum()
assert abs(a-b).sum().sum() < atol
assert abs(a - b).sum().sum() < atol


def test_asymmetric_origin_grain(prism_dense):
x = prism_dense.iloc[..., 8:, :].incr_to_cum()
x = x[x.valuation<x.valuation_date]
assert x.grain('OYDM').development[0] == 1
x = x[x.valuation < x.valuation_date]
assert x.grain("OYDM").development[0] == 1

x = x[x.valuation < x.valuation_date]
assert x.grain("OYDM").development[0] == 1


def test_vector_triangle_grain_mismatch(prism):
tri = prism['Paid'].sum().incr_to_cum().grain('OQDM')
tri = prism["Paid"].sum().incr_to_cum().grain("OQDM")
exposure = tri.latest_diagonal
tri = tri.grain('OQDQ')
assert (tri / exposure).development_grain == 'Q'
tri = tri.grain("OQDQ")
assert (tri / exposure).development_grain == "Q"


def test_annual_trailing(prism):
tri = prism['Paid'].sum().incr_to_cum()
tri = prism["Paid"].sum().incr_to_cum()
# (limit data to November)
tri = tri[tri.valuation<tri.valuation_date].incr_to_cum()
tri = tri.grain('OQDQ', trailing=True).grain('OYDY')
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]))


def test_development_age():
assert (
cl.load_sample("raa").ddims == [12, 24, 36, 48, 60, 72, 84, 96, 108, 120]
).all()


def test_development_age_quarterly():
assert (
cl.load_sample("quarterly").ddims
== [
3,
6,
9,
12,
15,
18,
21,
24,
27,
30,
33,
36,
39,
42,
45,
48,
51,
54,
57,
60,
63,
66,
69,
72,
75,
78,
81,
84,
87,
90,
93,
96,
99,
102,
105,
108,
111,
114,
117,
120,
123,
126,
129,
132,
135,
]
).all()
8 changes: 5 additions & 3 deletions chainladder/core/triangle.py
Original file line number Diff line number Diff line change
Expand Up @@ -425,11 +425,13 @@ def incr_to_cum(self, inplace=False):
xp = self.get_array_module()
if not self.is_cumulative:
if self.is_pattern:
if hasattr(self,"is_additive"):
if hasattr(self, "is_additive"):
if self.is_additive:
values = xp.nan_to_num(self.values[...,::-1])
values = xp.nan_to_num(self.values[..., ::-1])
values = num_to_value(values, 0)
self.values = xp.cumsum(values,-1)[...,::-1] * self.nan_triangle
self.values = (
xp.cumsum(values, -1)[..., ::-1] * self.nan_triangle
)
else:
values = xp.nan_to_num(self.values[..., ::-1])
values = num_to_value(values, 1)
Expand Down

0 comments on commit 5510c94

Please sign in to comment.