stackoverflow with dplyr 01 - mutate_all

Page content

I. 개요

dplyr 문법에 관한 설명은 사실 차고 넘친다. 구체적인 설명은 하지 않겠다. Google이나 Naver에서 dplyr을 검색해보자! 검색하면 쉽게 정리된 글들이 많이 있다.

그런데, 실제 실무에서 다루는 데이터의 질과 양은 다 다르다. 데이터 가공은 결국 연구자의 환경에 따라 달라지는데, 조금 더 효과적으로 dplyr 문법을 사용하려면 결국엔 아이디어가 필요하고, 그리고 stackoverflow를 찾게 되어 있다. 집단 지성의 힘이랄까?

그래서 가급적, stackoverflow에 나와 있는 문제 중 재미있는 해결법 등을 소개하며 연재하려고 한다.

제 강의를 들으신 분들에게 작은 도움이 되기를 바랍니다.

II. Problems

2017년 쯤 올라온 질문이다. How to use dplyr::mutate_all for rounding selected columns 한번 읽기를 바란다.

  • 패키지 버전
# devtools::install_github("hadley/dplyr")
> packageVersion("dplyr")
[1] ‘0.5.0.9001
  • 데이터
> df
# A tibble: 6 × 7
    gene_symbol fold_change  pvalue ctr.mean_exp tre.mean_exp  ctr.cv  tre.cv
         <fctr>       <dbl>   <dbl>        <dbl>        <dbl>   <dbl>   <dbl>
1 0610005C13Rik     1.54037 0.53120      0.00583      0.00899 5.49291 6.06505
2 0610007P14Rik     1.10976 0.00033     59.67286     66.22232 0.20263 0.28827
3 0610009B22Rik     0.78500 0.00000     83.28470     65.37819 0.17445 0.33958
4 0610009L18Rik     0.79852 0.00011      6.88321      5.49638 0.46288 0.53295
5 0610009O20Rik     0.91615 0.00387     14.67696     13.44630 0.25430 0.26679
6 0610010B08Rik     0.87931 0.01455      1.10363      0.97043 0.39564 0.52364
  • 하고 싶었던 것

I’d like to round the floats (2nd columns onward) to 3 digits. What’s the way to do it with dplyr::mutate_all()

round() 함수를 적용해서 소수점 3자리로 통일하고 싶다는 것이다. 내용적(content)으로는 사실 간단한 문제다. 그런데, Column이 숫자와, 문자 등 복잡하게 섞여 있으면 프로그래밍적(programming)으로는 쉽지 않다.

  • 시도했던 것
cols <- names(df)[2:7]
# df <- df %>% mutate_each_(funs(round(.,3)), cols)
# Warning message:
#'mutate_each_' is deprecated.
# Use 'mutate_all' instead.
# See help("Deprecated") 

df <- df %>% mutate_all(funs(round(.,3)), cols)
  • 에러 메시지
Error in mutate_impl(.data, dots) : 
  3 arguments passed to 'round'which requires 1 or 2 arguments

우선, 프로그래밍 입문자들에게 위 에러의 공유는 매우 좋은 본보기가 될 것 같다. 프로그래밍과 관련된 업무를 하는 사람에게 에러는 사실 좋은 것이다. 수정하면서 조금 더 나은 결과물이 나오는 하나의 과정이기 때문에 그렇다. (여담이지만, 처음 자바를 배울 때 이게 너무 힘들어서 포기했다. 에러를 잡는데 많은 시간이 걸려서 포기했던 지난날을 떠올리며..)

그런데, 실무에서 신입이 가장 어려운 부분은 에러가 난 것을 어떻게 공유하고 전달해야 할지 모르는데 있다. 신입 뿐만 아니라 모든 사람들이 에러와 시간을 보내고 있는데, 위 에러 메시지만 던져주면 당연히 상사도 모른다!

위와 같이 전체적인 맥락과 무엇을 하려는지 명시해주면, 에러를 이해하기도 그리고 수정하기가 훨씬 수월해진다.

III. Understanding

그렇다면 무엇이 문제일까? 결론적으로 말하면 문법이 맞지 않은 거다. 왜 문법이 맞지 않은 것일까?

