최근 삼성의 갤럭시노트7 폭발과 리콜, "배터리 불량이 폭발의인"이라는 1차 분석(--> 결과론적으로 잘못된, 섣부른 불량원인 분석 결과 발표, 조치였음....)과 중국산 배터리로 교체 후의 재 판매, 그리고 재 폭발... 결국 생산 중단과 환불에 이른 일련의 다사다난했던 사건들이 있었습니다. 

 

삼성전자 엔지니어들이 아직 불량원인을 파악하지 못했고, 폭발을 재연을 못하고 있다고 하니 "폭발의 참 원인(Root cause)'을 찾는 것이 얼마나 어려운 일인지, 또 얼마나 중요한 일인지 다시금 생각해보게 됩니다.

 

삼성의 엔지니어들이라면 우리나라 최고 실력자, 전문가 집단일텐데요, 아직도 불량원인을 파악하지 못한 것을 보면 불량원인이 표면적인 원인, 1차원적인 원인이 아니기 때문이지 않을까요? 

 

좀더 얇아진 스마트폰 속에, 더 커진 배터리를 집어넣고, 방수처리를 했으며, 홍채 인식 등 연산이 많이 필요한 애플리케이션도 추가되고... 여러가지 요인들이, 스마트폰을 사용하는 다양한 상황, 순서, 흐름 속에서 서로 상승작용을 일으키면서 어느 순간 임계점을 넘으면... 그때 드디어 "배터리"가 폭발하지 않았을까요? 

 

제일 마지막 단계의 결과, 현상인 "배터리"만 쳐다보기보다는, 폭발에 이르게된 연결고리를 찾아야 폭발 사고를 연구실에서 '재연'할 수 있지 않을까요? 

(저는 엔지니어가 아니므로 폭발 원인 모릅니다.  모르니깐 자꾸 물음표만 남발을... -_-;;;) 

 

 

 

분석전문가의 역할, 역량에 대해서 말할 때 여러가지가 있겠습니다만은, 저는 "좋은 질문을 잘 던지는 능력", "(데이터를 가지고 풀어야 할) 문제를 정확하게 정의하는 능력" 첫번째로 꼽고 싶습니다.  그리고 오늘 포스팅의 주제는 "좋은 질문"을 한번도 아니고 5번이나 꼬리에 꼬리를 물고 던지는 기법인 "5 Why?"에 대한 이야기입니다.

 

 

 

 

 

 

문제의 원인을 제대로 찾지 못하면 제대로 된 해결책을 찾지 못하는 것은 당연한 결과이겠지요.  "기계가 멈춘 문제"에 대해서 "5 Why?"를 적용해본 예시를 가지고 참원인(Root Cause)까지 도달하지 못했을 때 어떻게 잘못된 대응방안이 도출되는지 살펴보겠습니다.

 

 

* 출처 : http://www.slideshare.net/aakashkulkarni3/9akk105151d0113-5-whys

 

 

 

> Problem : 기계가 멈추었다.

 

> 5 why?

 

 Why?

Questions (5 why?) 

Answer 

Action Item

 1st why?

 왜 기계가 멈추었을까?

과부하가 걸려 퓨즈가 나갔다

 ☞ 퓨즈 교환 

 2nd why?

 왜 과부하가 걸렸을까?

축에 윤활유가 충분하지 않았다

 ☞ 윤활유 보충 

 3rd why?

 왜 윤활유가 충분하지

않았을까?

윤활 펌프가 잘 작동하지 않았다

 ☞ 윤활 펌프 교체

 4th why?

 왜 윤활 펌프가 잘

작동하지 않았을까?

펌프 축이 마모되어 흡입력이

떨어졌다

 ☞ 펌프 축 수리

 5th why?

 왜 펌프 축이

마모되었을까?

여과기가 붙어 있지 않아서

절삭 칩이 들어갔다 

 ☞ 여과기 교체

 

 

 

위의 5 why? 사례에서 보듯이 왜라는 질문(question, why?)에 깊이, 수준에 따라서 답(answer)이 달라지고, 답에 따라서 대응방안(action item)이 현격히 달라집니다.

 

5 why?의 시작단계에서 질문을 끝마치고 피상적인 대응방안을 수립할 경우 그 문제는 반드시 '재발'할 것입니다.  문제를 발본색원하려면 근본원인, 참원인(root cause)를 찾아야겠지요.

 

 

 

5 Why? 관련해서 미국 토머스 제퍼슨 기념관 사례도 재미있어서 소개합니다.  토머스 제퍼슨 기념관의 외벽을 계속 페인트칠 해도 다시 부식이 되곤 하더랍니다. 그래서 기념관장이 직원에게 "Why?"를 다섯번 물어보았다고 해요.  

 

기념관장 : "왜 대리석들이 빨리 부식될까요?"

 

직원 : "대리석을 비눗물로 자주 씻기 때문입니다"

 

 

기념관장 : "그럼 왜 비눗물로 자주 씻는가요?"

 

 

 

직원 : "비둘기 배설물 때문에 비눗물로 자주 씻기 때문입니다"

 

 

기념과장 : "그러면 왜 비둘기들이 많이 올까요?"

 

직원 : "그야 비둘기의 먹이인 거미가 많이 오기 때문이지요."

 

 

기념관장 : "음...그러면 왜 거미들이 많이 오는거지요?"

 

직원 : "그거야 거미들의 먹이인 나방이 많이 오기 때문이지요"

 

 

기념관장 : "아하, 그럼 왜 나방은 몰려드는 거지요?"

 

직원 : (지친 목소리로??? ^^;;;) "실내 전등을 주변보다 더 일찍 켜기 때문이지요."

 

 

기념관장은 "나방이 몰려드는 시간을 피해 2시간 늦게 조명을 켜는 조치"를 취했다고 하는군요!   기념관 외벽에 페인트칠을 하는 횟수가 줄었을것 같지요?! ^^

 

단, 한국에서 상호간에 신뢰가 쌓이지 않은 상태에서 "왜?"를 5번씩이나 동일 인물에게 던질 경우 "당신 나한테 왜 그래?  무슨 감정 있어?"라는 반응과 함께 멱살을 잡힐 수도 있으니 눈치껏, 요령껏 질문을 던지시길...^^; 

 

반응적 경청!  눈 맞추고, 고개 끄덕 끄덕 해주고, 맞장구도 쳐가면서... 참원인에 도달하기 위한 꼬리에 꼬리를 무는 질문을 던지시길...!!!  4번, 3번, 2번만에라도 참원인을 찾으면 좋고요. Good luck!

 

 

 

참고로, 불량원인분석을 할 때 "5 Why?" 기법 말고도요, 일본의 품질관리 통계학 박사인 카오루 이시카와 박사가 개발한 "생선뼈 다이어그램(Fish bone diagram)"도 많이 사용됩니다.  아래에 예시가 있는데요, working conditions, raw materials, management, tchnology, machine, workers 의 관점에서 체계적으로 문제의 참원인을 탐색해나가는데 있어 유용한 방법론이라고 하겠습니다.

 

 

* 그림 출처 : http://www.conceptdraw.com/How-To-Guide/picture/Fishbone-Causes-of-low-quality-output.png

 

 

 

분석가가 통계기법, 기계학습 이론만 잘 안다고 해서 불량원인 분석을 잘할거라고 생각할 수는 없습니다. 재료/부품, 제품설계, 생산 프로세스, 생산 장비/설비의 변경점, 고객의 제품 사용 상황(특히, 사고 나기 직전 1~2시간 전에 제품가지고 무얼하고 있었는지, 어떤 징후같은게 없었는지...)을 이해하고 있어야 하고, 잘 이해하려면 좋은 질문을 던질 줄 알아야 하고, 잘 관찰해야 하고, 잘 경청해야 한는 점을 다시 한번 강조하면서 이번 포스팅을 마칠까 합니다.

 

 

그나저나 제 와이프도 갤럭시노트7 예약주문해서 구매했다가 아직 환불 안하고 가지고 있는데요, 무얼로 바꾸어야 할지 고민이네요.... 

 

728x90
반응형
Posted by Rfriend
,

window function은 n개의 행을 input으로 받아서 n개의 행을 가진 output을 반환하는 함수를 말합니다.

 

