Jadi kami biasa mengatakan kepada setiap R pengguna baru bahwa " apply
tidak vektor, lihat Patrick Burns R Inferno Circle 4 " yang mengatakan (saya kutip):
Refleks yang umum adalah menggunakan fungsi dalam keluarga terapan. Ini bukan vektorisasi, ini menyembunyikan loop . Fungsi apply memiliki perulangan for dalam definisinya. Fungsi lapply mengubur loop, tetapi waktu eksekusi cenderung kurang lebih sama dengan for loop eksplisit.
Memang, sekilas apply
kode sumber mengungkapkan loop:
grep("for", capture.output(getAnywhere("apply")), value = TRUE)
## [1] " for (i in 1L:d2) {" " else for (i in 1L:d2) {"
Oke sejauh ini, tapi lihat lapply
atau vapply
sebenarnya ungkapkan gambaran yang sama sekali berbeda:
lapply
## function (X, FUN, ...)
## {
## FUN <- match.fun(FUN)
## if (!is.vector(X) || is.object(X))
## X <- as.list(X)
## .Internal(lapply(X, FUN))
## }
## <bytecode: 0x000000000284b618>
## <environment: namespace:base>
Jadi ternyata tidak ada R for
loop yang bersembunyi di sana, melainkan mereka memanggil fungsi tertulis C internal.
Pengamatan sekilas ke dalam lubang kelinci mengungkapkan gambaran yang hampir sama
Selain itu, mari kita ambil colMeans
fungsinya misalnya, yang tidak pernah dituduh tidak vektorisasi
colMeans
# function (x, na.rm = FALSE, dims = 1L)
# {
# if (is.data.frame(x))
# x <- as.matrix(x)
# if (!is.array(x) || length(dn <- dim(x)) < 2L)
# stop("'x' must be an array of at least two dimensions")
# if (dims < 1L || dims > length(dn) - 1L)
# stop("invalid 'dims'")
# n <- prod(dn[1L:dims])
# dn <- dn[-(1L:dims)]
# z <- if (is.complex(x))
# .Internal(colMeans(Re(x), n, prod(dn), na.rm)) + (0+1i) *
# .Internal(colMeans(Im(x), n, prod(dn), na.rm))
# else .Internal(colMeans(x, n, prod(dn), na.rm))
# if (length(dn) > 1L) {
# dim(z) <- dn
# dimnames(z) <- dimnames(x)[-(1L:dims)]
# }
# else names(z) <- dimnames(x)[[dims + 1]]
# z
# }
# <bytecode: 0x0000000008f89d20>
# <environment: namespace:base>
Hah? Itu juga hanya panggilan .Internal(colMeans(...
yang juga bisa kita temukan di lubang kelinci . Jadi apa bedanya dengan ini .Internal(lapply(..
?
Sebenarnya benchmark cepat mengungkapkan bahwa sapply
performanya tidak lebih buruk dari colMeans
dan jauh lebih baik daripada for
loop untuk kumpulan data besar
m <- as.data.frame(matrix(1:1e7, ncol = 1e5))
system.time(colMeans(m))
# user system elapsed
# 1.69 0.03 1.73
system.time(sapply(m, mean))
# user system elapsed
# 1.50 0.03 1.60
system.time(apply(m, 2, mean))
# user system elapsed
# 3.84 0.03 3.90
system.time(for(i in 1:ncol(m)) mean(m[, i]))
# user system elapsed
# 13.78 0.01 13.93
Dengan kata lain, apakah benar untuk mengatakan bahwa lapply
dan vapply
benar-benar vectorised (dibandingkan dengan apply
yang merupakan for
lingkaran yang juga panggilan lapply
) dan apa yang Patrick Luka bakar benar-benar berarti untuk mengatakan?
sumber
*apply
fungsi berulang kali memanggil fungsi R, yang membuatnya berputar. Mengenai kinerja yang baik darisapply(m, mean)
: Mungkin kode-Clapply
apakah metode hanya mengirimkan sekali dan kemudian memanggil metode itu berulang kali?mean.default
cukup dioptimalkan.Jawaban:
Pertama-tama, dalam contoh Anda, Anda melakukan pengujian pada "data.frame" yang tidak adil
colMeans
,apply
dan"[.data.frame"
karena memiliki overhead:system.time(as.matrix(m)) #called by `colMeans` and `apply` # user system elapsed # 1.03 0.00 1.05 system.time(for(i in 1:ncol(m)) m[, i]) #in the `for` loop # user system elapsed # 12.93 0.01 13.07
Pada matriks, gambarnya sedikit berbeda:
mm = as.matrix(m) system.time(colMeans(mm)) # user system elapsed # 0.01 0.00 0.01 system.time(apply(mm, 2, mean)) # user system elapsed # 1.48 0.03 1.53 system.time(for(i in 1:ncol(mm)) mean(mm[, i])) # user system elapsed # 1.22 0.00 1.21
Mengembalikan bagian utama dari pertanyaan, perbedaan utama antara
lapply
/mapply
/ etc dan R-loop langsung adalah tempat perulangan dilakukan. Seperti yang dicatat Roland, loop C dan R perlu mengevaluasi fungsi R di setiap iterasi yang paling mahal. Fungsi C yang sangat cepat adalah yang melakukan segala sesuatu di C, jadi, saya rasa, seharusnya ini yang dimaksud dengan "vektorisasi"?Contoh di mana kita menemukan mean di setiap elemen "daftar":
( EDIT 11 Mei '16 : Saya percaya contoh dengan menemukan "mean" bukanlah penyiapan yang baik untuk perbedaan antara mengevaluasi fungsi R secara berulang dan kode yang dikompilasi, (1) karena kekhususan algoritme mean R pada "numerik" s lebih sederhana
sum(x) / length(x)
dan (2) seharusnya lebih masuk akal untuk menguji pada "list" denganlength(x) >> lengths(x)
. Jadi, contoh "mean" dipindahkan ke akhir dan diganti dengan yang lain.)Sebagai contoh sederhana, kita dapat mempertimbangkan penemuan kebalikan dari setiap
length == 1
elemen "daftar":Dalam sebuah
tmp.c
file:#include <R.h> #define USE_RINTERNALS #include <Rinternals.h> #include <Rdefines.h> /* call a C function inside another */ double oppC(double x) { return(ISNAN(x) ? NA_REAL : -x); } SEXP sapply_oppC(SEXP x) { SEXP ans = PROTECT(allocVector(REALSXP, LENGTH(x))); for(int i = 0; i < LENGTH(x); i++) REAL(ans)[i] = oppC(REAL(VECTOR_ELT(x, i))[0]); UNPROTECT(1); return(ans); } /* call an R function inside a C function; * will be used with 'f' as a closure and as a builtin */ SEXP sapply_oppR(SEXP x, SEXP f) { SEXP call = PROTECT(allocVector(LANGSXP, 2)); SETCAR(call, install(CHAR(STRING_ELT(f, 0)))); SEXP ans = PROTECT(allocVector(REALSXP, LENGTH(x))); for(int i = 0; i < LENGTH(x); i++) { SETCADR(call, VECTOR_ELT(x, i)); REAL(ans)[i] = REAL(eval(call, R_GlobalEnv))[0]; } UNPROTECT(2); return(ans); }
Dan di sisi R:
system("R CMD SHLIB /home/~/tmp.c") dyn.load("/home/~/tmp.so")
dengan data:
set.seed(007) myls = rep_len(as.list(c(NA, runif(3))), 1e7) #a closure wrapper of `-` oppR = function(x) -x for_oppR = compiler::cmpfun(function(x, f) { f = match.fun(f) ans = numeric(length(x)) for(i in seq_along(x)) ans[[i]] = f(x[[i]]) return(ans) })
Pembandingan:
#call a C function iteratively system.time({ sapplyC = .Call("sapply_oppC", myls) }) # user system elapsed # 0.048 0.000 0.047 #evaluate an R closure iteratively system.time({ sapplyRC = .Call("sapply_oppR", myls, "oppR") }) # user system elapsed # 3.348 0.000 3.358 #evaluate an R builtin iteratively system.time({ sapplyRCprim = .Call("sapply_oppR", myls, "-") }) # user system elapsed # 0.652 0.000 0.653 #loop with a R closure system.time({ forR = for_oppR(myls, "oppR") }) # user system elapsed # 4.396 0.000 4.409 #loop with an R builtin system.time({ forRprim = for_oppR(myls, "-") }) # user system elapsed # 1.908 0.000 1.913 #for reference and testing system.time({ sapplyR = unlist(lapply(myls, oppR)) }) # user system elapsed # 7.080 0.068 7.170 system.time({ sapplyRprim = unlist(lapply(myls, `-`)) }) # user system elapsed # 3.524 0.064 3.598 all.equal(sapplyR, sapplyRprim) #[1] TRUE all.equal(sapplyR, sapplyC) #[1] TRUE all.equal(sapplyR, sapplyRC) #[1] TRUE all.equal(sapplyR, sapplyRCprim) #[1] TRUE all.equal(sapplyR, forR) #[1] TRUE all.equal(sapplyR, forRprim) #[1] TRUE
(Mengikuti contoh asli dari mean temuan):
#all computations in C all_C = inline::cfunction(sig = c(R_ls = "list"), body = ' SEXP tmp, ans; PROTECT(ans = allocVector(REALSXP, LENGTH(R_ls))); double *ptmp, *pans = REAL(ans); for(int i = 0; i < LENGTH(R_ls); i++) { pans[i] = 0.0; PROTECT(tmp = coerceVector(VECTOR_ELT(R_ls, i), REALSXP)); ptmp = REAL(tmp); for(int j = 0; j < LENGTH(tmp); j++) pans[i] += ptmp[j]; pans[i] /= LENGTH(tmp); UNPROTECT(1); } UNPROTECT(1); return(ans); ') #a very simple `lapply(x, mean)` C_and_R = inline::cfunction(sig = c(R_ls = "list"), body = ' SEXP call, ans, ret; PROTECT(call = allocList(2)); SET_TYPEOF(call, LANGSXP); SETCAR(call, install("mean")); PROTECT(ans = allocVector(VECSXP, LENGTH(R_ls))); PROTECT(ret = allocVector(REALSXP, LENGTH(ans))); for(int i = 0; i < LENGTH(R_ls); i++) { SETCADR(call, VECTOR_ELT(R_ls, i)); SET_VECTOR_ELT(ans, i, eval(call, R_GlobalEnv)); } double *pret = REAL(ret); for(int i = 0; i < LENGTH(ans); i++) pret[i] = REAL(VECTOR_ELT(ans, i))[0]; UNPROTECT(3); return(ret); ') R_lapply = function(x) unlist(lapply(x, mean)) R_loop = function(x) { ans = numeric(length(x)) for(i in seq_along(x)) ans[i] = mean(x[[i]]) return(ans) } R_loopcmp = compiler::cmpfun(R_loop) set.seed(007); myls = replicate(1e4, runif(1e3), simplify = FALSE) all.equal(all_C(myls), C_and_R(myls)) #[1] TRUE all.equal(all_C(myls), R_lapply(myls)) #[1] TRUE all.equal(all_C(myls), R_loop(myls)) #[1] TRUE all.equal(all_C(myls), R_loopcmp(myls)) #[1] TRUE microbenchmark::microbenchmark(all_C(myls), C_and_R(myls), R_lapply(myls), R_loop(myls), R_loopcmp(myls), times = 15) #Unit: milliseconds # expr min lq median uq max neval # all_C(myls) 37.29183 38.19107 38.69359 39.58083 41.3861 15 # C_and_R(myls) 117.21457 123.22044 124.58148 130.85513 169.6822 15 # R_lapply(myls) 98.48009 103.80717 106.55519 109.54890 116.3150 15 # R_loop(myls) 122.40367 130.85061 132.61378 138.53664 178.5128 15 # R_loopcmp(myls) 105.63228 111.38340 112.16781 115.68909 128.1976 15
sumber
all_C
dan AndaC_and_R
. Saya juga ditemukan di dokumentasi daricompiler::cmpfun
sebuah versi R lama lapply yang berisi R yang sebenarnyafor
lingkaran, aku mulai curiga bahwa Burns mengacu bahwa versi lama yang vectorised sejak itu dan ini adalah jawaban yang sebenarnya untuk pertanyaan saya .. ..la1
dari?compiler::cmpfun
tampaknya, masih, menghasilkan efisiensi yang sama dengan semua kecualiall_C
fungsi. Saya kira, itu-memang- menjadi masalah definisi; adalah "vektorisasi" yang berarti fungsi apa pun yang menerima tidak hanya skalar, fungsi apa pun yang memiliki kode C, fungsi apa pun yang menggunakan komputasi dalam C saja?lapply
tidak vektorisasi hanya karena mengevaluasi fungsi R di setiap iterasi dengan kode C-nya?Bagi saya, vektorisasi terutama tentang membuat kode Anda lebih mudah ditulis dan lebih mudah dipahami.
Tujuan dari fungsi vektorisasi adalah untuk menghilangkan pembukuan yang terkait dengan loop for. Misalnya, alih-alih:
means <- numeric(length(mtcars)) for (i in seq_along(mtcars)) { means[i] <- mean(mtcars[[i]]) } sds <- numeric(length(mtcars)) for (i in seq_along(mtcars)) { sds[i] <- sd(mtcars[[i]]) }
Kamu bisa menulis:
means <- vapply(mtcars, mean, numeric(1)) sds <- vapply(mtcars, sd, numeric(1))
Itu membuatnya lebih mudah untuk melihat apa yang sama (data input) dan apa yang berbeda (fungsi yang Anda terapkan).
Keuntungan sekunder dari vektorisasi adalah bahwa for-loop sering ditulis dalam C, bukan dalam R. Ini memiliki manfaat kinerja yang substansial, tetapi menurut saya itu bukan properti utama vektorisasi. Vektorisasi pada dasarnya adalah tentang menyelamatkan otak Anda, bukan menyelamatkan pekerjaan komputer.
sumber
for
loop C dan R. Oke, loop C mungkin dioptimalkan oleh kompiler, tetapi poin utama kinerja adalah apakah konten loop itu efisien. Dan jelas kode yang dikompilasi biasanya lebih cepat daripada kode yang diinterpretasikan. Tapi mungkin itu yang ingin Anda katakan.Saya setuju dengan pandangan Patrick Burns bahwa ini agak menyembunyikan loop dan bukan vektorisasi kode . Inilah alasannya:
Pertimbangkan
C
cuplikan kode ini :for (int i=0; i<n; i++) c[i] = a[i] + b[i]
Apa yang ingin kami lakukan cukup jelas. Tapi bagaimana tugas itu dilakukan atau bagaimana itu bisa dilakukan sebenarnya tidak. Sebuah for-loop secara default adalah konstruksi serial. Itu tidak menginformasikan apakah atau bagaimana sesuatu dapat dilakukan secara paralel.
Cara yang paling jelas adalah kode dijalankan secara berurutan . Muat
a[i]
danb[i]
ke register, tambahkan, simpan hasilnyac[i]
, dan lakukan ini untuk masing-masingi
.Namun, prosesor modern memiliki set instruksi vektor atau SIMD yang mampu beroperasi pada vektor data selama instruksi yang sama ketika melakukan operasi yang sama (misalnya, menambahkan dua vektor seperti yang ditunjukkan di atas). Bergantung pada prosesor / arsitektur, dimungkinkan untuk menambahkan, katakanlah, empat angka dari
a
dan dib
bawah instruksi yang sama, alih-alih satu per satu.Akan sangat bagus jika kompilator mengidentifikasi blok kode seperti itu dan secara otomatis memvektoralnya, yang merupakan tugas yang sulit. Vektorisasi kode otomatis adalah topik penelitian yang menantang dalam Ilmu Komputer. Namun seiring waktu, penyusun menjadi lebih baik dalam hal itu. Anda dapat memeriksa kemampuan vektorisasi otomatis di
GNU-gcc
sini . Begitu pula untuk diLLVM-clang
sini . Dan Anda juga dapat menemukan beberapa tolok ukur di tautan terakhir dibandingkan dengangcc
danICC
(kompiler Intel C ++).gcc
(Saya aktifv4.9
) misalnya tidak melakukan vektorisasi kode secara otomatis pada-O2
pengoptimalan level. Jadi jika kita mengeksekusi kode yang ditunjukkan di atas, itu akan dijalankan secara berurutan. Berikut waktu untuk menambahkan dua vektor bilangan bulat dengan panjang 500 juta.Kita perlu menambahkan bendera
-ftree-vectorize
atau mengubah pengoptimalan ke level-O3
. (Perhatikan bahwa-O3
melakukan pengoptimalan tambahan lainnya juga). Flag-fopt-info-vec
ini berguna karena menginformasikan ketika sebuah loop berhasil di-vectorisasi).# compiling with -O2, -ftree-vectorize and -fopt-info-vec # test.c:32:5: note: loop vectorized # test.c:32:5: note: loop versioned for vectorization because of possible aliasing # test.c:32:5: note: loop peeled for vectorization to enhance alignment
Ini memberi tahu kita bahwa fungsi tersebut vektorisasi. Berikut adalah pengaturan waktu yang membandingkan versi non-vektorisasi dan vektorisasi pada vektor integer dengan panjang 500 juta:
x = sample(100L, 500e6L, TRUE) y = sample(100L, 500e6L, TRUE) z = vector("integer", 500e6L) # result vector # non-vectorised, -O2 system.time(.Call("Csum", x, y, z)) # user system elapsed # 1.830 0.009 1.852 # vectorised using flags shown above at -O2 system.time(.Call("Csum", x, y, z)) # user system elapsed # 0.361 0.001 0.362 # both results are checked for identicalness, returns TRUE
Bagian ini dapat dilewati dengan aman tanpa kehilangan kontinuitas.
Penyusun tidak akan selalu memiliki informasi yang cukup untuk melakukan vektorisasi. Kita bisa menggunakan spesifikasi OpenMP untuk pemrograman paralel , yang juga menyediakan arahan kompiler simd untuk menginstruksikan kompiler untuk membuat vektor kode. Sangat penting untuk memastikan bahwa tidak ada memori yang tumpang tindih, kondisi balapan, dll .. saat melakukan vektorisasi kode secara manual, jika tidak maka akan menghasilkan hasil yang salah.
#pragma omp simd for (i=0; i<n; i++) c[i] = a[i] + b[i]
Dengan melakukan ini, kami secara khusus meminta kompilator untuk melakukan vektorisasi apa pun yang terjadi. Kita perlu mengaktifkan ekstensi OpenMP dengan menggunakan bendera waktu kompilasi
-fopenmp
. Dengan melakukan itu:# timing with -O2 + OpenMP with simd x = sample(100L, 500e6L, TRUE) y = sample(100L, 500e6L, TRUE) z = vector("integer", 500e6L) # result vector system.time(.Call("Cvecsum", x, y, z)) # user system elapsed # 0.360 0.001 0.360
yang bagus! Ini telah diuji dengan gcc v6.2.0 dan llvm clang v3.9.0 (keduanya diinstal melalui homebrew, MacOS 10.12.3), keduanya mendukung OpenMP 4.0.
Dalam hal ini, meskipun halaman Wikipedia di Array Programming menyebutkan bahwa bahasa yang beroperasi di seluruh array biasanya menyebutnya sebagai operasi vektor , itu sebenarnya adalah IMO yang menyembunyikan loop (kecuali jika benar-benar vektorisasi).
Dalam kasus R, genap
rowSums()
ataucolSums()
kode dalam C jangan memanfaatkan vektorisasi kode IIUC; itu hanya satu loop di C. Hal yang sama berlaku untuklapply()
. Dalam kasusapply()
, itu dalam R. Semua ini karena itu bersembunyi loop .HTH
Referensi:
sumber
Jadi untuk menjumlahkan jawaban / komentar hebat menjadi beberapa jawaban umum dan memberikan beberapa latar belakang: R memiliki 4 jenis loop ( dari urutan tidak-vektorisasi ke vektor )
for
Loop R yang berulang kali memanggil fungsi R di setiap iterasi ( Tidak vektorisasi )Jadi
*apply
keluarga adalah tipe kedua. Kecualiapply
yang lebih dari tipe pertamaAnda dapat memahami ini dari komentar di kode sumbernya
Itu berarti bahwa
lapply
kode C menerima fungsi yang tidak dievaluasi dari R dan kemudian mengevaluasinya di dalam kode C itu sendiri. Ini pada dasarnya adalah perbedaan antara panggilanlapply
s.Internal
Yang memiliki
FUN
argumen yang memegang fungsi R.Dan
colMeans
.Internal
panggilan yang tidak memilikiFUN
argumencolMeans
, tidak sepertilapply
tahu persis fungsi apa yang perlu digunakan, sehingga menghitung mean secara internal dalam kode C.Anda dapat dengan jelas melihat proses evaluasi fungsi R di setiap iterasi dalam
lapply
kode C.for(R_xlen_t i = 0; i < n; i++) { if (realIndx) REAL(ind)[0] = (double)(i + 1); else INTEGER(ind)[0] = (int)(i + 1); tmp = eval(R_fcall, rho); // <----------------------------- here it is if (MAYBE_REFERENCED(tmp)) tmp = lazy_duplicate(tmp); SET_VECTOR_ELT(ans, i, tmp); }
Singkatnya,
lapply
tidak vektorisasi , meskipun memiliki dua keuntungan yang mungkin dibandingkanfor
loop R biasaMengakses dan menugaskan dalam satu lingkaran tampaknya lebih cepat di C (yaitu dalam
lapply
memasukkan fungsi) Meskipun perbedaannya tampak besar, kami tetap berada di tingkat mikrodetik dan yang mahal adalah penilaian fungsi R di setiap iterasi. Contoh sederhana:ffR = function(x) { ans = vector("list", length(x)) for(i in seq_along(x)) ans[[i]] = x[[i]] ans } ffC = inline::cfunction(sig = c(R_x = "data.frame"), body = ' SEXP ans; PROTECT(ans = allocVector(VECSXP, LENGTH(R_x))); for(int i = 0; i < LENGTH(R_x); i++) SET_VECTOR_ELT(ans, i, VECTOR_ELT(R_x, i)); UNPROTECT(1); return(ans); ') set.seed(007) myls = replicate(1e3, runif(1e3), simplify = FALSE) mydf = as.data.frame(myls) all.equal(ffR(myls), ffC(myls)) #[1] TRUE all.equal(ffR(mydf), ffC(mydf)) #[1] TRUE microbenchmark::microbenchmark(ffR(myls), ffC(myls), ffR(mydf), ffC(mydf), times = 30) #Unit: microseconds # expr min lq median uq max neval # ffR(myls) 3933.764 3975.076 4073.540 5121.045 32956.580 30 # ffC(myls) 12.553 12.934 16.695 18.210 19.481 30 # ffR(mydf) 14799.340 15095.677 15661.889 16129.689 18439.908 30 # ffC(mydf) 12.599 13.068 15.835 18.402 20.509 30
Seperti yang disebutkan oleh @Roland, ini menjalankan loop C terkompilasi, bukan loop R yang ditafsirkan
Meskipun saat memvektor kode Anda, ada beberapa hal yang perlu Anda pertimbangkan.
df
) adalah dari kelasdata.frame
, beberapa fungsi Vectorized (seperticolMeans
,colSums
,rowSums
, dll) harus mengubahnya menjadi sebuah matriks pertama, hanya karena ini adalah bagaimana mereka dirancang. Artinya untuk yang besardf
ini bisa membuat overhead yang besar. Sementaralapply
tidak perlu melakukan ini karena mengekstrak vektor sebenarnya daridf
(sepertidata.frame
hanya daftar vektor) dan dengan demikian, jika Anda tidak memiliki banyak kolom tetapi banyak baris,lapply(df, mean)
terkadang bisa menjadi pilihan yang lebih baik daripadacolMeans(df)
..Primitive
, dan generic (S3
,S4
) lihat di sini untuk beberapa informasi tambahan. Fungsi generik harus melakukan pengiriman metode yang terkadang membutuhkan biaya yang mahal. Misalnya,mean
adalahS3
fungsi generik whilesum
isPrimitive
. Jadi beberapa kalilapply(df, sum)
bisa sangat efisien dibandingkancolSums
dengan alasan yang disebutkan di atassumber
colMeans
dll yang dibangun untuk menangani hanya matriks. (2) Saya agak bingung dengan kategori ketiga Anda; Saya tidak tahu apa -exaclty- yang Anda maksud. (3) Karena Anda mengacu secara khususlapply
, saya yakin tidak ada bedanya antara"[<-"
di R dan C; mereka berdua mengalokasikan "daftar" (SEXP) sebelumnya dan mengisinya di setiap iterasi (SET_VECTOR_ELT
di C), kecuali saya kehilangan maksud Anda.do.call
bahwa ia membangun panggilan fungsi di lingkungan C dan hanya mengevaluasinya; meskipun saya mengalami kesulitan untuk membandingkannya dengan perulangan atau vektorisasi karena itu melakukan hal yang berbeda. Anda, sebenarnya, benar tentang mengakses dan menetapkan perbedaan antara C dan R, meskipun keduanya tetap pada tingkat mikrodetik dan tidak mempengaruhi hasil hasil yang sangat besar, karena yang mahal adalah panggilan fungsi R berulang (bandingkanR_loop
danR_lapply
dalam jawaban saya ). (Saya akan mengedit posting Anda dengan patokan; Saya harap Anda, tetap saja, tidak keberatan)Vectorize()
sebagai contoh) menggunakannya juga dalam pengertian UI. Saya pikir banyak ketidaksepakatan di utas ini disebabkan oleh penggunaan satu istilah untuk dua konsep yang terpisah tetapi terkait.