반복 처리를 이용한 함수 적용

Author

Mike K Smith

Published

March 6, 2023

데이터의 행, 열 또는 서브셋에 대해 함수를 반복적으로 적용하는 방법을 배웁니다. R의 강점인 벡터화(Vectorization)와 반복문을 효율적으로 사용하는 방법을 살펴봅니다.

목표

  • R의 루프(Loop)와 효율성 이해
  • apply 계열 함수 활용
  • {tidyverse}across()rowwise() 활용
  • {purrr} 패키지를 이용한 리스트 기반 반복 처리

1. 벡터화: 루프가 필수가 아닌 이유

SAS에서는 DATA 단계를 통해 행 단위로 처리하는 방식이 익숙하지만, R은 벡터(Vector) 단위 처리에 최적화되어 있습니다. 예를 들어, 모든 열의 비결측치 개수를 셀 때 R은 벡터 전체를 한 번에 평가합니다.

# 단일 열(SUBJID)의 비결측치 개수 계산
adsl_saf %>%
  summarise(nonmiss = sum(!is.na(SUBJID)))
  nonmiss
1     254

R은 is.na() 함수를 벡터의 모든 요소에 동시에 적용하여 TRUE/FALSE 벡터를 반환하고, sum()은 이를 1/0으로 취급하여 합계를 구합니다. 명시적인 루프 없이도 매우 빠르게 처리됩니다.

2. 명시적 루프 (for loop) 사용하기

모든 열에 대해 같은 작업을 반복해야 한다면 루프를 고려할 수 있습니다. 하지만 R에서 루프를 사용할 때는 결과값을 담을 객체를 미리 생성하는 것이 성능상 매우 중요합니다.

varNames <- names(adsl_saf)
counts <- vector("integer", length(varNames)) # 결과 저장용 객체 미리 생성

for(i in 1:length(varNames)){
  counts[i] <- sum(!is.na(adsl_saf[[varNames[i]]]))
}

names(counts) <- varNames
head(counts)
STUDYID USUBJID  SUBJID  SITEID SITEGR1     ARM 
    254     254     254     254     254     254 

이 방식은 SAS 사용자에게 익숙할 수 있지만, 결과가 데이터 프레임이 아닌 벡터로 반환되어 후속 작업(select() 등)이 불편할 수 있습니다.

3. Apply 계열 함수

기본 R의 apply 함수군은 입력을 받아 각 요소에 함수를 적용합니다.

  • apply(): 배열이나 행렬의 행(MARGIN=1) 또는 열(MARGIN=2) 단위 처리
  • lapply(): 리스트(List) 요소별 처리 (결과는 리스트)
  • sapply(): 결과를 벡터나 행렬로 단순화(Simplify)하여 반환
# 데이터 프레임의 각 열에 함수 적용
sapply(adsl_saf, function(x) sum(!is.na(x))) %>% head()
STUDYID USUBJID  SUBJID  SITEID SITEGR1     ARM 
    254     254     254     254     254     254 

4. Tidy 반복 처리 (across & rowwise)

{tidyverse}는 더 직관적이고 예측 가능한 반복 처리 방법을 제공합니다.

across()를 이용한 열 단위 처리

summarise()mutate() 내부에서 여러 열에 동시에 함수를 적용할 때 사용합니다.

# 모든 수치형 변수의 중앙값 계산
adsl_saf %>%
  summarise(across(where(is.numeric), median, na.rm = TRUE))
Warning: There was 1 warning in `summarise()`.
ℹ In argument: `across(where(is.numeric), median, na.rm = TRUE)`.
Caused by warning:
! The `...` argument of `across()` is deprecated as of dplyr 1.1.0.
Supply arguments directly to `.fns` through an anonymous function instead.

  # Previously
  across(a:b, mean, na.rm = TRUE)

  # Now
  across(a:b, \(x) mean(x, na.rm = TRUE))
  TRT01PN TRT01AN TRTDUR AVGDD CUMDOSE AGE AGEGR1N RACEN BMIBL HEIGHTBL
