Masalah terbesar dan akar dari ketidakefektifan adalah pengindeksan data.frame, maksud saya semua baris ini di mana Anda menggunakan temp[,]
.
Cobalah untuk menghindari ini sebanyak mungkin. Saya mengambil fungsi Anda, mengubah pengindeksan dan di sini version_A
dayloop2_A <- function(temp){
res <- numeric(nrow(temp))
for (i in 1:nrow(temp)){
res[i] <- i
if (i > 1) {
if ((temp[i,6] == temp[i-1,6]) & (temp[i,3] == temp[i-1,3])) {
res[i] <- temp[i,9] + res[i-1]
} else {
res[i] <- temp[i,9]
}
} else {
res[i] <- temp[i,9]
}
}
temp$`Kumm.` <- res
return(temp)
}
Seperti yang Anda lihat, saya membuat vektor res
yang mengumpulkan hasil. Pada akhirnya saya menambahkannya data.frame
dan saya tidak perlu dipusingkan dengan nama. Jadi seberapa baik itu?
Saya menjalankan setiap fungsi data.frame
dengan nrow
dari 1.000 hingga 10.000 oleh 1.000 dan mengukur waktu dengansystem.time
X <- as.data.frame(matrix(sample(1:10, n*9, TRUE), n, 9))
system.time(dayloop2(X))
Hasilnya adalah
Anda dapat melihat bahwa versi Anda bergantung secara eksponensial nrow(X)
. Versi modifikasi memiliki hubungan linier, dan lm
model sederhana memprediksi bahwa untuk 850.000 baris perhitungan membutuhkan waktu 6 menit dan 10 detik.
Kekuatan vektorisasi
Seperti yang dinyatakan Shane dan Calimo dalam jawaban mereka, vektorisasi adalah kunci kinerja yang lebih baik. Dari kode Anda, Anda bisa bergerak di luar lingkaran:
- pengkondisian
- inisialisasi hasil (yang
temp[i,9]
)
Ini mengarah ke kode ini
dayloop2_B <- function(temp){
cond <- c(FALSE, (temp[-nrow(temp),6] == temp[-1,6]) & (temp[-nrow(temp),3] == temp[-1,3]))
res <- temp[,9]
for (i in 1:nrow(temp)) {
if (cond[i]) res[i] <- temp[i,9] + res[i-1]
}
temp$`Kumm.` <- res
return(temp)
}
Bandingkan hasil untuk fungsi ini, kali ini nrow
dari 10.000 hingga 100.000 dengan 10.000.
Tuning yang disetel
Tweak lain adalah mengubah pengindeksan loop temp[i,9]
ke res[i]
(yang persis sama di iterasi loop ke-i). Lagi-lagi perbedaan antara pengindeksan vektor dan pengindeksan a data.frame
.
Hal kedua: ketika Anda melihat loop Anda dapat melihat bahwa tidak perlu untuk mengulang semua i
, tetapi hanya untuk orang-orang yang sesuai dengan kondisi.
Jadi di sini kita mulai
dayloop2_D <- function(temp){
cond <- c(FALSE, (temp[-nrow(temp),6] == temp[-1,6]) & (temp[-nrow(temp),3] == temp[-1,3]))
res <- temp[,9]
for (i in (1:nrow(temp))[cond]) {
res[i] <- res[i] + res[i-1]
}
temp$`Kumm.` <- res
return(temp)
}
Kinerja yang Anda peroleh sangat tergantung pada struktur data. Tepatnya - pada persen TRUE
nilai dalam kondisi. Untuk data simulasi saya, dibutuhkan waktu komputasi untuk 850.000 baris di bawah satu detik.
Saya ingin Anda dapat melangkah lebih jauh, saya melihat setidaknya dua hal yang dapat dilakukan:
- menulis sebuah
C
kode untuk melakukan cumsum kondisional
jika Anda tahu bahwa dalam urutan max data Anda tidak besar maka Anda dapat mengubah loop ke vektor sementara, sesuatu seperti
while (any(cond)) {
indx <- c(FALSE, cond[-1] & !cond[-n])
res[indx] <- res[indx] + res[which(indx)-1]
cond[indx] <- FALSE
}
Kode yang digunakan untuk simulasi dan angka tersedia di GitHub .
res = c(1,2,3,4)
dancond
semuaTRUE
, maka hasil akhir harus:1
,3
(penyebab1+2
),6
(penyebab kedua adalah sekarang3
, dan ketiga adalah3
juga),10
(6+4
). Melakukan penjumlahan sederhana yang Anda punya1
,3
,5
,7
.Strategi umum untuk mempercepat kode R.
Pertama, cari tahu di mana bagian yang lambat itu sebenarnya. Tidak perlu mengoptimalkan kode yang tidak berjalan lambat. Untuk sejumlah kecil kode, hanya memikirkannya saja bisa berhasil. Jika itu gagal, RProf dan alat profiling serupa dapat membantu.
Setelah Anda mengetahui hambatannya, pikirkan algoritma yang lebih efisien untuk melakukan apa yang Anda inginkan. Perhitungan seharusnya hanya dijalankan sekali jika memungkinkan, jadi:
Menggunakan fungsi yang lebih efisien dapat menghasilkan peningkatan kecepatan sedang atau besar. Misalnya,
paste0
menghasilkan keuntungan efisiensi kecil tetapi.colSums()
dan kerabatnya menghasilkan keuntungan yang agak lebih nyata.mean
adalah sangat lambat .Maka Anda dapat menghindari beberapa masalah yang sangat umum :
cbind
akan memperlambat Anda dengan sangat cepat.Cobalah untuk vektorisasi yang lebih baik , yang seringkali dapat tetapi tidak selalu membantu. Dalam hal ini, secara inheren perintah vektor seperti
ifelse
,,diff
dan sejenisnya akan memberikan peningkatan lebih dariapply
keluarga perintah (yang memberikan sedikit atau tidak ada peningkatan kecepatan selama loop yang ditulis dengan baik).Anda juga dapat mencoba untuk memberikan informasi lebih kepada fungsi R . Misalnya, gunakan
vapply
daripadasapply
, dan tentukancolClasses
saat membaca dalam data berbasis teks . Keuntungan kecepatan akan bervariasi tergantung pada seberapa banyak tebakan yang Anda hilangkan.Selanjutnya, pertimbangkan paket yang dioptimalkan :
data.table
Paket ini dapat menghasilkan peningkatan kecepatan besar-besaran di mana penggunaannya dimungkinkan, dalam manipulasi data dan membaca sejumlah besar data (fread
).Selanjutnya, cobalah untuk memperoleh kecepatan dengan cara yang lebih efisien untuk menelepon R :
Ra
danjit
dalam konser untuk kompilasi just-in-time (Dirk memiliki contoh dalam presentasi ini ).Dan terakhir, jika semua hal di atas masih membuat Anda tidak secepat yang Anda butuhkan, Anda mungkin perlu pindah ke bahasa yang lebih cepat untuk cuplikan kode yang lambat . Kombinasi dari
Rcpp
dan diinline
sini membuat hanya mengganti bagian paling lambat dari algoritma dengan kode C ++ sangat mudah. Di sini, misalnya, adalah upaya pertama saya untuk melakukannya , dan itu meledak bahkan solusi R yang sangat dioptimalkan.Jika Anda masih menghadapi masalah setelah semua ini, Anda hanya perlu lebih banyak daya komputasi. Lihatlah paralelisasi ( http://cran.r-project.org/web/views/HighPerformanceComputing.html ) atau bahkan solusi berbasis GPU (
gpu-tools
).Tautan ke panduan lain
sumber
Jika Anda menggunakan
for
loop, Anda kemungkinan besar mengkode R seolah-olah itu C atau Java atau yang lainnya. Kode R yang di-vectorised dengan benar sangat cepat.Ambil contoh dua bit kode sederhana ini untuk menghasilkan daftar 10.000 integer secara berurutan:
Contoh kode pertama adalah bagaimana seseorang akan mengkode loop menggunakan paradigma pengkodean tradisional. Diperlukan 28 detik untuk menyelesaikannya
Anda bisa mendapatkan peningkatan hampir 100 kali dengan tindakan sederhana mengalokasikan memori:
Tetapi menggunakan operasi vektor R dasar menggunakan operator usus besar
:
operasi ini hampir seketika:sumber
a[i]
tidak berubah. Tetapisystem.time({a <- NULL; for(i in 1:1e5){a[i] <- 2*i} }); system.time({a <- 1:1e5; for(i in 1:1e5){a[i] <- 2*i} }); system.time({a <- NULL; a <- 2*(1:1e5)})
memiliki hasil yang serupa.rep(1, 1e5)
- timingnya identik.Ini bisa dibuat lebih cepat dengan melewatkan loop dengan menggunakan indeks atau
ifelse()
pernyataan bersarang .sumber
i
nilai -th tergantung padai-1
-th sehingga mereka tidak dapat diatur dengan cara Anda melakukannya (menggunakanwhich()-1
).Saya tidak suka menulis ulang kode ... Juga tentu saja ifelse dan lapply adalah pilihan yang lebih baik tetapi kadang-kadang sulit untuk membuatnya cocok.
Saya sering menggunakan data.frame karena orang akan menggunakan daftar seperti
df$var[i]
Ini adalah contoh buatan:
versi data.frame:
daftar versi:
17x kali lebih cepat untuk menggunakan daftar vektor daripada data.frame.
Ada komentar tentang mengapa data internal frame sangat lambat dalam hal ini? Orang akan berpikir mereka beroperasi seperti daftar ...
Untuk kode yang lebih cepat, lakukan ini
class(d)='list'
alih-alihd=as.list(d)
danclass(d)='data.frame'
sumber
[<-.data.frame
, yang entah bagaimana dipanggil ketika Anda lakukand$foo[i] = mark
dan mungkin akhirnya membuat salinan baru vektor dari kemungkinan seluruh data. Bingkai pada setiap<-
modifikasi. Itu akan membuat pertanyaan menarik pada SO.df$var[i]
notasi melalui[<-.data.frame
fungsi yang sama ? Saya perhatikan itu memang cukup lama. Jika tidak, fungsi apa yang digunakannya?d$foo[i]=mark
akan diterjemahkan secara kasar ke dalamd <- `$<-`(d, 'foo', `[<-`(d$foo, i, mark))
, tetapi dengan beberapa penggunaan variabel sementara.Seperti yang disebutkan Ari di akhir jawabannya, paket
Rcpp
daninline
membuatnya sangat mudah untuk mempercepat. Sebagai contoh, cobainline
kode ini (peringatan: tidak diuji):Ada prosedur serupa untuk memasukkan
#include
hal-hal, di mana Anda baru saja melewati parameterke fungsi cxx, seperti
include=inc
. Apa yang benar-benar keren tentang ini adalah ia melakukan semua penghubungan dan kompilasi untuk Anda, jadi membuat prototipe sangat cepat.Penafian: Saya tidak sepenuhnya yakin bahwa kelas tmp harus numerik dan bukan matriks numerik atau yang lainnya. Tapi saya yakin sebagian besar.
Sunting: jika Anda masih membutuhkan kecepatan lebih setelah ini, OpenMP adalah fasilitas paralelisasi yang baik untuk
C++
. Saya belum pernah mencoba menggunakannyainline
, tetapi seharusnya berhasil. Idenya adalah, dalam kasusn
core, memiliki iterasi loopk
dilakukan olehk % n
. Sebuah pengantar yang cocok ditemukan di Matloff adalah The Art of R Programming , tersedia di sini , dalam bab 16, Beralih ke C .sumber
Jawabannya sangat bagus. Satu aspek kecil yang tidak dicakup adalah bahwa pertanyaan menyatakan " PC saya masih berfungsi (sekitar 10 jam sekarang) dan saya tidak tahu tentang runtime ". Saya selalu memasukkan kode berikut ke dalam loop ketika mengembangkan untuk merasakan bagaimana perubahan tampaknya mempengaruhi kecepatan dan juga untuk memonitor berapa lama waktu yang dibutuhkan untuk menyelesaikannya.
Bekerja dengan lapply juga.
Jika fungsi di dalam loop cukup cepat tetapi jumlah loop besar maka pertimbangkan untuk hanya mencetak sesering mencetak ke konsol itu sendiri memiliki overhead. misalnya
sumber
cat(sprintf("\nNow running... %40s, %s/%s \n", nm[i], i, n))
karena saya biasanya mengulangi hal-hal yang disebutkan (dengan nama dinm
).Dalam R, Anda sering dapat mempercepat pemrosesan loop dengan menggunakan
apply
fungsi keluarga (dalam kasus Anda, itu mungkin akan menjadireplicate
). Lihatlahplyr
paket yang menyediakan bilah kemajuan.Pilihan lain adalah untuk menghindari loop sama sekali dan menggantinya dengan aritmatika vektor. Saya tidak yakin persis apa yang Anda lakukan, tetapi Anda mungkin dapat menerapkan fungsi Anda ke semua baris sekaligus:
Ini akan jauh lebih cepat, dan kemudian Anda dapat menyaring baris dengan kondisi Anda:
Aritmatika vektor membutuhkan lebih banyak waktu dan memikirkan masalah, tetapi terkadang Anda dapat menghemat beberapa kali lipat dalam waktu pelaksanaan.
sumber
Memproses dengan
data.table
adalah opsi yang layak:Jika Anda mengabaikan kemungkinan keuntungan dari penyaringan kondisi, itu sangat cepat. Tentunya, jika Anda bisa melakukan perhitungan pada subset data, ada baiknya.
sumber