노동절을 보내는 가장 좋은 방법은 역시 노동 아니겠습니까?
노동절 맞이 야근을 하던 중 (이번에도) 페이스북 R 스터디 그룹에 올라온 질문을 뒤늦게 보게 됐습니다.
안녕하세요.
이제야 R을 처음 접하고 있는 사람입니다.
혹시 R에서 데이터 구조를
이런 그림 형태로 바꿀 수 있는 방법이 있을까요?
이 그림을 보고 와이드 폼 vs 롱 폼을 떠올리신 분이 적지 않으실 겁니다.
R에서 이 두 형태 사이를 오갈 때는 기본적으로 tidyr 패키지를 쓰시면 됩니다.
혹시 이런 내용을 처음 접해 보셨다면 '최대한 친절하게 쓴 R로 데이터 깔끔하게 만들기(feat. tidyr)' 포스트가 도움이 될 수도 있습니다.
그런데 질문자께서 요구하시는 건 내용이 조금 다릅니다.
원래 저 와이드 폼을 롱 폼으로 바꾸는 건 데이터를 이렇게 바꾸는 걸 뜻합니다.
## # A tibble: 4 x 3 ## 지역 성별 인원 #### 1 서울 남자 5 ## 2 서울 여자 3 ## 3 부산 남자 3 ## 4 부산 여자 2
질문자께서는 서울 남자는 다섯 번, 서울 여자는 세 번 하는 식으로 출력하는 방법을 궁금해하고 계십니다.
이럴 때는 어떻게 접근해야 할까요?
답은 금방 올라왔습니다.
이 질문이 처음 올라온 건 4월 28일 오후 9시 20분이었습니다.
이로부터 55분 뒤에 '기본 함수들로 간단하게 함수를 구현해 봤습니다'라는 답글이 붙었습니다.
답변자께서 남기신 코드는 이렇습니다.
wide.format <- data.frame(
male = c(5, 3),
female = c(3, 2)
)
rownames(wide.format) <- c('seoul','busan')
wide.format
## male female ## seoul 5 3 ## busan 3 2
unbox <- function(df){
cn <- colnames(df)
rn <- rownames(df)
result <- data.frame()
for(i in 1:length(rn)){
for(j in 1:length(cn)){
temp <-
data.frame(
rep(rn[i], df[i,j]),
rep(cn[j], df[i,j])
)
result <- rbind(result, temp)
}
}
colnames(result) <- paste('x', 1:ncol(result), sep='')
result
}
finish <- unbox(wide.format)
colnames(finish) <- c('region', 'sex')
finish
## region sex ## 1 seoul male ## 2 seoul male ## 3 seoul male ## 4 seoul male ## 5 seoul male ## 6 seoul female ## 7 seoul female ## 8 seoul female ## 9 busan male ## 10 busan male ## 11 busan male ## 12 busan female ## 13 busan female
위에서 보시는 것처럼 잘 작동합니다. 코드가 이해가 가시나요?
기본적으로 같은 문자(또는 숫자)를 여러 번 반복해 출력하는 rep() 함수를 이용해 코드를 쓰셨습니다.
rep() 함수는 'rep(반복 출력하고 싶은 문자 또는 숫자, 반복 횟수)' 형태로 씁니다.
예를 들어 아래처럼 쓰면 숫자 1을 두 번 출력하게 됩니다.
rep(1, 2)
[1] 1 1
물론 제목에서 말씀드린 것처럼 이렇게 데이터 구조를 바꾸는 방법이 하나만 있는 건 아닙니다.
46분 뒤에 '아 이거 코드 배틀인가요?'라는 멘트와 함께 이런 코드가 등장합니다.
df = data.frame(지역 = c("서울", "부산"),
남자 = c(5, 3),
여자 = c(3, 2))
df_new = data.frame(성별 = rep(colnames(df)[-1], times = colSums(df[, -1])),
지역 = rep(rep(df[, 1], ncol(df) - 1), times = unlist(df[, -1])))
df_new
## 성별 지역 ## 1 남자 서울 ## 2 남자 서울 ## 3 남자 서울 ## 4 남자 서울 ## 5 남자 서울 ## 6 남자 부산 ## 7 남자 부산 ## 8 남자 부산 ## 9 여자 서울 ## 10 여자 서울 ## 11 여자 서울 ## 12 여자 부산 ## 13 여자 부산
이번에도 마찬가지로 역시나 잘 작동합니다.
이번에는 코드 길이가 줄어든 대신 colSums(), unlist() 같은 함수가 추가로 등장했습니다.
어느 방법이 더 좋거나 나쁘다는 말씀을 드리려는 게 아닙니다.
그냥 같은 문제를 해결하는 코드가 여러 종류가 있을 수 있다는 말씀을 드리는 것뿐입니다.
R에 익숙하신 분이라면 짧은 코드를 선호하실 수도 있지만 최종 결과가 나오는 이유를 알기 어려워 코드를 수정하는 데 어려움을 겪을 수도 있습니다.
제가 저 문제를 보고 생각한 코드는 두 답변 사이에 올라온 코드와 가깝습니다.
먼저 세 번째 코드는 이렇게 생겼습니다.
df <- tibble(loc = c('서울', '부산'),
남자 = c(5, 3),
여자 = c(3, 2))
setup <- gather(df, sex, value, -1)
result <- tibble(성별 = rep(setup$sex, setup$value),
장소 = rep(setup$loc, setup$value))
result
## # A tibble: 13 x 2 ## 성별 장소 ## <chr> <chr> ## 1 남자 서울 ## 2 남자 서울 ## 3 남자 서울 ## 4 남자 서울 ## 5 남자 서울 ## 6 남자 부산 ## 7 남자 부산 ## 8 남자 부산 ## 9 여자 서울 ## 10 여자 서울 ## 11 여자 서울 ## 12 여자 부산 ## 13 여자 부산
아, 이 코드는 tidyverse 패키지를 먼저 불러오셔야 작동합니다.
#install.packages('tidyverse')
library('tidyverse')
## -- Attaching packages --------------------------------------------------------------------------------- tidyverse 1.3.0 --
## √ ggplot2 3.2.1 √ purrr 0.3.2 ## √ tibble 2.1.3 √ dplyr 0.8.3 ## √ tidyr 0.0.3 √ stringr 1.4.0 ## √ readr 1.3.1 √ forcats 0.3.0
## -- Conflicts ------------------------------------------------------------------------------------ tidyverse_conflicts() -- ## x dplyr::filter() masks stats::filter() ## x dplyr::lag() masks stats::lag()
이 블로그를 꾸준히 읽어 오신 분은 아시겠지만 저도 물론 tidyverse를 이용할 겁니다.
그리고 순전히 저 위에 있는 코드와 다른 코드를 쓰고 말겠다는 이유로 이렇게 코드를 쓸 겁니다.
tribble(
~지역, ~남자, ~여자,
'서울', 5, 3,
'부산', 3, 2
) %>%
pivot_longer(-지역, names_to='성별', values_to='인원') %>%
group_by(성별, 지역) %>%
expand(count=seq(1:인원)) %>%
arrange(성별, desc(지역)) %>%
select(-count)
## # A tibble: 13 x 2 ## # Groups: 성별, 지역 [4] ## 성별 지역 ## <chr> <chr> ## 1 남자 서울 ## 2 남자 서울 ## 3 남자 서울 ## 4 남자 서울 ## 5 남자 서울 ## 6 남자 부산 ## 7 남자 부산 ## 8 남자 부산 ## 9 여자 서울 ## 10 여자 서울 ## 11 여자 서울 ## 12 여자 부산 ## 13 여자 부산
이 코드 가운데 arrange() 함수를 이용해 정렬하는 부분은 역시 순전히 질문자께서 남기신 그림과 똑같은 모양을 출력하는 게 존재 이유입니다.
select() 함수로 중간에 활용한 count 열을 삭제하는 것도 마찬가지입니다.
위에 있는 두 문장이 이해가 가지 않으시면 '최대한 친절하게 쓴 R로 데이터 뽑아내기(feat. dplyr)' 포스트가 도움이 될 수 있습니다.
두 줄을 지우면 이런 결과가 나타납니다.
다시 말씀드리지만 코드를 이렇게 쓴 건 순전히 tidyverse를 이용하되 저 위에 있는 코드와 다른 코드를 쓰려는 목적입니다.
이것 말고도 얼마든 다른 방법이 있을 수 있습니다.
개인적으로는 맨 처음에 기본 함수로 기능을 구현하신 두 분이 진짜 실력자라고 생각합니다.
저 같은 '초짜'는 아예 저렇게 접근할 엄두 자체를 내지 못하니까요.
그래도 tidyverse를 사랑하는 한 사람으로서 오늘도 외칩니다.
여러분 모두 Happy Tidyversing!
댓글,