이동식 저장소

4. dplyr 본문

CS/R 전산실습

4. dplyr

해스끼 2020. 12. 2. 15:11

dplyr의 함수를 이용하여 데이터 프레임을 다양한 방법으로 다듬을 수 있다. dplyr 함수는 세 가지 그룹으로 나눌 수 있다. filter(), slice(), arrange(), distinct()와 같이 행(row)을 대상으로 작업하는 함수, select(), rename(), mutate(), relocate()와 같이 열(column)을 대상으로 작업하는 함수, 요약 통계량을 계산하는 함수 summarise()가 있다.

1. 행을 작업 대상으로 하는 함수

1.1 조건에 의한 행 선택: filter()

filter()를 이용하여 특정한 조건을 만족하는 행을 선택할 수 있다. 조건을 설정할 때에는 다양한 비교 연산자(>, <, !=, == 등)와 논리 연산자(&, |, !)가 사용되며, %in%도 유용하게 사용된다. & 대신 ,를 쓸 수도 있다.

var %in% c(x, y, ...)는 변수 varc(x, y, ...)의 값 중 하나를 가질 때 TRUE를 반환한다. |보다 %in%을 쓰는 게 여러 면에서 더 좋다.

벡터 또는 스칼라 x가 특정 구간에 속하는지를 확인하려면 x >= left, x <= right로 쓸 수도 있지만, between(x, left, right)로도 같은 결과를 얻을 수 있다.

결측값의 확인은 is.na()로 할 수 있다.

1.2 인덱스에 의한 행 선택: slice()

slice()

slice()를 사용하여 특정 행을 선택하거나 제거할 수 있다. 양의 정수를 입력하면 해당 행을 선택하고, 음의 정수를 입력하면 해당 행을 제거한다. 예를 들어 df의 5-10번째 행을 선택하고자 한다면 df %>% slice(5:10)을 실행해야 한다. 만약 5-10번째 행을 제거하고자 한다면 df %>% slice(-(5:10))을 실행해야 한다. 괄호에 주의하자.

df의 마지막 행을 선택하고자 할 때는 n()을 함께 사용하면 좋다. n()은 데이터 프레임의 행의 개수를 반환하는 함수이며, 단독으로 사용될 수는 없고 dplyr의 함수와 함께 사용되어야 한다.

df %>% slice(n())

slice_head(), slice_tail()

데이터 프레임의 처음 또는 마지막 몇 행을 선택하고자 할 때 사용하는 함수이다. 행의 개수를 지정할 때는 n=10처럼, 선택할 행의 비율을 지정할 때는 prop=0.1처럼 사용하면 된다.

df %>% slice_head(n=10)       # 앞에서부터 10개의 행을 선택
df %>% slice_tail(prop=0.3)   # 뒤에서 비율 0.3만큼 행을 선택

랜덤으로 행 선택: slice_sample()

랜덤으로 행을 선택하고자 할 때 slice_sample()을 사용할 수 있다. 마찬가지로 nprop을 사용하여 행의 개수 또는 비율을 지정할 수 있으며, 복원추출을 하고자 한다면 replace=TRUE를 설정해야 한다. 기본적으로는 비복원추출을 실행한다.

df %>% slice_sample(n=3)
df %>% slice_sample(prop=0.2, replace=TRUE)

slice_max(), slice_min()

특정 변수가 가장 크거나 가장 작은 행을 선택하고자 할 때 사용하는 함수이다. 데이터프레임을 특정 변수로 정렬한 후 위에서부터 행을 선택한다고 보면 좋다.

df %>% slice_max(var, n=10)     # var이 가장 큰 10개의 행 선택
df %>% slice_min(var, prop=0.1) # var이 작은 순서대로 전체 행의 10%를 선택

1.3 행의 정렬: arrange()

특정 변수를 기준으로 행을 재배열할 때 arrange()를 사용한다. 매개변수로는 정렬의 기준이 되는 변수를 넣으면 되는데, 2개 이상을 넣을 경우 앞의 변수값이 같을 때 뒤의 변수가 기준으로 사용된다. 기본적으로 오름차순 정렬되며, 내림차순으로 정렬할 때는 desc()를 같이 사용해야 한다.

df %>% arrange(var1)             # var1으로 오름차순
df %>% arrange(var1, desc(var2)) # var1으로 오름차순, var1이 같을 경우 var2로 내림차순

1.4 중복된 행 제거: distinct()

중복된 행을 제거할 때 distinct()를 사용한다. 매개변수로는 중복 여부를 결정할 변수를 넘겨준다. 매개변수가 없을 경우 모든 행이 같을 때에만 중복된 것으로 결정한다. .keep_all=TRUE를 지정하면 데이터프레임의 모든 열을 가져올 수 있다.

