Repository: easystats/correlation
Pull request #346: Add 'missing' arg
Author: login: mattansb
Created: 2025-04-02T11:34:57Z | Merged: 2025-04-18T15:43:19Z

``` r
library(correlation)

data <- mtcars
data[1,1] <- NA

cor(data[,1:3], use = "pairwise")
#>             mpg        cyl       disp
#> mpg   1.0000000 -0.8521139 -0.8496233
#> cyl  -0.8521139  1.0000000  0.9020329
#> disp -0.8496233  0.9020329  1.0000000
correlation(data[,1:3]) |> as.matrix()
#>             mpg        cyl       disp
#> mpg   1.0000000 -0.8521139 -0.8496233
#> cyl  -0.8521139  1.0000000  0.9020329
#> disp -0.8496233  0.9020329  1.0000000

cor(data[,1:3], use = "complete")
#>             mpg        cyl       disp
#> mpg   1.0000000 -0.8521139 -0.8496233
#> cyl  -0.8521139  1.0000000  0.9051234
#> disp -0.8496233  0.9051234  1.0000000
correlation(data[,1:3], use = "complete") |> as.matrix()
#>             mpg        cyl       disp
#> mpg   1.0000000 -0.8521139 -0.8496233
#> cyl  -0.8521139  1.0000000  0.9051234
#> disp -0.8496233  0.9051234  1.0000000
```

