Skip to content

[Feature] cor_diff() : test for differences between correlations #338

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ S3method(pcor_to_cor,matrix)
S3method(plot,easycor_test)
S3method(plot,easycormatrix)
S3method(plot,easycorrelation)
S3method(print,cor_diff)
S3method(print,easycormatrix)
S3method(print,easycorrelation)
S3method(print,easymatrixlist)
Expand All @@ -38,6 +39,7 @@ S3method(summary,easycorrelation)
S3method(visualisation_recipe,easycor_test)
S3method(visualisation_recipe,easycormatrix)
S3method(visualisation_recipe,easycorrelation)
export(cor_diff)
export(cor_lower)
export(cor_smooth)
export(cor_sort)
Expand Down
121 changes: 121 additions & 0 deletions R/cor_diff.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
#' Test differences between correlations
#'
#' @description
#' Tests whether the correlation between two variables `x` and `y` is different
#' from the correlation between `x2` and `y2`.
#'
#' `cor_diff()` returns a table containing an index of difference precision (i.e.,
#' the estimated difference divided by its standard error) and an associated p-value.
#' A significant p-value indicates that the correlation between `x` and `y` is
#' different from the correlation between `x2` and `y2`.
#'
#' @param data A data frame of observations.
#' @param x,y,x2,y2 The variable names in `data` to be used. `x` and `y` can also
#' be pairs of variables, in which case the second variable is used as `x2` and `y2`.
#' @param method Can be `"parametric"` or `"bootstrapping"`. If `"parametric"`,
#' the [psych::r.test()] function is used. If `"bootstrapping"`, a bootstrapping
#' procedure is used.
#' @param ... Other arguments to be passed, for instance `iterations` (default: 1000)
#' if method is bootstrapping.
#'
#' @examples
#' cor_diff(iris, c("Sepal.Length", "Sepal.Width"), c("Sepal.Length", "Petal.Width"))
#' cor_diff(iris,
#' c("Sepal.Length", "Sepal.Width"),
#' c("Sepal.Length", "Petal.Width"),
#' method = "bootstrapping", iterations = 100
#' )
#' @export
cor_diff <- function(data, x, y, x2 = NULL, y2 = NULL, method = "parametric", ...) {
# If pairs are passed
if (length(x) == 2 && length(y) == 2) {
x2 <- y[1]
y2 <- y[2]
y <- x[2]
x <- x[1]
}

# Compute
if (method %in% c("bootstrapping")) {

Check warning on line 39 in R/cor_diff.R

View workflow job for this annotation

GitHub Actions / lint / lint

file=R/cor_diff.R,line=39,col=19,[unnecessary_concatenation_linter] Remove unnecessary c() of a constant.

Check warning on line 39 in R/cor_diff.R

View workflow job for this annotation

GitHub Actions / lint-changed-files / lint-changed-files

file=R/cor_diff.R,line=39,col=19,[unnecessary_concatenation_linter] Remove unnecessary c() of a constant.
out <- .cor_diff_bootstrapping(data, x, y, x2, y2, ...)
} else {
out <- .cor_diff_parametric(data, x, y, x2, y2, ...)
}
class(out) <- c("cor_diff", class(out))
out
}



# Methods -----------------------------------------------------------------



#' @keywords internal
.cor_diff_parametric <- function(data, x, y, x2, y2, ...) {
insight::check_if_installed("psych", "for 'parametric' correlation difference method")

args <- list(n = nrow(data), r12 = stats::cor(data[[x]], data[[y]]))

Check warning on line 58 in R/cor_diff.R

View workflow job for this annotation

GitHub Actions / lint / lint

file=R/cor_diff.R,line=58,col=3,[object_overwrite_linter] 'args' is an exported object from package 'base'. Avoid re-using such symbols.

Check warning on line 58 in R/cor_diff.R

View workflow job for this annotation

GitHub Actions / lint-changed-files / lint-changed-files

file=R/cor_diff.R,line=58,col=3,[object_overwrite_linter] 'args' is an exported object from package 'base'. Avoid re-using such symbols.
if (x == x2 && y != y2) {
args$r13 <- stats::cor(data[[x]], data[[y2]])
args$r23 <- stats::cor(data[[y]], data[[y2]])
} else if (y == y2 && x != x2) {
args$r13 <- stats::cor(data[[y]], data[[x2]])
args$r23 <- stats::cor(data[[x]], data[[x2]])
} else {
args$r34 <- stats::cor(data[[x2]], data[[y2]])
}
test <- do.call(psych::r.test, args)

out <- data.frame(

Check warning on line 70 in R/cor_diff.R

View workflow job for this annotation

GitHub Actions / lint / lint

file=R/cor_diff.R,line=70,col=10,[strings_as_factors_linter] Supply an explicit value for stringsAsFactors for this code to work before and after R version 4.0.

Check warning on line 70 in R/cor_diff.R

View workflow job for this annotation

GitHub Actions / lint-changed-files / lint-changed-files

file=R/cor_diff.R,line=70,col=10,[strings_as_factors_linter] Supply an explicit value for stringsAsFactors for this code to work before and after R version 4.0.
Method = "parametric"
)
if ("t" %in% names(test)) {
out$t <- test$t
} else {
out$z <- test$z
}
out$p <- test$p
out
}

