Program :: Iteration with loop

Published by onesixx on

http://r4ds.had.co.nz/iteration.html

 

Intro .

코드 중복(copy&paste, duplication)을 줄이기위해, 우는 Function을 사용하거나  Iteration을 활용한다. 

  •  Function을 사용 :  코드의 반복되는 패턴을 찾아, 쉽게 수정되고 재사용할수 있는 독립적인 한 덩어리로 발췌해 낸다. 
  •  iteration : multiple inputs(다른 컬럼, 다른 데이터셋) 에 대해 같은 작업을 반복하여 수행한다.

iteration의  2개의 패러다임

  • imperative programming
       : 명령어   for loops 와  while loops 사용 => 명확하긴하지만, 장황하다 ( 1.output 2, sequence 3. body)
  • functional programming  :함수
      : 중복된 코드를 발췌하고, loop패턴으로 일반화하여 function을 만든다. 

문서의 흐름

  • For Loops  
  • Map Functions
  • Walk

예제 데이터>

options(digits=2)
#options(scipen=666)
set.seed(1616)
library(tidyverse)
df <- tibble(a=rnorm(10), b=rnorm(10), c=rnorm(10), d=rnorm(10))

rescale01 <- function(x){
  rng <- range(x, na.rm = TRUE)
  (x - rng[1]) / (rng[2] - rng[1])
}

sample(100,10)
rchisq(10,2)
rbeta(10,2,3)
rbinom(10,4,0.5)
rgamma(10,1)
rweibull(10,1)
rhyper(10,3,2,1)

imperative programming

1) For loop 

data

df <- tibble(a=rnorm(10),b=rnorm(10),c=rnorm(10),d=rnorm(10))

Q> df의 각 열의 median을 구하라

방법1.  copy&paste

median(df$a); median(df$b); median(df$c); median(df$d);

방법2-1.  imperative programming ::  for loop 활용

output <- vector("double", ncol(df))  # 1. output
for (i in seq_along(df)) {            # 2. sequence
  output[[i]] <- median(df[[i]])      # 3. body
}
output

seq_along(df)1: length(df)의 safe version 

df2 <- tribble()
1:length(df2)     # wrong
seq_along(df2)    # right

 

방법2-2. imperative programming :: apply함수 활용

sapply(df, median)

 

방법2-3. imperative programming :: map함수 활용

 

2) For loop 의 변형 

아래 4가지 변형에 대해 각각 알아보자

  • (새로운 object를 생성하는 대신) 기존 object를 수정
  • (index 대신) 이름 또는 값에 대해 Looping 한다. 
  • output의 (잘모르는) 길이를 Handling
  • sequences의 (잘모르는)길이를 Handling 

0. Copy& paste로 iteration 구현

### copy&paste
df$a <- rescale01(df$a)
df$b <- rescale01(df$b)
df$c <- rescale01(df$c)
df$d <- rescale01(df$d)

 

