# Black linter, optional
%load_ext lab_black

import pandas as pd
import numpy as np
import chainladder as cl
import os

print("pandas: " + pd.__version__)
print("numpy: " + np.__version__)
print("chainladder: " + cl.__version__)

"""
Explanation: Extending Development Patterns with Tails
Getting Started
This tutorial focuses on extending the developent patterns beyond the tail. 
Note that a lot of the examples shown here might not be applicable in a real world scenario, and is only meant to demonstrate some of the functionalities included in the package. The user should always exercise their best actuarial judgement, and follow any applicable laws, the Code of Professional Conduct, and applicable Actuarial Standards of Practice.
Be sure to make sure your packages are updated. For more info on how to update your pakages, visit Keeping Packages Updated.
End of explanation
"""


quarterly = cl.load_sample("quarterly")
quarterly["paid"]

"""
Explanation: Basic Tail Fitting
Tails are another class of transformers. Similar to the Development estimator, they come with fit, transform and fit_transform methods. Also, like our Development estimator, you can define a tail in the absence of data or if you believe development will continue beyond your latest evaluation period.
End of explanation
"""


tail = cl.TailCurve()
tail.fit(quarterly)

print("Triangle latest", quarterly.development.max())
tail.fit(quarterly).ldf_["paid"]

"""
Explanation: Upon fitting data, we get updated ldf_ and cdf_ attributes that extend beyond the length of the triangle. Notice how the tail includes extra development periods (age 147) beyond the end of the triangle (age 135) at which point an age-to-ultimate tail factor is applied.
End of explanation
"""


exp = cl.TailCurve(curve="exponential").fit(quarterly["paid"])
exp.tail_

inv = cl.TailCurve(curve="inverse_power").fit(quarterly["paid"])
inv.tail_

"""
Explanation: These extra twelve months (147 - 135, or one year) of development patterns are included as it is typical to want to track IBNR run-off over a 1-year time horizon from the valuation date. The one-year extension is currently fixed at one year and there is no ability to extend it even further. However, a subsequent version of chainladder will look to address this issue.  
Curve Fitting
Curve fitting takes selected development patterns and extrapolates them using either an exponential or inverse_power fit.  In most cases, the inverse_power produces a thicker (more conservative) tail.
End of explanation
"""


quarterly["incurred"]

cl.TailCurve(fit_period=(12, None), extrap_periods=50).fit(quarterly).ldf_["incurred"]

cl.TailCurve(fit_period=(1, None), extrap_periods=50).fit(quarterly).ldf_["incurred"]

"""
Explanation: When fitting a tail, by default, all of the data will be used; however, we can specify which period of development patterns we want to begin including in the curve fitting process with fit_period. 
Patterns will also be generated for 100 periods beyond the end of the triangle by default, or we can specify how far beyond the triangle to project the tail factor to before dropping the age-to-age factor down to 1.0 using extrap_periods.
Note that even though we can extrapolate the curve many years beyond the end of the triangle for computational purposes, the resultant development factors will compress all ldf_ beyond one year into a single age-ultimate factor.
End of explanation
"""


print("First attempt:")
try:
    cl.TailCurve().fit(cl.Development().fit(quarterly))
    print("This doesn't pass")
except:
    print("This fails because we did not transform the triangle")

print("\nSecond attempt:")
try:
    cl.TailCurve().fit(cl.Development().fit_transform(quarterly))
    print("This passes because we transformed the triangle")
except:
    print("This doesn't fail")

"""
Explanation: In this example, we ignore the first five development patterns for curve fitting, and we allow our tail extrapolation to go 50 quarters beyond the end of the triangle.  Note that both fit_period and extrap_periods follow the development_grain of the triangle being fit.
Chaining Multiple Transformers
It is very common to need to get the development factors first then apply a tail curve to extend our development pattern. chainladder transformers take Triangle objects as inputs, but the returned objects are also Triangle objects with their transform method. To chain multiple transformers together, we must invoke the transform method on each transformer similar to how sklearn approaches its own tranformers.
End of explanation
"""


dev = cl.Development().fit_transform(quarterly)
tail = cl.TailCurve().fit(dev)
tail.cdf_["paid"]

"""
Explanation: We can also invoke the methods without chaining the operations together.
End of explanation
"""


sequence = [
    ("simple_dev", cl.Development(average="simple")),
    ("inverse_power_tail", cl.TailCurve(curve="inverse_power")),
]

pipe = cl.Pipeline(steps=sequence).fit(quarterly)

"""
Explanation: Chaining multiple transformers together is a very common pattern in chainladder.  Like its inspiration sklearn, we can create an overall estimator known as a Pipeline that combines multiple transformers and optional predictors in one estimator.
End of explanation
"""


print(pipe.named_steps.simple_dev)
print(pipe.named_steps.inverse_power_tail)

"""
Explanation: Pipeline keeps references to each step with its named_steps argument.
End of explanation
"""


