আর এর পরিবার কী সিনট্যাকটিক চিনির চেয়ে বেশি প্রয়োগ করে?


152

... কার্যকর করার সময় এবং / অথবা মেমরি সম্পর্কিত।

যদি এটি সত্য না হয় তবে কোড স্নিপেট দিয়ে প্রমাণ করুন। দ্রষ্টব্য যে ভেক্টরাইজেশন দ্বারা গতিবেগ গণনা করা হয় না। Speedup থেকে আসতে হবে apply( tapply, sapply, ...) নিজেই।

উত্তর:


152

আর এর 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


30
আর এর জন্য বেশিরভাগ মাল্টি কোর প্যাকেজগুলি applyফাংশনগুলির পরিবারের মাধ্যমে সমান্তরালতা প্রয়োগ করে। অতএব কাঠামোগত প্রোগ্রামগুলি যাতে তারা প্রয়োগ করে সেগুলি খুব সামান্য প্রান্তিক ব্যয়ে সমান্তরাল হওয়ার সুযোগ দেয়।
শার্পি

শার্পি - এর জন্য আপনাকে ধন্যবাদ! উদাহরণস্বরূপ কোনও ধারণা যা (উইন্ডোজ এক্সপিতে) দেখায়?
তাল গালিলি

5
আমি snowfallপ্যাকেজটির দিকে তাকানোর এবং তাদের ভিগনেটে উদাহরণগুলি চেষ্টা করার পরামর্শ দেব । প্যাকেজের snowfallশীর্ষে তৈরি করে snowএবং সমান্তরালতার বিবরণটিকে বিমূর্ত করে তোলে এমনকি সমান্তরাল applyক্রিয়াকলাপগুলি কার্যকর করতে এটি মৃতকে সহজ করে তোলে ।
শার্পি

1
@ শার্পি তবে নোটটি যেটি foreachতখন থেকে সহজলভ্য হয়ে গেছে এবং এসও সম্পর্কে অনেকগুলি জিজ্ঞাসাবাদ করা হবে বলে মনে হয়।
এরি বি ফ্রেডম্যান

1
@ শান, আপনার উত্তরের একেবারে শীর্ষে, আপনি lapplyএকটি forলুপের তুলনায় "কিছুটা দ্রুত" একটি মামলার উদাহরণ হিসাবে অন্য প্রশ্নের সাথে লিঙ্ক করেছেন । যাইহোক, সেখানে, আমি এমন কোনও পরামর্শ দিচ্ছি না। আপনি কেবলমাত্র এটির lapplyচেয়ে দ্রুতগতির কথা উল্লেখ করেছেন sapply, যা অন্যান্য কারণে একটি সুপরিচিত তথ্য ( sapplyআউটপুটকে সহজ করার চেষ্টা করে এবং তাই ডেটা আকারের চেকিং এবং সম্ভাব্য রূপান্তরগুলি প্রচুর করতে হয়)। সম্পর্কিত কিছুই for। আমি কিছু অনুপস্থিত করছি?
ফ্লোডেল

70

কখনও কখনও স্পিডআপ যথেষ্ট পরিমাণে হতে পারে, যেমন একাধিক ফ্যাক্টরের গ্রুপিংয়ের উপর ভিত্তি করে গড় অর্জনের জন্য আপনাকে বাসা বাঁধতে হয়। এখানে আপনার কাছে দুটি পন্থা রয়েছে যা আপনাকে ঠিক একই ফলাফল দেয়:

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 

এই নাও. আমি কী জিতলাম? ;-)


আহা, এত মিষ্টি :-) আমি আসলে ভাবছিলাম যে কেউ যদি আমার পরিবর্তে দেরিতে উত্তর আসে তবেই।
জোরিস মেয়েস

1
আমি সর্বদা "সক্রিয়" অনুসারে বাছাই করি। :) আপনার উত্তর কীভাবে সাধারণ করা যায় তা নিশ্চিত নন; কখনও কখনও *applyদ্রুত হয়। তবে আমি মনে করি যে আরও গুরুত্বপূর্ণ বিষয়টি হ'ল পার্শ্ব প্রতিক্রিয়া (আমার উত্তরটি উদাহরণ সহ আপডেট করা)।
শেন

1
আমি মনে করি যে প্রয়োগটি বিশেষত দ্রুততর হয় যখন আপনি বিভিন্ন সাবসেটের উপরে কোনও ফাংশন প্রয়োগ করতে চান। যদি কোনও নেস্টেড লুপের জন্য কোনও স্মার্ট প্রয়োগ সমাধান থাকে তবে আমার ধারণা। প্রয়োগের সমাধানটিও দ্রুত হবে। বেশিরভাগ ক্ষেত্রে প্রয়োগ আমার অনুমানের চেয়ে বেশি গতি অর্জন করে না তবে আমি অবশ্যই পার্শ্বপ্রতিক্রিয়ার সাথে একমত হই।
জোরিস মেয়েস

