数据挖掘(1)

R的近邻分析:数据预测

周世荣

2024/04/01

Overview

\(~\)

KNN 算法背景介绍

KNN 算法的背景

Fix, Evelyn(1904-1965)

Joseph L. Hodges (1922-2000)

\(~\)

\(~\)

KNN算法的基本原理

KNN 基本原理

亮度 饱和度 颜色类型
40 20 红色
50 50 蓝色
60 90 蓝色
10 25 红色
70 70 蓝色
20 10 红色
25 50 蓝色
40 35 ?
mycolors <- colorRampPalette(c("blue", "Red"))(1e3);#mycolors[1:3]
barplot(rep(1, times = 1e3), col = mycolors, border = mycolors, axes = FALSE)
# to rgb
#mycolorsRgb <- farver::decode_colour(mycolors, to = "rgb"); dim(mycolorsRgb); head(mycolorsRgb, 3)
# to hsl
#mycolorsHsl <- farver::decode_colour(mycolors, to = "hsl"); dim(mycolorsHsl); head(mycolorsHsl, 3)

vc1 = c(40, 50, 60, 10, 70, 20, 25, 40)
vc2 = c(20, 50, 90, 25, 70, 10, 50, 35)
typ = c('红色','蓝色','蓝色','红色','蓝色','红色','蓝色','黑色')
cols = sapply(typ, function(x){
  switch(x,
  '红色' = 'red',
  '蓝色' = 'blue',
  '黑色' = 'black'
  )
}) 
plot(vc1, vc2, type='p', pch=16, cex=4, col=typ, 
     xlim = c(0,80),
     ylim = c(-10, 100)
       )

KNN算法的基本原理

library(plotrix)
op = par(mar=c(4,4,3,1))
plot(vc1, vc2, type='p', pch=16, cex=4, col=cols, 
     xlim = c(0,80),
     ylim = c(-10, 100),
     main = '红蓝颜色的饱和度和亮度分布图'
       )
draw.circle(40, 35, seq(10, 30, by=5), 
            border = 'gray50')
par(op)

\(~\)

KNN算法的距离度量

距离度量

library(plotrix)
op = par(mar=c(4,4,3,1))
plot(vc1, vc2, type='p', pch=16, cex=4, col=cols, 
     xlim = c(0,80),
     ylim = c(-10, 100),
     main = '红蓝颜色的饱和度和亮度分布图'
       )
draw.circle(40, 35, seq(10, 30, by=5), 
            border = 'gray50')
segments(40, 35, 50, 35, col='red', lwd=4)
segments(50, 35, 50, 50, col='red', lwd=4)
segments(40, 35, 50, 50, col='red', lty=2, lwd=4)
text(40, 32, 'A (40, 35)', col='blue', font=2)
text(50, 53, 'B (50, 50)', col='blue', font=2)
text(50, 32, 'C (50, 35)', col='blue', font=2)       
par(op)

# 计算AC 和 BC 的距离
lAC = 50 - 40; lBC = 50 - 35

# 计算 AB
AB = sqrt(lAC^2 + lBC^2); AB
## [1] 18.02776

距离度量

# 1. 构建计算欧式距离的函数
f.Euclid = function(a, b=c(40, 35)){
  # b默认为本例中目标样本的坐标
  l1 = b[1] - a[1]
  l2 = b[2] - a[2]
  d = sqrt(l1^2 + l2^2)
  d
}

# 2. 将样本依次循环应用到该函数
## for method
vc1s = c(40, 50, 60, 10, 70, 20, 25)
vc2s = c(20, 50, 90, 25, 70, 10, 50)
typ = c('红色','蓝色','蓝色','红色','蓝色','红色','蓝色','黑色')
res = numeric(length(vc1s)); 
for(i in 1:length(vc1s)){
  ai = c(vc1s[i], vc2s[i])
  res[i] = f.Euclid(ai)
}
res
## [1] 15.00000 18.02776 58.52350 31.62278 46.09772 32.01562 21.21320
## apply method
vcs = cbind(vc1s, vc2s)
res = apply(vcs, 1, function(x) f.Euclid(x))
res
## [1] 15.00000 18.02776 58.52350 31.62278 46.09772 32.01562 21.21320
# 3. 把计算结果按从小到达的顺序排列
sort(res) # 给出排序
## [1] 15.00000 18.02776 21.21320 31.62278 32.01562 46.09772 58.52350
order(res) # 给出索引
## [1] 1 2 7 4 6 5 3
# 4. 整合结果
knitr::kable(
  data.frame(vc1s, vc2s, 
           `颜色种类` = typ[-length(typ)],
           `欧式距离` = res)[order(res),],
  align='c',
  row.names = FALSE,
  caption = "红蓝颜色样本距离目标样本点的欧式距离",
  format="html"
)
红蓝颜色样本距离目标样本点的欧式距离
vc1s vc2s 颜色种类 欧式距离
40 20 红色 15.00000
50 50 蓝色 18.02776
25 50 蓝色 21.21320
10 25 红色 31.62278
20 10 红色 32.01562
70 70 蓝色 46.09772
60 90 蓝色 58.52350
f.myknn = function(trainData, trainlabel, testPoint, k=1){
  ntr = nrow(trainData)
  res = numeric(ntr); 
  for(i in 1:ntr){
    ai = trainData[i,]
    res[i] = f.Euclid(ai, testPoint)
}
res = table((trainlabel[order(res)])[1:k])
# 判断结果是不是只包含一类
ifelse(length(res)==1, names(res),
       names(res)[max(res)])
}
f.myknn(trainData =vcs, 
        trainlabel = typ[-length(typ)], 
        testPoint = c(40, 35),
        k = 3
        )
