Intoduction to GraphCentrality using FGraph

Graph centrality is a concept used in network analysis to identify and measure the relative importance or significance of nodes within a graph. It helps us understand which nodes play a central role in the network, indicating their influence, importance, or prominence. Centrality measures can be applied to various types of networks, including social networks, transportation networks, communication networks, and more.

Creating a graph by reading a complete graph representation as one.

Step 1 is the creation of a graph example where we can visualise the graph centrality. We will visualise the graph via Cytopscape

open Graphoscope
open Cytoscape.NET
open FSharpAux

let centralityEdges =
    seq{
        1,1,2,2,1.
        1,1,4,4,1.
        1,1,5,5,1.
        1,1,6,6,1.
        2,2,3,3,1.
    }

let centralityGraph = AdjGraph.ofSeq centralityEdges

let renderCyGraph (nodeLabelF:int -> CyParam.CyStyleParam ) =

    CyGraph.initEmpty ()
    |> CyGraph.withElements [
            for (sk,s,tk,t,el) in (AdjGraph.toSeq centralityGraph) do
                let sk, tk = (string sk), (string tk)
                yield Elements.node sk [ nodeLabelF s ]
                yield Elements.node tk [ nodeLabelF t ]
                yield Elements.edge  (sprintf "%s_%s" sk tk) sk tk [ ]
        ]
    |> CyGraph.withStyle "node"
        [
            CyParam.content =. CyParam.label
            CyParam.color "#A00975"
        ]
    |> CyGraph.withStyle "edge"
        [
            CyParam.content =. CyParam.label
            CyParam.Curve.style "bezier"
            CyParam.color "#438AFE"
        ]
    |> CyGraph.withLayout  (
        Layout.initBreadthfirst(Layout.LayoutOptions.Generic())
            )  
    |> CyGraph.withZoom(CytoscapeModel.Zoom.Init(ZoomingEnabled=false))
    |> CyGraph.withSize(800, 400)
    |> Cytoscape.NET.HTML.toGraphHTML() 

renderCyGraph (fun x -> CyParam.label $"Node: {x}")

Closeness Centrality

Closeness centrality assesses how quickly a node can reach all other nodes in the network. Nodes with high closeness centrality are considered central because they are close to many other nodes in terms of geodesic distance (the shortest path).

let closenessCentrality =
    Measures.ClosenessCentrality.computeWithEdgeData centralityGraph


renderCyGraph (fun x -> CyParam.label ($"Node: {x};Closeness: {((closenessCentrality.Item x)|>Math.round 3)}"))

Betweenness Centrality

Betweenness centrality measures how often a node lies on the shortest path between pairs of other nodes. Nodes with high betweenness centrality act as bridges or intermediaries in the network.

let betweenness = 
    Measures.BetweennessCentrality.computeWithEdgeData centralityGraph

renderCyGraph (fun x -> CyParam.label ($"Node: {x};Betweenness: {betweenness.Item x}"))

Node Eccentricity

Node eccentricity is a concept used in graph theory and network analysis to measure the centrality or importance of a node within a graph. It quantifies how far a node is from the farthest other node in the network in terms of the shortest path length. In other words, it represents the maximum distance between a node and any other node in the graph.

