---
title: "Fitting and Comparing Severity Candidates"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Fitting and Comparing Severity Candidates}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r setup, include=FALSE}
knitr::opts_chunk$set(collapse = TRUE, comment = "#>")
library(actuarialfitdist)
```

# Workflow and data interpretation

`fit_severity_candidates()` fits competing parametric ground-up severity
families and returns a list with `fits`, a `comparison` data frame, and
`excluded_rows` / `excluded_details`. The observations **must be ground-up**;
net paid losses should not be substituted without reconstructing that basis.
A finite policy censoring limit means values at the limit are right-censored;
no limit or deductible column is needed if it does not apply.

```{r simulated-data}
set.seed(128)
n <- 750
latent <- rlnorm(n, log(14000), 1.0)
limits <- sample(c(20000, 50000, 100000, Inf), n, replace = TRUE)
dat <- data.frame(ground_up_loss = pmin(latent, limits),
                  censor_limit = limits)
```

# Fit, compare, and extract a selected fit

```{r candidate-fit}
candidates <- fit_severity_candidates(dat, loss = "ground_up_loss",
  limit = "censor_limit", distributions = c("lognormal", "gamma", "weibull"),
  hessian = FALSE)
print(candidates$comparison)
candidates$comparison[order(candidates$comparison$AIC), ]
fit_lognormal <- candidates$fits$lognormal
summary(fit_lognormal)
```

Lower AIC/BIC is better **relative to other fits on the same observation model**.
A higher log likelihood is better. These are not absolute goodness-of-fit
measures and can hide tail or layer errors. Not every distribution should be
forced onto every loss dataset; inspect convergence and factor stability.

# Visual comparison

Modeled portfolio probabilities reproduce row-level censoring limits and deductibles.
Thus the histogram comparisons are made on the **observed** basis.
The new analytic default computes expected bin counts without Monte Carlo
simulation; `method = "simulation"` restores the stochastic plot. The default
horizontal axis is logarithmic, and specifying `xlim` changes only the displayed
range. Use `scale_range = c(lower, upper)` when fitting with `scale_by`.

```{r candidate-plots, fig.width=8, fig.height=5}
plot_candidate_fits(candidates, type = "histogram",
  x_scale = "log10", xlim = c(500, 200000),
  x_text_angle = 45, x_text_size = 8)
```

```{r candidate-data}
# Deterministic summaries: the very same bins drawn in the default plot.
head(candidate_plot_data(candidates, output = "plot_ready",
  type = "histogram", xlim = c(500, 200000)))

# Old row-level simulations remain available when requested.
head(candidate_plot_data(candidates, nsim = 1, seed = 22, output = "raw"))
```

# Convert fits to rating factors and assess sensitivity

`limit` in `severity_factors()` is a **payment limit after the deductible**:
`E[min(max(v*X-d,0),L)]`. This is distinct from the ground-up censoring limit
used for fitting. In particular, with `valuation_factor = 1`, finite payment
limits require integrated survival between `d` and `d + L`.

```{r candidate-factors}
factors <- severity_factors(candidates, deductible = c(500, 1000),
  limit = c(25000, 50000, Inf), base_deductible = 500, base_limit = 25000)
head(factors)
compare_factors(candidates, metric = "rating_factor",
  deductible = c(500, 1000), limit = c(25000, 50000),
  base_deductible = 500, base_limit = 25000)
```

By default, the grid is Cartesian. Use `grid = "paired"` to match vectors
positionally, allowing scalar recycling. This prevents unexpectedly generating
10,000 scenarios from two vectors of length 100.

# Excluded claims

Rows with below-deductible or above-censor-limit losses are excluded by default
with a warning. The original positions and reasons are retained. This behavior
is not a claim that these payments were necessarily erroneous: extra-contractual
payments, bad-faith settlements, and mismatched ground-up definitions require
specific analyst investigation, not silent censoring.

```{r anomaly-audit}
bad <- dat
bad$ground_up_loss[2] <- bad$censor_limit[2] + 1000
# Use a guaranteed finite limit in the example.
bad$censor_limit[2] <- 10000
bad$ground_up_loss[2] <- 10500
fit <- suppressWarnings(fit_severity(bad, loss = "ground_up_loss",
  limit = "censor_limit", distribution = "lognormal", hessian = FALSE))
fit$excluded_rows
fit$excluded_details
```


# Optimization diagnostics

Inspect the candidate table for outlying runtimes or optimizer evaluation
counts. When fitting `scale_intercept = TRUE`, the optimizer automatically
scales its intercept parameter; it does not constrain its value. A caller can
still supply `control$parscale` explicitly. The displayed fit coefficients
remain in their original units.

```{r optimizer-table}
candidates$comparison[, c("distribution", "elapsed_seconds",
  "function_evaluations", "gradient_evaluations", "convergence_code")]
```
