고객이탈 WA_Fn UseC_ Telco Customer Churn.csv 데이터를 웹사이트에서 바로 불러들인다.
그리고 나서 skimr
팩키지 skim
함수를 통해 전반적인 데이터 현황을 살펴보고 나서, 더러운 데이터에 대한 위생관리에 대한 전략을 수립한다. 데이터 전처리 전략에 바탕을 두고 차근차근 데이터 전처리 작업을 진행한다.
더러운 데이터를 멸균하여 깨끗한 상태를 만드는데 다음과 같은 팩키지가 도구로 활용된다.
dplyr
janitor
forctas
skimr
전산(IT) 담당자들이 생각하는 잘 정제된 멸균상태의 깨끗한 데이터와 데이터 분석과 모형개발을 위한 데이터는 차이가 크다. 설사 전산담당자가 보기에 깨끗한 질좋은 데이터라 할지라도 데이터 과학자가 보기에는 더럽기 그지없는 데이터도 참 많다.
데이터에 대한 위생상태 점검에 들어가보자. 크게 세가지 영역으로 나눠서 살펴본다.
위생상태 점검을 위해서 janitor
팩키지 함수를 두루 익혀 사용한다. 결측값 시각화를 위해서 Amelia
팩키지 missmap()
함수도 함께 사용한다.
# 0. 환경설정 ------
library(tidyverse)
library(caret)
library(janitor)
library(ggridges)
library(ggthemes)
library(cowplot)
library(corrplot)
library(corrr)
library(plotly)
library(crosstalk)
library(skimr)
library(viridis)
# 1. 데이터 ------
## 1.1. 데이터 가져오기 -----
churn_dat <- read_csv("https://community.watsonanalytics.com/wp-content/uploads/2015/03/WA_Fn-UseC_-Telco-Customer-Churn.csv")
다양한 방식으로 변수명을 사람들이 나름대로 규칙을 만들어서 데이터에 붙이게 된다. 이를 janitor
팩키지 clean_names()
함수를 사용해서 R 관계를 따르는 변수명으로 변환시킨다.
원본 데이터 변수명
## 1.2. 데이터 위생점검 -----
### 변수명
names(churn_dat)
[1] "customerID" "gender" "SeniorCitizen"
[4] "Partner" "Dependents" "tenure"
[7] "PhoneService" "MultipleLines" "InternetService"
[10] "OnlineSecurity" "OnlineBackup" "DeviceProtection"
[13] "TechSupport" "StreamingTV" "StreamingMovies"
[16] "Contract" "PaperlessBilling" "PaymentMethod"
[19] "MonthlyCharges" "TotalCharges" "Churn"
R 관계를 따르는 변수명
## 1.2. 데이터 위생점검 -----
### 변수명
churn_dat %>%
clean_names() %>%
names()
[1] "customer_id" "gender" "senior_citizen"
[4] "partner" "dependents" "tenure"
[7] "phone_service" "multiple_lines" "internet_service"
[10] "online_security" "online_backup" "device_protection"
[13] "tech_support" "streaming_tv" "streaming_movies"
[16] "contract" "paperless_billing" "payment_method"
[19] "monthly_charges" "total_charges" "churn"
관측점(행)에 중복이 있게 되면 기계학습 모형 함수에서 받아들여지지 않는 경우도 다반사다. 이를 위해서 중복된 관측점을 제거하는 것이 필요하다. 이를 위해서 janitor
팩키지 get_dupes()
함수를 사용해서 중복된 관측점도 위생점검 대상에 포함시킨다.
### 데이터프레임 행에 대한 위생점검
#### 중복 확인
churn_dat %>%
clean_names() %>%
get_dupes(customer_id)
# A tibble: 0 x 22
# ... with 22 variables: customer_id <chr>, dupe_count <int>,
# gender <chr>, senior_citizen <int>, partner <chr>, dependents <chr>,
# tenure <int>, phone_service <chr>, multiple_lines <chr>,
# internet_service <chr>, online_security <chr>, online_backup <chr>,
# device_protection <chr>, tech_support <chr>, streaming_tv <chr>,
# streaming_movies <chr>, contract <chr>, paperless_billing <chr>,
# payment_method <chr>, monthly_charges <dbl>, total_charges <dbl>,
# churn <chr>
모든 변수에 대해서 존재하는 결측값에 대해서 summarize_all()
함수와 is.na()
함수를 사용해서 파악하고, Amelia
팩키지 missmap()
함수를 사용해서 시각적으로도 확인한다.
#### 결측점 확인: total_charges 결측값 제거
churn_dat %>%
summarise_all(funs(sum(is.na(.)))) %>%
gather(variable, missing_cnt) %>%
arrange(desc(missing_cnt))
# A tibble: 21 x 2
variable missing_cnt
<chr> <int>
1 TotalCharges 11
2 customerID 0
3 gender 0
4 SeniorCitizen 0
5 Partner 0
6 Dependents 0
7 tenure 0
8 PhoneService 0
9 MultipleLines 0
10 InternetService 0
# ... with 11 more rows
# map(churn_df, ~ sum(is.na(.))) %>% keep( function(x) x > 0)
#### 결측값 시각화
Amelia::missmap(churn_dat, main = "결측값과 관측값")
janitor
팩키지 remove_empty()
함수를 사용해서 빈행과 빈열을 점검한다.
### 데이터프레임 빈 행과 열에 대한 위생점검
churn_dat %>%
remove_empty(c("rows", "cols")) %>% dim()
[1] 7043 21
class
함수를 사용해서 데이터프레임 변수 자료형을 파악한다.
map_df(churn_dat, class) %>%
gather(variable, value) %>%
count(value)
# A tibble: 3 x 2
value n
<chr> <int>
1 character 17
2 integer 2
3 numeric 2
janitor
, dplyr
, Amelia
등 팩키지를 활용하여 고객이탈 원본 데이터에 대한 위생점검 결과 다음 사항이 파악되어 조치가 필요하다.
senior_citizen
)도 요인(factor)형으로 자료변환이 필요함.위생점검 결과 보고서를 바탕으로 변수명 변환, 결측값처리, 문자열 변수 자료변환 등의 일련의 과정을 sanitize_data()
함수에 적어 일괄적으로 반영시킨다.
### 위생점검 결과 반영 코드
sanitize_data <- function(df) {
res_df <- df %>%
clean_names() %>%
select(-customer_id) %>%
filter(complete.cases(.)) %>%
mutate_if(is.character, as.factor) %>%
mutate(senior_citizen = factor(senior_citizen, levels = c(0, 1), labels = c("No", "Yes")))
return(res_df)
}
churn_df <- sanitize_data(churn_dat)
원본데이터를 정보기술 영역에서 데이터 과학 영역으로 가져온 후에 위생/멸균 작업을 거쳤다. 다음 단계로 모형과 시각화를 위한 데이터 정제작업을 추가로 수행한다. 이를 위해서 recipes
와 같은 특별한 팩키지가 동원되기도 하지만, 현재로는 tidyverse
팩키지의 핵심기능만 사용해서 모형과 시각화를 위한 데이터 정제작업을 진행한다.
문자형 변수를 범주형 즉, 요인형(factor) 자료구조로 변환시키게 되면 다양한 연산작업이 가능하다. 대표적인 것이 각 요인별로 수준(level)이 몇개씩인지 파악하고, 최빈값을 갖는 범주가 무엇인지 파악도 가능하다 이를 통해서 불필요한 범주는 없애고 유사 범주는 묶는 작업을 통해 시각화와 모형 개발을 위한 적절한 범주형 변수에 대한 작업을 사전에 취할 수 있다.
nlevels()
함수를 통해 범주형 변수별로 수준 갯수를 파악할 수 있다.
## 1.3. 모형 데이터프레임 -----
### 범주형 데이터
#### 범주형 변수 범주(level) 개수
churn_df %>%
select_if(is.factor) %>%
summarise_all(nlevels) %>%
gather(variable, cnt)
# A tibble: 17 x 2
variable cnt
<chr> <int>
1 gender 2
2 senior_citizen 2
3 partner 2
4 dependents 2
5 phone_service 2
6 multiple_lines 3
7 internet_service 3
8 online_security 3
9 online_backup 3
10 device_protection 3
11 tech_support 3
12 streaming_tv 3
13 streaming_movies 3
14 contract 3
15 paperless_billing 2
16 payment_method 4
17 churn 2
skim()
함수에 범주형 변수만 넣어서 단변량 기초 요약통계량 산출도 가능하다. 관심있는 변수(churn
)와 교차분석은 아래에서 추가로 진행한다.
#### 범주형 변수 요약 통계량
churn_df %>%
select_if(is.factor) %>%
skim()
Factor Variables
# A tibble: 17 x 7
var type complete missing n n_unique stat
<chr> <chr> <dbl> <dbl> <dbl> <dbl> <chr>
1 churn factor 7032 0 7032 2.00 No: 5163 Yes:…
2 contract factor 7032 0 7032 3.00 Month-to-mont…
3 dependents factor 7032 0 7032 2.00 No: 4933 Yes:…
4 device_protection factor 7032 0 7032 3.00 No: 3094 No i…
5 gender factor 7032 0 7032 2.00 Female: 3483 …
6 internet_service factor 7032 0 7032 3.00 DSL: 2416 Fib…
7 multiple_lines factor 7032 0 7032 3.00 No: 3385 No p…
8 online_backup factor 7032 0 7032 3.00 No: 3087 No i…
9 online_security factor 7032 0 7032 3.00 No: 3497 No i…
10 paperless_billing factor 7032 0 7032 2.00 No: 2864 Yes:…
11 partner factor 7032 0 7032 2.00 No: 3639 Yes:…
12 payment_method factor 7032 0 7032 4.00 Bank transfer…
13 phone_service factor 7032 0 7032 2.00 No: 680 Yes: …
14 senior_citizen factor 7032 0 7032 2.00 No: 5890 Yes:…
15 streaming_movies factor 7032 0 7032 3.00 No: 2781 No i…
16 streaming_tv factor 7032 0 7032 3.00 No: 2809 No i…
17 tech_support factor 7032 0 7032 3.00 No: 3472 No i…
ggplot
팩키지와 tidy evaluation
을 결합하여 고객이탈 변수(churn
)와 교차하여 시각화도 가능하다. 인용부호를 넣어 변수를 전달하는 경우와 인용부호없이 변수를 전달하는 경우에 따라 달리 확인하고 사용한다.
#### 범주형 변수
cat_var <- churn_df %>%
select_if(is.factor) %>%
colnames()
#### 범주형 변수 시각화
visualize_category <- function(variable) {
# var <- enquo(variable)
var <- sym(variable)
churn_df %>%
select_if(is.factor) %>%
count(!!var, churn) %>%
ggplot(aes(x=!!var, y=n, fill=churn)) +
geom_col() +
labs(x=rlang::quo_text(var), y="") +
scale_y_continuous(labels = scales::comma) +
scale_fill_viridis_d() +
theme(legend.position = "none") +
scale_x_discrete(label = function(x) abbreviate(x, minlength=7))
}
# visualize_category(gender)
visualize_category("gender")
고객이탈 변수(churn
)와 교차하여 모든 범주형 변수를 시각화하고자 할 경우 각 범주형 변수를 고객이탈 변수와 교차분석하여 시각화한 객체를 리스트 객체로 담아내고 이를 multiplot()
함수의 도움으로 한장에 시각화하여 담아낸다.
multiplot <- function(..., plotlist=NULL, file, cols=1, layout=NULL) {
require(grid)
plots <- c(list(...), plotlist)
numPlots = length(plots)
if (is.null(layout)) {
layout <- matrix(seq(1, cols * ceiling(numPlots/cols)), ncol = cols, nrow = ceiling(numPlots/cols))
}
if (numPlots==1) {
print(plots[[1]])
}
else {
grid.newpage()
pushViewport(viewport(layout = grid.layout(nrow(layout), ncol(layout))))
for (i in 1:numPlots) {
matchidx <- as.data.frame(which(layout == i, arr.ind = TRUE))
print(plots[[i]], vp = viewport(layout.pos.row = matchidx$row, layout.pos.col = matchidx$col))
}
}
}
cat_barplot_lst <- map(setdiff(cat_var, "churn"), visualize_category)
multiplot(plotlist=cat_barplot_lst, cols=4)
아니요(“No”)로 범주를 묶는 것이 타당한 것으로 앞선 시각화 결과를 반영하여 범주를 묶는 작업을 해당 범주형 변수별로 작업을 수행하고 나서, 범주형 변수 기술 통계량도 산출해본다.
#### 범주형 범주 결합
churn_m_df <- churn_df %>%
mutate(multiple_lines = ifelse(str_detect(multiple_lines, "No"), "No", "Yes"),
internet_service = ifelse(str_detect(internet_service, "No"), "No", "Yes"),
online_security = ifelse(str_detect(online_security, "No"), "No", "Yes"),
online_backup = ifelse(str_detect(online_backup, "No"), "No", "Yes"),
device_protection = ifelse(str_detect(device_protection, "No"), "No", "Yes"),
tech_support = ifelse(str_detect(tech_support, "No"), "No", "Yes"),
streaming_tv = ifelse(str_detect(streaming_tv, "No"), "No", "Yes"),
streaming_movies = ifelse(str_detect(streaming_movies, "No"), "No", "Yes"))
cat_desc_table <- function(variable) {
var <- sym(variable)
churn_m_df %>%
tabyl(!!var, churn) %>%
adorn_totals("row") %>%
adorn_percentages("row") %>%
adorn_pct_formatting() %>%
adorn_ns() %>%
adorn_title("combined") %>%
tbl_df()
}
# cat_desc_table("gender")
cat_desc_table_list <- map(setdiff(cat_var, "churn"), cat_desc_table)
names(cat_desc_table_list) <- setdiff(cat_var, "churn")
listviewer::jsonedit(cat_desc_table_list)
is.numeric()
함수를 통해 연속형 변수를 발래내고 이를 churn
변수와 교차하여 고객이탈별로 연속형 변수에 어떠한 변동이 있는지 시각적으로 확인해본다.
### 연속형 데이터
cont_var <- churn_df %>%
select_if(is.numeric) %>%
names()
y_p <- churn_df %>%
select(cont_var, churn) %>%
ggplot(aes(x = churn, fill = churn)) +
geom_bar(alpha = 0.8) +
scale_fill_viridis_d() +
guides(fill = FALSE)
x_p <- churn_df %>%
select(cont_var, churn) %>%
gather(variable, value, -churn) %>%
ggplot(aes(x = value, y = churn, color = churn, fill = churn)) +
facet_wrap( ~ variable, scale = "free", ncol = 3) +
scale_color_viridis_d() +
scale_fill_viridis_d() +
geom_density_ridges(alpha = 0.8) +
guides(fill = FALSE, color = FALSE)
plot_grid(y_p, x_p, rel_widths = c(1,3))
고객이탈 유무를 기준으로 연속형 변수에 대한 기술통계를 skim()
함수로 산출해 본다.
churn_df %>%
select(cont_var, churn) %>%
group_by(churn) %>%
skim
Numeric Variables
# A tibble: 3 x 13
var type missing complete n mean sd min `25% quantile`
<chr> <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 monthly… nume… 0 7032 7032 64.8 30.1 18.2 35.6
2 tenure inte… 0 7032 7032 32.4 24.5 1.00 9.00
3 total_c… nume… 0 7032 7032 2283 2267 18.8 401
# ... with 4 more variables: median <dbl>, `75% quantile` <dbl>,
# max <dbl>, hist <chr>
Factor Variables
# A tibble: 1 x 7
var type complete missing n n_unique stat
<chr> <chr> <dbl> <dbl> <dbl> <dbl> <chr>
1 churn factor 7032 0 7032 2.00 No: 5163 Yes: 1869 NA: 0
멸균 위생작업이 완료된 데이터프레임에 대해서 범주형 변수와 연속형 변수로 나누어서 시각화와 모형개발을 위해 적절한 형태로 정제작업을 수행한다. 그리고 나서 이를 저장하여 다음 모형화 작업을 수행하도록 준비한다.
# 시각화/모형 데이터프레임 정제작업 ------
churn_m_df <- churn_df %>%
mutate(multiple_lines = ifelse(str_detect(multiple_lines, "No"), "No", "Yes"),
internet_service = ifelse(str_detect(internet_service, "No"), "No", "Yes"),
online_security = ifelse(str_detect(online_security, "No"), "No", "Yes"),
online_backup = ifelse(str_detect(online_backup, "No"), "No", "Yes"),
device_protection = ifelse(str_detect(device_protection, "No"), "No", "Yes"),
tech_support = ifelse(str_detect(tech_support, "No"), "No", "Yes"),
streaming_tv = ifelse(str_detect(streaming_tv, "No"), "No", "Yes"),
streaming_movies = ifelse(str_detect(streaming_movies, "No"), "No", "Yes"))
# 시각화/모형 데이터프레임 저장 ------
# churn_m_df %>% write_rds("data/churn_m_df.rds")