요약표 데이터 통합

Author

Mike K Smith

Published

February 15, 2023

이 프로젝트에서는 이전 프로젝트(2, 3)에서 작성한 코드를 결합하여 인구통계 요약표(Demography Summary Table)의 최종 데이터를 구성하는 방법을 배웁니다.

목표

  • 빈도와 백분율 데이터 산출
  • 기술 통계량 데이터 산출
  • 행 기반 결합(bind_rows)을 통한 최종 데이터셋 생성

데이터 소스

익명화된 CDISC 데이터셋: https://github.com/phuse-org/phuse-scripts/tree/master/data/adam/cdisc

미니 프로젝트 시작

  1. ADSL 데이터 로드 및 전처리
library(tidyverse)
Warning: package 'ggplot2' was built under R version 4.4.3
Warning: package 'tibble' was built under R version 4.4.3
Warning: package 'purrr' was built under R version 4.4.2
Warning: package 'lubridate' was built under R version 4.4.2
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.1.4     ✔ readr     2.1.5
✔ forcats   1.0.0     ✔ stringr   1.5.1
✔ ggplot2   3.5.2     ✔ tibble    3.3.0
✔ lubridate 1.9.4     ✔ tidyr     1.3.1
✔ purrr     1.0.4     
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(rio)

adsl <- import(file = "./data/adsl.xpt")

adsl_eff <- adsl %>%
  filter(EFFFL == "Y" ) %>%
  mutate(SEX = recode(SEX, "M" = "Male", "F" = "Female"))
  1. 치료군 및 성별별 빈도(n)와 총계(N) 산출
Big_N_cnt <- adsl_eff %>%
  group_by(TRT01AN, TRT01A) %>%
  count(name = "N")

small_n_cnt <- adsl_eff %>%
  group_by(TRT01AN, TRT01A, SEX) %>%
  count(name = "n")

여담: 빈도가 0인 범주 처리하기

데이터에 특정 범주의 관측치가 없는 경우, 해당 행이 누락될 수 있습니다. 이를 방지하기 위해 complete() 함수를 사용하여 모든 가능한 조합을 생성하고 빈도를 0으로 채울 수 있습니다.

myData <- tribble(
  ~TRT01AN,  ~TRT01A, ~SEX,
  1, "Placebo", "M",
  1, "Placebo", "F",
  2, "Active", "F",
  2, "Active", "F",
  3, "Comparator", "M",
  3, "Comparator", "M"
)

# complete를 사용하여 누락된 조합(Active-Male, Comparator-Female)을 0으로 채움
myData %>%
  group_by(TRT01AN, TRT01A, SEX) %>%
  count(name = "n") %>%
  ungroup() %>%
  complete(nesting(TRT01AN, TRT01A), SEX, fill = list(n = 0))
# A tibble: 6 × 4
  TRT01AN TRT01A     SEX       n
    <dbl> <chr>      <chr> <int>
1       1 Placebo    F         1
2       1 Placebo    M         1
3       2 Active     F         2
4       2 Active     M         0
5       3 Comparator F         0
6       3 Comparator M         2

  1. 연령 그룹(Age Group) 빈도 산출 및 병합
Agegrp_N_cnt <- adsl_eff %>%
  group_by(TRT01AN, TRT01A, AGEGR1) %>%
  count(name = "age_total")

age_n_cnt <- adsl_eff %>%
  group_by(TRT01AN, TRT01A, SEX, AGEGR1) %>%
  count(name = "age_n")

age_mrg_cnt <- age_n_cnt %>% 
  left_join(Agegrp_N_cnt, by = c("TRT01AN", "TRT01A", "AGEGR1")) %>%
  left_join(Big_N_cnt, by = c("TRT01AN", "TRT01A")) %>%
  left_join(small_n_cnt, by = c("TRT01A", "TRT01AN", "SEX")) %>%
  ungroup()
  1. 백분율 계산 및 포맷 지정
age_n_pct <- age_mrg_cnt %>%
  mutate(perc_age = round((age_n/n)*100, 1)) %>%
  mutate(perc_achar = format(perc_age, nsmall = 1)) %>%
  mutate(npct = paste(age_n, paste0("(", perc_achar, ")"))) %>% 
  select(AGEGR1, TRT01A, SEX, npct)
  1. 데이터 전치 및 레이블 변경