1. 기본의 Object 수정하기 (새로운 Object를 생성하지 않고)

 보통 list 나 data.frame를 loop를 통해 수정하게 되는데, 싱글대괄호[가 아니라  대괄호를 두개 [[ 사용한다는것을 주의!!

  • Output:  Output(df$a)이 Input(df$a)과 같으므로, 굳이 정의할 필요가 없다
  • Sequence:  seq_along(df).
  • Body: apply rescale01().
### for-loop
                                 # 1. output (same as the input!)
for (i in seq_along(df)) {       # 2. sequence
  df[[i]] <- rescale01(df[[i]])  # 3. body
}

 

2. (index 대신) name 또는 value에 대해 Looping 한다  ::  Looping patterns

1.  index에 대한 loop :   for (i in seq_along(xs))

값은 이런 방법 x[[i]] 으로 추출했다. 

2. element에 대한 Loop : for (x in xs)                       

효율적으로 ouput을 저장하는것이 어렵기 때문에,  파일을 저장하거나 plotting 하는데 있어서는 꽤 유용한다. 

3. names에 대한 Loop : for (nm in names(xs))           

 x[[nm]]을 통해 value에 구할수 있는 name을 제공하고, 이는 만약 file명이나 plot 제목으로 name을 사용할때 유용하다.
명명된 output을 생성하기위해, 아래와 같이 결과 vector를 명명 해야만 한다. 

results <- vector("list", length(x)) 
names(results) <- names(x)

일반적으로 name과 value 둘다를 추출할수 있는 위치를 제공하는 index를 사용해서 sequence의 반복을 만든다.

for (i in seq_along(x)) { 
    name <- names(x)[[i]] 
    value <- x[[i]] 
}

3. output의 (잘 모르는) 길이 – Handling outputs of unknown length.

예를 들어, random길이를 가진 random 벡터를 시뮬레이션할 때 처럼,  가끔 output의 길이를 모를 때가 있다.
vector를 점진적으로 늘리면서 문제를 해결해보려 할것이다. 

means <- c(0, 1, 2)

output <- double()
for (i in seq_along(means)) {
  n <- sample(100, 1)
  output <- c(output, rnorm(n, means[[i]]))
}
str(output)
#>  num [1:202] 0.912 0.205 2.584 -0.789 0.588 ...

  그러나, 이건 매우 비효율적이다. 각 iteration에서 R은 이전 iteration단계의 모든 데이터를 copy해야만한다. 
기술적으로 말하면, 각 loop에 대해 2차(제곱)을 만들어낸다.  
여기서 3번의 loop에 대해 제곱번 그러니까 3의 제곱 = 9번 실행된다. 

더 좋은 방법은 loop가 한번 돌고나면 single vector에 combine하는 것이다. 
A better solution to save the results in a list, and then combine into a single vector after the loop is done:

means <- c(0, 1, 2)

out <- vector("list", length(means))
for (i in seq_along(means)) {
  n <- sample(100, 1)
  out[[i]] <- rnorm(n, means[[i]])
}
str(out)
#> List of 3
#>  $ : num [1:83] 0.367 1.13 -0.941 0.218 1.415 ...
#>  $ : num [1:21] -0.485 -0.425 2.937 1.688 1.324 ...
#>  $ : num [1:40] 2.34 1.59 2.93 3.84 1.3 ...
str(unlist(out))
#>  num [1:144] 0.367 1.13 -0.941 0.218 1.415 ...

vector의 list 를 flat하게 하기 위해 unlist()를 사용했다. 
더 엄격하게 option을 적용하려면 purrr::flatten_dbl() 사용한다. input이 double형의 List가 아니면 에러가 난다. 

이 패턴은 다른 곳에서도 발생한다. 

  1. You might be generating a long string. Instead of paste()ing together each iteration with the previous, save the output in a character vector and then combine that vector into a single string with paste(output, collapse = “”).

  2. You might be generating a big data frame. Instead of sequentially rbind()ing in each iteration,
    save the output in a list, then use dplyr::bind_rows(output) to combine the output into a single data frame.

Watch out for this pattern.
Whenever you see it, switch to a more complex result object, and then combine in one step at the end

cf) linear, quadratic, cubic, quartic, quintic, sextic   https://en.wikipedia.org/wiki/Degree_of_a_polynomial

4. sequences의 (잘 모르는)길이 – Handling sequences of unknown length.

가끔 input sequence가 얼마나 길지 알지못하는 경우가 있다. simulation에서는 흔한일이다.
예를 들어, 연속해서 3개의 head가 될때까지 loop되길 원한다. for loop로 몇몇의 iteration을 할수 없다. 
대신에 , while loop를 사용한다. for loop와 비슷하지만, Condition과 body만으로 이루어진 
Sometimes you don’t even know how long the input sequence should run for. This is common when doing simulations. For example, you might want to loop until you get three heads in a row. You can’t do that sort of iteration with the for loop. Instead, you can use a while loop. A while loop is simpler than for loop because it only has two components, a condition and a body:

while (condition) {
  # body
}

A while loop is also more general than a for loop, because you can rewrite any for loop as a while loop, but you can’t rewrite every while loop as a for loop:

for (i in seq_along(x)) {
  # body
}

# Equivalent to
i <- 1
while (i <= length(x)) {
  # body
  i <- i + 1 
}

Here’s how we could use a while loop to find how many tries it takes to get three heads in a row:

flip <- function() sample(c("T", "H"), 1)

flips <- 0
nheads <- 0

while (nheads < 3) {
  if (flip() == "H") {
    nheads <- nheads + 1
  } else {
    nheads <- 0
  }
  flips <- flips + 1
}
flips
#> [1] 3

I mention while loops only briefly, because I hardly ever use them. They’re most often used for simulation, which is outside the scope of this book. However, it is good to know they exist so that you’re prepared for problems where the number of iterations is not known in advance.

 

 

 

 

 

Categories: Tidyverse

onesixx

Blog Owner

Leave a Reply

Your email address will not be published.