## [1] "蓝色"

\(~\)

KNN算法的k值选择

K值选择

\(~\)

KNN算法的实施

KNN算法的实施

set.seed(12) ## 设置随机种子,保证可重复

# 设置两组模拟变量
simulatedDataGroup1 <- cbind(X = rnorm(100), Y = rnorm(100)) 
simulatedDataGroup2 <- cbind(X = rnorm(100, mean = 2), Y = rnorm(100, mean = 2)) 

#组合数据
combinedData <- matrix(nrow = 200, ncol = 2)                                                                   
combinedData[1:100, ] <- simulatedDataGroup1
combinedData[101:200, ] <- simulatedDataGroup2 

#设置标签
groupMembership <- c(rep("red", 100), rep("blue", 100)) 

plot(combinedData, col = groupMembership, main = "原始数据")

ns1 = nrow(combinedData) 

labelPrect = NULL
for (i in 1:ns1) {
  trainData = combinedData[-i,]
  trainlabel = groupMembership[-i]
  testPoint = combinedData[i,]
  labelPrect = c(labelPrect, f.myknn(trainData, trainlabel, testPoint,k=3))
}

# 判断预测和真实值之间的相似性
tp = table(groupMembership, labelPrect);tp
##                labelPrect
## groupMembership blue red
##            blue   81  19
##            red     2  98
tpr = (1 - sum(diag(tp))/sum(tp))*100 # 计算判错率的百分比
tpr
## [1] 10.5
tprs = NULL
kv = c(1, 3, 5, 7, 9, 11, 13, 17, 19)
for(k in kv){
  ns1 = nrow(combinedData) 
  labelPrect = NULL
  for (i in 1:ns1) {
  trainData = combinedData[-i,]
  trainlabel = groupMembership[-i]
  testPoint = combinedData[i,]
  labelPrect = c(labelPrect, f.myknn(trainData, trainlabel, testPoint, k=k))
  }
  # 判断预测和真实值之间的相似性
  tp = table(groupMembership, labelPrect);#tp
  tprs = c(tprs, (1 - sum(diag(tp))/sum(tp))*100) #   计算判错率的百分比
}
tprs
## [1] 12.0000000 10.5000000  2.7027027  2.2222222  1.6129032  1.7241379  0.9345794
## [8]  1.0309278  1.0638298
plot(kv, tprs, type = 'o')
grid()

k-nearest neighbour classification for test set from training set. For each row of the test set, the k nearest (in Euclidean distance) training set vectors are found, and the classification is decided by majority vote , with ties broken at random. If there are ties for the kth nearest vector, all candidates are included in the vote.

library(class)

tprs1 = NULL
kv = c(1, 3, 5, 7, 9, 11, 13, 17, 19)
for(k in kv){
  ns1 = nrow(combinedData) 
  labelPrect = NULL
  for (i in 1:ns1) {
  trainData = combinedData[-i,]
  trainlabel = groupMembership[-i]
  testPoint = combinedData[i,]
  labelPrect = c(labelPrect, knn(trainData,  testPoint, trainlabel, k=k))
  }
  # 判断预测和真实值之间的相似性
  tp = table(groupMembership, labelPrect);#tp
  tprs1 = c(tprs1, (1 - sum(diag(tp))/sum(tp))*100) #   计算判错率的百分比
}
tprs1
## [1] 12.0  9.5  8.5  8.0  7.5  7.0  8.0  7.0  7.0
plot(kv, tprs1, type = 'o', ylim=c(0,12))
points(kv, tprs, col='red')
lines(kv, tprs, col='red')
legend('topright', c('f.myknn', 'knn'), 
       lty=1, col = c('red', 'black'),
       pch =1
       )
grid()

KNN算法的实施

op = par(mfrow=c(1,2),mar=c(4,6,4,4))
SampleId<-sample(x=1:200,size=50)
groupMembershipNu = ifelse(groupMembership=='red',0,1)
Data <- data.frame(combinedData,groupMembershipNu)
DataTest<-Data[SampleId,]   
DataTrain<-Data[-SampleId,]  
plot(Data[,1:2],pch=Data[,3]+1,cex=0.8,xlab="x1",ylab="x2",main="全部样本")
plot(DataTrain[,1:2],pch=DataTrain[,3]+1,cex=0.8,xlab="x1",ylab="x2",main="训练样本和测试样本")
points(DataTest[,1:2],pch=DataTest[,3]+16,col=2,cex=0.8)