1      54      54  133.5    54    2322  77       2     1  24.2   162.85
  WEIGHTBL EDUCLVL DURDIS VISNUMEN MMSETOT
1     66.7      12  36.25       11      19

starts_with(), contains(), where(is.character) 등 다양한 조건으로 대상 열을 선택할 수 있어 매우 유연합니다.

rowwise()를 이용한 행 단위 처리

데이터의 각 행별로 계산이 필요할 때 사용합니다.

# 각 피험자 행별로 수치형 데이터 중 결측치가 몇 개인지 계산
adsl_saf %>%
  rowwise(SUBJID) %>%
  summarise(missing_cnt = sum(is.na(c_across(where(is.numeric))))) %>%
  head()
`summarise()` has grouped output by 'SUBJID'. You can override using the
`.groups` argument.
# A tibble: 6 × 2
# Groups:   SUBJID [6]
  SUBJID missing_cnt
  <chr>        <int>
1 1015             0
2 1023             0
3 1028             0
4 1033             0
5 1034             0
6 1047             0

5. {purrr}map() 함수

{purrr} 패키지는 리스트 형태의 데이터를 다루는 강력한 도구입니다. map() 함수는 리스트의 각 요소에 함수를 적용합니다.

# 치료군별로 데이터를 나누어 리스트로 만든 뒤, 각각 요약 정보 확인
adsl_saf %>%
  split(.$TRT01A) %>%
  map(~ skimr::skim(.x))
$Placebo
── Data Summary ────────────────────────
                           Values
Name                       .x    
Number of rows             86    
Number of columns          48    
_______________________          
Column type frequency:           
  character                28    
  Date                     5     
  numeric                  15    
________________________         
Group variables            None  

── Variable type: character ────────────────────────────────────────────────────
   skim_variable n_missing complete_rate min max empty n_unique whitespace
 1 STUDYID               0             1  12  12     0        1          0
 2 USUBJID               0             1  11  11     0       86          0
 3 SUBJID                0             1   4   4     0       86          0
 4 SITEID                0             1   3   3     0       16          0
 5 SITEGR1               0             1   3   3     0       11          0
 6 ARM                   0             1   7   7     0        1          0
 7 TRT01P                0             1   7   7     0        1          0
 8 TRT01A                0             1   7   7     0        1          0
 9 AGEGR1                0             1   3   5     0        3          0
10 AGEU                  0             1   5   5     0        1          0
11 RACE                  0             1   5  25     0        2          0
12 SEX                   0             1   1   1     0        2          0
13 ETHNIC                0             1  18  22     0        2          0
14 SAFFL                 0             1   1   1     0        1          0
15 ITTFL                 0             1   1   1     0        1          0
16 EFFFL                 0             1   1   1     0        2          0
17 COMP8FL               0             1   1   1     0        2          0
18 COMP16FL              0             1   1   1     0        2          0
19 COMP24FL              0             1   1   1     0        2          0
20 DISCONFL              0             1   0   1    58        2          0
21 DSRAEFL               0             1   0   1    78        2          0
22 DTHFL                 0             1   0   1    84        2          0
23 BMIBLGR1              0             1   3   6     0        3          0
24 DURDSGR1              0             1   3   4     0        2          0
25 RFSTDTC               0             1  10  10     0       81          0
26 RFENDTC               0             1  10  10     0       80          0
27 DCDECOD               0             1   5  27     0        9          0
28 DCREASCD              0             1   5  18     0       10          0

── Variable type: Date ─────────────────────────────────────────────────────────
  skim_variable n_missing complete_rate min        max        median    
1 TRTSDT                0             1 2012-07-09 2014-09-02 2013-07-12
2 TRTEDT                0             1 2012-09-01 2015-03-05 2013-11-22
3 DISONSDT              0             1 1998-06-13 2013-04-08 2010-08-17
4 VISIT1DT              0             1 2012-07-06 2014-08-29 2013-07-04
5 RFENDT                0             1 2012-09-02 2015-03-05 2013-11-22
  n_unique
