--- title: "Arc length as a statistical functional" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Arc length as a statistical functional} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set(collapse = TRUE, comment = "#>") ``` ```{r setup} library(arcstat) ``` ## The idea Most statistical functionals summarise a curve by its height somewhere: a moment, a quantile, a density value. Arc length summarises it by how far you travel along it. For a quantile function $Q$ on $(0,1)$ the arc length is $$\int_0^1 \sqrt{1 + Q'(u)^2}\, du,$$ which is large when the curve is steep somewhere and small when it is flat. It is a measure of curve complexity rather than of location or spread, and it responds to structure that the usual functionals average away. This package collects the arc-length constructions: a goodness-of-fit test, a family of distributions indexed by the shape of the quantile density, an equivalence family, the characteristic-function version, and a Bayesian test. ## Arc length of a quantile family The `arcq` family has quantile density $Q'(u) = \sigma[1 + \sum_k c_k P_k(u)]_+$ with $P_k$ the shifted Legendre polynomials. ```{r} o <- arcq(coef = c(0.5, -0.3), mu = 0, sigma = 1) arclength(o) ``` Arc length is a *shape* functional, so shifting the distribution cannot change it: ```{r} c(at_zero = arclength(arcq(0.5, mu = 0)), at_seventeen = arclength(arcq(0.5, mu = 17))) ``` The integrand is analytic on $[0,1]$, so Gauss-Legendre converges geometrically. Sixteen nodes already reach machine precision, where the equally spaced grid it replaced converges linearly: ```{r} vapply(c(8L, 16L, 32L, 64L), function(k) arclength(o, nodes = k), 0) ``` The integral is generally not elementary. With $Q'$ of degree one the antiderivative is an inverse hyperbolic sine; with $Q'$ of degree two it is an elliptic integral, which by Liouville's theorem has no elementary antiderivative; beyond that it is hyperelliptic. ## Goodness of fit Under the null the probability integral transform is uniform, and the arc length of the resulting probability plot has a known distribution with an analytic saddlepoint tail. The test is powerful against local density structure — multimodality, clustering, heaping — and weak against smooth location and scale departures, which is the opposite of the empirical-distribution tests. ```{r} set.seed(1) al_test(runif(200))$p.value # null holds al_test(c(runif(100), rbeta(100, 8, 8)))$p.value # a clustered middle ``` ## Characteristic-function arc length The same functional applied to the characteristic-function curve is scale free, and it has closed forms for several families. Two of them anchor the whole construction: ```{r} c(normal = cf_arclength_family("normal"), exponential = cf_arclength_family("exponential", lambda = 1), pi = pi) ``` A symmetric monotone (Polya) law carries the value two, and the exponential carries $\pi$ exactly. Because total arc length is scale free, the exponential rate cannot matter: ```{r} c(rate_1 = cf_arclength_family("exponential", lambda = 1), rate_2 = cf_arclength_family("exponential", lambda = 2)) ``` The empirical version approaches the theoretical one on a large sample: ```{r} set.seed(3) cf_arclength(rnorm(50000)) ``` ## The equivalence family A separate strand asks when two readings of a curve agree. The shoulder of a quantile density solves $3q'^2 = q q''$, transcendental in general. On the two-exponent family $q(u) = u^{\alpha}(1-u)^{\beta}$ it collapses to a quadratic, so the shoulder is available in closed form, and the returned root satisfies the *original* equation: ```{r} al <- -0.60; be <- -0.35 ub <- eq_ub_quad(al, be)[1] gp <- al / ub - be / (1 - ub) gpp <- -al / ub^2 - be / (1 - ub)^2 c(shoulder = ub, residual_of_original_equation = abs(2 * gp^2 - gpp)) ``` Equivalence itself is the vanishing of a closed-form discrepancy, and the solution curve is increasing in $\alpha$: ```{r} als <- seq(-0.62, -0.54, by = 0.02) bss <- vapply(als, eq_bstar, 0) rbind(alpha = als, beta_star = round(bss, 6)) c(discrepancy_on_the_curve = eq_E(-0.60, eq_bstar(-0.60))) ``` ## L-moments Population and sample L-moments are included, since the families above are estimated by matching them. A uniform grid has first L-moment $1/2$ and L-scale $1/6$ exactly: ```{r} u <- (seq_len(20000) - 0.5) / 20000 sample_lmoments(u, 4L)[1:2] ``` ## Implementation The numerical primitives run in a shared C back-end that is also bound from Python, and the two front ends are checked against each other value by value.