wiheto / teneto

Temporal Network Tools
GNU General Public License v3.0
85 stars 26 forks source link

How can I use `graphlet_stack_plot` from a `TemporalNetwork` object? #47

Closed jolespin closed 4 years ago

jolespin commented 5 years ago

I have a weighted undirected network with 3 timepoints. How can I use this with your plotting functions? It seems the TemporalNetwork is the main object for this package so I'm confused on how I can use this with the plotting functions.

wiheto commented 5 years ago

Hi!

Good question.

So you should be able to do

import teneto
tnet = TemporalNetwork(....)
tnet.plot('graphlet_stack_plot')

But it looks like my recent changes to increase speed are breaking something here. Oops!

So if you have a TemporalNetwork object, the best thing to do until I fix this is:

import matplotlib.pyplot as plt
fig, ax = plt.subplots(1)
# this creates a numpy array of node,node,time (all teneto functions work with this)
network = tnet.df_to_array()
teneto.plot.graphlet_stackplot(network, ax)

Typing fig.show(), fig.savefig() will get or save the figure for you. Now I just noticed that in more recent versions of matplotlib it is making an empty figure as well. So don't be alarmed if that happens.

So you helped me identify 2 bugs. Thank you! And hopefully this workaround works for you until I get around to solving them.

jolespin commented 5 years ago

Thanks! I thought I tried that but apparently not.

My networks are fully connected but weighted, is there a way to show the weights?

tnet = temporalgraph.teneto_network_
print(tn)
# <teneto.classes.network.TemporalNetwork object at 0x12e032a20>

fig, ax = plt.subplots()
# this creates a numpy array of node,node,time (all teneto functions work with this)
network = tnet.df_to_array()

teneto.plot.graphlet_stack_plot(network, ax, vminmax=[-1,1])

image

wiheto commented 5 years ago

I think you have a lot of nodes and the border between each square is overwhelming the figure. Try:

teneto.plot.graphlet_stack_plot(network, ax, vminmax=[-1,1], borderwidth=0)

jolespin commented 5 years ago

I have 54 nodes which are essentially the 4 iris features and 50 noise variables.

I got the following error:

teneto.plot.graphlet_stack_plot(network, ax, vminmax=[-1,1], borderwidth=0)
<teneto.classes.network.TemporalNetwork object at 0x12e032a20>
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-270-14b23efc9796> in <module>
      8 
      9 
---> 10 teneto.plot.graphlet_stack_plot(network, ax, vminmax=[-1,1], borderwidth=0)

~/anaconda/envs/µ_env/lib/python3.6/site-packages/teneto/plot/graphlet_stack_plot.py in graphlet_stack_plot(netin, ax, q, cmap, gridcolor, borderwidth, bordercolor, Fs, timeunit, t0, sharpen, vminmax)
    175         figmattmp_withborder = np.zeros(
    176             [figmattmp.shape[0] + (qb * 2), figmattmp.shape[1] + (qb * 2), 3]) + (np.array(bordercolor) * 255)
--> 177         figmattmp_withborder[qb:-qb, qb:-qb, :] = figmattmp
    178 
    179         # Make corners rounded. First make a circle and then take the relevant quarter for each corner.

ValueError: could not broadcast input array from shape (800,800,3) into shape (0,0,3)

If I increase the figsize would that help you think?

wiheto commented 5 years ago

Sorry, I forgot borderwidth can't be set to 0 for reasons I have forgotten (but that I noted in the documentation). Set it to 0.1 or something small.

jolespin commented 5 years ago

I'm getting the same error, it looks like anytihng smaller than 1 throws the error.

teneto.plot.graphlet_stack_plot(network, ax, vminmax=[-1,1], borderwidth=1-1e-5) didn't work.

wiheto commented 5 years ago

Yeah. It's been a while since I used this function :)

Does borderwidth=1 improve the figure at least? The default is 2.

jolespin commented 5 years ago

Haha, I understand. I just found your teneto paper in Network Neuroscience and I learned a lot! Looking forward to applying this to some microbiome datasets.

I tried the following but to no avail:

