Every estimate ships with its uncertainty
A correlation coefficient on its own is hard to act on.
smartcor therefore returns a full inference payload with
every estimate: a confidence interval, a p-value, the null hypothesis
being tested, and a short provenance string naming the source of each
quantity. The guiding principle is that the method dictates the
inference: a Kendall interval should not be computed the way a
Pearson interval is, and the package picks the right machinery for you
and tells you which it used.
The inference payload
Consider a routine continuous-continuous pair.
r = smart_cor(mtcars$mpg, mtcars$wt)
#>
#> ── Variable types ──
#>
#> ℹ Detected as continuous (numeric with 25 unique values).
#> mtcars$mpg: "continuous"
#> ℹ Detected as continuous (numeric with 29 unique values).
#> mtcars$wt: "continuous"
#>
#> ── Method selection ──
#>
#> ✔ Selected: Pearson CorrelationThe console block already surfaces the interval, the p-value, the
test, the null, and the source. For programmatic access,
tidy() lays the same payload out as columns.
library(tibble) # for printing
infcols = c("estimate", "statistic", "p.value", "p_method", "null_hypothesis",
"ci_lower", "ci_upper", "conf_level", "ci_method", "ci_source")
as.data.frame(tidy(r)[infcols])
#> estimate statistic p.value p_method null_hypothesis
#> 1 -0.8676594 -9.559044 1.293959e-10 t-test on r (cor.test) rho = 0
#> ci_lower ci_upper conf_level ci_method ci_source
#> 1 -0.9338264 -0.7440872 0.95 analytic Fisher z (cor.test)Two fields are worth dwelling on. ci_method is either
"analytic" (a closed-form or asymptotic interval) or
"bootstrap" (a resampled interval), and
ci_source names the exact reference or procedure behind it.
Together they make the interval auditable: you can always see
how it was produced, not just its endpoints.
Analytic intervals are the default
Thirteen of the fourteen methods carry a closed-form or asymptotic
confidence interval, and smartcor uses it by default. Only
Theil’s~U, which has no widely accepted analytic interval, falls back to
resampling. The table below is generated by the package itself: for each
method we force that estimator on a suitable pair and read back the
inference provenance, so it cannot drift from what the code actually
does.
set.seed(1)
colour = factor(sample(c("red", "blue", "green"), 90, replace = TRUE))
shape = factor(sample(c("circle", "square", "triangle"), 90, replace = TRUE))
one = function(method, x, y) {
tidy(smart_cor(x, y, method = method, verbose = FALSE))[
c("method_label", "ci_method", "ci_source", "p_method", "null_hypothesis")]
}
spec = list(
one("pearson", mtcars$mpg, mtcars$wt),
one("spearman", mtcars$mpg, mtcars$wt),
one("kendall", mtcars$mpg, mtcars$wt),
one("point_biserial", mtcars$mpg, mtcars$vs),
one("phi", mtcars$vs, mtcars$am),
one("tetrachoric", mtcars$vs, mtcars$am),
one("yules_q", mtcars$vs, mtcars$am),
one("polychoric", mtcars$gear, mtcars$carb),
one("polyserial", mtcars$mpg, mtcars$gear),
one("gamma", mtcars$gear, mtcars$carb),
one("rank_biserial", mtcars$vs, mtcars$gear),
one("cramers_v", colour, shape),
one("theils_u", colour, shape),
one("tschuprows_t", colour, shape)
)
map = do.call(rbind, spec)
knitr::kable(map[c("method_label", "ci_method", "ci_source")],
row.names = FALSE, caption = "How each method's confidence interval is computed.")| method_label | ci_method | ci_source |
|---|---|---|
| Pearson Correlation | analytic | Fisher z (cor.test) |
| Spearman Rank Correlation | analytic | Bonett and Wright (2000) |
| Kendall’s Tau-b | analytic | Fieller, Hartley, and Pearson (1957) |
| Point-Biserial Correlation (= Pearson) | analytic | Fisher z (cor.test); = Pearson |
| Phi Coefficient (= Pearson for 0/1) | analytic | Noncentral chi-square pivot |
| Tetrachoric Correlation | analytic | Olsson (1979) and Joreskog (1994) |
| Yule’s Q | analytic | Brown and Benedetti (1977; gamma on 2x2) |
| Polychoric Correlation | analytic | Olsson (1979) and Joreskog (1994) |
| Polyserial Correlation | analytic | Olsson, Drasgow, and Dorans (1982) |
| Goodman-Kruskal’s Gamma | analytic | Brown and Benedetti (1977) |
| Rank-Biserial Correlation | analytic | Cliff (1996) |
| Cramer’s V | analytic | Noncentral chi-square pivot |
| Theil’s U (Uncertainty Coefficient) | bootstrap | Percentile bootstrap |
| Tschuprow’s T | analytic | Noncentral chi-square pivot (rescaled V) |
The analytic sources are the standard ones: Fisher’s~ transform for Pearson and the point-biserial coefficient; Bonett and Wright (2000) for Spearman; Fieller, Hartley, and Pearson (1957) for Kendall; the asymptotic standard errors of Olsson (1979) and Olsson, Drasgow, and Dorans (1982) on the Fisher- scale for the latent-variable methods (polychoric, tetrachoric, polyserial); Brown and Benedetti (1977) for Goodman-Kruskal’s gamma and Yule’s~Q; Cliff (1996) for the rank-biserial coefficient; and a noncentral chi-square pivot for the chi-square-based association measures (phi, Cramer’s~V, Tschuprow’s~T). The noncentral pivot deserves a note: it inverts the chi-square test for the noncentrality parameter, so its intervals are asymmetric and correctly bound at zero when the association is weak, rather than spilling below it. One caveat: the point-biserial interval reuses the Fisher- variance , which assumes bivariate normality; a binary margin cannot satisfy that exactly, so treat this interval as an approximation.
The bootstrap argument
The bootstrap argument is a tri-state control.
-
"auto"(the default) uses the analytic interval when one exists and is finite, and resamples only when it does not, for example when a latent-variable optimiser returns a singular Hessian or a contingency table is too sparse for the pivot. -
TRUEalways adds a percentile bootstrap, which is useful for cross-checking an analytic interval against a distribution-free one. -
FALSEreports only the analytic interval, returningNAendpoints if none is available.
The number of resamples is set by n_boot (default 500).
Here we ask for a bootstrap interval on the same Pearson pair and
compare it against the
Fisher-
interval above.
rb = smart_cor(mtcars$mpg, mtcars$wt, bootstrap = TRUE, n_boot = 1000, verbose = FALSE)
tidy(rb)[c("estimate", "ci_lower", "ci_upper", "ci_method", "ci_source")]
#> # A tibble: 1 × 5
#> estimate ci_lower ci_upper ci_method ci_source
#> <dbl> <dbl> <dbl> <chr> <chr>
#> 1 -0.868 -0.925 -0.792 bootstrap Percentile bootstrapThe two intervals agree closely here, so the asymptotic approximation
holds up on this pair. Pass seed if you need the resampled
interval to be reproducible; a seeded call restores your
.Random.seed on exit. The confidence level is controlled by
conf_level; lowering it narrows the interval.
rbind(
`90%` = tidy(smart_cor(mtcars$mpg, mtcars$wt, conf_level = 0.90, verbose = FALSE))[c("ci_lower", "ci_upper")],
`95%` = tidy(smart_cor(mtcars$mpg, mtcars$wt, conf_level = 0.95, verbose = FALSE))[c("ci_lower", "ci_upper")]
)
#> # A tibble: 2 × 2
#> ci_lower ci_upper
#> * <dbl> <dbl>
#> 1 -0.926 -0.769
#> 2 -0.934 -0.744The null hypothesis is method-specific
A correlation p-value is only meaningful against a stated null, and
the natural null differs across methods. Pearson, Spearman, Kendall, and
the latent-variable methods test whether the (latent) correlation is
zero; the chi-square-based measures test independence of the two
variables; Theil’s~U tests whether one variable carries no information
about the other. The null_hypothesis and
p_method columns make this explicit, so a small p-value is
never ambiguous about what has been rejected.
knitr::kable(unique(map[c("method_label", "p_method", "null_hypothesis")]),
row.names = FALSE, caption = "The null hypothesis and test behind each p-value.")| method_label | p_method | null_hypothesis |
|---|---|---|
| Pearson Correlation | t-test on r (cor.test) | rho = 0 |
| Spearman Rank Correlation | t approximation (cor.test, exact = FALSE) | rho_S = 0 |
| Kendall’s Tau-b | asymptotic normal (cor.test, exact = FALSE) | tau = 0 |
| Point-Biserial Correlation (= Pearson) | t-test on r (cor.test) | rho_pb = 0 |
| Phi Coefficient (= Pearson for 0/1) | Pearson chi-square test of independence | variables are independent |
| Tetrachoric Correlation | Wald test on Fisher-z scale | rho = 0 |
| Yule’s Q | Wald test on Fisher-z scale | Q = 0 |
| Polychoric Correlation | Wald test on Fisher-z scale | rho = 0 |
| Polyserial Correlation | Wald test on Fisher-z scale | rho = 0 |
| Goodman-Kruskal’s Gamma | Wald test on Fisher-z scale | gamma = 0 |
| Rank-Biserial Correlation | Wald test on Fisher-z scale | delta = 0 |
| Cramer’s V | Pearson chi-square test of independence | variables are independent |
| Theil’s U (Uncertainty Coefficient) | Permutation test (B = 500, one-sided) | U = 0 (X independent of Y) |
| Tschuprow’s T | Pearson chi-square test of independence | variables are independent |
Reading interval widths
Interval widths are worth reading, not just the point estimates.
compare_methods() reports every applicable method for a
pair side by side, with each interval on its own terms.
compare_methods(mtcars$gear, mtcars$carb)
#>
#> ── Comparing methods ──
#>
#> mtcars$gear ("ordinal") × mtcars$carb ("ordinal")
#> N: 32
#> Methods: "polychoric", "kendall", "spearman", and "gamma"
#>
#> "polychoric": 0.2451 ← recommended
#> "kendall": 0.0980
#> "spearman": 0.1149
#> "gamma": 0.1405
#>
#> ── Method Comparison ───────────────────────────────────────────────────────────
#> Variables: mtcars$gear ("ordinal") × mtcars$carb ("ordinal")
#> N: 32
#>
#> Polychoric Correlation ← recommended
#> r = 0.2451 p = 0.2490 95% CI [-0.1734, 0.5886]
#> Kendall's Tau-b
#> r = 0.0980 p = 0.5302 95% CI [-0.1455, 0.3303]
#> Spearman Rank Correlation
#> r = 0.1149 p = 0.5312 95% CI [-0.2447, 0.4467]
#> Goodman-Kruskal's Gamma
#> r = 0.1405 p = 0.5433 95% CI [-0.3046, 0.5353]The Goodman-Kruskal gamma interval is much wider than the rank-based ones. Gamma discards all tied pairs, so its effective sample size is smaller and its interval is correspondingly less precise. Two methods with similar coefficients can differ a lot in how firmly those coefficients are pinned down.
Summary
Every result carries an interval, a p-value, an explicit null, and a
named source. Analytic intervals are the default for all but one method,
and the bootstrap argument covers the rest: a
distribution-free cross-check when you want one, and a fallback when an
analytic interval is unavailable.