1       81
2       80
3       85
4       80
5       80

── Variable type: numeric ──────────────────────────────────────────────────────
   skim_variable n_missing complete_rate   mean     sd    p0   p25   p50   p75
 1 TRT01PN               0             1   0     0       0     0     0     0  
 2 TRT01AN               0             1   0     0       0     0     0     0  
 3 TRTDUR                0             1 149.   60.3     7   131   182   183  
 4 AVGDD                 0             1   0     0       0     0     0     0  
 5 CUMDOSE               0             1   0     0       0     0     0     0  
 6 AGE                   0             1  75.2   8.59   52    69.2  76    81.8
 7 AGEGR1N               0             1   2.19  0.695   1     2     2     3  
 8 RACEN                 0             1   1.09  0.292   1     1     1     1  
 9 BMIBL                 0             1  23.6   3.67   15.1  21.2  23.4  25.6
10 HEIGHTBL              0             1 163.   11.5   137.  154   163.  171. 
11 WEIGHTBL              0             1  62.8  12.8    34    53.6  60.6  74.2
12 EDUCLVL               0             1  12.6   2.95    6    12    12    14.8
13 DURDIS                0             1  42.6  30.2     7.2  24.3  35.3  50.1
14 VISNUMEN              0             1  10.7   2.38    4    11    12    12  
15 MMSETOT               0             1  18.0   4.27   10    15    19.5  22  
    p100 hist 
 1   0   ▁▁▇▁▁
 2   0   ▁▁▇▁▁
 3 210   ▂▁▁▁▇
 4   0   ▁▁▇▁▁
 5   0   ▁▁▇▁▁
 6  89   ▁▃▇▇▇
 7   3   ▂▁▇▁▆
 8   2   ▇▁▁▁▁
 9  33.3 ▂▆▇▃▂
10 185.  ▂▇▇▇▅
11  86.2 ▂▇▇▇▇
12  21   ▂▇▂▃▁
13 183.  ▇▃▁▁▁
14  12   ▁▁▁▁▇
15  23   ▃▃▃▃▇

$`Xanomeline High Dose`
── Data Summary ────────────────────────
                           Values
Name                       .x    
Number of rows             84    
Number of columns          48    
_______________________          
Column type frequency:           
  character                28    
  Date                     5     
  numeric                  15    
________________________         
Group variables            None  

── Variable type: character ────────────────────────────────────────────────────
   skim_variable n_missing complete_rate min max empty n_unique whitespace
 1 STUDYID               0             1  12  12     0        1          0
 2 USUBJID               0             1  11  11     0       84          0
 3 SUBJID                0             1   4   4     0       84          0
 4 SITEID                0             1   3   3     0       15          0
 5 SITEGR1               0             1   3   3     0       11          0
 6 ARM                   0             1  20  20     0        1          0
 7 TRT01P                0             1  20  20     0        1          0
 8 TRT01A                0             1  20  20     0        1          0
 9 AGEGR1                0             1   3   5     0        3          0
10 AGEU                  0             1   5   5     0        1          0
11 RACE                  0             1   5  32     0        3          0
12 SEX                   0             1   1   1     0        2          0
13 ETHNIC                0             1  18  22     0        2          0
14 SAFFL                 0             1   1   1     0        1          0
15 ITTFL                 0             1   1   1     0        1          0
16 EFFFL                 0             1   1   1     0        2          0
17 COMP8FL               0             1   1   1     0        2          0
18 COMP16FL              0             1   1   1     0        2          0
19 COMP24FL              0             1   1   1     0        2          0
20 DISCONFL              0             1   0   1    27        2          0
21 DSRAEFL               0             1   0   1    44        2          0
22 DTHFL                 0             1   0   0    84        1          0
23 BMIBLGR1              0             1   3   6     0        3          0
24 DURDSGR1              0             1   3   4     0        2          0
25 RFSTDTC               0             1  10  10     0       78          0
26 RFENDTC               0             1  10  10     0       79          0
27 DCDECOD               0             1   9  27     0        7          0
28 DCREASCD              0             1   9  18     0        8          0

