---
title: "Heteroskedasticity of unknown form (González-Coya and Perron, 2024)"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Heteroskedasticity of unknown form (González-Coya and Perron, 2024)}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

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

```{r setup}
library(fgls)
```

## The idea

When \(\mathrm{Var}(u_t) = v_t\) varies across observations, weighted least
squares with weights \(1/v_t\) is efficient. The variances are unknown and
their functional form is rarely known. The approach of the 2024 paper:

1. run OLS and form \(r_t = \log \max(\hat u_t^2, \delta^2)\), where the floor
   \(\delta = 0.1\) (`delta`, in the units of \(y\)) avoids \(\log 0\);
2. regress \(r_t\) on a dictionary of candidate covariates \(z_t\) by an
   **adaptive Lasso**. A ridge regression gives weights
   \(|\hat\gamma^{ridge}_j|^{-\psi}\), and the penalty \(\lambda\) and the exponent
   \(\psi \in \{0, 0.25, 0.5, 0.75, 1, 2\}\) are chosen jointly by 10-fold
   cross-validation, with the same folds for every \(\psi\);
3. use \(\exp(\hat r_t)\) as the variances in weighted least squares.

Because the skedastic model is an approximation, the variance of \(\hat\beta\)
is a sandwich (eq. 6) with a bias correction for the estimated weights (eq. 8).
Confidence intervals therefore remain valid when the model for \(v_t\) is wrong.

## An example

```{r example}
het <- simulate_hetero(200, skedastic = "log2", seed = 10)
fit <- lasso_fgls(y ~ x, data = het, seed = 1)
summary(fit)
```

The summary reports the selected covariates, \(\psi\), \(\lambda\) and the degrees
of freedom of the skedastic fit used in the bias correction (the number of
non-zero coefficients, intercept included).

```{r skedastic}
fit$skedastic$coefficients
plot(fit, which = 4)
```

## Candidate covariates

`z = "auto"` (the default) builds \(x, x^2, \log x, (\log x)^2, \cos x,
\cos 2x\) from every non-constant regressor (`make_dictionary()`), which
includes the specification of the paper's simulations. Dummies are passed through
untransformed. You can pass names of regressors, a one-sided formula, or a
matrix, including covariates that are not regressors:

```{r z}
het2 <- simulate_hetero(200, skedastic = "x2", n_extra = 3, seed = 11)
fit2 <- lasso_fgls(y ~ x, data = het2, z = ~ x + I(x^2) + w1 + w2 + w3, seed = 1)
fit2$skedastic$selected
```

The Lasso copes with many candidates, even more than observations. In the
paper's robustness designs with about 60 candidates it loses little against OLS
when the true driver is absent, and gains when it, or a correlated proxy, is
present.

## No loss under homoskedasticity

If nothing is selected, the weights are constant and FGLS equals OLS:

```{r homo}
hom <- simulate_hetero(200, skedastic = "homoskedastic", seed = 12)
fit_h <- lasso_fgls(y ~ x, data = hom, seed = 1)
fit_h$skedastic$selected
all.equal(coef(fit_h), coef(lm(y ~ x, data = hom)))
```

## Alternatives and benchmarks

* `hetero = "wls_s1"` and `"wls_s2"` are the parametric skedastic functions of
  Romano and Wolf (2017), \(\exp(c + \gamma'\log z)\) and \(\exp(c + \gamma'z)\),
  with their HC3-type variance (eq. 7);
* `hetero = "svr"` uses support vector regression (requires 'e1071');
* `hetero = "known"` with `variance` gives GLS with known variances, the
  infeasible benchmark;
* `hetero = "none"` is OLS with HC3 standard errors.

```{r alternatives}
sapply(
  list(
    OLS = lasso_fgls(y ~ x, data = het, hetero = "none"),
    `WLS-S1` = lasso_fgls(y ~ x, data = het, hetero = "wls_s1", z = "x"),
    Lasso = fit,
    GLS = lasso_fgls(y ~ x, data = het, hetero = "known", variance = attr(het, "info")$v)
  ),
  function(f) sqrt(vcov(f)[2, 2])
)
```

## Weights and robustness options

`weight_diagnostics()` summarizes how concentrated the weights are; extreme
weights trigger a warning. `weight_clip = c(0.01, 0.99)` winsorizes the fitted
log-variances, and `fgls_control(delta_scale = TRUE)` makes the floor
proportional to the residual standard deviation.

```{r weights}
weight_diagnostics(fit)
```

## Computation

The Lasso problems are solved exactly along the whole penalty path (LARS
homotopy). `fgls_control(lasso_engine = "glmnet")` uses 'glmnet' followed by an
exact refinement instead; both give the same solutions.