let eccentricity (node:'NodeKey) =
    Measures.Eccentricity.computeOfNodeWithEdgeData centralityGraph node

renderCyGraph (fun x -> CyParam.label ($"Node: {x};Eccentricity: {eccentricity x}"))
No value returned by any evaluator

Distances

Another important metric to take into account involves statistics related to all the shortest paths within a graph. These statistics encompass the Diameter (which represents the longest among the shortest paths), the Radius (representing the shortest of the shortest paths), and the average path length. As these metrics rely on information from all the shortest paths within a graph, they are fundamentally derived from the results of the Floyd-Warshall algorithm for calculating shortest paths. Therefore it is wise to calculate this once and reuse it for the calulations.

let diameter =
    Measures.Diameter.ofAdjGraph id centralityGraph

let radius =
    Measures.Radius.ofAdjGraph id  centralityGraph
"The given graph has a diameter of 3 and a radius of 2."
namespace Graphoscope
namespace Cytoscape
namespace Cytoscape.NET
namespace FSharpAux
val centralityEdges: seq<int * int * int * int * float>
Multiple items
val seq: sequence: seq<'T> -> seq<'T>
<summary>Builds a sequence using sequence expression syntax</summary>
<param name="sequence">The input sequence.</param>
<returns>The result sequence.</returns>
<example id="seq-cast-example"><code lang="fsharp"> seq { for i in 0..10 do yield (i, i*i) } </code></example>


--------------------
type seq<'T> = System.Collections.Generic.IEnumerable<'T>
<summary>An abbreviation for the CLI type <see cref="T:System.Collections.Generic.IEnumerable`1" /></summary>
<remarks> See the <see cref="T:Microsoft.FSharp.Collections.SeqModule" /> module for further operations related to sequences. See also <a href="https://docs.microsoft.com/dotnet/fsharp/language-reference/sequences">F# Language Guide - Sequences</a>. </remarks>
val centralityGraph: AdjGraph<int,int,float>
Multiple items
module AdjGraph from Graphoscope

--------------------
type AdjGraph = new: unit -> AdjGraph static member addEdge: sourceKey: 'NodeKey -> targetKey: 'NodeKey -> data: 'EdgeData -> graph: AdjGraph<'NodeKey,'NodeData,'EdgeData> -> AdjGraph<'NodeKey,'NodeData,'EdgeData> (requires comparison) static member addEdges: edgeSeq: seq<'NodeKey * 'NodeKey * 'EdgeData> -> g: AdjGraph<'NodeKey,'NodeData,'EdgeData> -> AdjGraph<'NodeKey,'NodeData,'EdgeData> (requires comparison) static member addElement: nk1: 'NodeKey -> nd1: 'NodeData -> nk2: 'NodeKey -> nd2: 'NodeData -> ed: 'EdgeData -> g: AdjGraph<'NodeKey,'NodeData,'EdgeData> -> AdjGraph<'NodeKey,'NodeData,'EdgeData> (requires comparison) static member addNode: nk: 'NodeKey -> nd: 'NodeData -> g: AdjGraph<'NodeKey,'NodeData,'EdgeData> -> AdjGraph<'NodeKey,'NodeData,'EdgeData> (requires comparison) static member addNodes: nodeSeq: seq<'NodeKey * 'NodeData> -> g: AdjGraph<'NodeKey,'NodeData,'EdgeData> -> AdjGraph<'NodeKey,'NodeData,'EdgeData> (requires comparison) static member containsEdge: v1: 'NodeKey -> v2: 'NodeKey -> g: AdjGraph<'NodeKey,'NodeData,'EdgeData> -> bool (requires comparison) static member containsNode: vk: 'NodeKey -> g: AdjGraph<'NodeKey,'NodeData,'EdgeData> -> bool (requires comparison) static member countEdges: graph: AdjGraph<'NodeKey,'NodeData,'EdgeData> -> int (requires comparison) static member countNodes: g: AdjGraph<'NodeKey,'NodeData,'EdgeData> -> int (requires comparison) ...
<summary> Functions to operate on the AdjGraph representation </summary>

--------------------
type AdjGraph<'NodeKey,'NodeData,'EdgeData (requires comparison)> = System.Collections.Generic.Dictionary<'NodeKey,('NodeData * System.Collections.Generic.Dictionary<'NodeKey,'EdgeData>)>
<summary> Basic Adjacency Graph representation </summary>

--------------------
new: unit -> AdjGraph
static member AdjGraph.ofSeq: edgelist: seq<'NodeKey * 'NodeData * 'NodeKey * 'NodeData * 'EdgeData> -> AdjGraph<'NodeKey,'NodeData,'EdgeData> (requires comparison)
val renderCyGraph: nodeLabelF: (int -> CyParam.CyStyleParam) -> string
val nodeLabelF: (int -> CyParam.CyStyleParam)
Multiple items
val int: value: 'T -> int (requires member op_Explicit)
<summary>Converts the argument to signed 32-bit integer. This is a direct conversion for all primitive numeric types. For strings, the input is converted using <c>Int32.Parse()</c> with InvariantCulture settings. Otherwise the operation requires an appropriate static conversion method on the input type.</summary>
<param name="value">The input value.</param>
<returns>The converted int</returns>
<example id="int-example"><code lang="fsharp"></code></example>


--------------------
[<Struct>] type int = int32
<summary>An abbreviation for the CLI type <see cref="T:System.Int32" />.</summary>
<category>Basic Types</category>


--------------------
type int<'Measure> = int
<summary>The type of 32-bit signed integer numbers, annotated with a unit of measure. The unit of measure is erased in compiled code and when values of this type are analyzed using reflection. The type is representationally equivalent to <see cref="T:System.Int32" />.</summary>
<category>Basic Types with Units of Measure</category>
module CyParam from Cytoscape.NET
type CyStyleParam = { Name: string Value: obj }
module CyGraph from Cytoscape.NET
val initEmpty: unit -> CyGraph.CyGraph
val withElements: elems: seq<CytoscapeModel.Element> -> cy: CyGraph.CyGraph -> CyGraph.CyGraph
val sk: int
val s: int
val tk: int
val t: int
val el: float
static member AdjGraph.toSeq: graph: AdjGraph<'NodeKey,'NodeData,'EdgeData> -> seq<'NodeKey * 'NodeData * 'NodeKey * 'NodeData * 'EdgeData> (requires comparison and comparison and comparison)
val sk: string
val tk: string
Multiple items
val string: value: 'T -> string
<summary>Converts the argument to a string using <c>ToString</c>.</summary>
<remarks>For standard integer and floating point values the and any type that implements <c>IFormattable</c><c>ToString</c> conversion uses <c>CultureInfo.InvariantCulture</c>. </remarks>
<param name="value">The input value.</param>
<returns>The converted string.</returns>
<example id="string-example"><code lang="fsharp"></code></example>


--------------------
type string = System.String
<summary>An abbreviation for the CLI type <see cref="T:System.String" />.</summary>
<category>Basic Types</category>
module Elements from Cytoscape.NET
val node: id: string -> dataAttributes: CyParam.CyStyleParam list -> Elements.Node
val edge: id: string -> sourceId: string -> targetId: string -> dataAttributes: CyParam.CyStyleParam list -> Elements.Edge
val sprintf: format: Printf.StringFormat<'T> -> 'T
<summary>Print to a string using the given format.</summary>
<param name="format">The formatter.</param>
<returns>The formatted result.</returns>
<example>See <c>Printf.sprintf</c> (link: <see cref="M:Microsoft.FSharp.Core.PrintfModule.PrintFormatToStringThen``1" />) for examples.</example>
val withStyle: selector: string -> cyStyles: seq<CyParam.CyStyleParam> -> cy: CyGraph.CyGraph -> CyGraph.CyGraph
val content: v: 'a -> CyParam.CyStyleParam
val label: v: 'a -> CyParam.CyStyleParam
val color: v: 'a -> CyParam.CyStyleParam
module Curve from Cytoscape.NET.CyParam
val style: v: 'a -> CyParam.CyStyleParam
val withLayout: ly: Layout -> cy: CyGraph.CyGraph -> CyGraph.CyGraph
Multiple items
module Layout from Cytoscape.NET

--------------------
type Layout = inherit DynamicObj new: name: string -> Layout member name: string
<summary> Layout type inherits from dynamic object </summary>

--------------------
new: name: string -> Layout
val initBreadthfirst: applyOption: (Layout -> Layout) -> Layout
<summary> initializes a layout of type "breadthfirst" applying the givin layout option function. The "breadthfirst" layout puts nodes in a hierarchy, based on a breadthfirst traversal of the graph. It is best suited to trees and forests in its default top-down mode, and it is best suited to DAGs in its circle mode. </summary>
Multiple items
type LayoutOptions = new: unit -> LayoutOptions static member Cose: ?Refresh: int * ?BoundingBox: 'a0 * ?NodeDimensionsIncludeLabels: bool * ?Randomize: bool * ?ComponentSpacing: int * ?NodeRepulsion: 'a1 * ?NodeOverlap: int * ?IdealEdgeLength: 'a2 * ?EdgeElasticity: 'a3 * ?NestingFactor: float * ?Gravity: int * ?NumIter: int * ?InitialTemp: int * ?CoolingFactor: float * ?MinTemp: float -> ('L -> 'L) (requires 'L :> Layout) static member Generic: ?Positions: 'a0 * ?Zoom: 'a1 * ?Pan: 'a2 * ?Fit: bool * ?Padding: int * ?Animate: bool * ?AnimationDuration: int * ?AnimationEasing: 'a3 * ?AnimateFilter: 'a4 * ?AnimationThreshold: int * ?Ready: 'a5 * ?Stop: 'a6 * ?Transform: 'a7 -> ('L -> 'L) (requires 'L :> Layout)
<summary> Functions provide the options of the Layout objects </summary>

--------------------
new: unit -> Layout.LayoutOptions
static member Layout.LayoutOptions.Generic: ?Positions: 'a0 * ?Zoom: 'a1 * ?Pan: 'a2 * ?Fit: bool * ?Padding: int * ?Animate: bool * ?AnimationDuration: int * ?AnimationEasing: 'a3 * ?AnimateFilter: 'a4 * ?AnimationThreshold: int * ?Ready: 'a5 * ?Stop: 'a6 * ?Transform: 'a7 -> ('L -> 'L) (requires 'L :> Layout)
val withZoom: zoom: CytoscapeModel.Zoom -> cy: CyGraph.CyGraph -> CyGraph.CyGraph
namespace Cytoscape.NET.CytoscapeModel
Multiple items
type Zoom = inherit DynamicObj new: unit -> Zoom static member Init: ?Level: float * ?Position: (int * int) * ?RenderedPosition: (int * int) * ?ZoomingEnabled: bool -> Zoom static member Update: ?Level: float * ?Position: (int * int) * ?RenderedPosition: (int * int) * ?ZoomingEnabled: bool -> (Zoom -> Zoom)

--------------------
new: unit -> CytoscapeModel.Zoom
static member CytoscapeModel.Zoom.Init: ?Level: float * ?Position: (int * int) * ?RenderedPosition: (int * int) * ?ZoomingEnabled: bool -> CytoscapeModel.Zoom
val withSize: width: int * height: int -> cy: CyGraph.CyGraph -> CyGraph.CyGraph
type HTML = static member CreateGraphHTML: graphData: string * zooming: string * divId: string * cytoscapeReference: CytoscapeJSReference * ?Width: int * ?Height: int -> XmlNode list static member CreateGraphScript: graphData: string * zooming: string * cytoscapeReference: CytoscapeJSReference -> XmlNode static member Doc: graphHTML: XmlNode list * cytoscapeReference: CytoscapeJSReference * ?AdditionalHeadTags: XmlNode list -> XmlNode static member show: cy: Cytoscape * ?DisplayOpts: DisplayOptions -> unit static member toEmbeddedHTML: ?DisplayOpts: DisplayOptions -> (Cytoscape -> string) static member toGraphHTML: ?DisplayOpts: DisplayOptions -> (Cytoscape -> string) static member toGraphHTMLNodes: ?DisplayOpts: DisplayOptions -> (Cytoscape -> XmlNode list)
<summary> HTML template for Cytoscape </summary>
static member HTML.toGraphHTML: ?DisplayOpts: DisplayOptions -> (CytoscapeModel.Cytoscape -> string)
val x: int
val closenessCentrality: System.Collections.Generic.Dictionary<int,float>
namespace Graphoscope.Measures
Multiple items
type ClosenessCentrality = new: unit -> ClosenessCentrality static member computWithEdgeDataBy: weightF: ('EdgeData -> float) * graph: FGraph<'NodeKey,'NodeData,'EdgeData> -> Dictionary<'NodeKey,float> (requires comparison) static member compute: graph: FGraph<'NodeKey,'NodeData,'EdgeData> -> Dictionary<'NodeKey,float> (requires comparison) + 1 overload static member computeNormalised: graph: FGraph<'NodeKey,'NodeData,'EdgeData> -> Dictionary<'NodeKey,float> (requires comparison) + 1 overload static member computeNormalisedWithEdgeData: graph: FGraph<'NodeKey,'NodeData,float> -> Dictionary<'NodeKey,float> (requires comparison) + 1 overload static member computeNormalisedWithEdgeDataBy: weightF: ('EdgeData -> float) * graph: FGraph<'NodeKey,'NodeData,'EdgeData> -> Dictionary<'NodeKey,float> (requires comparison) + 1 overload static member computeWithEdgeData: graph: FGraph<'NodeKey,'NodeData,float> -> Dictionary<'NodeKey,float> (requires comparison) + 1 overload static member computeWithEdgeDataBy: weightF: ('EdgeData -> float) * graph: AdjGraph<'NodeKey,'NodeData,'EdgeData> -> Dictionary<'NodeKey,float> (requires comparison and comparison and comparison) static member ofAdjGraph: getEdgeWeightF: ('EdgeData -> float) -> graph: AdjGraph<'NodeKey,'NodeData,'EdgeData> -> Dictionary<'NodeKey,float> (requires comparison and comparison and comparison) static member ofAdjGraphNode: getEdgeWeightF: ('EdgeData -> float) -> graph: AdjGraph<'NodeKey,'NodeData,'EdgeData> -> nodeKey: 'NodeKey -> float (requires comparison and comparison and comparison) ...

--------------------
new: unit -> Measures.ClosenessCentrality
static member Measures.ClosenessCentrality.computeWithEdgeData: graph: AdjGraph<'NodeKey,'NodeData,float> -> System.Collections.Generic.Dictionary<'NodeKey,float> (requires comparison and comparison)
static member Measures.ClosenessCentrality.computeWithEdgeData: graph: FGraph<'NodeKey,'NodeData,float> -> System.Collections.Generic.Dictionary<'NodeKey,float> (requires comparison)
property System.Collections.Generic.Dictionary.Item: int -> float with get, set
module Math from FSharpAux
val round: digits: int -> x: float -> float
<summary> Rounds a double-precision floating-point value to a specified number of fractional digits. </summary>
val betweenness: System.Collections.Generic.Dictionary<int,float>
Multiple items
type BetweennessCentrality = new: unit -> BetweennessCentrality static member compute: graph: AdjGraph<'NodeKey,'NodeData,'EdgeData> -> Dictionary<'NodeKey,float> (requires comparison and comparison and comparison) static member computeWithEdgeData: graph: AdjGraph<'NodeKey,'NodeData,float> -> Dictionary<'NodeKey,float> (requires comparison and comparison) static member computeWithEdgeDataBy: getEdgeWeight: ('EdgeData -> float) * graph: AdjGraph<'NodeKey,'NodeData,'EdgeData> -> Dictionary<'NodeKey,float> (requires comparison and comparison and comparison) static member ofAdjGraph: getEdgeWeight: ('EdgeData -> float) -> graph: AdjGraph<'NodeKey,'NodeData,'EdgeData> -> Dictionary<'NodeKey,float> (requires comparison and comparison and comparison)

--------------------
new: unit -> Measures.BetweennessCentrality
static member Measures.BetweennessCentrality.computeWithEdgeData: graph: AdjGraph<'NodeKey,'NodeData,float> -> System.Collections.Generic.Dictionary<'NodeKey,float> (requires comparison and comparison)
val eccentricity: node: 'NodeKey -> 'a
val node: 'NodeKey
Multiple items
type Eccentricity = new: unit -> Eccentricity static member compute: graph: FGraph<'NodeKey,'NodeData,'EdgeData> -> seq<'NodeKey * float> (requires comparison) + 1 overload static member computeOfNode: graph: FGraph<'NodeKey,'NodeData,'EdgeData> * nodeKey: 'NodeKey -> float (requires comparison) + 1 overload static member computeOfNodeWithEdgeData: graph: FGraph<'NodeKey,'NodeData,float> * nodeKey: 'NodeKey -> float (requires comparison) + 1 overload static member computeOfNodeWithEdgeDataBy: getEdgeWeightF: ('EdgeData -> float) * graph: FGraph<'NodeKey,'NodeData,'EdgeData> * nodeKey: 'NodeKey -> float (requires comparison) + 1 overload static member computeWithEdgeData: graph: FGraph<'NodeKey,'NodeData,float> -> seq<'NodeKey * float> (requires comparison) + 1 overload static member computeWithEdgeDataBy: getEdgeWeightF: ('EdgeData -> float) * graph: FGraph<'NodeKey,'NodeData,'EdgeData> -> seq<'NodeKey * float> (requires comparison) + 1 overload static member ofAdjGraph: getEdgeWeightF: ('EdgeData -> float) -> graph: AdjGraph<'NodeKey,'NodeData,'EdgeData> -> seq<'NodeKey * float> (requires comparison and comparison and comparison) static member ofAdjGraphNode: getEdgeWeightF: ('EdgeData -> float) -> graph: AdjGraph<'NodeKey,'NodeData,'EdgeData> -> nodeKey: 'NodeKey -> float (requires comparison and comparison and comparison) static member ofFGraph: getEdgeWeightF: ('EdgeData -> float) -> graph: FGraph<'NodeKey,'NodeData,'EdgeData> -> seq<'NodeKey * float> (requires comparison) ...

--------------------
new: unit -> Measures.Eccentricity
static member Measures.Eccentricity.computeOfNodeWithEdgeData: graph: AdjGraph<'NodeKey,'NodeData,float> * nodeKey: 'NodeKey -> float (requires comparison and comparison)
static member Measures.Eccentricity.computeOfNodeWithEdgeData: graph: FGraph<'NodeKey,'NodeData,float> * nodeKey: 'NodeKey -> float (requires comparison)
val diameter: float
Multiple items
type Diameter = new: unit -> Diameter static member compute: graph: FGraph<'NodeKey,'NodeData,'EdgeData> -> float (requires comparison) + 1 overload static member computeWithEdgeData: graph: FGraph<'NodeKey,'NodeData,float> -> float (requires comparison) + 1 overload static member computeWithEdgeDataBy: weigthF: ('EdgeData -> float) * graph: FGraph<'NodeKey,'NodeData,'EdgeData> -> float (requires comparison) + 1 overload static member ofAdjGraph: weigthF: ('EdgeData -> float) -> graph: AdjGraph<'NodeKey,'NodeData,'EdgeData> -> float (requires comparison and comparison and comparison) static member ofFGraph: weigthF: ('EdgeData -> float) -> graph: FGraph<'NodeKey,'NodeData,'EdgeData> -> float (requires comparison) static member ofGraph2D: floydWarshall: float[,] -> float

--------------------
new: unit -> Measures.Diameter
static member Measures.Diameter.ofAdjGraph: weigthF: ('EdgeData -> float) -> graph: AdjGraph<'NodeKey,'NodeData,'EdgeData> -> float (requires comparison and comparison and comparison)
val id: x: 'T -> 'T
<summary>The identity function</summary>
<param name="x">The input value.</param>
<returns>The same value.</returns>
<example id="id-example"><code lang="fsharp"> id 12 // Evaulates to 12 id "abc" // Evaulates to "abc" </code></example>
val radius: float
Multiple items
type Radius = new: unit -> Radius static member compute: graph: FGraph<'NodeKey,'NodeData,'EdgeData> -> float (requires comparison) + 1 overload static member computeWithEdgeData: graph: FGraph<'NodeKey,'NodeData,float> -> float (requires comparison) + 1 overload static member computeWithEdgeDataBy: weightF: ('EdgeData -> float) * graph: FGraph<'NodeKey,'NodeData,'EdgeData> -> float (requires comparison) + 1 overload static member ofAdjGraph: weigthF: ('EdgeData -> float) -> graph: AdjGraph<'NodeKey,'NodeData,'EdgeData> -> float (requires comparison and comparison and comparison) static member ofFGraph: weigthF: ('EdgeData -> float) -> graph: FGraph<'NodeKey,'NodeData,'EdgeData> -> float (requires comparison) static member ofGraph2D: floydWarshall: float[,] -> float

--------------------
new: unit -> Measures.Radius
static member Measures.Radius.ofAdjGraph: weigthF: ('EdgeData -> float) -> graph: AdjGraph<'NodeKey,'NodeData,'EdgeData> -> float (requires comparison and comparison and comparison)