── Variable type: Date ─────────────────────────────────────────────────────────
  skim_variable n_missing complete_rate min        max        median    
1 TRTSDT                0             1 2012-07-20 2014-07-01 2013-06-04
2 TRTEDT                0             1 2012-10-20 2014-12-30 2013-08-22
3 DISONSDT              0             1 2001-07-15 2013-02-04 2010-06-04
4 VISIT1DT              0             1 2012-07-10 2014-06-24 2013-05-28
5 RFENDT                0             1 2012-10-23 2014-12-30 2013-08-25
  n_unique
1       78
2       78
3       82
4       75
5       79

── Variable type: numeric ──────────────────────────────────────────────────────
   skim_variable n_missing complete_rate    mean       sd    p0    p25    p50
 1 TRT01PN               0             1   81       0      81     81     81  
 2 TRT01AN               0             1   81       0      81     81     81  
 3 TRTDUR                0             1   99.4    70.6     1     37.8   76.5
 4 AVGDD                 0             1   71.6     8.11   54     70.2   75.1
 5 CUMDOSE               0             1 7551    5531.     54   2646   5778  
 6 AGE                   0             1   74.4     7.89   56     70.8   76  
 7 AGEGR1N               0             1    2.08    0.585   1      2      2  
 8 RACEN                 0             1    1.17    0.618   1      1      1  
 9 BMIBL                 0             1   25.3     4.16   13.7   22.7   24.8
10 HEIGHTBL              0             1  166.     10.1   146.   158.   165. 
11 WEIGHTBL              0             1   70.0    14.7    41.7   57.0   69.2
12 EDUCLVL               0             1   12.5     2.92    6     11.8   12  
13 DURDIS                0             1   40.5    24.7     2.2   23.9   36.0
14 VISNUMEN              0             1    8.98    2.87    4      7      9  
15 MMSETOT               0             1   18.5     4.16   10     16     20  
       p75    p100 hist 
 1    81      81   ▁▁▇▁▁
 2    81      81   ▁▁▇▁▁
 3   181.    200   ▆▅▂▁▇
 4    76.9    78.6 ▂▁▁▂▇
 5 13959   15417   ▆▅▂▁▇
 6    80      88   ▂▂▆▇▃
 7     2       3   ▂▁▇▁▂
 8     1       6   ▇▁▁▁▁
 9    27.8    34.5 ▁▃▇▅▂
10   173.    190.  ▅▅▇▅▁
11    80.3   108   ▅▆▇▃▂
12    15      20   ▂▂▇▃▁
13    52.4   135   ▇▇▃▁▁
14    12      12   ▃▃▂▃▇
15    22      24   ▃▂▅▆▇

$`Xanomeline Low Dose`
── Data Summary ────────────────────────
                           Values
Name                       .x    
Number of rows             84    
Number of columns          48    
_______________________          
Column type frequency:           
  character                28    
  Date                     5     
  numeric                  15    
________________________         
Group variables            None  

── Variable type: character ────────────────────────────────────────────────────
   skim_variable n_missing complete_rate min max empty n_unique whitespace
 1 STUDYID               0             1  12  12     0        1          0
 2 USUBJID               0             1  11  11     0       84          0
 3 SUBJID                0             1   4   4     0       84          0
 4 SITEID                0             1   3   3     0       17          0
 5 SITEGR1               0             1   3   3     0       11          0
 6 ARM                   0             1  19  19     0        1          0
 7 TRT01P                0             1  19  19     0        1          0
 8 TRT01A                0             1  19  19     0        1          0
 9 AGEGR1                0             1   3   5     0        3          0