par(op)
library("class")
errRatio<-vector()   
for(i in 1:30){    
 KnnFit<-knn(train=Data[,1:2],test=Data[,1:2],cl=as.factor(Data[,3]),k=i)
 CT<-table(Data[,3],KnnFit)  
 errRatio<-c(errRatio,(1-sum(diag(CT))/sum(CT))*100)     
}
plot(errRatio,type="l",xlab="近邻个数K",ylab="错判率(%)",main="近邻数K与错判率",ylim=c(0,12))

errRatio1<-vector()   
for(i in 1:30){
 KnnFit<-knn(train=DataTrain[,1:2],test=DataTest[,1:2],cl=as.factor(DataTrain[,3]),k=i) 
 CT<-table(DataTest[,3],KnnFit) 
 errRatio1<-c(errRatio1,(1-sum(diag(CT))/sum(CT))*100)    
}
lines(1:30,errRatio1,lty=2,col=2)

set.seed(12345)
errRatio2<-vector()   
for(i in 1:30){   
 KnnFit<-knn.cv(train=Data[,1:2],cl=as.factor(Data[,3]),k=i) 
 CT<-table(Data[,3],KnnFit)  
 errRatio2<-c(errRatio2,(1-sum(diag(CT))/sum(CT))*100)     
}
lines(1:30,errRatio2,col=2)
legend('bottomright', c('全部','置旁法','留一法'),
       col = c('black', 'red', 'red'), 
       lty = c(1, 2, 1)
       )

KNN算法的实施

set.seed(12345)
x1<-runif(60,-1,1)  
x2<-runif(60,-1,1)  
y<-runif(60,10,20)   
Data<-data.frame(Fx1=x1,Fx2=x2,Fy=y)
SampleId<-sample(x=1:60,size=18)  
DataTest<-Data[SampleId,]  
DataTrain<-Data[-SampleId,]  
mseVector<-vector()    
for(i in 1:30){
 KnnFit<-knn(train=DataTrain[,1:2],test=DataTest[,1:2],cl=DataTrain[,3],k=i,prob=FALSE) 
 KnnFit<-as.double(as.vector(KnnFit))   
 mse<-sum((DataTest[,3]-KnnFit)^2)/length(DataTest[,3])   
 mseVector<-c(mseVector,mse)
}
plot(mseVector,type="l",xlab="近邻个数K",ylab="均方误差",main="近邻数K与均方误差",ylim=c(0,80))

KNN算法的实施


# 设置网格
XInt <- seq(-2.5, 4.5, by = 0.1) 
YInt <- seq(-3, 6, by = 0.1)

colMatrix5 <- matrix(nrow = length(YInt), ncol = length(XInt))

# 计算每个网格点的类别
for (j in 1:length(XInt)) { 
  for (i in 1:length(YInt)) {
    NN5 <- knn(combinedData, 
               c(XInt[j], YInt[i]),
               groupMembership, 
               k = 5)
    colMatrix5[i, j] <- as.character(NN5)
  }
}

# 设置网格点的位置
rm(NN5, i, j)

Xwhich <- rep(NA, length(colMatrix5))

for (i in ((1:length(XInt))-1)) {
  Xwhich[(1+i*length(YInt)):(length(YInt)+i*length(YInt))] <- (i+1)
}

pointLocations <- data.frame(X = XInt[Xwhich], Y = rep(YInt, length(XInt)))

# 设置点的颜色
coloursPoints5 <- c(colMatrix5)
plot(pointLocations, col = coloursPoints5, cex = 0.1, main = "基于5NN的分类边界")
points(combinedData[,1], combinedData[,2], 
       col=groupMembership,pch=1)

colMatrix1 <- matrix(nrow = length(YInt), ncol = length(XInt))

for (j in 1:length(XInt)) { 
  for (i in 1:length(YInt)) {
    NN1 <- knn(combinedData, 
               c(XInt[j], YInt[i]), 
               groupMembership, 
               k = 1)
    colMatrix1[i, j] <- as.character(NN1)
  }
}

rm(NN1, i, j)

coloursPoints1 <- c(colMatrix1)

plot(pointLocations, col = coloursPoints1, cex = 0.3, main = "基于 1NN 的分类边界")
points(combinedData[,1], combinedData[,2], 
       col=groupMembership,pch=1)

\(~\)

KNN算法的适用性和特征选择

KNN算法的适用性

KNN算法的特征选择


  1. Fix Evelyn and Joseph L. Hodges. “Discriminatory Analysis - Nonparametric Discrimination: Consistency Properties (1961).” International Statistical Review 57 (1989): 238.↩︎

  2. Thomas Cover and Hart Peter. “Nearest neighbor pattern classification.” IEEE transactions on information theory 13.1 (1967): 21-27.↩︎