projection.py 17 KB

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