Repository: networkx/networkx

Issue:
Incorrect formula for collaboration_weighted_projected_graph
The formula given in `collaboration_weighted_projected_graph` makes little sense. For reference, the one in the paper by Newman is:

`w_{i, j} = \sum_k \frac{\delta_{i}^{k} \delta_{j}^{k}}{n_k - 1}`

where `i` and `j` are nodes from the "bottom" node set (the set of nodes to project onto), and `k` is a node of the "top" node set. The value `n_k` is the degree of node `k` in the bipartite network and `delta_{i}^{k}` is 1 if node `i` is linked to node `k` in the original bipartite graph or 0 otherwise.

It is probably also worth mentioning explicitly that "top" nodes with degree <= 1 are excluded.


Discussion:
For reference: http://networkx.readthedocs.io/en/stable/reference/generated/networkx.algorithms.bipartite.projection.collaboration_weighted_projected_graph.html?highlight=collaboration_weighted_projected_graph with `i = v`, `j = w`.  It seems that the first delta should have superscript `k` and the denominator should be `k` (or `n^k`), but not `k_w`.

To me the code looks correct, however.  Can someone confirm?

When fixing this, someone should apply PEP8 and add the decorator.  I bet we can also gain some speed by computing a degree dictionary once rather than twice in each iteration.

Resolving patch:
diff --git a/networkx/algorithms/bipartite/projection.py b/networkx/algorithms/bipartite/projection.py
--- a/networkx/algorithms/bipartite/projection.py
+++ b/networkx/algorithms/bipartite/projection.py
@@ -1,15 +1,17 @@
 # -*- coding: utf-8 -*-
-"""One-mode (unipartite) projections of bipartite graphs.
-"""
-import networkx as nx
-#    Copyright (C) 2011 by 
+#    Copyright (C) 2017 by
 #    Aric Hagberg <hagberg@lanl.gov>
 #    Dan Schult <dschult@colgate.edu>
 #    Pieter Swart <swart@lanl.gov>
 #    All rights reserved.
 #    BSD license.
