Repository: networkx/networkx

Issue:
Properly handle edge cases for greedy_modularity_communities
Currently we don't do a good job of catching edge cases like empty graph, no edges, 3 nodes 1 edge:
``` python
>>> greedy_modularity_communities(G) # empty graph
....
....
ValueError: cutoff must be between 1 and 0. Got 1.

>>> greedy_modularity_communities(G) # 3 nodes 1 edge
.....
.....
StopIteration:

>>>  greedy_modularity_communities(G) # no edges
....
....
ZeroDivisionError: division by zero
```

We should catch them and error out more gracefully.


Discussion:
Hi Mridul,

I would like to work on the issue, plus its visualization. Is this the desired behavior? 

Instead of ValueError, printing 'This is an empty graph.' 
Instead of StopIteration, printing 'Only one edge.' 
Instead of ZeroDivisionError, print 'No edges.'

Thanks.
I think the middle error was fixed in #5550 recently.  So that’s helpful. ;}

The other two cases could maybe raise a NetworkXPointlessConcept exception explaining that there are no edges upon which to build communities.  
But I’d prefer that we return an object that makes sense for the request rather than raise an exception.  That way when people apply this function to a large collection of graphs, and one happens to e.g. have no edges, it doesn’t break the process with an exception.  I know they can just use try/except. But it’s probably better for them to just get an empty list back…   Thoughts?

The first and 3rd case can be handled as one case by testing for `G.size() == 0` in which case my suggestion is to return `[{n} for n in G]`. That would be an empty list for the null graph, and each node in its own community for the no edges case. I think these make sense…???    We’ll need tests too, of course.
>  But I’d prefer that we return an object that makes sense for the request rather than raise an exception. That way when people apply this function to a large collection of graphs, and one happens to e.g. have no edges, it doesn’t break the process with an exception. I know they can just use try/except. But it’s probably better for them to just get an empty list back… Thoughts?

I agree that this behavior is preferable from a usability standpoint :+1: 

> The first and 3rd case can be handled as one case by testing for G.size() == 0 in which case my suggestion is to return [{n} for n in G]. That would be an empty list for the null graph, and each node in its own community for the no edges case. I think these make sense…???

I like this idea, assuming it makes sense in the context of what constitutes a "community" - I defer to the experts! @z3y50n does this sound reasonable to you?
The `ZeroDivisionError` also happens (of course) when inputting a graph without edges into `networkx.algorithms.community.quality.modularity`.


Resolving patch:
diff --git a/networkx/algorithms/community/modularity_max.py b/networkx/algorithms/community/modularity_max.py
--- a/networkx/algorithms/community/modularity_max.py
+++ b/networkx/algorithms/community/modularity_max.py
@@ -309,6 +309,9 @@ def greedy_modularity_communities(
     .. [4] Newman, M. E. J."Analysis of weighted networks"
        Physical Review E 70(5 Pt 2):056131, 2004.
     """
+    if not G.size():
+        return [{n} for n in G]
+
     if (cutoff < 1) or (cutoff > G.number_of_nodes()):
         raise ValueError(f"cutoff must be between 1 and {len(G)}. Got {cutoff}.")
     if best_n is not None:


Test patch:
diff --git a/networkx/algorithms/community/tests/test_modularity_max.py b/networkx/algorithms/community/tests/test_modularity_max.py
--- a/networkx/algorithms/community/tests/test_modularity_max.py
+++ b/networkx/algorithms/community/tests/test_modularity_max.py
@@ -331,3 +331,10 @@ def test_best_n():
     best_n = 1
     expected = [frozenset(range(13))]
     assert greedy_modularity_communities(G, best_n=best_n) == expected
+
+
+def test_greedy_modularity_communities_corner_cases():
+    G = nx.empty_graph()
+    assert nx.community.greedy_modularity_communities(G) == []
+    G.add_nodes_from(range(3))
+    assert nx.community.greedy_modularity_communities(G) == [{0}, {1}, {2}]