smartcor

smartcor detects variable types and selects a suitable correlation method for each pair. It supports continuous, count, binary, ordinal, and categorical variables and returns the estimate, inference, selected method, and rationale.

Before choosing a method, it checks the pair for outliers, normality, latent normality, and linearity. It chooses among 18 methods, following the decision framework of Harshvardhan and Ranjan (2026).

Install

Once the package is on CRAN, a single call installs it together with all of its dependencies (including polycor):

install.packages("smartcor")

To install the supplied archive instead, use remotes package.

remotes::install_local("smartcor.zip")

Load the example data

The package includes gss_2024_casestudy.csv. This CSV is frozen for reproducibility. The examples below read the bundled copy and do not download data.

library(smartcor)

csv = system.file("extdata", "gss_2024_casestudy.csv", package = "smartcor")
gss = read.csv(csv)

Correlate one pair

result = smart_cor(gss$coninc, gss$sex, verbose = FALSE)
result
#> 
#> ── Smart Correlation ───────────────────────────────────────────────────────────
#> Estimate: -0.1336
#> Method: Rank-Biserial Correlation
#> Variables: gss$coninc ("continuous") x gss$sex ("binary")
#> N: 3000
#> p-value: < 0.001
#> Test: Wald test on Fisher-z scale
#> H0: delta = 0
#> Small p-values (e.g., p < 0.05) indicate evidence against H0.
#> 95% CI: [-0.1743, -0.0925]
#> Source: Cliff (1996)
#> 
#> Diagnostics
#> Outliers (outer fence): none
#> Normality (Jarque-Bera): p < 2e-16 (rejected)
#> 
#> The Jarque-Bera test rejects normality of the within-group residuals (p <
#> 2e-16); Rank-Biserial Correlation selected.
#> 
#> ℹ Alternatives: "point_biserial" and "biserial" (pass `method = "..."` to use)

The fields are available directly:

result$estimate
result$method
result$p.value
c(result$ci_lower, result$ci_upper)
result$diagnostics

Set assume_latent_normal to control pairs that can use a latent-variable method:

smart_cor(gss$degree, gss$happy, assume_latent_normal = "auto")
smart_cor(gss$degree, gss$happy, assume_latent_normal = TRUE)
smart_cor(gss$degree, gss$happy, assume_latent_normal = FALSE)

The default, "auto", runs the likelihood-ratio test of latent normality for each affected pair. TRUE and FALSE fix the answer; the outlier and Jarque-Bera checks still run. A binary-by-binary table is saturated, so automatic selection uses phi; TRUE gives tetrachoric and FALSE gives Yule’s Q.

Diagnostics

Each diagnostic is also available on its own. All tests use alpha = 0.05 unless you pass another value to smart_cor(), smart_cormat(), smart_cor_df(), or compare_methods().

detect_outliers(gss$coninc)                      # outer fences, Q1 - 3 IQR and Q3 + 3 IQR
test_bivariate_normality(gss$age, gss$coninc)    # Mardia's test
test_bivariate_normality(gss$coninc, gss$degree) # Jarque-Bera and the LR test
test_bivariate_normality(gss$degree, gss$happy)  # LR test of the threshold model
test_linearity(gss$coninc, gss$age)

For two continuous/count variables without outliers, smart_cor() runs test_linearity(). The test regresses each variable on the other, orders the residuals by the regressor (averaging residuals at tied values), and applies the Box-Pierce, Ljung-Box, BDS, Breusch-Pagan, and RESET rule in each direction. A direction rejects linearity when a residual structure test and RESET both reject. Spearman is selected only when both directions reject; otherwise Pearson is retained. Each direction’s p-value is max(min(p_bp, p_lb, p_bds), p_reset), and the combined p-value is the larger of the two. An explicit method skips all diagnostics.

Because both directions are tested and tied values are averaged, the decision does not depend on argument order or row order. An inconclusive result retains Pearson and reports the reason. When the rule recommends robust/bootstrap Pearson inference, bootstrap = "auto" uses a paired-bootstrap interval; the p-value remains the standard Pearson t-test. bootstrap = FALSE disables that bootstrap.

Build a matrix