<sup>Created on 2025-04-02 with [reprex v2.1.1](https://reprex.tidyverse.org)</sup>



Diff:
diff --git a/DESCRIPTION b/DESCRIPTION
index f64e616d..18ddc272 100644
--- a/DESCRIPTION
+++ b/DESCRIPTION
@@ -1,7 +1,7 @@
 Type: Package
 Package: correlation
 Title: Methods for Correlation Analysis
-Version: 0.8.7
+Version: 0.8.7.1
 Authors@R:
     c(person(given = "Dominique",
              family = "Makowski",
diff --git a/NEWS.md b/NEWS.md
index caaa7bbe..1eca37f0 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -1,3 +1,7 @@
+# correlation 0.8.xxx
+
+- `correlation()` gains a `missing=` argument, similar to `stats::cor(use=)`, for controlling how missing data is handled.
+
 # correlation 0.8.7
 
 - The `format()` method for objects of class `easycormatrix` gets a `zap_small`
diff --git a/R/correlation.R b/R/correlation.R
index cd03d6db..f4cec05e 100644
--- a/R/correlation.R
+++ b/R/correlation.R
@@ -33,6 +33,12 @@
 #'   [insight::standardize_names()] on the output to get standardized column
 #'   names. This option can also be set globally by running
 #'   `options(easystats.standardize_names = TRUE)`.
+#' @param missing How should missing values be treated? If `"keep_pairwise"`
+#'   (default) then the correlation between each pair of variables is computed
+#'   using all complete pairs of observations on those variables. If
+#'   `"keep_complete"` then missing values are handled by case-wise deletion,
+#'   and correlations are computed using only observations with full data (based
+#'   on `data2`/`select`/`select2` when applicable).
 #' @inheritParams cor_test
 #'
 #' @details
@@ -247,6 +253,7 @@ correlation <- function(data,
                         select2 = NULL,
                         rename = NULL,
                         method = "pearson",
+                        missing = "keep_pairwise",
                         p_adjust = "holm",
                         ci = 0.95,
                         bayesian = FALSE,
@@ -318,6 +325,18 @@ correlation <- function(data,
     }
   }
 
+  missing <- insight::validate_argument(missing, options = c("keep_pairwise", "keep_complete"))
+  if (missing == "keep_complete") {
+    if (is.null(data2)) {
+      oo <- stats::complete.cases(data)
+      data <- data[which(oo), ]
+    } else {
+      oo <- stats::complete.cases(cbind(data, data2))
+      data <- data[which(oo), ]
+      data2 <- data2[which(oo), ]
+    }
+  }
+
   if (inherits(data, "grouped_df")) {
     rez <- .correlation_grouped_df(
       data,
@@ -378,7 +397,8 @@ correlation <- function(data,
       multilevel = multilevel,
       partial_bayesian = partial_bayesian,
       bayesian_prior = bayesian_prior,
-      include_factors = include_factors
+      include_factors = include_factors,
+      missing = missing
     )
   )
 
diff --git a/correlation.Rproj b/correlation.Rproj
index c1ab2141..793cf57e 100644
--- a/correlation.Rproj
+++ b/correlation.Rproj
@@ -1,4 +1,5 @@
 Version: 1.0
+ProjectId: 5ab51f05-a107-46c8-b998-a2432a78b2ad
 
 RestoreWorkspace: No
 SaveWorkspace: No
diff --git a/man/correlation.Rd b/man/correlation.Rd
index da2a6b2a..69dc1387 100644
--- a/man/correlation.Rd
+++ b/man/correlation.Rd
@@ -11,6 +11,7 @@ correlation(
   select2 = NULL,
   rename = NULL,
   method = "pearson",
+  missing = "keep_pairwise",
   p_adjust = "holm",
   ci = 0.95,
   bayesian = FALSE,
@@ -64,6 +65,13 @@ dichotomous factors involved, point-biserial if one dichotomous and one
 continuous and pearson otherwise). See below the \strong{details} section for a
 description of these indices.}
 
+\item{missing}{How should missing values be treated? If \code{"keep_pairwise"}
+(default) then the correlation between each pair of variables is computed
+using all complete pairs of observations on those variables. If
+\code{"keep_complete"} then missing values are handled by case-wise deletion,
+and correlations are computed using only observations with full data (based
+on \code{data2}/\code{select}/\code{select2} when applicable).}
+
 \item{p_adjust}{Correction method for frequentist correlations. Can be one of
 \code{"holm"} (default), \code{"hochberg"}, \code{"hommel"},
 \code{"bonferroni"}, \code{"BH"}, \code{"BY"}, \code{"fdr"},
diff --git a/tests/testthat/test-correlation.R b/tests/testthat/test-correlation.R
index 81b24bcb..01842f8c 100644
--- a/tests/testthat/test-correlation.R
+++ b/tests/testthat/test-correlation.R
@@ -211,3 +211,30 @@ test_that("correlation output with zap_small", {
   expect_snapshot(summary(r), variant = "windows")
   expect_snapshot(summary(r, zap_small = FALSE), variant = "windows")
 })
+
+
+test_that("missing values", {
+  data <- mtcars
+
+  data[1,1] <- NA
+
+  r_pairwise <- stats::cor(data[,1:5], use = "pairwise")
+  r_complete <- stats::cor(data[,1:5], use = "complete")
+
+  corr_pairwise <- correlation(data[,1:5])
+  corr_complete <- correlation(data[,1:5], missing = "keep_complete")
+
+  expect_equal(as.matrix(corr_pairwise), r_pairwise)
+  expect_equal(as.matrix(corr_complete), r_complete)
+
+
+
+  r_pairwise <- stats::cor(data[,1:2], data[,3:5], use = "pairwise")
+  r_complete <- stats::cor(data[,1:2], data[,3:5], use = "complete")
+
+  corr_pairwise <- correlation(data[,1:2], data[,3:5])
+  corr_complete <- correlation(data[,1:2], data[,3:5], missing = "keep_complete")
+
+  expect_equal(as.matrix(corr_pairwise), r_pairwise)
+  expect_equal(as.matrix(corr_complete), r_complete)
+})


Reviews:
user: login: DominiqueMakowski
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-02T12:19:58Z
user: login: strengejacke
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-02T13:33:22Z
user: login: DominiqueMakowski
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-06T07:11:25Z
user: login: mattansb
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-06T07:42:32Z
user: login: DominiqueMakowski
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-06T08:01:00Z
user: login: bwiernik
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T13:48:34Z
user: login: bwiernik
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T15:51:39Z
user: login: bwiernik
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-18T15:41:27Z

Review comments:
diff_hunk: @@ -247,6 +254,7 @@ correlation <- function(data,
                         select2 = NULL,
                         rename = NULL,
                         method = "pearson",
+                        use = c("pairwise.obs", "complete.obs"),
path: R/correlation.R
user: login: DominiqueMakowski
body: ```suggestion
                        missing = c("pairwise.obs", "complete.obs"),
```

Although I see the point of sticking with base-R argument names, `use` is a pretty vague one... Also, I'd move this argument to bethe last of the list as I think it's less "important"
created_at: 2025-04-02T12:19:58Z
author_association: MEMBER
diff_hunk: @@ -247,6 +254,7 @@ correlation <- function(data,
                         select2 = NULL,
                         rename = NULL,
                         method = "pearson",
+                        use = c("pairwise.obs", "complete.obs"),
path: R/correlation.R
user: login: strengejacke
body: And we now move to having just the default option in the function header, and the remaining options just in the docs.
created_at: 2025-04-02T13:32:37Z
author_association: MEMBER
diff_hunk: @@ -318,6 +326,18 @@ correlation <- function(data,
     }
   }
 
+  use <- match.arg(use, choices = c("pairwise.obs", "complete.obs"))
+  if (use == "complete.obs") {
path: R/correlation.R
user: login: strengejacke
body: Could use `insight::validate_argument()` instead of `match.arg()` (nicer error msg)
created_at: 2025-04-02T13:33:06Z
author_association: MEMBER
diff_hunk: @@ -318,6 +325,18 @@ correlation <- function(data,
     }
   }
 
+  missing <- insight::validate_argument(missing, options = c("keep.pairwise", "keep.complete"))
path: R/correlation.R
user: login: DominiqueMakowski
body: I'm going to be pendantic but we don't typically use dots in the easyverse 🙃  I think that you added the `keep.` in prevision for a future where we might have `impute.` or something which makes sense.

Maybe `keep_pairwise` and `keep_complete`?
created_at: 2025-04-06T07:11:25Z
author_association: MEMBER
diff_hunk: @@ -318,6 +325,18 @@ correlation <- function(data,
     }
   }
 
+  missing <- insight::validate_argument(missing, options = c("keep.pairwise", "keep.complete"))
path: R/correlation.R
user: login: mattansb
body: I mean, this is a string - so really it can even be `c("keep pairwise", "keep complete")`.
created_at: 2025-04-06T07:42:31Z
author_association: MEMBER
diff_hunk: @@ -318,6 +325,18 @@ correlation <- function(data,
     }
   }
 
+  missing <- insight::validate_argument(missing, options = c("keep.pairwise", "keep.complete"))
path: R/correlation.R
user: login: DominiqueMakowski
body: ![](https://media0.giphy.com/media/v1.Y2lkPTc5MGI3NjExOWl6OGNocmw3aHlrNTFybjcyb3FuZ3JnZWNlMGl5OXdidnV5cjJlciZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/F41xyZHAZTc3bJyCJr/giphy.gif)
created_at: 2025-04-06T08:01:00Z
author_association: MEMBER
diff_hunk: @@ -1,3 +1,7 @@
+# correlation 0.8.xxx
+
+- `correlation()` gains a `use=` argument, similar to `stats::cor(use=)`, for controlling how missing data is dealt with.
path: NEWS.md
user: login: bwiernik
body: ```suggestion
- `correlation()` gains a `missing=` argument, similar to `stats::cor(use=)`, for controlling how missing data is handled.
```

created_at: 2025-04-15T13:44:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -378,7 +397,8 @@ correlation <- function(data,
       multilevel = multilevel,
       partial_bayesian = partial_bayesian,
       bayesian_prior = bayesian_prior,
-      include_factors = include_factors
+      include_factors = include_factors,
+      use = use
path: R/correlation.R
user: login: bwiernik
body: ```suggestion
      missing = missing
```

created_at: 2025-04-15T13:45:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -318,6 +325,18 @@ correlation <- function(data,
     }
   }
 
+  missing <- insight::validate_argument(missing, options = c("keep_pairwise", "keep_complete"))
+  if (missing == "keep_complete") {
+    if (is.null(data2)) {
+      oo <- complete.cases(data)
+      data <- data[which(oo), ]
+    } else {
+      oo <- complete.cases(cbind(data, data2))
+      data <- data[which(oo), ]
+      data2 <- data2[which(oo), ]
path: R/correlation.R
user: login: bwiernik
body: ```suggestion
    if (is.null(data2)) {
      oo <- stats::complete.cases(data)
      data <- data[which(oo), ]
    } else {
      oo <- stats::complete.cases(cbind(data, data2))
      data <- data[which(oo), ]
      data2 <- data2[which(oo), ]
```

created_at: 2025-04-15T15:51:23Z
author_association: CONTRIBUTOR

PR comments:
user: login: bwiernik
created_at: 2025-04-02T18:10:24Z
body: I would say let's call the argument something like "handle_missing" -- also opens the door for future extension to FIML or imputation 
author_association: CONTRIBUTOR
user: login: DominiqueMakowski
created_at: 2025-04-02T18:11:53Z
body: `method_missing` ? In line with our many `method` args? 

author_association: MEMBER
user: login: mattansb
created_at: 2025-04-06T05:39:16Z
body: Okay, we now have `missing = "keep.pairwise" / "keep.complete"`. We can expand this argument at a later time to use fiml/mice/magic...
author_association: MEMBER
user: login: mattansb
created_at: 2025-04-08T16:36:57Z
body: Alright, I think that's it?
author_association: MEMBER
user: login: mattansb
created_at: 2025-04-15T13:40:29Z
body: @bwiernik can we merge?
author_association: MEMBER