programing

R의 적용 가족은 통사적인 설탕 이상입니까?

skycolor 2023. 6. 11. 10:31
반응형

R의 적용 가족은 통사적인 설탕 이상입니까?

...실행 시간 및/또는 메모리가 부족합니다.

이것이 사실이 아닌 경우 코드 스니펫으로 증명합니다.벡터화에 의한 속도 향상은 계산되지 않습니다.은 속도향상와합야니다서음에다은에서 나와야 .apply(tapply,sapply...) 그 자체입니다.

apply는 다른 R 함 는 다 른 함 에 비 향 예 제 지 하 공 습 않 니 다 을 능 성 수 된 상 의 해 수 프 루 :for) 이에 대한 한 가지 예외는 다음과 같습니다.lapply이는 R보다 C 코드에서 더 많은 작업을 수행하기 때문에 조금 더 빠를 수 있습니다(이 예에 대한 질문 참조).

그러나 일반적으로 성능아닌 명확성을 위해 적용 기능을 사용해야 합니다.

여기에 R을 사용한 기능 프로그래밍과 관련하여 중요한 구별인 기능을 적용하면 부작용없다는 을 추가하고 싶습니다.이 값은 다음을 사용하여 재정의할 수 있습니다.assign또는<<-하지만 그것은 매우 위험할 수 있습니다.변수의 상태가 기록에 따라 달라지기 때문에 부작용으로 인해 프로그램을 이해하기가 더 어렵습니다.

편집:

피보나치 시퀀스를 재귀적으로 계산하는 간단한 예를 사용하여 이 점을 강조합니다. 정확한 측정을 위해 여러 번 실행할 수 있지만 중요한 것은 어떤 방법도 성능이 크게 다르다는 것입니다.

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 

편집 2:

)에 으로 R("rpvm, rmpi, snow")을 합니다.apply가족의 기능 (심지어.foreach패키지는 이름에도 불구하고 기본적으로 동일합니다.에 여기간예있다습니가의 간단한 sapply에서 합니다.snow:

library(snow)
cl <- makeSOCKcluster(c("localhost","localhost"))
parSapply(cl, 1:20, get("+"), 3)

이 예에서는 추가 소프트웨어를 설치할 필요가 없는 소켓 클러스터를 사용합니다. 그렇지 않으면 PVM 또는 MPI와 같은 것이 필요합니다(Tierney의 클러스터링 페이지 참조). snow에는 다음과 같은 적용 기능이 있습니다.

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, ...)

은 말이 됩니다.apply기능은 부작용없으므로 병렬 실행에 사용해야 합니다.변수 값을 변경하는 경우for루프, 전체적으로 설정됩니다.다 면에, 모두.apply 사항은 호출에 할 수 있습니다(단, 한).assign또는<<-부작용을 도입할 수 있는 경우).특히 병렬 실행을 처리할 때는 로컬 변수와 글로벌 변수에 주의하는 것이 중요하다는 것은 말할 필요도 없습니다.

편집:

다음은 사이의 차이를 보여주는 간단한 예입니다.for그리고.*apply부작용에 관한 한:

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에서는 위상환변것는은에 변경됩니다.for하지만 아닙니다.*apply.

둘 이상의 요인 그룹을 기반으로 평균을 얻기 위해 루프에 둥지를 틀어야 하는 경우와 같이 속도 향상이 상당할 수 있습니다.여기에는 정확히 동일한 결과를 제공하는 두 가지 접근 방식이 있습니다.

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)

둘 다 정확히 동일한 결과를 제공하며, 평균과 명명된 행 및 열이 있는 5 x 10 행렬입니다.하지만 :

> 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 

여기 있습니다.내가 뭘 이겼어요? ;-)

...그리고 방금 다른 곳에서 썼듯이, vapply는 당신의 친구입니다! ...sapply와 비슷하지만, 반환 값 유형을 지정하면 훨씬 더 빨라집니다.

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 

2020년 1월 1일 업데이트:

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 루프를 값을 반환하는 패밀리 함수를 적용하여 부당하게 비교합니다.여기 요점을 강조하는 약간 다른 예가 있습니다.

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 

만약 당신이 결과를 저장할 계획이라면 가족 기능을 적용하는 것은 통사적인 설탕 이상일 수 있습니다.

(z의 단순한 언리스트는 0.2초에 불과하므로 래플리가 훨씬 빠릅니다.for 루프에서 z를 초기화하는 것은 매우 빠릅니다. 6번의 실행 중 마지막 5번의 평균을 제공하여 시스템 외부로 이동하기 때문입니다.시간은 사물에 거의 영향을 미치지 않을 것입니다.)

그러나 한 가지 더 주목해야 할 것은 성능, 명확성 또는 부작용의 부족과 관계없이 가족 기능 적용을 사용해야 하는 또 다른 이유가 있다는 것입니다.for루프는 일반적으로 루프 내에 가능한 한 많이 넣는 것을 촉진합니다.각 루프에는 정보를 저장하기 위한 변수 설정이 필요하기 때문입니다(다른 가능한 작업 중).적용 문은 반대로 편향되는 경향이 있습니다.데이터에 대해 여러 작업을 수행하려는 경우가 종종 있으며, 그 중 여러 작업은 벡터화될 수 있지만 일부 작업은 벡터화되지 않을 수도 있습니다.R에서는 다른 언어와 달리 적용 문(또는 함수의 벡터화 버전)에서 벡터화되지 않은 연산과 실제 벡터 연산으로 벡터화된 연산을 분리하여 실행하는 것이 가장 좋습니다.이로 인해 성능이 크게 향상되는 경우가 많습니다.

Joris Meys의 예를 들어, 전통적인 for 루프를 편리한 R 함수로 대체하면 특수한 기능 없이 유사한 속도 향상을 위해 보다 R 친화적인 방식으로 코드를 작성하는 효율성을 보여줄 수 있습니다.

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

이것은 결국 그것보다 훨씬 더 빠릅니다.for와 된 최적화 기능보다 .tapply기능.그래서가 아닙니다.vapply보다 훨씬 더 빠릅니다.for루프의 각 반복에서 하나의 작업만 수행하기 때문입니다.이 코드에서는 다른 모든 것이 벡터화됩니다. 메이스의 인 조리스 메이스에서.for각 반복에서 루프(7?) 작업이 많이 발생하고 실행하기 위한 설정이 상당히 많습니다.또한 이것이 그것보다 얼마나 더 콤팩트한지도 참고하세요.for판본

때 벡의부집위함에수때적용할를합분,tapplyfor 루프보다 훨씬 빠를 수 있습니다.예:

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그러나 대부분의 경우 속도 증가를 제공하지 않으며 경우에 따라 속도가 훨씬 더 느릴 수 있습니다.

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

하지만 이런 상황을 대비해서 우리는colSums그리고.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

언급URL : https://stackoverflow.com/questions/2275896/is-rs-apply-family-more-than-syntactic-sugar

반응형