image

wiheto commented 5 years ago

Let me do a little bit of testing and I will get back to you.

wiheto commented 5 years ago

So I was able to replicate you problem with any big network (think I had only done 35 nodes before)

So I've just updated the teneto from the main branch, so you'll have to update:

pip install -U git+http://github.com/wiheto/teneto

I can now do:

import matplotlib.pyplot as plt
import teneto 
a = teneto.generatenetwork.rand_binomial([60,60,3],0.5)
teneto.plot.graphlet_stack_plot(a,ax, borderwidth=0)

And it now makes:

Figure_1

And before I got the same as you.

Changing the colormap (cmap) will make the white parts less ugly and useful for weighted networks. Also, you may want to look into the parameter q. The default is 10, increasing this will generate higher quality bitmaps (each snapshot is converted into bitmap in order to be scaled to look 3D). Increasing this parameter to 15 or 20 will make the figure look nicer, but will use more RAM.

This was the first function I made for teneto back in 2016-ish. Some improvements can definitely be added to it. If there are any features you'd rather have, let me know.

jolespin commented 5 years ago

Thank you! This will be really useful moving forward.

While exploring your package, I've thought of a few features that would make it a little more broad use: (1) Build on top of networkx to export networkx graph objects and integrate into the rest of SciPy ecosystem; (2) Include option for non-integer node labels; (3) Add weighted edges (this would be huge!) to the slice plots; (4) Maybe a stacked network feature (networkx would help with this using the pos layouts);

image

In terms of this plot, it may be useful to have the following: (1) heatmap_kws where a user can define things like linewidth, edgecolor, etc. (2) Maybe a colorbar on the bottom? (3) Other rotations like this example?

image

wiheto commented 5 years ago

Thanks for the input.

(1) there is teneto.utils.tnet_to_nx() but I don't think I've made a from_nx() and should be added. Making these more prominent feature/part of TemporalNetowrk object is on the long to-do list. (2) Non-integer node labels are possible as meta-data that get carried over to the plots, just not part of the network data itself. I admit this could be better or be more integrated. I'll keep that in mind.
(3) Will do. That's a relatively easy one.

Regarding the other points, I want to completely remake all the visualizations with greater flexibility and add some new ideas and inspiration I've got since I made them. So these are all useful suggestions that I'll keep in mind.

I'm currently rewriting a lot of the other backend stuff at the moment regarding workflows and neuroimaging that's about to be released. Once I've done that, I'll hopefully get around to visualizations again.

jolespin commented 5 years ago

I had a question about the slice_plot. I just realized the point [3] about slice plots above was already implemented!

However, I had a question about the following:

It appears that the nodes are node_index + 1. Is this true?

For example, is my node=0 represented as node=1, ..., node=3 as node=4?

