聲明:本篇是基於張丹《R的極客思想》書本中的內容,但張丹在代碼中並未添加過多註釋,本人最近在研究推薦系統,並將張丹的代碼做了一些改動和詳細註釋貼上來供大家學習交流
#user-based 協同過濾推薦,3個近鄰,2個推薦結果
#1.構建數據模型
FileDataModel<-function(file_name){
user=unique(file_name$buyer_member_name)
item=unique(file_name$store_name)
uidx=match(file_name$buyer_member_name,user) #向量形式
iidx=match(file_name$store_name,item)
#user-item評分矩陣,初始化值=0,可根據實際情況更改爲其他值
M=matrix(0,length(user),length(item))
i=cbind(uidx,iidx,file_name$overall_rating)
for (n in 1:nrow(i)) {
M[i[n,][1],i[n,][2]]=i[n,][3]
}
#矩陣行列命名
dimnames(M)[[2]]=item
dimnames(M)[[1]]=user
return(M)
}
#2.改進的歐式距離相似度算法
EuclideanDistanceSimilarity<-function(M){
row=nrow(M) #用戶數
s=matrix(0,row,row) #初始化user-user相似度矩陣
for (z1 in 1:row){
for (z2 in 1:row){
#只計算下三角矩陣元素的值,不包含對角線元素
if (z1<z2) {
num=intersect(which(M[z2,] != 0),which(M[z1,] != 0)) #可進行計算的列-即都有正常的偏好評分
sum=0
for (z3 in num){
sum=sum+(M[z1,][z3]-M[z2,][z3])^2
}
#下三角矩陣賦值
s[z2,z1]=round(length(num)/(1+sqrt(sum)),3)
#對算法閾值進行限制
if (s[z2,z1]>1) s[z2,z1]=1
#if (s[z2,z1]<-1) s[z2,z1]=-1
}
}
}
ts=t(s)
w=which(upper.tri(ts)) #默認不包含對角元素
s[w]=ts[w]
s
}
#3.用戶最近鄰算法,選出最近的前n個用戶,傳入客戶相似度矩陣+近鄰個數
NearestNUserNeighborhood<-function(s,n){
row=nrow(s)
neighbor=matrix(0,row,n)
#user角度
for (z1 in 1:row){
for (z2 in 1:n){
m=which.max(s[,z1])
#neighbor只存儲索引
neighbor[z1,z2]=m
s[,z1][m]=0
}
}
neighbor
}
#4.基於部分用戶的推薦算法,對store_name進行推薦
#函數入參:buyer_member_name+recommender_num(推薦結果個數)+M(數據模型矩陣)+S(相似度矩陣)+N(近鄰矩陣)
M=FileDataModel(entropy_data_frame)
S=EuclideanDistanceSimilarity(M)
N=NearestNUserNeighborhood(S,3) #3個近鄰
#uid_char爲user name
UserBasedRecommender<-function(uid_char,n,M,S,N){
uid=which(rownames(M)==uid_char) #user name的行索引
row=ncol(N) #近鄰個數
col=ncol(M) #store_name個數
r=matrix(0,row,col)
N1=N[uid,] #N1-索引,length=近鄰個數
for (z1 in 1:length(N1)){
num=intersect(which(M[uid,] == 0),which(M[N1[z1],] != 0)) #可計算的列,uid在評分矩陣上沒有評分,而近鄰有評分
for (z2 in num){
r[z1,z2]=M[N1[z1],z2] * S[uid,N1[z1]] #評分乘相似度
}
}
#過濾推薦矩陣結果
sum=colSums(r) #向量形式,每個store_name一個值
s2=matrix(0,2,col)
for (z1 in 1:length(N1)){
num=intersect(which(colSums(r) != 0),which(M[N1[z1],] != 0)) #sum(評分乘相似度)不等於0,且近鄰用戶在store_name上的評分不等於0
for (z2 in num){
s2[1,][z2]=s2[1,][z2]+S[uid,N1[z1]]
s2[2,][z2]=s2[2,][z2]+1 #計數
}
}
s2[,which(s2[2,]==1)]=10000 #若在某個推薦上只有一個近鄰有評分時,讓s2矩陣對應的相似度無窮大
s2=s2[-2,]
r2=matrix(0,n,2)
rr=sum/s2 #sum(評分乘相似度)/sum(相似度)
item=dimnames(M)[[2]]
for (z1 in 1:n){
w=which.max(rr)
#0.5是可變化的值,即推薦閾值
if(rr[w]>0.5){
r2[z1,1]=item[which.max(rr)]
r2[z1,2]=as.double(rr[w])
rr[w]=0
}
}
r2
}