Repository: networkx/networkx

Issue:
Matplotlib 3.1 deprecation warning (iterable function)
MatplotLib 3.1 generates the following warning:


```
. . . /lib/python3.7/site-packages/networkx/drawing/nx_pylab.py:579: MatplotlibDeprecationWarning: 
The iterable function was deprecated in Matplotlib 3.1 and will be removed in 3.3. Use np.iterable instead.
  if not cb.iterable(width):
. . . /lib/python3.7/site-packages/networkx/drawing/nx_pylab.py:676: MatplotlibDeprecationWarning: 
The iterable function was deprecated in Matplotlib 3.1 and will be removed in 3.3. Use np.iterable instead.
  if cb.iterable(node_size):  # many node sizes
```
networkx.draw() hides axes on different subplot
I try to have a graph visualization on one subplot and a line plot of some values on another subplot. However, after I draw the graph on the first subplot, axes on the other subplot disappear (although I did not allow any shared axes). And I cannot bring them back.
```
import networkx as nx
import matplotlib.pyplot as plt
fig = plt.figure()
ax1 = fig.add_subplot(211)
ax2 = fig.add_subplot(212)
G = nx.graph_atlas(1)
nx.draw(G, ax=ax1)
plt.show()
```
Results in `ax2` having the ticks for both axes hidden, whereas if I run the same code without `nx.draw(...)` line the ticks are visible on both subplots.

How is it possible that `nx.draw(G, ax=ax1)` affects `ax2`?



Discussion:

Resolving patch:
diff --git a/networkx/algorithms/core.py b/networkx/algorithms/core.py
--- a/networkx/algorithms/core.py
+++ b/networkx/algorithms/core.py
@@ -2,12 +2,14 @@
 #    Aric Hagberg <hagberg@lanl.gov>
 #    Dan Schult <dschult@colgate.edu>
 #    Pieter Swart <swart@lanl.gov>
+#    Antoine Allard <antoine.allard@phy.ulaval.ca>
 #    All rights reserved.
 #    BSD license.
 #
 # Authors: Dan Schult (dschult@colgate.edu)
 #          Jason Grout (jason-sage@creativetrax.com)
 #          Aric Hagberg (hagberg@lanl.gov)
+#          Antoine Allard (antoine.allard@phy.ulaval.ca)
 """
 Find the k-cores of a graph.
 
@@ -30,13 +32,20 @@
 D-cores: Measuring Collaboration of Directed Graphs Based on Degeneracy
 Christos Giatsidis, Dimitrios M. Thilikos, Michalis Vazirgiannis, ICDM 2011.
 http://www.graphdegeneracy.org/dcores_ICDM_2011.pdf
+
+Multi-scale structure and topological anomaly detection via a new network \
+statistic: The onion decomposition
+L. Hébert-Dufresne, J. A. Grochow, and A. Allard
+Scientific Reports 6, 31708 (2016)
+http://doi.org/10.1038/srep31708
+
 """
 import networkx as nx
 from networkx.exception import NetworkXError
 from networkx.utils import not_implemented_for
 
-__all__ = ['core_number', 'find_cores', 'k_core',
-           'k_shell', 'k_crust', 'k_corona']
+__all__ = ['core_number', 'find_cores', 'k_core', 'k_shell',
+           'k_crust', 'k_corona', 'k_truss', 'onion_layers']
 
 
 @not_implemented_for('multigraph')
@@ -353,3 +362,161 @@ def k_corona(G, k, core_number=None):
     def func(v, k, c):
         return c[v] == k and k == sum(1 for w in G[v] if c[w] >= k)
     return _core_subgraph(G, func, k, core_number)