df %>% distinct(var1, .keep_all=TRUE)

2. 열을 작업 대상으로 하는 함수

2.1 열 선택: select()

열 번호 또는 열 이름으로 선택

열 번호를 콤마 또는 구간으로 넘겨줄 수 있다.

df %>% select(1, 2, 3)
df %>% select(1:3)

열 이름은 열 번호와 동등하다.

df %>% select(var1:var3, var10)

논리 부정 ! 또는 - 연산자를 이용하여 행을 제거할 수 있다. 정확히는 !는 나열된 열의 여집합을, -는 차집합을 구성한다. 상황에 따라 두 연산자를 사용한 결과가 미묘하게 달라질 수 있으므로 주의하자.

df %>% select(!c(1, 4, 10)) # 1, 4, 10번째 변수를 제외
df %>% select(-c(1, 4, 10)) # 같음
df %>% select(1:4, -1)      # 1~4번 변수 중 1번 변수를 제외 = 1~3번 변수
df %>% select(1:4, !1)      # 1~4번 변수 + (1번 변수 제외 나머지) = 전체

특정 타입의 변수만 선택

정수 또는 문자형 변수만 선택할 수 있다. where()를 함께 사용한다.

df %>% select(where(is.numeric))                       # 수(int, dbl)만 선택
df %>% select(where(is.numeric) | where(is.character)) # 수 또는 문자형 변수를 선택

select()에서 사용할 수 있는 함수

  • everything(): 모든 변수를 선택한다.
  • last_col(): 마지막 변수를 선택한다.
  • starts_with("x"): 이름이 x로 시작하는 변수를 선택한다.
  • ends_with("x"): 이름이 x로 끝나는 변수를 선택한다.
  • contains("x"): 이름에 x가 들어가는 변수를 선택한다.
  • num_range("x", 1:10): c("x1", "x2", ...)와 같음

이 밖에도 all_of(vec), any_of(vec) 등의 함수가 존재한다. 이처럼 select()에서 사용하는 열 선택 방법을 tidy-select라고 하며, tidyverse의 여러 함수에서 널리 사용된다.

변수를 벡터의 형태로 선택: pull()

데이터프레임 또는 tibble에서 변수의 값은 $ 기호를 이용하여 얻을 수 있다. 하지만 $로 값을 얻는 경우 데이터프레임이 반환된다. 변수값을 벡터로 얻고자 한다면 pull()을 사용해야 한다.

pull()을 이용하여 변수를 선택하려면 변수의 위치 또는 이름을 var로 지정하면 된다. 디폴트는 -1로, 맨 마지막 변수가 선택된다.

df %>% pull(var=1)    # 가져올 변수의 위치 지정
df %>% pull(var=var2) # 가져올 변수의 이름 지정

2.2 열 이름 변경: rename(), rename_with()

select()로도 열 이름을 변경할 수 있으나, 이 경우 데이터프레임에서 이름이 변경된 변수만 반환되기 때문에 조금 불편할 수 있다. 새로운 이름을 부여하는 방식은 new_name=old_name과 같다.(119p 참고)

df %>% select(model=model_name) # 변수 model_name이 model로 변경되고, model만 반환된다.

select()에서 이름을 변경하지 않은 변수도 모두 가져오고 싶다면 everything()을 사용해야 한다.

df %>% select(model=model_name, everything())

rename()을 사용하면 이름이 변경되지 않은 변수도 기본적으로 모두 가져올 수 있다.

df %>% rename(model=model_name) # 이름을 바꾸고, df 전체가 반환됨

여러 변수의 이름을 공통된 형식으로 바꾸고자 할 경우 rename_with()를 사용하면 좋다. rename()으로도 할 수 있으나 이름을 일일이 입력해야 한다. 예컨대 모든 변수의 이름을 대문자로 바꾸려면 다음과 같이 하면 된다.

df %>% rename_with(toupper)

기본적으로는 모든 변수의 이름이 바뀐다. 변경 대상을 지정하고 싶다면 tidy-select 방식으로 조건을 주면 된다.

df %>% rename_with(toupper, contains("a")) # 이름에 a를 포함하는 변수만 대문자로 바뀜

2.3 열의 위치 변경: relocate()

열의 위치를 변경하고자 할 경우 relocate()를 사용할 수 있다. tidy-select 방식으로 위치를 바꿀 열을 선택할 수 있다. 디폴트는 맨 앞으로 움직이며. .after 또는 .before 옵션을 주어 위치를 지정할 수 있다.

df %>% relocate(var5)                          # var5를 맨 앞으로
df %>% relocate(starts_with("a"), .after=var2) # 이름에 a가 포함되는 변수를 var2 뒤로 이동