-__author__ = """\n""".join(['Aric Hagberg <aric.hagberg@gmail.com>',
-                            'Jordi Torrents <jtorrents@milnou.net>'])
+#
+# Authors: Aric Hagberg <aric.hagberg@gmail.com>
+#          Jordi Torrents <jtorrents@milnou.net>
+"""One-mode (unipartite) projections of bipartite graphs."""
+import networkx as nx
+from networkx.utils import not_implemented_for
+
 __all__ = ['project',
            'projected_graph',
            'weighted_projected_graph',
@@ -17,17 +19,18 @@
            '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 
+    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. 
+    B : NetworkX graph
+      The input graph should be bipartite.
 
     nodes : list or iterable
       Nodes to project onto (the "bottom" nodes).
@@ -46,20 +49,20 @@ def projected_graph(B, nodes, multigraph=False):
     --------
     >>> from networkx.algorithms import bipartite
     >>> B = nx.path_graph(4)
-    >>> G = bipartite.projected_graph(B, [1,3]) 
+    >>> 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`]:
+    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()])
+    >>> print([sorted((u, v)) for u, v in G.edges()])
     [['a', 'b'], ['a', 'b']]
 
     Notes
@@ -80,9 +83,9 @@ def projected_graph(B, nodes, multigraph=False):
 
     See Also
     --------
-    is_bipartite, 
-    is_bipartite_node_set, 
-    sets, 
+    is_bipartite,
+    is_bipartite_node_set,
+    sets,
     weighted_projected_graph,
     collaboration_weighted_projected_graph,
     overlap_weighted_projected_graph,
@@ -91,75 +94,77 @@ def projected_graph(B, nodes, multigraph=False):
     if B.is_multigraph():
         raise nx.NetworkXError("not defined for multigraphs")
     if B.is_directed():
-        directed=True
+        directed = True
         if multigraph:
-            G=nx.MultiDiGraph()
+            G = nx.MultiDiGraph()
         else:
-            G=nx.DiGraph()
+            G = nx.DiGraph()
     else:
-        directed=False
+        directed = False
         if multigraph:
-            G=nx.MultiGraph()
+            G = nx.MultiGraph()
         else:
-            G=nx.Graph()
+            G = nx.Graph()
     G.graph.update(B.graph)
-    G.add_nodes_from((n,B.node[n]) for n in nodes)
+    G.add_nodes_from((n, B.node[n]) for n in nodes)
     for u in nodes:
-        nbrs2=set((v for nbr in B[u] for v in B[nbr])) -set([u])
+        nbrs2 = set(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])
+                    links = set(B[u]) & set(B.pred[n])
                 else:
-                    links=set(B[u]) & set(B[n])
+                    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)
+                    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)
+            G.add_edges_from((u, n) for n in nbrs2)
     return G
 
+
+@not_implemented_for('multigraph')
 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=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.
+    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. 
+    B : NetworkX graph
+        The input graph should be bipartite.
 
     nodes : list or iterable
         Nodes to project onto (the "bottom" nodes).
 
     ratio: Bool (default=False)
-        If True, edge weight is the ratio between actual shared neighbors 
-        and possible shared neighbors. If False, edges weight is the number 
+        If True, edge weight is the ratio between actual shared neighbors
+        and possible shared neighbors. If False, edges weight is the number
         of shared neighbors.
 
     Returns
     -------
-    Graph : NetworkX graph 
+    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])
+    >>> 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)
+    >>> 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.
@@ -170,30 +175,28 @@ def weighted_projected_graph(B, nodes, ratio=False):
 
     See Also
     --------
-    is_bipartite, 
-    is_bipartite_node_set, 
-    sets, 
+    is_bipartite,
+    is_bipartite_node_set,
+    sets,
     collaboration_weighted_projected_graph,
     overlap_weighted_projected_graph,
     generic_weighted_projected_graph
-    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 
+    .. [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_multigraph():
-        raise nx.NetworkXError("not defined for multigraphs")
     if B.is_directed():
-        pred=B.pred
-        G=nx.DiGraph()
+        pred = B.pred
+        G = nx.DiGraph()
     else:
-        pred=B.adj
-        G=nx.Graph()
+        pred = B.adj
+        G = nx.Graph()
     G.graph.update(B.graph)
-    G.add_nodes_from((n,B.node[n]) for n in nodes)
+    G.add_nodes_from((n, B.node[n]) for n in nodes)
     n_top = float(len(B) - len(nodes))
     for u in nodes:
         unbrs = set(B[u])
@@ -205,9 +208,11 @@ def weighted_projected_graph(B, nodes, ratio=False):
                 weight = len(common)
             else:
                 weight = len(common) / n_top
-            G.add_edge(u,v,weight=weight)
+            G.add_edge(u, v, weight=weight)
     return G
 
+
+@not_implemented_for('multigraph')
 def collaboration_weighted_projected_graph(B, nodes):
     r"""Newman's weighted projection of B onto one of its node sets.
 
@@ -216,47 +221,47 @@ def collaboration_weighted_projected_graph(B, nodes):
     using Newman's collaboration model [1]_:
 
     .. math::
-        
-        w_{v,u} = \sum_k \frac{\delta_{v}^{w} \delta_{w}^{k}}{k_w - 1}
-
-    where `v` and `u` are nodes from the same bipartite node set,
-    and `w` is a node of the opposite node set. 
-    The value `k_w` is the degree of node `w` in the bipartite
-    network and `\delta_{v}^{w}` is 1 if node `v` is
-    linked to node `w` in the original bipartite graph or 0 otherwise.
- 
+
+        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. 
+    B : NetworkX graph
+      The input graph should be bipartite.
 
     nodes : list or iterable
       Nodes to project onto (the "bottom" nodes).
 
     Returns
     -------
-    Graph : NetworkX graph 
+    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)
+    >>> 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 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.
@@ -267,67 +272,67 @@ def collaboration_weighted_projected_graph(B, nodes):
 
     See Also
     --------
-    is_bipartite, 
-    is_bipartite_node_set, 
-    sets, 
+    is_bipartite,
+    is_bipartite_node_set,
+    sets,
     weighted_projected_graph,
     overlap_weighted_projected_graph,
     generic_weighted_projected_graph,
-    projected_graph 
+    projected_graph
 
     References
     ----------
-    .. [1] Scientific collaboration networks: II. 
-        Shortest paths, weighted networks, and centrality, 
+    .. [1] Scientific collaboration networks: II.
+        Shortest paths, weighted networks, and centrality,
         M. E. J. Newman, Phys. Rev. E 64, 016132 (2001).
     """
-    if B.is_multigraph():
-        raise nx.NetworkXError("not defined for multigraphs")
     if B.is_directed():
-        pred=B.pred
-        G=nx.DiGraph()
+        pred = B.pred
+        G = nx.DiGraph()
     else:
-        pred=B.adj
-        G=nx.Graph()
+        pred = B.adj
+        G = nx.Graph()
     G.graph.update(B.graph)
-    G.add_nodes_from((n,B.node[n]) for n in nodes)
+    G.add_nodes_from((n, B.node[n]) for n in nodes)
     for u in nodes:
         unbrs = set(B[u])
-        nbrs2 = set((n for nbr in unbrs for n in B[nbr])) - set([u])
+        nbrs2 = set(n for nbr in unbrs for n in B[nbr] if n != u)
         for v in nbrs2:
             vnbrs = set(pred[v])
-            common = unbrs & vnbrs
-            weight = sum([1.0/(len(B[n]) - 1) for n in common if len(B[n])>1])
-            G.add_edge(u,v,weight=weight)
+            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
 
+
+@not_implemented_for('multigraph')
 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 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]_: 
+    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 
+        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)|)}
-    
+        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. 
+    B : NetworkX graph
+        The input graph should be bipartite.
 
     nodes : list or iterable
         Nodes to project onto (the "bottom" nodes).
@@ -336,22 +341,23 @@ def overlap_weighted_projected_graph(B, nodes, jaccard=True):
 
     Returns
     -------
-    Graph : NetworkX graph 
+    Graph : NetworkX graph
        A graph that is the projection onto the given nodes.
 
     Examples
     --------
     >>> from networkx.algorithms import bipartite
     >>> B = nx.path_graph(5)
-    >>> G = bipartite.overlap_weighted_projected_graph(B, [0, 2, 4])
+    >>> 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, [0, 2, 4], jaccard=False)
+    >>> 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.
@@ -362,43 +368,43 @@ def overlap_weighted_projected_graph(B, nodes, jaccard=True):
 
     See Also
     --------
-    is_bipartite, 
-    is_bipartite_node_set, 
-    sets, 
+    is_bipartite,
+    is_bipartite_node_set,
+    sets,
     weighted_projected_graph,
     collaboration_weighted_projected_graph,
     generic_weighted_projected_graph,
-    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 
+    .. [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_multigraph():
-        raise nx.NetworkXError("not defined for multigraphs")
     if B.is_directed():
-        pred=B.pred
-        G=nx.DiGraph()
+        pred = B.pred
+        G = nx.DiGraph()
     else:
-        pred=B.adj
-        G=nx.Graph()
+        pred = B.adj
+        G = nx.Graph()
     G.graph.update(B.graph)
-    G.add_nodes_from((n,B.node[n]) for n in nodes)
+    G.add_nodes_from((n, B.node[n]) for n in nodes)
     for u in nodes:
         unbrs = set(B[u])
         nbrs2 = set((n for nbr in unbrs for n in B[nbr])) - set([u])
         for v in nbrs2:
             vnbrs = set(pred[v])
             if jaccard:
-                weight = float(len(unbrs & vnbrs)) / len(unbrs | vnbrs)
+                wt = float(len(unbrs & vnbrs)) / len(unbrs | vnbrs)
             else:
-                weight = float(len(unbrs & vnbrs)) / min(len(unbrs),len(vnbrs))
-            G.add_edge(u,v,weight=weight)
+                wt = float(len(unbrs & vnbrs)) / min(len(unbrs), len(vnbrs))
+            G.add_edge(u, v, weight=wt)
     return G
 
+
+@not_implemented_for('multigraph')
 def generic_weighted_projected_graph(B, nodes, weight_function=None):
     r"""Weighted projection of B with a user-specified weight function.
 
@@ -407,25 +413,25 @@ def generic_weighted_projected_graph(B, nodes, weight_function=None):
     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 
+    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. 
+    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 
+    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 
+    Graph : NetworkX graph
        A graph that is the projection onto the given nodes.
 
     Examples
@@ -436,27 +442,27 @@ def generic_weighted_projected_graph(B, nodes, weight_function=None):
     ...     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.edge[u][nbr].get(weight, 1) + G.edge[v][nbr].get(weight, 1)
+    ...         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)
+    >>> B = nx.complete_bipartite_graph(2, 2)
     >>> # Add some arbitrary weight to the edges
-    >>> for i,(u,v) in enumerate(B.edges()):
+    >>> for i, (u, v) in enumerate(B.edges()):
     ...     B.edge[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})
-    >>> # Without specifying a function, the weight is equal to # shared partners
+    >>> # 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})]
@@ -467,7 +473,7 @@ def generic_weighted_projected_graph(B, nodes, weight_function=None):
     >>> 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.
@@ -478,34 +484,32 @@ def generic_weighted_projected_graph(B, nodes, weight_function=None):
 
     See Also
     --------
-    is_bipartite, 
-    is_bipartite_node_set, 
-    sets, 
+    is_bipartite,
+    is_bipartite_node_set,
+    sets,
     weighted_projected_graph,
     collaboration_weighted_projected_graph,
     overlap_weighted_projected_graph,
-    projected_graph 
+    projected_graph
 
     """
-    if B.is_multigraph():
-        raise nx.NetworkXError("not defined for multigraphs")
     if B.is_directed():
-        pred=B.pred
-        G=nx.DiGraph()
+        pred = B.pred
+        G = nx.DiGraph()
     else:
-        pred=B.adj
-        G=nx.Graph()
+        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.node[n]) for n in nodes)
+    G.add_nodes_from((n, B.node[n]) for n in nodes)
     for u in nodes:
         nbrs2 = set((n for nbr in set(B[u]) for n in B[nbr])) - set([u])
         for v in nbrs2:
             weight = weight_function(B, u, v)
-            G.add_edge(u,v,weight=weight)
+            G.add_edge(u, v, weight=weight)
     return G
 
 def project(B, nodes, create_using=None):


Test patch: