diff --git a/chainladder/core/tests/test_triangle.py b/chainladder/core/tests/test_triangle.py index 5563296a..4c9163d0 100644 --- a/chainladder/core/tests/test_triangle.py +++ b/chainladder/core/tests/test_triangle.py @@ -47,6 +47,79 @@ def test_link_ratio(raa, atol): raa.link_ratio * raa.iloc[:, :, :-1, :-1].values - raa.values[:, :, :-1, 1:] ).sum().sum() < atol + +def test_link_ratio_sets_pattern_metadata(raa: Triangle) -> None: + """ + When called on a Triangle that is not already a pattern, link_ratio should + return a new object with is_pattern=True, is_cumulative=False, and the + length of the origin and development axis reduced by 1. + + Parameters + ---------- + raa : Triangle + The RAA sample dataset Triangle. + + Returns + ------- + None + """ + assert not raa.is_pattern + assert not raa.is_full + + lr = raa.link_ratio + + assert lr.is_pattern + assert not lr.is_cumulative + # Both the origin and development axes shrink by one: development because + # ratios need adjacent pairs of columns, and origin since the last period only has 1 value. + assert lr.shape == (raa.shape[0], raa.shape[1], raa.shape[2] - 1, raa.shape[3] - 1) + + +def test_link_ratio_converts_zero_ratios_to_nan() -> None: + """ + obj.values = num_to_nan(obj.values) in link_ratio should turn any literal + zero age-to-age ratio into NaN rather than leaving it as 0. This is an intentional + design choice to improve memory efficiency - see GH#181: + + https://github.com/casact/chainladder-python/issues/181 + + Returns + ------- + None + """ + raa = cl.load_sample("raa").set_backend("numpy") + # Force a literal zero into the next-development cell so the ratio itself + # computes to exactly 0.0, distinct from the ordinary NaNs already in raa. + raa.values[0, 0, 0, 1] = 0.0 + + lr = raa.link_ratio.set_backend("numpy") + + assert np.isnan(lr.values[0, 0, 0, 0]) + + +def test_link_ratio_on_pattern_returns_self(raa: Triangle) -> None: + """ + When a Triangle already carries is_pattern=True (it is already a set of + link ratios / development patterns), link_ratio should short-circuit and + return the exact same object rather than recomputing ratios from it. + + Parameters + ---------- + raa : Triangle + The RAA sample dataset Triangle. + + Returns + ------- + None + """ + lr = raa.link_ratio + assert lr.is_pattern + + result = lr.link_ratio + + assert result is lr + + def test_align_pattern(raa, atol): with pytest.raises(ValueError): raa.align_pattern(raa) @@ -112,11 +185,11 @@ def test_rename_columns(genins, clrd) -> None: # Test the cascading of rename to triangle.columns_label. assert genins.columns_label == ['foo'] - genins.rename('columns',{'foo':'newfoo'}) + genins.rename('columns', {'foo': 'newfoo'}) assert genins.columns.to_list() == ['newfoo'] - genins.rename('columns',{'foo':'newnewfoo'}) + genins.rename('columns', {'foo': 'newnewfoo'}) assert genins.columns.to_list() == ['newfoo'] @@ -125,14 +198,14 @@ def test_rename_index() -> None: Test the renaming of triangle columns. """ auto = cl.load_sample('auto') - new_index = ['CommAuto','PersAuto'] - auto.rename('index',new_index) + new_index = ['CommAuto', 'PersAuto'] + auto.rename('index', new_index) assert np.all(auto.index.values.flatten() == new_index) def test_rename_exception(genins, clrd) -> None: # Test incorrect value argument - misspelling of string. with pytest.raises(ValueError): - genins.rename('origin', {'oldfoo':'foo'}) + genins.rename('origin', {'oldfoo': 'foo'}) # Test incorrect axis argument - misspelling of string. with pytest.raises(ValueError): @@ -171,13 +244,35 @@ def test_trend(raa, atol): assert abs((raa.trend(0.05).trend((1 / 1.05) - 1) - raa).sum().sum()) < 1e-5 +def test_trend_invalid_axis_raises(raa: Triangle) -> None: + """ + trend() only supports trending along the origin or valuation axes + (accepting either the string names or their positional equivalents, 2 and + -2). Any other axis value should raise ValueError rather than silently + doing something unexpected. + + Parameters + ---------- + raa : Triangle + The RAA sample dataset Triangle. + + Returns + ------- + None + """ + with pytest.raises( + ValueError, match="Only origin and valuation axes are supported for trending" + ): + raa.trend(0.05, axis="development") + + def test_valuation_shift(qtr): x = qtr.iloc[0, 0] assert x[x.valuation <= x.valuation_date] == x def test_quantile_vs_median(clrd): - xp = clrd.get_array_module() + clrd.get_array_module() assert clrd.quantile(q=0.5)["CumPaidLoss"] == clrd.median()["CumPaidLoss"] @@ -203,7 +298,7 @@ def test_development_before_origin_warns_and_drops() -> None: df = pd.DataFrame({ "origin": [2000, 2000, 2001, 2001], "development": [2001, 2002, 2000, 2002], # 2001/2000 row is invalid - "value": [100, 200, 999, 300], + "value": [100, 200, 999, 300], }) with pytest.warns(UserWarning, match="development before"): tri = cl.Triangle( @@ -228,10 +323,152 @@ def test_origin_and_value_setters(raa): ) +def test_index_setter_with_dataframe(clrd: Triangle) -> None: + """ + Assigning a pandas DataFrame to Triangle.index should replace kdims with + the DataFrame's values, replace key_labels with its columns, and rebuild + the slicers so that .loc/.iloc reflect the new labels. + + Parameters + ---------- + clrd : Triangle + The clrd sample dataset Triangle. + + Returns + ------- + None + """ + tri = clrd.iloc[:3] + new_index = pd.DataFrame({"Company": ["A", "B", "C"]}) + + tri.index = new_index + + assert tri.key_labels == ["Company"] + np.testing.assert_array_equal(tri.kdims, new_index.values) + # _set_slicers() must have rebuilt .loc against the new key label. + assert tri.loc["A"].kdims.tolist() == [["A"]] + assert tri.loc["A"] == clrd.iloc[:1] + + +def test_index_setter_length_mismatch_raises(clrd: Triangle) -> None: + """ + Attempt to reassign index with a DataFrame of incorrect row count. Raise an error. + + Parameters + ---------- + clrd : Triangle + The clrd sample dataset Triangle. + + Returns + ------- + None + """ + tri = clrd.iloc[:3] + mismatched_index = pd.DataFrame({"Company": ["A", "B"]}) + + with pytest.raises(ValueError): + tri.index = mismatched_index + + +def test_index_setter_non_dataframe_raises(clrd: Triangle) -> None: + """ + Triangle.index only accepts a pandas DataFrame. Assigning any other type + (e.g. a list) should raise a TypeError rather than being coerced. + + Parameters + ---------- + clrd : Triangle + The clrd sample dataset Triangle. + + Returns + ------- + None + """ + tri = clrd.iloc[:3] + + with pytest.raises(TypeError, match="index must be a pandas DataFrame"): + tri.index = ["A", "B", "C"] + + +def test_set_index_inplace(clrd: Triangle) -> None: + """ + Triangle.set_index(value, inplace=True) should mutate the calling + Triangle's index via the index setter and return that same object. + + Parameters + ---------- + clrd : Triangle + The clrd sample dataset Triangle. + + Returns + ------- + None + """ + tri = clrd.iloc[:3] + new_index = pd.DataFrame({"Company": ["A", "B", "C"]}) + + result = tri.set_index(new_index, inplace=True) + + assert result is tri + assert tri.key_labels == ["Company"] + np.testing.assert_array_equal(tri.kdims, new_index.values) + + +def test_set_index_not_inplace(clrd: Triangle) -> None: + """ + Triangle.set_index(value) with the default inplace=False should operate + on a copy: it returns a distinct Triangle with the new index applied, + leaving the original Triangle's kdims/key_labels untouched. + + Parameters + ---------- + clrd : Triangle + The clrd sample dataset Triangle. + + Returns + ------- + None + """ + tri = clrd.iloc[:3] + original_kdims = tri.kdims.copy() + original_key_labels = list(tri.key_labels) + new_index = pd.DataFrame({"Company": ["A", "B", "C"]}) + + result = tri.set_index(new_index) + + assert result is not tri + assert result.key_labels == ["Company"] + np.testing.assert_array_equal(result.kdims, new_index.values) + assert tri.key_labels == original_key_labels + np.testing.assert_array_equal(tri.kdims, original_kdims) + + def test_valdev1(qtr): assert qtr.dev_to_val().val_to_dev() == qtr +def test_dev_to_val_inplace_on_val_tri_returns_self(qtr: Triangle) -> None: + """ + Execute dev_to_val() on a triangle that is already a valuation triangle. Should + leave the triangle unchanged. + + Parameters + ---------- + qtr : Triangle + The qtr sample dataset Triangle. + + Returns + ------- + None + """ + val_tri = qtr.dev_to_val() + assert val_tri.is_val_tri + + result = val_tri.dev_to_val(inplace=True) + + assert result is val_tri + + def test_valdev2(qtr): a = qtr.dev_to_val().grain("OYDY").val_to_dev() b = qtr.grain("OYDY") @@ -576,7 +813,7 @@ def test_groupby_agg_auto_sparse(prism: Triangle) -> None: ------- None """ - result_default = prism.groupby("Line").sum() + result_default = prism.groupby("Line").sum() result_no_sparse = prism.groupby("Line").sum(auto_sparse=False) assert result_default.array_backend == "numpy" @@ -609,6 +846,67 @@ def test_auto_sparse_disabled_returns_self(prism: Triangle) -> None: cl.options.reset_option("AUTO_SPARSE") +def test_init_defaults_array_backend_to_option() -> None: + """ + When array_backend is not passed to the constructor (i.e. it is None), + Triangle.__init__ should fall back to cl.options.ARRAY_BACKEND rather than + a hardcoded default. + + Returns + ------- + None + """ + df = pd.DataFrame({ + "origin": [2000, 2000, 2001, 2001], + "development": [2000, 2001, 2001, 2002], + "value": [100, 200, 300, 400], + }) + cl.options.set_option("AUTO_SPARSE", False) + cl.options.set_option("ARRAY_BACKEND", "sparse") + try: + tri = cl.Triangle( + df, + origin="origin", + development="development", + columns="value", + cumulative=True, + ) + assert tri.array_backend == "sparse" + finally: + cl.options.reset_option("AUTO_SPARSE") + cl.options.reset_option("ARRAY_BACKEND") + + +def test_init_calls_set_backend_when_auto_sparse_disabled() -> None: + """ + When cl.options.AUTO_SPARSE is False, Triangle.__init__ should route + through self.set_backend(backend=array_backend) rather than + self._auto_sparse(), landing on exactly the requested backend. + + Returns + ------- + None + """ + df = pd.DataFrame({ + "origin": [2000, 2000, 2001, 2001], + "development": [2000, 2001, 2001, 2002], + "value": [100, 200, 300, 400], + }) + cl.options.set_option("AUTO_SPARSE", False) + try: + tri = cl.Triangle( + df, + origin="origin", + development="development", + columns="value", + cumulative=True, + array_backend="numpy", + ) + assert tri.array_backend == "numpy" + finally: + cl.options.reset_option("AUTO_SPARSE") + + def test_auto_sparse_converts_numpy_to_sparse(prism: Triangle) -> None: """ _auto_sparse() should convert a numpy-backed triangle to sparse when it is @@ -885,10 +1183,10 @@ def test_plot(raa: Triangle) -> None: try: ax_tri = raa.plot() - ax_df = raa.to_frame(origin_as_datetime=False).plot() + ax_df = raa.to_frame(origin_as_datetime=False).plot() lines_tri = ax_tri.get_lines() - lines_df = ax_df.get_lines() + lines_df = ax_df.get_lines() assert len(lines_tri) == len(lines_df) for lt, ld in zip(lines_tri, lines_df): @@ -931,6 +1229,112 @@ def test_sort_axis(clrd): ).sort_axis(3) == clrd.sort_axis(1) +def test_sort_axis_columns_reorders_values() -> None: + """ + sort_axis('columns') on a Triangle with out-of-order columns should not + just relabel the vdims, it should also permute the underlying values so + that each column's data stays matched to its (now reordered) label. + + Returns + ------- + None + """ + df = pd.DataFrame( + data={ + "origin": [2020, 2020, 2021, 2021], + "development": [2020, 2021, 2021, 2021], + "reported": [100, 200, 110, 110], + "paid": [50, 100, 60, 60], + } + ) + tr = cl.Triangle( + data=df, + origin="origin", + development="development", + columns=["reported", "paid"], + cumulative=True, + ) + sorted_tr = tr.sort_axis("columns") + + assert list(tr.columns) == ["reported", "paid"] + assert list(sorted_tr.columns) == ["paid", "reported"] + np.testing.assert_array_equal(sorted_tr["paid"].values, tr["paid"].values) + np.testing.assert_array_equal(sorted_tr["reported"].values, tr["reported"].values) + + # If the triangle is already sorted, leave values unchanged. + already_sorted = sorted_tr.sort_axis("columns") + assert list(already_sorted.columns) == ["paid", "reported"] + np.testing.assert_array_equal(already_sorted.values, sorted_tr.values) + + +def test_sort_axis_origin_reorders_values(raa: Triangle) -> None: + """ + sort_axis('origin') on a Triangle with out-of-order origin periods should + not just relabel the odims, it should also permute the underlying values + so that each origin row's data stays matched to its (now reordered) + label. + + Parameters + ---------- + raa : Triangle + The RAA sample dataset Triangle. + + Returns + ------- + None + """ + reversed_tr = raa.iloc[..., ::-1, :] + + assert list(reversed_tr.origin) != list(raa.origin) + + sorted_tr = reversed_tr.sort_axis("origin") + assert list(sorted_tr.origin) == list(raa.origin) + np.testing.assert_array_equal( + sorted_tr.set_backend("numpy").values, raa.set_backend("numpy").values + ) + + # When sorting an already-sorted triangle, leave values unchanged. + already_sorted = sorted_tr.sort_axis("origin") + assert list(already_sorted.origin) == list(sorted_tr.origin) + np.testing.assert_array_equal( + already_sorted.set_backend("numpy").values, sorted_tr.set_backend("numpy").values + ) + + +def test_sort_axis_development_reorders_values(raa: Triangle) -> None: + """ + sort_axis('development') on a Triangle with out-of-order development + periods should not just relabel the ddims, it should also permute the + underlying values so that each development column's data stays matched + to its (now reordered) label. + + Parameters + ---------- + raa : Triangle + The RAA sample dataset Triangle. + + Returns + ------- + None + """ + reversed_tr = raa.iloc[..., ::-1] + + assert list(reversed_tr.development) != list(raa.development) + + sorted_tr = reversed_tr.sort_axis("development") + assert list(sorted_tr.development) == list(raa.development) + np.testing.assert_array_equal( + sorted_tr.set_backend("numpy").values, raa.set_backend("numpy").values + ) + + # When sorting an already-sorted triangle, leave values unchanged. + already_sorted = sorted_tr.sort_axis("development") + assert list(already_sorted.development) == list(sorted_tr.development) + np.testing.assert_array_equal( + already_sorted.set_backend("numpy").values, sorted_tr.set_backend("numpy").values + ) + + def test_shift(raa): assert ( raa.iloc[..., 1:-1, 1:-1] @@ -943,6 +1347,51 @@ def test_shift(raa): ).to_frame(origin_as_datetime=False).fillna(0).sum().sum() == 0 +def test_shift_zero_periods_returns_self(raa: Triangle) -> None: + """ + shift(periods=0) should short-circuit and return the same Triangle + unchanged, rather than performing any lagging. + + Parameters + ---------- + raa : Triangle + The RAA sample dataset Triangle. + + Returns + ------- + None + """ + assert raa.shift(periods=0) is raa + assert raa.shift(periods=0, axis=2) is raa + assert raa.shift(periods=0, axis=3) is raa + + +def test_shift_invalid_axis_raises(raa: Triangle) -> None: + """ + shift() only supports lagging along the origin or development axes + (axis 2 or 3). Requesting the index or columns axis (0/1, or their string + names) should raise AttributeError. + + Parameters + ---------- + raa : Triangle + The RAA sample dataset Triangle. + + Returns + ------- + None + """ + with pytest.raises( + AttributeError, match="Lagging only supported for origin and development axes" + ): + raa.shift(axis="columns") + + with pytest.raises( + AttributeError, match="Lagging only supported for origin and development axes" + ): + raa.shift(axis=0) + + def test_array_protocol2(raa): import numpy as np @@ -1058,8 +1507,8 @@ def f(x): def test_repr_html(raa, clrd): - assert type(raa._repr_html_()) == str - assert type(clrd._repr_html_()) == str + assert type(raa._repr_html_()) is str + assert type(clrd._repr_html_()) is str def test_agg_sparse(): @@ -1242,22 +1691,22 @@ def test_origin_as_datetime_arg(clrd): ) -def test_full_triangle_and_full_expectation(raa,atol): +def test_full_triangle_and_full_expectation(raa, atol): raa_cum = raa - assert raa_cum.is_cumulative == True + assert raa_cum.is_cumulative raa_incr = raa_cum.cum_to_incr() - assert raa_incr.is_cumulative == False - assert raa_incr.incr_to_cum().is_cumulative == True + assert not raa_incr.is_cumulative + assert raa_incr.incr_to_cum().is_cumulative assert raa_incr.incr_to_cum() == raa_cum cl_fit_incr = cl.Chainladder().fit(X=raa_incr) cl_predict_incr = cl.Chainladder().fit_predict(X=raa_incr) - assert cl_fit_incr.X_.is_cumulative == False + assert not cl_fit_incr.X_.is_cumulative cl_fit_cum = cl.Chainladder().fit(X=raa_cum) cl_predict_cum = cl.Chainladder().fit_predict(X=raa_cum) - assert cl_fit_cum.X_.is_cumulative == True + assert cl_fit_cum.X_.is_cumulative assert cl_fit_incr.cdf_ == cl_fit_cum.cdf_ assert cl_fit_incr.ultimate_ == cl_fit_cum.ultimate_ @@ -1288,12 +1737,12 @@ def test_full_triangle_and_full_expectation(raa,atol): bf_fit_incr = cl.BornhuetterFerguson(apriori=1).fit( X=raa_incr, sample_weight=raa_incr.incr_to_cum().latest_diagonal * 0 ) - assert bf_fit_incr.X_.is_cumulative == False + assert not bf_fit_incr.X_.is_cumulative bf_fit_cum = cl.BornhuetterFerguson(apriori=1).fit( X=raa_cum, sample_weight=raa_cum.latest_diagonal * 0 ) - assert bf_fit_cum.X_.is_cumulative == True + assert bf_fit_cum.X_.is_cumulative assert bf_fit_incr.cdf_ == bf_fit_cum.cdf_ assert bf_fit_incr.ultimate_ == bf_fit_cum.ultimate_ @@ -1344,35 +1793,31 @@ def test_halfyear_grain(): def test_predict(raa): raa_cum = raa - assert cl.Chainladder().fit(raa_cum).X_.is_cumulative == True + assert cl.Chainladder().fit(raa_cum).X_.is_cumulative assert ( cl.BornhuetterFerguson() .fit(raa_cum, sample_weight=raa_cum.latest_diagonal * 0 + 40000) .X_.is_cumulative - == True ) - assert cl.Chainladder().fit_predict(raa_cum).is_cumulative == True + assert cl.Chainladder().fit_predict(raa_cum).is_cumulative assert ( cl.BornhuetterFerguson() .fit_predict(raa_cum, sample_weight=raa_cum.latest_diagonal * 0 + 40000) .is_cumulative - == True ) raa_incr = raa.cum_to_incr() - assert cl.Chainladder().fit(raa_incr).X_.is_cumulative == False + assert not cl.Chainladder().fit(raa_incr).X_.is_cumulative assert ( - cl.BornhuetterFerguson() + not cl.BornhuetterFerguson() .fit(raa_incr, sample_weight=raa_incr.latest_diagonal * 0 + 40000) .X_.is_cumulative - == False ) - assert cl.Chainladder().fit_predict(raa_incr).is_cumulative == False + assert not cl.Chainladder().fit_predict(raa_incr).is_cumulative assert ( - cl.BornhuetterFerguson() + not cl.BornhuetterFerguson() .fit_predict(raa_incr, sample_weight=raa_incr.latest_diagonal * 0 + 40000) .is_cumulative - == False ) @@ -1402,7 +1847,7 @@ def test_halfyear_development(): cumulative=True, ) ) - == cl.Triangle + is cl.Triangle ) data = [ @@ -1430,7 +1875,7 @@ def test_halfyear_development(): cumulative=True, ) ) - ) == cl.Triangle + ) is cl.Triangle def test_latest_diagonal_vs_full_tri_raa(raa): @@ -1503,18 +1948,18 @@ def test_semi_annual_grain(): assert Atri == Stri.grain('OYDY') def test_odd_quarter_end(): - data= pd.DataFrame([ + data = pd.DataFrame([ ["5/1/2023", 12, '4/30/2024', 100], ["8/1/2023", 9, "4/30/2024", 130], ["11/1/2023", 6, "4/30/2024", 160], ["2/1/2024", 3, "4/30/2024", 140]], - columns = ['origin', 'development', 'valuation', 'EarnedPremium']) + columns=['origin', 'development', 'valuation', 'EarnedPremium']) triangle = cl.Triangle( data, origin='origin', origin_format='%Y-%m-%d', development='valuation', columns='EarnedPremium', trailing=True, cumulative=True ) data_from_tri = triangle.to_frame(origin_as_datetime=True) - assert np.all(data_from_tri['2024Q2'].values == [100.,130.,160.,140.]) - assert np.all(data_from_tri.index == pd.DatetimeIndex(data=["5/1/2023","8/1/2023","11/1/2023","2/1/2024"],freq = 'QS-NOV')) + assert np.all(data_from_tri['2024Q2'].values == [100., 130., 160., 140.]) + assert np.all(data_from_tri.index == pd.DatetimeIndex(data=["5/1/2023", "8/1/2023", "11/1/2023", "2/1/2024"], freq='QS-NOV')) def test_single_valuation_date_preserves_exact_date(): @@ -1612,21 +2057,21 @@ def test_friedland_gl_self_insurer_grain() -> None: def test_OXDX_triangle(): - for x in [12,6,3,1]: - for y in [i for i in [12,6,3,1] if i <= x]: + for x in [12, 6, 3, 1]: + for y in [i for i in [12, 6, 3, 1] if i <= x]: first_orig = '2020-01-01' width = int(x / y) + 1 - dev_series = (pd.date_range(start=first_orig,periods = width, freq = str(y) + 'ME') + pd.DateOffset(months=y-1)).to_series() + dev_series = (pd.date_range(start=first_orig, periods=width, freq=str(y) + 'ME') + pd.DateOffset(months=y - 1)).to_series() tri_df = pd.DataFrame({ 'origin_date': pd.concat([pd.to_datetime([first_orig] * (width)).to_series(), (pd.to_datetime([first_orig]) + pd.DateOffset(months=x)).to_series()]).to_list(), - 'development_date': pd.concat([dev_series,dev_series.iloc[[0]] + pd.DateOffset(months=x)]).to_list(), - 'value': list(range(1,width + 2)) + 'development_date': pd.concat([dev_series, dev_series.iloc[[0]] + pd.DateOffset(months=x)]).to_list(), + 'value': list(range(1, width + 2)) }) for i in range(12): for j in range(y): test_data = tri_df.copy() test_data['origin_date'] += pd.DateOffset(months=i) - test_data['development_date'] += pd.DateOffset(months=i-j) + test_data['development_date'] += pd.DateOffset(months=i - j) tri = cl.Triangle( test_data, origin='origin_date', @@ -1634,19 +2079,19 @@ def test_OXDX_triangle(): columns='value', cumulative=True ) - assert tri.shape == (1,1,2,width) + assert tri.shape == (1, 1, 2, width) assert tri.sum().sum() == tri_df['value'].sum() - assert np.all(tri.development == [y-j + x * y for x in range(width)]) - #there's a known bug with origin that displays incorrect year when origin doesn't start on 1/1 - #if x == 12: - #assert np.all(tri.origin == ['2020','2021']) - #elif x in [6,3]: - #assert np.all(tri.origin.strftime('%Y') == pd.to_datetime(tri.odims).strftime('%Y')) - #assert np.all(tri.origin.strftime('%q').values.astype(float) == np.ceil((pd.to_datetime(tri.odims).strftime('%m').values.astype(int) - 0.5) / 3)) + assert np.all(tri.development == [y - j + x * y for x in range(width)]) + # there's a known bug with origin that displays incorrect year when origin doesn't start on 1/1 + # if x == 12: + # assert np.all(tri.origin == ['2020','2021']) + # elif x in [6,3]: + # assert np.all(tri.origin.strftime('%Y') == pd.to_datetime(tri.odims).strftime('%Y')) + # assert np.all(tri.origin.strftime('%q').values.astype(float) == np.ceil((pd.to_datetime(tri.odims).strftime('%m').values.astype(int) - 0.5) / 3)) def test_fillzero(): raa = cl.load_sample('raa') - zero = raa - raa[raa.origin=='1982'] + zero = raa - raa[raa.origin == '1982'] filled = zero.fillzero() assert (filled[filled.origin == '1982'][filled.development == 24].values.flatten()[0]) == 0 @@ -1665,8 +2110,8 @@ def test_2x2_triangle(): cumulative=True ) tri_from_df - assert np.array_equal(tri_from_df.cum_to_incr().values,np.array([[[[ 78000., 144000.], - [ 78000., np.float64(np.nan)]]]]), equal_nan=True) + assert np.array_equal(tri_from_df.cum_to_incr().values, np.array([[[[78000., 144000.], + [78000., np.float64(np.nan)]]]]), equal_nan=True) def test_triangle_init_from_dict() -> None: @@ -1843,16 +2288,16 @@ def test_xs(clrd): assert clrd.xs('Adriatic Ins Co') == clrd.loc['Adriatic Ins Co'] assert clrd.xs('Adriatic Ins Co').index.equals(clrd.loc['Adriatic Ins Co'].index) # when slicing with .loc on the all term in the index, Triangle will not drop any term - assert clrd.xs(('Agway Ins Co','comauto'), drop_level=False) == clrd.loc['Agway Ins Co','comauto'] - assert clrd.xs(('Agway Ins Co','comauto'), drop_level=False).index.equals(clrd.loc['Agway Ins Co','comauto'].index) + assert clrd.xs(('Agway Ins Co', 'comauto'), drop_level=False) == clrd.loc['Agway Ins Co', 'comauto'] + assert clrd.xs(('Agway Ins Co', 'comauto'), drop_level=False).index.equals(clrd.loc['Agway Ins Co', 'comauto'].index) # when all index terms are included in xs and drop_level is True, the default 'Total' index value is provided - assert clrd.xs(('Agway Ins Co','comauto'), drop_level=True).index.equals(cl.load_sample('genins').index) + assert clrd.xs(('Agway Ins Co', 'comauto'), drop_level=True).index.equals(cl.load_sample('genins').index) # when slicing with .loc on the second or subsequent terms in the index, Triangle will not drop the term - assert clrd.xs('comauto',level=1, drop_level=False) == clrd.loc[clrd['LOB'] == 'comauto'] - assert clrd.xs('comauto',level=1, drop_level=False).index.equals(clrd.loc[clrd['LOB'] == 'comauto'].index) + assert clrd.xs('comauto', level=1, drop_level=False) == clrd.loc[clrd['LOB'] == 'comauto'] + assert clrd.xs('comauto', level=1, drop_level=False).index.equals(clrd.loc[clrd['LOB'] == 'comauto'].index) # level works with either integer index or name of the index column - assert clrd.xs('comauto',level=1) == clrd.xs('comauto',level='LOB') - assert clrd.xs('comauto',level=1).index.equals(clrd.xs('comauto',level='LOB').index) + assert clrd.xs('comauto', level=1) == clrd.xs('comauto', level='LOB') + assert clrd.xs('comauto', level=1).index.equals(clrd.xs('comauto', level='LOB').index) def test_get_array_module_with_explicit_arr(raa: Triangle) -> None: