# Building the connection between cosine similarity and correlation in R

According to some articles (e.g. here) correlation is just a centered version of cosine similarity. I use the following code to calculate the cosine similarity matrix of the column vectors of a matrix X (slightly modified from here):

cos.sim <- function(ix)
{
A = X[,ix[1]]
B = X[,ix[2]]
return(t(A)%*%B/sqrt(sum(A^2)*sum(B^2)))
}
n <- ncol(X)
cmb <- expand.grid(i=1:n, j=1:n)
C <- matrix(apply(cmb,1,cos.sim),n,n)


My question
Which modifications of the code above are needed to get the correlation matrix cor(X) instead of the cosine similarity matrix. I guess the changes are minimal but I can't see them at the moment.

• I can't help with R because I'm not its user. But correlation is cosine for centered data. So, if you need cosine between data columns, do as if you are computing $r$, only do not center columns (or, equivalently, do not compute sum-of-squares of deviations, compute just sum-of-squares). – ttnphns May 9 '14 at 13:26

The answer is really right there in your linked articles. From the first, here are the formulae for cosine and correlation (lightly edited for brevity and clarity):

\begin{align} {\rm CosSim}(x,y) &= \frac{\sum_i x_i y_i}{ \sqrt{ \sum_i x_i^2} \sqrt{ \sum_i y_i^2 } } \\ \ \\ \ \\ {\rm Corr}(x,y) &= \frac{ \sum_i (x_i-\bar{x}) (y_i-\bar{y}) }{ \sqrt{\sum (x_i-\bar{x})^2} \sqrt{ \sum (y_i-\bar{y})^2 } } \\ \ \\ {\rm Corr}(x,y) &= {\rm CosSim}(x-\bar{x},\ y-\bar{y}) \end{align}

So the simplest adaptation is just to subtract the means from your input vectors:

library(MASS)  # we need this package to generate correlated data below
set.seed(2641) # this makes the example exactly reproducible
# now I generate correlated data:
X <- mvrnorm(1000, mu=c(100, 150), Sigma=rbind(c(30, 17),
c(17, 50) ) )
# I adapted the function somewhat, as the original was keyed to its context
cos.sim <- function(X, corr=FALSE){
if(corr){
A = X[,1] - mean(X[,1])
B = X[,2] - mean(X[,2])
} else {
A = X[,1]
B = X[,2]
}
return( t(A)%*%B / sqrt(sum(A^2)*sum(B^2)) )
}
cos.sim(X)
#           [,1]
# [1,] 0.9985756
cos.sim(X, corr=TRUE)
#           [,1]
# [1,] 0.4604822
cor(X)
#           [,1]      [,2]
# [1,] 1.0000000 0.4604822
# [2,] 0.4604822 1.0000000


Here is a matrix version:

set.seed(6616)
X3 <- mvrnorm(1000, mu=c(100, 150, 175), Sigma=rbind(c(30, 17, 12),
c(17, 50, 29),
c(12, 29, 46) ))
cos.sim.mat <- function(X, corr=FALSE){
if(corr){ X = apply(X, 2, function(x){ x-mean(x) }) }
denom = solve(diag(sqrt(diag(t(X)%*%X))))
return( denom%*%(t(X)%*%X)%*%denom )
}
cos.sim.mat(X3)
#           [,1]      [,2]      [,3]
# [1,] 1.0000000 0.9984552 0.9983700
# [2,] 0.9984552 1.0000000 0.9992154
# [3,] 0.9983700 0.9992154 1.0000000
cos.sim.mat(X3, corr=TRUE)
#           [,1]      [,2]      [,3]
# [1,] 1.0000000 0.3990872 0.2584569
# [2,] 0.3990872 1.0000000 0.5900067
# [3,] 0.2584569 0.5900067 1.0000000
cor(X3)
#           [,1]      [,2]      [,3]
# [1,] 1.0000000 0.3990872 0.2584569
# [2,] 0.3990872 1.0000000 0.5900067
# [3,] 0.2584569 0.5900067 1.0000000

• Thank you, yet I think the code doesn't work for matrices bigger than 2x2. Would you be so kind to check the code and perhaps modify it accordingly? Thank you again :-) – vonjd May 10 '14 at 15:49
• If your question is just asking for code, it would be off-topic on CV (see our help center). If you want to know how to get a correlation matrix, you should use the existing function cor(). The code I wrote above is to illustrate, in a transparent way, the difference between correlation and cosine. Nonetheless, I have added code that will output a matrix, but it is no longer as clear what the code is doing & how the correlation & cosine versions differ / are calculated. – gung - Reinstate Monica May 11 '14 at 19:17
• Thank you, I accepted your answer. My question was indeed to make the connection clear via some simple modifications on the code, not the code itself (I know that this would be off-topic). Anyway: What seems interesting to me is that the matrix version of your code (which works ok!) seems to have very little resemblance with the original code. You e.g. used this rather convoluted array of functions: solve(diag(sqrt(diag(t(X)%*%X))))%*%(t(X)%*%X)%*%solve(diag(sqrt(diag(t(X)%*%X)))) - I would have hoped that the modification to my code would be more easily comprehensible. Thank you again. – vonjd May 12 '14 at 6:37
• ...I just tried to add X = apply(X, 2, function(x){ x-mean(x) }) at the beginning of my code - and it did the trick (also with matrices)! Could this be all? – vonjd May 12 '14 at 6:47
• I added this as an answer for future reference. Thank you for your help. – vonjd May 12 '14 at 6:55

The only line that has to be added is X <- apply(X, 2, function(x){ x-mean(x) }) which just subtracts the mean column-wise:

X <- apply(X, 2, function(x){ x-mean(x) })
cos.sim <- function(ix)
{
A = X[,ix[1]]
B = X[,ix[2]]
return(t(A)%*%B/sqrt(sum(A^2)*sum(B^2)))
}
n <- ncol(X)
cmb <- expand.grid(i=1:n, j=1:n)
C <- matrix(apply(cmb,1,cos.sim),n,n)