Commit 095668c8 authored by Tiago Peixoto's avatar Tiago Peixoto

Implement closeness centrality

parent 23fa373d
......@@ -17,6 +17,7 @@ libgraph_tool_centrality_la_LDFLAGS = $(MOD_LDFLAGS)
libgraph_tool_centrality_la_SOURCES = \
graph_betweenness.cc \
graph_centrality_bind.cc \
graph_closeness.cc \
graph_eigentrust.cc \
graph_eigenvector.cc \
graph_hits.cc \
......@@ -25,6 +26,7 @@ libgraph_tool_centrality_la_SOURCES = \
graph_trust_transitivity.cc
libgraph_tool_centrality_la_include_HEADERS = \
graph_closeness.hh \
graph_eigentrust.hh \
graph_eigenvector.hh \
graph_pagerank.hh \
......
......@@ -20,6 +20,7 @@
using namespace boost;
void export_betweenness();
void export_closeness();
void export_eigentrust();
void export_eigenvector();
void export_hits();
......@@ -30,6 +31,7 @@ void export_pagerank();
BOOST_PYTHON_MODULE(libgraph_tool_centrality)
{
export_betweenness();
export_closeness();
export_eigentrust();
export_eigenvector();
export_hits();
......
// graph-tool -- a general graph modification and manipulation thingy
//
// Copyright (C) 2006-2013 Tiago de Paula Peixoto <tiago@skewed.de>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 3
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "graph_filtering.hh"
#include "graph.hh"
#include "graph_selectors.hh"
#include "graph_properties.hh"
#include "graph_closeness.hh"
using namespace std;
using namespace boost;
using namespace graph_tool;
void do_get_closeness(GraphInterface& gi, boost::any weight,
boost::any closeness, bool harmonic, bool norm)
{
if (weight.empty())
{
run_action<>()(gi,
bind<void>(get_closeness(), _1,
gi.GetVertexIndex(), no_weightS(),
_2, harmonic, norm),
writable_vertex_scalar_properties())(closeness);
}
else
{
run_action<>()(gi,
bind<void>(get_closeness(), _1,
gi.GetVertexIndex(), _2,
_3, harmonic, norm),
edge_scalar_properties(),
writable_vertex_scalar_properties())(weight, closeness);
}
}
void export_closeness()
{
python::def("closeness", &do_get_closeness);
}
// graph-tool -- a general graph modification and manipulation thingy
//
// Copyright (C) 2006-2013 Tiago de Paula Peixoto <tiago@skewed.de>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 3
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#ifndef GRAPH_CLOSENESS_HH
#define GRAPH_CLOSENESS_HH
#include <boost/graph/breadth_first_search.hpp>
#include <boost/graph/dijkstra_shortest_paths.hpp>
#include <boost/python/object.hpp>
#include <boost/python/list.hpp>
#include <boost/python/extract.hpp>
#include "histogram.hh"
#include "numpy_bind.hh"
namespace graph_tool
{
using namespace std;
using namespace boost;
struct no_weightS {};
template <class Map>
struct get_val_type
{
typedef typename property_traits<Map>::value_type type;
};
template <>
struct get_val_type<no_weightS>
{
typedef size_t type;
};
struct get_closeness
{
template <class Graph, class VertexIndex, class WeightMap, class Closeness>
void operator()(const Graph& g, VertexIndex vertex_index, WeightMap weights,
Closeness closeness, bool harmonic, bool norm)
const
{
typedef typename graph_traits<Graph>::vertex_descriptor vertex_t;
// select get_vertex_dists based on the existence of weights
typedef typename mpl::if_<is_same<WeightMap, no_weightS>,
get_dists_bfs,
get_dists_djk>::type get_vertex_dists_t;
// distance type
typedef typename get_val_type<WeightMap>::type val_type;
get_vertex_dists_t get_vertex_dists;
size_t HN = HardNumVertices()(g);
int i, N = num_vertices(g);
#pragma omp parallel for default(shared) private(i) schedule(dynamic)
for (i = 0; i < N; ++i)
{
vertex_t v = vertex(i, g);
if (v == graph_traits<Graph>::null_vertex())
continue;
unchecked_vector_property_map<val_type,VertexIndex>
dist_map(vertex_index, num_vertices(g));
for (int j = 0; j < N; ++j)
{
if (vertex(j, g) != graph_traits<Graph>::null_vertex())
dist_map[vertex(j, g)] = numeric_limits<val_type>::max();
}
dist_map[v] = 0;
size_t comp_size = 0;
get_vertex_dists(g, v, vertex_index, dist_map, weights, comp_size);
closeness[v] = 0;
typename graph_traits<Graph>::vertex_iterator v2, v_end;
for (tie(v2, v_end) = vertices(g); v2 != v_end; ++v2)
{
if (*v2 != v && dist_map[*v2] != numeric_limits<val_type>::max())
{
if (!harmonic)
closeness[v] += dist_map[*v2];
else
closeness[v] += 1. / dist_map[*v2];
}
}
if (!harmonic)
closeness[v] = 1 / closeness[v];
if (norm)
{
if (harmonic)
closeness[v] /= HN - 1;
else
closeness[v] *= comp_size - 1;
}
}
}
class component_djk_visitor: public dijkstra_visitor<>
{
public:
//component_visitor() { }
component_djk_visitor(size_t& comp_size)
: _comp_size(comp_size) { }
template <class Vertex, class Graph>
void discover_vertex(Vertex u, const Graph&)
{
++_comp_size;
}
private:
size_t& _comp_size;
};
// weighted version. Use dijkstra_shortest_paths()
struct get_dists_djk
{
template <class Graph, class Vertex, class VertexIndex,
class DistanceMap, class WeightMap>
void operator()(const Graph& g, Vertex s, VertexIndex vertex_index,
DistanceMap dist_map, WeightMap weights,
size_t& comp_size) const
{
component_djk_visitor vis(comp_size);
dijkstra_shortest_paths(g, s, vertex_index_map(vertex_index).
weight_map(weights).distance_map(dist_map).visitor(vis));
}
};
template <class DistMap>
class component_bfs_visitor: public bfs_visitor<>
{
public:
//component_visitor() { }
component_bfs_visitor(DistMap dist_map, size_t& comp_size)
: _dist_map(dist_map), _comp_size(comp_size) { }
template <class Vertex, class Graph>
void discover_vertex(Vertex, const Graph&)
{
++_comp_size;
}
template <class Edge, class Graph>
void tree_edge(Edge e, const Graph& g)
{
_dist_map[target(e, g)] = _dist_map[source(e, g)] + 1;
}
private:
DistMap _dist_map;
size_t& _comp_size;
};
// unweighted version. Use BFS.
struct get_dists_bfs
{
template <class Graph, class Vertex, class VertexIndex,
class DistanceMap>
void operator()(const Graph& g, Vertex s, VertexIndex vertex_index,
DistanceMap dist_map, no_weightS, size_t& comp_size) const
{
typedef typename graph_traits<Graph>::vertex_descriptor vertex_t;
typedef tr1::unordered_map<vertex_t,default_color_type,
DescriptorHash<VertexIndex> > cmap_t;
cmap_t cmap(0, DescriptorHash<VertexIndex>(vertex_index));
InitializedPropertyMap<cmap_t>
color_map(cmap, color_traits<default_color_type>::white());
component_bfs_visitor<DistanceMap> vis(dist_map, comp_size);
breadth_first_visit(g, s, visitor(vis).
color_map(color_map));
}
};
};
} // boost namespace
#endif // GRAPH_CLOSENESS_HH
......@@ -33,6 +33,7 @@ Summary
pagerank
betweenness
central_point_dominance
closeness
eigenvector
katz
hits
......@@ -52,8 +53,8 @@ from .. import _prop, ungroup_vector_property
import sys
import numpy
__all__ = ["pagerank", "betweenness", "central_point_dominance", "eigentrust",
"eigenvector", "katz", "hits", "trust_transitivity"]
__all__ = ["pagerank", "betweenness", "central_point_dominance", "closeness",
"eigentrust", "eigenvector", "katz", "hits", "trust_transitivity"]
def pagerank(g, damping=0.85, pers=None, weight=None, prop=None, epsilon=1e-6,
......@@ -319,6 +320,132 @@ def betweenness(g, vprop=None, eprop=None, weight=None, norm=True):
_prop("e", g, eprop), _prop("v", g, vprop), norm)
return vprop, eprop
def closeness(g, weight=None, source=None, vprop=None, norm=True, harmonic=False):
r"""
Calculate the closeness centrality for each vertex.
Parameters
----------
g : :class:`~graph_tool.Graph`
Graph to be used.
weight : :class:`~graph_tool.PropertyMap`, optional (default: None)
Edge property map corresponding to the weight value of each edge.
source : :class:`~graph_tool.Vertex`, optional (default: ``None``)
If specified, the centrality is computed for this vertex alone.
vprop : :class:`~graph_tool.PropertyMap`, optional (default: ``None``)
Vertex property map to store the vertex centrality values.
norm : bool, optional (default: ``True``)
Whether or not the centrality values should be normalized.
harmonic : bool, optional (default: ``False``)
If true, the sum of the inverse of the distances will be computed,
instead of the inverse of the sum.
Returns
-------
vertex_closeness : :class:`~graph_tool.PropertyMap`
A vertex property map with the vertex closeness values.
See Also
--------
central_point_dominance: central point dominance of the graph
pagerank: PageRank centrality
eigentrust: eigentrust centrality
eigenvector: eigenvector centrality
hits: hubs and authority centralities
trust_transitivity: pervasive trust transitivity
Notes
-----
The closeness centrality of a vertex :math:`i` is defined as,
.. math::
c_i = \frac{1}{\sum_j d_{ij}}
where :math:`d_{ij}` is the (possibly directed and/or weighted) distance
from :math:`i` to :math:`j`. In case there is no path between the two
vertices, here the distance is taken to be zero.
If ``harmonic == True``, the definition becomes
.. math::
c_i = \sum_j\frac{1}{d_{ij}},
but now, in case there is no path between the two vertices, we take
:math:`d_{ij} \to\infty` such that :math:`1/d_{ij}=0`.
If ``norm == True``, the values of :math:`c_i` are normalized by
:math:`n_i-1` where :math:`n_i` is the size of the (out-) component of
:math:`i`. If ``harmonic == True``, they are instead simply normalized by
:math:`N-1`.
The algorithm complexity of :math:`O(N(N + E))` for unweighted graphs and
:math:`O(N(N+E) \log N)` for weighted graphs. If the option ``source`` is
specified, this drops to :math:`O(N + E)` and :math:`O((N+E)\log N)`
respectively.
If enabled during compilation, this algorithm runs in parallel.
Examples
--------
.. doctest:: closeness
>>> g = gt.collection.data["polblogs"]
>>> g = gt.GraphView(g, vfilt=gt.label_largest_component(g))
>>> c = gt.closeness(g)
>>> gt.graph_draw(g, pos=g.vp["pos"], vertex_fill_color=c,
... vertex_size=gt.prop_to_size(c, mi=5, ma=15),
... vorder=c, output="polblogs_closeness.pdf")
<...>
.. testcode:: closeness
:hide:
gt.graph_draw(g, pos=g.vp["pos"], vertex_fill_color=c,
vertex_size=gt.prop_to_size(c, mi=5, ma=15),
vorder=c, output="polblogs_closeness.png")
.. figure:: polblogs_closeness.*
:align: center
Closeness values of the a political blogs network of [adamic-polblogs]_.
References
----------
.. [closeness-wikipedia] https://en.wikipedia.org/wiki/Closeness_centrality
.. [opsahl-node-2010] Opsahl, T., Agneessens, F., Skvoretz, J., "Node
centrality in weighted networks: Generalizing degree and shortest
paths". Social Networks 32, 245-251, 2010 :DOI:`10.1016/j.socnet.2010.03.006`
.. [adamic-polblogs] L. A. Adamic and N. Glance, "The political blogosphere
and the 2004 US Election", in Proceedings of the WWW-2005 Workshop on the
Weblogging Ecosystem (2005). :DOI:`10.1145/1134271.1134277`
"""
if source is None:
if vprop == None:
vprop = g.new_vertex_property("double")
libgraph_tool_centrality.\
closeness(g._Graph__graph, _prop("e", g, weight),
_prop("v", g, vprop), harmonic, norm)
return vprop
else:
max_dist = g.num_vertices() + 1
dist = shortest_distance(g, source=source, weight=weight,
max_dist=max_dist)
if harmonic:
dists = dist.fa[(dist.fa < max_dist) * (dist.fa > 0)]
c = (1. / dists).sum()
if norm:
c /= g.num_vertices() - 1
else:
dists = dist.fa[(dist.fa < max_dist) * (dist.fa > 0)]
c = 1. / dists.sum()
if norm:
c /= len(dists)
def central_point_dominance(g, betweenness):
r"""
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment