projection.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523
  1. """One-mode (unipartite) projections of bipartite graphs."""
  2. import networkx as nx
  3. from networkx.exception import NetworkXAlgorithmError
  4. from networkx.utils import not_implemented_for
  5. __all__ = [
  6. "projected_graph",
  7. "weighted_projected_graph",
  8. "collaboration_weighted_projected_graph",
  9. "overlap_weighted_projected_graph",
  10. "generic_weighted_projected_graph",
  11. ]
  12. def projected_graph(B, nodes, multigraph=False):
  13. r"""Returns the projection of B onto one of its node sets.
  14. Returns the graph G that is the projection of the bipartite graph B
  15. onto the specified nodes. They retain their attributes and are connected
  16. in G if they have a common neighbor in B.
  17. Parameters
  18. ----------
  19. B : NetworkX graph
  20. The input graph should be bipartite.
  21. nodes : list or iterable
  22. Nodes to project onto (the "bottom" nodes).
  23. multigraph: bool (default=False)
  24. If True return a multigraph where the multiple edges represent multiple
  25. shared neighbors. They edge key in the multigraph is assigned to the
  26. label of the neighbor.
  27. Returns
  28. -------
  29. Graph : NetworkX graph or multigraph
  30. A graph that is the projection onto the given nodes.
  31. Examples
  32. --------
  33. >>> from networkx.algorithms import bipartite
  34. >>> B = nx.path_graph(4)
  35. >>> G = bipartite.projected_graph(B, [1, 3])
  36. >>> list(G)
  37. [1, 3]
  38. >>> list(G.edges())
  39. [(1, 3)]
  40. If nodes `a`, and `b` are connected through both nodes 1 and 2 then
  41. building a multigraph results in two edges in the projection onto
  42. [`a`, `b`]:
  43. >>> B = nx.Graph()
  44. >>> B.add_edges_from([("a", 1), ("b", 1), ("a", 2), ("b", 2)])
  45. >>> G = bipartite.projected_graph(B, ["a", "b"], multigraph=True)
  46. >>> print([sorted((u, v)) for u, v in G.edges()])
  47. [['a', 'b'], ['a', 'b']]
  48. Notes
  49. -----
  50. No attempt is made to verify that the input graph B is bipartite.
  51. Returns a simple graph that is the projection of the bipartite graph B
  52. onto the set of nodes given in list nodes. If multigraph=True then
  53. a multigraph is returned with an edge for every shared neighbor.
  54. Directed graphs are allowed as input. The output will also then
  55. be a directed graph with edges if there is a directed path between
  56. the nodes.
  57. The graph and node properties are (shallow) copied to the projected graph.
  58. See :mod:`bipartite documentation <networkx.algorithms.bipartite>`
  59. for further details on how bipartite graphs are handled in NetworkX.
  60. See Also
  61. --------
  62. is_bipartite,
  63. is_bipartite_node_set,
  64. sets,
  65. weighted_projected_graph,
  66. collaboration_weighted_projected_graph,
  67. overlap_weighted_projected_graph,
  68. generic_weighted_projected_graph
  69. """
  70. if B.is_multigraph():
  71. raise nx.NetworkXError("not defined for multigraphs")
  72. if B.is_directed():
  73. directed = True
  74. if multigraph:
  75. G = nx.MultiDiGraph()
  76. else:
  77. G = nx.DiGraph()
  78. else:
  79. directed = False
  80. if multigraph:
  81. G = nx.MultiGraph()
  82. else:
  83. G = nx.Graph()
  84. G.graph.update(B.graph)
  85. G.add_nodes_from((n, B.nodes[n]) for n in nodes)
  86. for u in nodes:
  87. nbrs2 = {v for nbr in B[u] for v in B[nbr] if v != u}
  88. if multigraph:
  89. for n in nbrs2:
  90. if directed:
  91. links = set(B[u]) & set(B.pred[n])
  92. else:
  93. links = set(B[u]) & set(B[n])
  94. for l in links:
  95. if not G.has_edge(u, n, l):
  96. G.add_edge(u, n, key=l)
  97. else:
  98. G.add_edges_from((u, n) for n in nbrs2)
  99. return G
  100. @not_implemented_for("multigraph")
  101. def weighted_projected_graph(B, nodes, ratio=False):
  102. r"""Returns a weighted projection of B onto one of its node sets.
  103. The weighted projected graph is the projection of the bipartite
  104. network B onto the specified nodes with weights representing the
  105. number of shared neighbors or the ratio between actual shared
  106. neighbors and possible shared neighbors if ``ratio is True`` [1]_.
  107. The nodes retain their attributes and are connected in the resulting
  108. graph if they have an edge to a common node in the original graph.
  109. Parameters
  110. ----------
  111. B : NetworkX graph
  112. The input graph should be bipartite.
  113. nodes : list or iterable
  114. Distinct nodes to project onto (the "bottom" nodes).
  115. ratio: Bool (default=False)
  116. If True, edge weight is the ratio between actual shared neighbors
  117. and maximum possible shared neighbors (i.e., the size of the other
  118. node set). If False, edges weight is the number of shared neighbors.
  119. Returns
  120. -------
  121. Graph : NetworkX graph
  122. A graph that is the projection onto the given nodes.
  123. Examples
  124. --------
  125. >>> from networkx.algorithms import bipartite
  126. >>> B = nx.path_graph(4)
  127. >>> G = bipartite.weighted_projected_graph(B, [1, 3])
  128. >>> list(G)
  129. [1, 3]
  130. >>> list(G.edges(data=True))
  131. [(1, 3, {'weight': 1})]
  132. >>> G = bipartite.weighted_projected_graph(B, [1, 3], ratio=True)
  133. >>> list(G.edges(data=True))
  134. [(1, 3, {'weight': 0.5})]
  135. Notes
  136. -----
  137. No attempt is made to verify that the input graph B is bipartite, or that
  138. the input nodes are distinct. However, if the length of the input nodes is
  139. greater than or equal to the nodes in the graph B, an exception is raised.
  140. If the nodes are not distinct but don't raise this error, the output weights
  141. will be incorrect.
  142. The graph and node properties are (shallow) copied to the projected graph.
  143. See :mod:`bipartite documentation <networkx.algorithms.bipartite>`
  144. for further details on how bipartite graphs are handled in NetworkX.
  145. See Also
  146. --------
  147. is_bipartite,
  148. is_bipartite_node_set,
  149. sets,
  150. collaboration_weighted_projected_graph,
  151. overlap_weighted_projected_graph,
  152. generic_weighted_projected_graph
  153. projected_graph
  154. References
  155. ----------
  156. .. [1] Borgatti, S.P. and Halgin, D. In press. "Analyzing Affiliation
  157. Networks". In Carrington, P. and Scott, J. (eds) The Sage Handbook
  158. of Social Network Analysis. Sage Publications.
  159. """
  160. if B.is_directed():
  161. pred = B.pred
  162. G = nx.DiGraph()
  163. else:
  164. pred = B.adj
  165. G = nx.Graph()
  166. G.graph.update(B.graph)
  167. G.add_nodes_from((n, B.nodes[n]) for n in nodes)
  168. n_top = len(B) - len(nodes)
  169. if n_top < 1:
  170. raise NetworkXAlgorithmError(
  171. f"the size of the nodes to project onto ({len(nodes)}) is >= the graph size ({len(B)}).\n"
  172. "They are either not a valid bipartite partition or contain duplicates"
  173. )
  174. for u in nodes:
  175. unbrs = set(B[u])
  176. nbrs2 = {n for nbr in unbrs for n in B[nbr]} - {u}
  177. for v in nbrs2:
  178. vnbrs = set(pred[v])
  179. common = unbrs & vnbrs
  180. if not ratio:
  181. weight = len(common)
  182. else:
  183. weight = len(common) / n_top
  184. G.add_edge(u, v, weight=weight)
  185. return G
  186. @not_implemented_for("multigraph")
  187. def collaboration_weighted_projected_graph(B, nodes):
  188. r"""Newman's weighted projection of B onto one of its node sets.
  189. The collaboration weighted projection is the projection of the
  190. bipartite network B onto the specified nodes with weights assigned
  191. using Newman's collaboration model [1]_:
  192. .. math::
  193. w_{u, v} = \sum_k \frac{\delta_{u}^{k} \delta_{v}^{k}}{d_k - 1}
  194. where `u` and `v` are nodes from the bottom bipartite node set,
  195. and `k` is a node of the top node set.
  196. The value `d_k` is the degree of node `k` in the bipartite
  197. network and `\delta_{u}^{k}` is 1 if node `u` is
  198. linked to node `k` in the original bipartite graph or 0 otherwise.
  199. The nodes retain their attributes and are connected in the resulting
  200. graph if have an edge to a common node in the original bipartite
  201. graph.
  202. Parameters
  203. ----------
  204. B : NetworkX graph
  205. The input graph should be bipartite.
  206. nodes : list or iterable
  207. Nodes to project onto (the "bottom" nodes).
  208. Returns
  209. -------
  210. Graph : NetworkX graph
  211. A graph that is the projection onto the given nodes.
  212. Examples
  213. --------
  214. >>> from networkx.algorithms import bipartite
  215. >>> B = nx.path_graph(5)
  216. >>> B.add_edge(1, 5)
  217. >>> G = bipartite.collaboration_weighted_projected_graph(B, [0, 2, 4, 5])
  218. >>> list(G)
  219. [0, 2, 4, 5]
  220. >>> for edge in sorted(G.edges(data=True)):
  221. ... print(edge)
  222. ...
  223. (0, 2, {'weight': 0.5})
  224. (0, 5, {'weight': 0.5})
  225. (2, 4, {'weight': 1.0})
  226. (2, 5, {'weight': 0.5})
  227. Notes
  228. -----
  229. No attempt is made to verify that the input graph B is bipartite.
  230. The graph and node properties are (shallow) copied to the projected graph.
  231. See :mod:`bipartite documentation <networkx.algorithms.bipartite>`
  232. for further details on how bipartite graphs are handled in NetworkX.
  233. See Also
  234. --------
  235. is_bipartite,
  236. is_bipartite_node_set,
  237. sets,
  238. weighted_projected_graph,
  239. overlap_weighted_projected_graph,
  240. generic_weighted_projected_graph,
  241. projected_graph
  242. References
  243. ----------
  244. .. [1] Scientific collaboration networks: II.
  245. Shortest paths, weighted networks, and centrality,
  246. M. E. J. Newman, Phys. Rev. E 64, 016132 (2001).
  247. """
  248. if B.is_directed():
  249. pred = B.pred
  250. G = nx.DiGraph()
  251. else:
  252. pred = B.adj
  253. G = nx.Graph()
  254. G.graph.update(B.graph)
  255. G.add_nodes_from((n, B.nodes[n]) for n in nodes)
  256. for u in nodes:
  257. unbrs = set(B[u])
  258. nbrs2 = {n for nbr in unbrs for n in B[nbr] if n != u}
  259. for v in nbrs2:
  260. vnbrs = set(pred[v])
  261. common_degree = (len(B[n]) for n in unbrs & vnbrs)
  262. weight = sum(1.0 / (deg - 1) for deg in common_degree if deg > 1)
  263. G.add_edge(u, v, weight=weight)
  264. return G
  265. @not_implemented_for("multigraph")
  266. def overlap_weighted_projected_graph(B, nodes, jaccard=True):
  267. r"""Overlap weighted projection of B onto one of its node sets.
  268. The overlap weighted projection is the projection of the bipartite
  269. network B onto the specified nodes with weights representing
  270. the Jaccard index between the neighborhoods of the two nodes in the
  271. original bipartite network [1]_:
  272. .. math::
  273. w_{v, u} = \frac{|N(u) \cap N(v)|}{|N(u) \cup N(v)|}
  274. or if the parameter 'jaccard' is False, the fraction of common
  275. neighbors by minimum of both nodes degree in the original
  276. bipartite graph [1]_:
  277. .. math::
  278. w_{v, u} = \frac{|N(u) \cap N(v)|}{min(|N(u)|, |N(v)|)}
  279. The nodes retain their attributes and are connected in the resulting
  280. graph if have an edge to a common node in the original bipartite graph.
  281. Parameters
  282. ----------
  283. B : NetworkX graph
  284. The input graph should be bipartite.
  285. nodes : list or iterable
  286. Nodes to project onto (the "bottom" nodes).
  287. jaccard: Bool (default=True)
  288. Returns
  289. -------
  290. Graph : NetworkX graph
  291. A graph that is the projection onto the given nodes.
  292. Examples
  293. --------
  294. >>> from networkx.algorithms import bipartite
  295. >>> B = nx.path_graph(5)
  296. >>> nodes = [0, 2, 4]
  297. >>> G = bipartite.overlap_weighted_projected_graph(B, nodes)
  298. >>> list(G)
  299. [0, 2, 4]
  300. >>> list(G.edges(data=True))
  301. [(0, 2, {'weight': 0.5}), (2, 4, {'weight': 0.5})]
  302. >>> G = bipartite.overlap_weighted_projected_graph(B, nodes, jaccard=False)
  303. >>> list(G.edges(data=True))
  304. [(0, 2, {'weight': 1.0}), (2, 4, {'weight': 1.0})]
  305. Notes
  306. -----
  307. No attempt is made to verify that the input graph B is bipartite.
  308. The graph and node properties are (shallow) copied to the projected graph.
  309. See :mod:`bipartite documentation <networkx.algorithms.bipartite>`
  310. for further details on how bipartite graphs are handled in NetworkX.
  311. See Also
  312. --------
  313. is_bipartite,
  314. is_bipartite_node_set,
  315. sets,
  316. weighted_projected_graph,
  317. collaboration_weighted_projected_graph,
  318. generic_weighted_projected_graph,
  319. projected_graph
  320. References
  321. ----------
  322. .. [1] Borgatti, S.P. and Halgin, D. In press. Analyzing Affiliation
  323. Networks. In Carrington, P. and Scott, J. (eds) The Sage Handbook
  324. of Social Network Analysis. Sage Publications.
  325. """
  326. if B.is_directed():
  327. pred = B.pred
  328. G = nx.DiGraph()
  329. else:
  330. pred = B.adj
  331. G = nx.Graph()
  332. G.graph.update(B.graph)
  333. G.add_nodes_from((n, B.nodes[n]) for n in nodes)
  334. for u in nodes:
  335. unbrs = set(B[u])
  336. nbrs2 = {n for nbr in unbrs for n in B[nbr]} - {u}
  337. for v in nbrs2:
  338. vnbrs = set(pred[v])
  339. if jaccard:
  340. wt = len(unbrs & vnbrs) / len(unbrs | vnbrs)
  341. else:
  342. wt = len(unbrs & vnbrs) / min(len(unbrs), len(vnbrs))
  343. G.add_edge(u, v, weight=wt)
  344. return G
  345. @not_implemented_for("multigraph")
  346. def generic_weighted_projected_graph(B, nodes, weight_function=None):
  347. r"""Weighted projection of B with a user-specified weight function.
  348. The bipartite network B is projected on to the specified nodes
  349. with weights computed by a user-specified function. This function
  350. must accept as a parameter the neighborhood sets of two nodes and
  351. return an integer or a float.
  352. The nodes retain their attributes and are connected in the resulting graph
  353. if they have an edge to a common node in the original graph.
  354. Parameters
  355. ----------
  356. B : NetworkX graph
  357. The input graph should be bipartite.
  358. nodes : list or iterable
  359. Nodes to project onto (the "bottom" nodes).
  360. weight_function : function
  361. This function must accept as parameters the same input graph
  362. that this function, and two nodes; and return an integer or a float.
  363. The default function computes the number of shared neighbors.
  364. Returns
  365. -------
  366. Graph : NetworkX graph
  367. A graph that is the projection onto the given nodes.
  368. Examples
  369. --------
  370. >>> from networkx.algorithms import bipartite
  371. >>> # Define some custom weight functions
  372. >>> def jaccard(G, u, v):
  373. ... unbrs = set(G[u])
  374. ... vnbrs = set(G[v])
  375. ... return float(len(unbrs & vnbrs)) / len(unbrs | vnbrs)
  376. ...
  377. >>> def my_weight(G, u, v, weight="weight"):
  378. ... w = 0
  379. ... for nbr in set(G[u]) & set(G[v]):
  380. ... w += G[u][nbr].get(weight, 1) + G[v][nbr].get(weight, 1)
  381. ... return w
  382. ...
  383. >>> # A complete bipartite graph with 4 nodes and 4 edges
  384. >>> B = nx.complete_bipartite_graph(2, 2)
  385. >>> # Add some arbitrary weight to the edges
  386. >>> for i, (u, v) in enumerate(B.edges()):
  387. ... B.edges[u, v]["weight"] = i + 1
  388. ...
  389. >>> for edge in B.edges(data=True):
  390. ... print(edge)
  391. ...
  392. (0, 2, {'weight': 1})
  393. (0, 3, {'weight': 2})
  394. (1, 2, {'weight': 3})
  395. (1, 3, {'weight': 4})
  396. >>> # By default, the weight is the number of shared neighbors
  397. >>> G = bipartite.generic_weighted_projected_graph(B, [0, 1])
  398. >>> print(list(G.edges(data=True)))
  399. [(0, 1, {'weight': 2})]
  400. >>> # To specify a custom weight function use the weight_function parameter
  401. >>> G = bipartite.generic_weighted_projected_graph(
  402. ... B, [0, 1], weight_function=jaccard
  403. ... )
  404. >>> print(list(G.edges(data=True)))
  405. [(0, 1, {'weight': 1.0})]
  406. >>> G = bipartite.generic_weighted_projected_graph(
  407. ... B, [0, 1], weight_function=my_weight
  408. ... )
  409. >>> print(list(G.edges(data=True)))
  410. [(0, 1, {'weight': 10})]
  411. Notes
  412. -----
  413. No attempt is made to verify that the input graph B is bipartite.
  414. The graph and node properties are (shallow) copied to the projected graph.
  415. See :mod:`bipartite documentation <networkx.algorithms.bipartite>`
  416. for further details on how bipartite graphs are handled in NetworkX.
  417. See Also
  418. --------
  419. is_bipartite,
  420. is_bipartite_node_set,
  421. sets,
  422. weighted_projected_graph,
  423. collaboration_weighted_projected_graph,
  424. overlap_weighted_projected_graph,
  425. projected_graph
  426. """
  427. if B.is_directed():
  428. pred = B.pred
  429. G = nx.DiGraph()
  430. else:
  431. pred = B.adj
  432. G = nx.Graph()
  433. if weight_function is None:
  434. def weight_function(G, u, v):
  435. # Notice that we use set(pred[v]) for handling the directed case.
  436. return len(set(G[u]) & set(pred[v]))
  437. G.graph.update(B.graph)
  438. G.add_nodes_from((n, B.nodes[n]) for n in nodes)
  439. for u in nodes:
  440. nbrs2 = {n for nbr in set(B[u]) for n in B[nbr]} - {u}
  441. for v in nbrs2:
  442. weight = weight_function(B, u, v)
  443. G.add_edge(u, v, weight=weight)
  444. return G