# Mapping a function over a graph's edges or vertices

Take a graph with VertexWeights and/or EdgeWeights like

g = Graph[{1, 2, 3}, {1 <-> 2, 2 <-> 3, 3 <-> 1},
EdgeWeight -> {1 <-> 2 -> "edge1", 2 <-> 3 -> "edge2", 3 <-> 1 -> "edge3"},
VertexWeight -> {1 -> "vertex1", 2 -> "vertex2", 3 -> "vertex3"},
VertexLabels -> "VertexWeight", EdgeLabels -> "EdgeWeight"]


How can one map a function over vertices and/or edges?

Using PropertyValue and VertexList/EdgeList one can define a version of Map that works on graphs.

For edges:

graphEdgeMap[f_, g_] :=
With[{weights = PropertyValue[{g, #}, EdgeWeight] & /@ EdgeList[g]},
Graph[g, EdgeWeight -> Thread[EdgeList[g] -> (f /@ weights)] ] ]

(* Operator form *)
graphEdgeMap[f_][g_] := graphEdgeMap[f, g]


and analogous for vertices:

graphVertexMap[f_, g_] :=
With[{weights = PropertyValue[{g, #}, VertexWeight] & /@ VertexList[g]},
Graph[g, VertexWeight -> Thread[VertexList[g] -> (f /@ weights)] ] ]

(* Operator form *)
graphVertexMap[f_][g_] := graphVertexMap[f, g]


## Usage example

h // graphEdgeMap[ToUpperCase] // graphVertexMap[Style[#, Red] &]


Although used for styling/string manipulation in this example, these functions can of course be used for computation as well.

• Related: mathematica.stackexchange.com/q/95935/12 and mathematica.stackexchange.com/q/143601/12 The performance of the method you use leaves something to be desired. (But then why is doing this so difficult in the first place? In other non-Mathematica graph manipulation packages this is much easier.) Nov 16, 2017 at 10:28
• I don't remember all the various bugs I hit when implementing this for IGraph/M, but here's one that comes up fairly often: try using graphEdgeMap on g = KaryTree[5, EdgeWeight -> {1., 2., 3., 4.}]. I should have commented the code better and explain why things are done the exact way they are—unfortunately this is something that can't be understood by reading only the code (you'd think you have an opportunity to do something more compactly than it currently is in IGraph/M but then it triggers bugs in edge cases). Nov 16, 2017 at 10:35
• @Szabolcs I agree with your sentiment that this is more difficult than it should be. There should be a build-in for this really. Nov 16, 2017 at 10:44
• I suggested a builtin to WRI more than once, including in the W Community thread I linked below my post. Nothing happened so far. I think first they'd have to fix the mess the property system is. Nov 16, 2017 at 10:45

IGraph/M has functions precisely for this. They are implemented purely on Mathematica (they don't use the igraph C core), so you can look at the implementation.

Sadly, making this work reliable and with reasonable performance is not at all easy. It requires distinguishing between the various kinds of properties and crossing the minefield of property-related bugs. So I won't go into details, instead, I'll show how to use the functionality in IGraph/M. For many more example, see its documentation (evaluate IGDocumentation[]).

Needs["IGraphM"]

g = ExampleData[{"NetworkGraph", "Friendship"}]


Look at the labels with the vertex property extractor:

IGVertexProp[VertexLabels][g]
(* {Placed["Anna", After], Placed["Rose", Above],
Placed["Nora", Above], Placed["Ben", Before], Placed["Larry", Above],
Placed["Carol", Below], Placed["Rudy", Below],
Placed["Linda", Above], Placed["James", Below]} *)


Make them uppercase by mapping a function over the VertexLabels property

IGVertexMap[MapAt[ToUpperCase, {1}], VertexLabels, g]


We needed MapAt, because, as you can see above, all vertex labels are given as Placed expressions.

Compute edge betweenness values for each edge and store them in the EdgeWeight property:

wg = g // IGEdgeMap[# &, EdgeWeight -> EdgeBetweennessCentrality]


We are mapping the identity function #& here, as we don't want to transform the values in any way.

Notice that we used the operator form of IGEdgeMap.

Copy the value of the EdgeWeight property to the "betweenness" edge property:

wg = IGEdgeMap[# &, "betweenness" -> IGEdgeProp[EdgeWeight], wg]


The right-hand-side of -> must always contain a function that returns a value for each edge in order when applied to the graph. The result will be transformed with the function given as the first argument, and then stored in the property specified in the left-hand-side of ->.

The IGEdgeProp and IGVertexProp property extractors are useful for constructing the RHS of ->, and are often used alone there. But they can also be combined with other functions. Here's how to colour each edge based on the "betweenness" edge property. We Rescale the value to fit the domain of the colour function.

IGEdgeMap[ColorData["Rainbow"],
EdgeStyle -> Rescale@*IGEdgeProp["betweenness"], wg]


Here's a more complex styling example from the documentation, showing operator forms, and mapping a function that takes multiple arguments.

g = Graph[ExampleData[{"NetworkGraph", "EastAfricaEmbassyAttacks"}],
ImageSize -> Medium];

g //
IGEdgeMap[ (* save original weight in "weight" property *)
Identity, "weight" -> IGEdgeProp[EdgeWeight]
] /*
IGEdgeMap[ (* invert edge weights for betweenness calculation; for this purpose we need large weight = weak connection *)
1/# &, EdgeWeight
] /*
IGEdgeMap[ (* thickness by original weight, colour by betweenness based on inverse weight; map two-argument function *)
Directive[AbsoluteThickness[9 #1], ColorData["Rainbow"][#2]] &,
EdgeStyle -> {IGEdgeProp["weight"], Rescale@*EdgeBetweennessCentrality}
]


Finally, here are the usage messages of the functions related to this functionality area:

There's also the question of what to do when a requested property value does not exist. For custom properties, values may be assigned only to some edges/vertices. The current behaviour of IGraph/M is to return Missing[...]` in these cases.

• Technically, the version of IGraph/M containing this is still a prerelease, so I am still taking suggestions for improvements; but at this point I'll only make breaking changes if the improvements are significant. Nov 16, 2017 at 10:43
• This framework was mostly developed in this Wolfram Community thread, which also doubles as a feature request for something similar to be built in. Nov 16, 2017 at 10:44