igraph / rigraph

igraph R package
https://r.igraph.org
532 stars 200 forks source link

Allow arbitrary named edge value for strength weight argument #1344

Closed brooksambrose closed 2 months ago

brooksambrose commented 2 months ago

I have a graph with a "weight" edge attribute and a different attribute with a different name "altweight" that is an alternate measure of edge weight. Currently the strength function only aggregates the attributed called weight. I would like to pass the name of an arbitrary edge value (here "altweight") to the weights argument of strength for additional flexibility in calculating a strength score. Currently to accomplish the same task I have to save weight as an external vector, write over the weight edge attribute with the alternate vector, calculate the alternate strength scores, then write over weight with the saved vector, then delete the saved vector. It's not terrible, but not elegant either.

For edge values that are rather more complicated, like lists, the ability to pass a custom function to return the aggregation would be even more convenient.

szhorvat commented 2 months ago

No, you don't have to do any of this. Just use strength(graph, weights = E(graph)$altweight). You can pass in any weight vector, whether it's stored as an attribute or not. The "weight" attribute is special only in that it's used by default if you don't specify something else.

brooksambrose commented 2 months ago

Oh great thank you. Is there a function that performs this kind of edge bundling by vertex that would allow us to use a custom function? I also have edge lists that are more complicated that would require custom functions to aggregate.

szhorvat commented 2 months ago

Is there a function that performs this kind of edge bundling by vertex that would allow us to use a custom function?

I'm sorry, I'm not following. Can you clarify, perhaps through a specific example?

brooksambrose commented 2 months ago

In my use case, my graph is a single mode projection of a bimodal graph, and the edge data are sourced from the properties of the nodes of the missing mode. Being able to pass an sapply function to strength would allow aggregation of edge weights that are lists. In the following example the "weight" is a list of sets of items, and I want to count the unique members of the union of all the sets incident on the node as the strength.

I recognize that this is a different notion of what strength means in network terms, and is more like an apply function that works by bundling edges by node and performing a function on the bundle. Strength creates that bundle I presume, but can only sum the contents of the bundle, but it would be great if we could perform an arbitrary function on the bundle.

Currently to do the counting task I mentioned I would do something like the following:

library(igraph)
#> 
#> Attaching package: 'igraph'
#> The following objects are masked from 'package:stats':
#> 
#>     decompose, spectrum
#> The following object is masked from 'package:base':
#> 
#>     union
library(data.table)
g<-make_graph(c(1, 2, 2, 3, 3, 4), directed = FALSE)
E(g)$weight<-list(letters[1:3],letters[2:3],letters[3])
strength(g)
#> Error in strength(g): 'list' object cannot be coerced to type 'double'

df1<-df2<-as_data_frame(g)
colnames(df2)<-c('to','from','weight')
df<-data.table(merge(df1,df2,all = T))
df[,.(strength=uniqueN(unlist(weight))),by=from]
#>     from strength
#>    <num>    <int>
#> 1:     1        3
#> 2:     2        3
#> 3:     3        2
#> 4:     4        1

Created on 2024-04-15 with reprex v2.0.2

szhorvat commented 2 months ago

For edge values that are rather more complicated, like lists, the ability to pass a custom function to return the aggregation would be even more convenient.

This edit only showed up for me after I wrote my answer.

If I understand you, you are looking to generalize the idea of strength like this: The strength adds up the numerical weights of the incident edges of a vertex. What if instead of addition we want a custom aggregator function, even one that operates on objects different from numbers?

You could use incident_edges() to get the incident edges of all vertices, for example:

g <- make_graph(~ a-b-c-a-d-e-f-d)
E(g)$foo <- LETTERS[1:ecount(g)]

Now you can use inclist <- incident_edges(g, V(g)) to get incident edges. inclist$a would be the incident edges of vertex a and inclist$a$foo would give the foo attribute value of all incident edges of a. We could aggregate like this: lapply(inclist, function (x) paste(x$foo, collapse='')).

brooksambrose commented 2 months ago

Oh great thank you I will try that. I hadn't found incident_edges before but that seems like what I need.

szhorvat commented 2 months ago

Perhaps it wouldn't be a bad thing if the v parameter of the incident_edges() function were V(g) by default. What do you think @krlmlr ?

I have to say I'm not an R user and don't know what's idiomatic.