聰明不如鈍筆
총명불여둔필
assignment KindeR

R로 (깔끔하게) 데이터 구조를 바꾸는 몇 가지 방법


노동절을 보내는 가장 좋은 방법은 역시 노동 아니겠습니까?


노동절 맞이 야근을 하던 중 (이번에도) 페이스북 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!


댓글, 2

  • 댓글 수정/삭제 히지노
    2020.05.21 09:48

    안녕하세요, 간만에 인사드립니다.. 알려주신 내용으로 열공해서 어느정도 자립했습니다^^.
    한 가지, 중요한(저한테) 질문이 있는데요.. 형태소 분석할 때, 기존 R에 있는 사전 말고 별도의 사전을 만들어서(예를 들면 자동차 관련 직무기술사전), 그 사전으로 형태소 분석하고 단어 빈도수 추출하려고 하는데,, 별도의 사전을 만들고 등록하는 방법을 모르겠어요,
    구글링 해봐도 기존 사전에 단어 추가하는 방법만 나오는데,, 새로운 사전을 만들고 등록하여 형태소 분석하는 내용은 없어서 이렇게 문의드립니다. 혹시 팁을 좀 주실 수 있으신지요?

    •  수정/삭제 kini
      2020.05.24 20:37 신고

      이미 있는 사전에 새로운 낱말을 추가해도 같은 결과를 얻으실 수 있지 않나요?

account_circle
vpn_key
web

security

mode_edit
KindeR | 카테고리 다른 글 더 보기