2017-02-27 3 views
9

मान लीजिए मैं एक डेटा फ्रेम के रूप में निम्नानुसार है:कुशल कार्यान्वयन

> foo = data.frame(x = 1:9, id = c(1, 1, 2, 2, 2, 3, 3, 3, 3)) 
> foo 
    x id 
1 1 1 
2 2 1 
3 3 2 
4 4 2 
5 5 2 
6 6 3 
7 7 3 
8 8 3 
9 9 3 

मैं घंटा की बहुत ही कुशल कार्यान्वयन (क, ख) कि रकम की गणना करता है सब चाहते हैं (एक - XI) * (ख - xj) xi, xj के लिए एक ही आईडी वर्ग से संबंधित है। उदाहरण के लिए, मेरे वर्तमान कार्यान्वयन

h(a, b, foo){ 
    a.diff = a - foo$x 
    b.diff = b - foo$x 
    prod = a.diff%*%t(b.diff) 
    id.indicator = as.matrix(ifelse(dist(foo$id, diag = T, upper = T),0,1)) + diag(nrow(foo)) 
    return(sum(prod*id.indicator)) 
} 

उदाहरण के लिए है, के साथ (ए, बी) = (0, 1), यहाँ समारोह

> a.diff 
[1] -1 -2 -3 -4 -5 -6 -7 -8 -9 
> b.diff 
[1] 0 -1 -2 -3 -4 -5 -6 -7 -8 
> prod 
     [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] 
[1,] 0 1 2 3 4 5 6 7 8 
[2,] 0 2 4 6 8 10 12 14 16 
[3,] 0 3 6 9 12 15 18 21 24 
[4,] 0 4 8 12 16 20 24 28 32 
[5,] 0 5 10 15 20 25 30 35 40 
[6,] 0 6 12 18 24 30 36 42 48 
[7,] 0 7 14 21 28 35 42 49 56 
[8,] 0 8 16 24 32 40 48 56 64 
[9,] 0 9 18 27 36 45 54 63 72 
> id.indicator 
    1 2 3 4 5 6 7 8 9 
1 1 1 0 0 0 0 0 0 0 
2 1 1 0 0 0 0 0 0 0 
3 0 0 1 1 1 0 0 0 0 
4 0 0 1 1 1 0 0 0 0 
5 0 0 1 1 1 0 0 0 0 
6 0 0 0 0 0 1 1 1 1 
7 0 0 0 0 0 1 1 1 1 
8 0 0 0 0 0 1 1 1 1 
9 0 0 0 0 0 1 1 1 1 

हकीकत में में हर कदम से उत्पादन होता है, 1000 आईडी क्लस्टर तक हो सकते हैं, और प्रत्येक क्लस्टर कम से कम 40 होगा, इस विधि को id.indicator में स्पैस प्रविष्टियों और ऑफ-ब्लॉक-विकर्णों पर प्रोड में अतिरिक्त गणना के कारण अक्षम नहीं किया जाएगा जिसका उपयोग नहीं किया जाएगा ।

+2

सरल, और अभी भी बहुत तेज़: 'ज <- समारोह (ए, बी, foo) {राशि (tapply (foo $ x, foo $ आईडी, समारोह (एक्स) {राशि (tcrossprod (क - एक्स, बी - एक्स))}))} – alistaire

उत्तर

3

tapply आपको वेक्टर के समूहों में एक फ़ंक्शन लागू करने देता है, और परिणामस्वरूप मैट्रिक्स या वेक्टर में परिणाम को सरल बना देगा। tcrossprod का उपयोग करते हुए प्रत्येक समूह के लिए सभी संयोजनों गुणा करने के लिए, और कुछ को उपयुक्त रूप से बड़े डेटा पर इसका अच्छा प्रदर्शन:

# setup 
set.seed(47) 
foo = data.frame(x = 1:9, id = c(1, 1, 2, 2, 2, 3, 3, 3, 3)) 
foo2 <- data.frame(id = sample(1000, 40000, TRUE), x = rnorm(40000)) 

h_OP <- function(a, b, foo){ 
    a.diff = a - foo$x 
    b.diff = b - foo$x 
    prod = a.diff %*% t(b.diff) 
    id.indicator = as.matrix(ifelse(dist(foo$id, diag = T, upper = T),0,1)) + diag(nrow(foo)) 
    return(sum(prod * id.indicator)) 
} 

