উত্তর:
আর এর apply
ফাংশনগুলি অন্যান্য লুপিং ফাংশনগুলিতে (উদাঃ for
) উন্নত কর্মক্ষমতা সরবরাহ করে না । এর একটি ব্যতিক্রমটি lapply
যা কিছুটা দ্রুত হতে পারে কারণ এটি আর এর চেয়ে সি কোডে বেশি কাজ করে ( উদাহরণ হিসাবে এই প্রশ্নটি দেখুন) )।
তবে সাধারণভাবে, নিয়মটি হ'ল আপনার স্পষ্টতার জন্য একটি প্রয়োগ ফাংশন ব্যবহার করা উচিত, পারফরম্যান্সের জন্য নয় ।
আমি এটিতে যুক্ত করব যে প্রয়োগ ফাংশনগুলির কোনও পার্শ্ব প্রতিক্রিয়া নেই , যা আর এর সাথে কার্যকরী প্রোগ্রামিংয়ের ক্ষেত্রে গুরুত্বপূর্ণ পার্থক্য। এটি ব্যবহার করে 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:
আর এর জন্য সমান্তরাল প্যাকেজগুলির ব্যবহার সম্পর্কে (যেমন, 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
ফাংশনগুলি সমান্তরাল সম্পাদনের জন্য তাদের ব্যবহার করা উচিত since কোনও পার্শ্ব প্রতিক্রিয়া নেই । আপনি যখন একটি 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
তবে তা নোট করুন Note*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
আপনি যদি ফলাফলটি সংরক্ষণের পরিকল্পনা করেন তবে পারিবারিক ফাংশন প্রয়োগ করা সিনট্যাকটিক চিনির চেয়ে অনেক বেশি হতে পারে ।
(জেডের সরল তালিকাভুক্তি কেবলমাত্র 0.2 সেকেন্ড তাই ল্যাপলি খুব দ্রুত হয় lo লুপের জন্য জেডের সূচনা করা খুব দ্রুত কারণ কারণ আমি of রানের শেষ ৫ টির গড় দিচ্ছি যাতে সিস্টেমের বাইরে চলে যায় time সময় হবে বিষয়গুলিতে খুব কমই প্রভাবিত করুন)
তবে আরও একটি বিষয় লক্ষণীয় হ'ল পারিবারিক ফাংশনগুলি তাদের কর্মক্ষমতা, স্বচ্ছতা বা পার্শ্বপ্রতিক্রিয়ার অভাবের থেকে আলাদাভাবে প্রয়োগ করার আরও একটি কারণ রয়েছে। একজনfor
লুপ সাধারণত লুপের মধ্যে যথাসম্ভব যথোপযুক্ত প্রচার করে। এটি কারণ প্রতিটি লুপের তথ্য সংরক্ষণের জন্য ভেরিয়েবল সেটআপ করা প্রয়োজন (অন্যান্য সম্ভাব্য ক্রিয়াকলাপগুলির মধ্যে)। বিবৃতি প্রয়োগ করুন অন্যভাবে পক্ষপাতদুষ্ট থাকে। প্রায়শই আপনি আপনার ডেটাতে একাধিক ক্রিয়াকলাপ করতে চান, যার মধ্যে বেশ কয়েকটি ভেক্টরাইজড হতে পারে তবে কিছু হতে পারে না। আর এ, অন্য ভাষার মতো নয়, সেই অপারেশনগুলি আলাদা করে প্রয়োগ করা স্টেটমেন্ট (বা ফাংশনের ভেক্টরাইজড সংস্করণ) এবং ভ্যাক্টরাইজড প্রকৃত ভেক্টর অপারেশন হিসাবে ভেক্টরাইজ করা হয়নি এমনগুলি চালানো ভাল। এটি প্রায়শই দুর্দান্ত পারফরম্যান্স গতি দেয়।
জোরিস মাইস উদাহরণটি গ্রহণ করা যেখানে তিনি একটি কার্যকর আর ফাংশন দিয়ে লুপের জন্য একটি প্রচলিত rep
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
ফাংশনগুলির পরিবারের মাধ্যমে সমান্তরালতা প্রয়োগ করে। অতএব কাঠামোগত প্রোগ্রামগুলি যাতে তারা প্রয়োগ করে সেগুলি খুব সামান্য প্রান্তিক ব্যয়ে সমান্তরাল হওয়ার সুযোগ দেয়।