R에서 루프 작동 속도 향상
R에서 큰 성능 문제가 data.frame
있습니다. 객체 를 반복하는 함수를 작성했습니다 . 단순히 a에 새로운 열을 추가하고 data.frame
무언가를 축적합니다. (간단한 조작). 는 data.frame
대략 850K 행이 있습니다. 내 PC는 여전히 작동하고 있으며 (현재 약 10 시간) 런타임에 대해서는 전혀 모릅니다.
dayloop2 <- function(temp){
for (i in 1:nrow(temp)){
temp[i,10] <- i
if (i > 1) {
if ((temp[i,6] == temp[i-1,6]) & (temp[i,3] == temp[i-1,3])) {
temp[i,10] <- temp[i,9] + temp[i-1,10]
} else {
temp[i,10] <- temp[i,9]
}
} else {
temp[i,10] <- temp[i,9]
}
}
names(temp)[names(temp) == "V10"] <- "Kumm."
return(temp)
}
이 작업 속도를 높이는 방법에 대한 아이디어가 있습니까?
가장 큰 문제와 비효율의 뿌리는 data.frame을 인덱싱하는 것 temp[,]
입니다.
이것을 최대한 피하십시오. 나는 당신의 기능을 가지고, 색인 생성을 변경하고 여기 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)
}
보시다시피 res
결과를 수집하는 벡터 를 만듭니다 . 결국 나는 그것을 추가하고 data.frame
이름을 엉망으로 만들 필요가 없습니다. 그래서 얼마나 낫습니까?
나는 각 기능을 실행 data.frame
에 nrow
1,000에서 1,000 10,000과 함께 시간을 측정system.time
X <- as.data.frame(matrix(sample(1:10, n*9, TRUE), n, 9))
system.time(dayloop2(X))
결과는
버전이에서 지수에 의존한다는 것을 알 수 있습니다 nrow(X)
. 수정 된 버전에는 선형 관계가 있으며 간단한 lm
모델에서는 850,000 개의 행 계산에 6 분 10 초가 소요될 것으로 예측합니다.
벡터화의 힘
Shane과 Calimo의 답변에 따르면 벡터화는 성능 향상의 열쇠입니다. 코드에서 루프 외부로 이동할 수 있습니다.
- 조절
- 결과의 초기화 (
temp[i,9]
)
이 코드로 연결됩니다
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)
}
이 함수의 결과를 nrow
10,000 ~ 100,000 x 10,000으로 비교합니다.
튜닝 튜닝
또 다른 조정은 루프 인덱싱 temp[i,9]
에서 res[i]
(i 번째 루프 반복에서와 동일하게) 변경하는 것입니다. 벡터 인덱싱과 a 인덱싱의 차이점이 다시 data.frame
있습니다.
두 번째로 : 루프를 볼 때 모든 루프를 반복 할 필요는 없지만 i
조건에 맞는 루프 만 필요하다는 것을 알 수 있습니다.
그래서 우리는 간다
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)
}
얻는 성능은 데이터 구조에 따라 다릅니다. 정확하게- TRUE
조건 값의 백분율 . 시뮬레이션 된 데이터의 경우 1 초 미만의 850,000 행에 계산 시간이 걸립니다.
나는 당신이 더 나아갈 수 있기를 원합니다. 나는 할 수있는 적어도 두 가지를 봅니다.
C
조건부 적산을 수행 하는 코드 작성데이터 최대 시퀀스가 크지 않다는 것을 알고 있다면 루프를 벡터화로 변경할 수 있습니다.
while (any(cond)) { indx <- c(FALSE, cond[-1] & !cond[-n]) res[indx] <- res[indx] + res[which(indx)-1] cond[indx] <- FALSE }
시뮬레이션 및 수치에 사용되는 코드는 GitHub에서 사용할 수 있습니다 .
R 코드 속도를 높이기위한 일반적인 전략
먼저 느린 부분이 실제로 어디에 있는지 파악 하십시오. 느리게 실행되지 않는 코드를 최적화 할 필요가 없습니다. 소량의 코드의 경우 단순히 코드를 통해 생각하면 효과가 있습니다. 실패하면 RProf 및 유사한 프로파일 링 도구가 도움이 될 수 있습니다.
병목 현상을 파악한 후 원하는 작업을 수행 하기 위한 보다 효율적인 알고리즘 에 대해 생각해보십시오 . 계산은 가능하면 한 번만 실행해야합니다.
- 반복적으로 재 계산하는 대신 결과를 저장하고 액세스
- 루프에서 비 루프 종속 계산 수행
- 필요하지 않은 계산을 피하십시오 (예 : 고정 검색에 정규식을 사용하지 마십시오 )
보다 효율적인 기능을 사용하면 중간 또는 큰 속도 이득을 얻을 수 있습니다. 예를 들어, paste0
작은 효율 이득을 생성하지만 .colSums()
그 친족들은 다소 더 뚜렷한 이득을 생성합니다. mean
이다 특히 느린 .
그런 다음 특히 일반적인 문제를 피할 수 있습니다 .
cbind
정말 빨리 속도를 늦출 것입니다- 데이터 구조를 초기화 한 다음 매번 확장하는 대신 채우십시오 .
- 사전 할당을 사용하더라도 값별 접근 방식 대신 참조 별 접근 방식으로 전환 할 수 있지만 번거롭지 않을 수 있습니다.
- 더 많은 함정을 피하기 위해 R Inferno 를 살펴보십시오 .
항상 도움이 될 수는 있지만 더 나은 벡터화를 시도하십시오 . 이와 관련, 본질적으로 벡터화 명령 좋아 ifelse
, diff
그리고 같은이보다 더 향상을 제공 할 것입니다 apply
(잘 작성 루프를 통해 더 속도 향상에 조금 제공) 명령의 가족.
R 함수에 더 많은 정보를 제공 할 수도 있습니다 . 예를 들어, vapply
대신을sapply
사용 colClasses
하고 텍스트 기반 데이터를 읽을 때 지정하십시오 . 얼마나 많은 추측을 제거 하느냐에 따라 속도 게인이 달라집니다.
다음으로 최적화 된 패키지를 고려 하십시오 . data.table
패키지는 데이터 조작 및 대량의 데이터 읽기 ( fread
) 에서 사용이 가능한 경우 엄청난 속도 향상을 가져올 수 있습니다 .
다음으로 보다 효율적인 R 호출 방법을 통해 속도 향상을 시도하십시오 .
- R 스크립트를 컴파일하십시오. 또는 적시에 컴파일 하기 위해 함께
Ra
및jit
패키지를 함께 사용하십시오 (Dirk는 이 프리젠 테이션에 예가 있습니다 ). - 최적화 된 BLAS를 사용하고 있는지 확인하십시오. 이는 전반적으로 속도 향상을 제공합니다. 솔직히 R이 설치시 가장 효율적인 라이브러리를 자동으로 사용하지 않는 것은 부끄러운 일입니다. Revolution R이 그들이 한 작업을 전체 커뮤니티에 기여할 수 있기를 바랍니다.
- Radford Neal은 많은 최적화를 수행했으며, 그 중 일부는 R Core에 채택되었고 다른 일부는 pqR 로 분기되었습니다 .
마지막으로 위의 모든 사항이 여전히 필요한만큼 빠르지 않으면 느린 코드 스 니펫을 위해 더 빠른 언어 로 이동해야 할 수도 있습니다 . Rcpp
그리고 inline
여기 의 조합은 알고리즘의 가장 느린 부분 만 C ++ 코드로 쉽게 대체 할 수있게합니다. 예를 들어 여기에 대한 첫 번째 시도가 있으며 , 고도로 최적화 된 R 솔루션도 날려 버립니다.
이 모든 후에도 여전히 문제가 발생하면 더 많은 컴퓨팅 능력이 필요합니다. 병렬화 ( http://cran.r-project.org/web/views/HighPerformanceComputing.html ) 또는 GPU 기반 솔루션 ( gpu-tools
)을 살펴보십시오 .
다른 지침에 대한 링크
for
루프 를 사용 하는 경우 R이 C 또는 Java 인 것처럼 R을 코딩 할 가능성이 높습니다. 제대로 벡터화 된 R 코드는 매우 빠릅니다.
예를 들어 다음 두 간단한 비트 코드를 사용하여 10,000 개의 정수 목록을 순서대로 생성하십시오.
첫 번째 코드 예제는 전통적인 코딩 패러다임을 사용하여 루프를 코딩하는 방법입니다. 완료하는 데 28 초가 걸립니다
system.time({
a <- NULL
for(i in 1:1e5)a[i] <- i
})
user system elapsed
28.36 0.07 28.61
사전 할당 메모리의 간단한 동작으로 거의 100 배나 개선 할 수 있습니다.
system.time({
a <- rep(1, 1e5)
for(i in 1:1e5)a[i] <- i
})
user system elapsed
0.30 0.00 0.29
그러나 콜론 연산자를 사용하여 기본 R 벡터 연산을 사용하면 :
이 연산은 사실상 즉각적입니다.
system.time(a <- 1:1e5)
user system elapsed
0 0 0
인덱스 나 중첩 된 ifelse()
명령문 을 사용하여 루프를 건너 뛰면 훨씬 빨라질 수 있습니다 .
idx <- 1:nrow(temp)
temp[,10] <- idx
idx1 <- c(FALSE, (temp[-nrow(temp),6] == temp[-1,6]) & (temp[-nrow(temp),3] == temp[-1,3]))
temp[idx1,10] <- temp[idx1,9] + temp[which(idx1)-1,10]
temp[!idx1,10] <- temp[!idx1,9]
temp[1,10] <- temp[1,9]
names(temp)[names(temp) == "V10"] <- "Kumm."
Ari가 그의 대답 끝에 언급했듯이 Rcpp
및 inline
패키지를 사용하면 작업을 매우 빠르게 수행 할 수 있습니다. 예를 들어이 inline
코드를 사용해보십시오 (경고 : 테스트되지 않음).
body <- 'Rcpp::NumericMatrix nm(temp);
int nrtemp = Rccp::as<int>(nrt);
for (int i = 0; i < nrtemp; ++i) {
temp(i, 9) = i
if (i > 1) {
if ((temp(i, 5) == temp(i - 1, 5) && temp(i, 2) == temp(i - 1, 2) {
temp(i, 9) = temp(i, 8) + temp(i - 1, 9)
} else {
temp(i, 9) = temp(i, 8)
}
} else {
temp(i, 9) = temp(i, 8)
}
return Rcpp::wrap(nm);
'
settings <- getPlugin("Rcpp")
# settings$env$PKG_CXXFLAGS <- paste("-I", getwd(), sep="") if you want to inc files in wd
dayloop <- cxxfunction(signature(nrt="numeric", temp="numeric"), body-body,
plugin="Rcpp", settings=settings, cppargs="-I/usr/include")
dayloop2 <- function(temp) {
# extract a numeric matrix from temp, put it in tmp
nc <- ncol(temp)
nm <- dayloop(nc, temp)
names(temp)[names(temp) == "V10"] <- "Kumm."
return(temp)
}
#include
매개 변수를 전달하는 비슷한 절차 가 있습니다.
inc <- '#include <header.h>
로 cxxfunction에 include=inc
. 정말 멋진 점은 모든 링크 및 컴파일 작업을 수행하므로 프로토 타이핑이 정말 빠르다는 것입니다.
면책 조항 : tmp 클래스가 숫자 행렬이어야하며 숫자 행렬이나 다른 것이 아니라는 것을 확신하지 못합니다. 그러나 나는 대부분 확신합니다.
Edit: if you still need more speed after this, OpenMP is a parallelization facility good for C++
. I haven't tried using it from inline
, but it should work. The idea would be to, in the case of n
cores, have loop iteration k
be carried out by k % n
. A suitable introduction is found in Matloff's The Art of R Programming, available here, in chapter 16, Resorting to C.
I dislike rewriting code... Also of course ifelse and lapply are better options but sometimes it is difficult to make that fit.
Frequently I use data.frames as one would use lists such as df$var[i]
Here is a made up example:
nrow=function(x){ ##required as I use nrow at times.
if(class(x)=='list') {
length(x[[names(x)[1]]])
}else{
base::nrow(x)
}
}
system.time({
d=data.frame(seq=1:10000,r=rnorm(10000))
d$foo=d$r
d$seq=1:5
mark=NA
for(i in 1:nrow(d)){
if(d$seq[i]==1) mark=d$r[i]
d$foo[i]=mark
}
})
system.time({
d=data.frame(seq=1:10000,r=rnorm(10000))
d$foo=d$r
d$seq=1:5
d=as.list(d) #become a list
mark=NA
for(i in 1:nrow(d)){
if(d$seq[i]==1) mark=d$r[i]
d$foo[i]=mark
}
d=as.data.frame(d) #revert back to data.frame
})
data.frame version:
user system elapsed
0.53 0.00 0.53
list version:
user system elapsed
0.04 0.00 0.03
17x times faster to use a list of vectors than a data.frame.
Any comments on why internally data.frames are so slow in this regard? One would think they operate like lists...
For even faster code do this class(d)='list'
instead of d=as.list(d)
and class(d)='data.frame'
system.time({
d=data.frame(seq=1:10000,r=rnorm(10000))
d$foo=d$r
d$seq=1:5
class(d)='list'
mark=NA
for(i in 1:nrow(d)){
if(d$seq[i]==1) mark=d$r[i]
d$foo[i]=mark
}
class(d)='data.frame'
})
head(d)
The answers here are great. One minor aspect not covered is that the question states "My PC is still working (about 10h now) and I have no idea about the runtime". I always put in the following code into loops when developing to get a feel for how changes seem to affect the speed and also for monitoring how long it will take to complete.
dayloop2 <- function(temp){
for (i in 1:nrow(temp)){
cat(round(i/nrow(temp)*100,2),"% \r") # prints the percentage complete in realtime.
# do stuff
}
return(blah)
}
Works with lapply as well.
dayloop2 <- function(temp){
temp <- lapply(1:nrow(temp), function(i) {
cat(round(i/nrow(temp)*100,2),"% \r")
#do stuff
})
return(temp)
}
If the function within the loop is quite fast but the number of loops is large then consider just printing every so often as printing to the console itself has an overhead. e.g.
dayloop2 <- function(temp){
for (i in 1:nrow(temp)){
if(i %% 100 == 0) cat(round(i/nrow(temp)*100,2),"% \r") # prints every 100 times through the loop
# do stuff
}
return(temp)
}
In R, you can often speed-up loop processing by using the apply
family functions (in your case, it would probably be replicate
). Have a look at the plyr
package that provides progress bars.
Another option is to avoid loops altogether and replace them with vectorized arithmetics. I'm not sure exactly what you are doing, but you can probably apply your function to all rows at once:
temp[1:nrow(temp), 10] <- temp[1:nrow(temp), 9] + temp[0:(nrow(temp)-1), 10]
This will be much much faster, and then you can filter the rows with your condition:
cond.i <- (temp[i, 6] == temp[i-1, 6]) & (temp[i, 3] == temp[i-1, 3])
temp[cond.i, 10] <- temp[cond.i, 9]
Vectorized arithmetics requires more time and thinking about the problem, but then you can sometimes save several orders of magnitude in execution time.
Processing with data.table
is a viable option:
n <- 1000000
df <- as.data.frame(matrix(sample(1:10, n*9, TRUE), n, 9))
colnames(df) <- paste("col", 1:9, sep = "")
library(data.table)
dayloop2.dt <- function(df) {
dt <- data.table(df)
dt[, Kumm. := {
res <- .I;
ifelse (res > 1,
ifelse ((col6 == shift(col6, fill = 0)) & (col3 == shift(col3, fill = 0)) ,
res <- col9 + shift(res)
, # else
res <- col9
)
, # else
res <- col9
)
}
,]
res <- data.frame(dt)
return (res)
}
res <- dayloop2.dt(df)
m <- microbenchmark(dayloop2.dt(df), times = 100)
#Unit: milliseconds
# expr min lq mean median uq max neval
#dayloop2.dt(df) 436.4467 441.02076 578.7126 503.9874 575.9534 966.1042 10
If you ignore the possible gains from conditions filtering, it is very fast. Obviously, if you can do the calculation on the subset of data, it helps.
참고 URL : https://stackoverflow.com/questions/2908822/speed-up-the-loop-operation-in-r
'Programing' 카테고리의 다른 글
발리 타임 아웃 시간 변경 (0) | 2020.05.17 |
---|---|
querystring없이 URL 가져 오기 (0) | 2020.05.17 |
JSON 문자열을 만들 때 특수 문자를 이스케이프 처리하는 방법은 무엇입니까? (0) | 2020.05.17 |
마우스가 테이블의 행을 넘어갈 때 손으로 커서 변경 (0) | 2020.05.17 |
배경 이미지 위에 반투명 색상 레이어? (0) | 2020.05.17 |