User-based nearest neighbour implementation in R?

I am just starting to learn to use R and am not sure how to find the best packages yet. I am looking for a package that will allow me to calculate user-based nearest neighbours as an input for a recommendation.

My original data is approximately as below:

User    Item    Rating
Anna    Apple   2
Bob Apple   4
Carlos  Apple   1
Ethan   Apple   1
Gene    Apple   2
Anna    Banana  4
Dana    Banana  3
Ethan   Banana  5
Frank   Banana  5
Bob Carrot  1
Ethan   Carrot  2
Gene    Carrot  4

I have used the package reshape2 and dcast method to generate a different datastructure where users are rows, items are columns and the rating is in the value section (similar to an Excel pivot).

User    Apple   Banana  Carrot
Anna    2   4   n/a
Bob 4   n/a 1
Carlos  1   n/a n/a
Dana    n/a 3   n/a
Ethan   1   5   2
Frank   n/a 5   n/a
Gene    2   n/a 4

Is there any R package that will allow me to compute the similarity between users based on the user vectors? Preferably I would like to use the Jaccard coefficient as my similarity metric. I tried to google for R packages with user-based nearest neighbour implementations but came up empty.

Any help of alternative suggestions will be much appreciated.

Thanks!

• you are most of the way there, nearest neighbour is not too hard to implement yourself. As you observe, you just need to compute the distance between your candidate who doesn't have a score for your potential recommendation and the other users who have a score for that item and then select the smallest distance using whatever metric empirically works best. Testing will show whether that might be euclidean, manhattan, jaccard or something else. Aug 2 '15 at 12:09
• Thanks image_doctor, is there any resource you could direct me to where I can learn about implementing custom functions in R? I am familiar with the theory for Jaccard and I have some experience implementing this in Excel but I don't know how to "tell" R to loop through each pair of users to compute similarity or how to store the results? Aug 2 '15 at 13:03
• You'll probably find functional programming useful here, it will be faster than loops, here is a reference: adv-r.had.co.nz/Functionals.html Aug 2 '15 at 14:23
• I have the similar problem, in my dataset, V1 is my targeted variable, it has 8 classes, V2 to V5 are all binary variables. When I do KNN, I need to use Jaccard to measure my "distance", but there r no KNN package in R using Jaccard. Could you please tell me how to solve this problem? Thank you very much
– Ian
Nov 29 '15 at 2:49

Maybe the best answer for Your question is recommenderlab package for R available from CRAN. Here's the link to vignette. It has CF algorithms implemented and Jaccard coef. for 0/1 user feedback case.

Only similarity(distance) computation in base stats R package there is dist function. But it's limited to few distance measures. I know that when I search for distance measures in R package called vegan claimed to have many methods implemented. But I have never used it.

IMHO most basic recommender systems algorithms are really simple. It worth spending few minutes and implement it to get a better understanding and intuition how it works. Nevertheless, below my quick and dirty user-based kNN implementation for Your case. The code can be rewritten in better way (Adv.R is really good resource) - but this is only example for learning purpose and I do not take responsibilities for any damages :-)

require(reshape2)

User,Item,Rating
Anna,Apple,2
Bob,Apple,4
Carlos,Apple,1
Ethan,Apple,1
Gene,Apple,2
Anna,Banana,4
Dana,Banana,3
Ethan,Banana,5
Frank,Banana,5
Bob,Carrot,1
Ethan,Carrot,2
Gene,Carrot,4

R <- ratings %>% dcast(Item ~ User)
ProductNames <- R[,1]
R <- R[,-1] # only ratings

# tanimoto similarity (~Jaccard coefficiet)
tanimoto <- function(v1,v2) length(intersect(v1,v2))/length(union(v1,v2))

getKNN <- function(R, i, k, sim = tanimoto) {
similarity <- array(0,length(R))
for(j in 1:length(R)) {
if(i!=j) similarity[j] = sim(which(!is.na(R[,i])),which(!is.na(R[,j])))
}
idx <- order(similarity, decreasing = T)[1:k]
data.frame(idx = idx, similarity = similarity[idx])
}

kNNRecommender <- function(R, k) {
reco <- data.frame()
for(u in 1:length(R)) {
userItems <- which(!is.na(R[,u])) #items rated by user
nn <- getKNN(R,u, k) # get user neighbours

# rating prediction - wiegted by similarity
r <- array(0, c(dim(R), k))
for(i in 1:k) {
r[,i] = R[,nn[i,]$idx] * nn[i,]$similarity
}
r[is.na(r)] <- 0
userReco <- rowSums(r) / sum(nn$similarity) userRecoIdx <- order(userReco, decreasing = T) # remove items already rated by user userRecoIdx <- userRecoIdx[-userItems] # add to recommendations result reco <- rbind(reco, data.frame( User=u, Item=(if(length(userRecoIdx)==0) NA else userRecoIdx), Prediction=(if(length(userRecoIdx)==0) NA else userReco[userRecoIdx]) )) } reco } kNNRecommender(R, 2) • The only issue with recommenderlab is not memory hungry! Sep 23 '15 at 17:34 Complementing Bartłomiej Twardowski: The line removing items already rated by user cannot be applied since the indexes have been re-ordered, replace: userRecoIdx <- userRecoIdx[-userItems] by userRecoIdx <- setdiff(userRecoIdx,userItems) In order to consider sum of similarities with NA in another rows/columns userReco <- rowSums(r) / sum(nn$similarity)

by

userReco <- rowSums(r, na.rm = TRUE) / sum(nn\$similarity, na.rm = TRUE)