지난번 포스팅에서는 dplyr package의 window function 중에서 Ranking and Ordering을 하는 함수들로서 row_number(), min_rank(), dense_rank(), cume_dist(), percent_rank(), ntile() 에 대해서 알아보았습니다. (바로가기 http://rfriend.tistory.com/241)

 

이번 포스팅에서는 dplyr 패키지의 window function 두번째 시간으로서 특정 칼럼의 행을 위로 올리거나(Lead) 아니면 내리는(Lag) 함수에 대해서 알아보겠습니다.

 

lead() 나 lag() 함수는 시계열 데이터를 분석할 때 많이 사용하는 편입니다. 특정 그룹id와 날짜/시간 기준으로 정렬(sorting)을 해놓은 다음에, lead() 나 lag() 함수를 가지고 행을 하나씩 내리구요, 직전 날짜/시간 대비 이후의 값의 변화, 차이(difference)를 구하는 식으로 말이지요.

(시계열분석에 특화된 package를 사용하면 더 편하기 하지만.... 데이터 프레임을 가지고 dplyr 패키지의 lead()나 lag() 함수 알아놓는것도 유용해요)

 

 

 

 

 

먼저 간단한 벡터를 가지고 lead()와 lag() 사용법을 소개하겠습니다.

 

 

(1) lead(x, n = 1L, default = NA, ...) in {dplyr} package

 

lead() 함수는 벡터 값을 n = 1L (양의 정수값) 의 값 만큼 앞에서 제외하고, 제일 뒤의 n = 1L 값만큼의 값은 NA 로 채워놓은 값을 반환합니다.  이때, n  표기는 생략할 수 있습니다.

 

 

> ##-------------------------------------------------
> ## R dplyr package : window function - lead and lag
> ##-------------------------------------------------
> 
> library(dplyr)
> 
> # lead()
> x <- c(1:10)
> 
> lead(x, n = 1)
 [1]  2  3  4  5  6  7  8  9 10 NA
> 
> lead(x, 2)
 [1]  3  4  5  6  7  8  9 10 NA NA

 

 

 

 

(2) lag(x, n = 1L, default = NA, ...) in {dplyr} package

 

lag() 함수는 lead() 함수와 정반대로 생각하시면 됩니다.  lag() 함수의 n = 1L(양의 정수값) 만큼 제일 앞자리부터 뒤로 옮기고, n = 1L 개수 만큼의 자리에 NA 값을 채워넣은 값을 반환합니다.

 

default = "." 처럼 특정 값을 설정해주면 NA 대신 새로 설정해준 값 혹은 기호가 채워진 값을 반환합니다. (아래 세번째 예의 경우 "."으로 빈 자리가 채워지면서 모든 값에 큰 따옴표("")가 붙으면서 character 형태로 바뀌었습니다)

 

 

> # lag()
> x <- c(1:10)
> 
> lag(x, n = 1)
 [1] NA  1  2  3  4  5  6  7  8  9
> 
> lag(x, 2)
 [1] NA NA  1  2  3  4  5  6  7  8
> 
> lag(x, 2, default = ".")
 [1] "." "." "1" "2" "3" "4" "5" "6" "7" "8"

 

 

 

 

 


 

[문제] 위의 x_df 데이터 프레임의 group 별로 직전 대비 직후 값의 차이가 가장 큰 값(max)과 가장 작은 값을 각각 구하시오. (What are the max and min difference values between x and lag(x) of x_df dataframe by group?)

 

 

(0) 예제 데이터 프레임 만들기

 

분석할 때 보통 벡터 보다는 데이터 프레임을 가지고 많이 하므로 예제 데이터 프레임을 하나 만들어보겠습니다.  'group'이라는 요인(factor)형 변수와 seq_no 이라는 시간 순서를 나타내는 변수, 그리고 각 group별로 5개씩의 관찰값을 가진 숫자형(numeric) 변수 x로 구성된 데이터 프레임입니다.

 

 

> ##-- make data frame as an example
> group <- rep(c("A", "B"), each = 5)
> seq_no <- rep(1:5, 2)
> set.seed(1234)
> x <- round(100*runif(10), 1)
> 
> x_df <- data.frame(group, seq_no, x)
> x_df
   group seq_no    x
1      A      1 11.4
2      A      2 62.2
3      A      3 60.9
4      A      4 62.3
5      A      5 86.1
6      B      1 64.0
7      B      2  0.9
8      B      3 23.3
9      B      4 66.6
10     B      5 51.4

 

 

 

 

(1) lag() 하려고 하는 기준대로 정렬이 안되어 있으면 -> 먼저 정렬(sorting) 부터!

 

예제로 사용하려고 sample(nrow()) 함수로 무작위로 순서를 섞어서 x_df_random 이라는 데이터 프레임을 만들어보았습니다.  dplyr 패키지의 arrange() 함수를 가지고 group, seq_no 기준으로 정렬을 해보겠습니다. 

 

 

> # if data frame is not ordered properly, 
> # then arrnage it first by lag criteria
> x_df_random <- x_df[sample(nrow(x_df)),]
> x_df_random
   group seq_no    x
7      B      2  0.9
5      A      5 86.1
3      A      3 60.9
10     B      5 51.4
2      A      2 62.2
9      B      4 66.6
6      B      1 64.0
1      A      1 11.4
8      B      3 23.3
4      A      4 62.3
> 
> x_df_seq <- arrange(x_df_random, group, seq_no)
> x_df_seq
   group seq_no    x
1      A      1 11.4
2      A      2 62.2
3      A      3 60.9
4      A      4 62.3
5      A      5 86.1
6      B      1 64.0
7      B      2  0.9
8      B      3 23.3
9      B      4 66.6
10     B      5 51.4

 

 

 

 

(2) mutate() 함수와 lag() 함수로 group_x, x_lag 변수 만들기

 

 

> # making lagged variable at data frame with mutate() and lag() > x_df_seq_lag <- mutate(x_df_seq, + group_lag = lag(group, 1), + x_lag = lag(x, 1)) > > x_df_seq_lag group seq_no x group_lag x_lag 1 A 1 11.4 <NA> NA 2 A 2 62.2 A 11.4 3 A 3 60.9 A 62.2 4 A 4 62.3 A 60.9 5 A 5 86.1 A 62.3 6 B 1 64.0 A 86.1 <- need to delete this row 7 B 2 0.9 B 64.0 8 B 3 23.3 B 0.9 9 B 4 66.6 B 23.3 10 B 5 51.4 B 66.6

 

 

 

 

(3) group 과 group_lag 의 값이 서로 다르면 그 행(row)은 filter() 함수로 제외하기

 

 

> # if group and group_lag are different, then delete the row
> x_df_seq_lag_2 <- x_df_seq_lag %>% 
+   filter(group == group_lag)
> 
> x_df_seq_lag_2
  group seq_no    x group_lag x_lag
1     A      2 62.2         A  11.4
2     A      3 60.9         A  62.2
3     A      4 62.3         A  60.9
4     A      5 86.1         A  62.3
5     B      2  0.9         B  64.0
6     B      3 23.3         B   0.9
7     B      4 66.6         B  23.3
8     B      5 51.4         B  66.6

 

 

 

 

(4) group별로 x와 x_lag 값의 차이가 가장 큰 값(max)과 가장 작은 값(min) 구하기

 

지지난번 포스팅에서 소개했던 group_by()와 summarise(max()), summarise(min()) 함수를 이용하면 되겠습니다.

 

> # select max and min of difference between x and x_lag
> x_df_seq_lag_2 %>% 
+   group_by(group) %>% 
+   summarise(max_lag = max(x - x_lag, na.rm = TRUE), 
+             min_lag = min(x - x_lag, na.rm = TRUE))
# A tibble: 2 x 3
   group max_lag min_lag
  <fctr>   <dbl>   <dbl>
1      A    50.8    -1.3
2      B    43.3   -63.1

 

 

 


(5) 위의 1~4를 chain operator와 group_by() 를 사용해서 한번에 모두 처리하기



library(dplyr)


## sample data

group <- rep(c("A", "B"), each = 5)

seq_no <- rep(1:5, 2)

set.seed(1234)

x <- round(100*runif(10), 1)

x_df <- data.frame(group, seq_no, x)


## max and min of (x - x_lag)

x_df %>% 

  arrange(group, seq_no) %>% 

  group_by(group) %>% 

  summarise(max_lag = max(x - lag(x, 1), na.rm = TRUE), 

            min_lag = min(x - lag(x, 1), na.rm = TRUE))


# # A tibble: 2 x 3

# group max_lag min_lag

# <chr>   <dbl>   <dbl>

#   1 A        50.8    -1.3

# 2 B        43.3   -63.1

 



그리 어렵지 않지요? 

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

 

다음번 포스팅에서는 dplry package window function 세번째로 시간으로 Cumulative aggregates 를 하는데 사용하는 cumall(), cumany(), cummean() 함수, 그리고 Recycled aggregates 에 대해서 알아보겠습니다.

 

이번 포스팅이 도움이 되었다면 아래의 '공감 ~♡'를 눌러주세요.

(Tistory 가 포스팅별로 조회수를 알려주는 기능이 없다보니 '공감♡' 개수로 참고하려고)

 

 

728x90
반응형
Posted by Rfriend
,

R dplyr 패키지에 대해서 연재를 하고 있는데요, 번 포스팅에서는 Window Function 에 대해서 소개를 하겠습니다.

 

Window function 은 n개의 input을 받아서 n 개의 output을 반환하는 함수입니다.  순위(rank)를 구하는 함수나 누적합(cumulative sum)을 구하는 함수가 대표적인 Window function입니다.

 

이와 대비되는 Aggregation function n개의 input을 받아서 1개의 output 을 반환합니다.  요약통계량의 평균(mean), 합계(sum), 개수 세기(count) 등이 Aggregation function 에 속합니다.

 

 

[ Window function vs. Aggregation function ]

 

 

 

 

R의 {dplyr} package에서 사용할 수 있는 Window function에는 (1) Ranking and Ordering, (2) Lead and Lag, (3) Cumulative aggregates, (4) Recycled aggregates 등의 4가지 유형이 있습니다.

 

 

[ Types of Window functions in R {dplyr} package ]

 

 

 

차례대로 설명을 할 건데요, 한꺼번에 모두를 소개하기는 양이 꽤 많으니 이번에는 (1) Ranking and Ordering 의 row_number(), min_rank(), dense_rank(), cume_dist(), percent_rank(), ntile() 함수에 대해서만 우선 소개해드리겠습니다.

 

 - row_number(), min_rank(), dense_rank() 의 3개 함수는 순위에 대한 index를 반환하고,  

 - cume_dist(), percent_rank() 의 2개 함수는 순위에 대한 '0~1' 사이의 비율을 반환하며,

 - ntile() 은 n개의 동일한 개수로 데이터셋을 나누어줍니다.

 

 

먼저 순위(ranking) 에 대한 index 를 반환해주는 함수 3개를 살펴보겠습니다. 동일한 값이 있을 경우에 이를 처리하는 방법이 함수별로 차이가 있습니다. 

 

말로 일일이 설명하기가 좀 어려운데요, 아래에 c(1, 1, 1, 5, 5, 9, 7) 의 동일한 값 '1'이 3개, '5'가 2개 존재하는 간단한 벡터를 가지고 함수별로 어떤 결과를 반환하는지 비교해보는 예를 준비했습니다.  색깔 칠해놓은 동일 값들을 어떻게 index 처리하는지 유심히 비교해보시고요, 분석 목적에 맞는 함수를 선택해서 사용하시기 바랍니다.

 

 

(1-1) row_number() : 순위(ranking) index 반환, 동일값에 대해서는 '1, 2, 3, ...' 처리

 

 

##--------------------------------------------
## Window Functions in {dplyr} package
## (1) ranking and ordering window functions
##--------------------------------------------

library(dplyr)

# if there are ties, ties can be handled in several ways
x <- c(1, 1, 1, 5, 5, 9, 7)

# (1-1) row_number()
row_number(x)

 

 

 

[1] 1 2 3 4 5 7 6

 

 

 

 

(1-2) min_rank() : 순위(ranking) index 반환, 동일값에 대해서는 '1, 1, 1, 4, 4,...' 처리

 

 

# (1-2) min_rank()
x <- c(1, 1, 1, 5, 5, 9, 7)

min_rank(x)

 

 

[1] 1 1 1 4 4 7 6

 

 

 

 

(1-3) dense_rank() : 순위(ranking) index 반환, 동일값에 대해서는 '1, 1, 1, 2, 2,...' 처리

 

 

# (1-3) dense_rank()
x <- c(1, 1, 1, 5, 5, 9, 7)

 

dense_rank(x)

 

 

[1] 1 1 1 2 2 4 3

 

 

 

참고로 {base} 패키지의 rank() 함수도 동일한 기능을 제공하는데요, ties.method = c("average", "first", "random", "max", "min") 매개변수 별로 어떤 결과가 나오는 지는 http://rfriend.tistory.com/28  포스팅의 제일 하단 예시를 참고하시기 바랍니다.

 

 


 

 

아래 두개의 cume_dist(), percent_rank() 함수는 순위(ranking)에 대한 0~1 사이의 비율을 반환하는데요, 둘 사이에 미묘한 차이가 있으니 유심히 살펴보시기 바랍니다.

 

(1-4) cume_dist() : 현재 값보다 작거나 동일한 값의 순위(ranking) 상의 비율 (0~1)

 

 

# (1-4) cume_dist()
# : the proportion of values less than or equal to the current value
# : proportional value from 0 to 1
x <- c(1, 1, 1, 5, 5, 9, 7)
cume_dist(x)

 

 

[1] 0.4285714 0.4285714 0.4285714 0.7142857 0.7142857 1.0000000 0.8571429

 

 

 

위의 결과가 어떻게 나왔냐 하면요, 전체 7개의 숫자 중에서 첫번째 순위에 속하는 '1'이 3개 있으므로 3/7 = 0.4285714 가 나온 것입니다.  두번째 순위에 속하는 '5'는 2개가 있으며, 5보다 작은 수로 첫번째 순위의 '1'이 3개가 있으므로 '5'의 2개와 ''1'의 3개를 합친 5개를 총 개수인 7로 나눈 5/7 = 0.7142857이 나온 것입니다. (말로 설명하기가 참 어렵네요. -_-;)

 

 

> 3/7 # ordering like as c(1, 1, 1)
[1] 0.4285714
> 5/7 # ordering like as c(1, 1, 1, 5, 5)
[1] 0.7142857
> 6/7 # ordering like as c(1, 1, 1, 5, 5, 7)
[1] 0.8571429
> 7/7 # ordering like as c(1, 1, 1, 5, 5, 7, 9)
[1] 1

 

 

 

참고로, "패키지명:::함수명"을 실행하면 R 함수의 내부 소스 코드 (generic function)를 볼 수 있습니다.

cume_dist() 함수는 {base} 패키지의 rank(x, ties.method = "max") 를 가져다고 총 개수로 나누어준 것임을 알 수 있습니다.

 

> dplyr:::cume_dist
function (x) 
{
    rank(x, ties.method = "max", na.last = "keep")/sum(!is.na(x))
}
<environment: namespace:dplyr>

 

 

 

 

 

(1-5) percent_rank() : min_rank() 기준의 순위(ranking)에 대한 비율(0~1)

 

 

# (1-5) percent_rank()
# : the percentage of the rank
# : proportional value from 0 to 1
x <- c(1, 1, 1, 5, 5, 9, 7)

 

percent_rank(x)

 

 

[1] 0.0000000 0.0000000 0.0000000 0.5000000 0.5000000 1.0000000 0.8333333

 

 

 

percent_rank() 함수의 내부 소스 코드를 들여다보면 아래와 같이 dplyr패키지의 min_rank() 값에서 1을 뺀 후에, 이를 총 관측값 개수에서 1을 뺀 값으로 나누었군요.  소스 코드를 보지 않고서는 연산 로직을 알기가 쉽지 않은 경우입니다. (위 결과가 어떻게 나온건지 한참을 고민했네요... -_-;)

 

이제 왜 첫번째 순위의 '1'이 '0.0000000'이 나왔는지 아시겠지요?  min_rank(x) 가

[1] 1 1 1 4 4 7 6

의 값을 반환하므로 분자에 해당하는 (min_rank(x) - 1) = 1 - 1 = 0 이 되어, 전체 값이 '0'이 된 것입니다.

 

 

> dplyr:::percent_rank
function (x) 
{
    (min_rank(x) - 1)/(sum(!is.na(x)) - 1)
}
<environment: namespace:dplyr>

 

 

 

 


 

 

 (1-6) ntile() : 동일한 개수를 가진 n개의 sub 데이터로 나누기

 

Cars93 데이터프레임의 가격(Price)를 기준으로 정렬(ordering)이 된 상태에서 동일한 개수를 가진 4개의 sub group으로 나누려고 할 때 ntile() 함수를 쓰면 됩니다. mutate() 함수를 사용해서 quartile 이라는 새로운 칼럼을 생성한 후에 Cars93_quartile 이라는 새로운 데이터프레임을 생성해보는 예제를 만들어보았습니다.

 

 

# (1-6) ntile() : divides the data up into n evenly sized buckets
#Price per Price Quartiles (4 evenly sized buckets)

library(MASS) # to use Cars93 data frame

 

Cars93_quartile <- Cars93[ ,c("Manufacturer", "Model", "Type", "Price")] %>%
  mutate(quartile = ntile(Price, 4))

 

 

 

 

 

 

quartile 이라는 새로운 칼럼에 가격(Price)를 기준으로 순위 정렬이 된 상태에서 4등분된 buckets별로 몇 개씩 자동차가 들어가 있는지 한번 세어보겠습니다.  (summarise(n = n()), tally(), count() 함수 중에 편한거 사용하면 되겠습니다. )  1번째 bucket 에는 24개 자동차가 들어가 있고, 나머지 3개 bucket에는 23개씩 동일하게 들어가 있습니다. (총 93개로 정확히 4등분 할 수 없는 경우라서 그렇습니다)

 

 

# counting up by quartiles
Cars93_quartile %>% group_by(quartile) %>% summarise(n = n())

 

# A tibble: 4 x 2
  quartile     n
     <int> <int>
1        1    24
2        2    23
3        3    23
4        4    23

 

Cars93_quartile %>% group_by(quartile) %>% tally() # same result
Cars93_quartile %>% count(quartile) # same result

 

 

 

이상으로 dplyr 패키지의 Window function 중에서 첫번째로 ranking and ordering 관련 함수들인 row_number(), min_rank(), dense_rank(), cume_dist(), percent_rank(), ntile() 에 대한 소개를 마치겠습니다.  많은 도움이 되었기를 바랍니다.

 

다음번 포스팅에서는 dplyr 패키지의 Window function 의 두번째 시간으로 'Lead and Lag'에 대해서 알아보겠습니다.

 

 

이번 포스팅에 도움이 되었다면 아래의 '공감 ~ ♡'를 꾹 눌러주세요. ^^

 

728x90
반응형
Posted by Rfriend
,

그동안 R dplyr package 의 기본 함수와 chaining operator (%>%) 에 대해서 알아보았습니다.

이번 포스팅에서는 R dplyr package를 사용해서 그룹별로 행의 개수 세기 (counting rows up by group using dplyr package)를 해보겠습니다.  counting 하는 것은 기본 중의 기본이라서 탐색적분석(Exploratory Data Analysis) 할 때 수시로 사용하므로 알아두면 유용하겠지요?!

혹시 chaining operator (%>%, shift+ctr+M) 에 대해서 모르는 분은 http://rfriend.tistory.com/236 포스팅을 참고하세요.  

 R dplyr package 의 summarise(n = n()), summarise(dist_n = distinct_n(factor)), tally(), count() 함수에 대해서 하나씩 예를 들면서 설명하겠습니다.  summarise(n = n()), summarise(dist_n = distinct_n(factor)), tally() 함수는 group_by()를 chaining 해서 사용하며, 마지막의 count() 함수만 group_by() chaining 없이 사용합니다.

 

[ 그룹별로 행의 개수 세기 (counting rows up by group using dplyr package) ]

 

예시에 사용할 데이터는 MASS package에 내장되어 있는 Cars93 데이터 프레임입니다.

 ##--------------------------------------------------
## counting things up by group, using dplyr package
##--------------------------------------------------

library(dplyr)
library(MASS)

str(Cars93)

 

차종(Type)별로 행의 개수 (차량의 개수)를 세어보겠습니다.

 
'data.frame':	93 obs. of  28 variables:
 $ Manufacturer      : Factor w/ 32 levels "Acura","Audi",..: 1 1 2 2 3 4 4 4 4 5 ...
 $ Model             : Factor w/ 93 levels "100","190E","240",..: 49 56 9 1 6 24 54 74 73 35 ...
 $ Type              : Factor w/ 6 levels "Compact","Large",..: 4 3 1 3 3 3 2 2 3 2 ...
 $ Min.Price         : num  12.9 29.2 25.9 30.8 23.7 14.2 19.9 22.6 26.3 33 ...
 $ Price             : num  15.9 33.9 29.1 37.7 30 15.7 20.8 23.7 26.3 34.7 ...
 $ Max.Price         : num  18.8 38.7 32.3 44.6 36.2 17.3 21.7 24.9 26.3 36.3 ...
 $ MPG.city          : int  25 18 20 19 22 22 19 16 19 16 ...
 $ MPG.highway       : int  31 25 26 26 30 31 28 25 27 25 ...
 $ AirBags           : Factor w/ 3 levels "Driver & Passenger",..: 3 1 2 1 2 2 2 2 2 2 ...
 $ DriveTrain        : Factor w/ 3 levels "4WD","Front",..: 2 2 2 2 3 2 2 3 2 2 ...
 $ Cylinders         : Factor w/ 6 levels "3","4","5","6",..: 2 4 4 4 2 2 4 4 4 5 ...
 $ EngineSize        : num  1.8 3.2 2.8 2.8 3.5 2.2 3.8 5.7 3.8 4.9 ...
 $ Horsepower        : int  140 200 172 172 208 110 170 180 170 200 ...
 $ RPM               : int  6300 5500 5500 5500 5700 5200 4800 4000 4800 4100 ...
 $ Rev.per.mile      : int  2890 2335 2280 2535 2545 2565 1570 1320 1690 1510 ...
 $ Man.trans.avail   : Factor w/ 2 levels "No","Yes": 2 2 2 2 2 1 1 1 1 1 ...
 $ Fuel.tank.capacity: num  13.2 18 16.9 21.1 21.1 16.4 18 23 18.8 18 ...
 $ Passengers        : int  5 5 5 6 4 6 6 6 5 6 ...
 $ Length            : int  177 195 180 193 186 189 200 216 198 206 ...
 $ Wheelbase         : int  102 115 102 106 109 105 111 116 108 114 ...
 $ Width             : int  68 71 67 70 69 69 74 78 73 73 ...
 $ Turn.circle       : int  37 38 37 37 39 41 42 45 41 43 ...
 $ Rear.seat.room    : num  26.5 30 28 31 27 28 30.5 30.5 26.5 35 ...
 $ Luggage.room      : int  11 15 14 17 13 16 17 21 14 18 ...
 $ Weight            : int  2705 3560 3375 3405 3640 2880 3470 4105 3495 3620 ...
 $ Origin            : Factor w/ 2 levels "USA","non-USA": 2 2 2 2 2 1 1 1 1 1 ...
 $ Make              : Factor w/ 93 levels "Acura Integra",..: 1 2 4 3 5 6 7 9 8 10 ...
 $ sub_yn            : num  0 0 0 0 0 0 0 0 0 0 ...

 

 

자, 이제 dplyr 패키지를 사용해서 실습을 해보시지요.

 

(1)  dataframe %>% group_by(factor) %>% summarise(n = n())

먼저 summarise(n = n()) 함수입니다. group_by() 와 함께 chaining 해서 사용하는 예시입니다.

 

# using summarise(n = n()) in {dplyr} package
Cars93 %>%
  group_by(Type) %>%
  summarise(n = n())

 

 

# A tibble: 6 x 2
     Type     n
   <fctr> <int>
1 Compact    16
2   Large    11
3 Midsize    22
4   Small    21
5  Sporty    14
6     Van     9

 

 

 

 (2) dataframe %>% group_by(factor) %>% summarise(n = n(), n_dist = n_distinct())

차종(Type)별로 자동차 대수(행의 개수)와 더불어서, 차종별로 유일한 제조회사(Manufacturer)의 개수를 세어서 n_distinct_maker 라는 새로운 변수를 추가해보겠습니다.

 

# adding the number of distinct manufacturers
# by using multiple summaries inside summarise()
# like summarize(n = n(), n_distinct = n_distinct())
Cars93 %>%
  group_by(Type) %>%
  summarize(n = n(),
            n_distinct_maker = n_distinct(Manufacturer)) 

 

 

# A tibble: 6 x 3
     Type     n       n_distinct_maker
   <fctr> <int>            <int>
1 Compact    16               15
2   Large    11               10
3 Midsize    22               20
4   Small    21               16
5  Sporty    14               12
6     Van     9                8

 

 

 

 (3) dataframe %>% group_by(factor) %>% tally()

 R dplyr에는 summarise(n = n()) 함수와 동일한 기능, 동일한 결과를 반환하는 또 다른 함수로 tally() 가 있습니다.  group_by() 와 함께 chaining 해서 사용합니다.

 

# using tally() in {dplyr} package
Cars93 %>%
  group_by(Type) %>%
  tally()

 

 

# A tibble: 6 x 2
     Type     n
   <fctr> <int>
1 Compact    16
2   Large    11
3 Midsize    22
4   Small    21
5  Sporty    14
6     Van     9

 

 

 (4) dataframe %>% count(factor)

마지막으로 소개할 count(factor) 함수는 group_by(factor) chaining 없이 사용합니다.  위의 3개 보다 좀더 간단하긴 한데요, 해석이나 가독성 면에서 group_by() 가 들어가게 프로그램 짜는 것을 더 선호하는 사용자도 있을 듯 합니다. 

 

# using count() in {dplyr} package}
Cars93 %>%
  count(Type) # doing both grouping and counting (no need for group_by())

 

 

# A tibble: 6 x 2
     Type     n
   <fctr> <int>
1 Compact    16
2   Large    11
3 Midsize    22
4   Small    21
5  Sporty    14
6     Van     9

 

 


 

참고로, dplyr 패키지 말고도 {base} package의 table() 함수나, {sqldf} package의 sqldf() 함수를 사용해도 그룹별 관측치 개수 세기가 가능합니다.  아래 참고하세요.

 

(대안 1) {base} package의 table() 함수

counting 결과 제시 포맷이 위의 dplyr 패키지를 사용했을 때와는 다릅니다.  dplyr 패키지를 사용한 그룹별 행의 개수 세기에서는 차종(Type)이 별도 행, count 개수 n이 별도 행으로 제시가 되었었는데요, base 패키지의 table() 함수는 아래의 예시처럼 옆으로 차종이 죽~ 늘어서 있습니다.

 

## alternative : table()

# using table() in {base} package
table(Cars93$Type)

 

 

Compact   Large Midsize   Small  Sporty     Van 
     16      11      22      21      14       9 

 

 

table() 분석 결과는 데이터 프레임이 아니라 'table'입니다.

> str(table(Cars93$Type))
 'table' int [1:6(1d)] 16 11 22 21 14 9
 - attr(*, "dimnames")=List of 1
  ..$ : chr [1:6] "Compact" "Large" "Midsize" "Small" ...

 

 

(대안 2) {sqldf} 패키지의 sqldf() 함수

 

## alternative : sqldf() 

# using {sqldf} package
install.packages("sqldf")
library(sqldf)
sqldf('
      select Type, count(*) as n
      from Cars93
      group by Type
      order by Type
      ')

 

 
     Type  n
1 Compact 16
2   Large 11
3 Midsize 22
4   Small 21
5  Sporty 14
6     Van  9

 

 

sqldf 패키지의 sqldf() 함수에 대한 좀더 자세한 사용법은 http://rfriend.tistory.com/79  포스팅을 참고하세요.

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

 

이번 포스팅이 도움이 되셨다면 아래의 '공감 ~ ♡'를 눌러주세요. ^^

728x90
반응형
Posted by Rfriend
,

초등학교 4학년에서 중학교 3학년 자녀를 둔 학부모들에게, 제가 올해 읽었던 책 중에서 인상깊었던 2권의 책을 추천하고자 합니다. 

 

(저희 집의 중2 아들과 초4 딸이 있답니다.  그래서 예사롭지 않게 읽은 책들 이예요. ㅎㅎ)

 

"중2병의 비밀", 그리고 "무기력의 비밀" 이라는 책들인데요, 모두 김현수 의사선생님께서 지은 책이고, 책 제목에 '비밀'이 들어가 있습니다.  (책 표지도 약간 비슷해요. 묵직해보이는 가방을 맨 아이 그림이 들어가 있습니다.)

 

책에 소개되어 있는 저자 김현수 선생님의 프로필을 인용해보면 아래와 같은데요, 저자 소개글을 읽고 나면 제가 길게 책의 내용을 소개하지 않아도 저자가 품어온 문제인식과 아이들과 함께하는 세상을 좀더 행복한 곳으로 만들기 위해 저자가 밟아온 삶의 궤적이 책에 대한 신뢰와 호기심을 불러 일으킬 것이라고 생각합니다.

 

  의사로서의 첫 발령지인 '소년교도소'에서 '문제행동은 심리적 구조 신호'라는 것을 느끼면서 정신의학을 지망했다. 정신과 전문의를 취득한 이후 2001년 서울 봉천동에 '사는기쁨 신경정신과'와 지역주민상담센터 '빵과영혼'을 열었고, 이듬해에는 학업을 중단한 청소년들을 위한 대안학교 '성장학교 별'을 세워 지금까지 교장을 맡고 있다. 학업 중단, 가출, 비행, 학교폭력, 인터넷 중독, 은둔형 외톨이 등 다양한 청소년들의 어려움과 함께해 왔다.

  현장에서 다양한 아픈 아이들을 마난면서 '아이가 힘든 것이 단지 부모만의 문제가 아니라 사회의 문제'라는 각성을 갖게 되어 부모 교육 지원뿐 아니라 교육부, 여성가족부 등의 정부 기관과 시민모임과 함께 활동해오기도 하였다. 또한 최근에는 '아픈 아이들이 늘어가는 교실에서 선생님도 아프다.'는 현실을 목도하면서 아이들도 행복하고 교사도 행복한 학교를 만들기 위한 과정을 모색해 오기도 하였다.

  현재는 명지병원에서 정신건강의학과장과 환자공감센터장을 맡고 있으며, 세월호 참사 과정에서는 안산 정신건강트라우마센터장으로 아픔을 함께 했다.

 

    - 출처 : 중2병의 비밀, 표지의 김현수 저자 소개 내용

 

 

 

 

중학생과는 말이 잘 안 통한다고 해서 부모나 선생님은 중학생을 가리켜 "외계인"이라고도 하고, 어디로 튈지 몰라서 "럭비공"이라고도 합니다.  교과서에서는 "사춘기" 시기를 가리켜 "질풍노도의 시기"라고도 표현해놓았습니다.

 

학부모나 선생님도 분명 이런 "질풍노도의 시기"를 거쳐왔기에 중학생 자녀를, 중학교 학생을 잘 이해할 법도 한데, 위와 같은 말이 요즘에도 여전히 유효하게 쓰이는 걸 보면 실상은 그렇지 못한 경우가 많은것 같습니다. ("요즘 애들 버릇없어"라는 말이 2천년 전이나, 요즘이나 계속 쓰이고 있듯이 말이죠...)

 

중학생 자녀를 둔 부모라면 누구나 다 자녀를 좀더 잘 이해하고, 자녀와 소통하고, 자녀가 힘들어하는 것이 있다면 도와주고 지원해줄 수 있기를 바랄것이라고 봐요.  그런데 이때 '내가 사춘기 때에는 나는 안그랬던 것 같은데... 내가 살던 시절에는 안 그랬는데...' 이렇게 시작하면 아마도 한발짝도 진전을 보이기 힘들겁니다.  왜냐하면 학부모 세대가 살던 시절이랑 "요즘" 아이들이 사는 시대가 많이 다르기 때문입니다. 

 

아래의 "중2병의 비밀" 의 목차를 한번 찬찬히 읽어보시기 바랍니다.  그러면 대략 이 책이 무슨 얘기를 하고 있구나 감을 잡으실 수 있을 겁니다.  그리고 소위 "요즘" 아이들을 이해하려면 차분히 "요즘 아이들 설명서"를 가지고 공부를 해보는 것도 나쁘지 않은 선택이라고 생각합니다.  이 책의 저자가 그동안 숱하게 "요즘 아이들"과 상담하면서 보고, 듣고, 느낀 생생한 현장의 기록들이 담긴 책이라면 말이지요.

 

 



[ "중2병의 비밀" 목차 ]


1. 작은 가족이 주는 외로움 
    : 요즘 아이들은 형제 없이 사춘기를 겪어내야 한다.
 
2. 정서적 외로움
    : 마음을 놓치면 아이도 놓친다.
 
3. 자신감이 없을 때의 외로움
    : 집에서는 '왕자', 학교에서는 '엑스트라'
 
4. 잘하지 못할 때의 외로움
    : 15세의 위기, 벼락치기로 쫒아갈 수 없는 세상
 
5. 변화된 몸이 주는 외로움
    : 올라오는 성적 욕구, 누구와 이야기해야 하나요?
 
6. 적응 안 되는 몸이 주는 외로움
    : "몸이 근질근질한데 어쩌라고요!"
 
7. 존중받지 못할 때의 외로움
    : "제발 나만의 영역을 존중해주세요"
 
8. 세대 차이를 느낄 때의 외로움
    : 최고와 최선에 대한 시각 차이
 
9. 마음을 나눌 대상이 없을 때의 외로움
    : 관심은 Yes, 간섭은 NO!
 
10. 소속감이 없을 때의 외로움
    : "친구는 또 다른 나, 방해꾼이 아니에요"
 
11. 덜 자란 전두엽이 만드는 외로움
    : 문제행동 뒤에는 호르몬이 있다
 
12. 중2병은 잘못된 사회를 향한 아이들의 메시지입니다.


 

 

 

 

책 중간 중간에 삽화도 들어가 있고, "요즘" 아이들의 생생한 목소리도 들을 수 있다보니 딱딱한 이론서를 보는 듯한 난해함이나 '이거 나랑은 상관없는데...'라는 괴리감은 느끼기 힘들거예요.

 

 

 

책이 대강당에서 학부모들을 대상으로 강연회를 한 내용처럼 구어체로 되어있다거나, 수강 중인 학부모로부터 질문을 받고 저자가 답변하는 대화체로 되어있는 부분도 있고 해서 쉽게 빨리 빨리 읽힙니다.  다만 내용이 내용이다 보니 아마 짐작컨데 책을 읽는 중간 중간 '우리 아이는 어떻지?  나는 우리 아이에게 어떻지?  음... 그동안 내가 이게 문제였나?  이걸 몰랐었네?  오늘 오후에 아이가 돌아오면 이렇게 얘기를 해볼까? 다음번에는 이렇게 해볼까? ...' 등 등 많은 생각이 꼬리에 꼬리를 물 것이라고 예상합니다.

 

 

"중2병의 비밀" 책은 "자녀가 중학생이 된다는 것은 이별을 시작한다는 것입니다"라는 프롤로그로 시작하며, "자녀가 중학생이 된다는 것은 새로운 만남을 시작하는 것입니다"라는 에필로그로 끝납니다.

 

책의 목차에서도 보면 온통 '외로움'이라는 말로 도배가 되어 있는데요, 자녀가 중학생이 되면서 '이별'이 시작되는 와중에 자녀도 외롭고, 부모도 외롭고, 모두가 외로운 상황이 연출됩니다.  이 외로운 이별을 슬기롭게 대처하면 '새로운 만남'으로 넘어갈 수 있을 것입니다.

 

 

"중2병의 비밀"의 저자가 얘기해주는 "아이와의 정서적 교감을 높이기 위해 부모가 명심해야 할 자기점검 Tip"을 아래에 공유하오니 한번 거울에 자신의 모습을 비추어보는 시간을 가져보시면 좋겠습니다.  

  • 사춘기에 들어선 아이들은 이제 어린이가 아니라는 것을 확실히 인정하고 대해주어야 합니다.
  • 행동이나 과제를 점검하는 것보다 아이의 마음을 살피는 것이 먼저라는 것을 명심해주세요.
  • 아이들과 대화를 하려면 말하기보다 듣기가 먼저라는 것을 명심하세요.
  • 사춘기가 시작되면 부모의 기대가 더 큰 부담으로 다가온다는 것을 명심하세요.
  • 아이의 문제가 크게 다가오면, 혹시 내가 문제를 너무 크게 보는 것은 아닌가도 점검해주세요.

                - 출처 : "중2병의 비밀", 김현수 지음, Denstory, 2015, p61

 

 


 

두번째 추천 책인 "무기력의 비밀"로 넘어가 보겠습니다.

 

저자 김현수 선생님이 요 몇년 들어서 가장 많이 강연 요청을 받는 주제 중의 하나가 바로 "무기력한 아이들을 어떻게 해야 할까요?"라고 합니다.

 

신문 지상이나 방송, 책에도 보면 '3포 세대', '5포 세대', 'N포 세대'라는 말이 보통명사화 되어서 일상적으로 사용되고 있는 실정입니다.

 

혹시 자녀 중에 "나 좀 내버려 둬", "아무 것도 하고 싶지 않아", "난 하고 싶은게 없어", "난 할 줄 아는게 하나도 없어", "몰라요"라는 말을 입에 달고 사는 아이가 있나요?  혹시 자녀가 학교에 가면 잠만 자고 오는 아이, 아니면 학교 가기 싫다고 하는 아이가 있나요?  그렇다면 "무기력의 비밀"이라는 책이 작은 도움이나마 될 수 있을것 같습니다.

 

저자는 아이들의 무기력에 대해서 "이것은 비명이요, 무기력한 아이들의 침묵은 더 큰 마음의 목소리다. 희망 없음(hopeless)을, 스스로가 스스로에게 도움을 줄 수 없는 상태(helpless)임을, 자신을 포기하고 싶다는 것을, 알리는 신호다"라고 말하고 있습니다. 

 

이는 무기력에 대한 관점을 완전히 바꾼 것이라고 할 수 있습니다.  사랑하는 자녀가 비명을 지르고, SOS 신호를 보내고 있는데 이를 무시하거나 혹은 화내고 혼내기만 한다고 자녀가 바뀔거 같지는 않습니다.  저자는 아이들의 무기력을 응급상황에 비유하면서 "마음의 심폐소생술"이 필요하다고 말하고 있습니다.  위급환자에게 심폐소생술을 한다고 해서 위급환자가 완전히 치유되는 것은 아니지만, 그렇다고 심폐소생술을 하지 않으면 그 위급환자는 자칫 죽음이나 치명상을 입게 되어 완치와 행복한 삶을 기약할 수 없게 되겠지요.

 

저자 역시 이 책에서 말하는 '역설과 긍정', '환대, 참여, 존중', '격려', '지원' 등의 방법이 무기력한 아이를 완전히 생기발랄하고 희망 가득한 아이도 돌변시킬 수 있을 것이라고는 말하지 않습니다.  도리어 아이에 대한 신뢰를 가지고 존중해주면서 기다려주지 않으면, 너무 성급하게 아이가 변하기를 바라면 안된다고 강조하고 있습니다.  그리고 '사회 구조적인 시스템'의 문제 또한 같이 고민을 해야 하고 '개인 차원, 가정 차원의 노력'과 함께 '사회적인 변화'가 병행되어야 한다고도 말하고 있습니다. (사회 구조적인 시스템의 문제와 해법에 대해서는 다른 책을 통해서 말하겠다고 하니 기대가 됩니다)

 

어찌 되었든 응급처치를 위한 "마음의 심폐소생술"이 꼭 필요한 것은 부정 못할 것입니다.

 

 

"무기력의 비밀" 목차를 한번 보시지요.

 



[ "무기력의 비밀" 목차 ]


Part 1. 무기력 시스템 이해하기


01. 무기력 상태 이해하기
02. 원인에 따라 나타나는 무기력 현상
03. 관점의 전환, 무기력의 숨은 의미
04. 무기력은 어떻게 형성되는가? (1) 사회적 무기력
05. 무기력은 어떻게 형성되는가? (2) 가정과 학교에서의 무기력
06. 무기력의 심리유형별 특징
07. 무능함을 보여주는 회피와 4가지 패러다임




Part 2. 무기력한 아이들 돕기 - 잠자는 거인을 깨우는 법


여는 글 : 한 번에 한 명씩 구출하기


01. 변화를 이끄는 마음의 심폐소생술 (1) 역설과 긍정
02. 변화를 이끄는 마음의 심폐소생술 (2) 환대, 참여, 존중
03. 무기력에서 벗어나 다시 살도록 돕기 - 격려
04. 무기력한 아이들에게 다가가는 유형별 방법
05. 무기력한 아이들을 돕는 지원 전략
    : 회복탄력성 발휘하도록 돕기
    : 관계를 통해 도약하기
    : 성취감이라는 기름 붓기


닫는 글 : 나는 내 삶을 마음껏 살아보았나?
 

 

 

 


 

사족을 덧붙이자면, 정지우 감독의 "4등" 이라는 영화도 위의 책들 "중2병의 비밀", "무기력의 비밀"과 함께 패키지로 보면 좋을 것 같습니다.

 

이 영화 포스터에 자그마하게 써진 부제목 "난 수영이 좋은데 꼭 1등만 해야 하나요?"가 큰 울림으로 다가옵니다.

 

아이들이 분면 "좋아하는 것", "잘 하는 것"이 있을 텐데요, 그걸 아이들이 자발적으로 해보고, 찾아보고, 겪어보고, 시행착오도 해보고, 실패도 해보고 하는게 원래 누구나가 살면서 성장해나가는 자연스러운 과정일텐데요, 그걸 못 기다려주고, 못 믿어주고, 자녀가 해야 할 성장통을 부모가 대신 의사결정해주고 "내가 시키는 대로 나를 따르라"고만 하는게 요즘의 우리 세대의 부모들이 아닌가 싶어요.

 

 

 

 

 

영화 "4등", 요즘의 우리나라의 자녀 교육의 현실을 가감없이 서늘하게 묘사해놓기도 했고, 엄마와 주인공 아들이 주고 받는 대화, 엄마와 수영 코치의 대화, 엄마와 주인공 아들의 동생이 주는 받는 대화 속에서 많은 생각할 꺼리, 부모로서 자신을 돌아볼 꺼리를 주는 영화였습니다. (더 이상 내용을 쓰면 스포일러가 될 듯 하니 여기까지만... ^^;)

 

아무쪼록, 중학생 자녀를 두신 학부모라면 자녀를 좀 더 잘 이해하고, 그래서 자녀와 더 행복한 일상 꾸러나가는데 이번 포스팅이 조금이나마 도움이 되는 안내가 되었으면 하는 바램입니다.

728x90
반응형
Posted by Rfriend
,

아래의 예제와 같이 콤마로 구분되어 있는 문자형 변수(caracter variable with comma seperator delimiter)를 가진 데이터 프레임이 있다고 합시다.

 

두번째의 item 변수에 콤마로 구분되어 들어있는 다수의 항목들을 구분자 콤마(',')를 기준으로 분리(split)한 후에 => 이를 동일한 name을 key로 해서 세로로 주욱~ 늘여서 재구조화하여야 할 필요가 생겼다고 합시다.

 

데이터 구조변환의 전/후 모습을 말로 설명하려니 좀 어려운데요, 아래의 'Before (original dataset) => After (transformation)' 의 그림을 참고하시기 바랍니다.

 

 

 

이걸 R로 하는 방법을 소개합니다.

 


 

먼저 위의 Before 모양으로 간단한 데이터 프레임을 만들어보았습니다.

 


 ##--------------------------------------------------------
## splitting character of dataframe and reshaping dataset
##--------------------------------------------------------

 

# (1) creating sample dataframe
name_1 <- c("John")
item_1 <- c("Apple,Banana,Mango")

name_2 <- c("Jane")
item_2 <- c("Banana,Kiwi,Tomato,Mango")

name_3 <- c("Tom")
item_3 <- c("Apple,Tomato,Cherry,Milk,IceCream")

tr_1 <- cbind(name_1, item_1)
tr_2 <- cbind(name_2, item_2)
tr_3 <- cbind(name_3, item_3)
mart <- data.frame(rbind(tr_1, tr_2, tr_3))

 

# rename
install.packages("dplyr")
library(dplyr)
mart <- rename(mart,
               name = name_1,
               item = item_1)

 

#-------------

> mart
  name                              item
1 John                Apple,Banana,Mango
2 Jane          Banana,Kiwi,Tomato,Mango
3  Tom Apple,Tomato,Cherry,Milk,IceCream
 

 

 

 


 

위의 mart 라는 이름의 'Before' 데이터프레임을 'After' 모양의 데이터 프레임으로 구조 변환시키는 절차 및 R script는 아래와 같습니다.

 

 

[ 문자형 분리(split) 및 데이터 재구조화(reshape) 절차 ]

 

(1) mart_new <- data.table() : mart_new 라는 비어있는 데이터 테이블, 데이터 프레임을 생성  (<= 여기에 차곡차곡 loop돌린 결과를 rbind()로 쌓아가게 됨)

 

(2) n <- nrow(mart) : original dataset인 mart 의 총 행의 개수(nrow(mart))를 n 이라는 벡터로 할당 (<= 이 개수만큼 loop를 돌릴겁니다. 위 예제는 총 3개군요. John, Jane, Tom 이렇게요)

 

(3) for (i in 1:n)  : 1부터 n (즉, 3)까지 반복 수행 <= 매 행별로 아래의 문자열을 구분자를 기준으로 분리 후 세로로 재구조화하는 작업을 반복할 겁니다

 

(4) print(i) : 반복수행 loop 문이 어디쯤 돌고 있나 콘솔 창에서 확인하려고 집어넣었습니다. 굳이 없어도 됩니다.

 

(5) name_index <- as.character(mart[i, 1])  : i번째 행(row)의 1번째 열("name")을 indexing 해옵니다. 즉, i의 순서 1, 2, 3 별로 "John", "Jane", "Tom", 이렇게 순서대로 indexing 해옵니다.  

 

(6) item_index <- as.character(mart[i, 2])  : i번째 행(row)의 2번째 열("item")을 indexing 해옵니다. 

 

(7) item_index_split_temp <- data.frame(strsplit(item_index, split = ',')) : (6)번에서 i번째 행별로 indexing해 온 item을 구분자 'comma (',')'를 기준으로 문자열을 분리(split)한 후에, 이것을 데이터 프레임으로 생성합니다.

 

(8) mart_temp <- data.frame(cbind(name_index, item_index_split_temp))  : (5)번과 (7)번 결과물을 cbind()로 묶은 후에, 이것을 데이터 프레임으로 생성합니다.  cbind()로 결합할 때 name은 1개인데 item이 2개 이상일 경우에는 1개밖에 없는 name이 자동으로 반복으로 item의 개수만큼 재할당이 됩니다.

 

(9) names(mart_temp) <- c("name", "item")  : mart_temp의 변수 이름을 첫번째 변수는 "name", 두번째 변수는 "item"으로 지정해줍니다.

 

(10) mart_new <- rbind(mart_new, mart_temp) : (1)번에서 생성한 (처음에는 비어있는) 데이터프레임에 loop를 돌 때마다 생성이되는 (8, 9)번 데이터프레임을 차곡 차곡 rbind()로 반복적으로 쌓아줍니다.

(11) rm(name_index, item_index, item_index_split_temp, mart_temp)  :  중간 임시 데이터셋 삭제

 

끝.

 

 

[ R script ]

 

 

# (2) splitting charater by delimeter, reshaping data structure by loop programming

install.packages("data.table")
library(data.table)

mart_new <- data.table() # blank data.table

 

n <- nrow(mart) # total number of rows

 

for (i in 1:n){
 

  print(i) # to check progress
 
  name_index <- as.character(mart[i, 1])
  item_index <- as.character(mart[i, 2])
 
  item_index_split_temp <- data.frame(strsplit(item_index, split = ','))
  mart_temp <- data.frame(cbind(name_index, item_index_split_temp))
 
  names(mart_temp) <- c("name", "item")
 
  mart_new <- rbind(mart_new, mart_temp)
}
 

rm(name_index, item_index, item_index_split_temp, mart_temp) # delete temp dataset

 

# ----------------

> mart_new
    name     item
 1: John    Apple
 2: John   Banana
 3: John    Mango
 4: Jane   Banana
 5: Jane     Kiwi
 6: Jane   Tomato
 7: Jane    Mango
 8:  Tom    Apple
 9:  Tom   Tomato
10:  Tom   Cherry
11:  Tom     Milk
12:  Tom IceCream
 

 

 

 


 

Ashtray 님께서 댓글로 남겨주신 R script를 여러 사람이 좀더 쉽게 널리 공유할 수 있도록 아래에 공유합니다 (Ashtray님, R script 공유해주셔서 감사합니다. ^^).

 

구분자가 "\\^"를 기준으로 문자열을 분리하고, for loop 문을 사용해서 세로로 세워서 재구조화하는 절차는 같습니다.  대신 변수 개수가 다르다보니 for loop문이 조금 다르구요, data table이 아니라 list()를 사용했다는점도 다릅니다. 

 

R script 짜다보면, 그리고 다른 분께서 R script 짜놓은것을 보면 "동일한 input과 동일한 output이지만 가운데 process는 방법이 단 한가지만 있는게 아니구나" 하고 저도 배우게 됩니다.

 

 

names(data) <- c("number", "PK", "Call_ID", "Keyword_Count")
data$Keyword_Count <- strsplit(data$Keyword_Count, split="\\^")

datalist <- list()
k <- 1
for(i in 1:nrow(data)){
    for(j in 1:length(unlist(data$Keyword_Count[i]))){
        datalist[[k]] <- list(data$number[i], data$PK[i], data$Call_ID[i],
        data$Keyword_Count[[i]][j])
    k <- k+1
    }
}

data2 <- rbindlist(datalist)
data2[[4]] <- unlist(data2[[4]])

 

 

 


 

혹시 Spark을 사용하는 분이라면 (key, value) pair RDD로 구성된 데이터셋에 대해 flatMapValues() 라는 pair operation 을 사용하면 쉽게 key를 기준으로 세로로 재구성한 RDD를 만들 수 있습니다.  참고하세요.

 

 

 

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

 

이번 포스팅이 도움이 되었다면 아래의 '공감 ~ ♡'를 꾸욱 눌러주세요.

 

 

[Reference]

1. R 문자함수 : http://rfriend.tistory.com/37

2. R 반복 연산 loop 프로그래밍 : http://rfriend.tistory.com/90

 

 

728x90
반응형
Posted by Rfriend
,

R dplyr 패키지의 select() 함수를 쓰다보면 'Error in select() : Unused arguments in select() error' 가 발생하는 경우가 있는데요, 그 이유와 해결방법 3가지를 소개하겠습니다.

 

 

 

 

먼저 R dplyr 패키지를 로딩하고, 실습 예제로 사용할 MASS 패키지의 Cars93 데이터셋에 대해 살펴보면 아래와 같습니다.  (Cars93은 자동차 93개의 관측치, 차량 정보 27개의 변수를 가진 데이터 프레임).

 

> library(dplyr)
> library(MASS) # dataframe Cars93
> str(Cars93)
'data.frame':	93 obs. of  27 variables:
 $ Manufacturer      : Factor w/ 32 levels "Acura","Audi",..: 1 1 2 2 3 4 4 4 4 5 ...
 $ Model             : Factor w/ 93 levels "100","190E","240",..: 49 56 9 1 6 24 54 74 73 35 ...
 $ Type              : Factor w/ 6 levels "Compact","Large",..: 4 3 1 3 3 3 2 2 3 2 ...
 $ Min.Price         : num  12.9 29.2 25.9 30.8 23.7 14.2 19.9 22.6 26.3 33 ...
 $ Price             : num  15.9 33.9 29.1 37.7 30 15.7 20.8 23.7 26.3 34.7 ...
 $ Max.Price         : num  18.8 38.7 32.3 44.6 36.2 17.3 21.7 24.9 26.3 36.3 ...
 $ MPG.city          : int  25 18 20 19 22 22 19 16 19 16 ...
 $ MPG.highway       : int  31 25 26 26 30 31 28 25 27 25 ...
 $ AirBags           : Factor w/ 3 levels "Driver & Passenger",..: 3 1 2 1 2 2 2 2 2 2 ...
 $ DriveTrain        : Factor w/ 3 levels "4WD","Front",..: 2 2 2 2 3 2 2 3 2 2 ...
 $ Cylinders         : Factor w/ 6 levels "3","4","5","6",..: 2 4 4 4 2 2 4 4 4 5 ...
 $ EngineSize        : num  1.8 3.2 2.8 2.8 3.5 2.2 3.8 5.7 3.8 4.9 ...
 $ Horsepower        : int  140 200 172 172 208 110 170 180 170 200 ...
 $ RPM               : int  6300 5500 5500 5500 5700 5200 4800 4000 4800 4100 ...
 $ Rev.per.mile      : int  2890 2335 2280 2535 2545 2565 1570 1320 1690 1510 ...
 $ Man.trans.avail   : Factor w/ 2 levels "No","Yes": 2 2 2 2 2 1 1 1 1 1 ...
 $ Fuel.tank.capacity: num  13.2 18 16.9 21.1 21.1 16.4 18 23 18.8 18 ...
 $ Passengers        : int  5 5 5 6 4 6 6 6 5 6 ...
 $ Length            : int  177 195 180 193 186 189 200 216 198 206 ...
 $ Wheelbase         : int  102 115 102 106 109 105 111 116 108 114 ...
 $ Width             : int  68 71 67 70 69 69 74 78 73 73 ...
 $ Turn.circle       : int  37 38 37 37 39 41 42 45 41 43 ...
 $ Rear.seat.room    : num  26.5 30 28 31 27 28 30.5 30.5 26.5 35 ...
 $ Luggage.room      : int  11 15 14 17 13 16 17 21 14 18 ...
 $ Weight            : int  2705 3560 3375 3405 3640 2880 3470 4105 3495 3620 ...
 $ Origin            : Factor w/ 2 levels "USA","non-USA": 2 2 2 2 2 1 1 1 1 1 ...
 $ Make              : Factor w/ 93 levels "Acura Integra",..: 1 2 4 3 5 6 7 9 8 10 ...

 

 

 

 

(1) dplyr 패키지 select() 함수, unused arguments() error 가 왜 발생하나?

 

> # Error in select() of dplyr package
> select(Cars93, Price, MPG.highway)
Error in select(Cars93, Price, MPG.highway) : 
  unused arguments (Price, MPG.highway) 

 

dplyr select() 함수를 사용해서 변수를 선택하고 싶은데 위처럼 'Error in select() : unused arguments()' 에러가 발생하는 이유는 'MASS' 패키지의 select()함수와 'dplyr' 패키지의 select() 함수가 충돌하기 때문입니다.

 

 

 

해결 방법은 3가지가 있습니다.

 

(방법 1) dplyr::select()   <= select는 dplyr 패키지의 select 임을 명시적으로 지정

 

> # (a) 1st solution for Error in select() of dplyr package > dplyr::select(Cars93[c(1:6), ], Price, MPG.highway) Price MPG.highway 1 15.9 31 2 33.9 25 3 29.1 26 4 37.7 26 5 30.0 30 6 15.7 31 

 

만약 MASS 패키지의 select() 함수를 쓰고 싶다면 MASS::select() 라고 해주면 됩니다.  매번 select() 함수 앞에 패키지명을 붙이려면 좀 번거롭겠지요?  

 

아래의 (방법 2)로 설정해주면 select() 함수는 계속 dplyr 패키지의 것으로 사용할 수 있게 됩니다.  

 

 

 

 

(방법 2) select <- dplyr::select    <= select는 dplyr 패키지의 select 임을 명시적으로 지정

 

> # (b) 2nd solution for Error in select() of dplyr package > select <- dplyr::select > head(select(Cars93, Price, MPG.highway)) Price MPG.highway 1 15.9 31 2 33.9 25 3 29.1 26 4 37.7 26 5 30.0 30 6 15.7 31 

 

 

 

 

(방법 3) MASS 패키지를 먼저 로딩하고, dplyr 패키지를 나중에 로딩하기

 

> # (c) 3rd solution for Error in select() of dplyr package
> library(MASS) 
> library(dplyr) # dplyr loading after MASS
> head(select(Cars93, Price, MPG.highway))
  Price MPG.highway
1  15.9          31
2  33.9          25
3  29.1          26
4  37.7          26
5  30.0          30
6  15.7          31 

 

어떤 때는 에러가 없다가도 어떤 때는 에러가 발생하는 이유가 패키지 로딩 순서에도 있습니다.  이렇다 보니 R 사용자 입장에서는 '어? 지난번에는 에러가 안났는데...이거 뭐가 잘못된거지?' 하면서 당황하게 되는거 같습니다.

 

MASS 패키지의 select() 함수, pylr 패키지의 summarise() 함수, stats 패키지의 filter() 함수 등이 dplyr 패키지의 동일한 이름의 함수와 충돌하곤 합니다.  패키지간 함수 충돌로 인한 'Error in function : unused arguments'에러가 발생하면 위의 3가지 방법을 이용해서 해결해보세요.

 

 

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

 

이번 포스팅이 도움이 되셨다면 아래의 '공감 ~ ♡'를 꾸욱 눌러주세요. ^^

 

728x90
반응형
Posted by Rfriend
,

지난번 포스팅에서는 dplyr 패키지의

 

(1) filter(), slice(), arrange(), select(), rename() 함수 ( ☞ 바로 가기)

(2) distinct(), sample_n(), sample_frac(), mutate(), transmute(), summarise(), summarise_each(), group_by() 함수 ( ☞ 바로 가기) 에 대해서 알아보았습니다.

 

이번 포스팅에서는 dplyr 패키지의 chain operations (Operator %>%, shift+ctrl+M)에 대해서 알아보겠습니다. 

 

저는 R의 다른 함수를 사용하다가 처음으로 dplyr 패키지의 chain operation (%>%), pipe operator (%>%)을 접하면 '어, 이거 뭐지? 오타인가?' 하는 당혹감이 들었었습니다.  그런데 이게 쓰다보니 참 편하고 프로그램 가독성이 좋더군요.  R로 프로토타입핑 해놓고 시스템화를 위해 개발자에게 설명해줄 때도 개발자가 쉽게 이해를 하고요.

 

 

마치 ggplot2가 시각화를 문법으로 체계화했듯이 dplyr도 그동안 dplyr (1), (2)번 포스팅에서 다루었던 dplyr 함수들을 이번 시간에 소개할 chain operation으로 굴비 꿰듯이 엮어서 물 흐르듯이 데이터 조작, 전처리를 할 수 있는 매끈한 문법을 생성하게 됩니다.

 

 

 

먼저 R dplyr 패키지를 로딩하고, 실습 예제로 사용할 MASS 패키지의 Cars93 데이터셋에 대해 살펴보면 아래와 같습니다.  (Cars93은 자동차 93개의 관측치, 차량 정보 27개의 변수를 가진 데이터 프레임).

 

> library(dplyr)
> library(MASS) # dataframe Cars93
> str(Cars93)
'data.frame':	93 obs. of  27 variables:
 $ Manufacturer      : Factor w/ 32 levels "Acura","Audi",..: 1 1 2 2 3 4 4 4 4 5 ...
 $ Model             : Factor w/ 93 levels "100","190E","240",..: 49 56 9 1 6 24 54 74 73 35 ...
 $ Type              : Factor w/ 6 levels "Compact","Large",..: 4 3 1 3 3 3 2 2 3 2 ...
 $ Min.Price         : num  12.9 29.2 25.9 30.8 23.7 14.2 19.9 22.6 26.3 33 ...
 $ Price             : num  15.9 33.9 29.1 37.7 30 15.7 20.8 23.7 26.3 34.7 ...
 $ Max.Price         : num  18.8 38.7 32.3 44.6 36.2 17.3 21.7 24.9 26.3 36.3 ...
 $ MPG.city          : int  25 18 20 19 22 22 19 16 19 16 ...
 $ MPG.highway       : int  31 25 26 26 30 31 28 25 27 25 ...
 $ AirBags           : Factor w/ 3 levels "Driver & Passenger",..: 3 1 2 1 2 2 2 2 2 2 ...
 $ DriveTrain        : Factor w/ 3 levels "4WD","Front",..: 2 2 2 2 3 2 2 3 2 2 ...
 $ Cylinders         : Factor w/ 6 levels "3","4","5","6",..: 2 4 4 4 2 2 4 4 4 5 ...
 $ EngineSize        : num  1.8 3.2 2.8 2.8 3.5 2.2 3.8 5.7 3.8 4.9 ...
 $ Horsepower        : int  140 200 172 172 208 110 170 180 170 200 ...
 $ RPM               : int  6300 5500 5500 5500 5700 5200 4800 4000 4800 4100 ...
 $ Rev.per.mile      : int  2890 2335 2280 2535 2545 2565 1570 1320 1690 1510 ...
 $ Man.trans.avail   : Factor w/ 2 levels "No","Yes": 2 2 2 2 2 1 1 1 1 1 ...
 $ Fuel.tank.capacity: num  13.2 18 16.9 21.1 21.1 16.4 18 23 18.8 18 ...
 $ Passengers        : int  5 5 5 6 4 6 6 6 5 6 ...
 $ Length            : int  177 195 180 193 186 189 200 216 198 206 ...
 $ Wheelbase         : int  102 115 102 106 109 105 111 116 108 114 ...
 $ Width             : int  68 71 67 70 69 69 74 78 73 73 ...
 $ Turn.circle       : int  37 38 37 37 39 41 42 45 41 43 ...
 $ Rear.seat.room    : num  26.5 30 28 31 27 28 30.5 30.5 26.5 35 ...
 $ Luggage.room      : int  11 15 14 17 13 16 17 21 14 18 ...
 $ Weight            : int  2705 3560 3375 3405 3640 2880 3470 4105 3495 3620 ...
 $ Origin            : Factor w/ 2 levels "USA","non-USA": 2 2 2 2 2 1 1 1 1 1 ...
 $ Make              : Factor w/ 93 levels "Acura Integra",..: 1 2 4 3 5 6 7 9 8 10 ...

 

 

 

R dplyr Chain operations (Chaining) : Operator %>% (단축키 shift+ctrl+M)

 

 

(1) 어떨 때 chain operations 이 유용한가?

 

(1-1) 여러 단계의 절차를 필요로 해서 중간 결과를 저정 후 그 객체를 후속 절차에서 받아서 사용해야 하는 경우의 프로그래밍의 경우 chain operation %>%이 유용합니다.

 

예를 들어, "Cars93 데이터프레임에서 차생산국가(Origin), 차종(Type), 실린더개수(Cylinders)별로 차가격(Price)과 고속도로연비(MPG.highway)의 평균을 구하되, 차가격 평균 10 초과 or 고속도로연비 25 초과하는 경우만 선별해서 제시하시오"라는 문제가 있다고 합시다.

 

이 예제 문제를 단계별로 중간 산출물을 저장하고, 이를 후속 단계에서 받아서 프로그래밍해보면 아래와 같습니다.  프로그래밍이 길기도 하고, 단계가 하나씩 늘어날수록 헷갈리고 복잡해지지요?

 

 

# why chaining?  when to use chain operation?

# : (1) step-by-step operation, saving the intermediate results
 

select <- dplyr::select # assigning select() functon of dplyr package explicitly

 

a1 <- group_by(Cars93, Origin, Type, Cylinders)
a2 <- select(a1, Price, MPG.highway)
a3 <- summarise(a2,
                Price_m = mean(Price, na.rm = TRUE),
                MPG.highway_m = mean(MPG.highway, na.rm = TRUE))
a4 <- filter(a3, Price_m > 10 | MPG.highway_m > 25)

 

 

> a4
Source: local data frame [25 x 5]
Groups: Origin, Type [11]

   Origin    Type Cylinders  Price_m MPG.highway_m
   <fctr>  <fctr>    <fctr>    <dbl>         <dbl>
1     USA Compact         4 12.82857      30.57143
2     USA   Large         6 22.40000      27.28571
3     USA   Large         8 27.62500      25.75000
4     USA Midsize         4 15.87500      29.50000
5     USA Midsize         6 22.84000      27.20000
6     USA Midsize         8 40.10000      25.00000
7     USA   Small         4 10.04286      33.85714
8     USA  Sporty         4 14.60000      28.75000
9     USA  Sporty         6 19.53333      26.66667
10    USA  Sporty         8 38.00000      25.00000
# ... with 15 more rows

 

 

 

 

(1-2) 여러 단계의 각 단계를 괄호로 싸서 프로그래밍하다보니 깊이가 깊어져서 해석하고 이해하기 어려운 경우, dplyr의 chain operation %>% (pipe operator) 이 유용합니다.

 

아래의 R script는 (1-1)과 동일한 결과를 내는 프로그래밍인데요, 4 depth 까지 들어가다보니 안쪽에서 부터 바깥쪽으로 순서대로 해독하기가 많이 어렵습니다. @@~

 

 

# why chaining?  when to use chain operation?
# : (2) Wrapping the function calls inside each other is hard to interpret

filter(
  summarise(
    select(
      group_by(Cars93, Origin, Type, Cylinders),
      Price, MPG.highway
    ),
    Price_m = mean(Price, na.rm = TRUE),
    MPG.highway_m = mean(MPG.highway, na.rm = TRUE)
  ),
  Price_m > 10 | MPG.highway_m > 25

 

 

Adding missing grouping variables: `Origin`, `Type`, `Cylinders`
Source: local data frame [25 x 5]
Groups: Origin, Type [11]

   Origin    Type Cylinders  Price_m MPG.highway_m
   <fctr>  <fctr>    <fctr>    <dbl>         <dbl>
1     USA Compact         4 12.82857      30.57143
2     USA   Large         6 22.40000      27.28571
3     USA   Large         8 27.62500      25.75000
4     USA Midsize         4 15.87500      29.50000
5     USA Midsize         6 22.84000      27.20000
6     USA Midsize         8 40.10000      25.00000
7     USA   Small         4 10.04286      33.85714
8     USA  Sporty         4 14.60000      28.75000
9     USA  Sporty         6 19.53333      26.66667
10    USA  Sporty         8 38.00000      25.00000
# ... with 15 more rows

 

 

 

 

(2) dplyr 패키지의 chain operations은 어떻게 사용하는가? 

 

chain(pipe) operator 는 %>% 이며, 단축키는 shift+ctrl+M 입니다.

 

dataframe %>% group_by() %>% select() %>% summarise() %>% filter() 의 순서로 사용하시면 됩니다.  의도하는 분석 결과를 논리적인 순서대로 찬찬히 생각해보면서 프로그래밍하시면 됩니다. 

 

예를 들면 아래처럼요,

 

"(a) Cars93 데이터프레임에서  %>%  (b) 제조생산국(Origin), 차종(Type), 실린더개수(Cylinders)별로   %>%   (c) 차 가격(Price)과 고속도로 연비(MPG.highway) 변수에 대해   %>%   (d)  (결측값은 제외하고) 평균을 구하는데,   %>%   (e) 단, 가격 평균은 10을 넘거나 or 고속도로 연비는 25를 넘는 것만 알고 싶다"

 

 

# How to use dplyr's chain operations %>%
# : dataframe %>% group_by() %>% select() %>% summarise() %>% filter()

Cars93 %>%  # dataframe name
  group_by(Origin, Type, Cylinders) %>%  # group_by()
  select(Price, MPG.highway) %>%  # select() columns
  summarise(
    Price_m = mean(Price, na.rm = TRUE),
    MPG.highway_m = mean(MPG.highway, na.rm = TRUE)  # summarise()
  ) %>%
  filter(Price_m > 10 | MPG.highway_m > 25)  # filter() condition

 

 

Adding missing grouping variables: `Origin`, `Type`, `Cylinders`
Source: local data frame [25 x 5]
Groups: Origin, Type [11]

   Origin    Type Cylinders  Price_m MPG.highway_m
   <fctr>  <fctr>    <fctr>    <dbl>         <dbl>
1     USA Compact         4 12.82857      30.57143
2     USA   Large         6 22.40000      27.28571
3     USA   Large         8 27.62500      25.75000
4     USA Midsize         4 15.87500      29.50000
5     USA Midsize         6 22.84000      27.20000
6     USA Midsize         8 40.10000      25.00000
7     USA   Small         4 10.04286      33.85714
8     USA  Sporty         4 14.60000      28.75000
9     USA  Sporty         6 19.53333      26.66667
10    USA  Sporty         8 38.00000      25.00000
# ... with 15 more rows 

 

 

 

 

(3) dplyr chain operations 는 지켜야 할 순서가 있는가?

 

네, 지켜야 할 순서가 있습니다. 

 

아래에 group_by()를 제일 마지막, 혹은 summarise() 함수 뒤에 위치시켰더니 "unknown variable to group by : Origin'이라고 에러가 났습니다.

 

filter() 조건에 group_by()한 summarise() 중간 결과의 변수가 들어있는 경우에, filter()를 summarise() 함수 앞에 위치시켰더니 'Price_m'이라는 새로운 변수를 찾을 수 없다고 역시 에러가 났습니다.

 

# keep in mind the sequence order of chain operator in dplyr

> Cars93 %>%
+   select(Price, MPG.highway) %>%
+   summarise(
+     Price_m = mean(Price, na.rm = TRUE),
+     MPG.highway_m = mean(MPG.highway, na.rm = TRUE)
+   ) %>%
+   filter(Price_m > 10 | MPG.highway_m > 25) %>% 
+   group_by(Origin, Type, Cylinders)
Error in eval(substitute(expr), envir, enclos) : unknown variable to group by : Origin
> Cars93 %>%
+   select(Price, MPG.highway) %>%
+   summarise(
+     Price_m = mean(Price, na.rm = TRUE),
+     MPG.highway_m = mean(MPG.highway, na.rm = TRUE)
+   ) %>%
+   group_by(Origin, Type, Cylinders) %>% 
+   filter(Price_m > 10 | MPG.highway_m > 25)
Error in eval(substitute(expr), envir, enclos) : unknown variable to group by : Origin
> Cars93 %>%
+   group_by(Origin, Type, Cylinders) %>% 
+   select(Price, MPG.highway) %>%
+   filter(Price_m > 10 | MPG.highway_m > 25) %>% 
+   summarise(
+     Price_m = mean(Price, na.rm = TRUE),
+     MPG.highway_m = mean(MPG.highway, na.rm = TRUE)
+   )
Adding missing grouping variables: `Origin`, `Type`, `Cylinders` Error in eval(substitute(expr), envir, enclos) : object 'Price_m' not found

 

 

 

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

 

 

참고로, Python이나 Scala로 Spark 사용할 때 보면 '.' (dot) 을 이용해서 chain operation 하는게 있는데요, R의 '%>%'과 사용방법이 유사합니다. 

 

 [Spark에서 Python으로 map과 reduceByKey를 chaining 해서 단어 세기] 예시

 

rdd = sc.textFile("file")

words = rdd.flatMap(lambda x: x.split(" "))

result = words.map(lambda x: (x, 1)).reduceByKey(lambda x, y: x + y)

 

 

 

이번 포스팅이 도움이 되었다면 아래의 '공감 ~ ♡'를 꾸욱 눌러주세요.

 

[Reference]

1. Introduction to dplyr : http://127.0.0.1:26182/library/dplyr/doc/introduction.html

 


728x90
반응형
Posted by Rfriend
,

데이터 전처리, 조작을 위한 dplyr 패키지의 단일 테이블에 대한 함수(one-table verbs)로서 지난번 포스티에서는

 

 - 행(row) 선별 : filter(), slice()

 - 행(row) 정렬 : arrange()

 - 열(column) 선별 : select()

 - 열(column) 조건부 선별 : select(df, starts_with()), select(df, ends_with()), select(df, contains()),

select(df, matchs()), select(df, one_of()), select(df, num_range()) 

 - 변수 이름 변경 : rename()

 

함수에 대해서 알아보았습니다. (☞ 바로 가기 http://rfriend.tistory.com/234)

 

 

 

[ R dplyr package : a grammar of data manipulation ]

 

 

 

 

이번 포스팅에서는 dplyr 패키지에 대한 두번째 소개 포스팅으로서, distinct(), sample_n(), sample_frac(), mutate(), transmute(), summarise() 함수에 대해서 알아보겠습니다.

 

 dplyr verbs

 description

 similar {package} function

 filter() 

 Filter rows with condition

 {base} subset

 slice()

 Filter rows with position

 {base} subset 

 arrange()

 Re-order or arrange rows

 {base} order

 select()

 Select columns

 {base} subset
 > select(df, starts_with())  Select columns that start with a prefix   
 > select(df, ends_with())  Select columns that end with a prefix  
 > select(df, contains())  Select columns that contain a character string  
 > select(df, matchs())  Select columns that match a regular expression  
 > select(df, one_of())  Select columns that are from a group of names  
 > select(df, num_range())  Select columns from num_range a to n with a prefix  

 rename()

 Rename column name

 {reshape} rename
 distinct()  Extract distinct(unique) rows  {base} unique
 sample_n()  Random sample rows for a fixed number  {base} sample
 sample_frac()  Random sample rows for a fixed fraction  {base} sample

 mutate()

 Create(add) new columns. 
 mutate() allows you to refer to columns that you’ve just created

 {base} transform 
 transmute()

 Create(add) new columns.

 transmute() only keep the new columns.

 {base} transform

 summarise()

 Summarise values

 {base} summary

 

 

 

 

(1) 중복없는 유일한 값 추출 : distinct() 

 

distinct(dataframe, 기준 var1, 기준 var2, ...) 의 형식으로 중복없는 유일한(distinct, unique) 값을 추출하고자 하는 기준 변수를 기입해주면 됩니다.

 

{base} 패키지의 unique() 함수와 매우 기능을 수행하며, dplyr 패키지의 distinct() 가 C언어로 짜여져서 속도는 훨씬 빠릅니다.

 

[문제] Cars93 데이터 프레임에서 '차종(Type)'과 '생산국-미국여부(Origin)' 변수를 기준으로 중복없는 유일한 값을 추출하시오.

 

library(dplyr)
library(MASS) # to use Cars93 dataframe

 

> names(Cars93)
 [1] "Manufacturer"       "Model"              "Type"               "Min.Price"         
 [5] "Price"              "Max.Price"          "MPG.city"           "MPG.highway"       
 [9] "AirBags"            "DriveTrain"         "Cylinders"          "EngineSize"        
[13] "Horsepower"         "RPM"                "Rev.per.mile"       "Man.trans.avail"   
[17] "Fuel.tank.capacity" "Passengers"         "Length"             "Wheelbase"         
[21] "Width"              "Turn.circle"        "Rear.seat.room"     "Luggage.room"      
[25] "Weight"             "Origin"             "Make"

 

> # distinct(dataframe, var1, var2) :  find unique values in a table
> 
>
distinct(Cars93, Origin) Origin 1 non-USA 2 USA >
> distinct(Cars93, Type) Type 1 Small 2 Midsize 3 Compact 4 Large 5 Sporty 6 Van > > distinct(Cars93, Origin, Type) Origin Type 1 non-USA Small 2 non-USA Midsize 3 non-USA Compact 4 USA Midsize 5 USA Large 6 USA Compact 7 USA Sporty 8 USA Van 9 USA Small 10 non-USA Sporty 11 non-USA Van

 

참고로, {base} 패키지의 unique() 함수로는 unique(Cars93[, c("Origin", "Type")])  이렇게 입력해주면 되며, 위의 distinct() 함수와는 달리 rownames 가 dataset의 row 번호로 반환되는 차이점이 있습니다.  dplyr 패키지가 전반적으로 문법이 깔끔하다는 생각이 드실겁니다.

 

({base} 패키지의 unique() 함수, duplicated() 함수 포스팅 보러가기

         : http://rfriend.tistory.com/165 )

 

 

 

 

(2) 무작위 표본 데이터 추출 : sample_n(), sample_frac()

 

(2-1) sample_n(dataframe, a fixed number) : 특정 개수만큼 무작위 추출

 

[문제] Cars93 데이터 프레임(1~5번째 변수만 사용)에서 10개의 관측치를 무작위로 추출하시오.

 

> # sample_n() : randomly sample rows for a fixed number > sample_n(Cars93[, 1:5], 10) Manufacturer Model Type Min.Price Price 68 Oldsmobile Achieva Compact 13.0 13.5 38 Ford Crown_Victoria Large 20.1 20.9 75 Pontiac Firebird Sporty 14.0 17.7 59 Mercedes-Benz 300E Midsize 43.8 61.9 70 Oldsmobile Silhouette Van 19.5 19.5 49 Lexus ES300 Midsize 27.5 28.0 47 Hyundai Sonata Midsize 12.4 13.9 93 Volvo 850 Midsize 24.8 26.7 2 Acura Legend Midsize 29.2 33.9 41 Honda Prelude Sporty 17.0 19.8 >
> # random sampling one more time
> sample_n(Cars93[, 1:5], 10)
Manufacturer Model Type Min.Price Price 69 Oldsmobile Cutlass_Ciera Midsize 14.2 16.3 64 Nissan Sentra Small 8.7 11.8 44 Hyundai Excel Small 6.8 8.0 78 Saab 900 Compact 20.3 28.7 39 Geo Metro Small 6.7 8.4 22 Chrysler Imperial Large 29.5 29.5 7 Buick LeSabre Large 19.9 20.8 9 Buick Riviera Midsize 26.3 26.3 27 Dodge Dynasty Midsize 14.8 15.6 91 Volkswagen Corrado Sporty 22.9 23.3

 

 

 

 

(2-2) sample_frac(dataframe, a fixed fraction) : 특정 비율만큼 무작위 추출

 

[문제] Cars93 데이터 프레임(1~5번째 변수만 사용)에서 10%의 관측치를 무작위로 추출하시오.

 

> # sample_frac() : randomly sample rows for a fixed fraction
> nrow(Cars93)
[1] 93
> 
>
nrow(Cars93)*0.1 [1] 9.3 >
> sample_frac(Cars93[ , 1:5], 0.1) Manufacturer Model Type Min.Price Price 72 Plymouth Laser Sporty 11.4 14.4 8 Buick Roadmaster Large 22.6 23.7 80 Subaru Justy Small 7.3 8.4 31 Ford Festiva Small 6.9 7.4 75 Pontiac Firebird Sporty 14.0 17.7 90 Volkswagen Passat Compact 17.6 20.0 30 Eagle Vision Large 17.5 19.3 41 Honda Prelude Sporty 17.0 19.8 76 Pontiac Grand_Prix Midsize 15.4 18.5

 

 

Cars93 데이터 프레임은 관측치가 93개 이며, 10%는 9.3개에 해당하므로, 위 예제에서 sample_frac(Cars93, 0.1)은 총 9개의 무작위 샘플을 추출했습니다.

 

 

 

(2-3) smaple_n(dataframe, n, replace = TRUE) : 복원 추출

 

위의 두개의 예제는 한번 추출한 표본은 다시 추출하지 않는 '비복원 추출(sampling with replacement)'이었습니다. (눈을 감고 주머니에서 한번 뽑았으면, 뽑힌 공은 다시 주머니에 넣지 않고 옆에 따로 빼어놓고, 다시 눈을 감고 주머니에서 공을 뽑음)

 

dplyr 패키지의 sample_n(), sample_frac() 함수의 default 는 비복원추출이며, 만약 '복원추출(sampling with replacement, bootstrap sampling)'을 하고 싶다면 'replace = TRUE' 옵션을 설정해주면 됩니다. (눈을 감고 주머니에서 공을 뽑고, 뽑힌 공을 다시 주머니에 넣은 후에, 눈을 감고 다시 주머니에서 공을 뽑음) 

 

 

[문제] Cars93 데이터 프레임(1~5번까지 변수만 사용)에서 20개의 관측치를 무작위 복원추출 하시오.

 

> # sample_n(dataframe, n, replace = TRUE) : random sampling with replacement
> sample_n(Cars93[, 1:5], 20, replace = TRUE) # a bootstrap sample of 20 records
      Manufacturer      Model    Type Min.Price Price
53           Mazda        323   Small       7.4   8.3
49           Lexus      ES300 Midsize      27.5  28.0
92           Volvo        240 Compact      21.8  22.7
48        Infiniti        Q45 Midsize      45.4  47.9
64          Nissan     Sentra   Small       8.7  11.8
56           Mazda        MPV     Van      16.6  19.1
23           Dodge       Colt   Small       7.9   9.2
25           Dodge     Spirit Compact      11.9  13.3
68      Oldsmobile    Achieva Compact      13.0  13.5
43           Honda     Accord Compact      13.8  17.5
17       Chevrolet      Astro     Van      14.7  16.6
70      Oldsmobile Silhouette     Van      19.5  19.5
10        Cadillac    DeVille   Large      33.0  34.7
81          Subaru     Loyale   Small      10.5  10.9
58   Mercedes-Benz       190E Compact      29.0  31.9
52         Lincoln   Town_Car   Large      34.4  36.1
31            Ford    Festiva   Small       6.9   7.4
43.1         Honda     Accord Compact      13.8  17.5
47         Hyundai     Sonata Midsize      12.4  13.9
17.1     Chevrolet      Astro     Van      14.7  16.6

 

 

위 무작위 표본 추출 결과를 보면 17번 Cehvrolet Astro와 43번 Honda Accord가 중복으로 추출되었음을 알 수 있습니다.

 

 

 

(2-4) dataframe %>% group_by(factor_var) %>% sample_n(size) : 집단별 층화 표본 추출

 

분석을 하다 보면 집단, 그룹별로 동일한 수의 표본을 무작위 추출해서 분석해야 하는 경우가 있습니다.  특히 분석 주제 혹은 분석에 큰 영향을 미치는 요인 변수에 대한 집단 분포(distribution)가 한쪽 그룹으로 심하게 편향된 모집단(biased, unbalanced population)의 경우 층화 무작위 표본 추출(stratified random sampling)이 필요합니다.

 

[예제] Cars93 데이터 프레임에서 '제조국가_미국여부(Origin)'의 'USA', 'non-USA' 요인 속성별로 각 10개씩의 표본을 무작위 비복원 추출하시오.

 

> # dataframe %>% group_by(factor_var) %>% sample_n(size) : random sampling by group > Cars93[ , c("Manufacturer", "Model", "Origin")] %>% group_by(Origin) %>% sample_n(10) Source: local data frame [20 x 3] Groups: Origin [2] Manufacturer Model Origin <fctr> <fctr> <fctr> 1 Chrylser Concorde USA 2 Lincoln Town_Car USA 3 Ford Crown_Victoria USA 4 Oldsmobile Eighty-Eight USA 5 Oldsmobile Achieva USA 6 Lincoln Continental USA 7 Chrysler Imperial USA 8 Eagle Summit USA 9 Buick Riviera USA 10 Pontiac Bonneville USA 11 Volvo 240 non-USA 12 Mercedes-Benz 190E non-USA 13 Volkswagen Fox non-USA 14 Geo Metro non-USA 15 Honda Accord non-USA 16 Mazda 626 non-USA 17 Mazda Protege non-USA 18 Lexus SC300 non-USA 19 Acura Legend non-USA 20 Mercedes-Benz 300E non-USA

 

 

위의 '%>%' (단축키 : shift + ctrl + M)의 chaining 에 대해서는 다음번 포스팅에서 별도로 소개하겠으니 지금 궁금하시더라도 조금만 참아주세요. ^^;

 

 

 

 

(3) 새로운 변수 생성 : mutate(), transmute()

 

(3-1) mutate(dataframe, 새로운변수 = 기존변수 조합한 수식, ...)
       : 기존 변수 + 신규 변수 모두 keep 

 

새로운 변수를 생성할 때 mutate() 함수를 사용하며, 이는 {base} 패키지의 transform() 함수와 유사합니다만, 조금 더 유용합니다.

 

[문제] Cars93 데이터프레임에서 최소가격(Min.Price)과 최대가격(Max.Price)의 범위(range), 최소가격 대비 최대가격의 비율(=Max.Price/Min.Price) 의 새로운 변수를 생성하시오.

 

> # mutate(dataframe, new_var = operation of old vars, ...) : Create(add) new columns > Cars93_1 <- Cars93[c(1:10), c("Model", "Min.Price", "Max.Price")] # subset for better printing > Cars93_1 Model Min.Price Max.Price 1 Integra 12.9 18.8 2 Legend 29.2 38.7 3 90 25.9 32.3 4 100 30.8 44.6 5 535i 23.7 36.2 6 Century 14.2 17.3 7 LeSabre 19.9 21.7 8 Roadmaster 22.6 24.9 9 Riviera 26.3 26.3 10 DeVille 33.0 36.3 > > > Cars93_1 <- mutate(Cars93_1, + Price_range = Max.Price - Min.Price, + Price_Min_Max_ratio = Max.Price / Min.Price) > > > Cars93_1 Model Min.Price Max.Price Price_range Price_Min_Max_ratio 1 Integra 12.9 18.8 5.9 1.457364 2 Legend 29.2 38.7 9.5 1.325342 3 90 25.9 32.3 6.4 1.247104 4 100 30.8 44.6 13.8 1.448052 5 535i 23.7 36.2 12.5 1.527426 6 Century 14.2 17.3 3.1 1.218310 7 LeSabre 19.9 21.7 1.8 1.090452 8 Roadmaster 22.6 24.9 2.3 1.101770 9 Riviera 26.3 26.3 0.0 1.000000 10 DeVille 33.0 36.3 3.3 1.100000

 

 

 

 

{dplyr} 패키지의 mutate() 함수하나의 함수 명령문 안에서 신규로 생성한 변수를 바로 다른 신규 생성 변수의 input 변수로 사용할 수 있어서 편리합니다

 

반면에, {base} 패키지의 transform() 함수는 하나의 명령문 안에 신규 변수를 다른 신규 변수의 input 변수로 사용할 수 없습니다.  R 초급자가 transform() 함수 쓰다가 '객체가 없다고? 이게 왜 안되고 에러가 나지?'하면서 에러 메시지의 의미를 잘 이해를 못하고 어찌 조치를 취해야 할지 몰라 애를 먹는 부분이기도 합니다. 아래의 예제를 참고하시면 이해가 될 것입니다.

 

> # comparison with {dplyr} mutate() and {base} transform()
> # : mutate() allows you to refer to columns that you’ve just created
> Cars93_1 <- Cars93[c(1:10), c("Model", "Min.Price", "Max.Price")] # subset for better printing
> Cars93_1
        Model Min.Price Max.Price
1     Integra      12.9      18.8
2      Legend      29.2      38.7
3          90      25.9      32.3
4         100      30.8      44.6
5        535i      23.7      36.2
6     Century      14.2      17.3
7     LeSabre      19.9      21.7
8  Roadmaster      22.6      24.9
9     Riviera      26.3      26.3
10    DeVille      33.0      36.3
> 
> Cars93_2 <- mutate(Cars93_1, 
+                    Price_range = Max.Price - Min.Price, 
+                    Price_range_cd = ifelse(Price_range >= 5, 1, 0))
> 
> Cars93_2
        Model Min.Price Max.Price Price_range Price_range_cd
1     Integra      12.9      18.8         5.9              1
2      Legend      29.2      38.7         9.5              1
3          90      25.9      32.3         6.4              1
4         100      30.8      44.6        13.8              1
5        535i      23.7      36.2        12.5              1
6     Century      14.2      17.3         3.1              0
7     LeSabre      19.9      21.7         1.8              0
8  Roadmaster      22.6      24.9         2.3              0
9     Riviera      26.3      26.3         0.0              0
10    DeVille      33.0      36.3         3.3              0
> 
>
# {base} transform() > Cars93_3 <- transform(Cars93_1, + Price_range = Max.Price - Min.Price, + Price_range_cd = ifelse(Price_range >= 5, 1, 0))

Error in ifelse(Price_range >= 5, 1, 0) : object 'Price_range' not found

 

 

위의 예제에서 {base} 패키지의 transform() 함수로 Price_range_cd 신규 변수를 만들려면 (1) Price_range 신규 변수를 먼저 생성하고 -> (2) Price_range_cd 신규 변수를 생성하는 두단계 절차(multi steps)를 나누어서 밟아야 합니다.  mutate() 함수 대비 조금 더 불편하지요?  

 

 

 

 

(3-2) transmute(dataframe, 새로운 변수 = 기존 변수 조합한 수식, ...)
       : 신규 변수만 keep

 

위의 mutate() 함수가 기존 변수와 신규 변수를 모두 가지고 있는데 반해서, transmute() 함수는 신규 변수만 저장을 하고 기존 변수는 날려버립니다.

 

최소가격(Min.Price)과 최대가격(Max.Price)의 범위(range), 최소가격 대비 최대가격의 비율(=Max.Price/Min.Price) 의 새로운 변수를 생성한 후에, 이들 2개의 신규변수만 남겨두시오.

 

> # transmute() : Create(add) new columns > Cars93_1 <- Cars93[c(1:10), c("Model", "Min.Price", "Max.Price")] # subset for better printing > Cars93_1 Model Min.Price Max.Price 1 Integra 12.9 18.8 2 Legend 29.2 38.7 3 90 25.9 32.3 4 100 30.8 44.6 5 535i 23.7 36.2 6 Century 14.2 17.3 7 LeSabre 19.9 21.7 8 Roadmaster 22.6 24.9 9 Riviera 26.3 26.3 10 DeVille 33.0 36.3 > > Cars93_4 <- transmute(Cars93_1, + Price_range = Max.Price - Min.Price, + Price_Min_Max_ratio = Max.Price / Min.Price) > > Cars93_4 Price_range Price_Min_Max_ratio 1 5.9 1.457364 2 9.5 1.325342 3 6.4 1.247104 4 13.8 1.448052 5 12.5 1.527426 6 3.1 1.218310 7 1.8 1.090452 8 2.3 1.101770 9 0.0 1.000000 10 3.3 1.100000

 

 

 

 

 

 

 (4) 값 요약 : summarise()

 

(4-1) summarise(dataframe, mean, sd, ...) : 수치형 값에 대한 요약 통계량 계산

 

summarise() 함수이 제공하는 수치형 데이터에 대한 요약 통계량 옵션을 소개하자면요,

 

 - mean(x, na.rm = TRUE) : 평균, 결측값을 제외하고 계산하려면 na.rm = TRUE 추가

 - median(x, na.rm = TRUE) : 중앙값

 - sd(x, na.rm = TRUE) : 표준편차

 - min(x, na.rm = TRUE) : 최소값

 - max(x, na.rm = TRUE) : 최대값

 - IQR(x, na.rm = TRUE) : 사분위수 (Inter Quartile Range = Q3 - Q1)

 - sum(x, na.rm = TRUE) : 합, 결측값을 제외하고 계산하려면 na.rm = TRUE 추가

 

 

[예제] Cars93 데이터 프레임에서 가격(Price)의 (a) 평균, (b) 중앙값, (c) 표준편차, (d) 최소값, (e) 최대값, (f) 사분위수(IQR), (g) 합계를 구하시오. (단, 결측값은 포함하지 않고 계산함)

 

> # summarise() : Summarise numeric values
> #  > mean(), median(), sd(), min(), max(), IQR(), sum()
> # IQR : IQR(Inter quartile Range) = Upper Quartile - Lower Quartile
> summarise(Cars93, 
+           Price_mean = mean(Price, na.rm = TRUE), # mean of Price
+           Price_median = median(Price, na.rm = TRUE), # median of Price
+           Price_sd = sd(Price, na.rm = TRUE), # standard deviation of Price
+           Price_min = min(Price, na.rm = T), # min of Price
+           Price_max = max(Price, na.rm = T), # max of Price
+           Price_IQR = IQR(Price), na.rm = T, # IQR of Price
+           Price_sum = sum(Price, na.rm = TRUE)) # sum of Price

  Price_mean Price_median Price_sd Price_min Price_max Price_IQR na.rm Price_sum
1   19.50968         17.7  9.65943       7.4      61.9      11.1  TRUE    1814.4

 

 

 

 

 

(4-2) summarise(dataframe, n(), n_distinct(x), first(x), last(x), nth(x, n))
  : 개수 계산, 관측값 indexing

 

 - n() : 관측치 개수 계산, x 변수 입력하지 않음

 - n_distinct(x) : 중복없는 유일한 관측치 개수 계산, 기준이 되는 x변수 입력함

 - first(x) : 기준이 되는 x변수의 첫번째 관측치

 - last(x) : 기준이 되는 x변수의 마지막 관측치

 - nth(x, n) : 기준이 되는x변수의 n번째 관측치

 

 

[예제] Cars93_1 데이터 프레임에서 (a) 총 관측치의 개수, (b) 제조사(Manufacturer)의 개수(유일한 값), (c) 첫번째 관측치의 제조사 이름, (d) 마지막 관측치의 제조사 이름, (e) 5번째 관측치의 제조사 이름은?

 

> # summarise() : n(), n_distinct(), first(), last(), nth() > Cars93_1 <- Cars93[c(1:10), c("Manufacturer", "Model", "Type")] # subset for better print > Cars93_1 Manufacturer Model Type 1 Acura Integra Small 2 Acura Legend Midsize 3 Audi 90 Compact 4 Audi 100 Midsize 5 BMW 535i Midsize 6 Buick Century Midsize 7 Buick LeSabre Large 8 Buick Roadmaster Large 9 Buick Riviera Midsize 10 Cadillac DeVille Large > > summarise(Cars93_1, + tot_cnt = n(), # counting the number of all observations + Manufacturer_dist_cnt = n_distinct(Manufacturer), # distinct number of var + First_obs = first(Manufacturer), # first observation + Last_obs = last(Manufacturer), # last observation + Nth_5th_obs = nth(Manufacturer, 5)) # n'th observation



tot_cnt Manufacturer_dist_cnt First_obs Last_obs Nth_5th_obs 1 10 5 Acura Cadillac BMW

 

 

 

 

 

(4-3) summarise(group_by(dataframe, factor_var), mean, sd, ...) : 그룹별 요약 통계량 계산

 

그룹별 계산(Grouped operations)을 위해서 group_by() 함수를 이용합니다.

 

[예제] Cars93 데이터 프레임에서 '차종(Type)' 별로 구분해서 (a) 전체 관측치 개수, (b) (중복 없이 센) 제조사 개수, (c) 가격(Price)의 평균과 (d) 가격의 표준편차를 구하시오. (단, 결측값은 포함하지 않고 계산함)

 

> # summarise by group
> grouped <- group_by(Cars93, Type)
> summarise(grouped,
+           tot_conut = n(), # counting the number of cars
+           Manufacturer_dist_cnt = n_distinct(Manufacturer), # distinct number of var
+           Price_mean = mean(Price, na.rm = TRUE), # mean of Price
+           Price_sd = sd(Price, na.rm = TRUE) # standard deviation of Price
+           )
# A tibble: 6 x 5
     Type tot_conut Manufacturer_dist_cnt Price_mean  Price_sd
   <fctr>     <int>                 <int>      <dbl>     <dbl>
1 Compact        16                    15   18.21250  6.686890
2   Large        11                    10   24.30000  6.337507
3 Midsize        22                    20   27.21818 12.264841
4   Small        21                    16   10.16667  1.953288
5  Sporty        14                    12   19.39286  7.974716
6     Van         9                     8   19.10000  1.878164

 

 

 

 

(4) summarise_each() : 다수의 변수에 동일한 summarise 함수 적용

 

[문제] Cars93 데이터 프레임의 (i) 가격(Price) 변수와 (ii) 고속도로연비(MPG.highway) 의 두개의 변수에 대해 (a) 평균(mean), (b) 중앙값(median), (c) 표준편차(standard deviation) 의 3개의 함수를 동시에 적용하여 계산하시오.

 

> # summarize_each() : applies the same summary function(s) to multiple variables
> summarise_each(Cars93, funs(mean, median, sd), Price, MPG.highway)
  Price_mean MPG.highway_mean Price_median MPG.highway_median Price_sd MPG.highway_sd
1   19.50968         29.08602         17.7                 28  9.65943       5.331726

 

 

 

[Reference]

 - Introduction to dplyr (http://127.0.0.1:21980/library/dplyr/doc/introduction.html)

 - dplyr functions for single dataset (http://stat545.com/block010_dplyr-end-single-table.html)

 - dplyr tutorial (http://genomicsclass.github.io/book/pages/dplyr_tutorial.html)

 

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

 

이전 포스팅과 이번 포스팅으로서 dplyr 패키지의 데이터 전처리, 조작을 위한 기본기를 익혔습니다.

다음번 포스팅에서는 R dplyr 패키지의 심화 함수로서, chain operations (Operator %>%, shift+ctrl+M) 에 대해서 소개하겠습니다.

 

이번 포스팅이 도움이 되었다면 아래의 '공감 ~♡'를 꾸욱 눌러주세요.

 

 

728x90
반응형
Posted by Rfriend
,

이번 포스팅에서는 데이터프레임(dataframe)을 위한 데이터 전처리, 조작(data pre-processing, data manipulation)을 쉽고! 빠르게! 할 수 있도록 해주는 dplyr 패키지에 대해서 알아보겠습니다.

 

R help에 검색해보면 dplyr 패키지를 아래와 같이 소개하고 있습니다.

 

dplyr은 유연한 데이터 조작의 문법을 제공합니다.  이것은 plyr의 차기작으로서, 데이터프레임을 집중적으로 다루는 툴입니다

 

dplyr provides a flexible grammar of data manipulation. It's the next iteration of plyr, focused on tools for working with data frames (hence the d in the name)

 

 

데이터 조작을 위한 문법으로 체계화를 해서 한번 배워놓으면 쉽다는 점과 더불어, C언어로 만들어서 매우 빠르다는 점도 dplyr 패키지의 크나큰 장점 중의 하나입니다.

 

 

 

아마 R을 좀 다룬 분이라면은 그래프/시각화를 문법으로 승화시켜서 체계를 잡아놓은 "ggplot2" package ( "ggplot is an implementation of the grammer of Graphics in R")를 알고 계실텐데요, 이 ggplot2를 만든 그 유명한 Hadley Wickham 이 dplyr 패키지도 만들었습니다.  

 

아래에 소개한 것처럼, r-bloggers.com에 소개되어 있는 Hadley 인 인터뷰를 보면, 기존 통계학에서는 데이터 전처리(Data munging and manipulation)를 "내 일이 아니야"라고 무시했었다고 합니다. 그런데 Hadley Wickham이 보기에는 모델링과 시각화로 부터 통찰을 끄집에 내는데 있어서 데이터 조작, 전처리가 매우 중요하고 또 어려운 영역이라고 보기에 Hadley가 직접 나서서 이를 도와줄 수 있는 R packages를 만들었다고 나오네요.  ^^b

"Hadley Wickham on why he created all those R packages"

July 27, 2015 By David Smith

 

“There are definitely some academic statisticians who just don’t understand why what I do is statistics, but basically I think they are all wrong . What I do is fundamentally statistics. The fact that data science exists as a field is a colossal failure of statistics.

 

To me, that is what statistics is all about. It is gaining insight from data using modelling and visualizationData munging and manipulation is hard and statistics has just said that’s not our domain.”

* source : https://www.r-bloggers.com/hadley-wickham-on-why-he-created-all-those-r-packages/

 

 

Hadley Wickham의 Github repository 주소는요 https://github.com/hadley?tab=repositories  입니다. 여기 가보시면 엄청나게 많은 R packages들에 입이 쩍 벌어질겁니다. 만약에 노벨상에 R community에 기여한 공로를 치하하는 상으로 '노벨 R 상'이 있다면 Hadley Wickham이 그 첫번째 수상자가 된다고 해도 전혀 이상할게 없을 정도로 정말 R의 확산에 지대한 공헌을 하신 분입니다.

 

서론이 길었습니다.  ggplot2가 시각화/그래프에 관한한 우리를 실망시키지 않았듯이, dplyr 또한 데이터 전처리에 관한한 coverage의 방대함과 문법의 명료함이 우리를 매료시킬 것이라고 생각하며, 하나씩 예를 들어 설명을 시작해 보겠습니다.

 

아래의 설명은 browseVignettes(package = "dplyr") 치면 팝업으로 나오는 "Introduction to dplyr" HTML 페이지를 참조하였습니다.

 

예제로 사용할 데이터는 MASS 패키지에 들어있는 Cars93 데이터프레임입니다.  원래는 93개의 자동차 관측치에 27개의 변수를 가지고 있는데요, 예시들기 편하도록 앞에서부터 변수 8개만 선택해서 사용하겠습니다.  (Cars93_1 dataframe) 

 

> library(MASS)
> # subset Cars93
> Cars93_1 <- Cars93[, 1:8]
> str(Cars93_1)
'data.frame':	93 obs. of  8 variables:
 $ Manufacturer: Factor w/ 32 levels "Acura","Audi",..: 1 1 2 2 3 4 4 4 4 5 ...
 $ Model       : Factor w/ 93 levels "100","190E","240",..: 49 56 9 1 6 24 54 74 73 35 ...
 $ Type        : Factor w/ 6 levels "Compact","Large",..: 4 3 1 3 3 3 2 2 3 2 ...
 $ Min.Price   : num  12.9 29.2 25.9 30.8 23.7 14.2 19.9 22.6 26.3 33 ...
 $ Price       : num  15.9 33.9 29.1 37.7 30 15.7 20.8 23.7 26.3 34.7 ...
 $ Max.Price   : num  18.8 38.7 32.3 44.6 36.2 17.3 21.7 24.9 26.3 36.3 ...
 $ MPG.city    : int  25 18 20 19 22 22 19 16 19 16 ...
 $ MPG.highway : int  31 25 26 26 30 31 28 25 27 25 ...

 

 

 

 

단일 테이블을 대상으로 하는 dplyr 패키지의 함수들(Single table verbs)을 표로 정리해보면 아래와 같습니다.

 

 dplyr verbs

 description

 similar {package} function

 filter() 

 Filter rows with condition

 {base} subset

 slice()

 Filter rows with position

 {base} subset 

 arrange()

 Re-order or arrange rows

 {base} order

 select()

 Select columns

 {base} subset
 > select(df, starts_with())  Select columns that start with a prefix   
 > select(df, ends_with())  Select columns that end with a prefix  
 > select(df, contains())  Select columns that contain a character string  
 > select(df, matchs())  Select columns that match a regular expression  
 > select(df, one_of())  Select columns that are from a group of names  
 > select(df, num_range())  Select columns from num_range a to n with a prefix  

 rename()

 Rename column name

 {reshape} rename
 distinct()  Extract distinct(unique) rows  {base} unique
 sample_n()  Random sample rows for a fixed number  {base} sample
 sample_frac()  Random sample rows for a fixed fraction  {base} sample

 mutate()

 Create(add) new columns.
 mutate() allows you to refer to columns that you’ve just created.

 {base} transform 
 transmute()

 Create(add) new columns.

 transmute() only keeps the new columns.

 {base} transform

 summarise()

 Summarise values

 {base} summary

 

 

하나씩 설명을 이어가보면요,

 

(1) 데이터 프레임의 행 부분집합 선별 (select row subset from data frame) : filter(), slice()

 

 

(1-1) filter(dataframe, filter condition 1, filter condition 2, ...)

    : &(AND) 조건으로 row 데이터 부분집합 선별 (select a subset of rows in a data frame with 'AND' condition)

 

Cars93_1 데이터 프레임에 차종(Type)별로 보면 Compact 차종이 총 16개 있음을 알 수 있습니다. 

 

[문제] 차종(Type)이 "Compact"이면서 & 최대가격(Max.Price)이 20 백$ 이하이고 & 고속도로 연비(MPG.highway) 가 30 이상인 관측치를 선별하시오.

 

위 문제는 아래와 같이 dplyr의 filter() 함수를 사용하면 됩니다.  참고로, subset() 함수의 subset과 동일한 기능을 합니다.

 

> # number of cars by Type
> table(Cars93_1$Type)

Compact   Large Midsize   Small  Sporty     Van 
     16      11      22      21      14       9 
> 
> install.packages("dplyr")
> library(dplyr)
> # filter() : select a subset of rows in a data frame
> filter(Cars93_1, Type == c("Compact"), Max.Price <= 20, MPG.highway >= 30)
  Manufacturer    Model    Type Min.Price Price Max.Price MPG.city MPG.highway
1    Chevrolet Cavalier Compact       8.5  13.4      18.3       25          36
2    Chevrolet  Corsica Compact      11.4  11.4      11.4       25          34
3        Mazda      626 Compact      14.3  16.5      18.7       26          34
4       Nissan   Altima Compact      13.0  15.7      18.3       24          30
5   Oldsmobile  Achieva Compact      13.0  13.5      14.0       24          31
6      Pontiac  Sunbird Compact       9.4  11.1      12.8       23          31

 

 

 

 

(1-2) filter(dataframe, filter condition 1 | filter condition 2 | ...)

    : |(OR) 조건으로 row 데이터 부분집합 선별 (select a subset of rows in a data frame with 'OR' condition)

 

OR(또는) 조건으로 부분집합을 선별하려면 '|'를 사용하면 됩니다. (subset() 함수와 동일)

 

[문제] 차종(Type)이 "Compact"이거나 |(OR) 최대가격(Max.Price)이 20 백$ 이하이거나 |(OR) 고속도로 연비(MPG.highway) 가 30 이상인 관측치를 선별하시오.

 

위 문제는 래와 같이 dplyr의 filter() 함수를 사용하면 됩니다.  

 

> # filter(dataframe, condition1 | condition2) : or
> filter(Cars93_1, Type == c("Compact") | Max.Price <= 20 | MPG.highway >= 30)
    Manufacturer         Model    Type Min.Price Price Max.Price MPG.city MPG.highway
1          Acura       Integra   Small      12.9  15.9      18.8       25          31
2           Audi            90 Compact      25.9  29.1      32.3       20          26
3            BMW          535i Midsize      23.7  30.0      36.2       22          30
4          Buick       Century Midsize      14.2  15.7      17.3       22          31
5      Chevrolet      Cavalier Compact       8.5  13.4      18.3       25          36
6      Chevrolet       Corsica Compact      11.4  11.4      11.4       25          34
7      Chevrolet        Camaro  Sporty      13.4  15.1      16.8       19          28

     .....  이하 생략 (총 58개 관측치) ..... 

 

 

 

 

(1-3) slice(dataframe, from, to) : 위치를 지정해서 row 데이터 부분집합 선별하기 

 

filter()가 조건에 의한 선별이었다면, 위치(position)를 사용해서 부분집합 선별은 slice() 함수를 사용합니다.

 

[문제] Cars93_1 데이터프레임의 6번째에서 10번째 행(row)의 데이터를 선별하시오.

 

> # slice() : select rows by position
> slice(Cars93_1, 6:10)
  Manufacturer      Model    Type Min.Price Price Max.Price MPG.city MPG.highway
1        Buick    Century Midsize      14.2  15.7      17.3       22          31
2        Buick    LeSabre   Large      19.9  20.8      21.7       19          28
3        Buick Roadmaster   Large      22.6  23.7      24.9       16          25
4        Buick    Riviera Midsize      26.3  26.3      26.3       19          27
5     Cadillac    DeVille   Large      33.0  34.7      36.3       16          25

 

 

 

 

 (2) 데이터 프레임 행 정렬하기 (arrange rows of data frame) : arrange()

 

(2) arrange(dataframe, order criterion 1, order criterion 2, ...)

 

데이터 프레임을 정렬할 때 arrange() 함수를 쓰면 매우 편리합니다. 

여러개의 기준에 의해서 정렬을 하고 싶으면 기준 변수를 순서대로 나열하면 됩니다.

기본 정렬 옵셥은 오름차순(ascending)이며, 만약 내림차순(descending) 으로 정렬을 하고 싶다면 desc()를 입력해주면 됩니다.

 

[문제] 고속도로 연비(MPG.highway) 가 높은 순서대로 정렬을 하시오.  만약 고속도로 연비가 동일하다면 최고가격(Max.Price)가 낮은 순서대로 정렬하시오.  (난 연비가 높고 가격은 낮은 차가 좋아~)

 

> # arrange() : reorder rows of data frame
> arrange(Cars93_1, desc(MPG.highway), Max.Price)
    Manufacturer          Model    Type Min.Price Price Max.Price MPG.city MPG.highway
1            Geo          Metro   Small       6.7   8.4      10.0       46          50
2          Honda          Civic   Small       8.4  12.1      15.8       42          46
3         Suzuki          Swift   Small       7.3   8.6      10.0       39          43
4        Pontiac         LeMans   Small       8.2   9.0       9.9       31          41
5         Saturn             SL   Small       9.2  11.1      12.9       28          38
6          Mazda            323   Small       7.4   8.3       9.1       29          37
7         Subaru          Justy   Small       7.3   8.4       9.5       33          37
8         Toyota         Tercel   Small       7.8   9.8      11.8       32          37
9          Mazda        Protege   Small      10.9  11.6      12.3       28          36
10           Geo          Storm  Sporty      11.5  12.5      13.5       30          36

   .... 이하 생략 ....

 

 

 

 

참고로, arrange() 함수 말고도 아래처럼 order() 함수를 사용해서 indexing 하는 방법도 있습니다만, 아무래도 arrange() 함수가 더 깔끔하고 해석하기에 좋습니다.

 

> Cars93[order(-Cars93_1$MPG.highway, Cars93_1$Max.Price), ]
    Manufacturer          Model    Type Min.Price Price Max.Price MPG.city MPG.highway
39           Geo          Metro   Small       6.7   8.4      10.0       46          50
42         Honda          Civic   Small       8.4  12.1      15.8       42          46
83        Suzuki          Swift   Small       7.3   8.6      10.0       39          43
73       Pontiac         LeMans   Small       8.2   9.0       9.9       31          41
79        Saturn             SL   Small       9.2  11.1      12.9       28          38
53         Mazda            323   Small       7.4   8.3       9.1       29          37
80        Subaru          Justy   Small       7.3   8.4       9.5       33          37
84        Toyota         Tercel   Small       7.8   9.8      11.8       32          37
54         Mazda        Protege   Small      10.9  11.6      12.3       28          36
40           Geo          Storm  Sporty      11.5  12.5      13.5       30          36

 

 

 


 

(3) 데이터 프레임 변수 선별하기 : select()

 

(3-1) select(dataframe, VAR1, VAR2, ...) : 선별하고자 하는 변수 이름을 기입

 

[문제] Cars93_1 데이터 프레임으로부터 제조사명(Manufacturer), 최대가격(Max.Price), 고속도로연비(MPG.highway) 3개 변수(칼럼)를 선별하시오.

 

> # select() : Select columns by name
> select(Cars93_1, Manufacturer, Max.Price, MPG.highway)
    Manufacturer Max.Price MPG.highway
1          Acura      18.8          31
2          Acura      38.7          25
3           Audi      32.3          26
4           Audi      44.6          26
5            BMW      36.2          30

   .... 이하 생략 ....

 

 

 

 

(3-2) select(dataframe, VAR_a:VAR_n, ...) : a번째부터 n번째 변수 선별

 

서로 인접해서 줄지어서 있는 변수들을 선별하고자 할 때는 아래의 예시처럼 ':'를 사용하면 됩니다.

 

[문제] Cars93_1 데이터 프레임에서 1번째에 위치한 제조사(Manufacturer) ~ 5번째에 위치한 가격(Price)까지의 서로 이어저 있는 총 5개의 변수들을 선별하시오.

 

> select(Cars93_1, Manufacturer:Price)
    Manufacturer          Model    Type Min.Price Price
1          Acura        Integra   Small      12.9  15.9
2          Acura         Legend Midsize      29.2  33.9
3           Audi             90 Compact      25.9  29.1
4           Audi            100 Midsize      30.8  37.7
5            BMW           535i Midsize      23.7  30.0

     .... 이하 생략 ....

 

 

아래와 같이 서로 인접해서 줄지어서 있는 변수들의 위치를 알고 있으면 (가령 a부터 n번째 위치) 'a:n'처럼 숫자를 직접 입력해주면 바로 위의 결과와 동일한 결과를 얻을 수 있습니다.

 

> select(Cars93_1, 1:5)
    Manufacturer          Model    Type Min.Price Price
1          Acura        Integra   Small      12.9  15.9
2          Acura         Legend Midsize      29.2  33.9
3           Audi             90 Compact      25.9  29.1
4           Audi            100 Midsize      30.8  37.7
5            BMW           535i Midsize      23.7  30.0

     .... 이하 생략 ....

 

 

 

참고로, dplyr 패키지의 select() 함수는 base패키지에 내장되어 있는 subset(dataframe, select=...) 함수와 기능이 같습니다.  아래 subset() 함수 결과가 위와 동일합니다. (동일한 기능을 하는 함수가 여러개 있으니깐 헷갈리지요? ^^;)

 

> subset(Cars93_1, select = c(Manufacturer:Price))
    Manufacturer          Model    Type Min.Price Price
1          Acura        Integra   Small      12.9  15.9
2          Acura         Legend Midsize      29.2  33.9
3           Audi             90 Compact      25.9  29.1
4           Audi            100 Midsize      30.8  37.7
5            BMW           535i Midsize      23.7  30.0

   .... 이하 생략 .... 

 

 

 

 

(3-3) select(dataframe, -(VAR_a:VAR_n)) : a번째부터 n번째 변수는 쏙 빼고 선별

 

다시 dplyr 패키지로 돌아와서요, select() 함수에서 변수 앞에 '-'(minus) 부호를 사용하면 그 변수는 빼고(to drop variables) 선별하라는 뜻입니다.

 

> # select(dataframe, -var1, -var2, ...) : to drop variables
> select(Cars93_1, -(Manufacturer:Price)) Max.Price MPG.city MPG.highway 1 18.8 25 31 2 38.7 18 25 3 32.3 20 26 4 44.6 19 26 5 36.2 22 30

     .... 이하 생략 ....

 

 

 

 

(3-4) select(dataframe, starts_with("xx_name")) : "xx_name"으로 시작하는 모든 변수 선별

 

select() 함수 안에 starts_with() 를 사용해서 "xx_name"으로 시작하는 모든 변수를 선별할 수 있는 재미있는 기능도 가지고 있습니다.

 

[문제] Cars93_1 데이터 프레임에서 "MPG"로 시작하는 모든 변수를 선별하시오.

 

 

> # select(dataframe, starts_with("xx_name"))





> # : select all variables, starting with a "xx_name" prefix
> select(Cars93_1, starts_with("MPG"))
MPG.city MPG.highway 1 25 31 2 18 25 3 20 26 4 19 26 5 22 30
    .... 이하 생략 ....

 

"MPG"로 시작하는 변수가 "MPG.city"(도시 연비), "MPG.highway"(고속도로 연비) 두 개가 있군요.

 

 

 

 

(3-5) select(dataframe, ends_with("xx_name")) : "xx_name"으로 끝나는 모든 변수 선별

 

starts_with가() 있으면 ends_with()도 있지 않을까 싶지요?  네, 맞습니다.  "xx_name"으로 끝나는 모든 변수를 골라내고 싶다면 select() 함수 안에다가 ends_with() 를 추가해주면 됩니다.

 

[문제] Cars93_1 데이터 프레임에서 "Price"로 끝나는 모든 변수를 선별하시오.

 

> # select(dataframe, ends_with("xx_name"))
> #   : select all variables, ending with a "xx_name" prefix
> select(Cars93_1, ends_with("Price"))
   Min.Price Price Max.Price
1       12.9  15.9      18.8
2       29.2  33.9      38.7
3       25.9  29.1      32.3
4       30.8  37.7      44.6
5       23.7  30.0      36.2

    .... 이하 생략 .... 

 

"Price"로 끝나는 변수가 "Min.Price", "Price", "Max.Price" 총 3개가 있군요.

 

 

 

 

(3-6) select(dataframe, contains("xx_name")) : "xx_name"을 포함하는 모든 변수 선별

 

select() 함수에 contains() 를 사용하면 특정 이름을 포함하는 모든 변수를 선별할 수 있습니다. 이때 "xx_name"은 대소문자를 구분하지 않습니다.

 

[문제] Cars93_1 데이터 프레임에 있는 변수들 중에서 "P"를 포함하는 모든 변수를 선별하시오.

 

> # select(dataframe, contains("xx_string"))
> #   : select all variables which contains a "xx_string" literal string
> select(Cars93_1, contains("P"))
      Type Min.Price Price Max.Price MPG.city MPG.highway
1    Small      12.9  15.9      18.8       25          31
2  Midsize      29.2  33.9      38.7       18          25
3  Compact      25.9  29.1      32.3       20          26
4  Midsize      30.8  37.7      44.6       19          26
5  Midsize      23.7  30.0      36.2       22          30

   .... 이하 생략 ....

 

"P"를 포함하는 변수로는 "Type"(<- 이거는 소문자 'p'로서, 대소문자 구분 안함), "Min.Price", "Price", "Max.Price", "MPG.city", "MPG.highway"의 6개 변수가 있군요.

 

 

 

 

(3-7) select(dataframe, matches(".xx_string.")) : 정규 표현과 일치하는 문자열이 포함된 모든 변수 선별

 

역시 대소문자는 구분하지 않습니다.

 

[문제] 변수 문자열 중간에 "P"를 포함하는 변수를 모두 선별하시오

 

> # select(dataframe, matches(".xx_string."))
> #   : Select columns that match a regular expression
> head(select(Cars93_1, matches(".P.")))
     Type Min.Price Max.Price MPG.city MPG.highway
1   Small      12.9      18.8       25          31
2 Midsize      29.2      38.7       18          25
3 Compact      25.9      32.3       20          26
4 Midsize      30.8      44.6       19          26
5 Midsize      23.7      36.2       22          30
6 Midsize      14.2      17.3       22          31

 

> head(select(Cars93_1, matches("P"))) # exactly the same with contains("P")
     Type Min.Price Price Max.Price MPG.city MPG.highway
1   Small      12.9  15.9      18.8       25          31
2 Midsize      29.2  33.9      38.7       18          25
3 Compact      25.9  29.1      32.3       20          26
4 Midsize      30.8  37.7      44.6       19          26
5 Midsize      23.7  30.0      36.2       22          30
6 Midsize      14.2  15.7      17.3       22          31

 

위에 match() 옵션 안에다가 앞에 예제에는 (".P.")를, 뒤의 예제에는 점이 없이 ("P")를 사용했는데요, 그 결과를 보고 차이를 아시겠는지요?  앞뒤로 '.'(dot) 을 붙이면 시작과 끝 말고 변수명 중간에만 특정 문자열이 포함된 변수만 선별하라는 뜻입니다.  matches(".P.") 로 한 경우에는 "P"로 시작하는 "Price" 변수가 없는 반면에, 그냥 matches("P")로 한 경우는 "P"로 시작하는 "Price"변수가 포함되어 있습니다.

 

참고로, '.'(dot) 이 없이 matches()를 쓰면 contains() 와 동일한 결과를 반환합니다.

 

 

 

 

(3-8) select(dataframe, one_of(vars)) : 변수 이름 그룹에 포함된 모든 변수 선별

 

[문제] "Manufacturer", "MAX.Price", "MPG.highway" 의 3개 변수이름을 포함하는 변수 그룹이 있다고 할 때, Cars93 데이터 프레임에서 이 변수 그룹에 있는 변수가 있다면(<- 즉, 있을 수도 있지만 없을 수도 있다는 뜻임!) 모두 선별하시오.

 

> # select(dataframe, one_of(vars))
> #   : Select columns that are from a group of names
> vars <- c("Manufacturer", "MAX.Price", "MPG.highway")
> head(select(Cars93_1, one_of(vars)))
  Manufacturer MPG.highway
1        Acura          31
2        Acura          25
3         Audi          26
4         Audi          26
5          BMW          30
6        Buick          31
Warning message:
In one_of(vars) : Unknown variables: `MAX.Price`

 

 

위의 결과를 보니 "MAX.Price"라는 변수는 "Unknown variables"라고 해서 Warning mesage가 뜨는군요.  Cars93에 보면 "Max.Price"라는 변수는 있어도 "MAX.Price"라는 변수는 없거든요.  이처럼 변수 그룹 vars 에 나열된 이름 중에서 데이터 프레임에 포함된 변수는 선별해서 반환을 해주고, 만약 해당 이름의 변수가 없다면 그냥 Warning message를 제시해주는 것으로 잘 실행이 됩니다.

 

 

반면에 그냥 select() 함수로 위의 변수 그룹을 선별하라고 해보면요, 아래처럼  "object 'MAX.Price' not found" error 메시지와 함께 아예 실행이 안되요.  one_of() 함수가 어떤 때 쓰는건지 이제 이해하시겠지요?!

 

> select(Cars93_1, Manufacturer, MAX.Price, MPG.highway)
Error in eval(expr, envir, enclos) : object 'MAX.Price' not found

 

 

 

 

 

(3-9) select(dataframe, num_range("V", a:n)) : 접두사와 숫자 범위를 조합해서 변수 선별

 

변수 이름이 동일하게 특정 접두사로 시작하는 데이터 프레임의 경우 유용하게 사용할 수 있는 함수입니다.

 

[문제] "V1", "V2", "V3", "V4"의 4개 변수를 가진 df 데이터 프레임에서 "V2", "V3" 변수를 선별하시오. 단, 이때 접두사 "V"와 숫자 범위 2:3 을 조합해서 쓰는 num_range() 옵션을 사용하시오.

 

> # select(df, num_range("V", a:n))
> #   : Select columns from num_range a to n with a prefix
> V1 <- c(rep(1, 10))
> V2 <- c(rep(1:2, 5))
> V3 <- c(rep(1:5, 2))
> V4 <- c(rep(1:10))
> 
> df <- data.frame(V1, V2, V3, V4)
> df
   V1 V2 V3 V4
1   1  1  1  1
2   1  2  2  2
3   1  1  3  3
4   1  2  4  4
5   1  1  5  5
6   1  2  1  6
7   1  1  2  7
8   1  2  3  8
9   1  1  4  9
10  1  2  5 10
> 
> select(df, num_range("V", 2:3))
   V2 V3
1   1  1
2   2  2
3   1  3
4   2  4
5   1  5
6   2  1
7   1  2
8   2  3
9   1  4
10  2  5

 

 

 

 

 

 

(4) 데이터 프레임 변수 이름 변경하기 : rename()

 

dpylr 패키지의 rename() 함수는 rename(dataframe, new_var1 = old_var1, new_var2 = old_var2, ...) 의 형식으로 사용합니다. 

 

   - 새로운 변수 이름을 앞에, 이전 변수이름을 뒤에 위치시킵니다.

   - 큰 따옴표 안씁니다.  그냥 변수 이름만 쓰면 됩니다.

   - 이름을 변경하고자 하는 변수가 여러개 일 경우 ',' (comma)로 구분해서 연속해서 써줍니다.

 

 

[문제] Cars93_1 데이터 프레임의 8개 변수명 앞에 'New_' 라는 접두사(prefix)를 붙여서 변수 이름을 바꾸시오.

 

> # rename() : rename column name
> names(Cars93_1) 
[1] "Manufacturer" "Model"        "Type"         "Min.Price"    "Price"        "Max.Price"   
[7] "MPG.city"     "MPG.highway" 
> 

> # rename(dataframe, new_var1 = old_var1, new_var2 = old_var2, ...)
> Cars93_2 <- rename(Cars93_1,
+ New_Manufacturer = Manufacturer, + New_Model = Model, + New_Type = Type, + New_Min.Price = Min.Price, + New_Price = Price, + New_Max.Price = Max.Price, + New_MPG.city = MPG.city, + New_MPG.highway = MPG.highway) > > names(Cars93_2) [1] "New_Manufacturer" "New_Model" "New_Type" "New_Min.Price" [5] "New_Price" "New_Max.Price" "New_MPG.city" "New_MPG.highway"

 

 

 

이전에 plyr 패키지의 rename() 함수나 reshaple 패키지의 rename() 함수를 사용하던 분이라면 완전 헷갈리실 겁니다.  큰 따옴표("var_name")를 써야 하는건지 말아야 하는건지, 새로운 변수 이름(new_var)과 이전 변수 이름(old_var)의 위치가 앞인지 뒤인지, 변수가 여러개인 경우 c() 로 묶어주어야 하는지 아닌지가 패키지별로 조금씩 다르거든요. (참고  링크=> http://rfriend.tistory.com/41 ) 

 

저도 매번 헷갈립니다. -_-;  그래프는 ggplot2로 단일화해 나가듯이... 데이터 전처리는 dplyr 패키지로 단일화해 나가는 것도 헷갈림을 줄일 수 있는 좋은 전략이라고 생각합니다. (기존에 익혔던 함수가 아깝고 불편함을 못느낀다면 현상 유지도 물론 ok.)

 


dplyr 패키지의 distinct(), sample_n(), sample_frac(), mutate(), transmute(), summarise() 함수에 대해서는 다음번 포스팅에서 소개하겠습니다.

 

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

 

이번 포스팅이 도움이 되었다면 아래의 '공감 ~ ♡'를 꾸욱 눌러주세요. ^^

 

 

 

 

 

728x90
반응형
Posted by Rfriend
,