通过将《机器学习与R语言》一书中的代码tidyverse化,来学习这本书。
书中第一个例子是利用kNN算法来诊断乳腺癌。
首先载入需要用到的包:
library(tidyverse) # 清洗数据
library(here) # 设置数据文件路径
library(knitr) # 呈现更好看的表格
library(kableExtra) # 同上
library(class) # 使用包中的knn()函数
library(gmodels) # 使用包中的CrossTable()函数
然后导入数据并清洗:
wbcd <- read_csv(here('data', '01-wisc_bc_data.csv')) %>%
select(-id) %>%
mutate(diagnosis = factor(diagnosis, levels = c('B', 'M'),
labels = c('Benign', 'Malignant'))) %>%
mutate_if(is.numeric, ~ (.x - min(.x)) / (max(.x) - min(.x)))
首先使用here函数找到数据文件的路径,然后使用read_csv函数将其读入R中;随后通过select函数将id变量去掉;然后利用mutate函数将diagnosis变量改为因子型;最后利用mutate_if函数,将所有数值型的变量进行min-max标准化,这里用到了公式化的匿名函数,可以使代码更为简练。此时的数据是这样的:
wbcd %>% head()
## # A tibble: 6 x 31
## diagnosis radius_mean texture_mean perimeter_mean area_mean
## <fct> <dbl> <dbl> <dbl> <dbl>
## 1 Malignant 0.521 0.0227 0.546 0.364
## 2 Malignant 0.643 0.273 0.616 0.502
## 3 Malignant 0.601 0.390 0.596 0.449
## 4 Malignant 0.210 0.361 0.234 0.103
## 5 Malignant 0.630 0.157 0.631 0.489
## 6 Malignant 0.259 0.203 0.268 0.142
## # ... with 26 more variables: smoothness_mean <dbl>,
## # compactness_mean <dbl>, concavity_mean <dbl>, `concave
## # points_mean` <dbl>, symmetry_mean <dbl>, fractal_dimension_mean <dbl>,
## # radius_se <dbl>, texture_se <dbl>, perimeter_se <dbl>, area_se <dbl>,
## # smoothness_se <dbl>, compactness_se <dbl>, concavity_se <dbl>,
## # `concave points_se` <dbl>, symmetry_se <dbl>,
## # fractal_dimension_se <dbl>, radius_worst <dbl>, texture_worst <dbl>,
## # perimeter_worst <dbl>, area_worst <dbl>, smoothness_worst <dbl>,
## # compactness_worst <dbl>, concavity_worst <dbl>, `concave
## # points_worst` <dbl>, symmetry_worst <dbl>,
## # fractal_dimension_worst <dbl>
书中还提到了Z分数标准化,因为有现成的scale函数,所以代码会稍微简单:
wbcd <- read_csv(here('data', '01-wisc_bc_data.csv')) %>%
select(-id) %>%
mutate(diagnosis = factor(diagnosis, levels = c('B', 'M'),
labels = c('Benign', 'Malignant'))) %>%
mutate_if(is.numeric, scale)
下一步是创建训练数据集和测试数据集。首先先设定一个随机种子,保证结果可以复现,然后利用sample_n函数从完整数据中随机选择469行作为训练数据集,并利用setdiff函数筛选出训练数据集的补集作为测试数据集;最后利用pull函数把标签提取出来:
set.seed(0412)
wbcd_train <- wbcd %>% sample_n(469)
wbcd_test <- wbcd %>% setdiff(wbcd_train)
wbcd_train_labels <- wbcd_train %>% pull(1)
wbcd_test_labels <- wbcd_test %>% pull(1)
数据已经整理好,可以建模了,但是在书中没有看到将数据集中的标签变量去掉的过程,所以在这里的模型中,我把两个数据集的标签变量都去掉了:
wbcd_test_pred <- knn(train = wbcd_train[, -1], test = wbcd_test[, -1],
cl = wbcd_train_labels, k = 21)
看一下模型的性能:
CrossTable(wbcd_test_labels, wbcd_test_pred, prop.chisq = FALSE)
##
##
## Cell Contents
## |-------------------------|
## | N |
## | N / Row Total |
## | N / Col Total |
## | N / Table Total |
## |-------------------------|
##
##
## Total Observations in Table: 100
##
##
## | wbcd_test_pred
## wbcd_test_labels | Benign | Malignant | Row Total |
## -----------------|-----------|-----------|-----------|
## Benign | 68 | 0 | 68 |
## | 1.000 | 0.000 | 0.680 |
## | 0.986 | 0.000 | |
## | 0.680 | 0.000 | |
## -----------------|-----------|-----------|-----------|
## Malignant | 1 | 31 | 32 |
## | 0.031 | 0.969 | 0.320 |
## | 0.014 | 1.000 | |
## | 0.010 | 0.310 | |
## -----------------|-----------|-----------|-----------|
## Column Total | 69 | 31 | 100 |
## | 0.690 | 0.310 | |
## -----------------|-----------|-----------|-----------|
##
##
跟书中的结果不一样,但也不错。
最后,书中还使用不同的k值对模型进行了评估,但没有给出相应的代码,我这里补充了一下:
k <- map(1:30, ~ knn(train = wbcd_train[, -1], test = wbcd_test[, -1],
cl = wbcd_train_labels, k = .x)) %>%
enframe(name = 'k', value = 'prediction') %>%
unnest() %>%
mutate(label = rep(wbcd_test_labels, 30),
FN = prediction == 'Malignant' & label == 'Benign',
FP = prediction == 'Benign' & label == 'Malignant') %>%
group_by(k) %>%
summarise(FN = sum(FN),
FP = sum(FP),
total = FN + FP)
首先利用map函数将1到30分别映射到模型的k参数上,此时得到了会是一个长度为30的列表;随后利用enframe函数将列表变为行数为30的数据框,这时value变量下的每一个元素都包含100个字符;随后利用unnest将value变量中的字符解放出来,使数据框的行数变为3000;剩余的代码就比较简单,不多描述。
这时的数据是这样的:
print(k, n = nrow(k))
## # A tibble: 30 x 4
## k FN FP total
## <int> <int> <int> <int>
## 1 1 2 1 3
## 2 2 4 0 4
## 3 3 2 0 2
## 4 4 4 0 4
## 5 5 2 0 2
## 6 6 2 0 2
## 7 7 3 0 3
## 8 8 3 0 3
## 9 9 2 0 2
## 10 10 3 1 4
## 11 11 0 0 0
## 12 12 0 0 0
## 13 13 0 0 0
## 14 14 0 0 0
## 15 15 0 0 0
## 16 16 0 0 0
## 17 17 0 0 0
## 18 18 0 1 1
## 19 19 0 1 1
## 20 20 0 1 1
## 21 21 0 1 1
## 22 22 1 1 2
## 23 23 1 1 2
## 24 24 1 1 2
## 25 25 1 1 2
## 26 26 1 1 2
## 27 27 1 0 1
## 28 28 1 1 2
## 29 29 1 1 2
## 30 30 1 0 1
可以看到,k值从11到17时的结果都很“完美”。