2
এটি সামান্য অফ টপিক, তবে এই নির্দিষ্ট উদাহরণের জন্য, 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")])
dnlbrky

12
এই তুলনা অযৌক্তিক। tapplyএকটি নির্দিষ্ট কাজের জন্য একটি বিশেষ ফাংশন, হয় যে কেন এটি দ্রুত লুপ জন্য একটি চেয়ে আছে। এটি লুপের জন্য কী করতে পারে তা করতে পারে না (নিয়মিত applyকরতে পারে)। আপনি কমলা দিয়ে আপেল তুলনা করছেন।
এডিডি

47

... এবং যেমন আমি অন্য কোথাও লিখেছি, 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
কোলে

27

আমি অন্য কোথাও লিখেছি যে শেনের মতো উদাহরণটি বিভিন্ন ধরণের লুপিং সিনট্যাক্সের মধ্যে পারফরম্যান্সের পার্থক্যের উপর আসলেই চাপ দেয় না কারণ সময়টি লুপকে চাপ দেওয়ার পরিবর্তে সময়টি সমস্ত সময় ব্যয় করে। তদ্ব্যতীত, কোডটি অন্যায়ভাবে লুপের সাথে তুলনা করে কোনও স্মৃতিবিহীন কোনও লুপের সাথে পারিবারিক ফাংশন প্রয়োগ করে যা কোনও মান দেয়। এখানে কিছুটা আলাদা উদাহরণ যা পয়েন্টটিতে জোর দেয়।

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সংস্করণটির চেয়ে এটি আরও কতটা কমপ্যাক্ট রয়েছে তাও নোট করুন ।


4
কিন্তু শেন উদাহরণ বাস্তবসম্মত সময় যে অধিকাংশ হল হয় সাধারণত ফাংশন অতিবাহিত, লুপ না।
হ্যাডলি

9
নিজের জন্য কথা বলুন ...:) ... শেনের সম্ভবত একটি নির্দিষ্ট অর্থে বাস্তববাদী তবে একই অর্থে বিশ্লেষণটি একেবারেই অকেজো। লোকেরা যখন পুনরাবৃত্তি প্রচুর করতে হবে তখন পুনরাবৃত্তি ব্যবস্থার গতি সম্পর্কে চিন্তা করবে, অন্যথায় তাদের সমস্যা অন্য কোথাও হোক। এটি কোনও ফাংশনের ক্ষেত্রে সত্য। আমি যদি একটি পাপ লিখি যা 0.001 সেকেন্ড লাগে এবং অন্য কেউ লিখতে পারে যা 0,002 কে নিয়ে যায় কে ?? ঠিক আছে, যত তাড়াতাড়ি আপনি তাদের যত্ন নেওয়ার একটি গুছা করতে হবে।
জন

2
একটি 12 কোর 3 গিগাহার্টজ ইন্টেল সিওন, 64 বিট-এ, আমি আপনার কাছে বেশ আলাদা নম্বর পেয়েছি - লুপটি যথেষ্ট উন্নতি করে: আপনার তিনটি পরীক্ষার জন্য, আমি পেয়েছি 2.798 0.003 2.803; 4.908 0.020 4.934; 1.498 0.025 1.528, এবং ভ্যাপ্লি আরও ভাল:1.19 0.00 1.19
নট 101

2
এটি ওএস এবং আর সংস্করণে পরিবর্তিত হয় ... এবং নিখুঁত অর্থে সিপিইউতে। আমি ম্যাকের উপর 2.15.2 দিয়ে দৌড়েছি এবং sapply50% ধীর forএবং lapplyদ্বিগুণ দ্রুত গতিতে পেয়েছি ।
জন

1
আপনার উদাহরণে, আপনি সেট yকরতে চান 1:1e6, না numeric(1e6)(শূন্যগুলির একটি ভেক্টর)। বারবার বরাদ্দ foo(0)দেওয়ার চেষ্টা করা z[0]একটি সাধারণ forলুপের ব্যবহারের চিত্রিত করে না । বার্তাটি অন্যথায় স্পট করা আছে।
ফ্লোডেল

3

যখন কোনও ভেক্টরের সাবসেটের উপরে ফাংশন প্রয়োগ করা হয় তখন 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

7
এটি লক্ষ্য করা গুরুত্বপূর্ণ যে (কোডের ছোট ছোট টুকরাগুলির জন্য) microbenchmarkএটি এর চেয়ে অনেক বেশি সুনির্দিষ্ট system.time। আপনি যদি তুলনা করার চেষ্টা করেন system.time(f3(mat))এবং system.time(f4(mat))আপনি প্রায় প্রতিবারই আলাদা ফলাফল পাবেন। কখনও কখনও শুধুমাত্র একটি সঠিক বেঞ্চমার্ক পরীক্ষা দ্রুততম ফাংশনটি প্রদর্শন করতে সক্ষম হয়।
মিশেল
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.