h3_AEBilgrau <- function(a, b, foo) { 
    a.diff <- a - foo$x 
    b.diff <- b - foo$x 
    ids <- unique(foo$id) 
    res <- 0 
    for (i in seq_along(ids)) { 
    indx <- which(foo$id == ids[i]) 
    res <- res + sum(tcrossprod(a.diff[indx], b.diff[indx])) 
    } 
    return(res) 
} 

h_d.b <- function(a, b, foo){ 
    sum(sapply(split(foo, foo$id), function(d) sum(outer(a-d$x, b-d$x)))) 
} 

h_alistaire <- function(a, b, foo){ 
    sum(tapply(foo$x, foo$id, function(x){sum(tcrossprod(a - x, b - x))})) 
} 

सभी एक ही बात लौटने के लिए, और है कि छोटे डेटा पर अलग नहीं हैं:

h_OP(0, 1, foo) 
#> [1] 891 
h3_AEBilgrau(0, 1, foo) 
#> [1] 891 
h_d.b(0, 1, foo) 
#> [1] 891 
h_alistaire(0, 1, foo) 
#> [1] 891 

# small data test 
microbenchmark::microbenchmark(
    h_OP(0, 1, foo), 
    h3_AEBilgrau(0, 1, foo), 
    h_d.b(0, 1, foo), 
    h_alistaire(0, 1, foo) 
) 
#> Unit: microseconds 
#>      expr  min  lq  mean median  uq  max neval cld 
#>   h_OP(0, 1, foo) 143.749 157.8895 189.5092 189.7235 214.3115 262.258 100 b 
#> h3_AEBilgrau(0, 1, foo) 80.970 93.8195 112.0045 106.9285 125.9835 225.855 100 a 
#>   h_d.b(0, 1, foo) 355.084 381.0385 467.3812 437.5135 516.8630 2056.972 100 c 
#> h_alistaire(0, 1, foo) 148.735 165.1360 194.7361 189.9140 216.7810 287.990 100 b 

बड़े डेटा पर, अंतर अधिक स्पष्ट हो जाता है, हालांकि। मूल अपने लैपटॉप के क्रैश होने की धमकी दी है, लेकिन यहां सबसे तेजी से दो के लिए मानक हैं:

# on 1k groups, 40k rows 
microbenchmark::microbenchmark(
    h3_AEBilgrau(0, 1, foo2), 
    h_alistaire(0, 1, foo2) 
) 
#> Unit: milliseconds 
#>      expr  min  lq  mean median  uq  max neval cld 
#> h3_AEBilgrau(0, 1, foo2) 336.98199 403.04104 412.06778 410.52391 423.33008 443.8286 100 b 
#> h_alistaire(0, 1, foo2) 14.00472 16.25852 18.07865 17.22296 18.09425 96.9157 100 a 

एक और संभावना समूह द्वारा संक्षेप में प्रस्तुत करने के लिए एक data.frame उपयोग करने के लिए है, तो उचित कॉलम योग।बेस आर में आप aggregate के साथ ऐसा करेंगे, लेकिन अधिक जटिल समेकन के साथ इस तरह के दृष्टिकोण को सरल बनाने के लिए dplyr और data.table लोकप्रिय हैं।

aggregatetapply से धीमा है। dplyr aggregate से तेज़ है, लेकिन अभी भी धीमा है। डेटा.table, जो गति के लिए डिज़ाइन किया गया है, लगभग tapply जितना तेज़ है।

library(dplyr) 
library(data.table) 

h_aggregate <- function(a, b, foo){sum(aggregate(x ~ id, foo, function(x){sum(tcrossprod(a - x, b - x))})$x)} 
tidy_h <- function(a, b, foo){foo %>% group_by(id) %>% summarise(x = sum(tcrossprod(a - x, b - x))) %>% select(x) %>% sum()} 
h_dt <- function(a, b, foo){setDT(foo)[, .(x = sum(tcrossprod(a - x, b - x))), by = id][, sum(x)]} 

microbenchmark::microbenchmark(
    h_alistaire(1, 0, foo2), 
    h_aggregate(1, 0, foo2), 
    tidy_h(1, 0, foo2), 
    h_dt(1, 0, foo2) 
) 
#> Unit: milliseconds 
#>      expr  min  lq  mean median  uq  max neval cld 
#> h_alistaire(1, 0, foo2) 13.30518 15.52003 18.64940 16.48818 18.13686 62.35675 100 a 
#> h_aggregate(1, 0, foo2) 93.08401 96.61465 107.14391 99.16724 107.51852 143.16473 100 c 
#>  tidy_h(1, 0, foo2) 39.47244 42.22901 45.05550 43.94508 45.90303 90.91765 100 b 
#>  h_dt(1, 0, foo2) 13.31817 15.09805 17.27085 16.46967 17.51346 56.34200 100 a 
3
sum(sapply(split(foo, foo$id), function(d) sum(outer(a-d$x, b-d$x)))) 
#[1] 891 

