Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

multiplicative distance between graph nodes

I want to find the distance between all nodes in a graph, but rather than summing the edge weights I want to multiply them.

As an example:

library(igraph)

# create a weighted adjacency matrix
mx <- structure(c(0, 0.5, 0, 0, 0, 0.5, 0, 0.5, 0.5, 0, 0, 0.5, 0, 0, 0.5, 0, 0.5, 
    0, 0, 0, 0, 0, 0.5, 0, 0), .Dim = c(5L, 5L))

## convert to igraph object
mx2 <- graph.adjacency(mx, weighted = TRUE)

I can get the distance between all nodes as follows:

shortest.paths(mx2)

 [,1] [,2] [,3] [,4] [,5]
[1,]  0.0  0.5  1.0  1.0  1.5
[2,]  0.5  0.0  0.5  0.5  1.0
[3,]  1.0  0.5  0.0  1.0  0.5
[4,]  1.0  0.5  1.0  0.0  1.5
[5,]  1.5  1.0  0.5  1.5  0.0

But this calculates the distance between all nodes by summing the relevant weights I want to multiply them, which would result in the following:

      [,1] [,2] [,3]  [,4]  [,5]
[1,] 0.000 0.50 0.25 0.250 0.125
[2,] 0.500 0.00 0.50 0.500 0.250
[3,] 0.250 0.50 0.00 0.250 0.500
[4,] 0.250 0.50 0.25 0.000 0.125
[5,] 0.125 0.25 0.50 0.125 0.000

As far as I can see this can't be done using "out of the box" options in igraph, and I am struggling to figure it out on my own (in the real data the matrices are much larger, and a variety of sizes). Any suggestions would be much appreciated.

like image 529
flee Avatar asked Apr 24 '18 13:04

flee


2 Answers

Writing a function as in the answer above is surely the better way to go. But another way to think about the problem is that if you want products of weights, and shortest.paths() gives you sums of weights, then if you feed the shortest.paths() function the log of your weights, the exponent of the result will equal the product of weights.

This is a little finickier in practice than I thought it would be, as your weights fall between 0 and 1 and the shortest.paths() algorithm will not accept negative weights, but you can work around by multiplying through by -1 before and after computing the weights.

library(igraph)

## Cheat and log it
ln.mx <- structure(c(0, 0.5, 0, 0, 0, 0.5, 0, 0.5, 0.5, 0, 0, 0.5, 0, 0, 0.5, 0, 0.5, 
                  0, 0, 0, 0, 0, 0.5, 0, 0), .Dim = c(5L, 5L))
ln.mx <- ifelse(ln.mx!=0, log(ln.mx), 0) 

## convert to igraph object
ln.mx2 <- graph.adjacency(ln.mx, weighted = TRUE)

# The issue with the approach is that the shortest.path algorithm doesn't like
# negative weights. Since your weights fall in (0,1) their log is negative.
# We multiply edge weights by -1 to swap the sign, and then will 
# multiply again by -1 to get
# the result
E(ln.mx2)$weight <- -1*E(ln.mx2)$weight

# The result is just the regular shortest paths algorithm,
# times -1 (to undo the step above) and exponentiated to undue the logging
res <- exp(shortest.paths(ln.mx2)* -1)

# its still not perfect since the diagonal distance defaults to
# zero and exp(0) is 1, not 0. So we manually reset the diagonal
diag(res) <- 0

# The result is as hoped
res
#>       [,1] [,2] [,3]  [,4]  [,5]
#> [1,] 0.000 0.50 0.25 0.250 0.125
#> [2,] 0.500 0.00 0.50 0.500 0.250
#> [3,] 0.250 0.50 0.00 0.250 0.500
#> [4,] 0.250 0.50 0.25 0.000 0.125
#> [5,] 0.125 0.25 0.50 0.125 0.000
like image 192
gfgm Avatar answered Sep 25 '22 00:09

gfgm


Here is a proposal. There is probably a lot of room for improvement, but it gives the expected output. The idea is to extract the shortest path for each pair of nodes and then, multiply the weights associated to each path (I used some code from this answer).

shortest.paths.multi <- function(mx) {
  output <- mx
  mx2 <- graph.adjacency(mx, weighted = TRUE)
  for (r in 1:nrow(mx)){
    for (c in 1:nrow(mx)){
      SP <- shortest_paths(mx2, from = r, to = c)
      VP <- SP$vpath[[1]]
      EP <- rep(VP, each=2)[-1]
      EP <- EP[-length(EP)]
      output[r, c] <- prod(E(mx2)$weight[get.edge.ids(mx2, EP)])
    }
  }
  diag(output) <- 0
  output
}

shortest.paths.multi(mx)
      [,1] [,2] [,3]  [,4]  [,5]
[1,] 0.000 0.50 0.25 0.250 0.125
[2,] 0.500 0.00 0.50 0.500 0.250
[3,] 0.250 0.50 0.00 0.250 0.500
[4,] 0.250 0.50 0.25 0.000 0.125
[5,] 0.125 0.25 0.50 0.125 0.000

EDIT

Here is probably a better way to write this function:

shortest.paths.multi <- function(r, c){ 
  SP <- shortest_paths(mx2, from = r, to = c)
  VP <- SP$vpath[[1]]
  EP <- rep(VP, each=2)[-1]
  EP <- EP[-length(EP)]
  prod(E(mx2)$weight[get.edge.ids(mx2, EP)])
}

VecFun <- Vectorize(shortest.paths.multi)

output <- outer(1:nrow(mx), 1:ncol(mx), FUN = VecFun)
diag(output) <- 0
output
like image 25
DJack Avatar answered Sep 25 '22 00:09

DJack