जवाबों:
apply
आर में कार्य अन्य पाशन कार्यों (उदाहरण के लिए अधिक बेहतर प्रदर्शन प्रदान नहीं करते हैं for
)। इसका एक अपवाद है lapply
जो थोड़ा तेज़ हो सकता है क्योंकि यह R की तुलना में C कोड में अधिक काम करता है ( इस उदाहरण के लिए इस प्रश्न को देखें )।
लेकिन सामान्य तौर पर, नियम यह है कि आपको स्पष्टता के लिए एक लागू फ़ंक्शन का उपयोग करना चाहिए, प्रदर्शन के लिए नहीं ।
मैं यह है कि बढ़ेगी लागू कार्यों कोई साइड इफेक्ट है, जो एक महत्वपूर्ण अंतर है जब यह आर इस के साथ कार्यात्मक प्रोग्रामिंग के लिए आता है का उपयोग करके अधिरोहित जा सकता है assign
या <<-
है, लेकिन यह बहुत ही खतरनाक हो सकता है। दुष्प्रभाव भी समझने के लिए एक कार्यक्रम को कठिन बनाते हैं क्योंकि एक चर की स्थिति इतिहास पर निर्भर करती है।
संपादित करें:
सिर्फ एक तुच्छ उदाहरण के साथ इस पर जोर देने के लिए जो पुनरावर्तक रूप से फाइबोनैचि अनुक्रम की गणना करता है; यह एक सटीक उपाय प्राप्त करने के लिए कई बार चलाया जा सकता है, लेकिन मुद्दा यह है कि तरीकों में से कोई भी काफी अलग प्रदर्शन नहीं करता है:
> fibo <- function(n) {
+ if ( n < 2 ) n
+ else fibo(n-1) + fibo(n-2)
+ }
> system.time(for(i in 0:26) fibo(i))
user system elapsed
7.48 0.00 7.52
> system.time(sapply(0:26, fibo))
user system elapsed
7.50 0.00 7.54
> system.time(lapply(0:26, fibo))
user system elapsed
7.48 0.04 7.54
> library(plyr)
> system.time(ldply(0:26, fibo))
user system elapsed
7.52 0.00 7.58
2 संपादित करें:
R (उदाहरण के लिए rpvm, rmpi, स्नो) के लिए समानांतर पैकेज के उपयोग के बारे में, ये आम तौर पर apply
पारिवारिक कार्य प्रदान करते हैं (यहां तक कि foreach
पैकेज अनिवार्य रूप से समकक्ष है, नाम के बावजूद)। यहाँ sapply
समारोह का एक सरल उदाहरण है snow
:
library(snow)
cl <- makeSOCKcluster(c("localhost","localhost"))
parSapply(cl, 1:20, get("+"), 3)
यह उदाहरण सॉकेट क्लस्टर का उपयोग करता है, जिसके लिए कोई अतिरिक्त सॉफ़्टवेयर स्थापित करने की आवश्यकता नहीं है; अन्यथा आपको पीवीएम या एमपीआई ( टियरनी के क्लस्टरिंग पृष्ठ देखें ) जैसे कुछ की आवश्यकता होगी । snow
निम्नलिखित लागू कार्य हैं:
parLapply(cl, x, fun, ...)
parSapply(cl, X, FUN, ..., simplify = TRUE, USE.NAMES = TRUE)
parApply(cl, X, MARGIN, FUN, ...)
parRapply(cl, x, fun, ...)
parCapply(cl, x, fun, ...)
यह समझ में आता है कि apply
कार्यों का उपयोग समानांतर निष्पादन के लिए किया जाना चाहिए क्योंकि उनके कोई दुष्प्रभाव नहीं हैं । जब आप for
लूप के भीतर एक वैरिएबल वैल्यू बदलते हैं , तो यह ग्लोबली सेट होता है। दूसरी ओर, सभी apply
फ़ंक्शन सुरक्षित रूप से समानांतर में उपयोग किए जा सकते हैं क्योंकि फ़ंक्शन कॉल में स्थानीय परिवर्तन होते हैं (जब तक आप उपयोग करने की कोशिश नहीं करते हैं assign
या <<-
, जिस स्थिति में आप साइड इफेक्ट्स पेश कर सकते हैं)। कहने की जरूरत नहीं है, स्थानीय बनाम वैश्विक चर के बारे में सावधान रहना महत्वपूर्ण है, खासकर जब समानांतर निष्पादन से निपटना।
संपादित करें:
यहाँ एक तुच्छ उदाहरण के बीच अंतर दिखाने के लिए for
और *apply
अब तक साइड इफेक्ट्स का संबंध है:
> df <- 1:10
> # *apply example
> lapply(2:3, function(i) df <- df * i)
> df
[1] 1 2 3 4 5 6 7 8 9 10
> # for loop example
> for(i in 2:3) df <- df * i
> df
[1] 6 12 18 24 30 36 42 48 54 60
ध्यान दें कि df
मूल वातावरण में कैसे बदल जाता है for
लेकिन नहीं *apply
।
snowfall
पैकेज को देखें और उनके विगनेट में उदाहरणों को आज़माएँ। पैकेज के snowfall
ऊपर बनाता है snow
और समानांतर के विवरण को अमूर्त करता है और आगे भी समानांतर apply
कार्यों को निष्पादित करने के लिए इसे मृत सरल बनाता है।
foreach
जब से उपलब्ध हुआ है और लगता है कि एसओ के बारे में बहुत पूछताछ की जा रही है।
lapply
तुलना में "थोड़ा तेज" है for
। हालांकि, वहाँ, मैं ऐसा कुछ भी सुझाव नहीं देख रहा हूँ। आप केवल उसी lapply
का उल्लेख करते हैं sapply
, जो अन्य कारणों से अच्छी तरह से ज्ञात तथ्य है ( sapply
उत्पादन को सरल बनाने की कोशिश करता है और इसलिए बहुत सारे डेटा साइज की जाँच और संभावित रूपांतरण करना पड़ता है)। से संबंधित कुछ भी नहीं for
। क्या मैं कुछ भूल रहा हूँ?
कभी-कभी स्पीडअप पर्याप्त हो सकता है, जैसे कि जब आपको एक से अधिक कारकों के समूहन के आधार पर औसत प्राप्त करने के लिए लूप के लिए घोंसला बनाना पड़ता है। यहां आपके पास दो दृष्टिकोण हैं जो आपको सटीक परिणाम देते हैं:
set.seed(1) #for reproducability of the results
# The data
X <- rnorm(100000)
Y <- as.factor(sample(letters[1:5],100000,replace=T))
Z <- as.factor(sample(letters[1:10],100000,replace=T))
# the function forloop that averages X over every combination of Y and Z
forloop <- function(x,y,z){
# These ones are for optimization, so the functions
#levels() and length() don't have to be called more than once.
ylev <- levels(y)
zlev <- levels(z)
n <- length(ylev)
p <- length(zlev)
out <- matrix(NA,ncol=p,nrow=n)
for(i in 1:n){
for(j in 1:p){
out[i,j] <- (mean(x[y==ylev[i] & z==zlev[j]]))
}
}
rownames(out) <- ylev
colnames(out) <- zlev
return(out)
}
# Used on the generated data
forloop(X,Y,Z)
# The same using tapply
tapply(X,list(Y,Z),mean)
दोनों बिल्कुल समान परिणाम देते हैं, औसत और नामित पंक्तियों और स्तंभों के साथ 5 x 10 मैट्रिक्स है। परंतु :
> system.time(forloop(X,Y,Z))
user system elapsed
0.94 0.02 0.95
> system.time(tapply(X,list(Y,Z),mean))
user system elapsed
0.06 0.00 0.06
तुम वहाँ जाओ। मैं क्या जीत गया? ;-)
*apply
तेज होता है। लेकिन मुझे लगता है कि अधिक महत्वपूर्ण बिंदु दुष्प्रभाव है (एक उदाहरण के साथ मेरे जवाब को अपडेट किया गया)।
data.table
और भी तेज है और मुझे लगता है कि "आसान" है। library(data.table)
dt<-data.table(X,Y,Z,key=c("Y,Z"))
system.time(dt[,list(X_mean=mean(X)),by=c("Y,Z")])
tapply
एक विशेष कार्य के लिए एक विशेष समारोह, है कि क्यों यह तेजी से पाश के लिए एक से है। यह वह नहीं कर सकता जो लूप के लिए कर सकता है (जबकि नियमित apply
कर सकता है)। आप संतरे के साथ सेब की तुलना कर रहे हैं।
... और जैसा कि मैंने अभी और कहीं लिखा है, vapply आपका दोस्त है! ... यह नीलम जैसा है, लेकिन आप वापसी मूल्य प्रकार भी निर्दिष्ट करते हैं जो इसे बहुत तेज बनाता है।
foo <- function(x) x+1
y <- numeric(1e6)
system.time({z <- numeric(1e6); for(i in y) z[i] <- foo(i)})
# user system elapsed
# 3.54 0.00 3.53
system.time(z <- lapply(y, foo))
# user system elapsed
# 2.89 0.00 2.91
system.time(z <- vapply(y, foo, numeric(1)))
# user system elapsed
# 1.35 0.00 1.36
1 जनवरी, 2020 अपडेट:
system.time({z1 <- numeric(1e6); for(i in seq_along(y)) z1[i] <- foo(y[i])})
# user system elapsed
# 0.52 0.00 0.53
system.time(z <- lapply(y, foo))
# user system elapsed
# 0.72 0.00 0.72
system.time(z3 <- vapply(y, foo, numeric(1)))
# user system elapsed
# 0.7 0.0 0.7
identical(z1, z3)
# [1] TRUE
for
मेरे विंडोज 10, 2-कोर कंप्यूटर पर लूप तेज हैं। मैंने 5e6
तत्वों के साथ ऐसा किया था - एक लूप 2.9 सेकंड बनाम 3.1 सेकंड के लिए था vapply
।
मैंने कहीं और लिखा है कि शेन जैसा उदाहरण वास्तव में विभिन्न प्रकार के लूपिंग सिंटैक्स के बीच प्रदर्शन में अंतर पर जोर नहीं देता है क्योंकि फ़ंक्शन के भीतर समय व्यतीत होने के बजाय वास्तव में लूप पर जोर दिया जाता है। इसके अलावा, कोड गलत तरीके से लूप के लिए तुलना करता है जिसमें कोई भी मेमोरी नहीं है जो कि पारिवारिक फ़ंक्शन को लागू करता है जो मान लौटाता है। यहाँ थोड़ा अलग उदाहरण है जो इस बिंदु पर जोर देता है।
foo <- function(x) {
x <- x+1
}
y <- numeric(1e6)
system.time({z <- numeric(1e6); for(i in y) z[i] <- foo(i)})
# user system elapsed
# 4.967 0.049 7.293
system.time(z <- sapply(y, foo))
# user system elapsed
# 5.256 0.134 7.965
system.time(z <- lapply(y, foo))
# user system elapsed
# 2.179 0.126 3.301
यदि आप परिणाम को बचाने की योजना बनाते हैं तो परिवार के कार्यों को लागू करें सिंटैक्टिक चीनी की तुलना में बहुत अधिक हो सकता है ।
(z का सिंपल अनलिस्ट केवल 0.2 s है, इसलिए lapply बहुत तेज है। लूप के लिए z को इनिशियलाइज़ करना काफी तेज़ है क्योंकि मैं अंतिम 5 में से 6 रन का औसत दे रहा हूँ ताकि सिस्टम के बाहर चला जाए। शायद ही चीजों को प्रभावित करें)
एक और बात ध्यान देने योग्य है कि उनके प्रदर्शन, स्पष्टता या साइड इफेक्ट्स की कमी से स्वतंत्र पारिवारिक कार्यों को लागू करने का एक और कारण है। एक for
लूप आमतौर पर लूप के भीतर जितना संभव हो सके डालने को बढ़ावा देता है। ऐसा इसलिए है क्योंकि प्रत्येक लूप को जानकारी संग्रहीत करने के लिए चर की स्थापना की आवश्यकता होती है (अन्य संभावित कार्यों के बीच)। लागू करें बयानों को दूसरे तरीके से पक्षपाती किया जाता है। अक्सर कई बार आप अपने डेटा पर कई ऑपरेशन करना चाहते हैं, जिनमें से कई वेक्टर हो सकते हैं लेकिन कुछ नहीं हो सकते हैं। आर में, अन्य भाषाओं के विपरीत, उन ऑपरेशनों को अलग करना और उन लोगों को चलाना सबसे अच्छा है जो एक आवेदन विवरण (या फ़ंक्शन के वेक्टरकृत संस्करण) में सदिश नहीं किए गए हैं और जो सच्चे वेक्टर ऑपरेशन के रूप में वेक्टरकृत हैं। इससे अक्सर प्रदर्शन में तेजी आती है।
जोरिस मेय्स उदाहरण लेते हुए, जहां वह एक पारंपरिक आर लूप के लिए एक आसान आर फ़ंक्शन के साथ बदलता है हम इसका उपयोग विशेष फ़ंक्शन के बिना एक समान स्पीडअप के लिए अधिक आर अनुकूल तरीके से कोड लिखने की दक्षता दिखाने के लिए कर सकते हैं।
set.seed(1) #for reproducability of the results
# The data - copied from Joris Meys answer
X <- rnorm(100000)
Y <- as.factor(sample(letters[1:5],100000,replace=T))
Z <- as.factor(sample(letters[1:10],100000,replace=T))
# an R way to generate tapply functionality that is fast and
# shows more general principles about fast R coding
YZ <- interaction(Y, Z)
XS <- split(X, YZ)
m <- vapply(XS, mean, numeric(1))
m <- matrix(m, nrow = length(levels(Y)))
rownames(m) <- levels(Y)
colnames(m) <- levels(Z)
m
यह हवाओं के for
लूप की तुलना में बहुत तेज है और अनुकूलित tapply
कार्य में निर्मित की तुलना में थोड़ा धीमा है । ऐसा नहीं है क्योंकि vapply
यह बहुत तेजी से है, for
लेकिन क्योंकि यह लूप के प्रत्येक पुनरावृत्ति में केवल एक ऑपरेशन कर रहा है। इस कोड में और सब कुछ सदिश है। जोरिस मेय पारंपरिक for
लूप में कई (7?) ऑपरेशन प्रत्येक पुनरावृत्ति में हो रहे हैं और इसे निष्पादित करने के लिए सेटअप का थोड़ा सा हिस्सा है। यह भी ध्यान दें कि यह for
संस्करण की तुलना में कितना अधिक कॉम्पैक्ट है ।
2.798 0.003 2.803; 4.908 0.020 4.934; 1.498 0.025 1.528
, और अस्पष्ट भी बेहतर है:1.19 0.00 1.19
sapply
50% धीमी for
और lapply
दो बार के रूप में तेजी से मिला।
y
करने का मतलब है 1:1e6
, न कि numeric(1e6)
(शून्य का वेक्टर)। बार-बार आवंटित foo(0)
करने की कोशिश करना z[0]
अच्छी तरह से एक विशिष्ट for
लूप उपयोग की व्याख्या नहीं करता है । संदेश अन्यथा हाजिर है।
वेक्टर के सबसेट पर कार्य लागू करते समय, tapply
लूप की तुलना में बहुत तेज हो सकता है। उदाहरण:
df <- data.frame(id = rep(letters[1:10], 100000),
value = rnorm(1000000))
f1 <- function(x)
tapply(x$value, x$id, sum)
f2 <- function(x){
res <- 0
for(i in seq_along(l <- unique(x$id)))
res[i] <- sum(x$value[x$id == l[i]])
names(res) <- l
res
}
library(microbenchmark)
> microbenchmark(f1(df), f2(df), times=100)
Unit: milliseconds
expr min lq median uq max neval
f1(df) 28.02612 28.28589 28.46822 29.20458 32.54656 100
f2(df) 38.02241 41.42277 41.80008 42.05954 45.94273 100
apply
हालाँकि, ज्यादातर स्थिति में कोई गति वृद्धि प्रदान नहीं करता है, और कुछ मामलों में बहुत धीमी हो सकती है:
mat <- matrix(rnorm(1000000), nrow=1000)
f3 <- function(x)
apply(x, 2, sum)
f4 <- function(x){
res <- 0
for(i in 1:ncol(x))
res[i] <- sum(x[,i])
res
}
> microbenchmark(f3(mat), f4(mat), times=100)
Unit: milliseconds
expr min lq median uq max neval
f3(mat) 14.87594 15.44183 15.87897 17.93040 19.14975 100
f4(mat) 12.01614 12.19718 12.40003 15.00919 40.59100 100
लेकिन इन स्थितियों के लिए हमें मिल गया है colSums
और rowSums
:
f5 <- function(x)
colSums(x)
> microbenchmark(f5(mat), times=100)
Unit: milliseconds
expr min lq median uq max neval
f5(mat) 1.362388 1.405203 1.413702 1.434388 1.992909 100
microbenchmark
यह बहुत अधिक सटीक है system.time
। यदि आप तुलना करने की कोशिश करते हैं system.time(f3(mat))
और system.time(f4(mat))
आपको लगभग हर बार अलग परिणाम मिलेगा। कभी-कभी केवल एक उचित बेंचमार्क परीक्षण सबसे तेज कार्य दिखाने में सक्षम होता है।
apply
फ़ंक्शंस के परिवार के माध्यम से समानांतरकरण को भी लागू करते हैं। इसलिए स्ट्रक्चरिंग प्रोग्राम इसलिए वे उपयोग करते हैं, उन्हें बहुत मामूली सी लागत पर समानांतर करने की अनुमति देता है।