From 38b31a18568687e3746111307a60371102d09ce0 Mon Sep 17 00:00:00 2001 From: henrydingliu <106109320+henrydingliu@users.noreply.github.com> Date: Thu, 4 Jun 2026 18:34:04 -0700 Subject: [PATCH 01/83] Update conf.py --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 07167688..6db10340 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -16,7 +16,7 @@ bibtex_reference_style = 'author_year' comments_config = {'hypothesis': False, 'utterances': False} copyright = '2023' -exclude_patterns = ['**.ipynb_checkpoints', '.DS_Store', 'Thumbs.db', '_build'] +exclude_patterns = ['**.ipynb_checkpoints', '.DS_Store', 'Thumbs.db', '_build','/library/presentation_assets/'] extensions = ['sphinx_togglebutton', 'sphinx_copybutton', 'myst_nb', 'jupyter_book', 'sphinx_thebe', 'sphinx_comments', 'sphinx_external_toc', 'sphinx.ext.intersphinx', 'sphinx_design', 'sphinx_book_theme', 'sphinx.ext.autodoc', 'sphinx.ext.autosummary', 'sphinx.ext.doctest', 'sphinx.ext.linkcode', 'numpydoc', 'sphinx.ext.mathjax', 'linkcode', 'sphinxcontrib.bibtex', 'sphinx_jupyterbook_latex', 'sphinx_multitoc_numbering'] external_toc_exclude_missing = False external_toc_path = '_toc.yml' From 4a9b9dc12d4aecc24af10f6e0e48ec70e2e3be72 Mon Sep 17 00:00:00 2001 From: henrydingliu <106109320+henrydingliu@users.noreply.github.com> Date: Thu, 4 Jun 2026 18:45:23 -0700 Subject: [PATCH 02/83] Update conf.py --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 6db10340..79d133e4 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -16,7 +16,7 @@ bibtex_reference_style = 'author_year' comments_config = {'hypothesis': False, 'utterances': False} copyright = '2023' -exclude_patterns = ['**.ipynb_checkpoints', '.DS_Store', 'Thumbs.db', '_build','/library/presentation_assets/'] +exclude_patterns = ['**.ipynb_checkpoints', '.DS_Store', 'Thumbs.db', '_build','library/presentation_assets/'] extensions = ['sphinx_togglebutton', 'sphinx_copybutton', 'myst_nb', 'jupyter_book', 'sphinx_thebe', 'sphinx_comments', 'sphinx_external_toc', 'sphinx.ext.intersphinx', 'sphinx_design', 'sphinx_book_theme', 'sphinx.ext.autodoc', 'sphinx.ext.autosummary', 'sphinx.ext.doctest', 'sphinx.ext.linkcode', 'numpydoc', 'sphinx.ext.mathjax', 'linkcode', 'sphinxcontrib.bibtex', 'sphinx_jupyterbook_latex', 'sphinx_multitoc_numbering'] external_toc_exclude_missing = False external_toc_path = '_toc.yml' From ebf96d90e561f6eb5690d8658c56ad268551d7dd Mon Sep 17 00:00:00 2001 From: henrydingliu <106109320+henrydingliu@users.noreply.github.com> Date: Thu, 4 Jun 2026 19:14:16 -0700 Subject: [PATCH 03/83] Update conf.py --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 79d133e4..4676c404 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -16,7 +16,7 @@ bibtex_reference_style = 'author_year' comments_config = {'hypothesis': False, 'utterances': False} copyright = '2023' -exclude_patterns = ['**.ipynb_checkpoints', '.DS_Store', 'Thumbs.db', '_build','library/presentation_assets/'] +exclude_patterns = ['**.ipynb_checkpoints', '.DS_Store', 'Thumbs.db', '_build','library/presentation_assets/*'] extensions = ['sphinx_togglebutton', 'sphinx_copybutton', 'myst_nb', 'jupyter_book', 'sphinx_thebe', 'sphinx_comments', 'sphinx_external_toc', 'sphinx.ext.intersphinx', 'sphinx_design', 'sphinx_book_theme', 'sphinx.ext.autodoc', 'sphinx.ext.autosummary', 'sphinx.ext.doctest', 'sphinx.ext.linkcode', 'numpydoc', 'sphinx.ext.mathjax', 'linkcode', 'sphinxcontrib.bibtex', 'sphinx_jupyterbook_latex', 'sphinx_multitoc_numbering'] external_toc_exclude_missing = False external_toc_path = '_toc.yml' From 172c8ab5fa716e5fbaecaed9fca9d94ea12d74ff Mon Sep 17 00:00:00 2001 From: henrydingliu <106109320+henrydingliu@users.noreply.github.com> Date: Thu, 4 Jun 2026 19:15:27 -0700 Subject: [PATCH 04/83] Update conf.py --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 4676c404..55a13542 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -16,7 +16,7 @@ bibtex_reference_style = 'author_year' comments_config = {'hypothesis': False, 'utterances': False} copyright = '2023' -exclude_patterns = ['**.ipynb_checkpoints', '.DS_Store', 'Thumbs.db', '_build','library/presentation_assets/*'] +exclude_patterns = ['**.ipynb_checkpoints', '.DS_Store', 'Thumbs.db', '_build','library/presentation_assets/*','readme.md'] extensions = ['sphinx_togglebutton', 'sphinx_copybutton', 'myst_nb', 'jupyter_book', 'sphinx_thebe', 'sphinx_comments', 'sphinx_external_toc', 'sphinx.ext.intersphinx', 'sphinx_design', 'sphinx_book_theme', 'sphinx.ext.autodoc', 'sphinx.ext.autosummary', 'sphinx.ext.doctest', 'sphinx.ext.linkcode', 'numpydoc', 'sphinx.ext.mathjax', 'linkcode', 'sphinxcontrib.bibtex', 'sphinx_jupyterbook_latex', 'sphinx_multitoc_numbering'] external_toc_exclude_missing = False external_toc_path = '_toc.yml' From bdc83c5b2be70f9d6497ca20cb9753e48b518d63 Mon Sep 17 00:00:00 2001 From: henrydingliu <106109320+henrydingliu@users.noreply.github.com> Date: Thu, 4 Jun 2026 19:23:58 -0700 Subject: [PATCH 05/83] Update conf.py --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 55a13542..dea4715b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -16,7 +16,7 @@ bibtex_reference_style = 'author_year' comments_config = {'hypothesis': False, 'utterances': False} copyright = '2023' -exclude_patterns = ['**.ipynb_checkpoints', '.DS_Store', 'Thumbs.db', '_build','library/presentation_assets/*','readme.md'] +exclude_patterns = ['**.ipynb_checkpoints', '.DS_Store', 'Thumbs.db', '_build','**/presentation_assets/**','readme.md'] extensions = ['sphinx_togglebutton', 'sphinx_copybutton', 'myst_nb', 'jupyter_book', 'sphinx_thebe', 'sphinx_comments', 'sphinx_external_toc', 'sphinx.ext.intersphinx', 'sphinx_design', 'sphinx_book_theme', 'sphinx.ext.autodoc', 'sphinx.ext.autosummary', 'sphinx.ext.doctest', 'sphinx.ext.linkcode', 'numpydoc', 'sphinx.ext.mathjax', 'linkcode', 'sphinxcontrib.bibtex', 'sphinx_jupyterbook_latex', 'sphinx_multitoc_numbering'] external_toc_exclude_missing = False external_toc_path = '_toc.yml' From 6e92b74f8ba6abfa6727cfd5871575b0876ed649 Mon Sep 17 00:00:00 2001 From: henrydingliu <106109320+henrydingliu@users.noreply.github.com> Date: Thu, 4 Jun 2026 19:58:58 -0700 Subject: [PATCH 06/83] Update conf.py --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index dea4715b..b3445f22 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -16,7 +16,7 @@ bibtex_reference_style = 'author_year' comments_config = {'hypothesis': False, 'utterances': False} copyright = '2023' -exclude_patterns = ['**.ipynb_checkpoints', '.DS_Store', 'Thumbs.db', '_build','**/presentation_assets/**','readme.md'] +exclude_patterns = ['**.ipynb_checkpoints', '.DS_Store', 'Thumbs.db', '_build','library/presentation_assets/**','README.md'] extensions = ['sphinx_togglebutton', 'sphinx_copybutton', 'myst_nb', 'jupyter_book', 'sphinx_thebe', 'sphinx_comments', 'sphinx_external_toc', 'sphinx.ext.intersphinx', 'sphinx_design', 'sphinx_book_theme', 'sphinx.ext.autodoc', 'sphinx.ext.autosummary', 'sphinx.ext.doctest', 'sphinx.ext.linkcode', 'numpydoc', 'sphinx.ext.mathjax', 'linkcode', 'sphinxcontrib.bibtex', 'sphinx_jupyterbook_latex', 'sphinx_multitoc_numbering'] external_toc_exclude_missing = False external_toc_path = '_toc.yml' From 6c11c9bcf0c7e585df9aef1657e86703874be100 Mon Sep 17 00:00:00 2001 From: henrydingliu <106109320+henrydingliu@users.noreply.github.com> Date: Thu, 4 Jun 2026 20:17:18 -0700 Subject: [PATCH 07/83] Update conf.py --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index b3445f22..07167688 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -16,7 +16,7 @@ bibtex_reference_style = 'author_year' comments_config = {'hypothesis': False, 'utterances': False} copyright = '2023' -exclude_patterns = ['**.ipynb_checkpoints', '.DS_Store', 'Thumbs.db', '_build','library/presentation_assets/**','README.md'] +exclude_patterns = ['**.ipynb_checkpoints', '.DS_Store', 'Thumbs.db', '_build'] extensions = ['sphinx_togglebutton', 'sphinx_copybutton', 'myst_nb', 'jupyter_book', 'sphinx_thebe', 'sphinx_comments', 'sphinx_external_toc', 'sphinx.ext.intersphinx', 'sphinx_design', 'sphinx_book_theme', 'sphinx.ext.autodoc', 'sphinx.ext.autosummary', 'sphinx.ext.doctest', 'sphinx.ext.linkcode', 'numpydoc', 'sphinx.ext.mathjax', 'linkcode', 'sphinxcontrib.bibtex', 'sphinx_jupyterbook_latex', 'sphinx_multitoc_numbering'] external_toc_exclude_missing = False external_toc_path = '_toc.yml' From a8b91a58a044c95630b2da897557a9da1be52fee Mon Sep 17 00:00:00 2001 From: Kenneth Hsu Date: Thu, 4 Jun 2026 15:59:40 -0700 Subject: [PATCH 08/83] Added development_format --- chainladder/utils/data/_manifest.py | 4 ++++ chainladder/utils/utility_functions.py | 17 +++++++---------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/chainladder/utils/data/_manifest.py b/chainladder/utils/data/_manifest.py index bf21d808..75702d6a 100644 --- a/chainladder/utils/data/_manifest.py +++ b/chainladder/utils/data/_manifest.py @@ -27,6 +27,8 @@ Measure column name(s) loaded into the Triangle. ``cumulative`` ``True`` if the measures are cumulative, ``False`` if incremental. +``development_format`` + Optional. Passed to ``Triangle`` by :func:`chainladder.load_sample`. """ SAMPLES: dict = { @@ -157,6 +159,7 @@ "Paid Claims", ], "cumulative": True, + "development_format": "%Y-12-31", }, "friedland_med_mal": { "origin": "Accident Year", @@ -169,6 +172,7 @@ "Open Claim Counts", ], "cumulative": True, + "development_format": "%Y", }, "friedland_qs": { "origin": "Accident Year", diff --git a/chainladder/utils/utility_functions.py b/chainladder/utils/utility_functions.py index 7e8ac606..11335cb8 100644 --- a/chainladder/utils/utility_functions.py +++ b/chainladder/utils/utility_functions.py @@ -10,10 +10,7 @@ import numpy as np import pandas as pd -from chainladder import ( - __dt64_unit__, - __dt64_dtype__ -) +from chainladder import __dt64_unit__, __dt64_dtype__ from chainladder.utils.sparse import sp from chainladder.utils.data._manifest import SAMPLES from io import StringIO @@ -117,6 +114,8 @@ def load_sample(key: str, *args, **kwargs) -> Triangle: columns = config["columns"] cumulative = config["cumulative"] + development_format = config.get("development_format", None) + df = pd.read_csv(filepath_or_buffer=dataset_path) return Triangle( @@ -126,6 +125,7 @@ def load_sample(key: str, *args, **kwargs) -> Triangle: index=index, columns=columns, cumulative=cumulative, + development_format=development_format, *args, **kwargs, ) @@ -818,6 +818,7 @@ def PTF_formula( return "+".join(formula_parts) return "" + def date_delta_adjustment(date: str) -> str: """ Subtracts the default pandas datetime delta from a date in "YYYY-MM-DD" string format. @@ -855,10 +856,6 @@ def date_delta_adjustment(date: str) -> str: '2025-10-31 23:59:59.999999' """ + res: str = str(pd.Timestamp(date) - pd.Timedelta(1, unit=__dt64_unit__)) - res: str = str( - pd.Timestamp(date) - \ - pd.Timedelta(1, unit=__dt64_unit__) - ) - - return res \ No newline at end of file + return res From bf71b47041a84558f61db617c58701dbdc6c2dca Mon Sep 17 00:00:00 2001 From: Kenneth Hsu Date: Thu, 4 Jun 2026 16:04:00 -0700 Subject: [PATCH 09/83] removed the development_format from friedland_med_mal --- chainladder/utils/data/_manifest.py | 1 - 1 file changed, 1 deletion(-) diff --git a/chainladder/utils/data/_manifest.py b/chainladder/utils/data/_manifest.py index 75702d6a..21589a8e 100644 --- a/chainladder/utils/data/_manifest.py +++ b/chainladder/utils/data/_manifest.py @@ -172,7 +172,6 @@ "Open Claim Counts", ], "cumulative": True, - "development_format": "%Y", }, "friedland_qs": { "origin": "Accident Year", From 9db4fc7a584c12ad6186973a6cfc6dcafa5c70f1 Mon Sep 17 00:00:00 2001 From: Gene Dan Date: Fri, 5 Jun 2026 14:08:53 -0500 Subject: [PATCH 10/83] FIX: Make quarterly.csv development dates ISO 8601-compatible. --- chainladder/core/tests/test_triangle.py | 20 + chainladder/core/triangle.py | 2 +- chainladder/utils/data/quarterly.csv | 552 ++++++++++++------------ 3 files changed, 297 insertions(+), 277 deletions(-) diff --git a/chainladder/core/tests/test_triangle.py b/chainladder/core/tests/test_triangle.py index d5b0aab9..504f341f 100644 --- a/chainladder/core/tests/test_triangle.py +++ b/chainladder/core/tests/test_triangle.py @@ -1061,3 +1061,23 @@ def test_xs(clrd): assert clrd.xs('comauto',level=1).index.equals(clrd.xs('comauto',level='LOB').index) +def test_to_datetime_uninferrable_format_raises() -> None: + """ + Initialize a triangle with incorrect date format on the origin axis. Should raise a ValueError. + + Returns + ------- + None + + """ + with pytest.raises(ValueError, match="Unable to infer datetime"): + cl.Triangle( + data={ + 'origin': ['1995/Q1', '1996/Q1'], + 'development': ['1995Q1', '1996Q1'], + 'value': [1.0, 2.0]}, + origin='origin', + development='development', + columns='value', + cumulative=True + ) \ No newline at end of file diff --git a/chainladder/core/triangle.py b/chainladder/core/triangle.py index 4658f02b..eed90ffb 100644 --- a/chainladder/core/triangle.py +++ b/chainladder/core/triangle.py @@ -34,7 +34,7 @@ class Triangle(TriangleBase): Parameters ---------- - data: DataFrame or DataFrameXchg, or dict + data: DataFrame | DataFrameXchg | dict A single dataframe that contains columns representing all other arguments to the Triangle constructor. One may supply a DataFrame-like object (referred to as DataFrameXchg) supporting the __dataframe__ diff --git a/chainladder/utils/data/quarterly.csv b/chainladder/utils/data/quarterly.csv index 416dfb4d..abf62531 100644 --- a/chainladder/utils/data/quarterly.csv +++ b/chainladder/utils/data/quarterly.csv @@ -1,277 +1,277 @@ development,origin,incurred,paid -1995Q1,1995,44.0,3.0 -1996Q1,1996,42.0,1.0 -1997Q1,1997,17.0,1.0 -1998Q1,1998,10.0,1.0 -1999Q1,1999,13.0,1.0 -2000Q1,2000,2.0,1.0 -2001Q1,2001,4.0,1.0 -2002Q1,2002,2.0,1.0 -2003Q1,2003,3.0,1.0 -2004Q1,2004,4.0,4.0 -2005Q1,2005,21.0,1.0 -2006Q1,2006,13.0,1.0 -1995Q2,1995,96.0,24.0 -1996Q2,1996,136.0,16.0 -1997Q2,1997,43.0,17.0 -1998Q2,1998,43.0,11.0 -1999Q2,1999,41.0,14.0 -2000Q2,2000,29.0,6.0 -2001Q2,2001,25.0,7.0 -2002Q2,2002,34.0,10.0 -2003Q2,2003,19.0,9.0 -2004Q2,2004,38.0,16.0 -2005Q2,2005,79.0,7.0 -1995Q3,1995,194.0,65.0 -1996Q3,1996,202.0,54.0 -1997Q3,1997,135.0,55.0 -1998Q3,1998,107.0,40.0 -1999Q3,1999,109.0,47.0 -2000Q3,2000,88.0,28.0 -2001Q3,2001,151.0,37.0 -2002Q3,2002,115.0,45.0 -2003Q3,2003,90.0,31.0 -2004Q3,2004,138.0,49.0 -2005Q3,2005,115.0,36.0 -1995Q4,1995,420.0,141.0 -1996Q4,1996,365.0,135.0 -1997Q4,1997,380.0,166.0 -1998Q4,1998,238.0,93.0 -1999Q4,1999,306.0,113.0 -2000Q4,2000,254.0,100.0 -2001Q4,2001,333.0,128.0 -2002Q4,2002,290.0,110.0 -2003Q4,2003,692.0,94.0 -2004Q4,2004,371.0,170.0 -2005Q4,2005,299.0,97.0 -1996Q1,1995,621.0,273.0 -1997Q1,1996,541.0,260.0 -1998Q1,1997,530.0,296.0 -1999Q1,1998,393.0,185.0 -2000Q1,1999,481.0,225.0 -2001Q1,2000,380.0,194.0 -2002Q1,2001,777.0,271.0 -2003Q1,2002,472.0,236.0 -2004Q1,2003,597.0,192.0 -2005Q1,2004,583.0,289.0 -2006Q1,2005,422.0,183.0 -1996Q2,1995,715.0,418.0 -1997Q2,1996,651.0,398.0 -1998Q2,1997,714.0,442.0 -1999Q2,1998,574.0,343.0 -2000Q2,1999,657.0,379.0 -2001Q2,2000,501.0,297.0 -2002Q2,2001,663.0,427.0 -2003Q2,2002,809.0,442.0 -2004Q2,2003,929.0,299.0 -2005Q2,2004,756.0,442.0 -1996Q3,1995,748.0,550.0 -1997Q3,1996,817.0,594.0 -1998Q3,1997,813.0,587.0 -1999Q3,1998,732.0,474.0 -2000Q3,1999,821.0,570.0 -2001Q3,2000,615.0,415.0 -2002Q3,2001,856.0,579.0 -2003Q3,2002,1054.0,668.0 -2004Q3,2003,883.0,408.0 -2005Q3,2004,902.0,601.0 -1996Q4,1995,906.0,692.0 -1997Q4,1996,988.0,758.0 -1998Q4,1997,945.0,701.0 -1999Q4,1998,894.0,643.0 -2000Q4,1999,1007.0,715.0 -2001Q4,2000,735.0,521.0 -2002Q4,2001,988.0,722.0 -2003Q4,2002,1543.0,890.0 -2004Q4,2003,1117.0,792.0 -2005Q4,2004,1111.0,793.0 -1997Q1,1995,950.0,814.0 -1998Q1,1996,1052.0,871.0 -1999Q1,1997,966.0,811.0 -2000Q1,1998,935.0,744.0 -2001Q1,1999,1021.0,832.0 -2002Q1,2000,788.0,616.0 -2003Q1,2001,1063.0,838.0 -2004Q1,2002,1617.0,1078.0 -2005Q1,2003,1092.0,873.0 -2006Q1,2004,1212.0,948.0 -1997Q2,1995,973.0,876.0 -1998Q2,1996,1122.0,964.0 -1999Q2,1997,1008.0,891.0 -2000Q2,1998,967.0,831.0 -2001Q2,1999,1141.0,955.0 -2002Q2,2000,842.0,697.0 -2003Q2,2001,1167.0,937.0 -2004Q2,2002,1505.0,1198.0 -2005Q2,2003,1176.0,949.0 -1997Q3,1995,997.0,916.0 -1998Q3,1996,1139.0,1017.0 -1999Q3,1997,1028.0,940.0 -2000Q3,1998,1019.0,902.0 -2001Q3,1999,1171.0,1048.0 -2002Q3,2000,912.0,758.0 -2003Q3,2001,1199.0,1020.0 -2004Q3,2002,1599.0,1325.0 -2005Q3,2003,1198.0,1019.0 -1997Q4,1995,1030.0,959.0 -1998Q4,1996,1173.0,1052.0 -1999Q4,1997,1069.0,976.0 -2000Q4,1998,1037.0,951.0 -2001Q4,1999,1249.0,1118.0 -2002Q4,2000,915.0,810.0 -2003Q4,2001,1242.0,1091.0 -2004Q4,2002,1695.0,1412.0 -2005Q4,2003,1230.0,1064.0 -1998Q1,1995,1020.0,968.0 -1999Q1,1996,1169.0,1089.0 -2000Q1,1997,1064.0,1000.0 -2001Q1,1998,1062.0,989.0 -2002Q1,1999,1267.0,1183.0 -2003Q1,2000,953.0,859.0 -2004Q1,2001,1307.0,1160.0 -2005Q1,2002,1818.0,1524.0 -2006Q1,2003,1221.0,1100.0 -1998Q2,1995,1035.0,1002.0 -1999Q2,1996,1174.0,1108.0 -2000Q2,1997,1073.0,1022.0 -2001Q2,1998,1079.0,1022.0 -2002Q2,1999,1289.0,1214.0 -2003Q2,2000,963.0,892.0 -2004Q2,2001,1305.0,1207.0 -2005Q2,2002,1716.0,1615.0 -1998Q3,1995,1055.0,1020.0 -1999Q3,1996,1205.0,1141.0 -2000Q3,1997,1086.0,1043.0 -2001Q3,1998,1099.0,1053.0 -2002Q3,1999,1358.0,1293.0 -2003Q3,2000,977.0,916.0 -2004Q3,2001,1348.0,1239.0 -2005Q3,2002,1819.0,1653.0 -1998Q4,1995,1072.0,1031.0 -1999Q4,1996,1225.0,1175.0 -2000Q4,1997,1112.0,1059.0 -2001Q4,1998,1138.0,1074.0 -2002Q4,1999,1400.0,1327.0 -2003Q4,2000,990.0,935.0 -2004Q4,2001,1362.0,1305.0 -2005Q4,2002,1839.0,1720.0 -1999Q1,1995,1070.0,1041.0 -2000Q1,1996,1238.0,1194.0 -2001Q1,1997,1100.0,1068.0 -2002Q1,1998,1126.0,1093.0 -2003Q1,1999,1400.0,1363.0 -2004Q1,2000,1001.0,950.0 -2005Q1,2001,1362.0,1323.0 -2006Q1,2002,1820.0,1760.0 -1999Q2,1995,1051.0,1035.0 -2000Q2,1996,1228.0,1208.0 -2001Q2,1997,1111.0,1080.0 -2002Q2,1998,1163.0,1132.0 -2003Q2,1999,1409.0,1383.0 -2004Q2,2000,1005.0,967.0 -2005Q2,2001,1376.0,1347.0 -1999Q3,1995,1062.0,1045.0 -2000Q3,1996,1239.0,1217.0 -2001Q3,1997,1115.0,1091.0 -2002Q3,1998,1198.0,1145.0 -2003Q3,1999,1437.0,1396.0 -2004Q3,2000,1013.0,982.0 -2005Q3,2001,1376.0,1365.0 -1999Q4,1995,1070.0,1054.0 -2000Q4,1996,1254.0,1226.0 -2001Q4,1997,1128.0,1099.0 -2002Q4,1998,1207.0,1162.0 -2003Q4,1999,1468.0,1438.0 -2004Q4,2000,1029.0,1004.0 -2005Q4,2001,1383.0,1375.0 -2000Q1,1995,1069.0,1060.0 -2001Q1,1996,1249.0,1231.0 -2002Q1,1997,1128.0,1104.0 -2003Q1,1998,1209.0,1177.0 -2004Q1,1999,1476.0,1457.0 -2005Q1,2000,1030.0,1013.0 -2006Q1,2001,1411.0,1387.0 -2000Q2,1995,1076.0,1068.0 -2001Q2,1996,1262.0,1243.0 -2002Q2,1997,1142.0,1130.0 -2003Q2,1998,1215.0,1192.0 -2004Q2,1999,1488.0,1474.0 -2005Q2,2000,1056.0,1028.0 -2000Q3,1995,1081.0,1075.0 -2001Q3,1996,1264.0,1249.0 -2002Q3,1997,1170.0,1135.0 -2003Q3,1998,1216.0,1197.0 -2004Q3,1999,1524.0,1504.0 -2005Q3,2000,1056.0,1036.0 -2000Q4,1995,1088.0,1079.0 -2001Q4,1996,1267.0,1252.0 -2002Q4,1997,1159.0,1138.0 -2003Q4,1998,1240.0,1213.0 -2004Q4,1999,1550.0,1521.0 -2005Q4,2000,1066.0,1046.0 -2001Q1,1995,1089.0,1081.0 -2002Q1,1996,1266.0,1253.0 -2003Q1,1997,1155.0,1141.0 -2004Q1,1998,1243.0,1225.0 -2005Q1,1999,1550.0,1532.0 -2006Q1,2000,1066.0,1054.0 -2001Q2,1995,1091.0,1084.0 -2002Q2,1996,1263.0,1256.0 -2003Q2,1997,1167.0,1147.0 -2004Q2,1998,1259.0,1237.0 -2005Q2,1999,1561.0,1547.0 -2001Q3,1995,1090.0,1086.0 -2002Q3,1996,1278.0,1258.0 -2003Q3,1997,1177.0,1163.0 -2004Q3,1998,1281.0,1265.0 -2005Q3,1999,1566.0,1559.0 -2001Q4,1995,1094.0,1089.0 -2002Q4,1996,1273.0,1261.0 -2003Q4,1997,1194.0,1184.0 -2004Q4,1998,1284.0,1274.0 -2005Q4,1999,1585.0,1565.0 -2002Q1,1995,1094.0,1091.0 -2003Q1,1996,1269.0,1266.0 -2004Q1,1997,1196.0,1185.0 -2005Q1,1998,1286.0,1275.0 -2006Q1,1999,1583.0,1573.0 -2002Q2,1995,1095.0,1094.0 -2003Q2,1996,1279.0,1267.0 -2004Q2,1997,1198.0,1186.0 -2005Q2,1998,1289.0,1278.0 -2002Q3,1995,1098.0,1095.0 -2003Q3,1996,1281.0,1268.0 -2004Q3,1997,1197.0,1189.0 -2005Q3,1998,1292.0,1286.0 -2002Q4,1995,1096.0,1096.0 -2003Q4,1996,1299.0,1288.0 -2004Q4,1997,1198.0,1192.0 -2005Q4,1998,1297.0,1288.0 -2003Q1,1995,1097.0,1097.0 -2004Q1,1996,1296.0,1288.0 -2005Q1,1997,1201.0,1194.0 -2006Q1,1998,1298.0,1293.0 -2003Q2,1995,1097.0,1098.0 -2004Q2,1996,1302.0,1289.0 -2005Q2,1997,1201.0,1195.0 -2003Q3,1995,1101.0,1098.0 -2004Q3,1996,1303.0,1291.0 -2005Q3,1997,1200.0,1197.0 -2003Q4,1995,1098.0,1099.0 -2004Q4,1996,1300.0,1296.0 -2005Q4,1997,1203.0,1197.0 -2004Q1,1995,1099.0,1099.0 -2005Q1,1996,1300.0,1296.0 -2006Q1,1997,1200.0,1198.0 -2004Q2,1995,1103.0,1100.0 -2005Q2,1996,1302.0,1297.0 -2004Q3,1995,1100.0,1098.0 -2005Q3,1996,1300.0,1298.0 -2004Q4,1995,1098.0,1098.0 -2005Q4,1996,1303.0,1298.0 -2005Q1,1995,1100.0,1098.0 -2006Q1,1996,1300.0,1298.0 -2005Q2,1995,1100.0,1099.0 -2005Q3,1995,1098.0,1099.0 -2005Q4,1995,1101.0,1100.0 -2006Q1,1995,1100.0,1100.0 +1995-01,1995,44,3 +1996-01,1996,42,1 +1997-01,1997,17,1 +1998-01,1998,10,1 +1999-01,1999,13,1 +2000-01,2000,2,1 +2001-01,2001,4,1 +2002-01,2002,2,1 +2003-01,2003,3,1 +2004-01,2004,4,4 +2005-01,2005,21,1 +2006-01,2006,13,1 +1995-04,1995,96,24 +1996-04,1996,136,16 +1997-04,1997,43,17 +1998-04,1998,43,11 +1999-04,1999,41,14 +2000-04,2000,29,6 +2001-04,2001,25,7 +2002-04,2002,34,10 +2003-04,2003,19,9 +2004-04,2004,38,16 +2005-04,2005,79,7 +1995-07,1995,194,65 +1996-07,1996,202,54 +1997-07,1997,135,55 +1998-07,1998,107,40 +1999-07,1999,109,47 +2000-07,2000,88,28 +2001-07,2001,151,37 +2002-07,2002,115,45 +2003-07,2003,90,31 +2004-07,2004,138,49 +2005-07,2005,115,36 +1995-10,1995,420,141 +1996-10,1996,365,135 +1997-10,1997,380,166 +1998-10,1998,238,93 +1999-10,1999,306,113 +2000-10,2000,254,100 +2001-10,2001,333,128 +2002-10,2002,290,110 +2003-10,2003,692,94 +2004-10,2004,371,170 +2005-10,2005,299,97 +1996-01,1995,621,273 +1997-01,1996,541,260 +1998-01,1997,530,296 +1999-01,1998,393,185 +2000-01,1999,481,225 +2001-01,2000,380,194 +2002-01,2001,777,271 +2003-01,2002,472,236 +2004-01,2003,597,192 +2005-01,2004,583,289 +2006-01,2005,422,183 +1996-04,1995,715,418 +1997-04,1996,651,398 +1998-04,1997,714,442 +1999-04,1998,574,343 +2000-04,1999,657,379 +2001-04,2000,501,297 +2002-04,2001,663,427 +2003-04,2002,809,442 +2004-04,2003,929,299 +2005-04,2004,756,442 +1996-07,1995,748,550 +1997-07,1996,817,594 +1998-07,1997,813,587 +1999-07,1998,732,474 +2000-07,1999,821,570 +2001-07,2000,615,415 +2002-07,2001,856,579 +2003-07,2002,1054,668 +2004-07,2003,883,408 +2005-07,2004,902,601 +1996-10,1995,906,692 +1997-10,1996,988,758 +1998-10,1997,945,701 +1999-10,1998,894,643 +2000-10,1999,1007,715 +2001-10,2000,735,521 +2002-10,2001,988,722 +2003-10,2002,1543,890 +2004-10,2003,1117,792 +2005-10,2004,1111,793 +1997-01,1995,950,814 +1998-01,1996,1052,871 +1999-01,1997,966,811 +2000-01,1998,935,744 +2001-01,1999,1021,832 +2002-01,2000,788,616 +2003-01,2001,1063,838 +2004-01,2002,1617,1078 +2005-01,2003,1092,873 +2006-01,2004,1212,948 +1997-04,1995,973,876 +1998-04,1996,1122,964 +1999-04,1997,1008,891 +2000-04,1998,967,831 +2001-04,1999,1141,955 +2002-04,2000,842,697 +2003-04,2001,1167,937 +2004-04,2002,1505,1198 +2005-04,2003,1176,949 +1997-07,1995,997,916 +1998-07,1996,1139,1017 +1999-07,1997,1028,940 +2000-07,1998,1019,902 +2001-07,1999,1171,1048 +2002-07,2000,912,758 +2003-07,2001,1199,1020 +2004-07,2002,1599,1325 +2005-07,2003,1198,1019 +1997-10,1995,1030,959 +1998-10,1996,1173,1052 +1999-10,1997,1069,976 +2000-10,1998,1037,951 +2001-10,1999,1249,1118 +2002-10,2000,915,810 +2003-10,2001,1242,1091 +2004-10,2002,1695,1412 +2005-10,2003,1230,1064 +1998-01,1995,1020,968 +1999-01,1996,1169,1089 +2000-01,1997,1064,1000 +2001-01,1998,1062,989 +2002-01,1999,1267,1183 +2003-01,2000,953,859 +2004-01,2001,1307,1160 +2005-01,2002,1818,1524 +2006-01,2003,1221,1100 +1998-04,1995,1035,1002 +1999-04,1996,1174,1108 +2000-04,1997,1073,1022 +2001-04,1998,1079,1022 +2002-04,1999,1289,1214 +2003-04,2000,963,892 +2004-04,2001,1305,1207 +2005-04,2002,1716,1615 +1998-07,1995,1055,1020 +1999-07,1996,1205,1141 +2000-07,1997,1086,1043 +2001-07,1998,1099,1053 +2002-07,1999,1358,1293 +2003-07,2000,977,916 +2004-07,2001,1348,1239 +2005-07,2002,1819,1653 +1998-10,1995,1072,1031 +1999-10,1996,1225,1175 +2000-10,1997,1112,1059 +2001-10,1998,1138,1074 +2002-10,1999,1400,1327 +2003-10,2000,990,935 +2004-10,2001,1362,1305 +2005-10,2002,1839,1720 +1999-01,1995,1070,1041 +2000-01,1996,1238,1194 +2001-01,1997,1100,1068 +2002-01,1998,1126,1093 +2003-01,1999,1400,1363 +2004-01,2000,1001,950 +2005-01,2001,1362,1323 +2006-01,2002,1820,1760 +1999-04,1995,1051,1035 +2000-04,1996,1228,1208 +2001-04,1997,1111,1080 +2002-04,1998,1163,1132 +2003-04,1999,1409,1383 +2004-04,2000,1005,967 +2005-04,2001,1376,1347 +1999-07,1995,1062,1045 +2000-07,1996,1239,1217 +2001-07,1997,1115,1091 +2002-07,1998,1198,1145 +2003-07,1999,1437,1396 +2004-07,2000,1013,982 +2005-07,2001,1376,1365 +1999-10,1995,1070,1054 +2000-10,1996,1254,1226 +2001-10,1997,1128,1099 +2002-10,1998,1207,1162 +2003-10,1999,1468,1438 +2004-10,2000,1029,1004 +2005-10,2001,1383,1375 +2000-01,1995,1069,1060 +2001-01,1996,1249,1231 +2002-01,1997,1128,1104 +2003-01,1998,1209,1177 +2004-01,1999,1476,1457 +2005-01,2000,1030,1013 +2006-01,2001,1411,1387 +2000-04,1995,1076,1068 +2001-04,1996,1262,1243 +2002-04,1997,1142,1130 +2003-04,1998,1215,1192 +2004-04,1999,1488,1474 +2005-04,2000,1056,1028 +2000-07,1995,1081,1075 +2001-07,1996,1264,1249 +2002-07,1997,1170,1135 +2003-07,1998,1216,1197 +2004-07,1999,1524,1504 +2005-07,2000,1056,1036 +2000-10,1995,1088,1079 +2001-10,1996,1267,1252 +2002-10,1997,1159,1138 +2003-10,1998,1240,1213 +2004-10,1999,1550,1521 +2005-10,2000,1066,1046 +2001-01,1995,1089,1081 +2002-01,1996,1266,1253 +2003-01,1997,1155,1141 +2004-01,1998,1243,1225 +2005-01,1999,1550,1532 +2006-01,2000,1066,1054 +2001-04,1995,1091,1084 +2002-04,1996,1263,1256 +2003-04,1997,1167,1147 +2004-04,1998,1259,1237 +2005-04,1999,1561,1547 +2001-07,1995,1090,1086 +2002-07,1996,1278,1258 +2003-07,1997,1177,1163 +2004-07,1998,1281,1265 +2005-07,1999,1566,1559 +2001-10,1995,1094,1089 +2002-10,1996,1273,1261 +2003-10,1997,1194,1184 +2004-10,1998,1284,1274 +2005-10,1999,1585,1565 +2002-01,1995,1094,1091 +2003-01,1996,1269,1266 +2004-01,1997,1196,1185 +2005-01,1998,1286,1275 +2006-01,1999,1583,1573 +2002-04,1995,1095,1094 +2003-04,1996,1279,1267 +2004-04,1997,1198,1186 +2005-04,1998,1289,1278 +2002-07,1995,1098,1095 +2003-07,1996,1281,1268 +2004-07,1997,1197,1189 +2005-07,1998,1292,1286 +2002-10,1995,1096,1096 +2003-10,1996,1299,1288 +2004-10,1997,1198,1192 +2005-10,1998,1297,1288 +2003-01,1995,1097,1097 +2004-01,1996,1296,1288 +2005-01,1997,1201,1194 +2006-01,1998,1298,1293 +2003-04,1995,1097,1098 +2004-04,1996,1302,1289 +2005-04,1997,1201,1195 +2003-07,1995,1101,1098 +2004-07,1996,1303,1291 +2005-07,1997,1200,1197 +2003-10,1995,1098,1099 +2004-10,1996,1300,1296 +2005-10,1997,1203,1197 +2004-01,1995,1099,1099 +2005-01,1996,1300,1296 +2006-01,1997,1200,1198 +2004-04,1995,1103,1100 +2005-04,1996,1302,1297 +2004-07,1995,1100,1098 +2005-07,1996,1300,1298 +2004-10,1995,1098,1098 +2005-10,1996,1303,1298 +2005-01,1995,1100,1098 +2006-01,1996,1300,1298 +2005-04,1995,1100,1099 +2005-07,1995,1098,1099 +2005-10,1995,1101,1100 +2006-01,1995,1100,1100 From e11d092378f34eee97f8ee8a2d3f147c928948a5 Mon Sep 17 00:00:00 2001 From: Gene Dan Date: Sat, 6 Jun 2026 21:47:03 -0500 Subject: [PATCH 11/83] TST: Add unit tests to cover missing lines in base.py. --- chainladder/core/tests/test_triangle.py | 165 ++++++++++++++++++++++++ 1 file changed, 165 insertions(+) diff --git a/chainladder/core/tests/test_triangle.py b/chainladder/core/tests/test_triangle.py index 504f341f..c8c1660a 100644 --- a/chainladder/core/tests/test_triangle.py +++ b/chainladder/core/tests/test_triangle.py @@ -1061,6 +1061,171 @@ def test_xs(clrd): 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: + """ + Supply a ndarray to Triangle.get_array_module(), which should be np (numpy). + + Parameters + ---------- + raa: Triangle + The raa sample data set. + + Returns + ------- + None + + """ + assert raa.get_array_module(np.array([1.0])) is np + + +def test_get_array_module_invalid_backend_raises(raa: Triangle) -> None: + """ + Simulate typo in setting array backend, should raise an exception. + + Parameters + ---------- + raa: Triangle + The raa sample data set. + + Returns + ------- + None + + """ + tri = raa.copy() + tri.array_backend = 'mumpy' + with pytest.raises(Exception, match="Array backend is invalid or not properly set"): + tri.get_array_module() + + +def test_set_development_no_development_column() -> None: + """ + Initialize a triangle without a development dimension specified. Development will default to being + a single period set to the latest origin period. + + Returns + ------- + None + + """ + df = pd.DataFrame( + { + 'origin': [1995, 1996, 1997], + 'reported': [1.0, 2.0, 3.0] + } + ) + tri = cl.Triangle( + data=df, + origin='origin', + columns='reported', + cumulative=True + ) + assert tri.shape[-1] == 1 + assert tri.development[0] == str(tri.origin[-1]) + + +def test_set_development_age_instead_of_date_raises() -> None: + """ + Initialize a triangle with incorrect development periods specified. Should raise a ValueError. + + Returns + ------- + None + + """ + df = pd.DataFrame( + { + 'origin': [1995, 1996], + 'development': [12, 24], + 'reported': [1.0, 2.0] + } + ) + with pytest.raises(ValueError, match="Development lags could not be determined"): + cl.Triangle( + data=df, + origin='origin', + development='development', + columns='reported', + cumulative=True + ) + + +def test_input_validation_non_numeric_columns_raises() -> None: + """ + Initialize a triangle with reported losses as an invalid string data type. Should raise a TypeError. + + Returns + ------- + None + + """ + df = pd.DataFrame({ + 'origin': [1995, 1996], + 'dev': [1995, 1996], + 'reported': ['1000', '2000'], + }) + with pytest.raises(TypeError, match="column attribute must be numeric"): + cl.Triangle( + data=df, + origin='origin', + development='development', + columns='reported', + cumulative=True + ) + + +def test_input_validation_duplicate_columns_raises() -> None: + """ + Initialize a triangle with duplicate column names. Raise an AttributeError. + + Returns + ------- + None + + """ + df = pd.DataFrame( + [[1995, 1995, 1.0, 2.0], [1996, 1996, 3.0, 4.0]], + columns=pd.Index(['origin', 'development', 'reported', 'reported']) + ) + with pytest.raises(AttributeError, match="Columns are required to have unique names"): + cl.Triangle( + data=df, + origin='origin', + development='development', + columns='reported', + cumulative=True + ) + + +def test_get_axis_value(raa) -> None: + """ + Extract the Triangle axes using integer indices and string specifications. + + Parameters + ---------- + raa: Triangle + The raa sample data set. + + Returns + ------- + None + + """ + assert raa._get_axis_value(0).equals(raa.index) + assert raa._get_axis_value("index").equals(raa.index) + assert raa._get_axis_value(1).equals(raa.columns) + assert raa._get_axis_value("columns").equals(raa.columns) + assert raa._get_axis_value(2).equals(raa.origin) + assert raa._get_axis_value("origin").equals(raa.origin) + assert raa._get_axis_value(3).equals(raa.development) + assert raa._get_axis_value("development").equals(raa.development) + # Negative index support in TrianglePandas().get_axis() + assert raa._get_axis_value(-4).equals(raa.index) + assert raa._get_axis_value(-1).equals(raa.development) + with pytest.raises(ValueError): + raa._get_axis_value("dev") + + def test_to_datetime_uninferrable_format_raises() -> None: """ Initialize a triangle with incorrect date format on the origin axis. Should raise a ValueError. From ec03ef2588acc9c68f24586461eebe7594770e0a Mon Sep 17 00:00:00 2001 From: Gene Dan Date: Sat, 6 Jun 2026 22:01:03 -0500 Subject: [PATCH 12/83] FIX: Fix typo in test, but in column type checking. --- chainladder/core/base.py | 2 +- chainladder/core/tests/test_triangle.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/chainladder/core/base.py b/chainladder/core/base.py index c7e62b05..8c68522e 100644 --- a/chainladder/core/base.py +++ b/chainladder/core/base.py @@ -105,7 +105,7 @@ def str_to_list(arg: str | list) -> None | list: columns = str_to_list(columns) origin = str_to_list(origin) development = str_to_list(development) - if "object" in data[columns].dtypes: + if (data[columns].dtypes == "object").any(): raise TypeError("column attribute must be numeric.") if data[columns].shape[1] != len(columns): raise AttributeError("Columns are required to have unique names") diff --git a/chainladder/core/tests/test_triangle.py b/chainladder/core/tests/test_triangle.py index c8c1660a..7d53da16 100644 --- a/chainladder/core/tests/test_triangle.py +++ b/chainladder/core/tests/test_triangle.py @@ -1161,7 +1161,7 @@ def test_input_validation_non_numeric_columns_raises() -> None: """ df = pd.DataFrame({ 'origin': [1995, 1996], - 'dev': [1995, 1996], + 'development': [1995, 1996], 'reported': ['1000', '2000'], }) with pytest.raises(TypeError, match="column attribute must be numeric"): From 4a16b5a764725a49f2fa280d9b2d07bcf84f3c46 Mon Sep 17 00:00:00 2001 From: Gene Dan Date: Sat, 6 Jun 2026 22:08:24 -0500 Subject: [PATCH 13/83] FIX: Fix for pandas 3. --- chainladder/core/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chainladder/core/base.py b/chainladder/core/base.py index 8c68522e..2770e29c 100644 --- a/chainladder/core/base.py +++ b/chainladder/core/base.py @@ -105,7 +105,7 @@ def str_to_list(arg: str | list) -> None | list: columns = str_to_list(columns) origin = str_to_list(origin) development = str_to_list(development) - if (data[columns].dtypes == "object").any(): + if not all(pd.api.types.is_numeric_dtype(data[col]) for col in columns): raise TypeError("column attribute must be numeric.") if data[columns].shape[1] != len(columns): raise AttributeError("Columns are required to have unique names") From 03165968de39ffc79620191b483685f547dd87dd Mon Sep 17 00:00:00 2001 From: Gene Dan Date: Sat, 6 Jun 2026 22:09:10 -0500 Subject: [PATCH 14/83] FIX: Fix for pandas 3. --- chainladder/core/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chainladder/core/base.py b/chainladder/core/base.py index 2770e29c..03a791e7 100644 --- a/chainladder/core/base.py +++ b/chainladder/core/base.py @@ -105,7 +105,7 @@ def str_to_list(arg: str | list) -> None | list: columns = str_to_list(columns) origin = str_to_list(origin) development = str_to_list(development) - if not all(pd.api.types.is_numeric_dtype(data[col]) for col in columns): + if not all(pd.api.types.is_numeric_dtype(dt) for dt in data[columns].dtypes): raise TypeError("column attribute must be numeric.") if data[columns].shape[1] != len(columns): raise AttributeError("Columns are required to have unique names") From f1c882e8fc7a63d0e15afe626fc513f64bbfaa3e Mon Sep 17 00:00:00 2001 From: henrydingliu <106109320+henrydingliu@users.noreply.github.com> Date: Thu, 4 Jun 2026 21:58:16 -0700 Subject: [PATCH 15/83] Update friedland_wc_self_insurer.csv --- .../utils/data/friedland_wc_self_insurer.csv | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/chainladder/utils/data/friedland_wc_self_insurer.csv b/chainladder/utils/data/friedland_wc_self_insurer.csv index 01f8c86d..752eb1c8 100644 --- a/chainladder/utils/data/friedland_wc_self_insurer.csv +++ b/chainladder/utils/data/friedland_wc_self_insurer.csv @@ -1,4 +1,4 @@ -Accident Year,Calendar Year,Closed Claim Counts,Reported Claim Counts,Paid Claims,Paid Severities,Reported Claims,Reported Severities +Accident Year,Calendar Year,Closed Claim Counts,Reported Claim Counts,Paid Claims,Paid Severities,Reported Claims,Reported Severities,Payroll 2001,2001,789,1235,1318000,1670,3200000,2591 2001,2002,1196,1321,2842000,2377,4300000,3255 2001,2003,1255,1342,3750000,2989,4900000,3651 @@ -6,32 +6,32 @@ Accident Year,Calendar Year,Closed Claim Counts,Reported Claim Counts,Paid Claim 2001,2005,1324,1350,4650000,3511,5300000,3926 2001,2006,1327,1350,4850000,3655,5400000,4000 2001,2007,1332,1350,5050000,3790,5550000,4111 -2001,2008,1343,1350,5200000,3871,5650000,4185 +2001,2008,1343,1350,5200000,3871,5650000,4185,195000 2002,2002,978,1555,1780000,1820,4300000,2765 2002,2003,1506,1660,3817000,2535,5900000,3554 2002,2004,1609,1685,5016000,3117,6600000,3917 2002,2005,1629,1695,5750000,3530,6950000,4100 2002,2006,1669,1700,6100000,3654,7200000,4235 2002,2007,1676,1700,6300000,3759,7400000,4353 -2002,2008,1683,1700,6555000,3895,7500000,4412 +2002,2008,1683,1700,6555000,3895,7500000,4412,260000 2003,2003,1070,1628,1890000,1767,4800000,2948 2003,2004,1557,1740,4184000,2687,6600000,3793 2003,2005,1665,1762,5500000,3303,7400000,4200 2003,2006,1721,1771,6300000,3660,7800000,4404 2003,2007,1738,1775,6800000,3913,8100000,4563 -2003,2008,1748,1775,7100000,4061,8300000,4676 +2003,2008,1748,1775,7100000,4061,8300000,4676,280000 2004,2004,1029,1600,1900000,1847,4900000,3063 2004,2005,1525,1714,4100000,2688,6700000,3909 2004,2006,1618,1740,5560000,3436,7700000,4425 2004,2007,1688,1747,6430000,3810,8150000,4665 -2004,2008,1717,1750,6950000,4048,8600000,4914 +2004,2008,1717,1750,6950000,4048,8600000,4914,280000 2005,2005,974,1510,1960000,2012,5200000,3444 2005,2006,1459,1612,4290000,2941,7100000,4404 2005,2007,1532,1639,5688000,3712,7900000,4821 -2005,2008,1597,1647,6570000,4113,8350000,5071 +2005,2008,1597,1647,6570000,4113,8350000,5071,350000 2006,2006,1746,2750,4030000,2308,10100000,3673 2006,2007,2632,2941,8650000,3286,13800000,4692 -2006,2008,2761,2985,11400000,4129,15500000,5193 -2007,2007,1683,2640,4200000,3496,10500000,3962 -2007,2008,2572,2842,9043000,3516,14400000,5067 -2008,2008,1560,2438,4170000,2673,10300000,4225 +2006,2008,2761,2985,11400000,4129,15500000,5193,790000 +2007,2007,1683,2650,4200000,3496,10500000,3962 +2007,2008,2572,2842,9043000,3516,14400000,5067,780000 +2008,2008,1560,2438,4170000,2673,10300000,4225,740000 From a9ca51c1271de9f6c868f7ea33da0403947b5bdd Mon Sep 17 00:00:00 2001 From: henrydingliu <106109320+henrydingliu@users.noreply.github.com> Date: Thu, 4 Jun 2026 22:02:06 -0700 Subject: [PATCH 16/83] Update friedland_auto_freq_sev.csv --- .../utils/data/friedland_auto_freq_sev.csv | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/chainladder/utils/data/friedland_auto_freq_sev.csv b/chainladder/utils/data/friedland_auto_freq_sev.csv index 8e99ef45..54b8c9b8 100644 --- a/chainladder/utils/data/friedland_auto_freq_sev.csv +++ b/chainladder/utils/data/friedland_auto_freq_sev.csv @@ -1,4 +1,4 @@ -Accident Half-Year,Calendar Half-Year,Closed Claim Counts,Reported Claim Counts,Reported Claims,Reported Severity +Accident Half-Year,Calendar Half-Year,Closed Claim Counts,Reported Claim Counts,Reported Claims,Reported Severity,Paid Claims 2003-07,2003-07,2547,3556,14235000,4003 2003-07,2004-01,3262,3314,14960000,4514 2003-07,2004-07,3287,3301,14921000,4520 @@ -8,7 +8,7 @@ Accident Half-Year,Calendar Half-Year,Closed Claim Counts,Reported Claim Counts, 2003-07,2006-07,3292,3293,14860000,4513 2003-07,2007-01,3292,3293,14854000,4511 2003-07,2007-07,3292,3293,14850000,4510 -2003-07,2008-01,3292,3292,14847000,4510 +2003-07,2008-01,3292,3292,14847000,4510,14846000 2004-01,2004-01,2791,3492,14548000,4166 2004-01,2004-07,3217,3262,14674000,4498 2004-01,2005-01,3240,3250,14643000,4506 @@ -17,7 +17,7 @@ Accident Half-Year,Calendar Half-Year,Closed Claim Counts,Reported Claim Counts, 2004-01,2006-07,3243,3245,14610000,4502 2004-01,2007-01,3243,3245,14610000,4502 2004-01,2007-07,3243,3244,14611000,4504 -2004-01,2008-01,3242,3243,14617000,4507 +2004-01,2008-01,3242,3243,14617000,4507,14614000 2004-07,2004-07,2099,2980,12129000,4070 2004-07,2005-01,2677,2712,12576000,4637 2004-07,2005-07,2695,2704,12541000,4638 @@ -25,32 +25,32 @@ Accident Half-Year,Calendar Half-Year,Closed Claim Counts,Reported Claim Counts, 2004-07,2006-07,2697,2700,12523000,4683 2004-07,2007-01,2698,2700,12523000,4638 2004-07,2007-07,2698,2699,12510000,4635 -2004-07,2008-01,2698,2699,12502000,4632 +2004-07,2008-01,2698,2699,12502000,4632,12502000 2005-01,2005-01,2370,2896,11980000,4137 2005-01,2005-07,2735,2768,11921000,4307 2005-01,2006-01,2751,2761,11882000,4304 2005-01,2006-07,2754,2758,11862000,4301 2005-01,2007-01,2755,2758,11854000,4298 2005-01,2007-07,2755,2758,11844000,4294 -2005-01,2008-01,2756,2757,11841000,4295 +2005-01,2008-01,2756,2757,11841000,4295,11840000 2005-07,2005-07,1966,2814,11283000,4010 2005-07,2006-01,2609,2650,11843000,4469 2005-07,2006-07,2630,2640,11805000,4472 2005-07,2007-01,2634,2639,11789000,4467 2005-07,2007-07,2634,2638,11772000,4462 -2005-07,2008-01,2634,2636,11770000,4465 +2005-07,2008-01,2634,2636,11770000,4465,11765000 2006-01,2006-01,2261,2808,11947000,4254 2006-01,2006-07,2671,2712,11856000,4372 2006-01,2007-01,2694,2704,11820000,4371 2006-01,2007-07,2696,2701,11772000,4359 -2006-01,2008-01,2697,2700,11760000,4356 +2006-01,2008-01,2697,2700,11760000,4356,11755000 2006-07,2006-07,1949,2799,12503000,4467 2006-07,2007-01,2637,2675,12762000,4771 2006-07,2007-07,2659,2670,12706000,4759 -2006-07,2008-01,2662,2668,12697000,4759 +2006-07,2008-01,2662,2668,12697000,4759,12679000 2007-01,2007-01,2059,2578,11662000,4524 2007-01,2007-07,2496,2533,11523000,4549 -2007-01,2008-01,2520,2529,11492000,4544 +2007-01,2008-01,2520,2529,11492000,4544,11406000 2007-07,2007-07,2083,2791,12647000,4531 -2007-07,2008-01,2732,2778,12854000,4627 -2008-01,2008-01,2533,3139,14071000,4483 +2007-07,2008-01,2732,2778,12854000,4627,12648000 +2008-01,2008-01,2533,3139,14071000,4483,11833000 From 279e69eb122de3f2cb048dfe09e2464c00802f24 Mon Sep 17 00:00:00 2001 From: henrydingliu <106109320+henrydingliu@users.noreply.github.com> Date: Thu, 4 Jun 2026 22:04:52 -0700 Subject: [PATCH 17/83] Update friedland_wc_self_insurer.csv --- .../utils/data/friedland_wc_self_insurer.csv | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/chainladder/utils/data/friedland_wc_self_insurer.csv b/chainladder/utils/data/friedland_wc_self_insurer.csv index 752eb1c8..6671bda7 100644 --- a/chainladder/utils/data/friedland_wc_self_insurer.csv +++ b/chainladder/utils/data/friedland_wc_self_insurer.csv @@ -1,37 +1,37 @@ Accident Year,Calendar Year,Closed Claim Counts,Reported Claim Counts,Paid Claims,Paid Severities,Reported Claims,Reported Severities,Payroll -2001,2001,789,1235,1318000,1670,3200000,2591 -2001,2002,1196,1321,2842000,2377,4300000,3255 -2001,2003,1255,1342,3750000,2989,4900000,3651 -2001,2004,1310,1349,4300000,3283,5200000,3855 -2001,2005,1324,1350,4650000,3511,5300000,3926 -2001,2006,1327,1350,4850000,3655,5400000,4000 -2001,2007,1332,1350,5050000,3790,5550000,4111 +2001,2001,789,1235,1318000,1670,3200000,2591, +2001,2002,1196,1321,2842000,2377,4300000,3255, +2001,2003,1255,1342,3750000,2989,4900000,3651, +2001,2004,1310,1349,4300000,3283,5200000,3855, +2001,2005,1324,1350,4650000,3511,5300000,3926, +2001,2006,1327,1350,4850000,3655,5400000,4000, +2001,2007,1332,1350,5050000,3790,5550000,4111, 2001,2008,1343,1350,5200000,3871,5650000,4185,195000 -2002,2002,978,1555,1780000,1820,4300000,2765 -2002,2003,1506,1660,3817000,2535,5900000,3554 -2002,2004,1609,1685,5016000,3117,6600000,3917 -2002,2005,1629,1695,5750000,3530,6950000,4100 -2002,2006,1669,1700,6100000,3654,7200000,4235 -2002,2007,1676,1700,6300000,3759,7400000,4353 +2002,2002,978,1555,1780000,1820,4300000,2765, +2002,2003,1506,1660,3817000,2535,5900000,3554, +2002,2004,1609,1685,5016000,3117,6600000,3917, +2002,2005,1629,1695,5750000,3530,6950000,4100, +2002,2006,1669,1700,6100000,3654,7200000,4235, +2002,2007,1676,1700,6300000,3759,7400000,4353, 2002,2008,1683,1700,6555000,3895,7500000,4412,260000 -2003,2003,1070,1628,1890000,1767,4800000,2948 -2003,2004,1557,1740,4184000,2687,6600000,3793 -2003,2005,1665,1762,5500000,3303,7400000,4200 -2003,2006,1721,1771,6300000,3660,7800000,4404 -2003,2007,1738,1775,6800000,3913,8100000,4563 +2003,2003,1070,1628,1890000,1767,4800000,2948, +2003,2004,1557,1740,4184000,2687,6600000,3793, +2003,2005,1665,1762,5500000,3303,7400000,4200, +2003,2006,1721,1771,6300000,3660,7800000,4404, +2003,2007,1738,1775,6800000,3913,8100000,4563, 2003,2008,1748,1775,7100000,4061,8300000,4676,280000 -2004,2004,1029,1600,1900000,1847,4900000,3063 -2004,2005,1525,1714,4100000,2688,6700000,3909 -2004,2006,1618,1740,5560000,3436,7700000,4425 -2004,2007,1688,1747,6430000,3810,8150000,4665 +2004,2004,1029,1600,1900000,1847,4900000,3063, +2004,2005,1525,1714,4100000,2688,6700000,3909, +2004,2006,1618,1740,5560000,3436,7700000,4425, +2004,2007,1688,1747,6430000,3810,8150000,4665, 2004,2008,1717,1750,6950000,4048,8600000,4914,280000 -2005,2005,974,1510,1960000,2012,5200000,3444 -2005,2006,1459,1612,4290000,2941,7100000,4404 -2005,2007,1532,1639,5688000,3712,7900000,4821 +2005,2005,974,1510,1960000,2012,5200000,3444, +2005,2006,1459,1612,4290000,2941,7100000,4404, +2005,2007,1532,1639,5688000,3712,7900000,4821, 2005,2008,1597,1647,6570000,4113,8350000,5071,350000 -2006,2006,1746,2750,4030000,2308,10100000,3673 -2006,2007,2632,2941,8650000,3286,13800000,4692 +2006,2006,1746,2750,4030000,2308,10100000,3673, +2006,2007,2632,2941,8650000,3286,13800000,4692, 2006,2008,2761,2985,11400000,4129,15500000,5193,790000 -2007,2007,1683,2650,4200000,3496,10500000,3962 +2007,2007,1683,2650,4200000,3496,10500000,3962, 2007,2008,2572,2842,9043000,3516,14400000,5067,780000 2008,2008,1560,2438,4170000,2673,10300000,4225,740000 From b754e74201fb95010cf3fe3f5c925a3e98a90131 Mon Sep 17 00:00:00 2001 From: henrydingliu <106109320+henrydingliu@users.noreply.github.com> Date: Thu, 4 Jun 2026 22:05:37 -0700 Subject: [PATCH 18/83] Update friedland_auto_freq_sev.csv --- .../utils/data/friedland_auto_freq_sev.csv | 90 +++++++++---------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/chainladder/utils/data/friedland_auto_freq_sev.csv b/chainladder/utils/data/friedland_auto_freq_sev.csv index 54b8c9b8..bd91893c 100644 --- a/chainladder/utils/data/friedland_auto_freq_sev.csv +++ b/chainladder/utils/data/friedland_auto_freq_sev.csv @@ -1,56 +1,56 @@ Accident Half-Year,Calendar Half-Year,Closed Claim Counts,Reported Claim Counts,Reported Claims,Reported Severity,Paid Claims -2003-07,2003-07,2547,3556,14235000,4003 -2003-07,2004-01,3262,3314,14960000,4514 -2003-07,2004-07,3287,3301,14921000,4520 -2003-07,2005-01,3291,3299,14911000,4520 -2003-07,2005-07,3292,3295,14926000,4530 -2003-07,2006-01,3292,3294,14864000,4512 -2003-07,2006-07,3292,3293,14860000,4513 -2003-07,2007-01,3292,3293,14854000,4511 -2003-07,2007-07,3292,3293,14850000,4510 +2003-07,2003-07,2547,3556,14235000,4003, +2003-07,2004-01,3262,3314,14960000,4514, +2003-07,2004-07,3287,3301,14921000,4520, +2003-07,2005-01,3291,3299,14911000,4520, +2003-07,2005-07,3292,3295,14926000,4530, +2003-07,2006-01,3292,3294,14864000,4512, +2003-07,2006-07,3292,3293,14860000,4513, +2003-07,2007-01,3292,3293,14854000,4511, +2003-07,2007-07,3292,3293,14850000,4510, 2003-07,2008-01,3292,3292,14847000,4510,14846000 -2004-01,2004-01,2791,3492,14548000,4166 -2004-01,2004-07,3217,3262,14674000,4498 -2004-01,2005-01,3240,3250,14643000,4506 -2004-01,2005-07,3242,3247,14626000,4505 -2004-01,2006-01,3243,3247,14621000,4503 -2004-01,2006-07,3243,3245,14610000,4502 -2004-01,2007-01,3243,3245,14610000,4502 -2004-01,2007-07,3243,3244,14611000,4504 +2004-01,2004-01,2791,3492,14548000,4166, +2004-01,2004-07,3217,3262,14674000,4498, +2004-01,2005-01,3240,3250,14643000,4506, +2004-01,2005-07,3242,3247,14626000,4505, +2004-01,2006-01,3243,3247,14621000,4503, +2004-01,2006-07,3243,3245,14610000,4502, +2004-01,2007-01,3243,3245,14610000,4502, +2004-01,2007-07,3243,3244,14611000,4504, 2004-01,2008-01,3242,3243,14617000,4507,14614000 -2004-07,2004-07,2099,2980,12129000,4070 -2004-07,2005-01,2677,2712,12576000,4637 -2004-07,2005-07,2695,2704,12541000,4638 -2004-07,2006-01,2697,2702,12531000,4638 -2004-07,2006-07,2697,2700,12523000,4683 -2004-07,2007-01,2698,2700,12523000,4638 -2004-07,2007-07,2698,2699,12510000,4635 +2004-07,2004-07,2099,2980,12129000,4070, +2004-07,2005-01,2677,2712,12576000,4637, +2004-07,2005-07,2695,2704,12541000,4638, +2004-07,2006-01,2697,2702,12531000,4638, +2004-07,2006-07,2697,2700,12523000,4683, +2004-07,2007-01,2698,2700,12523000,4638, +2004-07,2007-07,2698,2699,12510000,4635, 2004-07,2008-01,2698,2699,12502000,4632,12502000 -2005-01,2005-01,2370,2896,11980000,4137 -2005-01,2005-07,2735,2768,11921000,4307 -2005-01,2006-01,2751,2761,11882000,4304 -2005-01,2006-07,2754,2758,11862000,4301 -2005-01,2007-01,2755,2758,11854000,4298 -2005-01,2007-07,2755,2758,11844000,4294 +2005-01,2005-01,2370,2896,11980000,4137, +2005-01,2005-07,2735,2768,11921000,4307, +2005-01,2006-01,2751,2761,11882000,4304, +2005-01,2006-07,2754,2758,11862000,4301, +2005-01,2007-01,2755,2758,11854000,4298, +2005-01,2007-07,2755,2758,11844000,4294, 2005-01,2008-01,2756,2757,11841000,4295,11840000 -2005-07,2005-07,1966,2814,11283000,4010 -2005-07,2006-01,2609,2650,11843000,4469 -2005-07,2006-07,2630,2640,11805000,4472 -2005-07,2007-01,2634,2639,11789000,4467 -2005-07,2007-07,2634,2638,11772000,4462 +2005-07,2005-07,1966,2814,11283000,4010, +2005-07,2006-01,2609,2650,11843000,4469, +2005-07,2006-07,2630,2640,11805000,4472, +2005-07,2007-01,2634,2639,11789000,4467, +2005-07,2007-07,2634,2638,11772000,4462, 2005-07,2008-01,2634,2636,11770000,4465,11765000 -2006-01,2006-01,2261,2808,11947000,4254 -2006-01,2006-07,2671,2712,11856000,4372 -2006-01,2007-01,2694,2704,11820000,4371 -2006-01,2007-07,2696,2701,11772000,4359 +2006-01,2006-01,2261,2808,11947000,4254, +2006-01,2006-07,2671,2712,11856000,4372, +2006-01,2007-01,2694,2704,11820000,4371, +2006-01,2007-07,2696,2701,11772000,4359, 2006-01,2008-01,2697,2700,11760000,4356,11755000 -2006-07,2006-07,1949,2799,12503000,4467 -2006-07,2007-01,2637,2675,12762000,4771 -2006-07,2007-07,2659,2670,12706000,4759 +2006-07,2006-07,1949,2799,12503000,4467, +2006-07,2007-01,2637,2675,12762000,4771, +2006-07,2007-07,2659,2670,12706000,4759, 2006-07,2008-01,2662,2668,12697000,4759,12679000 -2007-01,2007-01,2059,2578,11662000,4524 -2007-01,2007-07,2496,2533,11523000,4549 +2007-01,2007-01,2059,2578,11662000,4524, +2007-01,2007-07,2496,2533,11523000,4549, 2007-01,2008-01,2520,2529,11492000,4544,11406000 -2007-07,2007-07,2083,2791,12647000,4531 +2007-07,2007-07,2083,2791,12647000,4531, 2007-07,2008-01,2732,2778,12854000,4627,12648000 2008-01,2008-01,2533,3139,14071000,4483,11833000 From 4818aaa539f2a311ab600ced9372deeef4d49a50 Mon Sep 17 00:00:00 2001 From: henrydingliu <106109320+henrydingliu@users.noreply.github.com> Date: Thu, 4 Jun 2026 22:06:40 -0700 Subject: [PATCH 19/83] Update _manifest.py --- chainladder/utils/data/_manifest.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/chainladder/utils/data/_manifest.py b/chainladder/utils/data/_manifest.py index 21589a8e..ae74914e 100644 --- a/chainladder/utils/data/_manifest.py +++ b/chainladder/utils/data/_manifest.py @@ -104,6 +104,7 @@ "Reported Claim Counts", "Reported Claims", "Reported Severity", + "Paid Claims", ], "cumulative": True, }, @@ -254,6 +255,7 @@ "Paid Severities", "Reported Claims", "Reported Severities", + "Payroll", ], "cumulative": True, }, From dfe2d296927225f7856b6a11d71d1a92be5bb4b7 Mon Sep 17 00:00:00 2001 From: henrydingliu <106109320+henrydingliu@users.noreply.github.com> Date: Thu, 4 Jun 2026 22:17:25 -0700 Subject: [PATCH 20/83] Delete chainladder/utils/data/friedland_xyz_freq_sev.csv --- .../utils/data/friedland_xyz_freq_sev.csv | 67 ------------------- 1 file changed, 67 deletions(-) delete mode 100644 chainladder/utils/data/friedland_xyz_freq_sev.csv diff --git a/chainladder/utils/data/friedland_xyz_freq_sev.csv b/chainladder/utils/data/friedland_xyz_freq_sev.csv deleted file mode 100644 index 5906f597..00000000 --- a/chainladder/utils/data/friedland_xyz_freq_sev.csv +++ /dev/null @@ -1,67 +0,0 @@ -Accident Year,Calendar Year,Closed Claim Counts,Reported Claim Counts,Reported Claims,Reported Severities -1998,1998,,,, -1998,1999,,,, -1998,2000,,,1171000, -1998,2001,510,634,12380000,19526 -1998,2002,547,635,13216000,20813 -1998,2003,575,635,14067000,22152 -1998,2004,598,637,14688000,23058 -1998,2005,612,637,16366000,25692 -1998,2006,620,637,16163000,25374 -1998,2007,635,637,15835000,24859 -1998,2008,637,637,15822000,24839 -1999,1999,,,, -1999,2000,,,13255000, -1999,2001,686,1026,16405000,15989 -1999,2002,819,1039,19639000,18902 -1999,2003,910,1047,22473000,21464 -1999,2004,980,1050,23764000,22632 -1999,2005,1007,1053,25094000,23831 -1999,2006,1036,1047,24795000,23682 -1999,2007,1039,1047,25071000,23946 -1999,2008,1044,1047,25107000,23980 -2000,2000,,,15676000, -2000,2001,650,1354,18749000,13847 -2000,2002,932,1397,21900000,15676 -2000,2003,1095,1411,27144000,19237 -2000,2004,1216,1410,29488000,20914 -2000,2005,1292,1408,34458000,24473 -2000,2006,1367,1408,36949000,26242 -2000,2007,1391,1408,37505000,26637 -2000,2008,1402,1408,37246000,26453 -2001,2001,304,1305,11827000,9063 -2001,2002,681,1421,16004000,11262 -2001,2003,936,1449,21022000,14508 -2001,2004,1092,1458,26578000,18229 -2001,2005,1225,1458,34205000,23460 -2001,2006,1357,1455,37136000,25523 -2001,2007,1432,1455,38541000,26489 -2001,2008,1446,1455,38798000,26665 -2002,2002,203,1342,12811000,9546 -2002,2003,607,1514,20370000,13455 -2002,2004,841,1548,26656000,17219 -2002,2005,1089,1557,37667000,24192 -2002,2006,1327,1549,44414000,28673 -2002,2007,1464,1552,48701000,31379 -2002,2008,1523,1554,48169000,30997 -2003,2003,181,1373,9651000,7029 -2003,2004,614,1616,16995000,10517 -2003,2005,941,1630,30354000,18622 -2003,2006,1263,1626,40594000,24966 -2003,2007,1507,1629,44231000,27152 -2003,2008,1568,1629,44373000,27239 -2004,2004,235,1932,16995000,8796 -2004,2005,848,2168,40180000,18533 -2004,2006,1442,2234,58866000,26350 -2004,2007,1852,2249,71707000,31884 -2004,2008,2029,2258,70288000,31129 -2005,2005,295,2067,28674000,13872 -2005,2006,1119,2293,47432000,20686 -2005,2007,1664,2367,70340000,29717 -2005,2008,1946,2390,70655000,29563 -2006,2006,307,1473,27066000,18375 -2006,2007,906,1645,46783000,28440 -2006,2008,1201,1657,48804000,29453 -2007,2007,329,1192,19477000,16340 -2007,2008,791,1264,31732000,25104 -2008,2008,276,1036,18632000,17985 From c734a4e914ac92807dbcb72313d8c5490872ee1e Mon Sep 17 00:00:00 2001 From: henrydingliu <106109320+henrydingliu@users.noreply.github.com> Date: Thu, 4 Jun 2026 22:18:57 -0700 Subject: [PATCH 21/83] Update friedland_xyz_auto_bi.csv --- .../utils/data/friedland_xyz_auto_bi.csv | 134 +++++++++--------- 1 file changed, 67 insertions(+), 67 deletions(-) diff --git a/chainladder/utils/data/friedland_xyz_auto_bi.csv b/chainladder/utils/data/friedland_xyz_auto_bi.csv index 9004b3a8..773f0cf6 100644 --- a/chainladder/utils/data/friedland_xyz_auto_bi.csv +++ b/chainladder/utils/data/friedland_xyz_auto_bi.csv @@ -1,67 +1,67 @@ -Accident Year,Calendar Year,Paid Claims,Reported Claims -1998,1998,, -1998,1999,, -1998,2000,6309,11171 -1998,2001,8521,12380 -1998,2002,10082,13216 -1998,2003,11620,14067 -1998,2004,13242,14688 -1998,2005,14419,16366 -1998,2006,15311,16163 -1998,2007,15764,15835 -1998,2008,15822,15822 -1999,1999,, -1999,2000,4666,13255 -1999,2001,9861,16405 -1999,2002,13971,19639 -1999,2003,18127,22473 -1999,2004,22032,23764 -1999,2005,23511,25094 -1999,2006,24146,24795 -1999,2007,24592,25071 -1999,2008,24817,25107 -2000,2000,1302,15676 -2000,2001,6513,18749 -2000,2002,12139,21900 -2000,2003,17828,27144 -2000,2004,24030,29488 -2000,2005,28853,34458 -2000,2006,33222,36949 -2000,2007,35902,37505 -2000,2008,36782,37246 -2001,2001,1539,11827 -2001,2002,5952,16004 -2001,2003,12319,21022 -2001,2004,18609,26578 -2001,2005,24387,34205 -2001,2006,31090,37136 -2001,2007,37070,38541 -2001,2008,38519,38798 -2002,2002,2318,12811 -2002,2003,7932,20370 -2002,2004,13822,26656 -2002,2005,22095,37667 -2002,2006,31945,44414 -2002,2007,40629,48701 -2002,2008,44437,48169 -2003,2003,1743,9651 -2003,2004,6240,16995 -2003,2005,12683,30354 -2003,2006,22892,40594 -2003,2007,34505,44231 -2003,2008,39320,44373 -2004,2004,2221,16995 -2004,2005,9898,40180 -2004,2006,25950,58866 -2004,2007,43439,71707 -2004,2008,52811,70288 -2005,2005,3043,28674 -2005,2006,12219,47432 -2005,2007,27073,70340 -2005,2008,40026,70655 -2006,2006,3531,27066 -2006,2007,11778,46783 -2006,2008,22819,48804 -2007,2007,3529,19477 -2007,2008,11865,31732 -2008,2008,3409,18632 +Accident Year,Calendar Year,Paid Claims,Reported Claims,Closed Claim Counts,Reported Claim Counts,Case Outstanding,Reported Severities +1998,1998,,,,,, +1998,1999,,,,,, +1998,2000,6309,11171,,,4861, +1998,2001,8521,12380,510,634,3859,19526 +1998,2002,10082,13216,547,635,3134,20813 +1998,2003,11620,14067,575,635,2447,22152 +1998,2004,13242,14688,598,637,1446,23058 +1998,2005,14419,16366,612,637,1947,25692 +1998,2006,15311,16163,620,637,853,25374 +1998,2007,15764,15835,635,637,71,24859 +1998,2008,15822,15822,637,637,0,24839 +1999,1999,,,,,, +1999,2000,4666,13255,,,8589, +1999,2001,9861,16405,686,1026,6544,15989 +1999,2002,13971,19639,819,1039,5668,18902 +1999,2003,18127,22473,910,1047,4347,21464 +1999,2004,22032,23764,980,1050,1732,22632 +1999,2005,23511,25094,1007,1053,1583,23831 +1999,2006,24146,24795,1036,1047,649,23682 +1999,2007,24592,25071,1039,1047,479,23946 +1999,2008,24817,25107,1044,1047,290,23980 +2000,2000,1302,15676,,,14374, +2000,2001,6513,18749,650,1354,12237,13847 +2000,2002,12139,21900,932,1397,9760,15676 +2000,2003,17828,27144,1095,1411,9316,19237 +2000,2004,24030,29488,1216,1410,5458,20914 +2000,2005,28853,34458,1292,1408,5605,24473 +2000,2006,33222,36949,1367,1408,3727,26242 +2000,2007,35902,37505,1391,1408,1603,26637 +2000,2008,36782,37246,1402,1408,465,26453 +2001,2001,1539,11827,304,1305,10288,9063 +2001,2002,5952,16004,681,1421,10052,11262 +2001,2003,12319,21022,936,1449,8703,14508 +2001,2004,18609,26578,1092,1458,7969,18229 +2001,2005,24387,34205,1225,1458,9818,23460 +2001,2006,31090,37136,1357,1455,6046,25523 +2001,2007,37070,38541,1432,1455,1471,26489 +2001,2008,38519,38798,1446,1455,278,26665 +2002,2002,2318,12811,203,1342,10494,9546 +2002,2003,7932,20370,607,1514,12439,13455 +2002,2004,13822,26656,841,1548,12833,17219 +2002,2005,22095,37667,1089,1557,15572,24192 +2002,2006,31945,44414,1327,1549,12469,28673 +2002,2007,40629,48701,1464,1552,8072,31379 +2002,2008,44437,48169,1523,1554,3731,30997 +2003,2003,1743,9651,181,1373,7908,7029 +2003,2004,6240,16995,614,1616,10755,10517 +2003,2005,12683,30354,941,1630,17671,18622 +2003,2006,22892,40594,1263,1626,17702,24966 +2003,2007,34505,44231,1507,1629,9726,27152 +2003,2008,39320,44373,1568,1629,5052,27239 +2004,2004,2221,16995,235,1932,14774,8796 +2004,2005,9898,40180,848,2168,30281,18533 +2004,2006,25950,58866,1442,2234,32916,26350 +2004,2007,43439,71707,1852,2249,28268,31884 +2004,2008,52811,70288,2029,2258,17477,31129 +2005,2005,3043,28674,295,2067,25631,13872 +2005,2006,12219,47432,1119,2293,35213,20686 +2005,2007,27073,70340,1664,2367,43268,29717 +2005,2008,40026,70655,1946,2390,30629,29563 +2006,2006,3531,27066,307,1473,23535,18375 +2006,2007,11778,46783,906,1645,35005,28440 +2006,2008,22819,48804,1201,1657,25985,29453 +2007,2007,3529,19477,329,1192,15948,16340 +2007,2008,11865,31732,791,1264,19867,25104 +2008,2008,3409,18632,276,1036,15223,17985 From 55177b093395316c60b831e327c6afbd5d230002 Mon Sep 17 00:00:00 2001 From: henrydingliu <106109320+henrydingliu@users.noreply.github.com> Date: Thu, 4 Jun 2026 22:19:41 -0700 Subject: [PATCH 22/83] Delete chainladder/utils/data/friedland_xyz_case.csv --- chainladder/utils/data/friedland_xyz_case.csv | 67 ------------------- 1 file changed, 67 deletions(-) delete mode 100644 chainladder/utils/data/friedland_xyz_case.csv diff --git a/chainladder/utils/data/friedland_xyz_case.csv b/chainladder/utils/data/friedland_xyz_case.csv deleted file mode 100644 index 475803a5..00000000 --- a/chainladder/utils/data/friedland_xyz_case.csv +++ /dev/null @@ -1,67 +0,0 @@ -Accident Year,Calendar Year,Case Outstanding,Incremental Paid Claims,Incremental Paid to Previous Case,Case to Previous Case,Paid Claims -1998,1998,,,,, -1998,1999,,,,, -1998,2000,4861,6309,,,6309 -1998,2001,3859,2212,0.455,0.794,8521 -1998,2002,3134,1561,0.405,0.812,10082 -1998,2003,2447,1537,0.491,0.781,11619 -1998,2004,1446,1622,0.663,0.591,13241 -1998,2005,1947,1177,0.814,1.346,14418 -1998,2006,853,892,0.458,0.438,15310 -1998,2007,71,453,0.532,0.084,15763 -1998,2008,0,58,0.816,0,15821 -1999,1999,,,,, -1999,2000,8589,4666,,,4666 -1999,2001,6544,5195,0.605,0.762,9861 -1999,2002,5668,4110,0.628,0.866,13971 -1999,2003,4347,4156,0.733,0.767,18127 -1999,2004,1732,3906,0.899,0.398,22033 -1999,2005,1583,1478,0.854,0.914,23511 -1999,2006,649,635,0.401,0.41,24146 -1999,2007,479,446,0.688,0.739,24592 -1999,2008,290,225,0.469,0.605,24817 -2000,2000,14374,1302,,,1302 -2000,2001,12237,5210,0.362,0.851,6512 -2000,2002,9760,5627,0.46,0.798,12139 -2000,2003,9316,5689,0.583,0.954,17828 -2000,2004,5458,6202,0.666,0.586,24030 -2000,2005,5605,4823,0.884,1.027,28853 -2000,2006,3727,4369,0.78,0.665,33222 -2000,2007,1603,2680,0.719,0.43,35902 -2000,2008,465,880,0.549,0.29,36782 -2001,2001,10288,1539,,,1539 -2001,2002,10052,4413,0.429,0.977,5952 -2001,2003,8703,6367,0.633,0.866,12319 -2001,2004,7969,6289,0.723,0.916,18608 -2001,2005,9818,5778,0.725,1.232,24386 -2001,2006,6046,6703,0.683,0.616,31089 -2001,2007,1471,5980,0.989,0.243,37069 -2001,2008,278,1450,0.985,0.189,38519 -2002,2002,10494,2318,,,2318 -2002,2003,12439,5614,0.535,1.185,7932 -2002,2004,12833,5891,0.474,1.032,13823 -2002,2005,15572,8273,0.645,1.213,22096 -2002,2006,12469,9850,0.633,0.801,31946 -2002,2007,8072,8683,0.696,0.647,40629 -2002,2008,3731,3809,0.472,0.462,44438 -2003,2003,7908,1743,,,1743 -2003,2004,10755,4497,0.569,1.36,6240 -2003,2005,17671,6443,0.599,1.643,12683 -2003,2006,17702,10209,0.578,1.002,22892 -2003,2007,9726,11613,0.656,0.549,34505 -2003,2008,5052,4815,0.495,0.519,39320 -2004,2004,14774,2221,,,2221 -2004,2005,30281,7677,0.52,2.05,9898 -2004,2006,32916,16052,0.53,1.087,25950 -2004,2007,28268,17489,0.531,0.859,43439 -2004,2008,17477,9372,0.332,0.618,52811 -2005,2005,25631,3043,,,3043 -2005,2006,35213,9176,0.358,1.374,12219 -2005,2007,43268,14854,0.422,1.229,27073 -2005,2008,30629,12953,0.299,0.708,40026 -2006,2006,23535,3531,,,3531 -2006,2007,35005,8247,0.35,1.487,11778 -2006,2008,25985,11041,0.315,0.742,22819 -2007,2007,15948,3529,,,3529 -2007,2008,19867,8336,0.523,1.246,11865 -2008,2008,15223,3409,,,3409 From 0c1a8b1f2df3a86e14484b17e7f2b343fb73b1bc Mon Sep 17 00:00:00 2001 From: henrydingliu <106109320+henrydingliu@users.noreply.github.com> Date: Thu, 4 Jun 2026 22:21:26 -0700 Subject: [PATCH 23/83] Update _manifest.py --- chainladder/utils/data/_manifest.py | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/chainladder/utils/data/_manifest.py b/chainladder/utils/data/_manifest.py index ae74914e..93cc4a9c 100644 --- a/chainladder/utils/data/_manifest.py +++ b/chainladder/utils/data/_manifest.py @@ -274,14 +274,7 @@ "origin": "Accident Year", "development": "Calendar Year", "index": None, - "columns": ["Paid Claims", "Reported Claims"], - "cumulative": True, - }, - "friedland_xyz_case": { - "origin": "Accident Year", - "development": "Calendar Year", - "index": None, - "columns": ["Case Outstanding", "Paid Claims"], + "columns": ["Paid Claims", "Reported Claims", "Closed Claim Counts", "Reported Claim Counts", "Case Outstanding", "Reported Severities"], "cumulative": True, }, "friedland_xyz_disp": { @@ -291,18 +284,6 @@ "columns": ["Disposal Rate", "Closed Claim Counts", "Paid Claims"], "cumulative": True, }, - "friedland_xyz_freq_sev": { - "origin": "Accident Year", - "development": "Calendar Year", - "index": None, - "columns": [ - "Closed Claim Counts", - "Reported Claim Counts", - "Reported Claims", - "Reported Severities", - ], - "cumulative": True, - }, "genins": { "origin": "origin", "development": "development", From 1003dbde2a99bc1a6a73febac2b1c3357a0fc30a Mon Sep 17 00:00:00 2001 From: henrydingliu <106109320+henrydingliu@users.noreply.github.com> Date: Thu, 4 Jun 2026 22:23:15 -0700 Subject: [PATCH 24/83] Update chapter_6.rst --- docs/friedland/chapter_6.rst | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/docs/friedland/chapter_6.rst b/docs/friedland/chapter_6.rst index e28c3e3d..6af1648a 100644 --- a/docs/friedland/chapter_6.rst +++ b/docs/friedland/chapter_6.rst @@ -211,13 +211,9 @@ Table 7 - Ratio of Paid Claims to Earned Premium Table 8 - Reported Claim Count Development Triangle ####################################################### -The count data is stored under a different name. - .. doctest:: - >>> tri_cnt = cl.load_sample('friedland_xyz_freq_sev') - >>> tri_cnt = tri_cnt[tri_cnt.origin >= '2002'][tri_cnt.development <= 84] - >>> tri_cnt["Reported Claim Counts"] + >>> tri["Reported Claim Counts"] 12 24 36 48 60 72 84 2002 1342.0 1514.0 1548.0 1557.0 1549.0 1552.0 1554.0 2003 1373.0 1616.0 1630.0 1626.0 1629.0 1629.0 NaN @@ -232,7 +228,7 @@ Table 9 - Closed Claim Count Development Triangle .. doctest:: - >>> tri_cnt["Closed Claim Counts"] + >>> tri["Closed Claim Counts"] 12 24 36 48 60 72 84 2002 203.0 607.0 841.0 1089.0 1327.0 1464.0 1523.0 2003 181.0 614.0 941.0 1263.0 1507.0 1568.0 NaN @@ -247,7 +243,7 @@ Table 10 - Ratio of Closed-to-Reported Claim Counts .. doctest:: - >>> (tri_cnt["Closed Claim Counts"] / tri_cnt["Reported Claim Counts"]).round(decimals=3) + >>> (tri["Closed Claim Counts"] / tri["Reported Claim Counts"]).round(decimals=3) 12 24 36 48 60 72 84 2002 0.151 0.401 0.543 0.699 0.857 0.943 0.98 2003 0.132 0.380 0.577 0.777 0.925 0.963 NaN @@ -264,7 +260,7 @@ The losses are stored in the thousands. When calcualting severity, we need to mu .. doctest:: - >>> (tri["Reported Claims"] / tri_cnt["Reported Claim Counts"] * 1000).round(decimals=0) + >>> (tri["Reported Claims"] / tri["Reported Claim Counts"] * 1000).round(decimals=0) 12 24 36 48 60 72 84 2002 9546.0 13454.0 17220.0 24192.0 28673.0 31380.0 30997.0 2003 7029.0 10517.0 18622.0 24966.0 27152.0 27239.0 NaN @@ -281,7 +277,7 @@ Table 13 – Average Paid Claim Development Triangle .. doctest:: - >>> (tri["Paid Claims"] / tri_cnt["Closed Claim Counts"] * 1000).round(decimals=0) + >>> (tri["Paid Claims"] / tri["Closed Claim Counts"] * 1000).round(decimals=0) 12 24 36 48 60 72 84 2002 11419.0 13068.0 16435.0 20289.0 24073.0 27752.0 29177.0 2003 9630.0 10163.0 13478.0 18125.0 22896.0 25077.0 NaN @@ -296,7 +292,7 @@ Table 14 – Average Case Outstanding Development Triangle .. doctest:: - >>> ((tri["Reported Claims"] - tri["Paid Claims"]) / (tri_cnt["Reported Claim Counts"] - tri_cnt["Closed Claim Counts"]) * 1000).round(decimals=0) + >>> ((tri["Reported Claims"] - tri["Paid Claims"]) / (tri["Reported Claim Counts"] - tri["Closed Claim Counts"]) * 1000).round(decimals=0) 12 24 36 48 60 72 84 2002 9212.0 13713.0 18153.0 33274.0 56167.0 91727.0 120387.0 2003 6634.0 10734.0 25647.0 48766.0 79721.0 82836.0 NaN From d88155b6f22af3d9dddab500ef61e626b2db1d1a Mon Sep 17 00:00:00 2001 From: priyam0k <87162535+priyam0k@users.noreply.github.com> Date: Sun, 7 Jun 2026 22:20:42 +0530 Subject: [PATCH 25/83] Deprecate dask array backend (#842) --- chainladder/__init__.py | 55 +++++++---- chainladder/core/base.py | 11 ++- chainladder/core/common.py | 6 +- chainladder/utils/tests/test_utilities.py | 113 ++++++++++++++++++++-- 4 files changed, 154 insertions(+), 31 deletions(-) diff --git a/chainladder/__init__.py b/chainladder/__init__.py index 6c9c2fde..27afcc85 100644 --- a/chainladder/__init__.py +++ b/chainladder/__init__.py @@ -49,6 +49,23 @@ "The parameter 'option' is deprecated and will be removed in a future release. Use 'pat' instead." ) +# Array backends slated for removal, mapped to the issue tracking each one. +# Selecting one of these (via set_option, ARRAY_PRIORITY, or set_backend, or by +# passing a Dask dataframe to the Triangle constructor) emits a +# DeprecationWarning. +_DEPRECATED_BACKENDS: dict[str, str] = { + "cupy": "https://github.com/casact/chainladder-python/issues/843", + "dask": "https://github.com/casact/chainladder-python/issues/842", +} + + +def _deprecated_backend_message(backend: str) -> str: + """Build the deprecation message for a soon-to-be-removed array backend.""" + return ( + f"The '{backend}' array backend is deprecated and will be removed in a " + f"future release. See {_DEPRECATED_BACKENDS[backend]}." + ) + @overload def _resolve_pat(pat: str | None, option: str | None, required: Literal[True] = ...) -> str: ... @@ -189,28 +206,30 @@ def set_option( self._validate_option(pat) if value is _UNSET: raise TypeError("set_option() missing required argument: 'value'.") - if pat == "ARRAY_BACKEND" and value == "cupy": + if pat == "ARRAY_BACKEND" and value in _DEPRECATED_BACKENDS: warnings.warn( - "The 'cupy' array backend is deprecated and will be removed in a " - "future release. See https://github.com/casact/chainladder-python/issues/843.", + _deprecated_backend_message(value), DeprecationWarning, stacklevel=2, ) - elif pat == "ARRAY_PRIORITY" and isinstance(value, list) and "cupy" in value: - # Only warn when 'cupy' is prioritized ahead of a non-deprecated - # backend ('numpy' or 'sparse'), i.e. cupy would actually be - # selected over a supported backend. - cupy_index = value.index("cupy") - if any( - backend in value and value.index(backend) > cupy_index - for backend in ("numpy", "sparse") - ): - warnings.warn( - "The 'cupy' array backend is deprecated and will be removed in a " - "future release. See https://github.com/casact/chainladder-python/issues/843.", - DeprecationWarning, - stacklevel=2, - ) + elif pat == "ARRAY_PRIORITY" and isinstance(value, list): + # Only warn when a deprecated backend ('cupy' or 'dask') is + # prioritized ahead of a non-deprecated backend ('numpy' or + # 'sparse'), i.e. it would actually be selected over a supported + # backend. The position in the list determines precedence. + for backend in _DEPRECATED_BACKENDS: + if backend not in value: + continue + backend_index = value.index(backend) + if any( + supported in value and value.index(supported) > backend_index + for supported in ("numpy", "sparse") + ): + warnings.warn( + _deprecated_backend_message(backend), + DeprecationWarning, + stacklevel=2, + ) setattr(self, pat, value) def reset_option( diff --git a/chainladder/core/base.py b/chainladder/core/base.py index 03a791e7..bb0e7caf 100644 --- a/chainladder/core/base.py +++ b/chainladder/core/base.py @@ -12,7 +12,8 @@ from chainladder import ( __dt64_unit__, __dt64_dtype__, - options + options, + _deprecated_backend_message ) from chainladder.core.common import Common @@ -162,6 +163,14 @@ def _aggregate_data( ): """Summarize dataframe to the level specified in axes""" if type(data) != pd.DataFrame: + # A non-pandas input here is a Dask dataframe. stacklevel=3 points + # the warning at the user's Triangle(...) call (warn -> this + # method -> Triangle.__init__ -> user). + warnings.warn( + _deprecated_backend_message("dask"), + DeprecationWarning, + stacklevel=3, + ) # Dask dataframes are mutated. data["__origin__"] = origin_date data["__development__"] = development_date diff --git a/chainladder/core/common.py b/chainladder/core/common.py index 3db5a342..da91141a 100644 --- a/chainladder/core/common.py +++ b/chainladder/core/common.py @@ -9,6 +9,7 @@ import pandas as pd from chainladder import options +from chainladder import _DEPRECATED_BACKENDS, _deprecated_backend_message from chainladder.utils.cupy import cp from chainladder.utils.dask import dp from chainladder.utils.sparse import sp @@ -180,10 +181,9 @@ def set_backend( # Warn once, at the public entry point, so stacklevel=2 points at the # user's call site rather than an internal recursive call. The _warn # flag suppresses duplicate warnings from internal recursion below. - if _warn and backend == "cupy": + if _warn and backend in _DEPRECATED_BACKENDS: warnings.warn( - "The 'cupy' array backend is deprecated and will be removed in a " - "future release. See https://github.com/casact/chainladder-python/issues/843.", + _deprecated_backend_message(backend), DeprecationWarning, stacklevel=2, ) diff --git a/chainladder/utils/tests/test_utilities.py b/chainladder/utils/tests/test_utilities.py index 84313a0f..d525d5d7 100644 --- a/chainladder/utils/tests/test_utilities.py +++ b/chainladder/utils/tests/test_utilities.py @@ -4,6 +4,7 @@ import chainladder as cl import copy import numpy as np +import pandas as pd from chainladder import ( __dt64_unit__ @@ -390,6 +391,21 @@ def test_set_option_cupy_backend_deprecated() -> None: cl.options.reset_option('ARRAY_BACKEND') +def test_set_option_dask_backend_deprecated() -> None: + """ + Setting ARRAY_BACKEND to 'dask' should emit a DeprecationWarning. See issue #842. + + Returns + ------- + None + """ + try: + with pytest.warns(DeprecationWarning, match="dask"): + cl.options.set_option('ARRAY_BACKEND', 'dask') + finally: + cl.options.reset_option('ARRAY_BACKEND') + + def test_set_option_cupy_priority_deprecated() -> None: """ Setting ARRAY_PRIORITY with 'cupy' ahead of a non-deprecated backend @@ -401,31 +417,49 @@ def test_set_option_cupy_priority_deprecated() -> None: """ try: with pytest.warns(DeprecationWarning, match="cupy"): - cl.options.set_option('ARRAY_PRIORITY', ['cupy', 'dask', 'sparse', 'numpy']) + cl.options.set_option('ARRAY_PRIORITY', ['cupy', 'numpy', 'sparse', 'dask']) + finally: + cl.options.reset_option('ARRAY_PRIORITY') + + +def test_set_option_dask_priority_deprecated() -> None: + """ + Setting ARRAY_PRIORITY with 'dask' ahead of a non-deprecated backend + ('numpy' or 'sparse') should emit a DeprecationWarning. See issue #842. + + Returns + ------- + None + """ + try: + with pytest.warns(DeprecationWarning, match="dask"): + cl.options.set_option('ARRAY_PRIORITY', ['dask', 'numpy', 'sparse', 'cupy']) finally: cl.options.reset_option('ARRAY_PRIORITY') -def test_set_option_cupy_priority_last_no_warning(recwarn) -> None: +def test_set_option_deprecated_priority_last_no_warning(recwarn) -> None: """ - Setting ARRAY_PRIORITY with 'cupy' ranked below every non-deprecated - backend should not warn, since cupy would never be selected over a - supported backend. See issue #843. + Setting ARRAY_PRIORITY with the deprecated backends ('cupy' and 'dask') + ranked below every non-deprecated backend should not warn, since neither + would ever be selected over a supported backend. See issues #842 and #843. Returns ------- None """ try: - cl.options.set_option('ARRAY_PRIORITY', ['dask', 'sparse', 'numpy', 'cupy']) + cl.options.set_option('ARRAY_PRIORITY', ['numpy', 'sparse', 'dask', 'cupy']) assert not [w for w in recwarn if issubclass(w.category, DeprecationWarning)] finally: cl.options.reset_option('ARRAY_PRIORITY') -def test_set_option_non_cupy_no_warning(recwarn) -> None: +def test_set_option_supported_backend_no_warning(recwarn) -> None: """ - Setting backends other than 'cupy' should not emit a DeprecationWarning. + Setting a non-deprecated backend ('sparse'), and a priority list where no + deprecated backend precedes a supported one, should not emit a + DeprecationWarning. Returns ------- @@ -433,7 +467,7 @@ def test_set_option_non_cupy_no_warning(recwarn) -> None: """ try: cl.options.set_option('ARRAY_BACKEND', 'sparse') - cl.options.set_option('ARRAY_PRIORITY', ['dask', 'sparse', 'numpy']) + cl.options.set_option('ARRAY_PRIORITY', ['sparse', 'numpy']) assert not [w for w in recwarn if issubclass(w.category, DeprecationWarning)] finally: cl.options.reset_option('ARRAY_BACKEND') @@ -461,6 +495,67 @@ def test_set_backend_cupy_deprecated(clrd) -> None: assert cupy_warnings[0].filename == __file__ +def test_set_backend_dask_deprecated(clrd) -> None: + """ + Triangle.set_backend('dask') should emit exactly one DeprecationWarning, + pointing at the caller. See issue #842. + + Returns + ------- + None + """ + with pytest.warns(DeprecationWarning, match="dask") as record: + try: + clrd.set_backend('dask', deep=True) + except Exception: + # The actual conversion can fail when the optional 'dask' + # dependency is not installed; we only care that the deprecation + # warning fired at the public entry point. + pass + dask_warnings = [ + w for w in record + if issubclass(w.category, DeprecationWarning) and "dask" in str(w.message) + ] + assert len(dask_warnings) == 1 + assert dask_warnings[0].filename == __file__ + + +def test_triangle_dask_input_deprecated() -> None: + """ + Passing a non-pandas (Dask) dataframe to the Triangle constructor should + emit a DeprecationWarning. The 'dask' dependency is optional and not + installed in the test environment, so a pandas subclass is used to take + the same non-pandas code path in ``_aggregate_data``. See issue #842. + + Returns + ------- + None + """ + class _NonPandasFrame(pd.DataFrame): + @property + def _constructor(self): + return _NonPandasFrame + + data = _NonPandasFrame({ + "origin": [2020, 2020, 2021], + "development": [2020, 2021, 2021], + "values": [100.0, 150.0, 200.0], + }) + with pytest.warns(DeprecationWarning, match="dask") as record: + cl.Triangle( + data, + origin="origin", + development="development", + columns="values", + ) + dask_warnings = [ + w for w in record + if issubclass(w.category, DeprecationWarning) and "dask" in str(w.message) + ] + assert len(dask_warnings) == 1 + assert dask_warnings[0].filename == __file__ + + def test_describe_option(capsys: CaptureFixture[str]) -> None: """ Supply an option to cl.options.describe_option(). Attribute name, type, default/current From 214d14e54c59ae8dd711e6dc76201eafb9cda061 Mon Sep 17 00:00:00 2001 From: priyam0k <87162535+priyam0k@users.noreply.github.com> Date: Sun, 7 Jun 2026 22:34:39 +0530 Subject: [PATCH 26/83] Gate dask warning to actual dask dataframes (#842) --- chainladder/core/base.py | 20 +++++---- chainladder/utils/tests/test_utilities.py | 54 ++++++++++++++++++++--- 2 files changed, 59 insertions(+), 15 deletions(-) diff --git a/chainladder/core/base.py b/chainladder/core/base.py index bb0e7caf..267f353e 100644 --- a/chainladder/core/base.py +++ b/chainladder/core/base.py @@ -163,14 +163,18 @@ def _aggregate_data( ): """Summarize dataframe to the level specified in axes""" if type(data) != pd.DataFrame: - # A non-pandas input here is a Dask dataframe. stacklevel=3 points - # the warning at the user's Triangle(...) call (warn -> this - # method -> Triangle.__init__ -> user). - warnings.warn( - _deprecated_backend_message("dask"), - DeprecationWarning, - stacklevel=3, - ) + # A non-pandas input that reaches this branch is a Dask dataframe. + # Only the Dask backend is deprecated, so gate the warning on the + # data's module rather than warning for every pandas subclass that + # also takes this path. stacklevel=3 points the warning at the + # user's Triangle(...) call (warn -> this method -> + # Triangle.__init__ -> user). + if type(data).__module__.split(".")[0] == "dask": + warnings.warn( + _deprecated_backend_message("dask"), + DeprecationWarning, + stacklevel=3, + ) # Dask dataframes are mutated. data["__origin__"] = origin_date data["__development__"] = development_date diff --git a/chainladder/utils/tests/test_utilities.py b/chainladder/utils/tests/test_utilities.py index d525d5d7..1ae02430 100644 --- a/chainladder/utils/tests/test_utilities.py +++ b/chainladder/utils/tests/test_utilities.py @@ -522,21 +522,27 @@ def test_set_backend_dask_deprecated(clrd) -> None: def test_triangle_dask_input_deprecated() -> None: """ - Passing a non-pandas (Dask) dataframe to the Triangle constructor should - emit a DeprecationWarning. The 'dask' dependency is optional and not - installed in the test environment, so a pandas subclass is used to take - the same non-pandas code path in ``_aggregate_data``. See issue #842. + Passing a Dask dataframe to the Triangle constructor should emit a + DeprecationWarning. The 'dask' dependency is optional and not installed in + the test environment, so a pandas subclass whose module is spoofed to + 'dask' is used to take the same non-pandas code path in ``_aggregate_data`` + while still supporting the pandas operations performed there. See issue + #842. Returns ------- None """ - class _NonPandasFrame(pd.DataFrame): + class _FakeDaskFrame(pd.DataFrame): @property def _constructor(self): - return _NonPandasFrame + return _FakeDaskFrame - data = _NonPandasFrame({ + # The warning is gated on the data's top-level module being 'dask', so the + # detection mirrors a real dask dataframe (e.g. dask.dataframe.core). + _FakeDaskFrame.__module__ = "dask.dataframe.core" + + data = _FakeDaskFrame({ "origin": [2020, 2020, 2021], "development": [2020, 2021, 2021], "values": [100.0, 150.0, 200.0], @@ -556,6 +562,40 @@ def _constructor(self): assert dask_warnings[0].filename == __file__ +def test_triangle_pandas_subclass_no_dask_warning(recwarn) -> None: + """ + Passing a pandas subclass (not a Dask dataframe) to the Triangle + constructor should not emit the dask DeprecationWarning, even though such + inputs take the same non-pandas code path in ``_aggregate_data``. See + issue #842. + + Returns + ------- + None + """ + class _PandasSubclass(pd.DataFrame): + @property + def _constructor(self): + return _PandasSubclass + + data = _PandasSubclass({ + "origin": [2020, 2020, 2021], + "development": [2020, 2021, 2021], + "values": [100.0, 150.0, 200.0], + }) + cl.Triangle( + data, + origin="origin", + development="development", + columns="values", + ) + dask_warnings = [ + w for w in recwarn + if issubclass(w.category, DeprecationWarning) and "dask" in str(w.message) + ] + assert dask_warnings == [] + + def test_describe_option(capsys: CaptureFixture[str]) -> None: """ Supply an option to cl.options.describe_option(). Attribute name, type, default/current From 96f69094f290aabf8c49ad6d647c58d1c9689dfd Mon Sep 17 00:00:00 2001 From: Gene Dan Date: Sun, 7 Jun 2026 16:47:16 -0500 Subject: [PATCH 27/83] TST: Add tests for missing lines in _LocBase. --- chainladder/core/tests/test_slicing.py | 85 ++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/chainladder/core/tests/test_slicing.py b/chainladder/core/tests/test_slicing.py index c42f1d46..ddd91630 100644 --- a/chainladder/core/tests/test_slicing.py +++ b/chainladder/core/tests/test_slicing.py @@ -1,6 +1,12 @@ +from __future__ import annotations + import numpy as np import pytest +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from chainladder import Triangle def test_slice_by_boolean(clrd): assert ( @@ -125,6 +131,85 @@ def test_sparse_at_iat(prism): prism.iloc[0, 0, 0, 0] = 1.0 +def test_empty_index_raises(raa: Triangle) -> None: + """ + Pass an empty list to Triangle.iloc and raise an empty Triangle error. + + Parameters + ---------- + raa: Triangle + The raa sample data set fixture. + + Returns + ------- + None + + """ + with pytest.raises(ValueError, match="Slice returns empty Triangle"): + raa.iloc[[], :] + + +def test_get_idx_fancy_origin_raises(raa: Triangle) -> None: + """ + Attempt fancy indexing on origin axis, raise an error. + + Parameters + ---------- + raa: Triangle + The raa sample data set fixture. + + Returns + ------- + None + + """ + with pytest.raises(ValueError, match="Fancy indexing on origin/development is not supported"): + raa.iloc[0, 0, [0, 1, 5], :] + + +def test_get_idx_fancy_development_raises(raa: Triangle) -> None: + """ + Attempt fancy indexing on development axis, raise an error. + + Parameters + ---------- + raa: Triangle + The raa sample data set fixture. + + Returns + ------- + None + + """ + with pytest.raises(ValueError, match="Fancy indexing on origin/development is not supported"): + raa.iloc[0, 0, :, [0, 1, 5]] + + +def test_get_idx_non_contiguous_index_and_columns(clrd: Triangle) -> None: + """ + Pass lists of non-contiguous index expressions of the index and column axes to Triangle.iloc. Check the values + of the index and columns returned. + + Parameters + ---------- + clrd: Triangle + The clrd sample data set fixture. + + Returns + ------- + None + + """ + result = clrd.iloc[[0, 1, 5], [0, 1, 5], :, :] + expected_index = [ + ['Adriatic Ins Co', 'othliab'], + ['Adriatic Ins Co', 'ppauto'], + ['Agency Ins Co Of MD Inc', 'ppauto'], + ] + assert result.index.values.tolist() == expected_index + assert result.columns.tolist() == ['IncurLoss', 'CumPaidLoss', 'EarnedPremNet'] + + def test_sparse_at_iat1(prism): t = prism.copy() t.iat[0, 0, 0, 0] = 5.0 From a418aa397442342a5d04a9333c21b29faf3ab59e Mon Sep 17 00:00:00 2001 From: Gene Dan Date: Sun, 7 Jun 2026 16:47:39 -0500 Subject: [PATCH 28/83] DOCS: Add type aliases for Triangle, IndexExpression, and _AxisKey. --- chainladder/core/typing.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 chainladder/core/typing.py diff --git a/chainladder/core/typing.py b/chainladder/core/typing.py new file mode 100644 index 00000000..bcc7a607 --- /dev/null +++ b/chainladder/core/typing.py @@ -0,0 +1,22 @@ +from __future__ import annotations + +import numpy as np + +from types import EllipsisType +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from chainladder import Triangle + +# Alias for a Triangle or any object that behaves like one. +TriangleLike = "Triangle" + +# A raw indexing expression as passed by the user to an indexer such as +# .iloc[] or .loc[] before normalization. +IndexExpression = int | slice | list | np.ndarray | tuple | EllipsisType + +# A single axis key after normalization: a bounded slice, an integer position, +# or a fancy-index array. +_AxisKey = slice | int | np.ndarray + + From 9d8fe709fbdff75834ebb00eca19c57852882377 Mon Sep 17 00:00:00 2001 From: Gene Dan Date: Sun, 7 Jun 2026 16:47:58 -0500 Subject: [PATCH 29/83] TST: Exclude typing.py from coverage. --- .coveragerc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.coveragerc b/.coveragerc index f786e10c..74e71713 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,7 +1,9 @@ # .coveragerc to control coverage.py [run] branch = True -omit = *tests* +omit = + *tests* + */core/typing.py [report] show_missing = True From c9c0de953103d7d10a23120f7b1b2442ba9a0f53 Mon Sep 17 00:00:00 2001 From: Gene Dan Date: Sun, 7 Jun 2026 16:48:40 -0500 Subject: [PATCH 30/83] DOCS: Add annotations, typing, and return consistent tuples for key formatting functions. --- chainladder/core/slice.py | 130 ++++++++++++++++++++++++++++---------- 1 file changed, 95 insertions(+), 35 deletions(-) diff --git a/chainladder/core/slice.py b/chainladder/core/slice.py index 4c5b98e8..9022ead0 100644 --- a/chainladder/core/slice.py +++ b/chainladder/core/slice.py @@ -3,15 +3,20 @@ # file, You can obtain one at https://mozilla.org/MPL/2.0/. from __future__ import annotations -import pandas as pd import numpy as np -from sparse import _slicing -from chainladder.utils.utility_functions import num_to_nan +import pandas as pd -from typing import TYPE_CHECKING +from chainladder.core.typing import _AxisKey +from chainladder.utils.utility_functions import num_to_nan +from sparse import _slicing +from typing import ( + cast, + TYPE_CHECKING +) if TYPE_CHECKING: from chainladder import Triangle + from chainladder.core.typing import IndexExpression class _LocBase: """ @@ -21,42 +26,77 @@ class _LocBase: def __init__(self, obj): self.obj = obj - def get_idx(self, idx: tuple): - """ Returns a slice of the original Triangle """ - obj = self.obj.copy() - i_idx = _LocBase._contig_slice(idx[0]) - c_idx = _LocBase._contig_slice(idx[1]) - o_idx = _LocBase._contig_slice(idx[2]) - d_idx = _LocBase._contig_slice(idx[3]) + def get_idx(self, idx: tuple[_AxisKey, _AxisKey, _AxisKey, _AxisKey]) -> Triangle: + """ + Returns a slice of the original Triangle + + Parameters + ---------- + idx: tuple + The index to slice on. + + Returns + ------- + The requested slice of the original Triangle. + + """ + obj: Triangle = self.obj.copy() + idx = cast(tuple[_AxisKey, _AxisKey, _AxisKey, _AxisKey], idx) + i_idx: slice | np.ndarray = _LocBase._contig_slice(idx[0]) + c_idx: slice | np.ndarray = _LocBase._contig_slice(idx[1]) + o_idx: slice | np.ndarray = _LocBase._contig_slice(idx[2]) + d_idx: slice | np.ndarray = _LocBase._contig_slice(idx[3]) if type(o_idx) != slice or type(d_idx) != slice: raise ValueError("Fancy indexing on origin/development is not supported.") if type(i_idx) is slice or type(c_idx) is slice: obj.values = obj.values[i_idx, c_idx, o_idx, d_idx] + # Case when index and column indexing expressions are arrays. else: obj.values = obj.values[i_idx, :, o_idx, d_idx][:, c_idx, ...] + # Set the new dimension values. obj.kdims = obj.kdims[i_idx] obj.vdims = obj.vdims[c_idx] obj.odims, obj.ddims = obj.odims[o_idx], obj.ddims[d_idx] + # Set indexers. obj.iloc, obj.loc = Ilocation(obj), Location(obj) - obj.valuation_date = np.minimum(obj.valuation.max(), obj.valuation_date) + obj.valuation_date = cast(pd.Timestamp, np.minimum(obj.valuation.max(), obj.valuation_date)) return obj @staticmethod - def _contig_slice(arr): - """ Try to make a contiguous slicer from an array of indices """ - if type(arr) is slice: + def _contig_slice(arr: _AxisKey) -> slice | np.ndarray: + """ + Try to make a contiguous slicer from an _AxisKey. + + Parameters + ---------- + arr: _AxisKey + The _AxisKey to be transformed into a contiguous slicer. + + Returns + ------- + slice | np.ndarray + A contiguous slice, if it can be constructed, otherwise, the original arr. + """ + + # If arr is already a slice, return it. + if isinstance(arr, slice): return arr - if type(arr) in [int, np.int64, np.int32]: - arr = [arr] + # If arr is int, construct a contiguous slice and return it. + if isinstance(arr, int): + return slice(arr, arr + 1) + # For single-element array, add 1 to return a contiguous slice. if len(arr) == 1: return slice(arr[0], arr[0] + 1) diff = np.diff(arr) if len(diff) == 0: - raise ValueError("Slice returns empty Triangle") + raise ValueError("Slice returns empty Triangle.") + # Case when each step is the same. if max(diff) == min(diff): step = max(diff) + # Return the original array if no conversion is possible. else: return arr + # Normalize the boundaries and step. step = None if step == 1 else step min_arr = None if min(arr) == 0 else min(arr) max_arr = max(arr) + 1 @@ -74,15 +114,33 @@ def __setitem__(self, key, values): ) self.obj.values.__setitem__(self._normalize_index(key), values) - def _normalize_index(self, key: tuple) -> tuple: - key: tuple = _slicing.normalize_index(key, self.obj.shape) + def _normalize_index(self, key: IndexExpression) -> tuple[_AxisKey, _AxisKey, _AxisKey, _AxisKey]: + """ + Converts an indexing expression into a standard 4-D format. When the indexing has fewer dimensions than 4, + slices for the remaining dimensions are added. + + Parameters + ---------- + key: IndexExpression + The indexing expression passed to the calling indexer. + + Returns + ------- + tuple[_AxisKey, _AxisKey, _AxisKey, _AxisKey] + A normalized, 4-D indexing expression. + + """ + # Apply sparse normalization, fills out the rest of the dimensions using the shape of the Triangle. + key: tuple[_AxisKey, _AxisKey, _AxisKey, _AxisKey] = _slicing.normalize_index(key, self.obj.shape) l = [] + # Preserve start/stop/step boundaries if the user has specified them, otherwise replace them with None. + # None indicates "go-to-boundary" for the slice. for n, i in enumerate(key): if type(i) is slice: - start = i.start if i.start > 0 else None - stop = i.stop if i.stop > -1 else None - stop = None if stop == self.obj.shape[n] else stop - step = None if start is None and stop is None else i.step + start: int | None= i.start if i.start > 0 else None + stop: int | None = i.stop if i.stop > -1 else None + stop: int | None = None if stop == self.obj.shape[n] else stop + step: int | None = None if start is None and stop is None else i.step l.append(slice(start, stop, step)) else: l.append(i) @@ -118,7 +176,7 @@ def __getitem__(self, key): obj.set_index(obj.index.iloc[:, 1:], inplace=True) return obj - def format_key(self, key): + def format_key(self, key) -> tuple: """ Converts keys to loc slices """ if (type(key) is tuple and len(key) > 1 and len(self.obj.key_labels) > 1 and type(key[1]) is str @@ -129,13 +187,15 @@ def format_key(self, key): key_mask = tuple([i if i is Ellipsis else 0 for i in key]) if len(key_mask) < len(self.obj.shape) and Ellipsis not in key_mask: key_mask = tuple(list(key_mask) + [Ellipsis]) - key_mask = list(self._normalize_index(key_mask)) + normalized = self._normalize_index(key_mask) key = [item for item in key if item is not Ellipsis] + key_mask = [] for i in range(len(self.obj.shape)): - if key_mask[i] == 0: - key_mask[i] = key[0] - key.pop(0) - return key_mask + if normalized[i] == 0: + key_mask.append(key.pop(0)) + else: + key_mask.append(normalized[i]) + return tuple(key_mask) def index_key(self, key): if type(key) == pd.Series and len(key) != len(self.obj): @@ -166,13 +226,13 @@ def other_key(self, key, idx): raise AttributeError("Unable to slice.") - def key_to_slice(self, key): + def key_to_slice(self, key) -> tuple: """ Converts keys to integer slices """ key = self.format_key(key) - out = [self.index_key(key[0]), + out = (self.index_key(key[0]), self.other_key(key[1], 'columns'), self.other_key(key[2], 'origin'), - self.other_key(key[3], 'development')] + self.other_key(key[3], 'development')) return out def __setitem__(self, key, values): @@ -283,7 +343,7 @@ def _set_slicers(self) -> None: class At(Location): - def _check_index(self, key): + def _check_index(self, key) -> tuple[_AxisKey, _AxisKey, _AxisKey, _AxisKey]: idx = self.key_to_slice(key) for item in idx: if type(item) is slice: @@ -320,7 +380,7 @@ def __setitem__(self, key, values): class Iat(Ilocation): - def _check_index(self, key): + def _check_index(self, key) -> tuple[_AxisKey, _AxisKey, _AxisKey, _AxisKey]: idx = self._normalize_index(key) types = {type(i) for i in idx} if len(types) > 1 or list(types)[0] != int: From 2a711e171b7828071e67cffbadaaf47e0daee25e Mon Sep 17 00:00:00 2001 From: Gene Dan Date: Sun, 7 Jun 2026 17:08:05 -0500 Subject: [PATCH 31/83] FIX: Apply Bugbot fixes. --- chainladder/core/slice.py | 3 ++- chainladder/core/typing.py | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/chainladder/core/slice.py b/chainladder/core/slice.py index 9022ead0..36cc1149 100644 --- a/chainladder/core/slice.py +++ b/chainladder/core/slice.py @@ -82,8 +82,9 @@ def _contig_slice(arr: _AxisKey) -> slice | np.ndarray: if isinstance(arr, slice): return arr # If arr is int, construct a contiguous slice and return it. - if isinstance(arr, int): + elif isinstance(arr, (int, np.int32, np.int64)): return slice(arr, arr + 1) + arr = cast(np.ndarray, arr) # For single-element array, add 1 to return a contiguous slice. if len(arr) == 1: return slice(arr[0], arr[0] + 1) diff --git a/chainladder/core/typing.py b/chainladder/core/typing.py index bcc7a607..e37de375 100644 --- a/chainladder/core/typing.py +++ b/chainladder/core/typing.py @@ -3,7 +3,7 @@ import numpy as np from types import EllipsisType -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, TypeAlias if TYPE_CHECKING: from chainladder import Triangle @@ -13,10 +13,10 @@ # A raw indexing expression as passed by the user to an indexer such as # .iloc[] or .loc[] before normalization. -IndexExpression = int | slice | list | np.ndarray | tuple | EllipsisType +IndexExpression: TypeAlias = int | slice | list | np.ndarray | tuple | EllipsisType # A single axis key after normalization: a bounded slice, an integer position, # or a fancy-index array. -_AxisKey = slice | int | np.ndarray +_AxisKey: TypeAlias = slice | int | np.int64 | np.int32 | np.ndarray From 3b655c56f188294ba69f697b8a45b7e4c862a1ba Mon Sep 17 00:00:00 2001 From: Gene Dan Date: Sun, 7 Jun 2026 17:08:57 -0500 Subject: [PATCH 32/83] FIX: Apply Bugbot fixes. --- chainladder/core/typing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chainladder/core/typing.py b/chainladder/core/typing.py index e37de375..6af3c76a 100644 --- a/chainladder/core/typing.py +++ b/chainladder/core/typing.py @@ -9,7 +9,7 @@ from chainladder import Triangle # Alias for a Triangle or any object that behaves like one. -TriangleLike = "Triangle" +TriangleLike: TypeAlias = Triangle # A raw indexing expression as passed by the user to an indexer such as # .iloc[] or .loc[] before normalization. From 4f4c0ec3140e2eed067c74b43d0e6b5e7159d2fa Mon Sep 17 00:00:00 2001 From: Gene Dan Date: Sun, 7 Jun 2026 17:13:04 -0500 Subject: [PATCH 33/83] FIX: Apply Bugbot fixes. --- chainladder/core/typing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chainladder/core/typing.py b/chainladder/core/typing.py index 6af3c76a..9d7fca35 100644 --- a/chainladder/core/typing.py +++ b/chainladder/core/typing.py @@ -9,7 +9,7 @@ from chainladder import Triangle # Alias for a Triangle or any object that behaves like one. -TriangleLike: TypeAlias = Triangle +TriangleLike: TypeAlias = "Triangle" # A raw indexing expression as passed by the user to an indexer such as # .iloc[] or .loc[] before normalization. From bc0e3c450f5796e46f7b2b53824050594f04cd92 Mon Sep 17 00:00:00 2001 From: Kenneth Hsu Date: Tue, 9 Jun 2026 11:52:37 -0700 Subject: [PATCH 34/83] Switching codecov to main --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index f6e359f1..9c8cda0b 100644 --- a/README.rst +++ b/README.rst @@ -21,7 +21,7 @@ .. |Build Status| image:: https://github.com/casact/chainladder-python/actions/workflows/pytest.yml/badge.svg :target: https://github.com/casact/chainladder-python/actions/workflows/pytest.yml -.. |codecov io| image:: https://codecov.io/gh/casact/chainladder-python/branch/master/graphs/badge.svg +.. |codecov io| image:: https://codecov.io/gh/casact/chainladder-python/branch/main/graphs/badge.svg :target: https://codecov.io/github/casact/chainladder-python?branch=latest .. |Documentation Status| image:: https://readthedocs.org/projects/chainladder-python/badge/?version=main From c3cfe7f0ce6fcf2fad81dc177c6611b28f09c894 Mon Sep 17 00:00:00 2001 From: Gene Dan Date: Mon, 8 Jun 2026 15:31:08 -0500 Subject: [PATCH 35/83] TST: Add tests for missing lines in slice.py. --- chainladder/core/tests/test_slicing.py | 37 ++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/chainladder/core/tests/test_slicing.py b/chainladder/core/tests/test_slicing.py index ddd91630..d7b68fca 100644 --- a/chainladder/core/tests/test_slicing.py +++ b/chainladder/core/tests/test_slicing.py @@ -126,6 +126,43 @@ def test_at_iat_exceptions(raa): raa.at["Total", "values", "1985", 0:2] +def test_other_key_unsupported_iterable_raises(raa: Triangle) -> None: + """ + Pass a nonsense key to raa.loc[], should raise an error. + + Parameters + ---------- + raa: Triangle + The raa sample data set fixture. + + Returns + ------- + None + + """ + with pytest.raises(AttributeError, match="Unable to slice"): + raa.loc[:, (0, 1)] + + +def test_at_setitem_triangle_value(raa: Triangle) -> None: + """ + Use Triangle.at to set a single value via a TriangleSlicer. + + Parameters + ---------- + raa: Triangle + The raa sample data set fixture. + + Returns + ------- + None + + """ + tri = raa.copy() + tri.at["Total", "values", "1981", 12] = tri.iloc[0, 0, 1:2, 0:1] + assert tri.at["Total", "values", "1981", 12] == 106.0 + + @pytest.mark.xfail def test_sparse_at_iat(prism): prism.iloc[0, 0, 0, 0] = 1.0 From be4867216014dd9eb00b404952266199d8e14d54 Mon Sep 17 00:00:00 2001 From: Gene Dan Date: Mon, 8 Jun 2026 15:31:20 -0500 Subject: [PATCH 36/83] DOCS: Add typing and annotations. --- chainladder/core/slice.py | 244 +++++++++++++++++++++++++++++++------ chainladder/core/typing.py | 8 +- 2 files changed, 210 insertions(+), 42 deletions(-) diff --git a/chainladder/core/slice.py b/chainladder/core/slice.py index 36cc1149..204f9be0 100644 --- a/chainladder/core/slice.py +++ b/chainladder/core/slice.py @@ -1,14 +1,22 @@ +""" +Support pandas-style slicing to the Triangle class. +""" # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at https://mozilla.org/MPL/2.0/. from __future__ import annotations +import importlib import numpy as np import pandas as pd -from chainladder.core.typing import _AxisKey +from chainladder.core.typing import ( + _AxisKey, + _LabelKey +) + from chainladder.utils.utility_functions import num_to_nan -from sparse import _slicing + from typing import ( cast, TYPE_CHECKING @@ -17,6 +25,11 @@ if TYPE_CHECKING: from chainladder import Triangle from chainladder.core.typing import IndexExpression + from sparse import COO + from typing import Literal + + +_slicing = importlib.import_module("sparse._slicing") class _LocBase: """ @@ -105,13 +118,33 @@ def _contig_slice(arr: _AxisKey) -> slice | np.ndarray: min_arr, max_arr = max_arr - 1, min_arr - 1 if min_arr else min_arr return slice(min_arr, max_arr, step) - def __setitem__(self, key, values): + def __setitem__( + self, + key: tuple[_AxisKey, _AxisKey, _AxisKey, _AxisKey], + values: int | float | TriangleSlicer + ) -> None: + """ + Supports the square bracket syntax [] for setting Triangle values. Only supported for numpy backend. + + Parameters + ---------- + key: tuple[_AxisKey, _AxisKey, _AxisKey, _AxisKey] + Indicates the slice of the Triangle you want to set values for. + values: int | float | TriangleSlicer + The value(s) you want to assign to the slice of the Triangle. + + Returns + ------- + None + + """ if self.obj.array_backend == "sparse": raise ValueError('Setting values with sparse backend requires .at or .iat') if isinstance(values, TriangleSlicer): values = values.values + # Create a slice for any key elements that are integers, otherwise preserve the slice or array. key = tuple( - [slice(item, item + 1) if type(item) is int else item for item in key] + [slice(item, item + 1) if isinstance(item, int) else item for item in key] ) self.obj.values.__setitem__(self._normalize_index(key), values) @@ -137,7 +170,7 @@ def _normalize_index(self, key: IndexExpression) -> tuple[_AxisKey, _AxisKey, _A # Preserve start/stop/step boundaries if the user has specified them, otherwise replace them with None. # None indicates "go-to-boundary" for the slice. for n, i in enumerate(key): - if type(i) is slice: + if isinstance(i, slice): start: int | None= i.start if i.start > 0 else None stop: int | None = i.stop if i.stop > -1 else None stop: int | None = None if stop == self.obj.shape[n] else stop @@ -148,48 +181,122 @@ def _normalize_index(self, key: IndexExpression) -> tuple[_AxisKey, _AxisKey, _A key = tuple(l) return key - def _sparse_setitem(self, key, values): + def _sparse_setitem( + self, + key: tuple[int, int, int, int], + values: int | float + ) -> None: + """ + Set slice of Triangle when backend is sparse. + + Parameters + ---------- + key: tuple[int, int, int, int] + values: int | float + + Returns + ------- + None + + """ + # Enforce sparse array type. + arr: COO = cast("COO", cast(object, self.obj.values)) + key: tuple = cast("tuple", cast(object, key)) + # Check if a stored value exists at the coordinate point. check = ( - (self.obj.values.coords[0] == key[0]) * - (self.obj.values.coords[1] == key[1]) * - (self.obj.values.coords[2] == key[2]) * - (self.obj.values.coords[3] == key[3])) + (arr.coords[0] == key[0]) * + (arr.coords[1] == key[1]) * + (arr.coords[2] == key[2]) * + (arr.coords[3] == key[3])) + # If it does, index the location and assign the value directly. if check.max(): data_index = np.where(check == True)[0][0] - self.obj.values.data[data_index] = values + arr.data[data_index] = values + # Otherwise, create a new sparse array with the updated coordinates and data. else: - self.obj.values.coords = np.concatenate( - (self.obj.values.coords, np.array(key)[:, None]), 1) - self.obj.values.data = np.concatenate( - (self.obj.values.data, np.array([values])), 0) + # Append the new coordinate. + arr.coords = np.concatenate( + (arr.coords, np.array(key)[:, None]), axis=1) + # Append the new data element. + arr.data = np.concatenate( + (arr.data, np.array([values])), axis=0) + # Construct the new sparse array and assign to Triangle. self.obj.values = self.obj.get_array_module().COO( - self.obj.values.coords, self.obj.values.data, prune=True, - has_duplicates=False, shape=self.obj.shape, - fill_value=self.obj.values.fill_value) + coords=arr.coords, + data=arr.data, + prune=True, + has_duplicates=False, + shape=self.obj.shape, + fill_value=arr.fill_value + ) class Location(_LocBase): """ class to generate .loc[] functionality """ - def __getitem__(self, key): - obj = self.get_idx(self.key_to_slice(key)) + def __getitem__( + self, + key: _LabelKey + ) -> Triangle: + """ + Support square bracket indexing of Triangle.loc[] to extract data. + + Parameters + ---------- + key: _LabelKey + The pandas-style slice that you wish to extract from the Triangle. + + Returns + ------- + Triangle + The desired slice of a Triangle, specified by key. + + """ + # Extract the desired slice. + obj: Triangle = self.get_idx(self.key_to_slice(key)) + # Drop the top-level index if unique, otherwise, return the slice. if len(obj) > 1 and obj.index.iloc[:, 0].nunique() == 1: obj.set_index(obj.index.iloc[:, 1:], inplace=True) return obj - def format_key(self, key) -> tuple: - """ Converts keys to loc slices """ - if (type(key) is tuple and len(key) > 1 + def format_key(self, key: _LabelKey) -> tuple[_LabelKey, _LabelKey, _LabelKey, _LabelKey]: + """ + Aligns a user-supplied label-based key to the Triangle's 4 axes, leaving each + element as a label-based selector for index_key/other_key to resolve later. + + Parameters + ---------- + key: _LabelKey + The user-supplied label-based key. + + Returns + ------- + tuple[_LabelKey, _LabelKey, _LabelKey, _LabelKey] + A 4-tuple of per-axis label-based selectors. + """ + # Parse the user-supplied key and determine data type and purpose. + # Preprocess into a common tuple-format prior to standardizing the dimensions. + + # Case when key is a tuple representing an index row. + if (isinstance(key, tuple) and len(key) > 1 and len(self.obj.key_labels) > 1 and type(key[1]) is str and key[1] in self.obj.index[self.obj.key_labels[1]].unique()): key = (key,) + # Case when tuple elements represent separate dimensions, keep as-is. + elif isinstance(key, tuple): + pass + # Otherwise, convert to tuple. else: - key = (key,) if type(key) is not tuple else key + key = (key,) + + # Create a placeholder normalized key, with 0 for mapping to user-supplied dimensions, and + # a full slice otherwise. key_mask = tuple([i if i is Ellipsis else 0 for i in key]) if len(key_mask) < len(self.obj.shape) and Ellipsis not in key_mask: key_mask = tuple(list(key_mask) + [Ellipsis]) - normalized = self._normalize_index(key_mask) + normalized: tuple = self._normalize_index(key_mask) key = [item for item in key if item is not Ellipsis] + # Populate a new formatted key by replacing the 0s with the user-supplied key elements. key_mask = [] for i in range(len(self.obj.shape)): if normalized[i] == 0: @@ -198,38 +305,95 @@ def format_key(self, key) -> tuple: key_mask.append(normalized[i]) return tuple(key_mask) - def index_key(self, key): - if type(key) == pd.Series and len(key) != len(self.obj): + def index_key(self, key: _LabelKey) -> np.ndarray: + """ + Converts a label-based key into an integer-based one. Intended to be used for the index axis. + + Parameters + ---------- + key: _LabelKey + A label-based to be converted into an integer-based key. + + Returns + ------- + np.ndarray + An integer-based key. + + """ + # Case when key is a single index row and not a boolean mask, preprocess into a DataFrame of labels. + if isinstance(key, pd.Series) and len(key) != len(self.obj): key = key.to_frame().T - if type(key) == pd.Series: + # Case boolean mask. Extract the positions where True. + if isinstance(key, pd.Series): idx = np.where(key)[0] - elif type(key) == pd.DataFrame: + # Case DataFrame of labels, find positions in index. + elif isinstance(key, pd.DataFrame): idx = (self.obj.index.reset_index().set_index(self.obj.key_labels) .loc[key.set_index(list(key.columns)).index]).values.flatten() + # Case Pandas-style label selectors, extract positions from index. elif type(key) in [slice, list, tuple]: idx = (self.obj.index.reset_index() .set_index(self.obj.key_labels).loc[key]).values.flatten() + # Case scalar, locate position in first level of index. else: idx = np.where(self.obj.kdims[:, 0]==key)[0] return idx - def other_key(self, key, idx): - if type(key) is np.ndarray: + def other_key( + self, + key: _LabelKey, + idx: Literal['columns', 'origin', 'development'] + ) -> np.ndarray | slice: + """ + Converts a label-based key into an integer-based one. Intended to be used for axes other than the index. + + Parameters + ---------- + key: _LabelKey + A label-based to be converted into an integer-based key. + idx: Literal['columns', 'origin', 'development'] + The axis to which the key applies. + + Returns + ------- + np.ndarray | slice + An integer-based key. + + """ + # Case boolean mask, return positions. + if isinstance(key, np.ndarray): return np.where(key)[0] - if key == slice(None, None, None): - return key + # Case full-axis slice, simply return it. + if isinstance(key, slice) and (key == slice(None, None, None)): + return cast(slice, key) + # Otherwise, extract the index and then find the positions. s = getattr(self.obj, idx) + obj_idx = pd.Series(range(len(s)), index=s) if type(key) in [slice, list]: - return pd.Series(range(len(s)), index=s).loc[key].values + return obj_idx.loc[key].values if not hasattr(key, '__iter__') or type(key) is str: - return np.array([pd.Series(range(len(s)), index=s).loc[key]]) + return np.array([obj_idx.loc[key]]) else: raise AttributeError("Unable to slice.") - def key_to_slice(self, key) -> tuple: - """ Converts keys to integer slices """ - key = self.format_key(key) + def key_to_slice(self, key: _LabelKey) -> tuple: + """ + Converts keys to integer slices. + + Parameters + ---------- + key: + The pandas-style slice that you wish to extract from the Triangle. + Returns + ------- + tuple + A 4-tuple of integer-based slices. + """ + + # Preprocess key into a normalized 4-D key. + key: tuple[_LabelKey, _LabelKey, _LabelKey, _LabelKey] = self.format_key(key) + # Transform into integer-based slices. out = (self.index_key(key[0]), self.other_key(key[1], 'columns'), self.other_key(key[2], 'origin'), @@ -244,10 +408,10 @@ class Ilocation(_LocBase): Class to generate .iloc[] functionality. """ - def __getitem__(self, key: tuple): + def __getitem__(self, key: IndexExpression) -> Triangle: return self.get_idx(self._normalize_index(key)) - def __setitem__(self, key, values): + def __setitem__(self, key: IndexExpression, values): super().__setitem__(self._normalize_index(key), values) diff --git a/chainladder/core/typing.py b/chainladder/core/typing.py index 9d7fca35..6d8c9751 100644 --- a/chainladder/core/typing.py +++ b/chainladder/core/typing.py @@ -1,6 +1,7 @@ from __future__ import annotations import numpy as np +import pandas as pd from types import EllipsisType from typing import TYPE_CHECKING, TypeAlias @@ -11,12 +12,15 @@ # Alias for a Triangle or any object that behaves like one. TriangleLike: TypeAlias = "Triangle" -# A raw indexing expression as passed by the user to an indexer such as -# .iloc[] or .loc[] before normalization. +# A raw indexing expression as passed by the user to an indexer such aa .iloc[] before normalization. IndexExpression: TypeAlias = int | slice | list | np.ndarray | tuple | EllipsisType # A single axis key after normalization: a bounded slice, an integer position, # or a fancy-index array. _AxisKey: TypeAlias = slice | int | np.int64 | np.int32 | np.ndarray +# A label-based selector for a single Triangle axis, as accepted by .loc[] +# before index_key/other_key resolve it to a positional _AxisKey. +_LabelKey: TypeAlias = IndexExpression | str | pd.Series | pd.DataFrame + From d97ecb53b370caa7b784e4e3a24761999b6a41dd Mon Sep 17 00:00:00 2001 From: Gene Dan Date: Mon, 8 Jun 2026 15:55:51 -0500 Subject: [PATCH 37/83] FIX: Code cleanup to handle warnings. --- chainladder/core/slice.py | 8 ++++---- chainladder/core/tests/test_slicing.py | 24 ++++++++++++------------ 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/chainladder/core/slice.py b/chainladder/core/slice.py index 204f9be0..67e42b95 100644 --- a/chainladder/core/slice.py +++ b/chainladder/core/slice.py @@ -377,7 +377,7 @@ def other_key( raise AttributeError("Unable to slice.") - def key_to_slice(self, key: _LabelKey) -> tuple: + def key_to_slice(self, key: _LabelKey) -> tuple[_AxisKey, _AxisKey, _AxisKey, _AxisKey]: """ Converts keys to integer slices. @@ -392,7 +392,7 @@ def key_to_slice(self, key: _LabelKey) -> tuple: """ # Preprocess key into a normalized 4-D key. - key: tuple[_LabelKey, _LabelKey, _LabelKey, _LabelKey] = self.format_key(key) + key = self.format_key(key) # Transform into integer-based slices. out = (self.index_key(key[0]), self.other_key(key[1], 'columns'), @@ -400,8 +400,8 @@ def key_to_slice(self, key: _LabelKey) -> tuple: self.other_key(key[3], 'development')) return out - def __setitem__(self, key, values): - super().__setitem__(self.key_to_slice(key), values) + def __setitem__(self, key: _LabelKey, values: int | float | TriangleSlicer) -> None: + super().__setitem__(cast(tuple[_AxisKey, _AxisKey, _AxisKey, _AxisKey], self.key_to_slice(key)), values) class Ilocation(_LocBase): """ diff --git a/chainladder/core/tests/test_slicing.py b/chainladder/core/tests/test_slicing.py index d7b68fca..c0c84f00 100644 --- a/chainladder/core/tests/test_slicing.py +++ b/chainladder/core/tests/test_slicing.py @@ -21,12 +21,12 @@ def test_slice_by_loc(clrd): def test_slice_origin(raa): assert raa[raa.origin > "1985"].shape == (1, 1, 5, 10) - raa.loc[..., raa.origin <= "1994", :] + _= raa.loc[..., raa.origin <= "1994", :] def test_slice_development(raa): assert raa[raa.development < 72].shape == (1, 1, 10, 5) - raa.loc[..., 24:] + _= raa.loc[..., 24:] def test_slice_by_loc_iloc(clrd): @@ -93,7 +93,7 @@ def test_missing_first_lag(raa): x = raa.copy() x.values[:, :, :, 0] = 0 x = x.sum(0) - x.link_ratio.shape == (1, 1, 9, 9) + assert x.link_ratio.shape == (1, 1, 9, 9) def test_reverse_slice_integrity(clrd): @@ -110,20 +110,20 @@ def test_loc_tuple(clrd): def test_at_iat(raa): raa1 = raa.copy() raa2 = raa.copy() - raa1.at["Total", "values", "1985", 120] + _= raa1.at["Total", "values", "1985", 120] raa1.at["Total", "values", "1985", 120] = 5 raa1.at["Total", "values", "1985", 12] = 5 raa2.iat[0, 0, 4, -1] = 5 - raa2.iat[0, 0, 4, -1] + _= raa2.iat[0, 0, 4, -1] raa2.iat[-1, -1, 4, 0] = 5 assert raa1 == raa2 def test_at_iat_exceptions(raa): with pytest.raises(ValueError): - raa.iat[0, 0, 4, :] + _= raa.iat[0, 0, 4, :] with pytest.raises(ValueError): - raa.at["Total", "values", "1985", 0:2] + _= raa.at["Total", "values", "1985", 0:2] def test_other_key_unsupported_iterable_raises(raa: Triangle) -> None: @@ -141,7 +141,7 @@ def test_other_key_unsupported_iterable_raises(raa: Triangle) -> None: """ with pytest.raises(AttributeError, match="Unable to slice"): - raa.loc[:, (0, 1)] + _= raa.loc[:, (0, 1)] def test_at_setitem_triangle_value(raa: Triangle) -> None: @@ -183,7 +183,7 @@ def test_empty_index_raises(raa: Triangle) -> None: """ with pytest.raises(ValueError, match="Slice returns empty Triangle"): - raa.iloc[[], :] + _= raa.iloc[[], :] def test_get_idx_fancy_origin_raises(raa: Triangle) -> None: @@ -201,7 +201,7 @@ def test_get_idx_fancy_origin_raises(raa: Triangle) -> None: """ with pytest.raises(ValueError, match="Fancy indexing on origin/development is not supported"): - raa.iloc[0, 0, [0, 1, 5], :] + _= raa.iloc[0, 0, [0, 1, 5], :] def test_get_idx_fancy_development_raises(raa: Triangle) -> None: @@ -219,7 +219,7 @@ def test_get_idx_fancy_development_raises(raa: Triangle) -> None: """ with pytest.raises(ValueError, match="Fancy indexing on origin/development is not supported"): - raa.iloc[0, 0, :, [0, 1, 5]] + _= raa.iloc[0, 0, :, [0, 1, 5]] def test_get_idx_non_contiguous_index_and_columns(clrd: Triangle) -> None: @@ -260,6 +260,6 @@ def test_sparse_column_assignment(prism): t["Paid2"] = t["Paid"] # New from physical t["Paid2"] = lambda x: x["Paid"] # Existing from virtual t["Paid"] = t["Paid2"] # Existing from physical - t["Paid3"] = lambda t: t["Paid"] # New from virtual + t["Paid3"] = lambda x: x["Paid"] # New from virtual assert out == t["Paid"] assert t.shape == (34244, 6, 120, 120) From 96db5d9aafa853541ef78dc19e057eaf992d58dc Mon Sep 17 00:00:00 2001 From: Gene Dan Date: Tue, 9 Jun 2026 08:24:04 -0500 Subject: [PATCH 38/83] TST: Add more meaningful assertions and annotations. --- chainladder/core/tests/test_slicing.py | 35 ++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/chainladder/core/tests/test_slicing.py b/chainladder/core/tests/test_slicing.py index c0c84f00..89c5f118 100644 --- a/chainladder/core/tests/test_slicing.py +++ b/chainladder/core/tests/test_slicing.py @@ -8,7 +8,7 @@ if TYPE_CHECKING: from chainladder import Triangle -def test_slice_by_boolean(clrd): +def test_slice_by_boolean(clrd : Triangle) -> None: assert ( clrd[clrd["LOB"] == "ppauto"].loc["Wolverine Mut Ins Co"]["CumPaidLoss"] == clrd.loc["Wolverine Mut Ins Co"].loc["ppauto"]["CumPaidLoss"] @@ -19,14 +19,39 @@ def test_slice_by_loc(clrd): assert clrd.loc["Aegis Grp"].loc["comauto"].index.iloc[0, 0] == "comauto" -def test_slice_origin(raa): +def test_slice_origin(raa: Triangle) -> None: + """ + Slice the Triangle on the origin. Check the shape and year boundary. + + Parameters + ---------- + raa: Triangle + The raa sample data set fixture + + Returns + ------- + None + """ assert raa[raa.origin > "1985"].shape == (1, 1, 5, 10) - _= raa.loc[..., raa.origin <= "1994", :] + assert raa.loc[..., raa.origin <= "1985", :].origin.max().year == 1985 -def test_slice_development(raa): +def test_slice_development(raa: Triangle) -> None: + """ + Slice the Triangle on the development axis. Check the shape and development periods. + + Parameters + ---------- + raa: Triangle + The raa sample data set fixture + + Returns + ------- + None + + """ assert raa[raa.development < 72].shape == (1, 1, 10, 5) - _= raa.loc[..., 24:] + assert raa.loc[..., 24:].development.max() <= 120 def test_slice_by_loc_iloc(clrd): From 37701806a4f809a1c22a556645995774c3a1e071 Mon Sep 17 00:00:00 2001 From: Gene Dan Date: Tue, 9 Jun 2026 08:30:46 -0500 Subject: [PATCH 39/83] FIX: Fix boundary on development slicing test. --- chainladder/core/tests/test_slicing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chainladder/core/tests/test_slicing.py b/chainladder/core/tests/test_slicing.py index 89c5f118..ab9a2d2b 100644 --- a/chainladder/core/tests/test_slicing.py +++ b/chainladder/core/tests/test_slicing.py @@ -51,7 +51,7 @@ def test_slice_development(raa: Triangle) -> None: """ assert raa[raa.development < 72].shape == (1, 1, 10, 5) - assert raa.loc[..., 24:].development.max() <= 120 + assert raa.loc[..., 24:].development.min() <= 24 def test_slice_by_loc_iloc(clrd): From 283e66c942e61262c635214b736fc577df8d3654 Mon Sep 17 00:00:00 2001 From: Gene Dan Date: Tue, 9 Jun 2026 08:31:08 -0500 Subject: [PATCH 40/83] FIX: Fix boundary on development slicing test. --- chainladder/core/tests/test_slicing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chainladder/core/tests/test_slicing.py b/chainladder/core/tests/test_slicing.py index ab9a2d2b..aab0ff91 100644 --- a/chainladder/core/tests/test_slicing.py +++ b/chainladder/core/tests/test_slicing.py @@ -51,7 +51,7 @@ def test_slice_development(raa: Triangle) -> None: """ assert raa[raa.development < 72].shape == (1, 1, 10, 5) - assert raa.loc[..., 24:].development.min() <= 24 + assert raa.loc[..., 24:].development.min() == 24 def test_slice_by_loc_iloc(clrd): From 789d295dcb6139cad354eeab9470025f30d07cf9 Mon Sep 17 00:00:00 2001 From: "henrydingliu@gmail.com" Date: Thu, 28 May 2026 04:32:40 +0000 Subject: [PATCH 41/83] adding support for multi-triangle in mackchainladder --- chainladder/methods/mack.py | 2 +- chainladder/methods/tests/test_mack.py | 11 +- docs/friedland/chapter_7.rst | 157 ------------------------- 3 files changed, 10 insertions(+), 160 deletions(-) diff --git a/chainladder/methods/mack.py b/chainladder/methods/mack.py index cfcf71e0..15bf820e 100644 --- a/chainladder/methods/mack.py +++ b/chainladder/methods/mack.py @@ -242,7 +242,7 @@ def _get_full_std_err_(self, X=None): val = xp.broadcast_to(xp.array(avg + [avg[-1]]), X.shape) weight = xp.sqrt(full.values[..., : len(X.ddims)] ** (2 - val)) obj.values = X.sigma_.values / num_to_nan(weight) - w = lxp.concatenate((X.w_, lxp.ones((1, 1, val.shape[2], 1))), 3) + w = lxp.concatenate((X.w_, lxp.ones((val.shape[0], val.shape[1], val.shape[2], 1))), 3) w[xp.isnan(w)] = 1 obj.values = xp.nan_to_num(obj.values) * xp.array(w) obj.valuation_date = full.valuation_date diff --git a/chainladder/methods/tests/test_mack.py b/chainladder/methods/tests/test_mack.py index c9adc020..ac9bf6c5 100644 --- a/chainladder/methods/tests/test_mack.py +++ b/chainladder/methods/tests/test_mack.py @@ -1,4 +1,5 @@ import chainladder as cl +import numpy as np def test_mack_to_triangle(): assert ( @@ -14,10 +15,16 @@ def test_mack_to_triangle(): .summary_ ) - def test_mack_malformed(): a = cl.load_sample('raa') b = a.iloc[:, :, :-1] x = cl.MackChainladder().fit(a) y = cl.MackChainladder().fit(b) - assert x.process_risk_.iloc[:,:,:-1] == y.process_risk_ \ No newline at end of file + assert x.process_risk_.iloc[:,:,:-1] == y.process_risk_ + +def test_multi_triangle_mack(clrd,atol): + tri = clrd.loc['Agway Ins Co']['IncurLoss','CumPaidLoss'] + mack = cl.MackChainladder().fit(tri) + for i in range(len(tri.index)): + for j in range(len(tri.columns)): + assert np.all(abs(mack.full_std_err_.iloc[i,j].values-cl.MackChainladder().fit(tri.iloc[i,j]).full_std_err_.values) < atol) \ No newline at end of file diff --git a/docs/friedland/chapter_7.rst b/docs/friedland/chapter_7.rst index 64f6f1b1..9b2630a8 100644 --- a/docs/friedland/chapter_7.rst +++ b/docs/friedland/chapter_7.rst @@ -27,7 +27,6 @@ PART 1 - Data Triangle We have already imported the necessary packages loading the ``Triangle`` at the top of p106. Let's take a look at the ``Triangle`` we just loaded. .. doctest:: - :hide: >>> tri = cl.load_sample('friedland_us_industry_auto') >>> tri['Reported Claims'] @@ -43,162 +42,6 @@ We have already imported the necessary packages loading the ``Triangle`` at the 2006 46582684.0 54641339.0 NaN NaN NaN NaN NaN NaN NaN NaN 2007 48853563.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN -.. code-block:: - - >>> tri = cl.load_sample('friedland_us_industry_auto') - -.. raw:: html - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1224364860728496108120
199837,017,48743,169,00945,568,91946,784,55847,337,31847,533,26447,634,41947,689,65547,724,67847,742,304
199938,954,48446,045,71848,882,92450,219,67250,729,29250,926,77951,069,28551,163,54051,185,767
200041,155,77649,371,47852,358,47653,780,32254,303,08654,582,95054,742,18854,837,929
200142,394,06950,584,11253,704,29655,150,11855,895,58356,156,72756,299,562
200244,755,24352,971,64356,102,31257,703,85158,363,56458,592,712
200345,163,10252,497,73155,468,55157,015,41157,565,344
200445,417,30952,640,32255,553,67356,976,657
200546,360,86953,790,06156,786,410
200646,582,68454,641,339
200748,853,563
- PART 2 - Age-to-Age Factors ---------------------------- From 86f5acf1bb5260c0fac926e148e811f13c2c9060 Mon Sep 17 00:00:00 2001 From: henrydingliu <106109320+henrydingliu@users.noreply.github.com> Date: Thu, 11 Jun 2026 19:26:17 -0700 Subject: [PATCH 42/83] Update test_predict.py --- chainladder/methods/tests/test_predict.py | 28 ++++++++++++----------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/chainladder/methods/tests/test_predict.py b/chainladder/methods/tests/test_predict.py index 6e9b852f..c4fab221 100644 --- a/chainladder/methods/tests/test_predict.py +++ b/chainladder/methods/tests/test_predict.py @@ -8,18 +8,20 @@ apriori = cl_ult * 0 + (float(cl_ult.sum()) / 10) # Mean Chainladder Ultimate apriori_1989 = apriori[apriori.origin < "1990"] - -def test_cc_predict(): - cc = cl.CapeCod().fit(raa_1989, sample_weight=apriori_1989) - assert cc.predict(raa, sample_weight=apriori) - -def test_bf_predict(): - bf = cl.BornhuetterFerguson().fit(raa_1989, sample_weight=apriori_1989) - assert bf.predict(raa, sample_weight=apriori) - -def test_el_predict(): - bf = cl.ExpectedLoss().fit(raa_1989, sample_weight=apriori_1989) - assert bf.predict(raa, sample_weight=apriori) +@pytest.mark.parametrize( + "estimators", + [ + cl.CapeCod, + cl.BornhuetterFerguson, + cl.ExpectedLoss, + cl.Benktander, + cl.Chainladder + ], +) +def test_predict_and_weights(estimators,atol): + est = estimators().fit(raa_1989, sample_weight=apriori_1989) + pred = est.predict(raa, sample_weight=apriori) + assert pred def test_mack_predict(): mack = cl.MackChainladder().fit(raa_1989) @@ -186,4 +188,4 @@ def test_odd_shaped_triangle(): ) ult1 = cl.Chainladder().fit(cl.Development(average="volume").fit_transform(tr.grain("OYDQ"))).ultimate_.sum() ult2 = cl.Chainladder().fit(cl.Development(average="volume").fit_transform(tr)).ultimate_.grain("OYDQ").sum() - assert abs(ult1 - ult2) < 1e-5 \ No newline at end of file + assert abs(ult1 - ult2) < 1e-5 From 81ff370b9d5ea3d20762f221ff4366e1f0af9b69 Mon Sep 17 00:00:00 2001 From: henrydingliu <106109320+henrydingliu@users.noreply.github.com> Date: Thu, 11 Jun 2026 19:30:57 -0700 Subject: [PATCH 43/83] Update triangle.py --- chainladder/core/triangle.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/chainladder/core/triangle.py b/chainladder/core/triangle.py index eed90ffb..284c32c4 100644 --- a/chainladder/core/triangle.py +++ b/chainladder/core/triangle.py @@ -918,6 +918,40 @@ def is_pattern(self) -> bool: def is_pattern(self, pattern: bool): self._pattern = pattern + def align_pattern(self, X:Triangle, sample_weight:Triangle|None=None) -> Triangle: + """ + Vertically align a selected pattern to origin period latest diagonal. Triangle must be a selected pattern. + + Parameters + ---------- + X: Triangle + The target triangle to align to + + sample_weight: Triangle, option (default=None) + Exposure triangle + + Returns + ------- + Triangle + Triangle of selected pattern across origin periods + + """ + if not self._pattern: + raise ValueError("Triangle is not a selected pattern, such as .ldf_ or .cdf_") + valuation = X.valuation_date + pattern = self.iloc[..., : X.shape[-1]] + a = X.iloc[0, 0] * 0 + a = a + a.nan_triangle + if X.array_backend == "sparse": + a = a - a[a.valuation < a.valuation_date] + if sample_weight: + X = X * a + sample_weight * a + else: + X = X * a + pattern = X / X * pattern + pattern.valuation_date = valuation + return pattern.latest_diagonal + @property def is_ultimate(self) -> bool: """ From ce3f6ee34cc768982c6d3764005e20bdcb036d31 Mon Sep 17 00:00:00 2001 From: henrydingliu <106109320+henrydingliu@users.noreply.github.com> Date: Thu, 11 Jun 2026 19:31:54 -0700 Subject: [PATCH 44/83] Update base.py --- chainladder/methods/base.py | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/chainladder/methods/base.py b/chainladder/methods/base.py index 26bf84fb..7ad3117e 100644 --- a/chainladder/methods/base.py +++ b/chainladder/methods/base.py @@ -25,19 +25,7 @@ def validate_X(self, X): def _align_cdf(self, X, sample_weight=None): """ Vertically align CDF to origin period latest diagonal. """ - valuation = X.valuation_date - cdf = X.cdf_.iloc[..., : X.shape[-1]] - a = X.iloc[0, 0] * 0 - a = a + a.nan_triangle - if X.array_backend == "sparse": - a = a - a[a.valuation < a.valuation_date] - if sample_weight: - X = X * a + sample_weight * a - else: - X = X * a - cdf = X / X * cdf - cdf.valuation_date = valuation - return cdf.latest_diagonal + return X.cdf_.align_pattern(X,sample_weight) def _set_ult_attr(self, ultimate): """ Ultimate scaffolding """ From 276dedfb9b46a7706577cffd2cd9f02af6e9a8e5 Mon Sep 17 00:00:00 2001 From: henrydingliu <106109320+henrydingliu@users.noreply.github.com> Date: Thu, 11 Jun 2026 19:35:22 -0700 Subject: [PATCH 45/83] Update test_triangle.py --- chainladder/core/tests/test_triangle.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/chainladder/core/tests/test_triangle.py b/chainladder/core/tests/test_triangle.py index 7d53da16..cf475f77 100644 --- a/chainladder/core/tests/test_triangle.py +++ b/chainladder/core/tests/test_triangle.py @@ -49,7 +49,10 @@ def test_link_ratio(raa, atol): raa.link_ratio * raa.iloc[:, :, :-1, :-1].values - raa.values[:, :, :-1, 1:] ).sum().sum() < atol - +def test_align_pattern(raa, atol): + with pytest.raises(ValueError): + raa.align_pattern(raa) + def test_incr_to_cum(clrd): clrd.cum_to_incr().incr_to_cum() == clrd @@ -1245,4 +1248,4 @@ def test_to_datetime_uninferrable_format_raises() -> None: development='development', columns='value', cumulative=True - ) \ No newline at end of file + ) From 7d0b241c7f37cd54d5976a621e4d0a46a45e5c80 Mon Sep 17 00:00:00 2001 From: henrydingliu <106109320+henrydingliu@users.noreply.github.com> Date: Thu, 11 Jun 2026 21:15:19 -0700 Subject: [PATCH 46/83] Update benktander.py --- chainladder/methods/benktander.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/chainladder/methods/benktander.py b/chainladder/methods/benktander.py index 6681d5b8..a83536dd 100644 --- a/chainladder/methods/benktander.py +++ b/chainladder/methods/benktander.py @@ -243,6 +243,8 @@ def predict(self, X, sample_weight=None): 2012 15914.716737 2013 17193.715555 """ + if sample_weight is None: + raise ValueError("sample_weight is required.") X_new = super().predict(X, sample_weight) X_new.expectation_ = self._get_benktander_aprioris(X, sample_weight) X_new.ultimate_ = self._get_ultimate(X_new, X_new.expectation_) From aa1744631fdb2635b98744399da0aab07e6de57e Mon Sep 17 00:00:00 2001 From: henrydingliu <106109320+henrydingliu@users.noreply.github.com> Date: Thu, 11 Jun 2026 21:16:28 -0700 Subject: [PATCH 47/83] Update test_predict.py --- chainladder/methods/tests/test_predict.py | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/chainladder/methods/tests/test_predict.py b/chainladder/methods/tests/test_predict.py index c4fab221..0b7d57cf 100644 --- a/chainladder/methods/tests/test_predict.py +++ b/chainladder/methods/tests/test_predict.py @@ -22,26 +22,17 @@ def test_predict_and_weights(estimators,atol): est = estimators().fit(raa_1989, sample_weight=apriori_1989) pred = est.predict(raa, sample_weight=apriori) assert pred + #Test validation of sample_weight requirement. Should raise a value error if no weight is supplied. + if estimators != cl.Chainladder: + with pytest.raises(ValueError): + estimators().fit(raa_1989) + with pytest.raises(ValueError): + estimators().fit(raa_1989, sample_weight=apriori_1989).predict(raa) def test_mack_predict(): mack = cl.MackChainladder().fit(raa_1989) assert mack.predict(raa_1989) -def test_capecod_fit_weight(): - """ - Test validation of sample_weight requirement. Should raise a value error if no weight is supplied. - """ - with pytest.raises(ValueError): - cl.CapeCod().fit(raa_1989) - -def test_capecod_predict_weight(): - """ - Test validation of sample_weight requirement. Should raise a value error if no weight is supplied. - """ - with pytest.raises(ValueError): - cc = cl.CapeCod().fit(raa_1989, sample_weight=apriori_1989) - cc.predict(raa) - def test_bs_random_state_predict(clrd): tri = clrd.groupby("LOB").sum().loc["wkcomp", ["CumPaidLoss", "EarnedPremNet"]] X = cl.BootstrapODPSample(random_state=100).fit_transform(tri["CumPaidLoss"]) From 00c252a690e4b8ce2c7a3e5b888848d9fe84d9b9 Mon Sep 17 00:00:00 2001 From: henrydingliu <106109320+henrydingliu@users.noreply.github.com> Date: Thu, 11 Jun 2026 22:58:12 -0700 Subject: [PATCH 48/83] Update pyproject.toml setting max sparse version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index eee129d2..ec317bf0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,7 +32,7 @@ keywords = ["actuarial", "reserving", "insurance", "chainladder", "IBNR"] dependencies = [ "pandas >=2.3.3", "scikit-learn>1.4.2", - "sparse>=0.9", + "sparse>=0.9, <=0.17.0", "numpy>=2.0", "matplotlib", # Required for TriangleDisplay.heatmap() "dill", From 4f4f2d9736df006bedd86863c40a5bd82267504b Mon Sep 17 00:00:00 2001 From: henrydingliu <106109320+henrydingliu@users.noreply.github.com> Date: Fri, 12 Jun 2026 08:23:55 -0700 Subject: [PATCH 49/83] Delete docs/friedland/chapter_8.ipynb --- docs/friedland/chapter_8.ipynb | 853 --------------------------------- 1 file changed, 853 deletions(-) delete mode 100644 docs/friedland/chapter_8.ipynb diff --git a/docs/friedland/chapter_8.ipynb b/docs/friedland/chapter_8.ipynb deleted file mode 100644 index 6b9f3515..00000000 --- a/docs/friedland/chapter_8.ipynb +++ /dev/null @@ -1,853 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 3, - "id": "9bf32865", - "metadata": {}, - "outputs": [], - "source": [ - "import pandas as pd\n", - "import chainladder as cl\n", - "\n", - "pd.set_option('display.max_columns', None)\n", - "pd.set_option('display.width', 1000)\n", - "\n", - "auto_bi = cl.load_sample('friedland_auto_bi_insurer')\n", - "\n", - "# Exhibit — Reported / Paid / reform factors (1)–(2) / adjusted claims / premium / LDF (7)–(8) / ultimate / claim ratio\n", - "exhibit = pd.DataFrame(\n", - " [\n", - " [2000, 10_000_000, 9_500_000, 1.005, 1.050, 10_050_000, 9_975_000, 10_012_500, 24_000_000, 2.954, 0.670, 19_816_540, 0.830],\n", - " [2001, 8_000_000, 7_200_000, 1.020, 1.150, 8_160_000, 8_280_000, 8_220_000, 18_000_000, 2.580, 0.670, 14_209_092, 0.790],\n", - " [2002, 9_400_000, 7_600_000, 1.030, 1.250, 9_682_000, 9_500_000, 9_591_000, 19_000_000, 2.253, 0.670, 14_477_710, 0.760],\n", - " [2003, 15_600_000, 7_800_000, 1.100, 1.350, 17_160_000, 10_530_000, 13_845_000, 23_000_000, 1.968, 0.670, 18_255_463, 0.790],\n", - " [2004, 16_500_000, 11_200_000, 1.200, 1.750, 19_800_000, 19_600_000, 19_700_000, 32_000_000, 1.719, 0.750, 25_398_225, 0.790],\n", - " [2005, 18_500_000, 10_200_000, 1.400, 2.500, 25_900_000, 25_500_000, 25_700_000, 47_000_000, 1.501, 1.000, 38_575_700, 0.820],\n", - " [2006, 16_500_000, 6_000_000, 1.800, 5.000, 29_700_000, 30_000_000, 29_850_000, 50_000_000, 1.311, 1.000, 39_133_350, 0.780],\n", - " [2007, 14_000_000, 3_000_000, 2.900, 15.000, 40_600_000, 45_000_000, 42_800_000, 57_000_000, 1.145, 1.000, 49_006_000, 0.860],\n", - " [2008, 8_700_000, 750_000, 4.000, 90.000, None, None, None, None, None, None, None, None],\n", - " ],\n", - " columns=[\n", - " 'Accident Year',\n", - " 'Reported',\n", - " 'Paid',\n", - " '(1)',\n", - " '(2)',\n", - " 'Reported to 7/1/08',\n", - " 'Paid Reform',\n", - " 'Reported Paid Claims',\n", - " 'Premium',\n", - " '(7)',\n", - " '(8)',\n", - " 'Claims',\n", - " 'Claim Ratio',\n", - " ],\n", - ")\n", - "exhibit" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f84de055", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
2008
200010,000,000
20018,000,000
20029,400,000
200315,600,000
200416,500,000
200518,500,000
200616,500,000
200714,000,000
20088,700,000
" - ], - "text/plain": [ - " 2008\n", - "2000 10000000.0\n", - "2001 8000000.0\n", - "2002 9400000.0\n", - "2003 15600000.0\n", - "2004 16500000.0\n", - "2005 18500000.0\n", - "2006 16500000.0\n", - "2007 14000000.0\n", - "2008 8700000.0" - ] - }, - "execution_count": null, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Page 140, Exhibit 1 column 2\n", - "auto_bi = cl.load_sample(\"friedland_auto_bi_insurer\")\n", - "auto_bi[\"Reported Claims\"].latest_diagonal" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f5519bd6", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
2008
20009,500,000
20017,200,000
20027,600,000
20037,800,000
200411,200,000
200510,200,000
20066,000,000
20073,000,000
2008750,000
" - ], - "text/plain": [ - " 2008\n", - "2000 9500000.0\n", - "2001 7200000.0\n", - "2002 7600000.0\n", - "2003 7800000.0\n", - "2004 11200000.0\n", - "2005 10200000.0\n", - "2006 6000000.0\n", - "2007 3000000.0\n", - "2008 750000.0" - ] - }, - "execution_count": null, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Page 140, Exhibit 1 column 3\n", - "auto_bi[\"Paid Claims\"].latest_diagonal" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "id": "ca66fbf5", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
DevelopmentConstant(patterns={'paid': {12: 90.0, 24: 15.0, 36: 5.0, 48: 2.5,\n",
-              "                                       60: 1.75, 72: 1.35, 84: 1.25, 96: 1.15,\n",
-              "                                       108: 1.05},\n",
-              "                              'reported': {12: 4.0, 24: 2.9, 36: 1.8, 48: 1.4,\n",
-              "                                           60: 1.2, 72: 1.1, 84: 1.03, 96: 1.02,\n",
-              "                                           108: 1.005}},\n",
-              "                    style='cdf')
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" - ], - "text/plain": [ - "DevelopmentConstant(patterns={'paid': {12: 90.0, 24: 15.0, 36: 5.0, 48: 2.5,\n", - " 60: 1.75, 72: 1.35, 84: 1.25, 96: 1.15,\n", - " 108: 1.05},\n", - " 'reported': {12: 4.0, 24: 2.9, 36: 1.8, 48: 1.4,\n", - " 60: 1.2, 72: 1.1, 84: 1.03, 96: 1.02,\n", - " 108: 1.005}},\n", - " style='cdf')" - ] - }, - "execution_count": 20, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Page 140, Exhibit 1 column 4-5\n", - "# Direct age(in months): value representation per LOB\n", - "patterns = {\n", - " 'reported': {12: 4.0, 24: 2.9, 36: 1.8, 48: 1.4, 60: 1.2, 72: 1.1, 84: 1.03, 96: 1.02, 108: 1.005},\n", - " 'paid': {12: 90.0, 24: 15.0, 36: 5.0, 48: 2.5, 60: 1.75, 72: 1.35, 84: 1.25, 96: 1.15, 108: 1.05}\n", - "}\n", - "\n", - "cl.DevelopmentConstant(patterns=patterns, style='cdf')\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c74260ee", - "metadata": {}, - "outputs": [], - "source": [ - "cl.DevelopmentConstant(patterns=patterns, style='cdf').fit(auto_bi[\"Paid Claims\"]).ldf_" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} From c1792bf591127214ff021d27498b0ebb39c21cca Mon Sep 17 00:00:00 2001 From: Nick Kinney Date: Fri, 12 Jun 2026 15:39:31 -0400 Subject: [PATCH 50/83] docs: use uppercase option name AUTO_SPARSE after options refactor (#889) The options refactor added Options._validate_option(), which checks names against the uppercase attribute keys. The Backends cell in triangle.ipynb still passed 'auto_sparse' and raised ValueError. (ARRAY_BACKEND was already corrected on experimental.) Closes #889. --- docs/user_guide/triangle.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/user_guide/triangle.ipynb b/docs/user_guide/triangle.ipynb index 29087933..b4a6ac1a 100644 --- a/docs/user_guide/triangle.ipynb +++ b/docs/user_guide/triangle.ipynb @@ -430,7 +430,7 @@ } ], "source": [ - "cl.options.set_option('auto_sparse', False)\n", + "cl.options.set_option('AUTO_SPARSE', False)\n", "prism = cl.load_sample('prism', array_backend='sparse')\n", "prism.array_backend" ] From 5299fb34ec2fee4dcba57738b9c616a1f0b5d973 Mon Sep 17 00:00:00 2001 From: SaguaroDev <74339271+SaguaroDev@users.noreply.github.com> Date: Sun, 14 Jun 2026 07:50:42 -0400 Subject: [PATCH 51/83] docs: reset AUTO_SPARSE after backend demo so prism stays sparse The options refactor made set_option validate names against uppercase keys. After #895 corrected 'auto_sparse' to 'AUTO_SPARSE', the call actually succeeds and leaves AUTO_SPARSE=False set globally for the rest of the notebook. Downstream the prism timeit cells reload the sample with no explicit backend, so they loaded dense and OOM-killed the doctest kernel (surfacing as the runner shutdown/cancel on Doctest 3.12). Previously the lowercase name raised ValueError, so the option never actually changed. Reset AUTO_SPARSE immediately after the demo, mirroring the cupy cell's reset_option(), so the sample loads sparse and the build completes. --- docs/user_guide/triangle.ipynb | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/docs/user_guide/triangle.ipynb b/docs/user_guide/triangle.ipynb index b4a6ac1a..0db555b7 100644 --- a/docs/user_guide/triangle.ipynb +++ b/docs/user_guide/triangle.ipynb @@ -414,24 +414,14 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "id": "bc2151fc", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'sparse'" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "cl.options.set_option('AUTO_SPARSE', False)\n", "prism = cl.load_sample('prism', array_backend='sparse')\n", + "cl.options.reset_option('AUTO_SPARSE')\n", "prism.array_backend" ] }, From e7c0bf00d7a63aafd3f739f4855fe9e712f06b90 Mon Sep 17 00:00:00 2001 From: Nick Kinney Date: Sun, 14 Jun 2026 13:14:03 -0400 Subject: [PATCH 52/83] docs: reset ULT_VAL after demo in methods notebook The Ultimates section sets ULT_VAL to 2050 to demonstrate the option but never restores it. The mutated global then propagates to later cells, and the Complete Triangles run-off cell raises: ValueError: Could not convert object to NumPy timedelta Add a reset_option('ULT_VAL') cell immediately after the demo so the global returns to its default and downstream cells execute cleanly. --- docs/user_guide/methods.ipynb | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/user_guide/methods.ipynb b/docs/user_guide/methods.ipynb index 4679631b..de0df830 100644 --- a/docs/user_guide/methods.ipynb +++ b/docs/user_guide/methods.ipynb @@ -160,6 +160,18 @@ "cl.options.get_option('ULT_VAL')" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "ult-val-reset", + "metadata": {}, + "outputs": [], + "source": [ + "# Reset ULT_VAL to its default so the demo above does not affect later cells\n", + "cl.options.reset_option('ULT_VAL')\n", + "cl.options.get_option('ULT_VAL')" + ] + }, { "cell_type": "markdown", "id": "retired-january", From 5b6c2f602c4e42ec3a3bbe0fce730cafb60b67a8 Mon Sep 17 00:00:00 2001 From: henrydingliu <106109320+henrydingliu@users.noreply.github.com> Date: Sun, 14 Jun 2026 10:49:59 -0700 Subject: [PATCH 53/83] Update stochastic-tutorial.ipynb --- docs/getting_started/tutorials/stochastic-tutorial.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/getting_started/tutorials/stochastic-tutorial.ipynb b/docs/getting_started/tutorials/stochastic-tutorial.ipynb index 903ed71b..4637b638 100644 --- a/docs/getting_started/tutorials/stochastic-tutorial.ipynb +++ b/docs/getting_started/tutorials/stochastic-tutorial.ipynb @@ -241,7 +241,7 @@ } }, "source": [ - "clrd_first_lags.link_ratio.to_frame(origin_as_datetime=True).mean()[0]" + "clrd_first_lags.link_ratio.to_frame(origin_as_datetime=True).mean().iloc[0]" ], "outputs": [ { From 24ec6cd3eb9ceb771b7b09f0f4a5e94cdbcaab18 Mon Sep 17 00:00:00 2001 From: henrydingliu <106109320+henrydingliu@users.noreply.github.com> Date: Sun, 14 Jun 2026 11:19:10 -0700 Subject: [PATCH 54/83] Update pyproject.toml --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index ec317bf0..357cf3ab 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -79,6 +79,7 @@ test = [ "dask", 'statsmodels' ] +pandas3 = ["pandas>=3"] performance = [ "dask", # For distributed computing "cupy", # For GPU acceleration (if available) From 9bb9721b7b1b4cdfeddc2a9cd1c5db76c43711c6 Mon Sep 17 00:00:00 2001 From: henrydingliu <106109320+henrydingliu@users.noreply.github.com> Date: Sun, 14 Jun 2026 11:24:29 -0700 Subject: [PATCH 55/83] Update pyproject.toml --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 357cf3ab..ec317bf0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -79,7 +79,6 @@ test = [ "dask", 'statsmodels' ] -pandas3 = ["pandas>=3"] performance = [ "dask", # For distributed computing "cupy", # For GPU acceleration (if available) From 39ed398e360541ffd8d94efa9d30a084aafe1ae5 Mon Sep 17 00:00:00 2001 From: henrydingliu <106109320+henrydingliu@users.noreply.github.com> Date: Sun, 14 Jun 2026 11:26:49 -0700 Subject: [PATCH 56/83] Update pytest.yml --- .github/workflows/pytest.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index b870da3a..63342eb9 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -91,3 +91,19 @@ jobs: flags: unittests name: codecov-umbrella fail_ci_if_error: false + doctest-pandas3: + name: Doctest (ubuntu-latest, 3.12, Pandas3) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - name: Install uv + uses: astral-sh/setup-uv@v7 + with: + version: "latest" + python-version: "3.12" + - name: Install dependencies + run: | + uv sync --extra docs + uv pip install "pandas>=3" + - name: Run doctests + run: uv run --directory docs jb build . --builder=custom --custom-builder=doctest From d3312d4d97f62b70c9a3eecf162560c8b089ef57 Mon Sep 17 00:00:00 2001 From: henrydingliu <106109320+henrydingliu@users.noreply.github.com> Date: Sun, 14 Jun 2026 11:40:29 -0700 Subject: [PATCH 57/83] Update pytest.yml --- .github/workflows/pytest.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 63342eb9..f7bf980b 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -102,8 +102,6 @@ jobs: version: "latest" python-version: "3.12" - name: Install dependencies - run: | - uv sync --extra docs - uv pip install "pandas>=3" + run: uv sync --extra docs - name: Run doctests - run: uv run --directory docs jb build . --builder=custom --custom-builder=doctest + run: uv run --with "pandas>=3,<4" --directory docs jb build . --builder=custom --custom-builder=doctest From aff286f8fc087de89112de6abd502c60493ef908 Mon Sep 17 00:00:00 2001 From: henrydingliu <106109320+henrydingliu@users.noreply.github.com> Date: Sun, 14 Jun 2026 11:58:40 -0700 Subject: [PATCH 58/83] Update pytest.yml --- .github/workflows/pytest.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index f7bf980b..aebf1149 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -105,3 +105,11 @@ jobs: run: uv sync --extra docs - name: Run doctests run: uv run --with "pandas>=3,<4" --directory docs jb build . --builder=custom --custom-builder=doctest + - name: Upload sphinx error logs + if: failure() || always() + uses: actions/upload-artifact@v4 + with: + name: sphinx-error-logs + path: /tmp/sphinx-err-*.log + if-no-files-found: ignore + From e432fc7f3aa315ce2c019e01ce4f760fd3147544 Mon Sep 17 00:00:00 2001 From: henrydingliu <106109320+henrydingliu@users.noreply.github.com> Date: Sun, 14 Jun 2026 12:26:07 -0700 Subject: [PATCH 59/83] Update pytest.yml --- .github/workflows/pytest.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index aebf1149..ac973f7e 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -105,11 +105,11 @@ jobs: run: uv sync --extra docs - name: Run doctests run: uv run --with "pandas>=3,<4" --directory docs jb build . --builder=custom --custom-builder=doctest - - name: Upload sphinx error logs + - name: Upload mystnb error logs if: failure() || always() uses: actions/upload-artifact@v4 with: name: sphinx-error-logs - path: /tmp/sphinx-err-*.log + path: /tmp/*-err.log if-no-files-found: ignore From f6ed637eb0d19e74f7333a91968eed4c1608f48b Mon Sep 17 00:00:00 2001 From: henrydingliu <106109320+henrydingliu@users.noreply.github.com> Date: Sun, 14 Jun 2026 13:01:42 -0700 Subject: [PATCH 60/83] Update pytest.yml --- .github/workflows/pytest.yml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index ac973f7e..03173b72 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -106,10 +106,8 @@ jobs: - name: Run doctests run: uv run --with "pandas>=3,<4" --directory docs jb build . --builder=custom --custom-builder=doctest - name: Upload mystnb error logs - if: failure() || always() + if: failure() uses: actions/upload-artifact@v4 with: - name: sphinx-error-logs - path: /tmp/*-err.log - if-no-files-found: ignore - + name: myst-error-logs + path: docs/_build/doctest/reports/*.log From 2e7ce68f2c49fb48855a84ccb475f120ccfe9970 Mon Sep 17 00:00:00 2001 From: henrydingliu <106109320+henrydingliu@users.noreply.github.com> Date: Sun, 14 Jun 2026 13:10:05 -0700 Subject: [PATCH 61/83] Update pytest.yml --- .github/workflows/pytest.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 03173b72..808c4344 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -106,8 +106,9 @@ jobs: - name: Run doctests run: uv run --with "pandas>=3,<4" --directory docs jb build . --builder=custom --custom-builder=doctest - name: Upload mystnb error logs - if: failure() + if: failure() || always() uses: actions/upload-artifact@v4 with: name: myst-error-logs path: docs/_build/doctest/reports/*.log + if-no-files-found: ignore From 54045027cd4b4b7ce66ed58000fb9ff02638ab18 Mon Sep 17 00:00:00 2001 From: henrydingliu <106109320+henrydingliu@users.noreply.github.com> Date: Sun, 14 Jun 2026 13:16:48 -0700 Subject: [PATCH 62/83] Update pytest.yml --- .github/workflows/pytest.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 808c4344..3d7bf42f 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -110,5 +110,5 @@ jobs: uses: actions/upload-artifact@v4 with: name: myst-error-logs - path: docs/_build/doctest/reports/*.log + path: docs/_build/doctest/reports/**/*.log if-no-files-found: ignore From 7ed258587a608dd25f28d505ddf6cbbf5f54d11a Mon Sep 17 00:00:00 2001 From: Nick Kinney Date: Sun, 14 Jun 2026 17:00:49 -0400 Subject: [PATCH 63/83] build: floor patsy>=1.0.2 and statsmodels>=0.14.6 for pandas 3 (#889) The new Pandas3 doctest job on experimental surfaced two notebook import/exec errors that do not appear in the pandas 2 build: - user_guide/development.ipynb raised TypeError: Cannot interpret '' as a data type from patsy 1.0.1, which calls np.issubdtype on pandas 3's new default str dtype. patsy 1.0.2 handles it. - getting_started/tutorials/stochastic-tutorial.ipynb raised TypeError: deprecate_kwarg() missing 1 required positional argument at 'import statsmodels.api'. statsmodels 0.14.5 re-exports pandas' deprecate_kwarg with the old signature; pandas 3 changed it. statsmodels 0.14.6 wraps it in a compat shim. Both are dependency-floor issues, not chainladder or notebook-content bugs. Refresh uv.lock so the Pandas3 job resolves the fixed versions. The remaining plot_munich.ipynb error (YearBegin period frequency) is a genuine pandas 3 plotting regression and is left for a separate fix. --- pyproject.toml | 6 ++-- uv.lock | 78 +++++++++++++++++++++++++------------------------- 2 files changed, 42 insertions(+), 42 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index ec317bf0..ae476921 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,7 +36,7 @@ dependencies = [ "numpy>=2.0", "matplotlib", # Required for TriangleDisplay.heatmap() "dill", - "patsy", + "patsy>=1.0.2", # 1.0.1 breaks on pandas 3 default str dtype (np.issubdtype on StringDtype) ] [project.urls] @@ -67,7 +67,7 @@ docs = [ "ipython", "parso>=0.8", "polars", # For docs examples - "statsmodels" # For docs example + "statsmodels>=0.14.6" # For docs example; <0.14.6 fails to import under pandas 3 ] test = [ "lxml", @@ -77,7 +77,7 @@ test = [ "ipython", "pytest-cov", # For coverage testing "dask", - 'statsmodels' + 'statsmodels>=0.14.6' ] performance = [ "dask", # For distributed computing diff --git a/uv.lock b/uv.lock index 9a240d4f..ad742ae5 100644 --- a/uv.lock +++ b/uv.lock @@ -288,7 +288,7 @@ requires-dist = [ { name = "numpydoc", marker = "extra == 'docs'" }, { name = "pandas", specifier = ">=2.3.3" }, { name = "parso", marker = "extra == 'docs'", specifier = ">=0.8" }, - { name = "patsy" }, + { name = "patsy", specifier = ">=1.0.2" }, { name = "polars", marker = "extra == 'docs'" }, { name = "pyright", marker = "extra == 'dev'" }, { name = "pytest", marker = "extra == 'dev'" }, @@ -301,8 +301,8 @@ requires-dist = [ { name = "sphinx", marker = "extra == 'docs'" }, { name = "sphinx-rtd-theme", marker = "extra == 'docs'" }, { name = "sphinx-togglebutton", marker = "extra == 'docs'" }, - { name = "statsmodels", marker = "extra == 'docs'" }, - { name = "statsmodels", marker = "extra == 'test'" }, + { name = "statsmodels", marker = "extra == 'docs'", specifier = ">=0.14.6" }, + { name = "statsmodels", marker = "extra == 'test'", specifier = ">=0.14.6" }, ] provides-extras = ["dev", "docs", "test", "performance", "all"] @@ -2193,15 +2193,15 @@ wheels = [ [[package]] name = "patsy" -version = "1.0.1" +version = "1.0.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "numpy", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d1/81/74f6a65b848ffd16c18f920620ce999fe45fe27f01ab3911260ce4ed85e4/patsy-1.0.1.tar.gz", hash = "sha256:e786a9391eec818c054e359b737bbce692f051aee4c661f4141cc88fb459c0c4", size = 396010, upload-time = "2024-11-12T14:10:54.642Z" } +sdist = { url = "https://files.pythonhosted.org/packages/be/44/ed13eccdd0519eff265f44b670d46fbb0ec813e2274932dc1c0e48520f7d/patsy-1.0.2.tar.gz", hash = "sha256:cdc995455f6233e90e22de72c37fcadb344e7586fb83f06696f54d92f8ce74c0", size = 399942, upload-time = "2025-10-20T16:17:37.535Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/87/2b/b50d3d08ea0fc419c183a84210571eba005328efa62b6b98bc28e9ead32a/patsy-1.0.1-py2.py3-none-any.whl", hash = "sha256:751fb38f9e97e62312e921a1954b81e1bb2bcda4f5eeabaf94db251ee791509c", size = 232923, upload-time = "2024-11-12T14:10:52.85Z" }, + { url = "https://files.pythonhosted.org/packages/f1/70/ba4b949bdc0490ab78d545459acd7702b211dfccf7eb89bbc1060f52818d/patsy-1.0.2-py2.py3-none-any.whl", hash = "sha256:37bfddbc58fcf0362febb5f54f10743f8b21dd2aa73dec7e7ef59d1b02ae668a", size = 233301, upload-time = "2025-10-20T16:17:36.563Z" }, ] [[package]] @@ -3408,7 +3408,7 @@ wheels = [ [[package]] name = "statsmodels" -version = "0.14.5" +version = "0.14.6" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, @@ -3419,38 +3419,38 @@ dependencies = [ { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "scipy", version = "1.16.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/64/cc/8c1bf59bf8203dea1bf2ea811cfe667d7bcc6909c83d8afb02b08e30f50b/statsmodels-0.14.5.tar.gz", hash = "sha256:de260e58cccfd2ceddf835b55a357233d6ca853a1aa4f90f7553a52cc71c6ddf", size = 20525016, upload-time = "2025-07-07T12:14:23.195Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3a/2c/55b2a5d10c1a211ecab3f792021d2581bbe1c5ca0a1059f6715dddc6899d/statsmodels-0.14.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9fc2b5cdc0c95cba894849651fec1fa1511d365e3eb72b0cc75caac44077cd48", size = 10058241, upload-time = "2025-07-07T12:13:16.286Z" }, - { url = "https://files.pythonhosted.org/packages/66/d9/6967475805de06691e951072d05e40e3f1c71b6221bb92401193ee19bd2a/statsmodels-0.14.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b8d96b0bbaeabd3a557c35cc7249baa9cfbc6dd305c32a9f2cbdd7f46c037e7f", size = 9734017, upload-time = "2025-07-07T12:05:08.498Z" }, - { url = "https://files.pythonhosted.org/packages/df/a8/803c280419a7312e2472969fe72cf461c1210a27770a662cbe3b5cd7c6fe/statsmodels-0.14.5-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:145bc39b2cb201efb6c83cc3f2163c269e63b0d4809801853dec6f440bd3bc37", size = 10459677, upload-time = "2025-07-07T14:21:51.809Z" }, - { url = "https://files.pythonhosted.org/packages/a1/25/edf20acbd670934b02cd9344e29c9a03ce040122324b3491bb075ae76b2d/statsmodels-0.14.5-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d7c14fb2617bb819fb2532e1424e1da2b98a3419a80e95f33365a72d437d474e", size = 10678631, upload-time = "2025-07-07T14:22:05.496Z" }, - { url = "https://files.pythonhosted.org/packages/64/22/8b1e38310272e766abd6093607000a81827420a3348f09eff08a9e54cbaf/statsmodels-0.14.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1e9742d8a5ac38a3bfc4b7f4b0681903920f20cbbf466d72b1fd642033846108", size = 10699273, upload-time = "2025-07-07T14:22:19.487Z" }, - { url = "https://files.pythonhosted.org/packages/d1/6f/6de51f1077b7cef34611f1d6721392ea170153251b4d977efcf6d100f779/statsmodels-0.14.5-cp310-cp310-win_amd64.whl", hash = "sha256:1cab9e6fce97caf4239cdb2df375806937da5d0b7ba2699b13af33a07f438464", size = 9644785, upload-time = "2025-07-07T12:05:20.927Z" }, - { url = "https://files.pythonhosted.org/packages/14/30/fd49902b30416b828de763e161c0d6e2cc04d119ae4fbdd3f3b43dc8f1be/statsmodels-0.14.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4b7091a8442076c708c926de3603653a160955e80a2b6d931475b7bb8ddc02e5", size = 10053330, upload-time = "2025-07-07T12:07:39.689Z" }, - { url = "https://files.pythonhosted.org/packages/ca/c1/2654541ff6f5790d01d1e5ba36405fde873f4a854f473e90b4fe56b37333/statsmodels-0.14.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:128872be8f3208f4446d91ea9e4261823902fc7997fee7e1a983eb62fd3b7c6e", size = 9735555, upload-time = "2025-07-07T12:13:28.935Z" }, - { url = "https://files.pythonhosted.org/packages/ce/da/6ebb64d0db4e86c0d2d9cde89e03247702da0ab191789f7813d4f9a348da/statsmodels-0.14.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f2ad5aee04ae7196c429df2174df232c057e478c5fa63193d01c8ec9aae04d31", size = 10307522, upload-time = "2025-07-07T14:22:32.853Z" }, - { url = "https://files.pythonhosted.org/packages/67/49/ac803ca093ec3845184a752a91cd84511245e1f97103b15cfe32794a3bb0/statsmodels-0.14.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f402fc793458dd6d96e099acb44cd1de1428565bf7ef3030878a8daff091f08a", size = 10474665, upload-time = "2025-07-07T14:22:46.011Z" }, - { url = "https://files.pythonhosted.org/packages/f0/c8/ae82feb00582f4814fac5d2cb3ec32f93866b413cf5878b2fe93688ec63c/statsmodels-0.14.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:26c028832730aebfbfd4e7501694e1f9ad31ec8536e776716673f4e7afd4059a", size = 10713120, upload-time = "2025-07-07T14:23:00.067Z" }, - { url = "https://files.pythonhosted.org/packages/05/ac/4276459ea71aa46e2967ea283fc88ee5631c11f29a06787e16cf4aece1b8/statsmodels-0.14.5-cp311-cp311-win_amd64.whl", hash = "sha256:ec56f771d9529cdc17ed2fb2a950d100b6e83a7c5372aae8ac5bb065c474b856", size = 9640980, upload-time = "2025-07-07T12:05:33.085Z" }, - { url = "https://files.pythonhosted.org/packages/5f/a5/fcc4f5f16355660ce7a1742e28a43e3a9391b492fc4ff29fdd6893e81c05/statsmodels-0.14.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:37e7364a39f9aa3b51d15a208c2868b90aadb8412f868530f5cba9197cb00eaa", size = 10042891, upload-time = "2025-07-07T12:13:41.671Z" }, - { url = "https://files.pythonhosted.org/packages/1c/6f/db0cf5efa48277ac6218d9b981c8fd5e63c4c43e0d9d65015fdc38eed0ef/statsmodels-0.14.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4263d7f4d0f1d5ac6eb4db22e1ee34264a14d634b9332c975c9d9109b6b46e12", size = 9698912, upload-time = "2025-07-07T12:07:54.674Z" }, - { url = "https://files.pythonhosted.org/packages/4a/93/4ddc3bc4a59c51e6a57c49df1b889882c40d9e141e855b3517f6a8de3232/statsmodels-0.14.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:86224f6e36f38486e471e75759d241fe2912d8bc25ab157d54ee074c6aedbf45", size = 10237801, upload-time = "2025-07-07T14:23:12.593Z" }, - { url = "https://files.pythonhosted.org/packages/66/de/dc6bf2f6e8c8eb4c5815560ebdbdf2d69a767bc0f65fde34bc086cf5b36d/statsmodels-0.14.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c3dd760a6fa80cd5e0371685c697bb9c2c0e6e1f394d975e596a1e6d0bbb9372", size = 10424154, upload-time = "2025-07-07T14:23:25.365Z" }, - { url = "https://files.pythonhosted.org/packages/16/4f/2d5a8d14bebdf2b03b3ea89b8c6a2c837bb406ba5b7a41add8bd303bce29/statsmodels-0.14.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6264fb00e02f858b86bd01ef2dc05055a71d4a0cc7551b9976b07b0f0e6cf24f", size = 10652915, upload-time = "2025-07-07T14:23:39.337Z" }, - { url = "https://files.pythonhosted.org/packages/df/4c/2feda3a9f0e17444a84ba5398ada6a4d2e1b8f832760048f04e2b8ea0c41/statsmodels-0.14.5-cp312-cp312-win_amd64.whl", hash = "sha256:b2ed065bfbaf8bb214c7201656df840457c2c8c65e1689e3eb09dc7440f9c61c", size = 9611236, upload-time = "2025-07-07T12:08:06.794Z" }, - { url = "https://files.pythonhosted.org/packages/84/fd/4c374108cf108b3130240a5b45847a61f70ddf973429044a81a05189b046/statsmodels-0.14.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:906263134dd1a640e55ecb01fda4a9be7b9e08558dba9e4c4943a486fdb0c9c8", size = 10013958, upload-time = "2025-07-07T14:35:01.04Z" }, - { url = "https://files.pythonhosted.org/packages/5a/36/bf3d7f0e36acd3ba9ec0babd79ace25506b6872780cbd710fb7cd31f0fa2/statsmodels-0.14.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9118f76344f77cffbb3a9cbcff8682b325be5eed54a4b3253e09da77a74263d3", size = 9674243, upload-time = "2025-07-07T12:08:22.571Z" }, - { url = "https://files.pythonhosted.org/packages/90/ce/a55a6f37b5277683ceccd965a5828b24672bbc427db6b3969ae0b0fc29fb/statsmodels-0.14.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9dc4ee159070557c9a6c000625d85f653de437772fe7086857cff68f501afe45", size = 10219521, upload-time = "2025-07-07T14:23:52.646Z" }, - { url = "https://files.pythonhosted.org/packages/1e/48/973da1ee8bc0743519759e74c3615b39acdc3faf00e0a0710f8c856d8c9d/statsmodels-0.14.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a085d47c8ef5387279a991633883d0e700de2b0acc812d7032d165888627bef", size = 10453538, upload-time = "2025-07-07T14:24:06.959Z" }, - { url = "https://files.pythonhosted.org/packages/c7/d6/18903fb707afd31cf1edaec5201964dbdacb2bfae9a22558274647a7c88f/statsmodels-0.14.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9f866b2ebb2904b47c342d00def83c526ef2eb1df6a9a3c94ba5fe63d0005aec", size = 10681584, upload-time = "2025-07-07T14:24:21.038Z" }, - { url = "https://files.pythonhosted.org/packages/44/d6/80df1bbbfcdc50bff4152f43274420fa9856d56e234d160d6206eb1f5827/statsmodels-0.14.5-cp313-cp313-win_amd64.whl", hash = "sha256:2a06bca03b7a492f88c8106103ab75f1a5ced25de90103a89f3a287518017939", size = 9604641, upload-time = "2025-07-07T12:08:36.23Z" }, - { url = "https://files.pythonhosted.org/packages/fd/6c/0fb40a89d715412160097c6f3387049ed88c9bd866c8838a8852c705ae2f/statsmodels-0.14.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:07c4dad25bbb15864a31b4917a820f6d104bdc24e5ddadcda59027390c3bed9e", size = 10211256, upload-time = "2025-10-30T13:46:58.591Z" }, - { url = "https://files.pythonhosted.org/packages/88/4a/e36fe8b19270ab3e80df357da924c6c029cab0fb9a0fbd28aaf49341707d/statsmodels-0.14.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:babb067c852e966c2c933b79dbb5d0240919d861941a2ef6c0e13321c255528d", size = 10110933, upload-time = "2025-10-30T13:47:11.774Z" }, - { url = "https://files.pythonhosted.org/packages/8a/bf/1b7e7b1a6c09a88a9c5c9e60622c050dfd08af11c2e6d4a42dbc71b32ee1/statsmodels-0.14.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:110194b137286173cc676d7bad0119a197778de6478fc6cbdc3b33571165ac1e", size = 10253981, upload-time = "2025-10-30T16:32:22.399Z" }, - { url = "https://files.pythonhosted.org/packages/b8/d0/f95da95524bdd99613923ca61a3036d1308cee1290e5e8acb89f51736a8c/statsmodels-0.14.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c8a9c384a60c80731b278e7fd18764364c8817f4995b13a175d636f967823d1", size = 10460450, upload-time = "2025-10-30T16:32:44.985Z" }, - { url = "https://files.pythonhosted.org/packages/28/bb/59e7be0271be264b7b541baf3973f97747740950bfd5115de731f63da8ab/statsmodels-0.14.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:557df3a870a57248df744fdfcc444ecbc5bdbf1c042b8a8b5d8e3e797830dc2a", size = 10694060, upload-time = "2025-10-30T16:33:07.656Z" }, - { url = "https://files.pythonhosted.org/packages/8b/c0/b28d0fd0347ea38d3610052f479e4b922eb33bb8790817f93cd89e6e08ba/statsmodels-0.14.5-cp314-cp314-win_amd64.whl", hash = "sha256:95af7a9c4689d514f4341478b891f867766f3da297f514b8c4adf08f4fa61d03", size = 9648961, upload-time = "2025-10-30T13:47:24.303Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/0d/81/e8d74b34f85285f7335d30c5e3c2d7c0346997af9f3debf9a0a9a63de184/statsmodels-0.14.6.tar.gz", hash = "sha256:4d17873d3e607d398b85126cd4ed7aad89e4e9d89fc744cdab1af3189a996c2a", size = 20689085, upload-time = "2025-12-05T23:08:39.522Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/6d/9ec309a175956f88eb8420ac564297f37cf9b1f73f89db74da861052dc29/statsmodels-0.14.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f4ff0649a2df674c7ffb6fa1a06bffdb82a6adf09a48e90e000a15a6aaa734b0", size = 10142419, upload-time = "2025-12-05T19:27:35.625Z" }, + { url = "https://files.pythonhosted.org/packages/86/8f/338c5568315ec5bf3ac7cd4b71e34b98cb3b0f834919c0c04a0762f878a1/statsmodels-0.14.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:109012088b3e370080846ab053c76d125268631410142daad2f8c10770e8e8d9", size = 10022819, upload-time = "2025-12-05T19:27:49.385Z" }, + { url = "https://files.pythonhosted.org/packages/b0/77/5fc4cbc2d608f9b483b0675f82704a8bcd672962c379fe4d82100d388dbf/statsmodels-0.14.6-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e93bd5d220f3cb6fc5fc1bffd5b094966cab8ee99f6c57c02e95710513d6ac3f", size = 10118927, upload-time = "2025-12-05T23:07:51.256Z" }, + { url = "https://files.pythonhosted.org/packages/94/55/b86c861c32186403fe121d9ab27bc16d05839b170d92a978beb33abb995e/statsmodels-0.14.6-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:06eec42d682fdb09fe5d70a05930857efb141754ec5a5056a03304c1b5e32fd9", size = 10413015, upload-time = "2025-12-05T23:08:53.95Z" }, + { url = "https://files.pythonhosted.org/packages/f9/be/daf0dba729ccdc4176605f4a0fd5cfe71cdda671749dca10e74a732b8b1c/statsmodels-0.14.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0444e88557df735eda7db330806fe09d51c9f888bb1f5906cb3a61fb1a3ed4a8", size = 10441248, upload-time = "2025-12-05T23:09:09.353Z" }, + { url = "https://files.pythonhosted.org/packages/9a/1c/2e10b7c7cc44fa418272996bf0427b8016718fd62f995d9c1f7ab37adf35/statsmodels-0.14.6-cp310-cp310-win_amd64.whl", hash = "sha256:e83a9abe653835da3b37fb6ae04b45480c1de11b3134bd40b09717192a1456ea", size = 9583410, upload-time = "2025-12-05T19:28:02.086Z" }, + { url = "https://files.pythonhosted.org/packages/a9/4d/df4dd089b406accfc3bb5ee53ba29bb3bdf5ae61643f86f8f604baa57656/statsmodels-0.14.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6ad5c2810fc6c684254a7792bf1cbaf1606cdee2a253f8bd259c43135d87cfb4", size = 10121514, upload-time = "2025-12-05T19:28:16.521Z" }, + { url = "https://files.pythonhosted.org/packages/82/af/ec48daa7f861f993b91a0dcc791d66e1cf56510a235c5cbd2ab991a31d5c/statsmodels-0.14.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:341fa68a7403e10a95c7b6e41134b0da3a7b835ecff1eb266294408535a06eb6", size = 10003346, upload-time = "2025-12-05T19:28:29.568Z" }, + { url = "https://files.pythonhosted.org/packages/a9/2c/c8f7aa24cd729970728f3f98822fb45149adc216f445a9301e441f7ac760/statsmodels-0.14.6-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bdf1dfe2a3ca56f5529118baf33a13efed2783c528f4a36409b46bbd2d9d48eb", size = 10129872, upload-time = "2025-12-05T23:09:25.724Z" }, + { url = "https://files.pythonhosted.org/packages/40/c6/9ae8e9b0721e9b6eb5f340c3a0ce8cd7cce4f66e03dd81f80d60f111987f/statsmodels-0.14.6-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3764ba8195c9baf0925a96da0743ff218067a269f01d155ca3558deed2658ca", size = 10381964, upload-time = "2025-12-05T23:09:41.326Z" }, + { url = "https://files.pythonhosted.org/packages/28/8c/cf3d30c8c2da78e2ad1f50ade8b7fabec3ff4cdfc56fbc02e097c4577f90/statsmodels-0.14.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9e8d2e519852adb1b420e018f5ac6e6684b2b877478adf7fda2cfdb58f5acb5d", size = 10409611, upload-time = "2025-12-05T23:09:57.131Z" }, + { url = "https://files.pythonhosted.org/packages/bf/cc/018f14ecb58c6cb89de9d52695740b7d1f5a982aa9ea312483ea3c3d5f77/statsmodels-0.14.6-cp311-cp311-win_amd64.whl", hash = "sha256:2738a00fca51196f5a7d44b06970ace6b8b30289839e4808d656f8a98e35faa7", size = 9580385, upload-time = "2025-12-05T19:28:42.778Z" }, + { url = "https://files.pythonhosted.org/packages/25/ce/308e5e5da57515dd7cab3ec37ea2d5b8ff50bef1fcc8e6d31456f9fae08e/statsmodels-0.14.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fe76140ae7adc5ff0e60a3f0d56f4fffef484efa803c3efebf2fcd734d72ecb5", size = 10091932, upload-time = "2025-12-05T19:28:55.446Z" }, + { url = "https://files.pythonhosted.org/packages/05/30/affbabf3c27fb501ec7b5808230c619d4d1a4525c07301074eb4bda92fa9/statsmodels-0.14.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:26d4f0ed3b31f3c86f83a92f5c1f5cbe63fc992cd8915daf28ca49be14463a1c", size = 9997345, upload-time = "2025-12-05T19:29:10.278Z" }, + { url = "https://files.pythonhosted.org/packages/48/f5/3a73b51e6450c31652c53a8e12e24eac64e3824be816c0c2316e7dbdcb7d/statsmodels-0.14.6-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8c00a42863e4f4733ac9d078bbfad816249c01451740e6f5053ecc7db6d6368", size = 10058649, upload-time = "2025-12-05T23:10:12.775Z" }, + { url = "https://files.pythonhosted.org/packages/81/68/dddd76117df2ef14c943c6bbb6618be5c9401280046f4ddfc9fb4596a1b8/statsmodels-0.14.6-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:19b58cf7474aa9e7e3b0771a66537148b2df9b5884fbf156096c0e6c1ff0469d", size = 10339446, upload-time = "2025-12-05T23:10:28.503Z" }, + { url = "https://files.pythonhosted.org/packages/56/4a/dce451c74c4050535fac1ec0c14b80706d8fc134c9da22db3c8a0ec62c33/statsmodels-0.14.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:81e7dcc5e9587f2567e52deaff5220b175bf2f648951549eae5fc9383b62bc37", size = 10368705, upload-time = "2025-12-05T23:10:44.339Z" }, + { url = "https://files.pythonhosted.org/packages/60/15/3daba2df40be8b8a9a027d7f54c8dedf24f0d81b96e54b52293f5f7e3418/statsmodels-0.14.6-cp312-cp312-win_amd64.whl", hash = "sha256:b5eb07acd115aa6208b4058211138393a7e6c2cf12b6f213ede10f658f6a714f", size = 9543991, upload-time = "2025-12-05T23:10:58.536Z" }, + { url = "https://files.pythonhosted.org/packages/81/59/a5aad5b0cc266f5be013db8cde563ac5d2a025e7efc0c328d83b50c72992/statsmodels-0.14.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:47ee7af083623d2091954fa71c7549b8443168f41b7c5dce66510274c50fd73e", size = 10072009, upload-time = "2025-12-05T23:11:14.021Z" }, + { url = "https://files.pythonhosted.org/packages/53/dd/d8cfa7922fc6dc3c56fa6c59b348ea7de829a94cd73208c6f8202dd33f17/statsmodels-0.14.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:aa60d82e29fcd0a736e86feb63a11d2380322d77a9369a54be8b0965a3985f71", size = 9980018, upload-time = "2025-12-05T23:11:30.907Z" }, + { url = "https://files.pythonhosted.org/packages/ee/77/0ec96803eba444efd75dba32f2ef88765ae3e8f567d276805391ec2c98c6/statsmodels-0.14.6-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:89ee7d595f5939cc20bf946faedcb5137d975f03ae080f300ebb4398f16a5bd4", size = 10060269, upload-time = "2025-12-05T23:11:46.338Z" }, + { url = "https://files.pythonhosted.org/packages/10/b9/fd41f1f6af13a1a1212a06bb377b17762feaa6d656947bf666f76300fc05/statsmodels-0.14.6-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:730f3297b26749b216a06e4327fe0be59b8d05f7d594fb6caff4287b69654589", size = 10324155, upload-time = "2025-12-05T23:12:01.805Z" }, + { url = "https://files.pythonhosted.org/packages/ee/0f/a6900e220abd2c69cd0a07e3ad26c71984be6061415a60e0f17b152ecf08/statsmodels-0.14.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f1c08befa85e93acc992b72a390ddb7bd876190f1360e61d10cf43833463bc9c", size = 10349765, upload-time = "2025-12-05T23:12:18.018Z" }, + { url = "https://files.pythonhosted.org/packages/98/08/b79f0c614f38e566eebbdcff90c0bcacf3c6ba7a5bbb12183c09c29ca400/statsmodels-0.14.6-cp313-cp313-win_amd64.whl", hash = "sha256:8021271a79f35b842c02a1794465a651a9d06ec2080f76ebc3b7adce77d08233", size = 9540043, upload-time = "2025-12-05T23:12:33.887Z" }, + { url = "https://files.pythonhosted.org/packages/71/de/09540e870318e0c7b58316561d417be45eff731263b4234fdd2eee3511a8/statsmodels-0.14.6-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:00781869991f8f02ad3610da6627fd26ebe262210287beb59761982a8fa88cae", size = 10069403, upload-time = "2025-12-05T23:12:48.424Z" }, + { url = "https://files.pythonhosted.org/packages/ab/f0/63c1bfda75dc53cee858006e1f46bd6d6f883853bea1b97949d0087766ca/statsmodels-0.14.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:73f305fbf31607b35ce919fae636ab8b80d175328ed38fdc6f354e813b86ee37", size = 9989253, upload-time = "2025-12-05T23:13:05.274Z" }, + { url = "https://files.pythonhosted.org/packages/c1/98/b0dfb4f542b2033a3341aa5f1bdd97024230a4ad3670c5b0839d54e3dcab/statsmodels-0.14.6-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e443e7077a6e2d3faeea72f5a92c9f12c63722686eb80bb40a0f04e4a7e267ad", size = 10090802, upload-time = "2025-12-05T23:13:20.653Z" }, + { url = "https://files.pythonhosted.org/packages/34/0e/2408735aca9e764643196212f9069912100151414dd617d39ffc72d77eee/statsmodels-0.14.6-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3414e40c073d725007a6603a18247ab7af3467e1af4a5e5a24e4c27bc26673b4", size = 10337587, upload-time = "2025-12-05T23:13:37.597Z" }, + { url = "https://files.pythonhosted.org/packages/0f/36/4d44f7035ab3c0b2b6a4c4ebb98dedf36246ccbc1b3e2f51ebcd7ac83abb/statsmodels-0.14.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a518d3f9889ef920116f9fa56d0338069e110f823926356946dae83bc9e33e19", size = 10363350, upload-time = "2025-12-05T23:13:53.08Z" }, + { url = "https://files.pythonhosted.org/packages/26/33/f1652d0c59fa51de18492ee2345b65372550501ad061daa38f950be390b6/statsmodels-0.14.6-cp314-cp314-win_amd64.whl", hash = "sha256:151b73e29f01fe619dbce7f66d61a356e9d1fe5e906529b78807df9189c37721", size = 9588010, upload-time = "2025-12-05T23:14:07.28Z" }, ] [[package]] From b4c619dc5f57ab4206de42985a5a7da5444f1f82 Mon Sep 17 00:00:00 2001 From: henrydingliu <106109320+henrydingliu@users.noreply.github.com> Date: Sun, 14 Jun 2026 14:15:14 -0700 Subject: [PATCH 64/83] Update plot_munich.ipynb --- docs/gallery/plot_munich.ipynb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/gallery/plot_munich.ipynb b/docs/gallery/plot_munich.ipynb index 60ea5850..796ce967 100644 --- a/docs/gallery/plot_munich.ipynb +++ b/docs/gallery/plot_munich.ipynb @@ -52,9 +52,9 @@ " {'incurred':'Ultimate Incurred', 'paid': 'Ultimate Paid'}, axis=1)\n", "\n", "plot2_data = pd.concat(\n", - " ((cl_munich['paid'] / cl_munich['incurred']).to_frame().rename(\n", + " ((cl_munich['paid'] / cl_munich['incurred']).to_frame(origin_as_datetime=False).rename(\n", " columns={'2261': 'Munich'}),\n", - " (cl_traditional['paid'] / cl_traditional['incurred']).to_frame().rename(\n", + " (cl_traditional['paid'] / cl_traditional['incurred']).to_frame(origin_as_datetime=False).rename(\n", " columns={'2261': 'Traditional'})), axis=1)" ] }, From 0486f53867d37c46c6b21849d413371ca7fefa7a Mon Sep 17 00:00:00 2001 From: Ethan Kang Date: Sun, 14 Jun 2026 17:41:02 -0700 Subject: [PATCH 65/83] docs: show full inherited method docstrings on Triangle API page Add: inherited-members: to class_inherited autosummary template and remove the Inherited Methods autosummary table that only showed one-line summaries. Fixes #970. Co-authored-by: Cursor --- .../autosummary/class_inherited.rst | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/docs/_templates/autosummary/class_inherited.rst b/docs/_templates/autosummary/class_inherited.rst index 1f7dfc43..ee45f6cc 100644 --- a/docs/_templates/autosummary/class_inherited.rst +++ b/docs/_templates/autosummary/class_inherited.rst @@ -4,23 +4,6 @@ .. autoclass:: {{ objname }} :members: + :inherited-members: :undoc-members: :exclude-members: set_fit_request, set_predict_request, set_score_request, set_transform_request, {{ attributes | join(', ') }} - -{% set inherited = [] %} -{% for method in methods %} -{% if method in inherited_members and not method.startswith('_') %} -{% set _ = inherited.append(method) %} -{% endif %} -{% endfor %} - -{% if inherited %} -.. rubric:: Inherited Methods - -.. autosummary:: - :nosignatures: - -{% for method in inherited %} - {{ objname }}.{{ method }} -{% endfor %} -{% endif %} From cb1ef404d9b03e939da829859cdf25880eff9754 Mon Sep 17 00:00:00 2001 From: Ethan Kang Date: Mon, 15 Jun 2026 09:33:46 -0700 Subject: [PATCH 66/83] docs: add docstrings for Triangle broadcast_axis, copy, reindex Part 1 of #970 method docstring follow-up. Properties deferred per reviewer guidance. Refs #970. Co-authored-by: Cursor --- chainladder/core/triangle.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/chainladder/core/triangle.py b/chainladder/core/triangle.py index 284c32c4..3289ee92 100644 --- a/chainladder/core/triangle.py +++ b/chainladder/core/triangle.py @@ -1846,6 +1846,11 @@ def trend( return obj def broadcast_axis(self, axis, value): + """Broadcast values along an axis. + + .. deprecated:: + Use Triangle arithmetic for broadcasting instead. + """ warnings.warn( """ Broadcast axis is deprecated in favor of broadcasting @@ -1854,6 +1859,13 @@ def broadcast_axis(self, axis, value): return self def copy(self): + """Return a shallow copy of the Triangle. + + Returns + ------- + Triangle + A new Triangle with copied ``values`` and shared metadata. + """ X = object.__new__(self.__class__) X.__dict__.update(vars(self)) X._set_slicers() @@ -2149,6 +2161,22 @@ def sort_axis(self, axis): return obj def reindex(self, columns=None, fill_value=np.nan): + """Conform Triangle columns to a new set of labels. + + Any column in ``columns`` that is not already present is added and + filled with ``fill_value``. + + Parameters + ---------- + columns : list + Column labels for the returned Triangle. + fill_value : float, default nan + Value assigned to newly added columns. + + Returns + ------- + Triangle + """ obj = self.copy() for column in columns: if column not in obj.columns: From a4b9ff1cf954f661d41d8e7b262e69d712b7b550 Mon Sep 17 00:00:00 2001 From: Ethan Kang Date: Mon, 15 Jun 2026 09:35:01 -0700 Subject: [PATCH 67/83] docs: add docstrings for Triangle exp, log, sqrt, round Part 3 of #970 method docstring follow-up. Refs #970. Co-authored-by: Cursor --- chainladder/core/pandas.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/chainladder/core/pandas.py b/chainladder/core/pandas.py index 542dba01..68b376e5 100644 --- a/chainladder/core/pandas.py +++ b/chainladder/core/pandas.py @@ -438,9 +438,21 @@ def sort_index(self, *args, **kwargs): return self.iloc[self.index.sort_values(self.key_labels, *args, **kwargs).index] def exp(self): + """Return the exponential of each element. + + Returns + ------- + Triangle + """ return self.get_array_module().exp(self) def log(self): + """Return the natural logarithm of each element. + + Returns + ------- + Triangle + """ return self.get_array_module().log(self) def minimum(self, other): @@ -458,9 +470,26 @@ def maximum(self, other): return self.get_array_module().maximum(self, other) def sqrt(self): + """Return the non-negative square root of each element. + + Returns + ------- + Triangle + """ return self.get_array_module().sqrt(self) def round(self, decimals=0, *args, **kwargs): + """Round each element to the given number of decimal places. + + Parameters + ---------- + decimals : int, default 0 + Number of decimal places to round to. + + Returns + ------- + Triangle + """ return round(self, decimals) def xs( From a213e93f5217ad19a4d2a0e0ea00a66d23d907e3 Mon Sep 17 00:00:00 2001 From: Ethan Kang Date: Mon, 15 Jun 2026 12:32:16 -0700 Subject: [PATCH 68/83] Address review: document banker's rounding on round Co-authored-by: Cursor --- chainladder/core/pandas.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/chainladder/core/pandas.py b/chainladder/core/pandas.py index 68b376e5..84a876e1 100644 --- a/chainladder/core/pandas.py +++ b/chainladder/core/pandas.py @@ -481,6 +481,10 @@ def sqrt(self): def round(self, decimals=0, *args, **kwargs): """Round each element to the given number of decimal places. + Uses banker's rounding (round half to even). For example, + ``(8.5).round(0)`` returns 8, not 9. For conventional rounding, + add a small epsilon before rounding, e.g. ``(tri + 1e-9).round(0)``. + Parameters ---------- decimals : int, default 0 From 3539462e947c1f7476c30c3d1d485a9bf2293633 Mon Sep 17 00:00:00 2001 From: Ethan Kang Date: Mon, 15 Jun 2026 09:35:19 -0700 Subject: [PATCH 69/83] docs: add docstrings for Triangle head, tail, sort_index Part 4 of #970 method docstring follow-up. Refs #970. Co-authored-by: Cursor --- chainladder/core/pandas.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/chainladder/core/pandas.py b/chainladder/core/pandas.py index 542dba01..123165df 100644 --- a/chainladder/core/pandas.py +++ b/chainladder/core/pandas.py @@ -429,12 +429,40 @@ def astype(self, dtype, inplace=True): return obj def head(self, n=5): + """Return the first ``n`` rows along the index axis. + + Parameters + ---------- + n : int, default 5 + Number of rows to select. + + Returns + ------- + Triangle + """ return self.iloc[:n] def tail(self, n=5): + """Return the last ``n`` rows along the index axis. + + Parameters + ---------- + n : int, default 5 + Number of rows to select. + + Returns + ------- + Triangle + """ return self.iloc[-n:] def sort_index(self, *args, **kwargs): + """Sort Triangle rows by index labels. + + Returns + ------- + Triangle + """ return self.iloc[self.index.sort_values(self.key_labels, *args, **kwargs).index] def exp(self): From 38325a21760b4c1f2e62cd9e68cd2f5e7b633c7c Mon Sep 17 00:00:00 2001 From: Ethan Kang Date: Mon, 15 Jun 2026 12:32:36 -0700 Subject: [PATCH 70/83] Address review: use triangles wording in head and tail docs Co-authored-by: Cursor --- chainladder/core/pandas.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/chainladder/core/pandas.py b/chainladder/core/pandas.py index 123165df..da4b11f4 100644 --- a/chainladder/core/pandas.py +++ b/chainladder/core/pandas.py @@ -429,12 +429,12 @@ def astype(self, dtype, inplace=True): return obj def head(self, n=5): - """Return the first ``n`` rows along the index axis. + """Return the first ``n`` triangles along the index axis. Parameters ---------- n : int, default 5 - Number of rows to select. + Number of triangles to select. Returns ------- @@ -443,12 +443,12 @@ def head(self, n=5): return self.iloc[:n] def tail(self, n=5): - """Return the last ``n`` rows along the index axis. + """Return the last ``n`` triangles along the index axis. Parameters ---------- n : int, default 5 - Number of rows to select. + Number of triangles to select. Returns ------- From d3ed85ee711f5714f7a1be3a4b75da79c3a07750 Mon Sep 17 00:00:00 2001 From: Ethan Kang Date: Mon, 15 Jun 2026 09:35:34 -0700 Subject: [PATCH 71/83] docs: add docstrings for Triangle compute and pipe Part 5 of #970 method docstring follow-up. Refs #970. Co-authored-by: Cursor --- chainladder/core/base.py | 10 ++++++++++ chainladder/core/common.py | 16 ++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/chainladder/core/base.py b/chainladder/core/base.py index 267f353e..9006559f 100644 --- a/chainladder/core/base.py +++ b/chainladder/core/base.py @@ -651,6 +651,16 @@ def __array_function__(self, func, types, args, kwargs): return HANDLED_FUNCTIONS[func](*args, **kwargs) def compute(self, *args, **kwargs): + """Materialize a lazy dask-backed Triangle. + + When ``values`` is a dask array, compute it and update + ``array_backend`` to match the resulting array type. Returns ``self`` + unchanged when the Triangle is already materialized. + + Returns + ------- + Triangle + """ if hasattr(self.values, "chunks"): obj = self.copy() obj.values = obj.values.compute(*args, **kwargs) diff --git a/chainladder/core/common.py b/chainladder/core/common.py index da91141a..c1c5cc14 100644 --- a/chainladder/core/common.py +++ b/chainladder/core/common.py @@ -151,6 +151,22 @@ def full_triangle_(self): return _get_full_triangle(X, self.ultimate_, X.is_cumulative) def pipe(self, func, *args, **kwargs): + """Apply ``func(self, *args, **kwargs)``. + + Parameters + ---------- + func : callable + Function to apply to the Triangle. + *args + Positional arguments passed to ``func``. + **kwargs + Keyword arguments passed to ``func``. + + Returns + ------- + object + The return value of ``func``. + """ return func(self, *args, **kwargs) def set_backend( From af81678cbafe3211acd5f6092de6b7a9e8a894e0 Mon Sep 17 00:00:00 2001 From: Ethan Kang Date: Mon, 15 Jun 2026 12:32:52 -0700 Subject: [PATCH 72/83] Address review: add pipe docstring example Co-authored-by: Cursor --- chainladder/core/common.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/chainladder/core/common.py b/chainladder/core/common.py index c1c5cc14..77f84d51 100644 --- a/chainladder/core/common.py +++ b/chainladder/core/common.py @@ -166,6 +166,14 @@ def pipe(self, func, *args, **kwargs): ------- object The return value of ``func``. + + Examples + -------- + Keep development periods from 48 onward: + + >>> import chainladder as cl + >>> raa = cl.load_sample('raa') + >>> raa.pipe(lambda tri: tri.loc[..., 48:]) """ return func(self, *args, **kwargs) From d26f93165b12ce74acf84ec00dd7a6f1928eee81 Mon Sep 17 00:00:00 2001 From: henrydingliu <106109320+henrydingliu@users.noreply.github.com> Date: Mon, 15 Jun 2026 21:41:46 -0700 Subject: [PATCH 73/83] Update api.md --- docs/library/api.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/library/api.md b/docs/library/api.md index aac3f021..d33c03f0 100644 --- a/docs/library/api.md +++ b/docs/library/api.md @@ -169,8 +169,10 @@ Functions read_pickle read_json concat - minimum + date_delta_adjustment maximum + minimum + model_diagnostics Classes ------- From a9a330add2fff9703e2e4bd9715b5dc876119447 Mon Sep 17 00:00:00 2001 From: henrydingliu <106109320+henrydingliu@users.noreply.github.com> Date: Mon, 15 Jun 2026 21:48:41 -0700 Subject: [PATCH 74/83] Update api.md --- docs/library/api.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/library/api.md b/docs/library/api.md index d33c03f0..1e24edbd 100644 --- a/docs/library/api.md +++ b/docs/library/api.md @@ -169,7 +169,6 @@ Functions read_pickle read_json concat - date_delta_adjustment maximum minimum model_diagnostics From 6d0004be8dad5c6fb0552b03c4d344362d4eeca0 Mon Sep 17 00:00:00 2001 From: henrydingliu <106109320+henrydingliu@users.noreply.github.com> Date: Tue, 16 Jun 2026 17:23:13 -0700 Subject: [PATCH 75/83] Update stochastic-tutorial.ipynb --- docs/getting_started/tutorials/stochastic-tutorial.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/getting_started/tutorials/stochastic-tutorial.ipynb b/docs/getting_started/tutorials/stochastic-tutorial.ipynb index 4637b638..8d7de0b4 100644 --- a/docs/getting_started/tutorials/stochastic-tutorial.ipynb +++ b/docs/getting_started/tutorials/stochastic-tutorial.ipynb @@ -241,7 +241,7 @@ } }, "source": [ - "clrd_first_lags.link_ratio.to_frame(origin_as_datetime=True).mean().iloc[0]" + "clrd_first_lags.link_ratio.to_frame().mean().iloc[0]" ], "outputs": [ { From 6814ebf7dbcc0a23fc921bdbedda4d4bf73510a3 Mon Sep 17 00:00:00 2001 From: Kevin <74339271+SaguaroDev@users.noreply.github.com> Date: Wed, 17 Jun 2026 03:14:50 -0400 Subject: [PATCH 76/83] docs(utilities): live list_samples table + model_diagnostics demo (#915) (#960) The Sample Datasets section listed 18 datasets in a hand-maintained markdown table that had drifted out of date; the package now bundles 46. Replace the static table with a live cl.list_samples() call so the docs can no longer drift from the manifest. model_diagnostics was added to the public API but never demonstrated. Add a Model Diagnostics section showing it against a fitted Chainladder model. Closes #915. --- docs/user_guide/utilities.ipynb | 73 ++++++++++++++++++++------------- 1 file changed, 44 insertions(+), 29 deletions(-) diff --git a/docs/user_guide/utilities.ipynb b/docs/user_guide/utilities.ipynb index 6aa89f34..deb85ee6 100644 --- a/docs/user_guide/utilities.ipynb +++ b/docs/user_guide/utilities.ipynb @@ -2,7 +2,6 @@ "cells": [ { "cell_type": "markdown", - "id": "7e2c2fa7-9d32-404b-9121-51d06ac57324", "metadata": {}, "source": [ "# Utilities\n", @@ -13,38 +12,54 @@ "\n", "## Sample Datasets\n", "\n", - "A variety of datasets can be loaded using :func:`load_sample()`. These are\n", - "sample datasets that are used in a variety of examples within this\n", - "documentation.\n", - "\n", - "\n", - "| Dataset | Description | \n", - "|-----------|------------------------------------------------------|\n", - "| abc | ABC Data |\n", - "| auto | Auto Data |\n", - "| berqsherm | Data from the Berquist Sherman paper |\n", - "| cc_sample | Sample Insurance Data for Cape Cod Method in Struhuss|\n", - "| clrd | CAS Loss Reserving Database |\n", - "| genins | General Insurance Data used in Clark |\n", - "| ia_sample | Sample data for Incremental Additive Method in Schmidt|\n", - "| liab | more data|\n", - "| m3ir5 | more data|\n", - "| mcl | Sample insurance data for Munich Adjustment in Quarg|\n", - "| mortgage | more data|\n", - "| mw2008 | more data|\n", - "| mw2014 | more data|\n", - "| quarterly | Sample data to demonstrate changing Triangle grain|\n", - "| raa | Sample data used in Mack Chainladder|\n", - "| ukmotor | more data|\n", - "| usaa | more data|\n", - "| usauto | more data|\n", + "A variety of datasets can be loaded using `load_sample()`. These are sample\n", + "datasets that are used throughout the examples in this documentation. The full\n", + "list is available through `list_samples()`, which returns a table of every\n", + "bundled dataset along with its index, columns, and grain." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import chainladder as cl\n", "\n", + "cl.list_samples()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Model Diagnostics\n", "\n", + "`model_diagnostics()` summarizes a fitted IBNR model into a single Triangle\n", + "whose columns are the diagnostic vectors of interest, such as the latest\n", + "diagonal, IBNR, and ultimate. It accepts a fitted estimator or a `Pipeline`,\n", + "and an optional `groupby` to summarize at a coarser index level." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "model = cl.Chainladder().fit(cl.load_sample('raa'))\n", + "cl.model_diagnostics(model)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ "## Chainladder Persistence\n", "\n", - "All estimators can be persisted to disk or database\n", - "using ``to_json`` or ``to_pickle``. Restoring the estimator is as simple as\n", - "``cl.read_json`` or ``cl.read_pickle``.\n" + "All estimators can be persisted to disk or database using `to_json` or\n", + "`to_pickle`. Restoring the estimator is as simple as `cl.read_json` or\n", + "`cl.read_pickle`." ] }, { From 23d0e4c3c5fc44edb2eb4e26152af1f3a5e62c2d Mon Sep 17 00:00:00 2001 From: henrydingliu <106109320+henrydingliu@users.noreply.github.com> Date: Thu, 18 Jun 2026 20:51:27 -0700 Subject: [PATCH 77/83] Update constant.py --- chainladder/development/constant.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/chainladder/development/constant.py b/chainladder/development/constant.py index 458bd817..4a4c7b60 100644 --- a/chainladder/development/constant.py +++ b/chainladder/development/constant.py @@ -114,12 +114,14 @@ def __init__(self, patterns=None, style="ldf", callable_axis=0, groupby=None): def fit(self, X, y=None, sample_weight=None): """Fit the model with X. + Parameters ---------- X : Triangle-like     Set of LDFs to which the munich adjustment will be applied. y : Ignored sample_weight : Ignored + Returns ------- self : object From 7f6276f3e2268733e070d8f3de7589c73920dff0 Mon Sep 17 00:00:00 2001 From: henrydingliu <106109320+henrydingliu@users.noreply.github.com> Date: Thu, 18 Jun 2026 20:54:38 -0700 Subject: [PATCH 78/83] Update glm.py --- chainladder/development/glm.py | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/chainladder/development/glm.py b/chainladder/development/glm.py index 7cff4b69..7568f777 100644 --- a/chainladder/development/glm.py +++ b/chainladder/development/glm.py @@ -41,22 +41,16 @@ class TweedieGLM(DevelopmentBase): Column name for the response variable of the GLM. If omitted, then the first column of the Triangle will be used. power: float, default=1 - The power determines the underlying target distribution according - to the following table: - +-------+------------------------+ - | Power | Distribution | - +=======+========================+ - | 0 | Normal | - +-------+------------------------+ - | 1 | Poisson | - +-------+------------------------+ - | (1,2) | Compound Poisson Gamma | - +-------+------------------------+ - | 2 | Gamma | - +-------+------------------------+ - | 3 | Inverse Gaussian | - +-------+------------------------+ - For ``0 < power < 1``, no distribution exists. + The power determines the underlying target distribution according + to the following table: + Power Distribution + ===== ======================== + 0 Normal + 1 Poisson + (1,2) Compound Poisson Gamma + 2 Gamma + 3 Inverse Gaussian + For ``0 < power < 1``, no distribution exists. alpha: float, default=1 Constant that multiplies the penalty term and thus determines the regularization strength. ``alpha = 0`` is equivalent to unpenalized From 868597716c72e60335646e3e34c2b0d9b138e7cd Mon Sep 17 00:00:00 2001 From: henrydingliu <106109320+henrydingliu@users.noreply.github.com> Date: Thu, 18 Jun 2026 21:00:47 -0700 Subject: [PATCH 79/83] Update utility_functions.py --- chainladder/utils/utility_functions.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/chainladder/utils/utility_functions.py b/chainladder/utils/utility_functions.py index 84f70d45..cc967bf4 100644 --- a/chainladder/utils/utility_functions.py +++ b/chainladder/utils/utility_functions.py @@ -990,7 +990,8 @@ def model_diagnostics( - ``Run Off 1/2/3...``: Expected incremental emergence in successive future valuation periods (from ``full_expectation_``) - ``Apriori``: Expected ultimate for Benktander family of methods (from ``expectation_``) - Columns from the original Triangle are cross-joined into the index. ``Measure`` will contain all the columns from the original Triangle. + Columns from the original Triangle are cross-joined into the index. + ``Measure`` will contain all the columns from the original Triangle. """ from chainladder import Pipeline, Triangle From ac6234fdfc8a79d575aa81aa64e3899dadf2febd Mon Sep 17 00:00:00 2001 From: henrydingliu <106109320+henrydingliu@users.noreply.github.com> Date: Thu, 18 Jun 2026 21:08:51 -0700 Subject: [PATCH 80/83] Update glm.py --- chainladder/development/glm.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/chainladder/development/glm.py b/chainladder/development/glm.py index 7568f777..c8904bfa 100644 --- a/chainladder/development/glm.py +++ b/chainladder/development/glm.py @@ -43,6 +43,7 @@ class TweedieGLM(DevelopmentBase): power: float, default=1 The power determines the underlying target distribution according to the following table: + Power Distribution ===== ======================== 0 Normal @@ -50,6 +51,7 @@ class TweedieGLM(DevelopmentBase): (1,2) Compound Poisson Gamma 2 Gamma 3 Inverse Gaussian + For ``0 < power < 1``, no distribution exists. alpha: float, default=1 Constant that multiplies the penalty term and thus determines the From e70fae9be96c7e8714d7aa14f790a8959ef71a29 Mon Sep 17 00:00:00 2001 From: henrydingliu <106109320+henrydingliu@users.noreply.github.com> Date: Thu, 18 Jun 2026 21:24:33 -0700 Subject: [PATCH 81/83] Update glm.py --- chainladder/development/glm.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/chainladder/development/glm.py b/chainladder/development/glm.py index c8904bfa..551d812d 100644 --- a/chainladder/development/glm.py +++ b/chainladder/development/glm.py @@ -43,14 +43,19 @@ class TweedieGLM(DevelopmentBase): power: float, default=1 The power determines the underlying target distribution according to the following table: - - Power Distribution - ===== ======================== - 0 Normal - 1 Poisson - (1,2) Compound Poisson Gamma - 2 Gamma - 3 Inverse Gaussian + + * - Power + - Distribution + * - 0 + - Normal + * - 1 + - Poisson + * - (1,2) + - Compound Poisson Gamma + * - 2 + - Gamma + * - 3 + - Inverse Gaussian For ``0 < power < 1``, no distribution exists. alpha: float, default=1 From fc469994c3a3d620d86cd878af9f75d5c1068703 Mon Sep 17 00:00:00 2001 From: henrydingliu <106109320+henrydingliu@users.noreply.github.com> Date: Thu, 18 Jun 2026 21:38:06 -0700 Subject: [PATCH 82/83] Update glm.py --- chainladder/development/glm.py | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/chainladder/development/glm.py b/chainladder/development/glm.py index 551d812d..41900429 100644 --- a/chainladder/development/glm.py +++ b/chainladder/development/glm.py @@ -44,19 +44,16 @@ class TweedieGLM(DevelopmentBase): The power determines the underlying target distribution according to the following table: - * - Power - - Distribution - * - 0 - - Normal - * - 1 - - Poisson - * - (1,2) - - Compound Poisson Gamma - * - 2 - - Gamma - * - 3 - - Inverse Gaussian - + .. rst:: + + Power Distribution + ===== ======================== + 0 Normal + 1 Poisson + (1,2) Compound Poisson Gamma + 2 Gamma + 3 Inverse Gaussian + For ``0 < power < 1``, no distribution exists. alpha: float, default=1 Constant that multiplies the penalty term and thus determines the From eeb467d94cfcfed369035a2e91ba836948053af5 Mon Sep 17 00:00:00 2001 From: henrydingliu <106109320+henrydingliu@users.noreply.github.com> Date: Thu, 18 Jun 2026 22:00:59 -0700 Subject: [PATCH 83/83] Update glm.py --- chainladder/development/glm.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/chainladder/development/glm.py b/chainladder/development/glm.py index 41900429..eb488d11 100644 --- a/chainladder/development/glm.py +++ b/chainladder/development/glm.py @@ -44,15 +44,21 @@ class TweedieGLM(DevelopmentBase): The power determines the underlying target distribution according to the following table: - .. rst:: - - Power Distribution - ===== ======================== - 0 Normal - 1 Poisson - (1,2) Compound Poisson Gamma - 2 Gamma - 3 Inverse Gaussian + .. list-table:: + :header-rows: 1 + + * - Power + - Distribution + * - 0 + - Normal + * - 1 + - Poisson + * - (1,2) + - Compound Poisson Gamma + * - 2 + - Gamma + * - 3 + - Inverse Gaussian For ``0 < power < 1``, no distribution exists. alpha: float, default=1