Spaces:
Running
Running
"""One-mode (unipartite) projections of bipartite graphs.""" | |
import networkx as nx | |
from networkx.exception import NetworkXAlgorithmError | |
from networkx.utils import not_implemented_for | |
__all__ = [ | |
"projected_graph", | |
"weighted_projected_graph", | |
"collaboration_weighted_projected_graph", | |
"overlap_weighted_projected_graph", | |
"generic_weighted_projected_graph", | |
] | |
def projected_graph(B, nodes, multigraph=False): | |
r"""Returns the projection of B onto one of its node sets. | |
Returns the graph G that is the projection of the bipartite graph B | |
onto the specified nodes. They retain their attributes and are connected | |
in G if they have a common neighbor in B. | |
Parameters | |
---------- | |
B : NetworkX graph | |
The input graph should be bipartite. | |
nodes : list or iterable | |
Nodes to project onto (the "bottom" nodes). | |
multigraph: bool (default=False) | |
If True return a multigraph where the multiple edges represent multiple | |
shared neighbors. They edge key in the multigraph is assigned to the | |
label of the neighbor. | |
Returns | |
------- | |
Graph : NetworkX graph or multigraph | |
A graph that is the projection onto the given nodes. | |
Examples | |
-------- | |
>>> from networkx.algorithms import bipartite | |
>>> B = nx.path_graph(4) | |
>>> G = bipartite.projected_graph(B, [1, 3]) | |
>>> list(G) | |
[1, 3] | |
>>> list(G.edges()) | |
[(1, 3)] | |
If nodes `a`, and `b` are connected through both nodes 1 and 2 then | |
building a multigraph results in two edges in the projection onto | |
[`a`, `b`]: | |
>>> B = nx.Graph() | |
>>> B.add_edges_from([("a", 1), ("b", 1), ("a", 2), ("b", 2)]) | |
>>> G = bipartite.projected_graph(B, ["a", "b"], multigraph=True) | |
>>> print([sorted((u, v)) for u, v in G.edges()]) | |
[['a', 'b'], ['a', 'b']] | |
Notes | |
----- | |
No attempt is made to verify that the input graph B is bipartite. | |
Returns a simple graph that is the projection of the bipartite graph B | |
onto the set of nodes given in list nodes. If multigraph=True then | |
a multigraph is returned with an edge for every shared neighbor. | |
Directed graphs are allowed as input. The output will also then | |
be a directed graph with edges if there is a directed path between | |
the nodes. | |
The graph and node properties are (shallow) copied to the projected graph. | |
See :mod:`bipartite documentation <networkx.algorithms.bipartite>` | |
for further details on how bipartite graphs are handled in NetworkX. | |
See Also | |
-------- | |
is_bipartite, | |
is_bipartite_node_set, | |
sets, | |
weighted_projected_graph, | |
collaboration_weighted_projected_graph, | |
overlap_weighted_projected_graph, | |
generic_weighted_projected_graph | |
""" | |
if B.is_multigraph(): | |
raise nx.NetworkXError("not defined for multigraphs") | |
if B.is_directed(): | |
directed = True | |
if multigraph: | |
G = nx.MultiDiGraph() | |
else: | |
G = nx.DiGraph() | |
else: | |
directed = False | |
if multigraph: | |
G = nx.MultiGraph() | |
else: | |
G = nx.Graph() | |
G.graph.update(B.graph) | |
G.add_nodes_from((n, B.nodes[n]) for n in nodes) | |
for u in nodes: | |
nbrs2 = {v for nbr in B[u] for v in B[nbr] if v != u} | |
if multigraph: | |
for n in nbrs2: | |
if directed: | |
links = set(B[u]) & set(B.pred[n]) | |
else: | |
links = set(B[u]) & set(B[n]) | |
for l in links: | |
if not G.has_edge(u, n, l): | |
G.add_edge(u, n, key=l) | |
else: | |
G.add_edges_from((u, n) for n in nbrs2) | |
return G | |
def weighted_projected_graph(B, nodes, ratio=False): | |
r"""Returns a weighted projection of B onto one of its node sets. | |
The weighted projected graph is the projection of the bipartite | |
network B onto the specified nodes with weights representing the | |
number of shared neighbors or the ratio between actual shared | |
neighbors and possible shared neighbors if ``ratio is True`` [1]_. | |
The nodes retain their attributes and are connected in the resulting | |
graph if they have an edge to a common node in the original graph. | |
Parameters | |
---------- | |
B : NetworkX graph | |
The input graph should be bipartite. | |
nodes : list or iterable | |
Distinct nodes to project onto (the "bottom" nodes). | |
ratio: Bool (default=False) | |
If True, edge weight is the ratio between actual shared neighbors | |
and maximum possible shared neighbors (i.e., the size of the other | |
node set). If False, edges weight is the number of shared neighbors. | |
Returns | |
------- | |
Graph : NetworkX graph | |
A graph that is the projection onto the given nodes. | |
Examples | |
-------- | |
>>> from networkx.algorithms import bipartite | |
>>> B = nx.path_graph(4) | |
>>> G = bipartite.weighted_projected_graph(B, [1, 3]) | |
>>> list(G) | |
[1, 3] | |
>>> list(G.edges(data=True)) | |
[(1, 3, {'weight': 1})] | |
>>> G = bipartite.weighted_projected_graph(B, [1, 3], ratio=True) | |
>>> list(G.edges(data=True)) | |
[(1, 3, {'weight': 0.5})] | |
Notes | |
----- | |
No attempt is made to verify that the input graph B is bipartite, or that | |
the input nodes are distinct. However, if the length of the input nodes is | |
greater than or equal to the nodes in the graph B, an exception is raised. | |
If the nodes are not distinct but don't raise this error, the output weights | |
will be incorrect. | |
The graph and node properties are (shallow) copied to the projected graph. | |
See :mod:`bipartite documentation <networkx.algorithms.bipartite>` | |
for further details on how bipartite graphs are handled in NetworkX. | |
See Also | |
-------- | |
is_bipartite, | |
is_bipartite_node_set, | |
sets, | |
collaboration_weighted_projected_graph, | |
overlap_weighted_projected_graph, | |
generic_weighted_projected_graph | |
projected_graph | |
References | |
---------- | |
.. [1] Borgatti, S.P. and Halgin, D. In press. "Analyzing Affiliation | |
Networks". In Carrington, P. and Scott, J. (eds) The Sage Handbook | |
of Social Network Analysis. Sage Publications. | |
""" | |
if B.is_directed(): | |
pred = B.pred | |
G = nx.DiGraph() | |
else: | |
pred = B.adj | |
G = nx.Graph() | |
G.graph.update(B.graph) | |
G.add_nodes_from((n, B.nodes[n]) for n in nodes) | |
n_top = len(B) - len(nodes) | |
if n_top < 1: | |
raise NetworkXAlgorithmError( | |
f"the size of the nodes to project onto ({len(nodes)}) is >= the graph size ({len(B)}).\n" | |
"They are either not a valid bipartite partition or contain duplicates" | |
) | |
for u in nodes: | |
unbrs = set(B[u]) | |
nbrs2 = {n for nbr in unbrs for n in B[nbr]} - {u} | |
for v in nbrs2: | |
vnbrs = set(pred[v]) | |
common = unbrs & vnbrs | |
if not ratio: | |
weight = len(common) | |
else: | |
weight = len(common) / n_top | |
G.add_edge(u, v, weight=weight) | |
return G | |
def collaboration_weighted_projected_graph(B, nodes): | |
r"""Newman's weighted projection of B onto one of its node sets. | |
The collaboration weighted projection is the projection of the | |
bipartite network B onto the specified nodes with weights assigned | |
using Newman's collaboration model [1]_: | |
.. math:: | |
w_{u, v} = \sum_k \frac{\delta_{u}^{k} \delta_{v}^{k}}{d_k - 1} | |
where `u` and `v` are nodes from the bottom bipartite node set, | |
and `k` is a node of the top node set. | |
The value `d_k` is the degree of node `k` in the bipartite | |
network and `\delta_{u}^{k}` is 1 if node `u` is | |
linked to node `k` in the original bipartite graph or 0 otherwise. | |
The nodes retain their attributes and are connected in the resulting | |
graph if have an edge to a common node in the original bipartite | |
graph. | |
Parameters | |
---------- | |
B : NetworkX graph | |
The input graph should be bipartite. | |
nodes : list or iterable | |
Nodes to project onto (the "bottom" nodes). | |
Returns | |
------- | |
Graph : NetworkX graph | |
A graph that is the projection onto the given nodes. | |
Examples | |
-------- | |
>>> from networkx.algorithms import bipartite | |
>>> B = nx.path_graph(5) | |
>>> B.add_edge(1, 5) | |
>>> G = bipartite.collaboration_weighted_projected_graph(B, [0, 2, 4, 5]) | |
>>> list(G) | |
[0, 2, 4, 5] | |
>>> for edge in sorted(G.edges(data=True)): | |
... print(edge) | |
... | |
(0, 2, {'weight': 0.5}) | |
(0, 5, {'weight': 0.5}) | |
(2, 4, {'weight': 1.0}) | |
(2, 5, {'weight': 0.5}) | |
Notes | |
----- | |
No attempt is made to verify that the input graph B is bipartite. | |
The graph and node properties are (shallow) copied to the projected graph. | |
See :mod:`bipartite documentation <networkx.algorithms.bipartite>` | |
for further details on how bipartite graphs are handled in NetworkX. | |
See Also | |
-------- | |
is_bipartite, | |
is_bipartite_node_set, | |
sets, | |
weighted_projected_graph, | |
overlap_weighted_projected_graph, | |
generic_weighted_projected_graph, | |
projected_graph | |
References | |
---------- | |
.. [1] Scientific collaboration networks: II. | |
Shortest paths, weighted networks, and centrality, | |
M. E. J. Newman, Phys. Rev. E 64, 016132 (2001). | |
""" | |
if B.is_directed(): | |
pred = B.pred | |
G = nx.DiGraph() | |
else: | |
pred = B.adj | |
G = nx.Graph() | |
G.graph.update(B.graph) | |
G.add_nodes_from((n, B.nodes[n]) for n in nodes) | |
for u in nodes: | |
unbrs = set(B[u]) | |
nbrs2 = {n for nbr in unbrs for n in B[nbr] if n != u} | |
for v in nbrs2: | |
vnbrs = set(pred[v]) | |
common_degree = (len(B[n]) for n in unbrs & vnbrs) | |
weight = sum(1.0 / (deg - 1) for deg in common_degree if deg > 1) | |
G.add_edge(u, v, weight=weight) | |
return G | |
def overlap_weighted_projected_graph(B, nodes, jaccard=True): | |
r"""Overlap weighted projection of B onto one of its node sets. | |
The overlap weighted projection is the projection of the bipartite | |
network B onto the specified nodes with weights representing | |
the Jaccard index between the neighborhoods of the two nodes in the | |
original bipartite network [1]_: | |
.. math:: | |
w_{v, u} = \frac{|N(u) \cap N(v)|}{|N(u) \cup N(v)|} | |
or if the parameter 'jaccard' is False, the fraction of common | |
neighbors by minimum of both nodes degree in the original | |
bipartite graph [1]_: | |
.. math:: | |
w_{v, u} = \frac{|N(u) \cap N(v)|}{min(|N(u)|, |N(v)|)} | |
The nodes retain their attributes and are connected in the resulting | |
graph if have an edge to a common node in the original bipartite graph. | |
Parameters | |
---------- | |
B : NetworkX graph | |
The input graph should be bipartite. | |
nodes : list or iterable | |
Nodes to project onto (the "bottom" nodes). | |
jaccard: Bool (default=True) | |
Returns | |
------- | |
Graph : NetworkX graph | |
A graph that is the projection onto the given nodes. | |
Examples | |
-------- | |
>>> from networkx.algorithms import bipartite | |
>>> B = nx.path_graph(5) | |
>>> nodes = [0, 2, 4] | |
>>> G = bipartite.overlap_weighted_projected_graph(B, nodes) | |
>>> list(G) | |
[0, 2, 4] | |
>>> list(G.edges(data=True)) | |
[(0, 2, {'weight': 0.5}), (2, 4, {'weight': 0.5})] | |
>>> G = bipartite.overlap_weighted_projected_graph(B, nodes, jaccard=False) | |
>>> list(G.edges(data=True)) | |
[(0, 2, {'weight': 1.0}), (2, 4, {'weight': 1.0})] | |
Notes | |
----- | |
No attempt is made to verify that the input graph B is bipartite. | |
The graph and node properties are (shallow) copied to the projected graph. | |
See :mod:`bipartite documentation <networkx.algorithms.bipartite>` | |
for further details on how bipartite graphs are handled in NetworkX. | |
See Also | |
-------- | |
is_bipartite, | |
is_bipartite_node_set, | |
sets, | |
weighted_projected_graph, | |
collaboration_weighted_projected_graph, | |
generic_weighted_projected_graph, | |
projected_graph | |
References | |
---------- | |
.. [1] Borgatti, S.P. and Halgin, D. In press. Analyzing Affiliation | |
Networks. In Carrington, P. and Scott, J. (eds) The Sage Handbook | |
of Social Network Analysis. Sage Publications. | |
""" | |
if B.is_directed(): | |
pred = B.pred | |
G = nx.DiGraph() | |
else: | |
pred = B.adj | |
G = nx.Graph() | |
G.graph.update(B.graph) | |
G.add_nodes_from((n, B.nodes[n]) for n in nodes) | |
for u in nodes: | |
unbrs = set(B[u]) | |
nbrs2 = {n for nbr in unbrs for n in B[nbr]} - {u} | |
for v in nbrs2: | |
vnbrs = set(pred[v]) | |
if jaccard: | |
wt = len(unbrs & vnbrs) / len(unbrs | vnbrs) | |
else: | |
wt = len(unbrs & vnbrs) / min(len(unbrs), len(vnbrs)) | |
G.add_edge(u, v, weight=wt) | |
return G | |
def generic_weighted_projected_graph(B, nodes, weight_function=None): | |
r"""Weighted projection of B with a user-specified weight function. | |
The bipartite network B is projected on to the specified nodes | |
with weights computed by a user-specified function. This function | |
must accept as a parameter the neighborhood sets of two nodes and | |
return an integer or a float. | |
The nodes retain their attributes and are connected in the resulting graph | |
if they have an edge to a common node in the original graph. | |
Parameters | |
---------- | |
B : NetworkX graph | |
The input graph should be bipartite. | |
nodes : list or iterable | |
Nodes to project onto (the "bottom" nodes). | |
weight_function : function | |
This function must accept as parameters the same input graph | |
that this function, and two nodes; and return an integer or a float. | |
The default function computes the number of shared neighbors. | |
Returns | |
------- | |
Graph : NetworkX graph | |
A graph that is the projection onto the given nodes. | |
Examples | |
-------- | |
>>> from networkx.algorithms import bipartite | |
>>> # Define some custom weight functions | |
>>> def jaccard(G, u, v): | |
... unbrs = set(G[u]) | |
... vnbrs = set(G[v]) | |
... return float(len(unbrs & vnbrs)) / len(unbrs | vnbrs) | |
... | |
>>> def my_weight(G, u, v, weight="weight"): | |
... w = 0 | |
... for nbr in set(G[u]) & set(G[v]): | |
... w += G[u][nbr].get(weight, 1) + G[v][nbr].get(weight, 1) | |
... return w | |
... | |
>>> # A complete bipartite graph with 4 nodes and 4 edges | |
>>> B = nx.complete_bipartite_graph(2, 2) | |
>>> # Add some arbitrary weight to the edges | |
>>> for i, (u, v) in enumerate(B.edges()): | |
... B.edges[u, v]["weight"] = i + 1 | |
... | |
>>> for edge in B.edges(data=True): | |
... print(edge) | |
... | |
(0, 2, {'weight': 1}) | |
(0, 3, {'weight': 2}) | |
(1, 2, {'weight': 3}) | |
(1, 3, {'weight': 4}) | |
>>> # By default, the weight is the number of shared neighbors | |
>>> G = bipartite.generic_weighted_projected_graph(B, [0, 1]) | |
>>> print(list(G.edges(data=True))) | |
[(0, 1, {'weight': 2})] | |
>>> # To specify a custom weight function use the weight_function parameter | |
>>> G = bipartite.generic_weighted_projected_graph( | |
... B, [0, 1], weight_function=jaccard | |
... ) | |
>>> print(list(G.edges(data=True))) | |
[(0, 1, {'weight': 1.0})] | |
>>> G = bipartite.generic_weighted_projected_graph( | |
... B, [0, 1], weight_function=my_weight | |
... ) | |
>>> print(list(G.edges(data=True))) | |
[(0, 1, {'weight': 10})] | |
Notes | |
----- | |
No attempt is made to verify that the input graph B is bipartite. | |
The graph and node properties are (shallow) copied to the projected graph. | |
See :mod:`bipartite documentation <networkx.algorithms.bipartite>` | |
for further details on how bipartite graphs are handled in NetworkX. | |
See Also | |
-------- | |
is_bipartite, | |
is_bipartite_node_set, | |
sets, | |
weighted_projected_graph, | |
collaboration_weighted_projected_graph, | |
overlap_weighted_projected_graph, | |
projected_graph | |
""" | |
if B.is_directed(): | |
pred = B.pred | |
G = nx.DiGraph() | |
else: | |
pred = B.adj | |
G = nx.Graph() | |
if weight_function is None: | |
def weight_function(G, u, v): | |
# Notice that we use set(pred[v]) for handling the directed case. | |
return len(set(G[u]) & set(pred[v])) | |
G.graph.update(B.graph) | |
G.add_nodes_from((n, B.nodes[n]) for n in nodes) | |
for u in nodes: | |
nbrs2 = {n for nbr in set(B[u]) for n in B[nbr]} - {u} | |
for v in nbrs2: | |
weight = weight_function(B, u, v) | |
G.add_edge(u, v, weight=weight) | |
return G | |