columns = c("age", "coninc", "degree", "happy", "sex", "region")
matrix = smart_cormat(gss[, columns], seed = 1, verbose = FALSE)
matrix$methods
#>        age              coninc           degree           happy           
#> age    "pearson"        "spearman"       "kendall"        "kendall"       
#> coninc "spearman"       "pearson"        "kendall"        "kendall"       
#> degree "kendall"        "kendall"        "polychoric"     "kendall"       
#> happy  "kendall"        "kendall"        "kendall"        "polychoric"    
#> sex    "rank_biserial"  "rank_biserial"  "rank_biserial"  "polychoric"    
#> region "kw_eta_squared" "kw_eta_squared" "freemans_theta" "freemans_theta"
#>        sex             region          
#> age    "rank_biserial" "kw_eta_squared"
#> coninc "rank_biserial" "kw_eta_squared"
#> degree "rank_biserial" "freemans_theta"
#> happy  "polychoric"    "freemans_theta"
#> sex    "phi"           "cramers_v"     
#> region "cramers_v"     "cramers_v"
matrix$correlations
matrix$types
tidy(matrix)

Use smart_cor_df() when a script needs plain data frames:

plain = smart_cor_df(gss[, columns])
plain$correlations
plain$methods

Set nearPD = TRUE when downstream analysis requires a positive-definite matrix:

adjusted = smart_cormat(gss[, columns], nearPD = TRUE, verbose = FALSE)
adjusted$correlations
adjusted$original_correlations
adjusted$nearPD
plain = smart_cor_df(gss[, columns], nearPD = TRUE)

The default is nearPD = FALSE. The adjustment calls Matrix::nearPD(..., corr = TRUE) and preserves the diagonal of ones. Methods, pairwise details, and tidy() retain the original estimates and inference. Missing or infinite matrix entries must be resolved before adjustment.

Compare methods

comparison = compare_methods(
  gss$degree,
  gss$happy,
  bootstrap = FALSE,
  verbose = FALSE
)
comparison$results

Plot a matrix

plot(matrix)
ggcor_heatmap(matrix)
ggcor_method_heatmap(matrix)

Method selection

Variable types Default method Alternative
Correlation methods (signed, from -1 to +1)
Continuous + continuous Pearson (linear, no outliers) Spearman (outliers or nonlinear), Kendall’s tau
Continuous + binary Biserial (if latent normality holds) Point-biserial; rank-biserial (outliers or non-normal)
Binary + binary Pearson = phi Tetrachoric (latent normal), Yule’s Q (no latent continuum)
Continuous + ordinal Polyserial (if latent normality holds) Kendall’s tau (m <= 5) or Spearman
Ordinal + ordinal Polychoric (if latent normality holds) Kendall’s tau (m <= 5) or Spearman; Goodman-Kruskal gamma
Binary + ordinal Polychoric (if latent normality holds) Rank-biserial; Kendall’s tau, Spearman
Association measures (unsigned, from 0 to 1)
Continuous + categorical Correlation ratio eta-squared Kruskal-Wallis eta-squared (outliers or non-normal); Cramer’s V (binned)
Binary + categorical Cramer’s V –
Ordinal + categorical Freeman’s theta Cramer’s V
Categorical + categorical Cramer’s V Theil’s U, Tschuprow’s T

m is the number of categories of the ordinal variable (the smaller of the two for an ordinal pair). Count variables follow the continuous rows.

Methods

The package implements 18 methods: Pearson, Spearman, Kendall’s tau, point-biserial, biserial, rank-biserial, phi, tetrachoric, Yule’s Q, polychoric, polyserial, Goodman-Kruskal’s gamma, the correlation ratio (eta-squared), Kruskal-Wallis eta-squared, Freeman’s theta, Cramer’s V, Tschuprow’s T, and Theil’s U. Each comes with a confidence interval and a p-value.

Paper

The accompanying paper, smartcor: Intelligent Correlation Method Selection for Mixed Variable Types by M. Harshvardhan and Pritam Ranjan (2026), is available as an arXiv preprint: arXiv:2607.22285 (doi:10.48550/arXiv.2607.22285). The package vignettes cover the same material: method selection, the underlying theory, and inference.

Authors

M. Harshvardhan (maintainer) and Pritam Ranjan.

License

GPL (>= 3)