2
votes

I'm using the NetworkX library to work with some small- to medium-sized unweighted, unsigned, directed graphs representing usage of a Web 2.0 site (smallest graph: less than two dozen nodes, largest: a few thousand). One of the things I want to calculate is eigenvector centrality, as follows:

>>> eig = networkx.eigenvector_centrality(my_graph)
>>> eigs = [(v,k) for k,v in eig.iteritems()]
>>> eigs.sort()
>>> eigs.reverse() 

However, this gives unexpected results: nodes with 0 outdegree but receiving inward arcs from very central nodes appear at the very back of the list with 0.0 eigenvector centrality (not being a mathematician I may have got this confused, but I don't think that outward arcs should make any difference to a node's centrality to a directed graph). In the course of investigating these results, I noticed from the documentation that NetworkX calculates 'right' eigenvector centrality by default; out of curiosity, I decided to calculate 'left' eigenvector centrality by the recommended method, i.e. reversing the graph before calculating eigenvector centrality (see Networkx documentation). To my surprise, I got exactly the same result: every node was calculated to have exactly the same eigenvector centrality as before. I think this should be a very unlikely outcome (see Wikipedia article), but I have since replicated it with all the graphs I'm working with. Can anyone explain to me what I'm doing wrong?

N.B. Using the NetworkX implementation of the PageRank algorithm provides the results I was expecting, i.e. nodes receiving inward arcs from very central nodes have high centrality even if their outdegree is 0. PageRank is usually considered to be a variant of eigenvector centrality (see Wikipedia article).

Edit: following a request from Aric, I have included some data. This is an anonymised version of my smallest graph. (I couldn't post toy data in case the problem is specific to the structure of my graphs.) Running the code below on my machine (with Python 2.7) appears to reveal (a) that each node's right and left eigenvector centrality are the same, and (b) that nodes with outdegree 0 invariably also have eigenvector centrality 0, even if they are quite central to the graph as a whole (e.g. node 61).

import networkx

anon_e_list = [(10, 59), (10, 15), (10, 61), (15, 32), (16, 31), (16, 0), (16, 37), (16, 54), (16, 45), (16, 56), (16, 10), (16, 8), (16, 36), (16, 24), (16, 30), (18, 34), (18, 36), (18, 30), (19, 1), (19, 3), (19, 51), (19, 21), (19, 40), (19, 41), (19, 30), (19, 14), (19, 61), (21, 64), (26, 1), (31, 1), (31, 3), (31, 51), (31, 62), (31, 33), (31, 40), (31, 23), (31, 30), (31, 18), (31, 13), (31, 46), (31, 61), (32, 3), (32, 2), (32, 33), (32, 6), (32, 7), (32, 9), (32, 15), (32, 17), (32, 18), (32, 23), (32, 30), (32, 5), (32, 27), (32, 34), (32, 35), (32, 38), (32, 40), (32, 42), (32, 43), (32, 46), (32, 47), (32, 62), (32, 56), (32, 57), (32, 59), (32, 64), (32, 61), (33, 0), (33, 31), (33, 2), (33, 7), (33, 9), (33, 10), (33, 12), (33, 64), (33, 14), (33, 46), (33, 16), (33, 17), (33, 18), (33, 19), (33, 20), (33, 21), (33, 22), (33, 23), (33, 30), (33, 26), (33, 28), (33, 11), (33, 34), (33, 32), (33, 35), (33, 37), (33, 38), (33, 39), (33, 41), (33, 43), (33, 45), (33, 24), (33, 47), (33, 48), (33, 49), (33, 58), (33, 62), (33, 53), (33, 54), (33, 55), (33, 60), (33, 57), (33, 59), (33, 5), (33, 52), (33, 63), (33, 61), (34, 58), (34, 4), (34, 33), (34, 20), (34, 55), (34, 28), (34, 11), (34, 64), (35, 18), (35, 60), (35, 61), (37, 34), (37, 48), (37, 49), (37, 18), (37, 33), (37, 39), (37, 21), (37, 42), (37, 26), (37, 59), (37, 44), (37, 12), (37, 11), (37, 61), (41, 3), (41, 50), (41, 18), (41, 52), (41, 33), (41, 54), (41, 19), (41, 22), (41, 5), (41, 46), (41, 25), (41, 44), (41, 13), (41, 62), (41, 29), (44, 32), (44, 3), (44, 18), (44, 33), (44, 40), (44, 41), (44, 30), (44, 23), (44, 61), (50, 17), (50, 37), (50, 62), (50, 41), (50, 25), (50, 43), (50, 27), (50, 28), (50, 29), (54, 33), (54, 41), (54, 10), (54, 59), (54, 63), (54, 61), (58, 62), (58, 46), (59, 31), (59, 34), (59, 30), (59, 49), (59, 18), (59, 33), (59, 9), (59, 10), (59, 8), (59, 13), (59, 24), (59, 61), (60, 34), (60, 16), (60, 35), (60, 50), (60, 4), (60, 6), (60, 59), (60, 24), (63, 40), (63, 33), (63, 30), (63, 61), (63, 53)]

my_graph = networkx.DiGraph()
my_graph.add_edges_from(anon_e_list)
r_eig = networkx.eigenvector_centrality(my_graph)
my_graph2 = my_graph.reverse()
l_eig = networkx.eigenvector_centrality(my_graph2)

for nd in my_graph.nodes():
    print 'node: {} indegree: {} outdegree: {} right eig: {} left eig: {}'.format(nd,my_graph.in_degree(nd),my_graph.out_degree(nd),r_eig[nd],l_eig[nd])
1
Could you post more specifically what the problem is? Note that you'll probably get a lot of zeros in eigenvector centrality for nodes that aren't in a strongly connected component with more than 1 node. Eigenvector centrality probably isn't what you want for DiGraphs. - Aric
@Aric -- the specific problems are (a) that a node's outward arcs seem to influence its centrality, and (b) that I'm getting exactly the same results for right and left eigenvector centrality, which leads me to suspect that there's something wrong either with the way that NetworkX's eigenvector algorithm handles directed graphs or (more likely) with the way that I'm calling the eigenvector_centrality function. Each graph consists of a single, fairly strongly connected component, though I can't post data here for ethical reasons. Why wouldn't I want eigenvector centrality for a directed graph? - Westcroft_to_Apse
You could get all zero eigenvalues with a digraph, e.g. with a directed path. Or the largest eigenvalue/eigenvector could be complex. If you update your code example with a small graph we might be able to figure out your problem. - Aric
@Aric okay, I've added some data now. As you'll be able to see now, I'm not getting all zero eigenvalues. What are the consequences if the largest eigenvector is complex? Any help or explanation will be much appreciated. - Westcroft_to_Apse

1 Answers

4
votes

These two lines

my_graph2 = my_graph.copy()
my_graph2.reverse()

should be replaced with

my_graph2 = my_graph.reverse()

since the reverse() method by default returns a copy of the graph.