Jawaban:
The apply
fungsi dalam R tidak memberikan peningkatan kinerja lebih fungsi perulangan lainnya (misalnya for
). Satu pengecualian untuk ini adalah lapply
yang bisa sedikit lebih cepat karena ia bekerja lebih banyak dalam kode C daripada di R (lihat pertanyaan ini untuk contohnya ).
Namun secara umum, aturannya adalah Anda harus menggunakan fungsi terapkan untuk kejelasan, bukan untuk kinerja .
Saya ingin menambahkan ini yang menerapkan fungsi tidak memiliki efek samping , yang merupakan perbedaan penting ketika datang ke pemrograman fungsional dengan R. Ini dapat diganti dengan menggunakan assign
atau <<-
, tetapi itu bisa sangat berbahaya. Efek samping juga membuat program lebih sulit untuk dipahami karena keadaan variabel tergantung pada sejarah.
Edit:
Hanya untuk menekankan ini dengan contoh sepele yang secara rekursif menghitung urutan Fibonacci; ini dapat dijalankan beberapa kali untuk mendapatkan ukuran yang akurat, tetapi intinya adalah bahwa tidak ada metode yang memiliki kinerja yang sangat berbeda:
> 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
Edit 2:
Mengenai penggunaan paket paralel untuk R (mis. Rpvm, rmpi, snow), ini biasanya menyediakan apply
fungsi keluarga (bahkan foreach
paket itu pada dasarnya setara, terlepas dari namanya). Berikut adalah contoh sederhana dari sapply
fungsi di snow
:
library(snow)
cl <- makeSOCKcluster(c("localhost","localhost"))
parSapply(cl, 1:20, get("+"), 3)
Contoh ini menggunakan soket cluster, yang tidak perlu diinstal perangkat lunak tambahan; jika tidak, Anda akan membutuhkan sesuatu seperti PVM atau MPI (lihat halaman pengelompokan Tierney ). snow
memiliki fungsi terapkan berikut ini:
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, ...)
Masuk akal bahwa apply
fungsi harus digunakan untuk eksekusi paralel karena mereka tidak memiliki efek samping . Ketika Anda mengubah nilai variabel dalam satu for
lingkaran, itu diatur secara global. Di sisi lain, semua apply
fungsi dapat dengan aman digunakan secara paralel karena perubahan bersifat lokal pada pemanggilan fungsi (kecuali jika Anda mencoba menggunakan assign
atau <<-
, dalam hal ini Anda dapat menimbulkan efek samping). Tidak perlu dikatakan, sangat penting untuk berhati-hati tentang variabel lokal vs global, terutama ketika berhadapan dengan eksekusi paralel.
Edit:
Berikut adalah contoh sepele untuk menunjukkan perbedaan antara for
dan *apply
sejauh menyangkut efek samping:
> 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
Perhatikan bagaimana df
lingkungan induk diubah oleh for
tetapi tidak *apply
.
snowfall
paket dan mencoba contoh-contoh dalam sketsa mereka. snowfall
dibangun di atas snow
paket dan abstrak detail dari paralelisasi bahkan membuatnya lebih mudah untuk mengeksekusi apply
fungsi paralel .
foreach
telah tersedia dan tampaknya banyak bertanya tentang SO.
lapply
"sedikit lebih cepat" daripada satu for
lingkaran. Namun, di sana, saya tidak melihat ada yang menyarankan begitu. Anda hanya menyebutkan bahwa lapply
lebih cepat daripada sapply
, yang merupakan fakta terkenal karena alasan lain ( sapply
mencoba menyederhanakan output dan karenanya harus melakukan banyak pengecekan ukuran data dan konversi potensial). Tidak ada yang terkait dengan for
. Apakah saya melewatkan sesuatu?
Kadang-kadang speedup bisa menjadi besar, seperti ketika Anda harus bersarang untuk-loop untuk mendapatkan rata-rata berdasarkan pengelompokan lebih dari satu faktor. Di sini Anda memiliki dua pendekatan yang memberi Anda hasil yang sama persis:
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)
Keduanya memberikan hasil yang persis sama, menjadi matriks 5 x 10 dengan rata-rata dan diberi nama baris dan kolom. Tapi:
> 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
Ini dia. Apa yang saya menangkan? ;-)
*apply
lebih cepat. Tetapi saya berpikir bahwa poin yang lebih penting adalah efek sampingnya (memperbarui jawaban saya dengan contoh).
data.table
bahkan lebih cepat dan saya pikir "lebih mudah". 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
adalah fungsi khusus untuk tugas tertentu, itu sebabnya lebih cepat daripada for loop. Itu tidak bisa melakukan apa yang bisa dilakukan untuk loop (sementara biasa apply
bisa). Anda membandingkan apel dengan jeruk.
... dan seperti yang baru saja saya tulis di tempat lain, vapply adalah teman Anda! ... itu seperti sapply, tetapi Anda juga menentukan tipe nilai pengembalian yang membuatnya jauh lebih cepat.
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
Pembaruan 1 Januari 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
loop lebih cepat pada komputer Windows 10, 2-core saya. Saya melakukan ini dengan 5e6
elemen - loop adalah 2,9 detik vs 3,1 detik untuk vapply
.
Saya sudah menulis di tempat lain bahwa contoh seperti Shane's tidak benar-benar menekankan perbedaan kinerja di antara berbagai jenis sintaksis perulangan karena semua waktu dihabiskan dalam fungsi daripada benar-benar menekankan loop. Selain itu, kode ini secara tidak adil membandingkan loop untuk tanpa memori dengan menerapkan fungsi keluarga yang mengembalikan nilai. Inilah contoh yang sedikit berbeda yang menekankan intinya.
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
Jika Anda berencana untuk menyimpan hasilnya kemudian menerapkan fungsi keluarga dapat menjadi jauh lebih dari sintaksis gula.
(unlist sederhana z hanya 0.2s sehingga hasilnya jauh lebih cepat. Menginisialisasi z dalam for loop cukup cepat karena saya memberikan rata-rata 5 dari 6 berjalan terakhir sehingga bergerak di luar system.time akan hampir tidak mempengaruhi hal-hal)
Satu hal lagi yang perlu diperhatikan adalah bahwa ada alasan lain untuk menggunakan fungsi keluarga yang berlaku terlepas dari kinerja, kejelasan, atau kurangnya efek sampingnya. SEBUAHfor
loop biasanya mempromosikan penempatan sebanyak mungkin dalam loop. Ini karena setiap loop memerlukan pengaturan variabel untuk menyimpan informasi (di antara operasi lain yang mungkin). Pernyataan yang diterapkan cenderung bias sebaliknya. Sering kali Anda ingin melakukan beberapa operasi pada data Anda, beberapa di antaranya dapat di-vektor-kan tetapi beberapa mungkin tidak bisa. Dalam R, tidak seperti bahasa lain, yang terbaik adalah memisahkan operasi-operasi tersebut dan menjalankan yang tidak di-vektor-kan dalam pernyataan yang berlaku (atau versi fungsi yang di-vektor-kan) dan yang di-vektor-kan sebagai operasi vektor yang sebenarnya. Ini sering mempercepat kinerja luar biasa.
Mengambil contoh Joris Meys di mana ia menggantikan tradisional untuk loop dengan fungsi R praktis kita dapat menggunakannya untuk menunjukkan efisiensi penulisan kode dengan cara yang lebih ramah untuk speedup yang sama tanpa fungsi khusus.
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
Ini berakhir menjadi jauh lebih cepat daripada for
loop dan hanya sedikit lebih lambat daripada fungsi built-in yang dioptimalkan tapply
. Ini bukan karena vapply
jauh lebih cepat daripada for
tetapi karena itu hanya melakukan satu operasi di setiap iterasi dari loop. Dalam kode ini segala sesuatu yang lain adalah vektor. Dalam for
loop tradisional Joris Meys banyak (7?) Operasi yang terjadi di setiap iterasi dan ada cukup banyak pengaturan hanya untuk itu untuk dijalankan. Perhatikan juga seberapa kompaknya for
versi ini.
2.798 0.003 2.803; 4.908 0.020 4.934; 1.498 0.025 1.528
, dan vapply bahkan lebih baik:1.19 0.00 1.19
sapply
50% lebih lambat dari for
dan lapply
dua kali lebih cepat.
y
ke 1:1e6
, bukan numeric(1e6)
(vektor nol). Mencoba untuk mengalokasikan foo(0)
untuk z[0]
berulang tidak menggambarkan dengan baik khas for
penggunaan lingkaran. Pesannya dinyatakan tepat.
Saat menerapkan fungsi pada subset vektor, tapply
bisa jadi lebih cepat daripada for for. Contoh:
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
Namun, dalam kebanyakan situasi tidak memberikan peningkatan kecepatan, dan dalam beberapa kasus bahkan bisa lebih lambat:
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
Tetapi untuk situasi ini kita punya colSums
dan 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
itu jauh lebih tepat daripada system.time
. Jika Anda mencoba membandingkan system.time(f3(mat))
dan system.time(f4(mat))
Anda akan mendapatkan hasil yang berbeda hampir setiap waktu. Terkadang hanya tes benchmark yang tepat yang mampu menunjukkan fungsi tercepat.
apply
fungsi. Oleh karena itu, penataan program yang mereka gunakan berlaku memungkinkan mereka diparalelkan dengan biaya marjinal yang sangat kecil.