#' @keywords internal
.cor_diff_bootstrapping <- function(data, x, y, x2, y2, iterations = 1000, robust = FALSE, ...) {
diff <- rep(NA, iterations) # Initialize vector

Check warning on line 84 in R/cor_diff.R

View workflow job for this annotation

GitHub Actions / lint / lint

file=R/cor_diff.R,line=84,col=3,[object_overwrite_linter] 'diff' is an exported object from package 'base'. Avoid re-using such symbols.

Check warning on line 84 in R/cor_diff.R

View workflow job for this annotation

GitHub Actions / lint-changed-files / lint-changed-files

file=R/cor_diff.R,line=84,col=3,[object_overwrite_linter] 'diff' is an exported object from package 'base'. Avoid re-using such symbols.

# Bootstrap
for (i in 1:iterations) {
# Take random sample of data
dat <- data[sample(nrow(data), nrow(data), replace = TRUE), ]
# Compute diff
diff[i] <- stats::cor(dat[[x]], dat[[y]]) - stats::cor(dat[[x2]], dat[[y2]])
}

# Summarize
if (robust == FALSE) {

Check warning on line 95 in R/cor_diff.R

View workflow job for this annotation

GitHub Actions / lint / lint

file=R/cor_diff.R,line=95,col=7,[redundant_equals_linter] Using == on a logical vector is redundant. Well-named logical vectors can be used directly in filtering. For data.table's `i` argument, wrap the column name in (), like `DT[(is_treatment)]`.

Check warning on line 95 in R/cor_diff.R

View workflow job for this annotation

GitHub Actions / lint-changed-files / lint-changed-files

file=R/cor_diff.R,line=95,col=7,[redundant_equals_linter] Using == on a logical vector is redundant. Well-named logical vectors can be used directly in filtering. For data.table's `i` argument, wrap the column name in (), like `DT[(is_treatment)]`.
out <- data.frame(

Check warning on line 96 in R/cor_diff.R

View workflow job for this annotation

GitHub Actions / lint / lint

file=R/cor_diff.R,line=96,col=12,[strings_as_factors_linter] Supply an explicit value for stringsAsFactors for this code to work before and after R version 4.0.

Check warning on line 96 in R/cor_diff.R

View workflow job for this annotation

GitHub Actions / lint-changed-files / lint-changed-files

file=R/cor_diff.R,line=96,col=12,[strings_as_factors_linter] Supply an explicit value for stringsAsFactors for this code to work before and after R version 4.0.
Method = "bootstrapping",
z = mean(diff) / stats::sd(diff),
p = bayestestR::pd_to_p(as.numeric(bayestestR::p_direction(diff)))
)
} else {
out <- data.frame(

Check warning on line 102 in R/cor_diff.R

View workflow job for this annotation

GitHub Actions / lint / lint

file=R/cor_diff.R,line=102,col=12,[strings_as_factors_linter] Supply an explicit value for stringsAsFactors for this code to work before and after R version 4.0.

Check warning on line 102 in R/cor_diff.R

View workflow job for this annotation

GitHub Actions / lint-changed-files / lint-changed-files

file=R/cor_diff.R,line=102,col=12,[strings_as_factors_linter] Supply an explicit value for stringsAsFactors for this code to work before and after R version 4.0.
Method = "bootstrapping_robust",
z = stats::median(diff) / stats::mad(diff),
p = bayestestR::pd_to_p(as.numeric(bayestestR::p_direction(diff)))
)
}
out
}



# Printing ----------------------------------------------------------------

#' @export
print.cor_diff <- function(x, ...) {
insight::format_table(x, ...) |>
insight::export_table(title = "Correlation Difference Test") |>
print()
invisible(x)
}
38 changes: 38 additions & 0 deletions man/cor_diff.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions tests/testthat/test-cor_diff.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
test_that("cor_diff", {
expect_equal(
cor_diff(iris, "Sepal.Length", "Sepal.Width", "Sepal.Length", "Petal.Width")$t,
-10,
tolerance = 0.001
)
})
Loading