R Language
제어 흐름 구조
수색…
비고
for 루프는 도메인에서 태스크 또는 태스크 세트를 반복하기위한 플로우 제어 메소드입니다. for 루프의 핵심 구조는 다음과 같습니다.
for ( [index] in [domain]){
[body]
}
어디에
-
[index]
는 루프의 각 반복에 대해[domain]
값 하나만 사용합니다. -
[domain]
은 반복 할 값의 벡터입니다. -
[body]
은 각 반복에 적용 할 지침 집합입니다.
간단한 예로, for 루프를 사용하여 값 벡터의 누적 합계를 얻는 것을 고려해보십시오.
x <- 1:4
cumulative_sum <- 0
for (i in x){
cumulative_sum <- cumulative_sum + x[i]
}
cumulative_sum
For 루프의 구조 최적화
For 루프는 반복 할 작업을 개념화하고 실행하는 데 유용 할 수 있습니다. 그러나 신중하게 작성되지 않은 경우, apply
되는 함수 계열의 기본 사용과 비교할 때 실행 속도가 매우 느릴 수 있습니다. 그럼에도 불구하고 루프를 최적화하기 위해 for 루프 생성에 포함 할 수있는 몇 가지 요소가 있습니다. 대부분의 경우, for 루프를 잘 구축하면 계산 기능이 적용 기능에 매우 근접합니다.
'적절히 구성된'for 루프는 핵심 구조를 기반으로하며 루프의 각 반복을 캡처 할 객체를 선언하는 문을 포함합니다. 이 객체는 클래스와 길이를 선언해야합니다.
[output] <- [vector_of_length]
for ([index] in [length_safe_domain]){
[output][index] <- [body]
}
예를 들어 숫자 벡터의 각 값을 제곱하기위한 루프를 작성해 보겠습니다 (이 예제는 설명을위한 간단한 예입니다.이 작업을 완료하는 '올바른'방법은 x_squared <- x^2
).
x <- 1:100
x_squared <- vector("numeric", length = length(x))
for (i in seq_along(x)){
x_squared[i] <- x[i]^2
}
다시, 출력 x_squared
대한 소켓을 먼저 선언하고 x
와 길이가 같은 "numeric"클래스를 부여 x_squared
하십시오. 또한 seq_along
함수를 사용하여 "길이 안전 도메인"을 선언했습니다. seq_along
은 for 루프에서 사용하기에 적합한 객체에 대한 인덱스의 벡터를 생성합니다. 직관적으로 사용할 수 있지만 for (i in 1:length(x))
, x
길이가 0 인 경우 루프는 1:0
도메인에서 반복을 시도하여 오류가 발생합니다 (0 번째 인덱스는 R에서 정의되지 않음). ).
리셉터클 객체와 길이 안전 영역은 apply
함수 계열에 의해 내부적으로 처리되며 사용자는 for 루프 대신 apply
방법을 가능한 많이 채택하는 것이 좋습니다. 그러나 적절히 구성된 경우 for 루프는 효율성 저하를 최소화하면서 코드 명확성을 제공 할 수 있습니다.
루프 용 벡터화
For 루프는 각 반복 내에서 완료해야하는 작업을 개념화하는 데 유용한 도구가 될 수 있습니다. 루프가 완전히 개발되고 개념화되면 루프를 기능으로 전환하는 것이 좋습니다.
이 예제에서는 mtcars
데이터 세트의 각 열의 평균을 계산하는 for 루프를 개발합니다 (다시 colMeans
함수를 통해 수행 할 수있는 간단한 예제).
column_mean_loop <- vector("numeric", length(mtcars))
for (k in seq_along(mtcars)){
column_mean_loop[k] <- mean(mtcars[[k]])
}
for 루프는 함수의 루프 본문을 다시 작성하여 적용 함수로 변환 할 수 있습니다.
col_mean_fn <- function(x) mean(x)
column_mean_apply <- vapply(mtcars, col_mean_fn, numeric(1))
결과를 비교하려면 다음과 같이하십시오.
identical(column_mean_loop,
unname(column_mean_apply)) #* vapply added names to the elements
#* remove them for comparison
벡터화 된 형식의 장점은 몇 줄의 코드를 제거 할 수 있다는 것입니다. 출력 객체의 길이와 유형을 결정하고 길이 안전 영역을 반복하는 메커니즘은 apply 함수에 의해 처리됩니다. 또한 적용 함수는 루프보다 약간 빠릅니다. 속도의 차이는 반복 횟수와 신체의 복잡성에 따라 인간의 용어로는 무시해도됩니다.
기본 루프 구축
이 예에서는 데이터 프레임의 각 열에 대한 제곱의 편차를 계산합니다 (이 경우 mtcars
.
옵션 A : 정수 인덱스
squared_deviance <- vector("list", length(mtcars))
for (i in seq_along(mtcars)){
squared_deviance[[i]] <- (mtcars[[i]] - mean(mtcars[[i]]))^2
}
squared_deviance
는 예상대로 11 개 요소 목록입니다.
class(squared_deviance)
length(squared_deviance)
옵션 B : 문자 색인
squared_deviance <- vector("list", length(mtcars))
Squared_deviance <- setNames(squared_deviance, names(mtcars))
for (k in names(mtcars)){
squared_deviance[[k]] <- (mtcars[[k]] - mean(mtcars[[k]]))^2
}
결과로 data.frame
을 원하면 어떻게 될까요? 음, 목록을 다른 객체로 변환하는 많은 옵션이 있습니다. 그러나이 경우 가장 간단한 방법은 결과를 data.frame
에 저장하는 것 for
.
squared_deviance <- mtcars #copy the original
squared_deviance[TRUE]<-NA #replace with NA or do squared_deviance[,]<-NA
for (i in seq_along(mtcars)){
squared_deviance[[i]] <- (mtcars[[i]] - mean(mtcars[[i]]))^2
}
dim(squared_deviance)
[1] 32 11
문자 옵션 (B)을 사용하더라도 결과는 같은 이벤트가됩니다.
For 루프의 최적 구성
루프 구축에 좋은 효과를 설명하기 위해 네 가지 방법으로 각 열의 평균을 계산합니다.
- for 최적화 된 for 루프 사용하기
- for 루프 용으로 잘 최적화 된 사용
-
*apply
함수 패밀리 사용 -
colMeans
함수 사용하기
각 옵션은 코드로 표시됩니다. 각 옵션을 실행하기위한 계산 시간의 비교가 표시됩니다. 마지막으로 차이점에 대한 논의가 주어질 것입니다.
루프 최적화가 제대로 이루어지지 않았습니다.
column_mean_poor <- NULL
for (i in 1:length(mtcars)){
column_mean_poor[i] <- mean(mtcars[[i]])
}
잘 최적화 된 루프
column_mean_optimal <- vector("numeric", length(mtcars))
for (i in seq_along(mtcars)){
column_mean_optimal <- mean(mtcars[[i]])
}
vapply
함수
column_mean_vapply <- vapply(mtcars, mean, numeric(1))
colMeans
함수
column_mean_colMeans <- colMeans(mtcars)
효율성 비교
이 네 가지 접근 방식을 벤치마킹 한 결과는 다음과 같습니다 (코드가 표시되지 않음)
Unit: microseconds
expr min lq mean median uq max neval cld
poor 240.986 262.0820 287.1125 275.8160 307.2485 442.609 100 d
optimal 220.313 237.4455 258.8426 247.0735 280.9130 362.469 100 c
vapply 107.042 109.7320 124.4715 113.4130 132.6695 202.473 100 a
colMeans 155.183 161.6955 180.2067 175.0045 194.2605 259.958 100 b
최적화 된 for
루프는 잘못 작성된 for
루프를 가려 냈습니다. 잘못 구성된 for 루프는 출력 오브젝트의 길이를 계속 증가시키고 길이의 각 변경시 오브젝트의 클래스를 재평가합니다.
이러한 오버 헤드 부담 중 일부는 루프를 시작하기 전에 출력 객체 유형과 길이를 선언하여 최적화 된 for 루프에 의해 제거됩니다.
그러나이 예에서 vapply
함수를 사용하면 계산 효율이 두 배가됩니다. 그 이유는 결과에 숫자가 있어야한다고 말했기 때문입니다 (하나의 결과가 숫자가 아닌 경우 오류가 반환 됨).
의 사용 colMeans
기능은보다 터치 느린 vapply
기능. 이 차이는 약간의 오차 수행 검사에 기인 colMeans
주로에 as.matrix
(변환 때문에 mtcars
A는 data.frame
수행되지 않은) vapply
기능.
다른 루핑 구문 : while 및 repeat
R은 필요한 반복 작업이 불확실한 상황에서 일반적으로 사용되는 while
및 repeat
라는 두 개의 추가 루핑 구문을 제공합니다.
while
루프
while
루프의 일반적인 형식은 다음과 같습니다.
while (condition) {
## do something
## in loop body
}
condition
은 루프 본문에 들어가기 전에 평가됩니다. condition
이 TRUE
평가되면 루프 본문 내부의 코드가 실행되고 condition
이 FALSE
평가 될 때까지이 프로세스가 반복됩니다 (또는 break
문에 도달하면 아래 참조). for
루프와 달리 while
루프가 변수를 사용하여 증분 반복을 수행하면 변수는 미리 선언되고 초기화되어야하며 루프 본문 내에서 업데이트되어야합니다. 예를 들어 다음 루프는 동일한 작업을 수행합니다.
for (i in 0:4) {
cat(i, "\n")
}
# 0
# 1
# 2
# 3
# 4
i <- 0
while (i < 5) {
cat(i, "\n")
i <- i + 1
}
# 0
# 1
# 2
# 3
# 4
위의 while
루프에서는 무한 루프를 방지하기 위해 i <- i + 1
이 필요합니다.
또한 루프 본문에서 break
하라는 호출로 while
루프를 종료 할 수 있습니다.
iter <- 0
while (TRUE) {
if (runif(1) < 0.25) {
break
} else {
iter <- iter + 1
}
}
iter
#[1] 4
이 예제에서 condition
은 항상 TRUE
이므로 루프를 종료하는 유일한 방법은 본문 내부에서 break
호출하는 것입니다. iter
의 최종 값은이 예제가 실행될 때 PRNG의 상태에 따라 달라지며 코드가 실행될 때마다 다른 결과 (본질적으로)가 생성되어야합니다.
repeat
루프
repeat
구조는 본질적으로 while (TRUE) { ## something }
과 동일하며 다음 형식을 갖습니다.
repeat ({
## do something
## in loop body
})
여분의 {}
는 필수는 아니지만 ()
는 필수입니다. repeat
를 사용하여 이전 예제를 다시 작성하고,
iter <- 0
repeat ({
if (runif(1) < 0.25) {
break
} else {
iter <- iter + 1
}
})
iter
#[1] 2
break
더 많은 break
break
는 즉시 둘러싸는 루프 만 종료 한다는 것을 알아 두는 것이 중요합니다. 즉, 다음은 무한 루프입니다.
while (TRUE) {
while (TRUE) {
cat("inner loop\n")
break
}
cat("outer loop\n")
}
그러나 약간의 창의력을 발휘하면 중첩 된 루프에서 완전히 벗어날 수 있습니다. 예를 들어, 현재 상태에서 무한대로 반복되는 다음 표현식을 고려하십시오.
while (TRUE) {
cat("outer loop body\n")
while (TRUE) {
cat("inner loop body\n")
x <- runif(1)
if (x < .3) {
break
} else {
cat(sprintf("x is %.5f\n", x))
}
}
}
하나의 가능성은 달리 것을 인식하는 것입니다 break
의 return
식을 둘러싸는 루프의 여러 수준에서 제어를 리턴 할 수있는 능력을 가지고있다. 그러나 return
은 함수 내에서만 사용되기 때문에 break
를 return()
대체 할 수는 없지만 전체 표현식을 익명 함수로 래핑해야합니다.
(function() {
while (TRUE) {
cat("outer loop body\n")
while (TRUE) {
cat("inner loop body\n")
x <- runif(1)
if (x < .3) {
return()
} else {
cat(sprintf("x is %.5f\n", x))
}
}
}
})()
또는 식 앞에 더미 변수 ( exit
)를 만들고 종료 준비가되면 내부 루프에서 <<-
를 통해 활성화 할 수 있습니다.
exit <- FALSE
while (TRUE) {
cat("outer loop body\n")
while (TRUE) {
cat("inner loop body\n")
x <- runif(1)
if (x < .3) {
exit <<- TRUE
break
} else {
cat(sprintf("x is %.5f\n", x))
}
}
if (exit) break
}