CA: warn when duplicate rows are dropped from multi-VC comparisons

The pivots in add_multi_vc_comparisons() use values_fn = dplyr::first, so
repeated node/collocate/label rows -- as produced when the same collocate is
found at several context positions -- were silently reduced to their first
occurrence. Warn instead, naming mergeDuplicateCollocates() as the way to
combine those rows before comparing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Change-Id: I832ea9a2bc0d28256bba5a37e8b94721809f3a68
diff --git a/R/collocationAnalysis.R b/R/collocationAnalysis.R
index 4f827eb..d185a0c 100644
--- a/R/collocationAnalysis.R
+++ b/R/collocationAnalysis.R
@@ -661,6 +661,25 @@
     score_cols
   )
 
+  # The pivots below keep only the first row per node/collocate/label. Duplicates do occur
+  # legitimately (e.g. the same collocate found at several context positions), but silently
+  # discarding all but one of them would misrepresent the comparison, so say so.
+  comparison_keys <- paste(result$node, result$collocate, result$label, sep = "\r")
+  duplicate_keys <- unique(comparison_keys[duplicated(comparison_keys)])
+  if (length(duplicate_keys) > 0) {
+    warning(
+      sprintf(
+        paste0(
+          "%d node/collocate/label combination(s) occur more than once; only the first row ",
+          "of each is used for the multi-VC comparison columns. Consider ",
+          "mergeDuplicateCollocates() to combine context positions before comparing."
+        ),
+        length(duplicate_keys)
+      ),
+      call. = FALSE
+    )
+  }
+
   comparison <- result |>
     dplyr::select(node, collocate, label, dplyr::all_of(score_cols)) |>
     tidyr::pivot_wider(
diff --git a/tests/testthat/test-collocations.R b/tests/testthat/test-collocations.R
index a8b952f..61602c0 100644
--- a/tests/testthat/test-collocations.R
+++ b/tests/testthat/test-collocations.R
@@ -372,6 +372,61 @@
   expect_lte(unique(c1$loser_logDice_value), min(sample_result$logDice))
 })
 
+test_that("add_multi_vc_comparisons warns about dropped duplicate rows", {
+  sample_result <- tibble::tibble(
+    node = rep("n", 4),
+    collocate = c("c", "c", "c", "c"),
+    vc = c("vc1", "vc1", "vc2", "vc2"),
+    # the same collocate twice per label, as after collecting several context positions
+    label = c("A", "A", "B", "B"),
+    N = rep(100, 4),
+    O = c(10, 11, 20, 21),
+    O1 = rep(50, 4),
+    O2 = rep(30, 4),
+    E = rep(5, 4),
+    w = rep(2, 4),
+    leftContextSize = rep(1, 4),
+    rightContextSize = rep(1, 4),
+    frequency = c(10, 11, 20, 21),
+    logDice = c(5, 6, 7, 8),
+    pmi = c(2, 2, 3, 3)
+  )
+
+  expect_warning(
+    enriched <- RKorAPClient:::add_multi_vc_comparisons(sample_result),
+    "occur more than once"
+  )
+  expect_warning(
+    RKorAPClient:::add_multi_vc_comparisons(sample_result),
+    "mergeDuplicateCollocates"
+  )
+  # the first row of each combination is what ends up in the comparison
+  expect_true(all(enriched$logDice_A == 5))
+  expect_true(all(enriched$logDice_B == 7))
+})
+
+test_that("add_multi_vc_comparisons is silent for unique node/collocate/label rows", {
+  sample_result <- tibble::tibble(
+    node = rep("n", 4),
+    collocate = c("c1", "c1", "c2", "c2"),
+    vc = rep(c("vc1", "vc2"), 2),
+    label = rep(c("A", "B"), 2),
+    N = rep(100, 4),
+    O = c(10, 20, 30, 40),
+    O1 = rep(50, 4),
+    O2 = rep(30, 4),
+    E = rep(5, 4),
+    w = rep(2, 4),
+    leftContextSize = rep(1, 4),
+    rightContextSize = rep(1, 4),
+    frequency = c(10, 20, 30, 40),
+    logDice = c(5, 7, 6, 4),
+    pmi = c(2, 3, 4, 1)
+  )
+
+  expect_no_warning(RKorAPClient:::add_multi_vc_comparisons(sample_result))
+})
+
 test_that("add_multi_vc_comparisons reports imputation when verbose", {
   sample_result <- tibble::tibble(
     node = c("n", "n", "n"),