age_cat <- age_n_pct %>%
  pivot_wider(names_from = c(TRT01A, SEX), 
              values_from = npct, 
              values_fill = "0",
              names_sep = "_") %>%
  rename(category = AGEGR1) %>%
  arrange(category)

여담: 팩터(Factor)를 이용한 정렬 순서 제어

R은 문자형 변수를 기본적으로 영숫자 순으로 정렬합니다. 연령 그룹처럼 논리적 순서가 있는 경우 factor()를 사용하여 순서를 명시적으로 정의하는 것이 좋습니다.

myData <- tibble::tribble(
  ~ID, ~age,
  1, "18-44",
  2, ">=65",
  3, "45-64")

# 원하는 순서로 레벨 지정
myData <- myData %>%
  mutate(age = factor(age, levels = c("18-44", "45-64", ">=65")))

myData %>% arrange(age)
# A tibble: 3 × 2
     ID age  
  <dbl> <fct>
1     1 18-44
2     3 45-64
3     2 >=65 

  1. 연령 기술 통계량 산출 (프로젝트 3 복습)
age_stat <- adsl_eff %>%
  group_by(TRT01AN, TRT01A, SEX) %>%
  summarize(mean = mean(AGE) %>% round(1) %>% format(nsmall = 1),
            sd = sd(AGE) %>% round(1) %>% format(nsmall = 1), 
            med = median(AGE) %>% round(1) %>% format(nsmall = 1),              
            min = min(AGE) %>% format(nsmall = 1), 
            max = max(AGE) %>% format(nsmall = 1),
            n = n() %>% as.character(),
            .groups = 'drop') %>%
  mutate(range_minmax = paste0("(", min, ", ", max, ")"))
  1. 기술 통계량 데이터 전치
agestat_cat <- age_stat %>%
  select(TRT01A, SEX, n, mean, med, sd, range_minmax) %>% 
  mutate(across(where(is.numeric), as.character)) %>%
  pivot_longer(-c(TRT01A, SEX), names_to = "category", values_to = "values") %>%
  pivot_wider(names_from = c(TRT01A, SEX), values_from = values) %>%
  mutate(category = case_when(category == "n" ~ "N",
                              category == "med" ~ "Median", 
                              category == "mean" ~ "Mean", 
                              category == "sd" ~ "Std Dev", 
                              category == "range_minmax" ~ "Range (min, max)"))
  1. 최종 데이터 결합

bind_rows() 함수를 사용하여 위에서 만든 범주형 빈도 데이터(age_cat)와 연속형 요약 통계 데이터(agestat_cat)를 위아래로 합칩니다. 이는 SAS의 SET 문과 유사한 역할을 합니다.

dm_allcomb <- bind_rows(age_cat, agestat_cat)  
dm_allcomb
# A tibble: 8 × 7
  category         Placebo_Female Placebo_Male `Xanomeline Low Dose_Female`
  <chr>            <chr>          <chr>        <chr>                       
1 65-80            20 (43.5)      20 (60.6)    26 (55.3)                   
2 <65              8 (17.4)       5 (15.2)     4 ( 8.5)                    
3 >80              18 (39.1)      8 (24.2)     17 (36.2)                   
4 N                46             33           47                          
5 Mean             76.1           73.4         76.4                        
6 Median           77.5           74.0         78.0                        
7 Std Dev          8.5            8.1          7.6                         
8 Range (min, max) (59.0, 88.0)   (52.0, 85.0) (56.0, 87.0)                
# ℹ 3 more variables: `Xanomeline Low Dose_Male` <chr>,
#   `Xanomeline High Dose_Female` <chr>, `Xanomeline High Dose_Male` <chr>

챌린지

  1. 연령 범주의 표시 순서를 적절하게 조정해 보세요. (<65, 65-80, >80)
  2. N (전체 대상자 수) 행을 연령 범주 행보다 위로 이동시켜 보세요.
  3. 민족(Ethnicity)과 인종(Race) 변수에 대해서도 동일한 과정을 수행하여 요약표를 확장해 보세요.