jupyter-notebooknetgraph

Netgraph animation example not updating in Jupyter notebook


I'm trying to run an example animation for the Netgraph library in Jupyter notebook as shown here

import numpy as np
import matplotlib.pyplot as plt

from matplotlib.animation import FuncAnimation
from netgraph import Graph

# Simulate a dynamic network with
# - 5 frames / network states,
# - with 10 nodes at each time point,
# - an expected edge density of 25%, and
# - edge weights drawn from a Gaussian distribution.
total_frames = 5
total_nodes = 10
adjacency_matrix = np.random.rand(total_nodes, total_nodes) < 0.25
weight_matrix = np.random.randn(total_frames, total_nodes, total_nodes)

# Normalise the weights, such that they are on the interval [0, 1].
# They can then be passed directly to matplotlib colormaps (which expect floats on that interval).
vmin, vmax = -2, 2
weight_matrix[weight_matrix<vmin] = vmin
weight_matrix[weight_matrix>vmax] = vmax
weight_matrix -= vmin
weight_matrix /= vmax - vmin

cmap = plt.cm.RdGy

# plt.ion()
fig, ax = plt.subplots()
g = Graph(adjacency_matrix, edge_cmap=cmap, arrows=True, ax=ax)

def update(ii):
    artists = []
    for jj, kk in zip(*np.where(adjacency_matrix)):
        w = weight_matrix[ii, jj, kk]
        artist = g.edge_artists[(jj, kk)]
        artist.set_facecolor(cmap(w))
        artist.update_width(0.03 * np.abs(w-0.5)) # np.abs(w-0.5) so that large negative edges are also wide
        artists.append(artist)
    return artists

animation = FuncAnimation(fig, update, frames=total_frames, interval=200, blit=True)

The code runs but it only shows the final state not all the states as simulation progresses—so I only see a static picture of the graph.

I've tried to add

%matplotlib inline

in the cell preceding the code but that didn't solve the problem. I've also tried adding plt.ion() as shown in the code but I'm not sure where would be the right place to put it.

I'm on mac, using python 3.12 under jupyter notebook 7.2.1. Can anyone advice how to fix this please? Thank you.


Solution

  • The main issues are you are doing this in Jupyter Notebook 7+ and Matplotlib's FuncAnimation() is involved.
    And so you need to use the modern interactive backend for Maplotlib, which is ipympl. This allows for animations via Matplotlib's FuncAnimation() in modern Jupyter. (Modern Jupyter is JupyterLab 3 and 4+ and Jupyter Notebook 7+.) A lot of stuff you probably came across is for the 'classic' Jupyter Notebook and is outdated relative to your tech. Jupyter Notebook version 7+ are built on JupyterLab components and so you'll find you are often looking for solutions that work with JupyterLab now when considering what advice is not valid.

    Essentially, the answer to your question is covered in the first part of this answer. You only want to concern yourself with the section prior to 'Optional version with a player controller widget that doesn’t need ipympl'. Perhaps a shorter version of the issue I believe is at the crux your post's issue and a solution is covered here. It boils down, I believe to that you need to install ipympl, if not already installed, and use that. (You don't say how you installed Python and Jupyter on your Mac? If it was recently with the Ancaonda Distribution, then you probably don't need to install ipympl as I believe it is inluded. Otherwise you most likely need to install it first.) If you install it, you need to restart everything (computer, browser, Jupyter, kernel) as it needs to propagate a lot of changes in things. Additionally, it is probably necessary to do a browser refresh the Jupyter Notebook page that you have been working on so the old underlying messaging and Javascrip are all refreshed to after you installed ipympl. Browser cache's can cause you apparent headaches otherwise.

    Then with ipympl installed and everything refreshed and kernel restarted the code in a cell to run should be:

    %matplotlib ipympl
    import numpy as np
    import matplotlib.pyplot as plt
    
    from matplotlib.animation import FuncAnimation
    from netgraph import Graph
    
    # Simulate a dynamic network with
    # - 5 frames / network states,
    # - with 10 nodes at each time point,
    # - an expected edge density of 25%, and
    # - edge weights drawn from a Gaussian distribution.
    total_frames = 5
    total_nodes = 10
    adjacency_matrix = np.random.rand(total_nodes, total_nodes) < 0.25
    weight_matrix = np.random.randn(total_frames, total_nodes, total_nodes)
    
    # Normalise the weights, such that they are on the interval [0, 1].
    # They can then be passed directly to matplotlib colormaps (which expect floats on that interval).
    vmin, vmax = -2, 2
    weight_matrix[weight_matrix<vmin] = vmin
    weight_matrix[weight_matrix>vmax] = vmax
    weight_matrix -= vmin
    weight_matrix /= vmax - vmin
    
    cmap = plt.cm.RdGy
    
    # plt.ion()
    fig, ax = plt.subplots()
    g = Graph(adjacency_matrix, edge_cmap=cmap, arrows=True, ax=ax)
    
    def update(ii):
        artists = []
        for jj, kk in zip(*np.where(adjacency_matrix)):
            w = weight_matrix[ii, jj, kk]
            artist = g.edge_artists[(jj, kk)]
            artist.set_facecolor(cmap(w))
            artist.update_width(0.03 * np.abs(w-0.5)) # np.abs(w-0.5) so that large negative edges are also wide
            artists.append(artist)
        return artists
    
    animation = FuncAnimation(fig, update, frames=total_frames, interval=200, blit=True)
    

    (Optionally you can add at the bottom of that animation; or plt.show(), but it will work with only adding the %matplotlib ipympl to your code.)


    Testing Options without touching your own system or logging in:

    I just tested it in ephemeral Jupyter sessions launched from here & served via the MyBinder system. After the session came up, I opened a terminal there using the launcher and ran in the terminal pip install netgraph before opening a notebook file pasting in the code I provide above.

    You can scroll down there to choose the flavor of Jupyter that you'll get when you click 'launch binder'. The 'launch binder' badge at the top goes to JupyterLab, which doesn't match exactly your situation. For Jupyter Notebook 7+ it is easier to run %pip install netgraph in the notebook that comes up when the session starts and then run the provided code. To do as I stated above though is also possible. When the Jupyter Notebook 7+ session comes up, click on the 'Jupyter' logo in the upper left side above the notebook. That will get you to the Jupyter Dashboard where you can choose to open a terminal from under the 'New' drop-down on the upper right side there.
    Note as of now that is using Python 3.10, which is well behind what you are using. I don't think the Python version factors in this issue. I also tested it on Python 3.11 here and it worked as stated. (Sorry, I don't have a handy way to use Python 3.11 or 3.12 in MyBinder sessions with Jupyter Notebook 7+ that I can point you to; most often I simply rely on Jtpio's gists, like here but I needed to specify at least Python 3.11 recently for a test concerning Snakemake & troubleshooting hypertools, and so I happen to have that for JupyterLab use with ipympl included at least.)