graphology-metrics
Version:
Miscellaneous graph metrics for graphology.
151 lines (126 loc) • 4.49 kB
JavaScript
/**
* Graphology Edge Disparity
* ==========================
*
* Function computing the disparity score of each edge in the given graph. This
* score is typically used to extract the multiscale backbone of a weighted
* graph.
*
* The formula from the paper (relying on integral calculus) can be simplified
* to become:
*
* disparity(u, v) = min(
* (1 - normalizedWeight(u, v)) ^ (degree(u) - 1)),
* (1 - normalizedWeight(v, u)) ^ (degree(v) - 1))
* )
*
* where normalizedWeight(u, v) = weight(u, v) / weightedDegree(u)
* where weightedDegree(u) = sum(weight(u, v) for v in neighbors(u))
*
* This score can sometimes be found reversed likewise:
*
* disparity(u, v) = max(
* 1 - (1 - normalizedWeight(u, v)) ^ (degree(u) - 1)),
* 1 - (1 - normalizedWeight(v, u)) ^ (degree(v) - 1))
* )
*
* so that higher score means better edges. I chose to keep the metric close
* to the paper to keep the statistical test angle. This means that, in my
* implementation at least, a low score for an edge means a high relevance and
* increases its chances to be kept in the backbone.
*
* Note that this algorithm has no proper definition for directed graphs and
* is only useful if edges have varying weights. This said, it could be
* possible to compute the disparity score only based on edge direction, if
* we drop the min part.
*
* [Article]:
* Serrano, M. Ángeles, Marián Boguná, and Alessandro Vespignani. "Extracting
* the multiscale backbone of complex weighted networks." Proceedings of the
* national academy of sciences 106.16 (2009): 6483-6488.
*
* [Reference]:
* https://www.pnas.org/content/pnas/106/16/6483.full.pdf
* https://en.wikipedia.org/wiki/Disparity_filter_algorithm_of_weighted_network
*/
var isGraph = require('graphology-utils/is-graph');
var inferType = require('graphology-utils/infer-type');
var resolveDefaults = require('graphology-utils/defaults');
var createEdgeWeightGetter =
require('graphology-utils/getters').createEdgeWeightGetter;
/**
* Defaults.
*/
var DEFAULTS = {
edgeDisparityAttribute: 'disparity',
getEdgeWeight: 'weight'
};
// TODO: test without weight, to see what happens
function abstractDisparity(assign, graph, options) {
if (!isGraph(graph))
throw new Error(
'graphology-metrics/edge/disparity: the given graph is not a valid graphology instance.'
);
if (graph.multi || inferType(graph) === 'mixed')
throw new Error(
'graphology-metrics/edge/disparity: not defined for multi nor mixed graphs.'
);
options = resolveDefaults(options, DEFAULTS);
var getEdgeWeight = createEdgeWeightGetter(options.getEdgeWeight).fromEntry;
// Computing node weighted degrees
var weightedDegrees = {};
graph.forEachNode(function (node) {
weightedDegrees[node] = 0;
});
graph.forEachEdge(function (edge, attr, source, target, sa, ta, undirected) {
var weight = getEdgeWeight(edge, attr, source, target, sa, ta, undirected);
weightedDegrees[source] += weight;
weightedDegrees[target] += weight;
});
// Computing edge disparity
var previous, previousDegree, previousWeightedDegree;
var disparities = {};
graph.forEachAssymetricAdjacencyEntry(function (
source,
target,
sa,
ta,
edge,
attr,
undirected
) {
var weight = getEdgeWeight(edge, attr, source, target, sa, ta, undirected);
if (previous !== source) {
previous = source;
previousDegree = graph.degree(source);
previousWeightedDegree = weightedDegrees[source];
}
var targetDegree = graph.degree(target);
var targetWeightedDegree = weightedDegrees[target];
var normalizedWeightPerSource = weight / previousWeightedDegree;
var normalizedWeightPerTarget = weight / targetWeightedDegree;
var sourceScore = Math.pow(
1 - normalizedWeightPerSource,
previousDegree - 1
);
var targetScore = Math.pow(1 - normalizedWeightPerTarget, targetDegree - 1);
disparities[edge] = Math.min(sourceScore, targetScore);
});
if (assign) {
graph.updateEachEdgeAttributes(
function (edge, attr) {
attr[options.edgeDisparityAttribute] = disparities[edge];
return attr;
},
{attributes: [options.edgeDisparityAttribute]}
);
return;
}
return disparities;
}
var disparity = abstractDisparity.bind(null, false);
disparity.assign = abstractDisparity.bind(null, true);
/**
* Exporting.
*/
module.exports = disparity;