# install.packages("dplyr")
library(dplyr)
packageVersion("dplyr")
## [1] '0.8.5'

(1) mutate_all Vs. mutate_if or mutate_at

mutate_all과 mutate_if는 다르다. 7개의 변수 모두가 변환하는 건 아니다. 그중에서 일부 double 형태만 바꾸는 것이다. 즉, 애초에 함수를 잘못 쓴것이다.

따라서, 관련 답지를 봐도, mutate_all로 추천하기 보다는 mutate_ifmutate_at으로 추천하는 것을 볼 수 있다.

(2) across() 소개

버전이 업그레이드 된 경우에 쓸수 있는 새로운 함수를 소개하고 있는데, 사내망을 이용한다면, 위와 같은 답지는 도움이 안된다. dplyr 패키지 하나만 업그레이드의 문제가 아니라 의존성 있는 다른 패키지들도 동시 다발적으로 다 설치를 해줘야 한다. 안 그러면 패키지 설치 에러를 잡아야 하는 촌극이 발생한다. (원래 이 문제 해결하려는 게 아니었으니까)

물론 인터넷 환경은 상관이 없다. 그러나 사내망이라면 문제가 복잡해지기 때문에, 스터디용으로 활용가치는 있으나 실무에서 즉시 적용하기에는 문제가 있다.

IV. Application

이제 적용을 해보자. 적용을 할 때, 필자가 주로 쓰는 건, 본 포스트처럼, 가상의 Sample 데이터를 만들어보는 것이다.

(1) 응용방법 1

  • 데이터 생성
library(dplyr)
df  <- structure(list(gene_symbol = structure(1:6, .Label = c("0610005C13Rik", 
"0610007P14Rik", "0610009B22Rik", "0610009L18Rik", "0610009O20Rik", 
"0610010B08Rik"), class = "factor"), fold_change = c(1.54037, 
1.10976, 0.785, 0.79852, 0.91615, 0.87931), pvalue = c(0.5312, 
0.00033, 0, 0.00011, 0.00387, 0.01455), ctr.mean_exp = c(0.00583, 
59.67286, 83.2847, 6.88321, 14.67696, 1.10363), tre.mean_exp = c(0.00899, 
66.22232, 65.37819, 5.49638, 13.4463, 0.97043), ctr.cv = c(5.49291, 
0.20263, 0.17445, 0.46288, 0.2543, 0.39564), tre.cv = c(6.06505, 
0.28827, 0.33958, 0.53295, 0.26679, 0.52364)), .Names = c("gene_symbol", 
"fold_change", "pvalue", "ctr.mean_exp", "tre.mean_exp", "ctr.cv", 
"tre.cv"), row.names = c(NA, -6L), class = c("tbl_df", "tbl", 
"data.frame"))
  • 예시답안을 활용한 적용
    • mutate_at 함수를 활용해서 적용했다.
df %>% mutate_at(2:7, round, 3)
## # A tibble: 6 x 7
##   gene_symbol   fold_change pvalue ctr.mean_exp tre.mean_exp ctr.cv tre.cv
##   <fct>               <dbl>  <dbl>        <dbl>        <dbl>  <dbl>  <dbl>
## 1 0610005C13Rik       1.54   0.531        0.006        0.009  5.49   6.06 
## 2 0610007P14Rik       1.11   0           59.7         66.2    0.203  0.288
## 3 0610009B22Rik       0.785  0           83.3         65.4    0.174  0.34 
## 4 0610009L18Rik       0.799  0            6.88         5.50   0.463  0.533
## 5 0610009O20Rik       0.916  0.004       14.7         13.4    0.254  0.267
## 6 0610010B08Rik       0.879  0.015        1.10         0.97   0.396  0.524

위의 있는 값처럼 digits=3으로 변환된 것을 확인할 수 있다.

(2) 응용방법 2

그런데, 만약 Column 중간에 문자형 데이터가 있을 경우에는 df[2:7]당연히 적용이 되지 않는다. 그럼 어떻게 해야 할까?

  • mutate_if를 활용한 적용
    • 이번엔 문자열 데이터를 중간에 오도록 살짝 변형하자.
