{ "cells": [ { "cell_type": "markdown", "id": "0f0e5e3b-ac37-4865-8c51-ded927ea9b46", "metadata": {}, "source": [ "# Online Sandbox Tutorial\n", "\n", "Welcome! If you've come here to explore the capabilities of the `chainladder-python` package, you've landed in the perfect spot. This online sandbox tutorial is designed to provide you with a glimpse of the package's functionalities. \n", "\n", "We recommend setting aside about **one hour** to complete it.\n", "\n", "Got Stuck? Click [here](https://nbviewer.org/github/casact/chainladder-python/blob/master/docs/getting_started/online_sandbox/sandbox_workbook_filled.ipynb) for the filled in workbook. Have questions? Join the [discussion](https://github.com/casact/chainladder-python/discussions) on GitHub." ] }, { "cell_type": "markdown", "id": "d8f38e79-5010-4190-b38c-cbc1d85bde47", "metadata": { "tags": [] }, "source": [ "# Setting Up\n", "We will first need to install the package, as Google Colab's default environment doesn't have the chainladder package pre-installed. \n", "\n", "Simply execute `pip install chainladder`, Colab is smart enough to know that this is not a piece of python code, but to execute it in shell. FYI, `pip` stands for \"Package Installer for Python\". You will need to run this step using your terminal instead of using a python notebook when you are ready to install the package on your machine." ] }, { "cell_type": "code", "execution_count": null, "id": "be51a379-5efe-420e-b689-3bf93b96ebc8", "metadata": {}, "outputs": [], "source": [ "pip install __fill_in_code__" ] }, { "cell_type": "markdown", "id": "3d2bde34-d9e8-436d-8819-675e2ece7bc9", "metadata": {}, "source": [ "`%load_ext lab_black` is a linter, it makes code prettier, you may ignore this line." ] }, { "cell_type": "code", "execution_count": null, "id": "882cc191-5849-471e-8e13-65fdf3e01419", "metadata": {}, "outputs": [], "source": [ "%load_ext lab_black" ] }, { "cell_type": "markdown", "id": "011ee825-ca6d-4efc-b782-5e6f2a14bead", "metadata": {}, "source": [ "Other commonly used packages, such as `numpy`, `pandas`, and `matplotlib` are already pre-installed, we just need to load them into our environment." ] }, { "cell_type": "code", "execution_count": null, "id": "03fdf8fd-ecd1-4df4-b9cf-a4bf01d978f0", "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "import chainladder as cl\n", "\n", "print(\"chainladder\", cl.__version__)" ] }, { "cell_type": "markdown", "id": "42e0f37f-6d82-46ed-9f80-647cc7233046", "metadata": {}, "source": [ "# Your Journey Begins" ] }, { "cell_type": "markdown", "id": "c9a3a636-979a-4205-9762-469e8afb7e46", "metadata": {}, "source": [ "Let's begin by looking at a sample dataset, called `xyz`, which is hosted on https://raw.githubusercontent.com/casact/chainladder-python/master/chainladder/utils/data/xyz.csv.\n", "\n", "Let's load the dataset into the memory with `pandas`, then inspect its \"`head`\"." ] }, { "cell_type": "code", "execution_count": null, "id": "aa2c95b8-86b4-4846-b950-12c402477ec1", "metadata": { "tags": [] }, "outputs": [], "source": [ "xyz_df = pd.read_csv(\n", " __fill_in_code__\n", ")\n", "xyz_df.head()" ] }, { "cell_type": "markdown", "id": "996795b6-9361-4b5c-a00d-d9b6391b115f", "metadata": {}, "source": [ "Can you list all of the unique accident years?" ] }, { "cell_type": "code", "execution_count": null, "id": "4c11052c-291e-439f-ac0f-6736bb2b0b68", "metadata": {}, "outputs": [], "source": [ "xyz_df[__fill_in_code__].unique()" ] }, { "cell_type": "markdown", "id": "3d5be56c-1432-4ba2-85bc-16412fee1d66", "metadata": {}, "source": [ "How many are there?" ] }, { "cell_type": "code", "execution_count": null, "id": "cfeca5a6-366f-4abb-b3e9-51c91e7b9336", "metadata": {}, "outputs": [], "source": [ "xyz_df[__fill_in_code__].nunique()" ] }, { "cell_type": "markdown", "id": "8f870f4f-117c-467d-b3d7-d2941f964f23", "metadata": {}, "source": [ "# Triangle Basics" ] }, { "cell_type": "markdown", "id": "4d4ebbf6-bcdc-4c4f-be8c-168c4e7883ea", "metadata": {}, "source": [ "Let's load the data into the chainladder triangle format. And let's call it `xyz_tri`." ] }, { "cell_type": "code", "execution_count": null, "id": "2b51e0b6-c1d3-4976-8866-4800b15d27ec", "metadata": {}, "outputs": [], "source": [ "xyz_tri = cl.Triangle(\n", " data=__fill_in_code__,\n", " origin=\"AccidentYear\",\n", " development=\"DevelopmentYear\",\n", " columns=[\"Incurred\", \"Paid\", \"Reported\", \"Closed\", \"Premium\"],\n", " cumulative=True,\n", ")\n", "xyz_tri" ] }, { "cell_type": "markdown", "id": "2c404d26-4418-43b8-8687-58be1b6423f1", "metadata": {}, "source": [ "What does the incurred triangle look like?" ] }, { "cell_type": "code", "execution_count": null, "id": "fe9309fe-2744-4e4d-beff-0a36c1182386", "metadata": {}, "outputs": [], "source": [ "xyz_tri[__fill_in_code__]" ] }, { "cell_type": "markdown", "id": "ed9811e6-5761-4258-9942-19a620540361", "metadata": {}, "source": [ "How about paid?" ] }, { "cell_type": "code", "execution_count": null, "id": "278856cf-6d84-4fa6-ac57-4f57755580b8", "metadata": {}, "outputs": [], "source": [ "xyz_tri[__fill_in_code__]" ] }, { "cell_type": "markdown", "id": "04114ff8-107a-4c56-ab9a-8c36f53553df", "metadata": {}, "source": [ "# Pandas-like Operations" ] }, { "cell_type": "markdown", "id": "433b8ae8-1968-4dfc-a176-c8a8c93c5f97", "metadata": {}, "source": [ "Let's see how `.iloc[...]` and `.loc[...]` similarly to pandas. They take 4 parameters: [index, column, origin, valuation]." ] }, { "cell_type": "markdown", "id": "f0452527-796d-4185-929a-97241329b377", "metadata": {}, "source": [ "What if we want the row from AY 1998 Incurred data?" ] }, { "cell_type": "code", "execution_count": null, "id": "a13a157b-3fe9-4254-bc72-11d4e1705f29", "metadata": {}, "outputs": [], "source": [ "xyz_tri.iloc[__fill_in_code__, __fill_in_code__, __fill_in_code__, __fill_in_code__]" ] }, { "cell_type": "markdown", "id": "08b8557c-66fe-4a25-a8bf-5413ca1c1fbb", "metadata": {}, "source": [ "What if you only want the valuation at age 60 of AY 1998?" ] }, { "cell_type": "code", "execution_count": null, "id": "fb20eda1-4e4a-431d-8c8a-21cc87b8c472", "metadata": {}, "outputs": [], "source": [ "xyz_tri.iloc[__fill_in_code__, __fill_in_code__, __fill_in_code__, __fill_in_code__]" ] }, { "cell_type": "markdown", "id": "56683ffb-01ef-4e18-ba27-1b8ab31b9ae7", "metadata": {}, "source": [ "Let's use `.loc[...]` to get the incurred triangle." ] }, { "cell_type": "code", "execution_count": null, "id": "b8116ded-c788-483c-b2af-fde45b72ee4a", "metadata": {}, "outputs": [], "source": [ "xyz_tri.loc[__fill_in_code__, __fill_in_code__, __fill_in_code__, __fill_in_code__]" ] }, { "cell_type": "markdown", "id": "c9d515b7-c9a3-4045-ad79-78af1574be8a", "metadata": {}, "source": [ "How do we get the latest Incurred diagonal only?" ] }, { "cell_type": "code", "execution_count": null, "id": "5bce08b8-bf34-418e-ac3b-db253db44898", "metadata": {}, "outputs": [], "source": [ "xyz_tri[\"Incurred\"].__fill_in_code__" ] }, { "cell_type": "markdown", "id": "31b56210-cbcd-4bbb-af9f-063a3788867a", "metadata": {}, "source": [ "Very often, we want incremental triangles instead. Let's convert the Incurred triangle to the incremental form." ] }, { "cell_type": "code", "execution_count": null, "id": "b2766e7b-b1e6-4574-bfa7-fd70ccd556d7", "metadata": {}, "outputs": [], "source": [ "xyz_tri[\"Incurred\"].__fill_in_code__" ] }, { "cell_type": "markdown", "id": "6235668f-9025-4108-b987-f867f93c8ce6", "metadata": {}, "source": [ "We can also convert the triangle to the valuation format, what we often see on Schedule Ps." ] }, { "cell_type": "code", "execution_count": null, "id": "72487c9a-4438-4ab7-8a24-245485d4c637", "metadata": {}, "outputs": [], "source": [ "xyz_tri[\"Incurred\"].__fill_in_code__" ] }, { "cell_type": "markdown", "id": "6e404747-8e22-42c0-a1b5-45c95d702730", "metadata": {}, "source": [ "Another function that is often useful is the `.heatmap()` method. Let's inspect the incurred amount and see if there are trends." ] }, { "cell_type": "code", "execution_count": null, "id": "f20ed887-e5b1-40f7-81b5-14bd840cca23", "metadata": {}, "outputs": [], "source": [ "xyz_tri[\"Incurred\"].__fill_in_code__" ] }, { "cell_type": "markdown", "id": "27d110d2-ee73-4bb5-a411-3d27c0dd7673", "metadata": {}, "source": [ "# Development" ] }, { "cell_type": "markdown", "id": "a0d0950f-bec7-406d-b253-4cf1bfd925dd", "metadata": {}, "source": [ "How can we get the incurred link ratios?" ] }, { "cell_type": "code", "execution_count": null, "id": "ec16d0fd-ac17-4280-aabf-ad5795114d5f", "metadata": {}, "outputs": [], "source": [ "xyz_tri[\"Incurred\"].__fill_in_code__" ] }, { "cell_type": "markdown", "id": "c74c5352-a95b-4403-8322-962ded312e39", "metadata": {}, "source": [ "We can also apply a `.heatmap()` to make it too, to help us visulize the highs and lows." ] }, { "cell_type": "code", "execution_count": null, "id": "172c70be-2324-472f-b89c-29963695179a", "metadata": {}, "outputs": [], "source": [ "xyz_tri[\"Incurred\"].__fill_in_code__.__fill_in_code__" ] }, { "cell_type": "markdown", "id": "f5f212b0-3769-49cd-b7cc-b484f2877aa2", "metadata": {}, "source": [ "Let's get a volume-weighted average LDFs for our Incurred triangle." ] }, { "cell_type": "code", "execution_count": null, "id": "ba0b96cb-77eb-472c-84fd-c5c8c5c11e10", "metadata": {}, "outputs": [], "source": [ "cl.Development(average=\"volume\").fit(__fill_in_code__).ldf_" ] }, { "cell_type": "markdown", "id": "0c4baafd-e141-4566-a4ae-2f0a44ef828e", "metadata": {}, "source": [ "How about the CDFs?" ] }, { "cell_type": "code", "execution_count": null, "id": "b156f84b-dd0d-49d6-8eec-070d0143f40c", "metadata": {}, "outputs": [], "source": [ "cl.Development(average=\"volume\").fit(__fill_in_code__).__fill_in_code__" ] }, { "cell_type": "markdown", "id": "d51e5664-3106-41d1-b77f-8afa9ee94ff7", "metadata": {}, "source": [ "We can also use only the latest 3 periods in the calculation of CDFs." ] }, { "cell_type": "code", "execution_count": null, "id": "de88fdad-5d89-4cc2-adb0-bbeb7c77bbcb", "metadata": {}, "outputs": [], "source": [ "cl.Development(average=\"volume\", n_periods=__fill_in_code__).fit(xyz_tri[\"Incurred\"]).cdf_" ] }, { "cell_type": "markdown", "id": "b018bae9-6070-4795-8af6-b5e196aa1af1", "metadata": {}, "source": [ "# Deterministic Models" ] }, { "cell_type": "markdown", "id": "e7c7b88e-205d-45c8-b9e6-4586f29041a4", "metadata": {}, "source": [ "Before we can build any models, we need to use `fit_transform()`, so that the object is actually modified with our selected development pattern(s).\n", "\n", "Set the development of the triangle to use only 3 periods." ] }, { "cell_type": "code", "execution_count": null, "id": "9e5136d2-0c3c-44da-8440-57ca3cfbbb9d", "metadata": {}, "outputs": [], "source": [ "cl.Development(average=__fill_in_code__, n_periods=__fill_in_code__).fit_transform(__fill_in_code__)" ] }, { "cell_type": "markdown", "id": "1bd89481-e5c7-4a84-b2cc-a2e386ccdb15", "metadata": {}, "source": [ "Let's fit a chainladder model to our Incurred triangle." ] }, { "cell_type": "code", "execution_count": null, "id": "022e22e9-92a8-427c-bf5c-cf352df1437c", "metadata": {}, "outputs": [], "source": [ "cl_mod = cl.Chainladder().fit(__fill_in_code__)\n", "cl_mod" ] }, { "cell_type": "markdown", "id": "7b710342-5f86-408e-bf7e-76382b37f2d1", "metadata": {}, "source": [ "How can we get the model's ultimate estimate?" ] }, { "cell_type": "code", "execution_count": null, "id": "69f18923-73b1-4b80-9148-60a7bab5b118", "metadata": {}, "outputs": [], "source": [ "cl_mod.__fill_in_code__" ] }, { "cell_type": "markdown", "id": "b416a404-8d0f-46fc-a3e7-f5b5b884b4b4", "metadata": {}, "source": [ "How about just the IBNR?" ] }, { "cell_type": "code", "execution_count": null, "id": "5fad3aa0-03bc-4f84-a8b7-1a00dbdebe8d", "metadata": {}, "outputs": [], "source": [ "cl_mod.__fill_in_code__" ] }, { "cell_type": "markdown", "id": "70d8c018-21ca-4f2c-a764-433e310bb44a", "metadata": {}, "source": [ "Let's fit an Expected Loss model, with an aprior of 90% on Premium, and get its ultimates." ] }, { "cell_type": "code", "execution_count": null, "id": "22eba9fa-1890-4f6f-8a10-281142d2d58d", "metadata": {}, "outputs": [], "source": [ "cl.ExpectedLoss(apriori=__fill_in_code__).fit(\n", " xyz_tri[\"Incurred\"], sample_weight=xyz_tri[\"Premium\"].latest_diagonal\n", ").ultimate_" ] }, { "cell_type": "markdown", "id": "eb20b72a-4e49-4eaa-b8e8-d3801833e2d3", "metadata": {}, "source": [ "Try it on the Paid triangle, do you get the same ultimate?" ] }, { "cell_type": "code", "execution_count": null, "id": "411f48b0-8b86-4175-80f2-f5f4a19e6c46", "metadata": {}, "outputs": [], "source": [ "cl.ExpectedLoss(apriori=0.90).fit(\n", " xyz_tri[\"Paid\"], sample_weight=__fill_in_code__\n", ").ultimate_" ] }, { "cell_type": "markdown", "id": "fb1d7eda-f4c6-4990-9488-47235492001a", "metadata": {}, "source": [ "How about a Bornhuetter-Ferguson model?" ] }, { "cell_type": "code", "execution_count": null, "id": "d66c7c9a-71eb-4d56-beea-f275da062fc0", "metadata": {}, "outputs": [], "source": [ "cl.__fill_in_code__(apriori=0.90).fit(\n", " xyz_tri[\"Incurred\"], sample_weight=xyz_tri[\"Premium\"].latest_diagonal\n", ").ultimate_" ] }, { "cell_type": "markdown", "id": "5564ead9-d059-4d2c-839a-f988238e50ee", "metadata": {}, "source": [ "How about Benktander, with 1 iteration, which is the same as BF?" ] }, { "cell_type": "code", "execution_count": null, "id": "d504e48d-1f5d-4fd6-975b-155235ffb577", "metadata": {}, "outputs": [], "source": [ "cl.__fill_in_code__(apriori=0.90, n_iters=__fill_in_code__).fit(\n", " xyz_tri[\"Incurred\"], sample_weight=xyz_tri[\"Premium\"].latest_diagonal\n", ").ultimate_" ] }, { "cell_type": "markdown", "id": "002a76c2-7989-46ba-954b-d84c09b4675a", "metadata": {}, "source": [ "How about Cape Cod?" ] }, { "cell_type": "code", "execution_count": null, "id": "7089ea42-ad28-4edc-9e83-723a7bc25443", "metadata": {}, "outputs": [], "source": [ "cl.__fill_in_code__().fit(\n", " xyz_tri[\"Incurred\"], sample_weight=xyz_tri[\"Premium\"].latest_diagonal\n", ").ultimate_" ] }, { "cell_type": "markdown", "id": "5a0d73a2-0e05-4be2-91f0-9ef1ef56a7be", "metadata": {}, "source": [ "Let's store the Cape Cod model as `cc_result`. We can also use `.to_frame()` to leave `chainladder` and go to a `DataFrame`. Let's make a bar chart over origin years to see what they look like." ] }, { "cell_type": "code", "execution_count": null, "id": "f2cd9f8c-454d-4b9f-b936-a2f39e8fefde", "metadata": {}, "outputs": [], "source": [ "cc_result = (\n", " cl.CapeCod()\n", " .fit(xyz_tri[\"Incurred\"], sample_weight=xyz_tri[\"Premium\"].latest_diagonal)\n", " .ultimate_\n", ").to_frame()\n", "\n", "plt.plot(\n", " cc_result.index.year,\n", " cc_result[__fill_in_code__]\n", ")" ] }, { "cell_type": "markdown", "id": "3f9e62f8-225b-4046-8847-a6e8d971e14d", "metadata": {}, "source": [ "# Stochastic Models" ] }, { "cell_type": "markdown", "id": "36105614-e317-4a87-a42d-282f59b1d339", "metadata": {}, "source": [ "The Mack's Chainladder model is available. Let's use it on the Incurred triangle." ] }, { "cell_type": "code", "execution_count": null, "id": "e008ebdb-243d-4ed0-9256-86331df1070a", "metadata": {}, "outputs": [], "source": [ "mcl_mod = cl.__fill_in_code__().fit(__fill_in_code__)\n", "mcl_mod" ] }, { "cell_type": "markdown", "id": "3298c63c-5356-4d69-afa3-058b68daf777", "metadata": {}, "source": [ "There are many attributes that are available, such as `full_std_err_`, `total_process_risk_`, `total_parameter_risk_`, `mack_std_err_` and `total_mack_std_err_`." ] }, { "cell_type": "code", "execution_count": null, "id": "67f5d99b-7a5e-4640-a6e0-f8b654e6ce27", "metadata": {}, "outputs": [], "source": [ "__fill_in_code__.full_std_err_" ] }, { "cell_type": "markdown", "id": "bdb08c81-5921-4c41-ad63-96168ffd48b7", "metadata": {}, "source": [ "MackChainladder also has a `summary_` attribute." ] }, { "cell_type": "code", "execution_count": null, "id": "81fc38c1-d5b7-4262-94ae-bce5c7ac17e1", "metadata": {}, "outputs": [], "source": [ "__fill_in_code__.summary_" ] }, { "cell_type": "markdown", "id": "0e285585-62b6-48e4-8b1d-c5824ae5df46", "metadata": {}, "source": [ "Let's make a graph, that shows the Reported and IBNR as stacked bars, and error bars showing Mack Standard Errors." ] }, { "cell_type": "code", "execution_count": null, "id": "e615b86e-a907-4445-9e95-645090719f76", "metadata": {}, "outputs": [], "source": [ "plt.bar(\n", " mcl_mod.summary_.to_frame(origin_as_datetime=True).index.year,\n", " mcl_mod.summary_.to_frame(origin_as_datetime=True)[__fill_in_code__],\n", " label=\"Reported\",\n", ")\n", "plt.bar(\n", " mcl_mod.summary_.to_frame(origin_as_datetime=True).index.year,\n", " mcl_mod.summary_.to_frame(origin_as_datetime=True)[__fill_in_code__],\n", " bottom=mcl_mod.summary_.to_frame(origin_as_datetime=True)[__fill_in_code__],\n", " yerr=mcl_mod.summary_.to_frame(origin_as_datetime=True)[__fill_in_code__],\n", " label=\"IBNR\",\n", ")\n", "plt.legend(loc=\"upper left\")" ] }, { "cell_type": "markdown", "id": "785120ad-03cf-48a7-90d8-d1d56a75ef88", "metadata": {}, "source": [ "ODP Bootstrap is also available. Let's build sample 10,000 Incurred triangles." ] }, { "cell_type": "code", "execution_count": null, "id": "859e19f3-d526-435c-a845-4845a7a3956d", "metadata": {}, "outputs": [], "source": [ "xyz_tri_sampled = (\n", " cl.BootstrapODPSample(n_sims=__fill_in_code__).fit(__fill_in_code__).resampled_triangles_\n", ")\n", "xyz_tri_sampled" ] }, { "cell_type": "markdown", "id": "4391f730-5309-49b2-9c19-0801e3e66c7c", "metadata": {}, "source": [ "We can fit a basic chainladder to all sampled triangles. We now have 10,000 simulated chainladder models, all (most) with unique LDFs." ] }, { "cell_type": "code", "execution_count": null, "id": "fe6dbe70-1b2a-4fb0-aa6b-56380534704f", "metadata": {}, "outputs": [], "source": [ "cl_mod_bootstrapped = cl.Chainladder().fit(xyz_tri_sampled)\n", "cl_mod_bootstrapped" ] }, { "cell_type": "markdown", "id": "bb3d7c32-9e75-4ae4-ab23-0ca3f2a436b5", "metadata": {}, "source": [ "Let's make another graph." ] }, { "cell_type": "code", "execution_count": null, "id": "edeba1db-97e6-43df-b1c0-590c2d7cd098", "metadata": {}, "outputs": [], "source": [ "plt.bar(\n", " cl_mod_bootstrapped.ultimate_.__fill_in_code__.to_frame(origin_as_datetime=True).index.year,\n", " cl_mod_bootstrapped.ultimate_.__fill_in_code__.to_frame(origin_as_datetime=True)[\"2261\"],\n", " yerr=cl_mod_bootstrapped.ultimate_.__fill_in_code__.to_frame(origin_as_datetime=True)[\"2261\"],\n", ")" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "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.9.17" } }, "nbformat": 4, "nbformat_minor": 5 }