data = {'i': {0: 1, 1: 1, 2: 1, 3: 0, 4: 0, 5: 2, 6: 1, 7: 1, 8: 1, 9: 0, 10: 0, 11: 2, 12: 1, 13: 1, 14: 1, 15: 0, 16: 0, 17: 2, 18: 1, 19: 1, 20: 1, 21: 0, 22: 0, 23: 2, 24: 1, 25: 1, 26: 1, 27: 0, 28: 0, 29: 2}, 'j': {0: 0, 1: 2, 2: 3, 3: 2, 4: 3, 5: 3, 6: 0, 7: 2, 8: 3, 9: 2, 10: 3, 11: 3, 12: 0, 13: 2, 14: 3, 15: 2, 16: 3, 17: 3, 18: 0, 19: 2, 20: 3, 21: 2, 22: 3, 23: 3, 24: 0, 25: 2, 26: 3, 27: 2, 28: 3, 29: 3}, 't': {0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 1, 7: 1, 8: 1, 9: 1, 10: 1, 11: 1, 12: 2, 13: 2, 14: 2, 15: 2, 16: 2, 17: 2, 18: 3, 19: 3, 20: 3, 21: 3, 22: 3, 23: 3, 24: 4, 25: 4, 26: 4, 27: 4, 28: 4, 29: 4}, 'weight': {0: 0.2659470401637537, 1: 0.0039524797711067506, 2: 0.3923470839260313, 3: 0.49102741918275905, 4: 0.2262588904694168, 5: 0.1710384068278805, 6: 0.1923126782473425, 7: 0.12781954887218044, 8: 0.39298215657816105, 9: 0.46772102670469273, 10: 0.4094595748313839, 11: 0.42440023905062746, 12: 0.10234185290365067, 13: 0.05244268839774458, 14: 0.05199555720103665, 15: 0.5472686733556299, 16: 0.4514328026656794, 17: 0.38736764161421694, 18: 0.0645016034793305, 19: 0.1498045073145016, 20: 0.30735054419264946, 21: 0.5913901632348235, 22: 0.5225533959711175, 23: 0.44053414939490887, 24: 0.1874118285404232, 25: 0.010607704829083018, 26: 0.2169332366301485, 27: 0.5246201844818231, 28: 0.18150933312974768, 29: 0.3461465053250372}}
df_network = pd.DataFrame(data)
network = np.asarray([[[0.        , 0.        , 0.        , 0.        , 0.        ],
        [0.26594704, 0.19231268, 0.10234185, 0.0645016 , 0.18741183],
        [0.49102742, 0.46772103, 0.54726867, 0.59139016, 0.52462018],
        [0.22625889, 0.40945957, 0.4514328 , 0.5225534 , 0.18150933]],

       [[0.26594704, 0.19231268, 0.10234185, 0.0645016 , 0.18741183],
        [0.        , 0.        , 0.        , 0.        , 0.        ],
        [0.00395248, 0.12781955, 0.05244269, 0.14980451, 0.0106077 ],
        [0.39234708, 0.39298216, 0.05199556, 0.30735054, 0.21693324]],

       [[0.49102742, 0.46772103, 0.54726867, 0.59139016, 0.52462018],
        [0.00395248, 0.12781955, 0.05244269, 0.14980451, 0.0106077 ],
        [0.        , 0.        , 0.        , 0.        , 0.        ],
        [0.17103841, 0.42440024, 0.38736764, 0.44053415, 0.34614651]],

       [[0.22625889, 0.40945957, 0.4514328 , 0.5225534 , 0.18150933],
        [0.39234708, 0.39298216, 0.05199556, 0.30735054, 0.21693324],
        [0.17103841, 0.42440024, 0.38736764, 0.44053415, 0.34614651],
        [0.        , 0.        , 0.        , 0.        , 0.        ]]])

print("Nodes:", np.unique(df_network.loc[:,["i","j"]].values.ravel()))

with plt.style.context("seaborn-white"):
    fig, ax = plt.subplots()
    teneto.plot.slice_plot(network, ax, plotedgeweights=True, cmap=plt.cm.seismic, nodesize=200)

image

wiheto commented 5 years ago

Yes. (Sorry for late answer)

There’s an argument called timelabels if you want to change it to a list of strings.

Like I’ve said these plotting functions are a little old and I need to update them to be a little more pythonic in places. But I reasoned most want their first time point to be called “1” if integers, hence the implementation.

26 aug. 2019 kl. 12:04 skrev Josh L. Espinoza notifications@github.com:

I had a question about the slice_plot:

It appears that the nodes are node_index + 1. Is this true?

For example, is my node=0 represented as node=1, ..., node=3 as node=4?