2.4 열 추가: mutate(), transmute()

데이터프레임에 새로운 변수를 추가하고자 할 경우가 있다. 이때 데이터를 그냥 넣을 수도 있고, 데이터프레임에 있는 변수를 이용하여 새로운 변수를 추가할 수도 있다. 예를 들어 데이터프레임에 거리 dist와 시간 time이 있다면, 다음과 같이 속도 speed=dist/time을 계산하여 추가할 수 있다.

df %>% mutate(speed=dist/time) %>% relocate(speed) # speed를 계산하고 맨 앞으로 옮김

mutate()를 사용하면 데이터프레임에 변수가 추가되어 반환되고, transmute()를 사용하면 추가한 변수만 얻을 수 있다.

if_else(condition, val1, val2)를 사용하여 변수를 추가할 수도 있다. if_else()는 조건 conditon이 참인 경우 val1을, 거짓인 경우 val2를 반환한다. 또는 case_when()을 사용할 수도 있다. case_when()은 C에서의 switch 문과 비슷하다.

case_when(
    condition1 ~ value_1,
    condition2 ~ value_2,
    ...
    TRUE ~ value_n)

조건은 위에서부터 아래로 평가되므로, 가장 좁은 범위의 조건이 맨 위에 위치해야 한다. 또한 모든 value_i는 같은 타입이어야 한다. 맨 마지막의 TRUE 조건은 모든 조건에 해당하지 않는 경우에 발동한다.

df %>% mutate(speed_tag = case_when(speed < 5 ~ "SLOW",
                                    speed < 10 ~ "MIDDLE",
                                    TRUE ~ "FAST"))

3. 행 자료의 요약

변수의 요약통계량을 계산하고자 할 때 summarise()를 사용할 수 있다. 첫 번째 매개변수는 데이터프레임이고, 그 뒤에 name=fun()의 형식으로 fun()의 실행 결과를 이름 name으로 받을 수 있다.

df %>% sumamrise(total_num=n(), unique_num=n_distinct()) # 중복되지 않는 열의 개수

4. 데이터프레임 그룹화

함수 group_by()를 이용하여 데이터프레임을 그룹화하고, 여기에 dplyr 함수를 적용해 보자.

4.1 그룹 데이터프레임 생성: group_by()

group_by()는 한 개 이상의 변수를 사용하여 데이터프레임을 하나 이상의 그룹으로 나눈다.

df %>% group_by(var1)          # var1이 같은 행끼리 그룹화
df %>% group_by(var1, var2)    # var1, var2가 모두 같은 행끼리 그룹화

각 그룹에 속하는 행의 개수는 tally()를 사용하여 알 수 있다.

df %>% group_by(var1) %>% tally() # var1로 구분된 각 그룹의 데이터의 수를 반환

그룹화된 데이터프레임에 구성 변수를 추가해서 그룹을 더 세분화할 수도 있다. 이 경우 .add=TRUE를 지정해야 한다. .add=TRUE를 지정하지 않은 경우 그룹 변수가 새로운 변수로 대체된다.

df %>% group_by(var1) %>% group_by(var2, .add=TRUE)

ungroup()을 이용하여 그룹을 해체할 수 있다.

4.2 그룹 데이터 프레임에 dplyr 함수 적용하기

summarise()

그룹화된 데이터프레임에 summarise()를 적용하면 그룹별로 요약통계량을 계산한다.

df %>% group(var1) %>% summarise(var2_mean=mean(var2))

예를 들어 다음의 코드를 실행하면 var1 그룹별로 var2가 결측값인 행의 개수를 셀 수 있다.

df %>% group(var1) %>% summarise(na_count=sum(is.na(var2)))

벡터 x에 대해 first(x)last(x)는 각각 x의 첫 번째와 마지막 원소를 반환한다. 유사한 함수로 nth(x, i)x[i]를 반환한다. i가 음수일 경우 x의 뒤에서 i번째 원소를 반환한다.

데이터에 결측값이 있을 경우, mean(), max(), min() 등을 사용할 때 na.rm=TRUE를 반드시 지정하자.

select()

그룹을 구성하는 변수는 선택 대상이 아니어도 자동으로 선택된다.

arrange()

.by_group=TRUE 매개변수를 주면, 그룹 변수가 첫 번째 정렬 변수로 사용된다.

df %>% group_by(var1) %>% arrange(var2, .by_group=TRUE) 
# var1로 정렬, var1이 같을 경우 var2 기준으로 정렬

mutate(), transmute()

그룹화된 데이터프레임에 mean(), max() 등을 적용하면 각 그룹별로 결과를 계산한다. 따라서 그룹화된 데이터프레임에 이러한 함수를 사용하여 새로운 변수를 만들면 그룹화되지 않았을 때와 다른 결과가 나온다.

