데이터 시각화하는 과정은 엄청 노동집약적인 과정이다. 혹자는 고용창출시스템이라고도 하지만, 다른 한편으로는 데이터 분석을 시각화를 통해 구현하는 입장에서는 저녁이 없는 삶이기도 하다. “저녁이 있는 삶”을 위해서 기계학습을 도입하여 데이터에서 유의미한 정보를 추출하는 기계적인 과정을 기계에 맡기고 추출된 주요 변수를 바탕으로 시각화 후속작업을 수행하게 되는 것을 추진해보자.
Wine Quality 데이터를 다운로드 받아, 적포도주, 백포도주 변수에 수준(level)을 정의해서 하나의 데이터프레임으로 생성시킨다.
# 0. 환경설정 ------
library(tidyverse)
library(caret)
library(janitor)
library(ggridges)
library(ggthemes)
library(cowplot)
library(corrplot)
library(corrr)
library(plotly)
library(crosstalk)
# 1. 데이터 ------
red_dat <- read_csv("data/wineQualityReds.csv") %>%
mutate(wine="red")
white_dat <- read_csv("data/wineQualityWhites.csv") %>%
mutate(wine="white")
wine_df <- bind_rows(red_dat, white_dat) %>%
clean_names() %>%
select(-x1) %>%
mutate(wine=factor(wine, levels = c("red", "white"))) %>%
filter(complete.cases(.))
caret
팩키지로 백포도주와 적포도주 판별에 주요한 요인을 미치는 변수를 random forest
를 사용해서 뽑아본다. 이를 위해서 훈련, 시험데이터로 나누고 변수에 대한 전처리 작업을 수행하고 교차 타당도(repeatedcv
)를 5개로 나눠 3회 실시할 때 random forest
를 모형 기본 아키텍처로 설정한다.
전반적인 모형 성능을 wine_test
표본으로 성능을 평가한다.
# 2. 기계학습 ------
## 2.1. 훈련/시험 데이터 분할 ------
idx <- createDataPartition(wine_df$wine,
p = 0.7,
list = FALSE,
times = 1)
wine_train <- wine_df[ idx,]
wine_test <- wine_df[-idx,]
## 2.2. 모형적합 ------
fit_ctrl <- trainControl(method = "repeatedcv", number = 5, repeats = 3)
wine_rf <- train(wine ~ .,
data = wine_train,
method = "rf",
preProcess = c("scale", "center"),
trControl = fit_ctrl,
verbose = FALSE)
## 2.3. 모형성능평가 ------
test_predict <- predict(wine_rf, wine_test)
confusionMatrix(test_predict, wine_test$wine)
Confusion Matrix and Statistics
Reference
Prediction red white
red 472 3
white 7 1466
Accuracy : 0.9949
95% CI : (0.9906, 0.9975)
No Information Rate : 0.7541
P-Value [Acc > NIR] : <2e-16
Kappa : 0.9861
Mcnemar's Test P-Value : 0.3428
Sensitivity : 0.9854
Specificity : 0.9980
Pos Pred Value : 0.9937
Neg Pred Value : 0.9952
Prevalence : 0.2459
Detection Rate : 0.2423
Detection Prevalence : 0.2438
Balanced Accuracy : 0.9917
'Positive' Class : red
varImp()
함수로 random forest
모형에 백포도주와 적포도주를 분류하는데 중요한 역할을 수행한 변수를 추출하여 시각화한다. 변수가 많고 관측점이 많은 경우 시각화에 시간이 많이 걸리고 속도가 늦은 단점이 많아 상위 중요 변수 몇개만 추출한다.
# 3. 모형 설명 -----
## 3.1. 중요변수 추출 -----
wine_rf_imp <- varImp(wine_rf, scale = TRUE)
(top_three_variable_v <- wine_rf_imp$importance %>%
as.data.frame() %>%
rownames_to_column(var="variable") %>%
top_n(3, Overall) %>%
pull(variable))
[1] "volatile_acidity" "chlorides" "total_sulfur_dioxide"
## 3.2. 중요변수 시각화 -----
wine_rf_imp$importance %>%
as.data.frame() %>%
rownames_to_column(var="variable") %>%
ggplot(aes(x = reorder(variable, Overall), y = Overall)) +
geom_bar(stat = "identity", fill = "#1F77B4", alpha = 0.8) +
coord_flip() +
labs(y="중요도", x="") +
theme_minimal(base_family="NanumGothic")
기계학습을 통해서 volatile_acidity, chlorides, total_sulfur_dioxide 변수가 다른 변수에 비해서 포도주 분류에 중요한 역할을 수행하는 것을 확인했다. 이를 염두에 두고 시각화를 진행해 보자.
포도주 분류가 목적이기 때문에 예측변수와 예측에 동원되는 설명변수를 시각화한다.
# 2. 탐색적 데이터 분석 ------
## 2.1. 정적 시각화 -----
y_p <- wine_df %>%
ggplot(aes(x = wine, fill = wine)) +
geom_bar(alpha = 0.8) +
scale_fill_manual(values = c("red", "gray")) +
guides(fill = FALSE)
x_p <- wine_df %>%
gather(variable, value, fixed_acidity:quality) %>%
ggplot(aes(x = value, y = wine, color = wine, fill = wine)) +
facet_wrap( ~ variable, scale = "free", ncol = 3) +
scale_color_manual(values = c("red", "gray")) +
scale_fill_manual(values = c("red", "gray")) +
geom_density_ridges(alpha = 0.8) +
guides(fill = FALSE, color = FALSE)
plot_grid(y_p, x_p, rel_widths = c(1,3))
crosstalk
를 통해 예측변수와 설명변수를 연결하고 이를 plotly
팩키지로 인터랙티브 시각화한다.
## 2.2. 인터랙티브 시각화 -----
# https://github.com/clauswilke/ggridges/issues/12
wine_lng_df <- wine_df %>%
gather(variable, value, fixed_acidity:quality)
wine_lng_sd <- SharedData$new(wine_lng_df)
wine_y_g <- wine_lng_sd %>%
ggplot(aes(x = wine, fill = wine)) +
geom_bar(alpha = 0.8) +
scale_fill_manual(values = c("red", "gray")) +
guides(fill = FALSE)
wine_x_g <- wine_lng_df %>%
ggplot(aes(x = value, group=wine, color = wine, fill = wine)) +
geom_density(alpha = 0.8) +
facet_wrap(~variable, scales="free") +
scale_color_manual(values = c("red", "gray")) +
scale_fill_manual(values = c("red", "gray")) +
guides(fill = FALSE, color = FALSE)
subplot(wine_y_g, wine_x_g, nrows=1, widths=c(0.2, 0.4)) %>% highlight("plotly_click") %>% hide_legend()
기계학습 모형으로 추출한 변수를 대상으로 교차분석을 수행한다. 앞선 교차분석이 예측변수에 초점을 두었다면, 여기서는 설명변수가 사이의 교차분석에 관심을 둔다.
## 2.3. 교차분석 -----
### 중요변수만 추출한 데이터프레임
# top_three_variable_v <- c("volatile_acidity", "chlorides", "total_sulfur_dioxide")
wine_key_df <- wine_df %>%
select(top_three_variable_v, wine)
### 정적 교차그래프
GGally::ggpairs(wine_key_df, aes(color=wine, alpha=0.7))
### 인터랙티브 교차그래프
wine_sd <- wine_key_df %>% sample_frac(0.1) %>% SharedData$new(.)
wine_xy_g <- GGally::ggpairs(wine_sd, aes(color=wine, alpha=0.7))
highlight(ggplotly(wine_xy_g), on = "plotly_selected")
범주형 예측변수(포도주)를 제외한 나머지 설명변수 전체를 대상으로 피어슨 상관분석을 수행하고, corrplot
팩키지를 통해서 시각화한다. 문건웅님이 작성한 cor_mtest()
함수를 활용하여 유의성도 검정도 수행한다.
## 2.4. 상관분석 -----
### 2.4.1. 상관계수 시각화
wine_cor <- wine_df %>%
select(-wine) %>%
cor(method="pearson")
cor_mtest <- function(mat, conf.level = 0.95) {
mat <- as.matrix(mat)
n <- ncol(mat)
p.mat <- lowCI.mat <- uppCI.mat <- matrix(NA, n, n)
diag(p.mat) <- 0
diag(lowCI.mat) <- diag(uppCI.mat) <- 1
for (i in 1:(n - 1)) {
for (j in (i + 1):n) {
tmp <- cor.test(mat[, i], mat[, j], conf.level = conf.level)
p.mat[i, j] <- p.mat[j, i] <- tmp$p.value
lowCI.mat[i, j] <- lowCI.mat[j, i] <- tmp$conf.int[1]
uppCI.mat[i, j] <- uppCI.mat[j, i] <- tmp$conf.int[2]
}
}
return(list(p.mat, lowCI.mat, uppCI.mat))
}
wine_cor_test <- cor_mtest(wine_cor)
corrplot.mixed(wine_cor, lower="color", upper="number", p.mat=wine_cor_test[[1]], sig.level=0.05,
order = "hclust", addrect = 2)
corrr
팩키지를 사용해서 행렬 상관계수 자료형을 데이터프레임으로 변환시켜 이를 ggplot()
에 넣어 각 변수간 상관계수를 시각화하고, 이를 ggplotly()
함수에 넣어 인터랙티브하게 시각화시킨다.
### 2.4.2. 상관계수 분포
wine_cor_df <- wine_df %>%
select(-wine) %>%
correlate() %>%
shave(upper = TRUE) %>% # (3)
stretch(na.rm = TRUE)
wine_cor_g <- wine_cor_df %>%
ggplot(aes(x=x, y=y, size=abs(r), color=r, text=paste0("x: ", x,"\n",
"y: ", y, "\n",
"상관계수:", round(r, 2)))) +
geom_point() +
scale_colour_gradient2(low = "blue", mid = "white", high = "red") +
labs(x="", y="", color="상관계수") +
theme(axis.text.x = element_text(angle = 40, hjust = 1))
ggplotly(wine_cor_g, tooltip="text")
상관분석을 통해 유사성이 높은 변수를 시각적으로 확인을 한 후에 군집분석 알고리즘 동원해서 시각화하자.
### 2.4.3. 상관계수 군집분석
par(mfrow=c(2,2))
corrplot(wine_cor, order = "hclust", addrect = 3)
### 정적 그래프
wine_dist <- as.dist(1-wine_cor)
wine_hr <- hclust(wine_dist, method = "complete", members=NULL)
wine_cluster <- cutree(wine_hr, 3)
wine_var_color <- RColorBrewer::brewer.pal(3, "Set1")
plot(ape::as.phylo(wine_hr), edge.col=4, edge.width=2, show.node.label=TRUE, no.margin=TRUE)
plot(ape::as.phylo(wine_hr), type = "unrooted", cex = 0.9, no.margin = TRUE)
plot(ape::as.phylo(wine_hr), type = "fan", cex = 1.5, tip.color = wine_var_color[wine_cluster], label.offset = 0.1)
군집분석 결과물을 수목도(dendrogram)로 구현하는 것도 가능하다. 군집분석 객체를 as.dendrogram()
함수로 수목도 객체로 변환시키고 나서 dendro_data()
함수로 데이터프레임으로 변환이 가능하다. 이를 segment()
함수로 ggplot()
에 넣게 되면 정적 그래프로 시각화가 가능하고 그 다음은 동일하다.
### 인터랙티브 그래프
library(ggdendro)
ggdendrogram(wine_hr, rotate = TRUE, theme_dendro = TRUE)
wine_dend <- as.dendrogram(wine_hr)
wine_hr_data <- dendro_data(wine_hr, type="rectangle")
wine_dend_g <- ggplot() +
geom_segment(data=segment(wine_hr_data), aes(x=x, y=y, xend=xend, yend=yend)) +
geom_text(data=label(wine_hr_data), aes(x=x, y=y, label=label, hjust=0), size=3) +
coord_flip() +
scale_y_reverse(expand=c(0.2, 0))
ggplotly(wine_dend_g)
Shirin playgRound (July 20, 2018), “Explaining Black-Box Machine Learning Models - Code Part 1: tabular data + caret + iml”↩
Wine Quality: Modeling wine preferences by data mining from physicochemical properties.↩
STHDA, Beautiful dendrogram visualizations in R: 5+ must known methods - Unsupervised Machine Learning↩