How to reshape a networkx graph in Python?

我是研究僧i 提交于 2021-02-05 18:17:47

问题


So I created a really naive (probably inefficient) way of generating hasse diagrams.

Question:

I have 4 dimensions... p q r s .

I want to display it uniformly (tesseract) but I have no idea how to reshape it. How can one reshape a networkx graph in Python?

I've seen some examples of people using spring_layout() and draw_circular() but it doesn't shape in the way I'm looking for because they aren't uniform.

Is there a way to reshape my graph and make it uniform? (i.e. reshape my hasse diagram into a tesseract shape (preferably using nx.draw() )

Here's what mine currently look like: enter image description here

Here's my code to generate the hasse diagram of N dimensions

#!/usr/bin/python

import networkx as nx
import matplotlib.pyplot as plt
import itertools

H = nx.DiGraph()

axis_labels = ['p','q','r','s']

D_len_node = {}

#Iterate through axis labels
for i in xrange(0,len(axis_labels)+1):
    #Create edge from empty set
    if i == 0:
        for ax in axis_labels:
            H.add_edge('O',ax)
    else:
        #Create all non-overlapping combinations
        combinations = [c for c in itertools.combinations(axis_labels,i)]
        D_len_node[i] = combinations
    #Create edge from len(i-1) to len(i) #eg. pq >>> pqr, pq >>> pqs
    if i > 1:
        for node in D_len_node[i]:
            for p_node in D_len_node[i-1]:
                #if set.intersection(set(p_node),set(node)): Oops
                if all(p in node for p in p_node) == True: #should be this!
                    H.add_edge(''.join(p_node),''.join(node))

#Show Plot
nx.draw(H,with_labels = True,node_shape = 'o')
plt.show() 

I want to reshape it like this: enter image description here

If anyone knows of an easier way to make Hasse Diagrams, please share some wisdom but that's not the main aim of this post.


回答1:


This is a pragmatic, rather than purely mathematical answer.

I think you have two issues - one with layout, the other with your network.

1. Network

You have too many edges in your network for it to represent the unit tesseract. Caveat I'm not an expert on the maths here - just came to this from the plotting angle (matplotlib tag). Please explain if I'm wrong.

Your desired projection and, for instance, the wolfram mathworld page for a Hasse diagram for n=4 has only 4 edges connected all nodes, whereas you have 6 edges to the 2 and 7 edges to the 3 bit nodes. Your graph fully connects each "level", i.e. 4-D vectors with 0 1 values connect to all vectors with 1 1 value, which then connect to all vectors with 2 1 values and so on. This is most obvious in the projection based on the Wikipedia answer (2nd image below)

2. Projection

I couldn't find a pre-written algorithm or library to automatically project the 4D tesseract onto a 2D plane, but I did find a couple of examples, e.g. Wikipedia. From this, you can work out a co-ordinate set that would suit you and pass that into the nx.draw() call.

Here is an example - I've included two co-ordinate sets, one that looks like the projection you show above, one that matches this one from wikipedia.

import networkx as nx
import matplotlib.pyplot as plt
import itertools

H = nx.DiGraph()

axis_labels = ['p','q','r','s']

D_len_node = {}

#Iterate through axis labels
for i in xrange(0,len(axis_labels)+1):
    #Create edge from empty set
    if i == 0:
        for ax in axis_labels:
            H.add_edge('O',ax)
    else:
        #Create all non-overlapping combinations
        combinations = [c for c in itertools.combinations(axis_labels,i)]
        D_len_node[i] = combinations
    #Create edge from len(i-1) to len(i) #eg. pq >>> pqr, pq >>> pqs
    if i > 1:
        for node in D_len_node[i]:
            for p_node in D_len_node[i-1]:
                if set.intersection(set(p_node),set(node)):
                    H.add_edge(''.join(p_node),''.join(node))

#This is manual two options to project tesseract onto 2D plane 
# - many projections are available!!
wikipedia_projection_coords = [(0.5,0),(0.85,0.25),(0.625,0.25),(0.375,0.25),
                                (0.15,0.25),(1,0.5),(0.8,0.5),(0.6,0.5),
                                (0.4,0.5),(0.2,0.5),(0,0.5),(0.85,0.75),
                                (0.625,0.75),(0.375,0.75),(0.15,0.75),(0.5,1)]

#Build the "two cubes" type example projection co-ordinates
half_coords = [(0,0.15),(0,0.6),(0.3,0.15),(0.15,0),
               (0.55,0.6),(0.3,0.6),(0.15,0.4),(0.55,1)]
#make the coords symmetric
example_projection_coords = half_coords + [(1-x,1-y) for (x,y) in half_coords][::-1]

print example_projection_coords


def powerset(s):
    ch = itertools.chain.from_iterable(itertools.combinations(s, r) for r in range(len(s)+1))
    return [''.join(t) for t in ch]

pos={}
for i,label in enumerate(powerset(axis_labels)):
    if label == '':
       label = 'O'
    pos[label]= example_projection_coords[i]

#Show Plot
nx.draw(H,pos,with_labels = True,node_shape = 'o')
plt.show() 

Note - unless you change what I've mentioned in 1. above, they still have your edge structure, so won't look exactly the same as the examples from the web. Here is what it looks like with your existing network generation code - you can see the extra edges if you compare it to your example (e.g. I don't this pr should be connected to pqs:

'Two cube' projection

Projection of tesseract generated by code

Wikimedia example projection

Alternative projection of tesseract generated by code provided


Note

If you want to get into the maths of doing your own projections (and building up pos mathematically), you might look at this research paper.


EDIT:

Curiosity got the better of me and I had to search for a mathematical way to do this. I found this blog - the main result of which being the projection matrix:

4D to 2D projection matrix

This led me to develop this function for projecting each label, taking the label containing 'p' to mean the point has value 1 on the 'p' axis, i.e. we are dealing with the unit tesseract. Thus:

def construct_projection(label):
    r1 = r2 = 0.5
    theta = math.pi / 6
    phi = math.pi / 3
    x = int( 'p' in label) + r1 * math.cos(theta) * int('r' in label) - r2 * math.cos(phi) * int('s' in label)
    y = int( 'q' in label) + r1 * math.sin(theta) * int('r' in label) + r2 * math.sin(phi) * int('s' in label)
    return (x,y)

Gives a nice projection into a regular 2D octagon with all points distinct.

This will run in the above program, just replace

 pos[label] = example_projection_coords[i]

with

pos[label] = construct_projection(label)

This gives the result:

projection onto an octagon

play with r1,r2,theta and phi to your heart's content :)



来源:https://stackoverflow.com/questions/30689391/how-to-reshape-a-networkx-graph-in-python

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!