df  <- structure(list(fold_change = c(1.54037, 
1.10976, 0.785, 0.79852, 0.91615, 0.87931), pvalue = c(0.5312, 
0.00033, 0, 0.00011, 0.00387, 0.01455), ctr.mean_exp = c(0.00583, 
59.67286, 83.2847, 6.88321, 14.67696, 1.10363), tre.mean_exp = c(0.00899, 
66.22232, 65.37819, 5.49638, 13.4463, 0.97043), gene_symbol = structure(1:6, .Label = c("0610005C13Rik", 
"0610007P14Rik", "0610009B22Rik", "0610009L18Rik", "0610009O20Rik", 
"0610010B08Rik"), class = "factor"), ctr.cv = c(5.49291, 
0.20263, 0.17445, 0.46288, 0.2543, 0.39564), tre.cv = c(6.06505, 
0.28827, 0.33958, 0.53295, 0.26679, 0.52364)), .Names = c( 
"fold_change", "pvalue", "ctr.mean_exp", "tre.mean_exp", "gene_symbol", "ctr.cv", 
"tre.cv"), row.names = c(NA, -6L), class = c("tbl_df", "tbl", 
"data.frame"))

head(df)
## # A tibble: 6 x 7
##   fold_change  pvalue ctr.mean_exp tre.mean_exp gene_symbol   ctr.cv tre.cv
##         <dbl>   <dbl>        <dbl>        <dbl> <fct>          <dbl>  <dbl>
## 1       1.54  0.531        0.00583      0.00899 0610005C13Rik  5.49   6.07 
## 2       1.11  0.00033     59.7         66.2     0610007P14Rik  0.203  0.288
## 3       0.785 0           83.3         65.4     0610009B22Rik  0.174  0.340
## 4       0.799 0.00011      6.88         5.50    0610009L18Rik  0.463  0.533
## 5       0.916 0.00387     14.7         13.4     0610009O20Rik  0.254  0.267
## 6       0.879 0.0146       1.10         0.970   0610010B08Rik  0.396  0.524
  • mutate_at를 활용해보자.
df %>% mutate_at(2:7, round, 3)
Error in Math.factor(1:6, 3) : ‘round’ not meaningful for factors

보시다시피, round()함수는 당연한 말이지만, factors에는 적용되지 않는다. 이제 mutate_if를 적용해보자.

df %>% mutate_if(is.numeric, round, 3)
## # A tibble: 6 x 7
##   fold_change pvalue ctr.mean_exp tre.mean_exp gene_symbol   ctr.cv tre.cv
##         <dbl>  <dbl>        <dbl>        <dbl> <fct>          <dbl>  <dbl>
## 1       1.54   0.531        0.006        0.009 0610005C13Rik  5.49   6.06 
## 2       1.11   0           59.7         66.2   0610007P14Rik  0.203  0.288
## 3       0.785  0           83.3         65.4   0610009B22Rik  0.174  0.34 
## 4       0.799  0            6.88         5.50  0610009L18Rik  0.463  0.533
## 5       0.916  0.004       14.7         13.4   0610009O20Rik  0.254  0.267
## 6       0.879  0.015        1.10         0.97  0610010B08Rik  0.396  0.524

이번에는 잘 적용이 되었다. 이제, mutate_at을 사용할 것인지, 아니면 mutate_if를 사용할 것인지는 사용자의 데이터셋 환경에 따라 달라지는 것이다.

여기에서 응용하는 것은 사용자에게 달린 문제다.

물론, 에러는 또 나올 것이다. 그러면 위와 같은 방법으로 해결하면 된다. 처음이 어렵지만, 시간이 지나면, 이러한 과정은 이제 당연히 느껴질 것이고, 덤으로 영어실력도 조금씩 늘어날 것이다.

작은 도움이 되었기를 바랍니다.

V. Reference

stacoverflow. (2017). “How to use dplyr::mutate_all for rounding selected columns”. Retrieved from https://stackoverflow.com/questions/43314328/how-to-use-dplyrmutate-all-for-rounding-selected-columns

R 강의 소개