+
+
+@not_implemented_for('directed')
+@not_implemented_for('multigraph')
+def k_truss(G, k):
+    """Returns the k-truss of `G`.
+
+    The k-truss is the maximal subgraph of `G` which contains at least three
+    vertices where every edge is incident to at least `k` triangles.
+
+    Parameters
+    ----------
+    G : NetworkX graph
+      An undirected graph
+    k : int
+      The order of the truss
+
+    Returns
+    -------
+    H : NetworkX graph
+      The k-truss subgraph
+
+    Raises
+    ------
+    NetworkXError
+
+      The k-truss is not defined for graphs with self loops or parallel edges
+      or directed graphs.
+
+    Notes
+    -----
+    A k-clique is a (k-2)-truss and a k-truss is a (k+1)-core.
+
+    Not implemented for digraphs or graphs with parallel edges or self loops.
+
+    Graph, node, and edge attributes are copied to the subgraph.
+
+    References
+    ----------
+    .. [1] Bounds and Algorithms for k-truss. Paul Burkhardt, Vance Faber,
+       David G. Harris, 2018. https://arxiv.org/abs/1806.05523v2
+    .. [2] Trusses: Cohesive Subgraphs for Social Network Analysis. Jonathan
+       Cohen, 2005.
+    """
+    H = G.copy()
+
+    n_dropped = 1
+    while n_dropped > 0:
+        n_dropped = 0
+        to_drop = []
+        seen = set()
+        for u in H:
+            nbrs_u = set(H[u])
+            seen.add(u)
+            new_nbrs = [v for v in nbrs_u if v not in seen]
+            for v in new_nbrs:
+                if len(nbrs_u & set(H[v])) < k:
+                    to_drop.append((u, v))
+        H.remove_edges_from(to_drop)
+        n_dropped = len(to_drop)
+        H.remove_nodes_from(list(nx.isolates(H)))
+
+    return H
+
+
+@not_implemented_for('multigraph')
+@not_implemented_for('directed')
+def onion_layers(G):
+    """Returns the layer of each vertex in the onion decomposition of the graph.
+
+    The onion decomposition refines the k-core decomposition by providing
+    information on the internal organization of each k-shell. It is usually
+    used alongside the `core numbers`.
+
+    Parameters
+    ----------
+    G : NetworkX graph
+        A simple graph without self loops or parallel edges
+
+    Returns
+    -------
+    od_layers : dictionary
+        A dictionary keyed by vertex to the onion layer. The layers are
+        contiguous integers starting at 1.
+
+    Raises
+    ------
+    NetworkXError
+        The onion decomposition is not implemented for graphs with self loops
+        or parallel edges or for directed graphs.
+
+    Notes
+    -----
+    Not implemented for graphs with parallel edges or self loops.
+
+    Not implemented for directed graphs.
+
+    See Also
+    --------
+    core_number
+
+    References
+    ----------
+    .. [1] Multi-scale structure and topological anomaly detection via a new
+       network statistic: The onion decomposition
+       L. Hébert-Dufresne, J. A. Grochow, and A. Allard
+       Scientific Reports 6, 31708 (2016)
+       http://doi.org/10.1038/srep31708
+    .. [2] Percolation and the effective structure of complex networks
+       A. Allard and L. Hébert-Dufresne
+       Physical Review X 9, 011023 (2019)
+       http://doi.org/10.1103/PhysRevX.9.011023
+    """
+    if nx.number_of_selfloops(G) > 0:
+        msg = ('Input graph contains self loops which is not permitted; '
+               'Consider using G.remove_edges_from(nx.selfloop_edges(G)).')
+        raise NetworkXError(msg)
+    # Dictionaries to register the k-core/onion decompositions.
+    od_layers = {}
+    # Adjacency list
+    neighbors = {v: list(nx.all_neighbors(G, v)) for v in G}
+    # Effective degree of nodes.
+    degrees = dict(G.degree())
+    # Performs the onion decomposition.
+    current_core = 1
+    current_layer = 1
+    # Sets vertices of degree 0 to layer 1, if any.
+    isolated_nodes = [v for v in nx.isolates(G)]
+    if len(isolated_nodes) > 0:
+        for v in isolated_nodes:
+            od_layers[v] = current_layer
+            degrees.pop(v)
+        current_layer = 2
+    # Finds the layer for the remaining nodes.
+    while len(degrees) > 0:
+        # Sets the order for looking at nodes.
+        nodes = sorted(degrees, key=degrees.get)
+        # Sets properly the current core.
+        min_degree = degrees[nodes[0]]
+        if min_degree > current_core:
+            current_core = min_degree
+        # Identifies vertices in the current layer.
+        this_layer = []
+        for n in nodes:
+            if degrees[n] > current_core:
+                break
+            this_layer.append(n)
+        # Identifies the core/layer of the vertices in the current layer.
+        for v in this_layer:
+            od_layers[v] = current_layer
+            for n in neighbors[v]:
+                neighbors[n].remove(v)
+                degrees[n] = degrees[n] - 1
+            degrees.pop(v)
+        # Updates the layer count.
+        current_layer = current_layer + 1
+    # Returns the dictionaries containing the onion layer of each vertices.
+    return od_layers
diff --git a/networkx/drawing/nx_pylab.py b/networkx/drawing/nx_pylab.py
--- a/networkx/drawing/nx_pylab.py
+++ b/networkx/drawing/nx_pylab.py
@@ -414,7 +414,7 @@ def draw_networkx_nodes(G, pos,
                                  linewidths=linewidths,
                                  edgecolors=edgecolors,
                                  label=label)