min_rank(x)x의 각 원소가 몇 번째로 작은지를 계산한다. 같은 값이 여러 개 존재할 경우 가장 작은 순위를 부여한다.

filter()

조건을 지정할 때 요약통계량 함수(mean(), max() 등)를 사용하면 각 그룹별로 다른 값을 이용하여 행을 선택하게 된다.

slice()

그룹별로 행을 선택할 수 있다. 예를 들어 var1의 그룹별로 var2가 가장 큰 열을 선택하려면 다음과 같이 하면 된다.

df %>% group_by(var1) %>% slice_max(var2, n=1)

n(), cur_data (134p)

그룹별 행의 개수를 셀 때 n()을 이용할 수 있다.

df %>% group_by(var1) %>% summarise(group_n=n()) # var1의 각 그룹별 데이터의 개수

5. 여러 열을 대상으로 작업 수행: across()

여러 열을 대상으로 같은 작업을 수행해야 하는 경우 across()를 사용하면 편리하다.

across(.cols=everything(), .fns=NULL, ..., .names=NULL)
  • .cols: 작업을 수행할 열. tidy-select 방식으로 열을 선택할 수 있다.

  • .fns: 수행할 함수. 다음의 세 가지 방법으로 지정할 수 있다.

    • 함수 이름: ex) mean
    • purrr 방식으로 정의: ex) ~ mean(.x, na.rm=TRUE). .x는 각 열을 의미한다.
    • 여러 함수의 리스트: ex) list(Mean=mean, SD=sd)
  • ...: .fns에 넘겨줄 추가 매개변수이다. 보통 na.rm=TRUE 같은 걸 많이 쓴다.

  • .names: 결과물로 생성될 열의 이름을 지정할 수 있다. 선택된 열의 이름을 {col}, 함수의 이름을 {fn}으로 지정하여 문자열을 만들 수 있다. ex) .names="{col}_{fn}"

summarise()와 함께 사용

모든 숫자형 변수의 평균값을 계산해 보자.

df %>% summarise(across(where(is.numeric), mean)))

숫자형 변수의 평균을, 범주형 변수의 level의 개수를 구해 보자.

df %>% summarise(across(where(is.numeric), mean),
                 across(where(is.factor), nlevels))

이름이 Num으로 시작하는 변수의 평균과 표준편차를 구해 보자.

df %>% summarise(across(starts_with("Num"), list(M=mean, SD=sd)))

각 숫자형 변수의 결측값의 개수를 세어 보자.

df %>% summarise(across(where(is.numeric), ~ sum(is.na(.x))))

모든 숫자형 변수에 대해 중복되지 않는 값의 개수를 세어 보자.

df %>% summarise(across(where(is.numeric), ~ length(unique(.x))))

물론 특정 열만 지정할 수도 있다.

df %>% summarise(across(c(var1, var2), mean))

다른 함수와 함께 사용

숫자형 변수를 문자형으로 바꿔 보자.

df %>% mutate(across(where(is.numeric), ~ as.character(.x)))

var로 시작하는 변수의 값이 모두 5 이상인 행을 선택해 보자.

df %>% filter(across(starts_with("var"), ~ .x >= 5))

적어도 하나의 결측값을 갖는 행을 모두 제거해 보자. 모든 행을 대상으로 하는 것이므로 .cols는 생략할 수 있다.

df %>% filter(.fns = !is.na(.x))

그 밖에 다른 예시는 135~140p 참고

6. 행 단위 작업: rowwise()

dplyr은 기본적으로 열 단위 연산에 특화되어 있다. 하지만 rowwise()를 사용하여 행 단위 연산을 할 수 있다.

df1 = tibble(x=1:2, y=3:4, z=5:6)
# A tibble: 2 x 3
      x     y     z
  <int> <int> <int>
1     1     3     5
2     2     4     6

예를 들어 다음의 코드를 실행하면 각 행의 합이 계산된다.

df1 %>% rowwise() %>% mutate(total=sum(c(x, y, z)))
# A tibble: 2 x 4
# Rowwise: 
      x     y     z total
  <int> <int> <int> <int>
1     1     3     5     9
2     2     4     6    12

rowwise() 없이 실행하면 모든 변수의 합이 계산된다.

df1 %>% mutate(total=sum(c(x, y, z)))
# A tibble: 2 x 4
      x     y     z total
  <int> <int> <int> <int>
1     1     3     5    21
2     2     4     6    21

자세한 사용 방법은 141~143p 참고

'CS > R 전산실습' 카테고리의 다른 글

7. R 프로그래밍  (0) 2020.12.05
5. ggplot2  (0) 2020.12.03
Comments