#TESTING 
foo = data.frame(x = sample(1:9,10000,replace = TRUE), 
         id = sample(1:3, 10000, replace = TRUE)) 
system.time(sum(sapply(split(foo, foo$id), function(d) sum(outer(a-d$x, b-d$x))))) 
# user system elapsed 
# 0.15 0.01 0.17 
6

मैंने थोड़ा दौर खेला। सबसे पहले, अपने कार्यान्वयन:

foo = data.frame(x = 1:9, id = c(1, 1, 2, 2, 2, 3, 3, 3, 3)) 

h <- function(a, b, foo){ 
    a.diff = a - foo$x 
    b.diff = b - foo$x 
    prod = a.diff%*%t(b.diff) 
    id.indicator = as.matrix(ifelse(dist(foo$id, diag = T, upper = T),0,1)) + 
    diag(nrow(foo)) 
    return(sum(prod*id.indicator)) 
} 

h(a = 1, b = 0, foo = foo) 
#[1] 891 

इसके बाद, मैं सूचकांक मैट्रिक्स के लिए एक संस्करण (Matrix पैकेज के माध्यम से) एक उचित विरल मैट्रिक्स कार्यान्वयन का उपयोग और कार्य करने की कोशिश की। मैं tcrossprod का भी उपयोग करता हूं जो मुझे अक्सर a %*% t(b) से थोड़ा तेज़ लगता है।

library("Matrix") 

h2 <- function(a, b, foo) { 
    a.diff <- a - foo$x 
    b.diff <- b - foo$x 
    prod <- tcrossprod(a.diff, b.diff) # the same as a.diff%*%t(b.diff) 
    id.indicator <- do.call(bdiag, lapply(table(foo$id), function(n) matrix(1,n,n))) 
    return(sum(prod*id.indicator)) 
} 

h2(a = 1, b = 0, foo = foo) 
#[1] 891 

नोट इस समारोह foo$id पर निर्भर करता है कि हल कर जा रहा है।

आखिरकार, मैंने एन मैट्रिक्स द्वारा पूर्ण एन बनाने से बचने की कोशिश की।

h3 <- function(a, b, foo) { 
    a.diff <- a - foo$x 
    b.diff <- b - foo$x 
    ids <- unique(foo$id) 
    res <- 0 
    for (i in seq_along(ids)) { 
    indx <- which(foo$id == ids[i]) 
    res <- res + sum(tcrossprod(a.diff[indx], b.diff[indx])) 
    } 
    return(res) 
} 

h3(a = 1, b = 0, foo = foo) 
#[1] 891 

अपने उदाहरण पर बेंचमार्किंग:

library("microbenchmark") 
microbenchmark(h(a = 1, b = 0, foo = foo), 
       h2(a = 1, b = 0, foo = foo), 
       h3(a = 1, b = 0, foo = foo)) 
# Unit: microseconds 
#      expr  min  lq  mean median  uq  max neval 
# h(a = 1, b = 0, foo = foo) 248.569 261.9530 493.2326 279.3530 298.2825 21267.890 100 
# h2(a = 1, b = 0, foo = foo) 4793.546 4893.3550 5244.7925 5051.2915 5386.2855 8375.607 100 
# h3(a = 1, b = 0, foo = foo) 213.386 227.1535 243.1576 234.6105 248.3775 334.612 100 

अब, इस उदाहरण में, h3 सबसे तेज है और h2 वास्तव में धीमी है। लेकिन मुझे लगता है कि बड़े उदाहरणों के लिए दोनों तेजी से होंगे। शायद, h3 हालांकि बड़े उदाहरणों के लिए अभी भी जीतेंगे। जबकि अधिक अनुकूलन के बहुत सारे कमरे हैं, h3 तेज और अधिक मेमोरी कुशल होना चाहिए। इसलिए, मुझे लगता है कि आपको h3 के एक संस्करण के लिए जाना चाहिए जो अनावश्यक रूप से बड़ी मैट्रिक्स नहीं बनाता है।

+0

'टैब' कहां से आ रहा है? – Raad

+0

@NBATrends ओह, यह 'ids' होना चाहिए था; एक प्रोटोटाइप से एक बचे हुए। –

संबंधित मुद्दे