Migration guide from 2.X to 3.0#

Note

Much of the work leading to the NetworkX 3.0 release will be included in the NetworkX 2.6, 2.7, and 2.8 releases. For example, we are deprecating a lot of old code in these releases. This guide will discuss this ongoing work and will help you understand what changes you can make now to minimize the disruption caused by the move to 3.0.

This is a guide for people moving from NetworkX 2.X to NetworkX 3.0.

Any issues with these can be discussed on the mailing list.

The focus of 3.0 release is on addressing years of technical debt, modernizing our codebase, improving performance, and making it easier to contribute. We plan to release 3.0 in the summer.

Default dependencies#

We no longer depend on the “decorator” library, thus NetworkX no longer has any dependencies. However, NetworkX 3.0 includes many changes and improvements centered around tighter integration with other scientific Python libraries; namely numpy, scipy, matplotlib, and pandas.

There are no dependencies for NetworkX’s core functionality, such as the data structures (Graph, DiGraph, etc.) and common algorithms, but some functionality, e.g. functions found in the networkx.linalg package, are only available if these additional libraries are installed.

Improved integration with scientific Python#

NetworkX 3.0 includes several changes to improve and modernize the usage of numpy and scipy within networkx.

Replacing NumPy/SciPy matrices with arrays#

The numpy.matrix has long been discouraged due to significant departures from the ndarray interface, namely:

  • Matrices are always two-dimensional, leading to different results for common operations like indexing and broadcasting.

  • The multiplication operator is interpreted as matrix multiplication rather than element-wise multiplication.

These differences make code more difficult to understand and often require boilerplate in order to work with multiple formats. With the addition of a sparse array interface in scipy version 1.8, NetworkX 3.0 has replaced all instances of scipy sparse matrices and numpy matrices in favor of their array counterparts. Any functions that returned either scipy.sparse.spmatrix or numpy.matrix objects now return their corresponding array counterparts (scipy.sparse._sparray and numpy.ndarray, respectively) and explicit conversion functions that resulted in matrix objects have been removed (e.g. to_numpy_matrix). Users should expect all numpy and scipy.sparse objects to obey array semantics in NetworkX 3.X.

Switch to NumPy/SciPy implementations by default for some algorithms#

Some networkx analysis algorithms can be implemented with very high performance using linear algebra, such as the pagerank algorithm. In NetworkX 2.0, there were multiple implementations of the pagerank algorithm: pagerank (a pure-Python implementation), pagerank_numpy (for dense adjacency matrices), and pagerank_scipy (sparse adjacency matrices). In all practical use-cases, the SciPy implementation vastly outperforms the others. In NetworkX 3.0, the pagerank function now uses the SciPy implementation by default. This means that calling nx.pagerank now requires SciPy to be installed. The original Python implementation is still available for pedagogical purposes as networkx.algorithms.link_analysis.pagerank_alg._pagerank_python but is not exposed publicly to discourage it’s use.

Supporting numpy.random.Generator#

NumPy v1.17 introduced a new interface for pseudo-random number generation. The py_random_state and np_random_state decorators have added support for the new numpy.random.Generator instances; in other words, the seed argument now accepts numpy.random.Generator instances:

>>> G = nx.barbell_graph(6, 2)
>>> pos = nx.spring_layout(G, seed=np.random.default_rng(123456789))

The numpy.random.Generator interface includes several improvements over the original numpy.random.RandomState, including better statistical properties and improved performance. However Generator is not stream-compatible with RandomState and does not guarantee stream-compatibility with future versions of NumPy. Therefore, the best-practice is to be explicit when using random number generators. To guarantee exact reproducibility of random numbers across all versions of NetworkX (past and future), RandomState is recommended:

>>> rng = np.random.RandomState(12345)
>>> pos = nx.spring_layout(G, seed=rng)

For new code where exact stream-reproducibility is less important, Generator is recommended:

>>>> rng = np.random.default_rng(12345)
>>> pos = nx.spring_layout(G, seed=rng)

Note

Exact reproducibility of random numbers with Generator is still possible but may require specific versions of numpy to be installed.

NumPy structured dtypes for multi-attribute adjacency matrices#

Prior to NetworkX 3.0, multi-attribute adjacency matrices were supported through the nx.to_numpy_recarray conversion function. numpy.recarray is a convenience wrapper around ndarray with structured dtypes. As such, thisconversion function has been removed in NetworkX 3.0 and support for structured dtypes has been added to to_numpy_array instead, generally improving supported for array representations of multi-attribute adjacency:

>>> import numpy as np
>>> edges = [
...     (0, 1, {"weight": 10, "cost": 2}),
...     (1, 2, {"weight": 5, "cost": 100})
... ]
>>> G = nx.Graph(edges)
>>> # Create an adjacency matrix with "weight" and "cost" attributes
>>> dtype = np.dtype([("weight", float), ("cost", int)])
>>> A = nx.to_numpy_array(G, dtype=dtype, weight=None)
>>> A
array([[( 0.,   0), (10.,   2), ( 0.,   0)],
       [(10.,   2), ( 0.,   0), ( 5., 100)],
       [( 0.,   0), ( 5., 100), ( 0.,   0)]],
      dtype=[('weight', '<f8'), ('cost', '<i8')])
>>> A["cost"]
array([[  0,   2,   0],
       [  2,   0, 100],
       [  0, 100,   0]])
>>> # The recarray interface can be recovered with ``view``
>>> A = nx.to_numpy_array(G, dtype=dtype, weight=None).view(np.recarray)
>>> A
rec.array([[( 0.,   0), (10.,   2), ( 0.,   0)],
           [(10.,   2), ( 0.,   0), ( 5., 100)],
           [( 0.,   0), ( 5., 100), ( 0.,   0)]],
          dtype=[('weight', '<f8'), ('cost', '<i8')])
>>> A.weight
array([[ 0., 10.,  0.],
       [10.,  0.,  5.],
       [ 0.,  5.,  0.]])

Deprecated code#

The functions read_gpickle and write_gpickle were removed in 3.0. You can read and write NetworkX graphs as Python pickles.

>>> import pickle
>>> G = nx.path_graph(4)
>>> with open('test.gpickle', 'wb') as f:
...     pickle.dump(G, f, pickle.HIGHEST_PROTOCOL)
...
>>> with open('test.gpickle', 'rb') as f:
...     G = pickle.load(f)
...

The functions read_yaml and write_yaml were removed in 3.0. You can read and write NetworkX graphs in YAML format using pyyaml.

>>> import yaml
>>> G = nx.path_graph(4)
>>> with open('test.yaml', 'w') as f:
...     yaml.dump(G, f)
...
>>> with open('test.yaml', 'r') as f:
...     G = yaml.load(f, Loader=yaml.Loader)