Yanıtlar:
apply
R fonksiyonlar, diğer döngü fonksiyon (örneğin daha iyi performans sağlamamaktadır for
). Bunun bir istisnası lapply
, C kodunda R'den daha fazla çalıştığı için biraz daha hızlı olabilir ( bunun bir örneği için bu soruya bakın ).
Ancak genel olarak, kural, performans için değil, netlik için bir uygulama işlevi kullanmanızdır .
Bunu , R ile fonksiyonel programlama söz konusu olduğunda önemli bir ayrım olan uygulama fonksiyonlarının hiçbir yan etkisi olmadığını da ekleyeceğim. Bu, assign
veya kullanılarak geçersiz kılınabilir <<-
, ancak bu çok tehlikeli olabilir. Bir değişkenin durumu tarihe bağlı olduğu için yan etkiler de bir programı anlamayı zorlaştırır.
Düzenle:
Sadece bunu tekrarlayan Fibonacci dizisini hesaplayan önemsiz bir örnekle vurgulamak için; bu, doğru bir ölçüm almak için birden fazla kez çalıştırılabilir, ancak nokta, yöntemlerin hiçbirinin önemli ölçüde farklı performansa sahip olmamasıdır:
> 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
Düzenleme 2:
R (örneğin rpvm, rmpi, kar) için paralel paketlerin kullanımı ile ilgili olarak, bunlar genellikle apply
aile işlevleri sağlar (hatta foreach
isme rağmen paket esasen eşdeğerdir). İşte sapply
fonksiyonun basit bir örneği snow
:
library(snow)
cl <- makeSOCKcluster(c("localhost","localhost"))
parSapply(cl, 1:20, get("+"), 3)
Bu örnek için ek yazılımın yüklenmesi gerekmeyen bir soket kümesi kullanılır; aksi takdirde PVM veya MPI gibi bir şeye ihtiyacınız olacaktır (bkz. Tierney'in kümeleme sayfası ). snow
aşağıdaki uygulama işlevlerine sahiptir:
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, ...)
Yan etkileri olmadığıapply
için işlevlerin paralel yürütme için kullanılması mantıklıdır . Bir döngü içindeki bir değişken değeri değiştirdiğinizde , bu değer global olarak ayarlanır. Öte yandan, tüm işlevler güvenli bir şekilde paralel olarak kullanılabilir, çünkü değişiklikler işlev çağrısında yereldir (kullanmaya çalışmazsanız veya bu durumda yan etkiler getiremezseniz). Söylemeye gerek yok, özellikle paralel yürütme ile uğraşırken yerel ve küresel değişkenler konusunda dikkatli olmak çok önemlidir.for
apply
assign
<<-
Düzenle:
İşte arasındaki farkı göstermek için önemsiz bir örnek for
ve *apply
böylece yan etkileri söz olarak:
> 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
Ana ortamdaki öğenin nasıl değiştirildiğini for
ancak değiştirilmediğini unutmayın *apply
.
snowfall
Pakete bakıp vinyetlerindeki örnekleri denemenizi öneririm . paketin snowfall
üzerine snow
kurulur ve paralelleştirmenin ayrıntılarını daha da soyutlayarak paralel apply
işlevlerin yerine getirilmesini kolaylaştırır .
foreach
zamandan beri kullanılabilir hale geldi ve SO hakkında çok sorulmuş gibi görünüyor.
lapply
bir for
döngüden "biraz daha hızlı" olan bir duruma örnek olarak başka bir soruya bağlanırsınız . Ancak, orada, bunu öneren hiçbir şey görmüyorum. Sadece lapply
bundan daha hızlı sapply
, diğer nedenlerle iyi bilinen bir gerçektir ( sapply
çıktıyı basitleştirmeye çalışır ve bu nedenle çok fazla veri boyutu kontrolü ve potansiyel dönüşüm yapmak zorundadır). İle ilgili bir şey yok for
. Bir şey mi kaçırıyorum?
Bazen hızlanma önemli olabilir, örneğin birden fazla faktöre sahip bir gruplamaya dayalı olarak ortalamayı almak için döngüler için iç içe geçirmeniz gerektiğinde. Burada size aynı sonucu veren iki yaklaşımınız var:
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)
Her ikisi de aynı sonucu verir, ortalamalar ve adlandırılmış satırlar ve sütunlar ile 5 x 10 matristir. Fakat :
> 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
İşte böyle. Ne kazandım? ;-)
*apply
daha hızlıdır. Ama bence daha önemli olan nokta yan etkilerdir (cevabımı bir örnekle güncelledi).
data.table
daha da hızlı ve "daha kolay" düşünüyorum. 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
belirli bir görev için özel bir işlevdir, bu yüzden for döngüsünden daha hızlıdır. Bir for döngüsünün yapabileceklerini yapamaz (düzenli apply
olabilir). Elmaları portakallarla karşılaştırıyorsunuz.
... ve başka bir yerde yazdığım gibi, vapply arkadaşın! ... aptal gibi, ama aynı zamanda çok daha hızlı yapan dönüş değeri türünü de belirlersiniz.
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 Ocak 2020 güncellemesi:
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
Windows 10, 2 çekirdekli bilgisayarımda döngüler daha hızlı. Bunu 5e6
elementlerle yaptım - bir döngü 2.9 saniye vs. 3.1 saniye idi vapply
.
Başka bir yerde yazdım, Shane's gibi bir örnek, çeşitli döngü sözdizimi arasındaki performans farkını gerçekten vurgulamıyor çünkü zaman aslında döngüyü vurgulamak yerine işlev içinde harcanıyor. Ayrıca, kod haksız yere bir for döngüsünü belleksiz ve bir değer döndüren uygulama ailesi işlevleriyle karşılaştırır. İşte konuyu vurgulayan biraz farklı bir örnek.
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
Sonucu kaydetmeyi planlıyorsanız, aile işlevlerini sözdizimsel şekerden çok daha fazla olabilir .
(z'nin basit listesi sadece 0,2 saniyedir, bu yüzden lapply çok daha hızlıdır. for döngüsünde z'yi başlatmak oldukça hızlıdır, çünkü son 5 çalışmanın ortalamasını veriyorum, böylece sistemin dışına taşınıyor. işleri neredeyse hiç etkilemez)
Yine de dikkat edilmesi gereken bir şey de, aile işlevlerini performanslarından, netliklerinden veya yan etkilerden bağımsız olarak kullanmanın başka bir nedeni olduğudur. Bir for
döngü tipik olarak döngü içine mümkün olduğunca fazla koymayı teşvik eder. Bunun nedeni, her döngünün bilgileri depolamak için değişkenlerin kurulumunu gerektirmesidir (diğer olası işlemlerin yanı sıra). Uygulama ifadeleri diğer taraftan önyargılı olma eğilimindedir. Çoğu zaman verileriniz üzerinde birden fazla işlem gerçekleştirilebilir, bunlardan birkaçı vektörleştirilebilir, ancak bazıları gerçekleştirilemeyebilir. R'de, diğer dillerden farklı olarak, bu işlemleri ayırmak ve bir uygulama deyiminde (veya işlevin vektörleştirilmiş sürümü) vektörleştirilmemiş olanları ve gerçek vektör işlemleri olarak vektörlenenleri çalıştırmak en iyisidir. Bu genellikle performansı muazzam bir şekilde hızlandırır.
Geleneksel bir döngü yerine kullanışlı bir R işlevinin yerini aldığı Joris Meys örneğini ele alarak, özel bir işlev olmadan benzer bir hızlandırma için kod yazma verimliliğini daha R dostu bir şekilde göstermek için kullanabiliriz.
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
Bu, for
döngüden çok daha hızlı ve yerleşik optimize edilmiş tapply
işleve göre biraz daha yavaş olur . Çünkü vapply
çok daha hızlı for
değil, çünkü döngünün her yinelemesinde sadece bir işlem gerçekleştiriyor. Bu kodda diğer her şey vektörleştirilir. Joris Meys geleneksel for
döngüsünde, her yinelemede birçok (7?) İşlem gerçekleşiyor ve sadece yürütmesi için biraz kurulum var. Bunun for
sürümden ne kadar daha kompakt olduğunu da unutmayın .
2.798 0.003 2.803; 4.908 0.020 4.934; 1.498 0.025 1.528
testiniz için , alıyorum ve vapply daha da iyi:1.19 0.00 1.19
sapply
% 50 daha yavaş for
ve lapply
iki kat daha hızlı aldım .
y
için 1:1e6
değil, numeric(1e6)
(sıfır bir vektör). Tekrar tekrar tahsis foo(0)
etmeye çalışmak z[0]
tipik bir for
döngü kullanımını iyi göstermez . Mesaj başka şekilde açıktır.
Bir vektörün alt kümelerine fonksiyonlar uygulanırken, tapply
for döngüsüne göre oldukça hızlı olabilir. Misal:
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
ancak, çoğu durumda hız artışı sağlamaz ve bazı durumlarda çok daha yavaş olabilir:
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
Ancak bu durumlar için elimizdeki colSums
ve 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
çok daha kesin olduğunu fark etmek önemlidir system.time
. Karşılaştırmaya çalışırsanız system.time(f3(mat))
ve system.time(f4(mat))
neredeyse her seferinde farklı sonuçlar elde ederseniz. Bazen en uygun işlevi sadece uygun bir kıyaslama testi gösterebilir.
apply
, işlev ailesi aracılığıyla paralelleştirme uygular . Bu nedenle, programları uygulayacakları şekilde yapılandırmak, çok düşük bir marjinal maliyetle paralel olmalarını sağlar.