10 AGEU                  0             1   5   5     0        1          0
11 RACE                  0             1   5  25     0        2          0
12 SEX                   0             1   1   1     0        2          0
13 ETHNIC                0             1  18  22     0        2          0
14 SAFFL                 0             1   1   1     0        1          0
15 ITTFL                 0             1   1   1     0        1          0
16 EFFFL                 0             1   1   1     0        2          0
17 COMP8FL               0             1   1   1     0        2          0
18 COMP16FL              0             1   1   1     0        2          0
19 COMP24FL              0             1   1   1     0        2          0
20 DISCONFL              0             1   0   1    25        2          0
21 DSRAEFL               0             1   0   1    40        2          0
22 DTHFL                 0             1   0   1    83        2          0
23 BMIBLGR1              0             1   3   6     0        3          0
24 DURDSGR1              0             1   3   4     0        2          0
25 RFSTDTC               0             1  10  10     0       77          0
26 RFENDTC               0             1  10  10     0       82          0
27 DCDECOD               0             1   5  27     0        7          0
28 DCREASCD              0             1   5  18     0        7          0

── Variable type: Date ─────────────────────────────────────────────────────────
  skim_variable n_missing complete_rate min        max        median    
1 TRTSDT                0             1 2012-07-22 2014-05-22 2013-05-23
2 TRTEDT                0             1 2012-08-28 2014-11-20 2013-08-15
3 DISONSDT              0             1 2002-07-16 2013-09-16 2009-10-01
4 VISIT1DT              0             1 2012-07-08 2014-05-10 2013-05-13
5 RFENDT                0             1 2012-09-01 2014-11-20 2013-08-17
  n_unique
1       77
2       80
3       83
4       77
5       82

── Variable type: numeric ──────────────────────────────────────────────────────
   skim_variable n_missing complete_rate    mean       sd    p0    p25    p50
 1 TRT01PN               0         1       54       0      54     54     54  
 2 TRT01AN               0         1       54       0      54     54     54  
 3 TRTDUR                0         1       99.0    68.2     2     36.8   82.5
 4 AVGDD                 0         1       54       0      54     54     54  
 5 CUMDOSE               0         1     5347.   3680.    108   1984.  4455  
 6 AGE                   0         1       75.7     8.29   51     71     77.5
 7 AGEGR1N               0         1        2.25    0.618   1      2      2  
 8 RACEN                 0         1        1.07    0.259   1      1      1  
 9 BMIBL                 1         0.988   25.1     4.27   17.7   22.2   24.3
10 HEIGHTBL              0         1      163.     10.4   136.   158.   163. 
11 WEIGHTBL              1         0.988   67.3    14.1    45.4   56.0   64.9
12 EDUCLVL               0         1       13.2     4.15    3     12     12  
13 DURDIS                0         1       48.7    29.6     7.8   26.4   40.2
14 VISNUMEN              0         1        9.01    2.87    4      7      9.5
15 MMSETOT               0         1       17.9     4.22   10     14     18  
      p75    p100 hist 
 1   54      54   ▁▁▇▁▁
 2   54      54   ▁▁▇▁▁
 3  182.    212   ▇▆▃▂▇
 4   54      54   ▁▁▇▁▁
 5 9801   11448   ▇▆▃▂▇
 6   82      88   ▂▁▅▇▇
 7    3       3   ▂▁▇▁▅
 8    1       2   ▇▁▁▁▁
 9   27.8    40.1 ▅▇▅▁▁
10  170.    196.  ▁▆▇▃▁
11   77.4   106.  ▇▇▇▂▂
12   16      24   ▂▂▇▅▁
13   66.4   131.  ▇▇▅▂▁
14   12      12   ▅▂▂▆▇
15   22      24   ▅▅▆▆▇

6. 성능 최적화 (병렬 처리)

반복 작업이 매우 많고 시간이 오래 걸린다면, 여러 프로세서를 사용하는 병렬 처리를 고려할 수 있습니다. {multidplyr}, {furrr}, {foreach} 등의 패키지가 활용됩니다.

챌린지

미니 프로젝트 7에서 만든 adsl_counts 함수를 활용해 보세요. adsl_saf 데이터를 인종(RACE)별로 나누어(split) 각 그룹에 대해 치료군별 성별 빈도를 산출해 보세요.