《机器学习与R语言》学习笔记01:kNN
通过将《机器学习与R语言》一书中的代码tidyverse
化,来学习这本书。
书中第一个例子是利用kNN算法来诊断乳腺癌。
首先载入需要用到的包:
library(tidyverse) # 清洗数据
library(here) # 设置数据文件路径
library(knitr) # 呈现更好看的表格
library(kableExtra) # 同上
library(class) # 使用包中的knn()函数
library(gmodels) # 使用包中的CrossTable()函数
然后导入数据并清洗:
wbcd <- read_csv(here('content', 'post', '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() %>%
kable() %>%
kable_styling(bootstrap_options = "striped", font_size = 12) %>%
scroll_box(width = "100%")
diagnosis | radius_mean | texture_mean | perimeter_mean | area_mean | smoothness_mean | compactness_mean | concavity_mean | concave points_mean | symmetry_mean | fractal_dimension_mean | radius_se | texture_se | perimeter_se | area_se | smoothness_se | compactness_se | concavity_se | concave points_se | symmetry_se | fractal_dimension_se | radius_worst | texture_worst | perimeter_worst | area_worst | smoothness_worst | compactness_worst | concavity_worst | concave points_worst | symmetry_worst | fractal_dimension_worst |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Malignant | 0.5210374 | 0.0226581 | 0.5459885 | 0.3637328 | 0.5937528 | 0.7920373 | 0.7031396 | 0.7311133 | 0.6863636 | 0.6055181 | 0.3561470 | 0.1204694 | 0.3690336 | 0.2738113 | 0.1592956 | 0.3513984 | 0.1356818 | 0.3006251 | 0.3116452 | 0.1830424 | 0.6207755 | 0.1415245 | 0.6683102 | 0.4506980 | 0.6011358 | 0.6192916 | 0.5686102 | 0.9120275 | 0.5984624 | 0.4188640 |
Malignant | 0.6431445 | 0.2725736 | 0.6157833 | 0.5015907 | 0.2898799 | 0.1817680 | 0.2036082 | 0.3487575 | 0.3797980 | 0.1413227 | 0.1564367 | 0.0825893 | 0.1244405 | 0.1256598 | 0.1193867 | 0.0813230 | 0.0469697 | 0.2538360 | 0.0845388 | 0.0911101 | 0.6069015 | 0.3035714 | 0.5398177 | 0.4352143 | 0.3475533 | 0.1545634 | 0.1929712 | 0.6391753 | 0.2335896 | 0.2228781 |
Malignant | 0.6014956 | 0.3902604 | 0.5957432 | 0.4494168 | 0.5143089 | 0.4310165 | 0.4625117 | 0.6356859 | 0.5095960 | 0.2112468 | 0.2296216 | 0.0943025 | 0.1803704 | 0.1629218 | 0.1508312 | 0.2839547 | 0.0967677 | 0.3898466 | 0.2056903 | 0.1270055 | 0.5563856 | 0.3600746 | 0.5084417 | 0.3745085 | 0.4835898 | 0.3853751 | 0.3597444 | 0.8350515 | 0.4037059 | 0.2134330 |
Malignant | 0.2100904 | 0.3608387 | 0.2335015 | 0.1029056 | 0.8113208 | 0.8113613 | 0.5656045 | 0.5228628 | 0.7762626 | 1.0000000 | 0.1390911 | 0.1758752 | 0.1266550 | 0.0381548 | 0.2514532 | 0.5432151 | 0.1429545 | 0.3536655 | 0.7281477 | 0.2872048 | 0.2483102 | 0.3859275 | 0.2413467 | 0.0940081 | 0.9154725 | 0.8140117 | 0.5486422 | 0.8848797 | 1.0000000 | 0.7737111 |
Malignant | 0.6298926 | 0.1565776 | 0.6309861 | 0.4892895 | 0.4303512 | 0.3478928 | 0.4639175 | 0.5183897 | 0.3782828 | 0.1868155 | 0.2338222 | 0.0930649 | 0.2205626 | 0.1636876 | 0.3323588 | 0.1679184 | 0.1436364 | 0.3570752 | 0.1361794 | 0.1457996 | 0.5197439 | 0.1239339 | 0.5069476 | 0.3415749 | 0.4373638 | 0.1724151 | 0.3194888 | 0.5584192 | 0.1575005 | 0.1425948 |
Malignant | 0.2588386 | 0.2025702 | 0.2679842 | 0.1415058 | 0.6786133 | 0.4619962 | 0.3697282 | 0.4020378 | 0.5186869 | 0.5511794 | 0.0807532 | 0.1171322 | 0.0687933 | 0.0380801 | 0.1970629 | 0.2343107 | 0.0927273 | 0.2153817 | 0.1937299 | 0.1446596 | 0.2682319 | 0.3126333 | 0.2639076 | 0.1367479 | 0.7127386 | 0.4827837 | 0.4277157 | 0.5982818 | 0.4770353 | 0.4549390 |
书中还提到了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;剩余的代码就比较简单,不多描述。
这时的数据是这样的:
k %>% kable() %>%
kable_styling(bootstrap_options = "striped", font_size = 12) %>%
scroll_box(width = "100%")
k | FN | FP | total |
---|---|---|---|
1 | 2 | 1 | 3 |
2 | 4 | 0 | 4 |
3 | 2 | 0 | 2 |
4 | 4 | 0 | 4 |
5 | 2 | 0 | 2 |
6 | 2 | 0 | 2 |
7 | 3 | 0 | 3 |
8 | 3 | 0 | 3 |
9 | 2 | 0 | 2 |
10 | 3 | 1 | 4 |
11 | 0 | 0 | 0 |
12 | 0 | 0 | 0 |
13 | 0 | 0 | 0 |
14 | 0 | 0 | 0 |
15 | 0 | 0 | 0 |
16 | 0 | 0 | 0 |
17 | 0 | 0 | 0 |
18 | 0 | 1 | 1 |
19 | 0 | 1 | 1 |
20 | 0 | 1 | 1 |
21 | 0 | 1 | 1 |
22 | 1 | 1 | 2 |
23 | 1 | 1 | 2 |
24 | 1 | 1 | 2 |
25 | 1 | 1 | 2 |
26 | 1 | 1 | 2 |
27 | 1 | 0 | 1 |
28 | 1 | 1 | 2 |
29 | 1 | 1 | 2 |
30 | 1 | 0 | 1 |
可以看到,k值从11到17时的结果都很“完美”。