-    plt.tick_params(
+    ax.tick_params(
         axis='both',
         which='both',
         bottom=False,
@@ -550,7 +550,6 @@ def draw_networkx_edges(G, pos,
     try:
         import matplotlib
         import matplotlib.pyplot as plt
-        import matplotlib.cbook as cb
         from matplotlib.colors import colorConverter, Colormap, Normalize
         from matplotlib.collections import LineCollection
         from matplotlib.patches import FancyArrowPatch
@@ -582,7 +581,7 @@ def draw_networkx_edges(G, pos,
 
     # Check if edge_color is an array of floats and map to edge_cmap.
     # This is the only case handled differently from matplotlib
-    if cb.iterable(edge_color) and (len(edge_color) == len(edge_pos)) \
+    if np.iterable(edge_color) and (len(edge_color) == len(edge_pos)) \
         and np.alltrue([isinstance(c,Number) for c in edge_color]):
             if edge_cmap is not None:
                 assert(isinstance(edge_cmap, Colormap))
@@ -635,7 +634,7 @@ def to_marker_edge(marker_size, marker):
             x2, y2 = dst
             shrink_source = 0  # space from source to tail
             shrink_target = 0  # space from  head to target
-            if cb.iterable(node_size):  # many node sizes
+            if np.iterable(node_size):  # many node sizes
                 src_node, dst_node = edgelist[i][:2]
                 index_node = nodelist.index(dst_node)
                 marker_size = node_size[index_node]
@@ -643,7 +642,7 @@ def to_marker_edge(marker_size, marker):
             else:
                 shrink_target = to_marker_edge(node_size, node_shape)
 
-            if cb.iterable(arrow_colors):
+            if np.iterable(arrow_colors):
                 if len(arrow_colors) == len(edge_pos):
                     arrow_color = arrow_colors[i]
                 elif len(arrow_colors)==1:
@@ -653,7 +652,7 @@ def to_marker_edge(marker_size, marker):
             else:
                 arrow_color = edge_color
 
-            if cb.iterable(width):
+            if np.iterable(width):
                 if len(width) == len(edge_pos):
                     line_width = width[i]
                 else:
@@ -690,7 +689,7 @@ def to_marker_edge(marker_size, marker):
     ax.update_datalim(corners)
     ax.autoscale_view()
 
-    plt.tick_params(
+    ax.tick_params(
         axis='both',
         which='both',
         bottom=False,
@@ -768,7 +767,6 @@ def draw_networkx_labels(G, pos,
     """
     try:
         import matplotlib.pyplot as plt
-        import matplotlib.cbook as cb
     except ImportError:
         raise ImportError("Matplotlib required for draw()")
     except RuntimeError:
@@ -805,7 +803,7 @@ def draw_networkx_labels(G, pos,
                     )
         text_items[n] = t
 
-    plt.tick_params(
+    ax.tick_params(
         axis='both',
         which='both',
         bottom=False,
@@ -958,7 +956,7 @@ def draw_networkx_edge_labels(G, pos,
                     )
         text_items[(n1, n2)] = t
 
-    plt.tick_params(
+    ax.tick_params(
         axis='both',
         which='both',
         bottom=False,
diff --git a/networkx/readwrite/gml.py b/networkx/readwrite/gml.py
--- a/networkx/readwrite/gml.py
+++ b/networkx/readwrite/gml.py
@@ -10,7 +10,7 @@
 """
 Read graphs in GML format.
 
-"GML, the G>raph Modelling Language, is our proposal for a portable
+"GML, the Graph Modelling Language, is our proposal for a portable
 file format for graphs. GML's key features are portability, simple
 syntax, extensibility and flexibility. A GML file consists of a
 hierarchical key-value lists. Graphs can be annotated with arbitrary


Test patch:
diff --git a/networkx/algorithms/tests/test_core.py b/networkx/algorithms/tests/test_core.py
--- a/networkx/algorithms/tests/test_core.py
+++ b/networkx/algorithms/tests/test_core.py
@@ -124,3 +124,31 @@ def test_k_corona(self):
         # k=2
         k_corona_subgraph = nx.k_corona(self.H, k=0)
         assert_equal(sorted(k_corona_subgraph.nodes()), [0])
+
+    def test_k_truss(self):
+        # k=-1
+        k_truss_subgraph = nx.k_truss(self.G, -1)
+        assert_equal(sorted(k_truss_subgraph.nodes()), list(range(1,21)))
+        # k=0
+        k_truss_subgraph = nx.k_truss(self.G, 0)
+        assert_equal(sorted(k_truss_subgraph.nodes()), list(range(1,21)))
+        # k=1
+        k_truss_subgraph = nx.k_truss(self.G, 1)
+        assert_equal(sorted(k_truss_subgraph.nodes()), list(range(1,13)))
+        # k=2
+        k_truss_subgraph = nx.k_truss(self.G, 2)
+        assert_equal(sorted(k_truss_subgraph.nodes()), list(range(1,9)))
+        # k=3
+        k_truss_subgraph = nx.k_truss(self.G, 3)
+        assert_equal(sorted(k_truss_subgraph.nodes()), [])
+
+    def test_onion_layers(self):
+        layers = nx.onion_layers(self.G)
+        nodes_by_layer = [sorted([n for n in layers if layers[n] == val])
+                          for val in range(1, 7)]
+        assert_nodes_equal(nodes_by_layer[0], [21])
+        assert_nodes_equal(nodes_by_layer[1], [17, 18, 19, 20])
+        assert_nodes_equal(nodes_by_layer[2], [10, 12, 13, 14, 15, 16])
+        assert_nodes_equal(nodes_by_layer[3], [9, 11])
+        assert_nodes_equal(nodes_by_layer[4], [1, 2, 4, 5, 6, 8])
+        assert_nodes_equal(nodes_by_layer[5], [3, 7])