Skip to content

Commit a0eae8e

Browse files
committed
use O(n log n) algorithm for concordance computation
1 parent ec8a0a0 commit a0eae8e

12 files changed

Lines changed: 3708 additions & 78 deletions

File tree

.Rbuildignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,5 @@
44
^\.Rproj\.user$
55
^tests/upgrades$
66
^examples$
7+
^\.renvignore$
8+
^benchmarks$

DESCRIPTION

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ Imports:
2929
mdscore,
3030
ppcor,
3131
purrr,
32-
Rcpp,
3332
statmod,
3433
VGAM
3534
Remotes:
@@ -39,5 +38,3 @@ Remotes:
3938
jasp-stats/jaspDescriptives,
4039
jasp-stats/jaspGraphs,
4140
jasp-stats/jaspTTests
42-
LinkingTo:
43-
Rcpp

NAMESPACE

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,3 @@ export(.getCorPlotItems)
2121
export(.bfPlotTitles)
2222
export(.drawPosteriorPlotCorBayes)
2323
export(.drawBfRobustnessPlotCorBayes)
24-
useDynLib(jaspRegression)
25-
importFrom(Rcpp, sourceCpp)

R/RcppExports.R

Lines changed: 0 additions & 7 deletions
This file was deleted.

R/concordance.R

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
concordance <- function(x, y) {
2+
3+
n <- length(x)
4+
stopifnot(length(y) == n)
5+
if (n == 0L) return(integer(0L))
6+
if (n == 1L) return(0L)
7+
if (n <= 130) concordance_naive(x, y)
8+
concordance_fenwick(x, y)
9+
10+
}
11+
12+
concordance_naive <- function(x, y) {
13+
dx <- sign(outer(x, x, `-`))
14+
dy <- sign(outer(y, y, `-`))
15+
.rowSums(dx * dy, nrow(dx), ncol(dx))
16+
}
17+
18+
concordance_fenwick <- function(x, y) {
19+
20+
n <- length(x)
21+
22+
# 1) order by x (stable), keep x-ordered copies
23+
ord <- order(x, y, method = "radix")
24+
xo <- x[ord]
25+
yo <- y[ord]
26+
27+
# 2) compute group runs for equal x (use rle to avoid building big lists)
28+
if (anyDuplicated(xo)) {
29+
rle_x <- rle(xo)
30+
group_lengths <- rle_x$lengths
31+
group_starts <- cumsum(c(1L, head(group_lengths, -1L)))
32+
group_ends <- cumsum(group_lengths)
33+
G <- length(group_lengths) # number of groups
34+
} else {
35+
# fast path
36+
rle_x <- rle(xo)
37+
group_lengths <- rep.int(1L, n)
38+
group_starts <- 1:n
39+
group_ends <- 1:n
40+
G <- n # number of groups
41+
}
42+
43+
# 3) compress y to ranks 1..m (strict ordering of unique y's)
44+
uniq_y <- sort(unique(yo))
45+
ry <- match(yo, uniq_y) # integer vector 1..m
46+
m <- length(uniq_y)
47+
48+
# allocate bit and result vectors (integers)
49+
bit <- integer(m) # 1-indexed BIT (positions 1..m)
50+
less_right <- integer(n)
51+
greater_right <- integer(n)
52+
# originally there were separate vectors, but we can reuse some memory
53+
# less_left <- integer(n)
54+
# greater_left <- integer(n)
55+
56+
# precompute the results of bitwAnd so we can just do lookup
57+
# lowbits <- (1:m) - ((1:m) & ((1:m) - 1L))
58+
# lowbits <- (1:m) - bitwAnd((1:m), ((1:m) - 1L))
59+
#lowbits <- vapply(1:m, \(i) bitwAnd(i, -i), 0L)# -> lowbits
60+
lowbits <- bitwAnd(1:m, -1:-m)
61+
62+
# Helper: inline bit_sum and bit_add are implemented as loops below
63+
# -------- Right sweep (groups processed from right to left) ----------
64+
total_seen <- 0L
65+
for (g in G:1L) {
66+
i1 <- group_starts[g]
67+
i2 <- group_ends[g]
68+
# query each index in this group (do NOT add them yet)
69+
for (i in i1:i2) {
70+
r <- ry[i]
71+
# less_right: bit_sum(r-1)
72+
j <- r - 1L
73+
s_less <- 0L
74+
while (j > 0L) {
75+
s_less <- s_less + bit[j]
76+
j <- j - lowbits[j] #bitwAnd(j, -j)
77+
}
78+
# leq_count: bit_sum(r)
79+
j <- r
80+
s_leq <- 0L
81+
while (j > 0L) {
82+
s_leq <- s_leq + bit[j]
83+
j <- j - lowbits[j] #bitwAnd(j, -j)
84+
}
85+
less_right[i] <- s_less
86+
greater_right[i] <- total_seen - s_leq
87+
}
88+
# now add this entire group into the BIT (so earlier groups see them)
89+
for (i in i1:i2) {
90+
r <- ry[i]
91+
j <- r
92+
while (j <= m) {
93+
bit[j] <- bit[j] + 1L
94+
j <- j + lowbits[j] #bitwAnd(j, -j)
95+
}
96+
total_seen <- total_seen + 1L
97+
}
98+
}
99+
out_xorder <- (greater_right - less_right) # partial result
100+
# reset for left sweep, these are now less_left and greater_left
101+
less_right[] <- 0L
102+
greater_right[] <- 0L
103+
104+
105+
# -------- Left sweep (groups processed from left to right) ----------
106+
bit[] <- 0L # reset BIT in-place (no new allocation)
107+
total_seen <- 0L
108+
for (g in 1L:G) {
109+
i1 <- group_starts[g]
110+
i2 <- group_ends[g]
111+
# query each index in this group (do NOT add them yet)
112+
for (i in i1:i2) {
113+
r <- ry[i]
114+
# less_left: bit_sum(r-1)
115+
j <- r - 1L
116+
s_less <- 0L
117+
while (j > 0L) {
118+
s_less <- s_less + bit[j]
119+
j <- j - lowbits[j] #bitwAnd(j, -j)
120+
}
121+
# leq_count: bit_sum(r)
122+
j <- r
123+
s_leq <- 0L
124+
while (j > 0L) {
125+
s_leq <- s_leq + bit[j]
126+
j <- j - lowbits[j] #bitwAnd(j, -j)
127+
}
128+
# less_left[i] <- s_less
129+
# greater_left[i] <- total_seen - s_leq
130+
less_right[i] <- s_less
131+
greater_right[i] <- total_seen - s_leq
132+
}
133+
# now add this group's ranks to BIT
134+
for (i in i1:i2) {
135+
r <- ry[i]
136+
j <- r
137+
while (j <= m) {
138+
bit[j] <- bit[j] + 1L
139+
j <- j + lowbits[j] #bitwAnd(j, -j)
140+
}
141+
total_seen <- total_seen + 1L
142+
}
143+
}
144+
145+
out_xorder <- out_xorder + (less_right - greater_right)
146+
# reuse some memory
147+
less_right[] <- 0L
148+
less_right[ord] <- out_xorder
149+
return(less_right)
150+
151+
# combine contributions (in x-sorted order), then restore original order
152+
# out_xorder <- (greater_right - less_right) + (less_left - greater_left)
153+
# out <- integer(n)
154+
# out[ord] <- out_xorder
155+
# out
156+
}

R/correlation.R

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1617,8 +1617,9 @@ CorrelationInternal <- function(jaspResults, dataset, options){
16171617
alternative <- match.arg(alternative)
16181618

16191619
if (method == "kendall") {
1620-
concordanceSumsVector <- concordanceVector_cpp(x, y)
1621-
sigmaHatSq <- 2 * (n-2) * var(concordanceSumsVector) / (n*(n-1))
1620+
1621+
concordanceSumsVector <- concordance(x, y)
1622+
sigmaHatSq <- 2 * (n-2) * var(concordanceSumsVector) / (n*(n - 1))
16221623
sigmaHatSq <- sigmaHatSq + 1 - (obsCor)^2
16231624
sigmaHatSq <- sigmaHatSq * 2 / (n*(n-1))
16241625

benchmarks/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
concordance_cache
2+
concordance_files
3+
concordance.rmarkdown

benchmarks/concordance.html

Lines changed: 3126 additions & 0 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)