[R] 5. mutate()
반응형
0. 예시로 쓰일 데이터 예제
set.seed(2021)
# 임의로 데이터를 생성한다. (100명의 유저가 특정 곡을 스트리밍한 이력)
temp <- tibble(
user_id = c(10000:10099),
user_age = sample(x = c("NA", round(runif(n = 100, min = 18, max = 50), 0)), size = 100, replace = TRUE),
user_gender = sample(x = c(NA, "남성", "여성"), size = 100, replace = TRUE),
song_id = sample(x = letters[1:15], size = 100, replace = TRUE),
streaming_count = rpois(n = 100, lambda = 20)
)
temp
## # A tibble: 100 x 5
## user_id user_age user_gender song_id streaming_count
## <int> <chr> <chr> <chr> <int>
## 1 10000 32 남성 m 23
## 2 10001 22 남성 i 21
## 3 10002 48 남성 g 18
## 4 10003 47 여성 g 18
## 5 10004 32 남성 o 14
## 6 10005 45 남성 a 11
## 7 10006 48 여성 g 15
## 8 10007 22 <NA> h 13
## 9 10008 35 <NA> g 19
## 10 10009 44 <NA> g 18
## # … with 90 more rows
1. mutate()
: 결측값 처리 측면에서 사용하기
- 이번에는 결측값 처리 및 변수 리코딩에 대해서 소개하고자 합니다.
- 관련 함수로
mutate()
,mutate_at()
,mutate_if()
,mutate_all()
등이 있습니다.
- tidy data에서 데이터 내에 새로운 변수를 덧붙이려면
mutate()
함수를 사용합니다. - 필요에 따라 많은 변수들에 일괄적으로 적용하기 위해서는
mutate_at()
,mutate_if()
,mutate_all()
함수를 이용하기도 합니다. - 변수변환은 크게 두 가지로 구성할 수 있습니다.
- 첫 번째는 변수에서 결측값을 확인하고 이를 처리하는 것 입니다.
- 두 번째로는 분석가가 원하는 방식으로 변수를 리코딩 하는 것 입니다.
- 관측값에서 결측값 여부를 판단하는 함수로
is.na()
함수가 있으며 TRUE 또는 FALSE와 같은 논리값이 도출됩니다. - 먼저
mutate()
,is.na()
함수를 이용하여 특정 변수의 결측값 여부를 판단하는 변수를 추가하여 해당 변수의 결측 여부를 확인할 수 있습니다.
# user_age 결측값 여부를 판단하는 변수 생성
temp %>%
mutate(user_age_na_yn = is.na(user_age)) %>%
filter(is.na(user_age)) %>%
head()
## # A tibble: 0 x 6
## # … with 6 variables: user_id <int>, user_age <chr>, user_gender <chr>,
## # song_id <chr>, streaming_count <int>, user_age_na_yn <lgl>
temp %>%
mutate(user_age_na_yn = is.na(user_age)) %>%
filter(!is.na(user_age)) %>%
head()
## # A tibble: 6 x 6
## user_id user_age user_gender song_id streaming_count user_age_na_yn
## <int> <chr> <chr> <chr> <int> <lgl>
## 1 10000 32 남성 m 23 FALSE
## 2 10001 22 남성 i 21 FALSE
## 3 10002 48 남성 g 18 FALSE
## 4 10003 47 여성 g 18 FALSE
## 5 10004 32 남성 o 14 FALSE
## 6 10005 45 남성 a 11 FALSE
- user_age가 결측값이면 TRUE를 반환하는 user_age_na_yn 이라는 이름의 새로운 변수가 추가되었습니다.
- 하나의 관측치에 최소 한 개 이상의 결측값이 있다면 결측 케이스로 잡고 싶습니다.
- 그렇다면
is.na(.)
함수와 해당 row의 합계를 계산하는rowSums()
를 이용하여 구할 수 있습니다.
temp %>%
mutate(na_yn = rowSums(is.na(.))) %>%
head(10)
## # A tibble: 10 x 6
## user_id user_age user_gender song_id streaming_count na_yn
## <int> <chr> <chr> <chr> <int> <dbl>
## 1 10000 32 남성 m 23 0
## 2 10001 22 남성 i 21 0
## 3 10002 48 남성 g 18 0
## 4 10003 47 여성 g 18 0
## 5 10004 32 남성 o 14 0
## 6 10005 45 남성 a 11 0
## 7 10006 48 여성 g 15 0
## 8 10007 22 <NA> h 13 1
## 9 10008 35 <NA> g 19 1
## 10 10009 44 <NA> g 18 1
- 만일 결측값이 있는 변수가 2개라면 해당 값은 2라는 값을 반환할 것이고 결측 여부를 판단할 때에는 na_yn 값이 1이상인 케이스를 선별하시면 됩니다.
- 특정 값이 잘못되어 해당 값을 결측값 또는 다른 값으로 대체하고 싶다면
ifelse()
함수를 이용할 수 있습니다. ifelse()
함수는 아래와 같은 형태를 갖습니다. SQL에서 IF 함수와 같습니다.
ifelse(조건, 조건을 만족할 경우 값, 조건을 만족하지 않는 경우 값)
- 이를 활용하여 결측값이나 잘못 입력된 값을 수정할 수 있습니다.
temp %>%
mutate(adjusted_user_gender = ifelse(is.na(user_gender), "여성", user_gender))
## # A tibble: 100 x 6
## user_id user_age user_gender song_id streaming_count adjusted_user_gender
## <int> <chr> <chr> <chr> <int> <chr>
## 1 10000 32 남성 m 23 남성
## 2 10001 22 남성 i 21 남성
## 3 10002 48 남성 g 18 남성
## 4 10003 47 여성 g 18 여성
## 5 10004 32 남성 o 14 남성
## 6 10005 45 남성 a 11 남성
## 7 10006 48 여성 g 15 여성
## 8 10007 22 <NA> h 13 여성
## 9 10008 35 <NA> g 19 여성
## 10 10009 44 <NA> g 18 여성
## # … with 90 more rows
- 만일 특정 값이 ’-’로 입력되어 이를 결측값으로 처리 하고 싶으면 아래와 같이 코드를 입력하여 보정할 수 있습니다.
데이터 %>%
mutate(변수 = ifelse(변수 == "-", NA, 변수))
- 하지만 위와 같은 케이스가 변수 하나가 아닌 변수가 여러 개인 경우
mutate_all()
함수를 이용할 수 있습니다.
데이터 %>%
mutate_all(funs(ifelse(. == "-", NA, .)))
- 원하는 변수만을 선정하고 싶다면
mutate_at()
함수를 사용하면 됩니다. 원하는 변수의 위치를 2:5(2번째부터 5번째)와 같이 지정한 후 함수에 대입하시면 됩니다. (또는vars()
함수를 이용하여 괄호 안에 해당 변수를 입력하시면 됩니다.)
데이터 %>%
mutate_at(2:5, funs(ifelse(. == "-", NA, .)))
데이터 %>%
mutate_at(vars(변수1, 변수2), funs(ifesle(. == "-", NA, .)))
2. mutate()
: 변수 리코딩(re-coding)
- 변수 리코딩은 앞서 언급했던 결측값 확인 및 변환 과정과 본질적으로 유사합니다.
- 리코딩 과정은 데이터 분석가가 어떤 데이터를 분석하고자 하는가에 따라 계속 바뀌기 때문에 분석하고자 하는 데이터를 염두해두면서 창의적으로 코딩 할 수 있는 능력이 필요합니다.
- 우선 먼저 R에서
tidyverse
의 경우 변수의 형태가 어떻게 정의되는지부터 파악해야 합니다. 내용은 아래의 표를 참고하시면 됩니다.
연구방법론 | 통계 분석 관련 문헌 | 컴퓨터 언어 관련 문헌 |
---|---|---|
명목변수 | 무순위 범주형 변수 | 논리형(logical-type) |
문자형(character-type) | ||
요인형(factor-type) | ||
순위변수 | 유순위 범주형 변수 | 요인형(factor-type) |
등간변수 | 연속형 변수 | 정수형(integer-type) |
더블형(double-type) | ||
날짜 및 시간형(date and time-type) | ||
비율변수 | 연속형 변수 | 정수형(integer_type) |
더블형(double-type) |
2. 1. 범주형 변수를 이분형 변수로..
- 가장 간단한 변수 리코딩 형태로 조건에 부합할 경우 1(Y), 그렇지 않는 경우에는 0(N)과 같이 값을 이분형으로 부여하는 방법이 있습니다.
- 이렇게 코딩된 변수를 흔히 이분 변수(binary variable) 또는 가변수(dummy variable)라고 불리웁니다.
- 이는
ifelse()
함수로 간단하게 리코딩할 수 있습니다. 또한ifelse()
함수 안에ifelse()
함수를 또 사용할 수 있어서 효율적으로 리코딩을 할 수 있습니다.
# 성별이 남성이면 1, 여성이면 2, 결측값은 여성으로 분류하는 예시
temp %>%
mutate(new_user_gender = ifelse( ifelse(is.na(user_gender), "여성", user_gender) == "남성", 1, 2)) %>%
head(10)
## # A tibble: 10 x 6
## user_id user_age user_gender song_id streaming_count new_user_gender
## <int> <chr> <chr> <chr> <int> <dbl>
## 1 10000 32 남성 m 23 1
## 2 10001 22 남성 i 21 1
## 3 10002 48 남성 g 18 1
## 4 10003 47 여성 g 18 2
## 5 10004 32 남성 o 14 1
## 6 10005 45 남성 a 11 1
## 7 10006 48 여성 g 15 2
## 8 10007 22 <NA> h 13 2
## 9 10008 35 <NA> g 19 2
## 10 10009 44 <NA> g 18 2
2. 2. 연속형 변수를 이분형 변수로..
- 마찬가지로 연속형 변수 또한 이분형으로 부여할 수 있습니다.
- 예시로 특정 곡에 대한 스트리밍 값이 20회 이상이면 만족, 그 외에는 불만족으로 임의로 분류해보겠습니다.
(어떤 변수를 고려하든 결측값 존재유무를 꼭 살펴봐야 합니다.)
# ifelse() 함수 사용, streaming_count가 20회 이상이면 만족, 그 외에는 불만족으로 가정
temp %>%
mutate(satisfaction_yn = ifelse(streaming_count >= 20, "만족", "불만족")) %>%
head(10)
## # A tibble: 10 x 6
## user_id user_age user_gender song_id streaming_count satisfaction_yn
## <int> <chr> <chr> <chr> <int> <chr>
## 1 10000 32 남성 m 23 만족
## 2 10001 22 남성 i 21 만족
## 3 10002 48 남성 g 18 불만족
## 4 10003 47 여성 g 18 불만족
## 5 10004 32 남성 o 14 불만족
## 6 10005 45 남성 a 11 불만족
## 7 10006 48 여성 g 15 불만족
## 8 10007 22 <NA> h 13 불만족
## 9 10008 35 <NA> g 19 불만족
## 10 10009 44 <NA> g 18 불만족
- 이러한 케이스는
ifelse()
함수 외에case_when()
함수를 이용해서도 구현할 수 있습니다. case_when()
함수는 SQL에서 CASE WHEN ~ THEN ~ ELSE ~ END 구문과 같습니다.
# case_when() 함수 사용, streaming_count가 20회 이상이면 만족, 그 외에는 불만족으로 가정
temp %>%
mutate(
satisfaction_yn = case_when(
streaming_count >= 20 ~ "만족",
TRUE ~ "불만족"
)
) %>%
head(10)
## # A tibble: 10 x 6
## user_id user_age user_gender song_id streaming_count satisfaction_yn
## <int> <chr> <chr> <chr> <int> <chr>
## 1 10000 32 남성 m 23 만족
## 2 10001 22 남성 i 21 만족
## 3 10002 48 남성 g 18 불만족
## 4 10003 47 여성 g 18 불만족
## 5 10004 32 남성 o 14 불만족
## 6 10005 45 남성 a 11 불만족
## 7 10006 48 여성 g 15 불만족
## 8 10007 22 <NA> h 13 불만족
## 9 10008 35 <NA> g 19 불만족
## 10 10009 44 <NA> g 18 불만족
2. 3. 범주형 변수의 수준 간소화
- 범주형 변수를 또 다른 범주형 변수로 리코딩 하는 목적은 크게 두 가지 입니다.
- 첫 번째로 범주형 변수의 수준을 줄여서 데이터를 간소화 하는 목적
- 두 번째로 데이터 분석의 가독성을 높이기 위해 범주형 변수의 수준을 재배치
- 여기서는 주로 분석할 때 쓰이는 첫 번째 케이스에 대해서만 살펴보겠습니다.
- 저는
ifelse()
함수보다는case_when()
함수가 활용도가 더 높다고 생각하기에case_when()
함수를 이용하겠습니다. - 예시로 곡 d, e, f는 인기곡, 나머지는 비인기곡으로 임의로 가정하였습니다.
- 여기서
%in%
구문은 SQL에서 IN 절과 같다고 생각하시면 됩니다. %in%
구문을 이용하여 포함되는 관계는 R에서 벡터를 정의할 때 쓰이는c()
괄호안에 묶어줍니다. 이는 텍스트형태도 가능합니다.
- 여기서
# d, e, f 곡은 인기곡, 나머지는 비인기곡으로 간주
temp %>%
mutate(
song_class_flag = case_when(
song_id %in% c("d", "e", "f") ~ "인기곡",
TRUE ~ "비인기곡"
)
)
## # A tibble: 100 x 6
## user_id user_age user_gender song_id streaming_count song_class_flag
## <int> <chr> <chr> <chr> <int> <chr>
## 1 10000 32 남성 m 23 비인기곡
## 2 10001 22 남성 i 21 비인기곡
## 3 10002 48 남성 g 18 비인기곡
## 4 10003 47 여성 g 18 비인기곡
## 5 10004 32 남성 o 14 비인기곡
## 6 10005 45 남성 a 11 비인기곡
## 7 10006 48 여성 g 15 비인기곡
## 8 10007 22 <NA> h 13 비인기곡
## 9 10008 35 <NA> g 19 비인기곡
## 10 10009 44 <NA> g 18 비인기곡
## # … with 90 more rows
- 다음은
forcats
라이브러리에 있는fct_collapse()
함수를 이용하겠습니다. fct_*()
형태로 구성된 함수들은 범주형 변수를 다루는 분석가들에게 매우 유용한 함수이며 해당 함수는 숫자가 아닌 텍스트 형식의 변수에 대해 리코딩할 수 있는 함수입니다.- 아래는
fct_collapse()
함수를 이용한 예시입니다.
# d, e, f 곡은 인기곡, 나머지는 비인기곡으로 간주
temp %>%
mutate(
song_class_flg = fct_collapse(
song_id, # 대상변수
"인기곡" = c("d", "e", "f"), # 조건 (지정 = 대상)
other_level = "비인기곡" # 그외 조건을 만족하지 않는 관측치들에 대헤 일괄처리
)
)
## # A tibble: 100 x 6
## user_id user_age user_gender song_id streaming_count song_class_flg
## <int> <chr> <chr> <chr> <int> <fct>
## 1 10000 32 남성 m 23 비인기곡
## 2 10001 22 남성 i 21 비인기곡
## 3 10002 48 남성 g 18 비인기곡
## 4 10003 47 여성 g 18 비인기곡
## 5 10004 32 남성 o 14 비인기곡
## 6 10005 45 남성 a 11 비인기곡
## 7 10006 48 여성 g 15 비인기곡
## 8 10007 22 <NA> h 13 비인기곡
## 9 10008 35 <NA> g 19 비인기곡
## 10 10009 44 <NA> g 18 비인기곡
## # … with 90 more rows
- 이 밖에 유용한 함수들을 몇개 설명드리겠습니다.
- 분석가가 정한 상위 N개가 아닌 다른 범주형 변수의 수준들을 하나로 뭉쳐주는
fct_lump()
함수가 있습니다. - 특정 변수와 상위 N개에 대한 파라미터를 지정해주면 해당 변수의 빈도 기준 상위 N개외 나머지를 “Ohter”로 구분지어줍니다.
temp %>%
mutate(
temp_flg = fct_lump(song_id, 4)
) %>%
head(10)
## # A tibble: 10 x 6
## user_id user_age user_gender song_id streaming_count temp_flg
## <int> <chr> <chr> <chr> <int> <fct>
## 1 10000 32 남성 m 23 Other
## 2 10001 22 남성 i 21 Other
## 3 10002 48 남성 g 18 g
## 4 10003 47 여성 g 18 g
## 5 10004 32 남성 o 14 Other
## 6 10005 45 남성 a 11 a
## 7 10006 48 여성 g 15 g
## 8 10007 22 <NA> h 13 Other
## 9 10008 35 <NA> g 19 g
## 10 10009 44 <NA> g 18 g
- 범주형 변수의 수준 배치를 변경해주는 함수 :
fct_relevel()
- 범주형 변수의 수준들 중 가장 빈도수가 높은 범주일수록 앞에 배치되도록 자동으로 리코딩 해주는 함수 :
fct_infreq()
- 지정된 함수를 기준으로 기준 변수가 크거나 또는 작도록 리코딩하는 함수 :
fct_reorder()
fct_reorder(범주형변수, 기준변수, fun = 적용함수, .desc = 논리값)
2. 4. 연속형 변수를 범주형 변수로..
- 종종 데이터 분석결과를 보다 쉽게 전달하기 위해 연속형 변수를 범주형 변수로 리코딩 합니다.
- 예를 들어 나이 변수를 연령대로 리코딩한다거나, 스트리밍 횟수를 카테고리화 시키는 것이 대표적인 예라고 생각할 수 있습니다.
- 이 또한 역시
cut()
함수를 이용하여 임의로 연속형 변수를 카테고리화 시키거나case_when()
함수를 이용하는 방법이 있습니다.- 이렇게 연속형 변수를 몇 개의 범주로 리코딩하면 데이터 분석 결과를 쉽게 전달할 수 있는 장점이 있지만 범주 구분 기준이 객관적이거나 합리적이지 않을 경우 분석 결과가 왜곡될 수 있음을 주의하시길 바랍니다.
# streaming_count를 범주형 변수화
temp %>%
mutate(
streaming_count_grouping = case_when(
streaming_count >= 1 & streaming_count <= 5 ~ "1~5",
streaming_count > 5 & streaming_count <= 10 ~ "6~10",
streaming_count > 10 & streaming_count <= 20 ~ "11~20",
TRUE ~ "21~"
)
) %>%
head(10)
## # A tibble: 10 x 6
## user_id user_age user_gender song_id streaming_count streaming_count_grouping
## <int> <chr> <chr> <chr> <int> <chr>
## 1 10000 32 남성 m 23 21~
## 2 10001 22 남성 i 21 21~
## 3 10002 48 남성 g 18 11~20
## 4 10003 47 여성 g 18 11~20
## 5 10004 32 남성 o 14 11~20
## 6 10005 45 남성 a 11 11~20
## 7 10006 48 여성 g 15 11~20
## 8 10007 22 <NA> h 13 11~20
## 9 10008 35 <NA> g 19 11~20
## 10 10009 44 <NA> g 18 11~20
- 위와 다르게 등간격으로 범주화 시킬 필요가 발생할 수 있습니다.
- 나이를 연령대로 범주화 시킬 때 일정한 간격으로 범주화 시키고 싶을 땐
cut_width()
함수를 이용할 수 있습니다. - 아래 예시는
cut_width()
함수를 이용하여 나이를 연령대로 범주화 시킨 예시 입니다.cut_width(변수, width = 폭, boundary = 경계값, colosed = "left")
- <참고> 경계값과 폐구간을 지정하는 boundary와 closed 옵션도 있습니다.
# user_age 변수를 10단위로 등간격 카테고리화
temp %>%
filter(!is.na(user_age)) %>%
mutate(
user_age_category = cut_width(user_age, width = 10)
) %>%
head(10)
## Warning in cut_width(user_age, width = 10): 강제형변환에 의해 생성된 NA 입니다
## # A tibble: 10 x 6
## user_id user_age user_gender song_id streaming_count user_age_category
## <int> <chr> <chr> <chr> <int> <fct>
## 1 10000 32 남성 m 23 (25,35]
## 2 10001 22 남성 i 21 [15,25]
## 3 10002 48 남성 g 18 (45,55]
## 4 10003 47 여성 g 18 (45,55]
## 5 10004 32 남성 o 14 (25,35]
## 6 10005 45 남성 a 11 (35,45]
## 7 10006 48 여성 g 15 (45,55]
## 8 10007 22 <NA> h 13 [15,25]
## 9 10008 35 <NA> g 19 (25,35]
## 10 10009 44 <NA> g 18 (35,45]
- 이 밖에 등간격을 나누는 함수 중 아래와 같은 유용한 함수도 있으니 참고해주세요.
cut_interval()
,cut_number()
# n등분한 후 n개의 범주로 지정하는 함수: cut_interval(변수, n)
temp %>%
filter(!is.na(user_age)) %>%
select(user_id, user_age) %>%
mutate(user_age = as.numeric(user_age)) %>%
mutate(
user_age_category = cut_interval(user_age, n = 10)
)
## Warning in mask$eval_all_mutate(quo): 강제형변환에 의해 생성된 NA 입니다
## # A tibble: 100 x 3
## user_id user_age user_age_category
## <int> <dbl> <fct>
## 1 10000 32 (31,34]
## 2 10001 22 [19,22]
## 3 10002 48 (46,49]
## 4 10003 47 (46,49]
## 5 10004 32 (31,34]
## 6 10005 45 (43,46]
## 7 10006 48 (46,49]
## 8 10007 22 [19,22]
## 9 10008 35 (34,37]
## 10 10009 44 (43,46]
## # … with 90 more rows
# 빈도수 기준으로 빈도수가 유사하게 n개의 범주로 지정해주는 함수: cut_number(변수, n)
temp %>%
filter(!is.na(user_age)) %>%
select(user_id, user_age) %>%
mutate(user_age = as.numeric(user_age)) %>%
mutate(
user_age_category = cut_number(user_age, n = 10)
)
## Warning in mask$eval_all_mutate(quo): 강제형변환에 의해 생성된 NA 입니다
## # A tibble: 100 x 3
## user_id user_age user_age_category
## <int> <dbl> <fct>
## 1 10000 32 (30.8,32.4]
## 2 10001 22 [19,22]
## 3 10002 48 (46,48]
## 4 10003 47 (46,48]
## 5 10004 32 (30.8,32.4]
## 6 10005 45 (40,45]
## 7 10006 48 (46,48]
## 8 10007 22 [19,22]
## 9 10008 35 (32.4,39]
## 10 10009 44 (40,45]
## # … with 90 more rows
2. 5. 변수의 데이터 타입 변환
- 여기서 언급할 내용은 데이터의 타입을 정의 및 변환 시키는 내용입니다.
- 이 부분은 어렵지 않으므로 간단한 함수만 언급하고 넘어가겠습니다.
- 범주형 변수를 문자형 데이터에서 요인형 데이터로, 또는 그 반대로 변환시키는데 사용되는 함수들은 아래와 같습니다.
as.factor()
: R 베이스 함수이기 때문에tidyverse
라이브러리를 불러오지 않아도 작동하며, 괄호 안의 변수를 요인형 데이터 형태의 변수로 리코딩합니다.as.character()
: 입력된 변수를 문자형 데이터 형태의 변수로 리코딩합니다.as_factor()
:as.factor()
함수와 본질적으로 동일하지만 라벨이 붙은 더블형(<dlb+lbl>
) 데이터 형태인 경우 라벨의 값을 요인형 데이터 형태의 변수로 리코딩합니다.
2. 6. 텍스트 형태의 변수 처리
- 해당 부분은 감정분석 등 텍스트 마이닝에 쓰이는 비정형 데이터에 대한 전처리할 때 주로 쓰이지만 정형 데이터를 주로 다루는 분야에서는 비교적 덜 사용하시는 함수들로 구성되어 있습니다.
- 보다 체계적이고 방대한 텍스트 데이터 처리 방법을 타이디 접근법으로 학습하시고 싶으신 분들은 [https://www.tidytextmining.com/]를 참고하시면 도움이 많이 되실겁니다.
- 해당 내용은 여기서는 생략하고 추가로 보완하겠습니다.
반응형
'tidyverse' 카테고리의 다른 글
[R] 7. 데이터 형태 변환 (0) | 2021.07.05 |
---|---|
[R] 6. 날짜 및 시간 변수 (lubridate) (3) | 2021.07.05 |
[R] 4. select(), filter(), group_by(), summarise(), arrange(), rename() (0) | 2021.07.05 |
[R] 3. 파이프 오퍼레이터 (%>%) (0) | 2021.07.05 |
[R] 2. tibble 데이터 (0) | 2021.07.05 |
TAGS.