data = {'i': {0: 1, 1: 1, 2: 1, 3: 0, 4: 0, 5: 2, 6: 1, 7: 1, 8: 1, 9: 0, 10: 0, 11: 2, 12: 1, 13: 1, 14: 1, 15: 0, 16: 0, 17: 2, 18: 1, 19: 1, 20: 1, 21: 0, 22: 0, 23: 2, 24: 1, 25: 1, 26: 1, 27: 0, 28: 0, 29: 2}, 'j': {0: 0, 1: 2, 2: 3, 3: 2, 4: 3, 5: 3, 6: 0, 7: 2, 8: 3, 9: 2, 10: 3, 11: 3, 12: 0, 13: 2, 14: 3, 15: 2, 16: 3, 17: 3, 18: 0, 19: 2, 20: 3, 21: 2, 22: 3, 23: 3, 24: 0, 25: 2, 26: 3, 27: 2, 28: 3, 29: 3}, 't': {0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 1, 7: 1, 8: 1, 9: 1, 10: 1, 11: 1, 12: 2, 13: 2, 14: 2, 15: 2, 16: 2, 17: 2, 18: 3, 19: 3, 20: 3, 21: 3, 22: 3, 23: 3, 24: 4, 25: 4, 26: 4, 27: 4, 28: 4, 29: 4}, 'weight': {0: 0.2659470401637537, 1: 0.0039524797711067506, 2: 0.3923470839260313, 3: 0.49102741918275905, 4: 0.2262588904694168, 5: 0.1710384068278805, 6: 0.1923126782473425, 7: 0.12781954887218044, 8: 0.39298215657816105, 9: 0.46772102670469273, 10: 0.4094595748313839, 11: 0.42440023905062746, 12: 0.10234185290365067, 13: 0.05244268839774458, 14: 0.05199555720103665, 15: 0.5472686733556299, 16: 0.4514328026656794, 17: 0.38736764161421694, 18: 0.0645016034793305, 19: 0.1498045073145016, 20: 0.30735054419264946, 21: 0.5913901632348235, 22: 0.5225533959711175, 23: 0.44053414939490887, 24: 0.1874118285404232, 25: 0.010607704829083018, 26: 0.2169332366301485, 27: 0.5246201844818231, 28: 0.18150933312974768, 29: 0.3461465053250372}} df_network = pd.DataFrame(data) network = np.asarray([[[0. , 0. , 0. , 0. , 0. ], [0.26594704, 0.19231268, 0.10234185, 0.0645016 , 0.18741183], [0.49102742, 0.46772103, 0.54726867, 0.59139016, 0.52462018], [0.22625889, 0.40945957, 0.4514328 , 0.5225534 , 0.18150933]],

   [[0.26594704, 0.19231268, 0.10234185, 0.0645016 , 0.18741183],
    [0.        , 0.        , 0.        , 0.        , 0.        ],
    [0.00395248, 0.12781955, 0.05244269, 0.14980451, 0.0106077 ],
    [0.39234708, 0.39298216, 0.05199556, 0.30735054, 0.21693324]],

   [[0.49102742, 0.46772103, 0.54726867, 0.59139016, 0.52462018],
    [0.00395248, 0.12781955, 0.05244269, 0.14980451, 0.0106077 ],
    [0.        , 0.        , 0.        , 0.        , 0.        ],
    [0.17103841, 0.42440024, 0.38736764, 0.44053415, 0.34614651]],

   [[0.22625889, 0.40945957, 0.4514328 , 0.5225534 , 0.18150933],
    [0.39234708, 0.39298216, 0.05199556, 0.30735054, 0.21693324],
    [0.17103841, 0.42440024, 0.38736764, 0.44053415, 0.34614651],
    [0.        , 0.        , 0.        , 0.        , 0.        ]]])

print("Nodes:", np.unique(df_network.loc[:,["i","j"]].values.ravel()))

with plt.style.context("seaborn-white"): fig, ax = plt.subplots() teneto.plot.slice_plot(network, ax, plotedgeweights=True, cmap=plt.cm.seismic, nodesize=200)

— You are receiving this because you commented. Reply to this email directly, view it on GitHub, or mute the thread.

wiheto commented 4 years ago

So the errors here for tnet.TemporalNetwork was actually fixed a while ago. The following now works:

import teneto
tnet.generatenetwork('rand_binomial', size=(5,5), prob=(0.5, 0.1))
tnet.plot('graphlet_stack_plot')

as does

import teneto
import matplotlib.pyplot as plt
tnet.generatenetwork('rand_binomial', size=(5,5), prob=(0.5, 0.1))
fig, ax = plt.subplots(1)
tnet.plot('graphlet_stack_plot', ax=ax)

Which was the original bug reported here.

The making more flexible stacking plots is on a feature todo list.