├── layers ├── __init__.py └── graph.py ├── utils ├── __init__.py └── sparse.py ├── examples ├── __init__.py ├── 01_karate_unsupervised.py └── 02_karate_semisupervised.py ├── R ├── R.Rproj ├── karate.R └── karate.graphml ├── environment.yml ├── README.md └── .gitignore /layers/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /utils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /R/R.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | -------------------------------------------------------------------------------- /environment.yml: -------------------------------------------------------------------------------- 1 | name: env_graph_convnet 2 | dependencies: 3 | - python=3.6.1 4 | # Analysis 5 | - matplotlib 6 | - seaborn 7 | # Calculations 8 | - nomkl 9 | - numpy 10 | - networkx 11 | - scikit-learn 12 | - scipy 13 | # Reporting 14 | - pandas 15 | # Packages 16 | - pip 17 | - pip: 18 | - python-igraph 19 | - tensorflow 20 | -------------------------------------------------------------------------------- /R/karate.R: -------------------------------------------------------------------------------- 1 | library(igraph) 2 | library(tidyverse) 3 | 4 | g <- make_graph("Zachary") 5 | oc <- cluster_optimal(g) 6 | 7 | colours <- c("red", "green", "blue", "cyan") 8 | 9 | V(g)$membership <- oc$membership 10 | V(g)$color <- map(V(g)$membership, function(x) colours[x]) %>% as.character 11 | 12 | g %>% igraph::write.graph("karate.graphml", format = "graphml") 13 | 14 | -------------------------------------------------------------------------------- /utils/sparse.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import scipy.sparse as sp 3 | 4 | 5 | def sparse_to_tuple(sparse_mx): 6 | """Convert sparse matrix to tuple representation.""" 7 | # The zeroth element of the tuple contains the cell location of each 8 | # non-zero value in the sparse matrix 9 | # The first element of the tuple contains the value at each cell location 10 | # in the sparse matrix 11 | # The second element of the tuple contains the full shape of the sparse 12 | # matrix 13 | def to_tuple(mx): 14 | if not sp.isspmatrix_coo(mx): 15 | mx = mx.tocoo() 16 | coords = np.vstack((mx.row, mx.col)).transpose() 17 | values = mx.data 18 | shape = mx.shape 19 | return coords, values, shape 20 | 21 | if isinstance(sparse_mx, list): 22 | for i in range(len(sparse_mx)): 23 | sparse_mx[i] = to_tuple(sparse_mx[i]) 24 | else: 25 | sparse_mx = to_tuple(sparse_mx) 26 | 27 | return sparse_mx 28 | -------------------------------------------------------------------------------- /layers/graph.py: -------------------------------------------------------------------------------- 1 | import tensorflow as tf 2 | 3 | 4 | def matmul(x, y, sparse=False): 5 | """Wrapper for sparse matrix multiplication.""" 6 | if sparse: 7 | return tf.sparse_tensor_dense_matmul(x, y) 8 | return tf.matmul(x, y) 9 | 10 | 11 | class GraphConvLayer: 12 | def __init__( 13 | self, 14 | input_dim, 15 | output_dim, 16 | activation=None, 17 | use_bias=False, 18 | name="graph_conv"): 19 | """Initialise a Graph Convolution layer. 20 | 21 | Args: 22 | input_dim (int): The input dimensionality. 23 | output_dim (int): The output dimensionality, i.e. the number of 24 | units. 25 | activation (callable): The activation function to use. Defaults to 26 | no activation function. 27 | use_bias (bool): Whether to use bias or not. Defaults to `False`. 28 | name (str): The name of the layer. Defaults to `graph_conv`. 29 | """ 30 | self.input_dim = input_dim 31 | self.output_dim = output_dim 32 | self.activation = activation 33 | self.use_bias = use_bias 34 | self.name = name 35 | 36 | with tf.variable_scope(self.name): 37 | self.w = tf.get_variable( 38 | name='w', 39 | shape=(self.input_dim, self.output_dim), 40 | initializer=tf.initializers.glorot_uniform()) 41 | 42 | if self.use_bias: 43 | self.b = tf.get_variable( 44 | name='b', 45 | initializer=tf.constant(0.1, shape=(self.output_dim,))) 46 | 47 | def call(self, adj_norm, x, sparse=False): 48 | x = matmul(x=x, y=self.w, sparse=sparse) # XW 49 | x = matmul(x=adj_norm, y=x, sparse=True) # AXW 50 | 51 | if self.use_bias: 52 | x = tf.add(x, self.use_bias) # AXW + B 53 | 54 | if self.activation is not None: 55 | x = self.activation(x) # activation(AXW + B) 56 | 57 | return x 58 | 59 | def __call__(self, *args, **kwargs): 60 | return self.call(*args, **kwargs) 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A tutorial on Graph Convolutional Neural Networks 2 | 3 | ## Data 4 | 5 | The data we use is [Zachary's karate club](https://en.wikipedia.org/wiki/Zachary%27s_karate_club), a standard toy social network. It is a data set consisting of: 6 | 7 | + 34 nodes, each corresponding to members of a karate club 8 | 9 | + 78 pairwise links that correspond to social interactions of the members outside of the club. 10 | 11 | A conflict arose in the club which caused the club to split into several factions. Using modularity based clustering, these factions can be recovered from the graph structure alone (except for one node). 12 | 13 | ## Code 14 | 15 | + `R/` contains the code necessary to produce the `graphml` representation of the karate club network, 16 | 17 | + `layers/graph.py` contains the TensorFlow implementation of the Graph Convolutional Layer, 18 | 19 | + `utils/sparse.py` contains helper functions for dealing with sparse matrices, 20 | 21 | + `examples/` contains two python scripts that demonstrate how Graph Convolutional Neural Networks perform in an unsupervised and semi-supervised manner, following the appendix of http://arxiv.org/abs/1609.02907. 22 | 23 | ## Requirements 24 | 25 | This project is built for running on an `Anaconda` virtual environment. I will add support for alternative setups later. 26 | 27 | ## Setup 28 | 29 | + Clone, 30 | 31 | + If you want to use the gpu version of tensorflow, edit the `environment.yml` 32 | 33 | ``` 34 | tensorflow -> tensorflow-gpu 35 | ``` 36 | 37 | + Create the Anaconda virtual environment `env_graph_convnet` 38 | 39 | ``` 40 | $ conda env create 41 | ``` 42 | 43 | And you're ready to go! 44 | 45 | ## Original implementation of Graph Convolutional Neural Networks 46 | 47 | For the original TensorFlow implementation (in a Keras style) see https://github.com/tkipf/gcn. 48 | 49 | ## References 50 | 51 | ### Blog posts 52 | 53 | + Great introductory post https://tkipf.github.io/graph-convolutional-networks/ 54 | 55 | ### Papers 56 | 57 | + Defferrard, M., Bresson, X., & Vandergheynst, P. (2016). Convolutional Neural Networks on Graphs with Fast Localized Spectral Filtering. Nips, (Nips), 1–14. http://arxiv.org/abs/1606.09375 58 | 59 | + Kipf, T. N., & Welling, M. (2016). Semi-Supervised Classification with Graph Convolutional Networks, 1–14. http://arxiv.org/abs/1609.02907 60 | 61 | + Kipf, T. N., & Welling, M. (2016). Variational Graph Auto-Encoders. Nipsw, (2), 1–3. http://arxiv.org/abs/1611.07308 62 | 63 | + Berg, R. van den, Kipf, T. N., & Welling, M. (2017). Graph Convolutional Matrix Completion. https://arxiv.org/pdf/1706.02263.pdf 64 | 65 | + Schlichtkrull, M., Kipf, T. N., Bloem, P., Berg, R. van den, Titov, I., & Welling, M. (2017). Modeling Relational Data with Graph Convolutional Networks, 1–12. http://arxiv.org/abs/1703.06103 66 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | ### Python template 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | env/ 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | wheels/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *,cover 49 | .hypothesis/ 50 | 51 | # Translations 52 | *.mo 53 | *.pot 54 | 55 | # Django stuff: 56 | *.log 57 | local_settings.py 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # dotenv 85 | .env 86 | 87 | # virtualenv 88 | .venv 89 | venv/ 90 | ENV/ 91 | 92 | # Spyder project settings 93 | .spyderproject 94 | 95 | # Rope project settings 96 | .ropeproject 97 | ### JetBrains template 98 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 99 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 100 | 101 | # User-specific stuff: 102 | .idea/**/workspace.xml 103 | .idea/**/tasks.xml 104 | .idea/dictionaries 105 | 106 | # Sensitive or high-churn files: 107 | .idea/**/dataSources/ 108 | .idea/**/dataSources.ids 109 | .idea/**/dataSources.xml 110 | .idea/**/dataSources.local.xml 111 | .idea/**/sqlDataSources.xml 112 | .idea/**/dynamic.xml 113 | .idea/**/uiDesigner.xml 114 | 115 | # Gradle: 116 | .idea/**/gradle.xml 117 | .idea/**/libraries 118 | 119 | # Mongo Explorer plugin: 120 | .idea/**/mongoSettings.xml 121 | 122 | ## File-based project format: 123 | *.iws 124 | 125 | ## Plugin-specific files: 126 | 127 | # IntelliJ 128 | /out/ 129 | 130 | # mpeltonen/sbt-idea plugin 131 | .idea_modules/ 132 | 133 | # JIRA plugin 134 | atlassian-ide-plugin.xml 135 | 136 | # Crashlytics plugin (for Android Studio and IntelliJ) 137 | com_crashlytics_export_strings.xml 138 | crashlytics.properties 139 | crashlytics-build.properties 140 | fabric.properties 141 | 142 | .idea/ 143 | -------------------------------------------------------------------------------- /examples/01_karate_unsupervised.py: -------------------------------------------------------------------------------- 1 | import scipy.sparse 2 | 3 | import matplotlib.pyplot as plt 4 | import networkx as nx 5 | import numpy as np 6 | import tensorflow as tf 7 | 8 | import layers.graph as lg 9 | import utils.sparse as us 10 | 11 | g = nx.read_graphml('R/karate.graphml') 12 | 13 | # nx.draw( 14 | # g, 15 | # cmap=plt.get_cmap('jet'), 16 | # node_color=np.log(list(nx.get_node_attributes(g, 'membership').values()))) 17 | 18 | adj = nx.adj_matrix(g) 19 | 20 | # Get important parameters of adjacency matrix 21 | n_nodes = adj.shape[0] 22 | 23 | # Some preprocessing 24 | adj_tilde = adj + np.identity(n=adj.shape[0]) 25 | d_tilde_diag = np.squeeze(np.sum(np.array(adj_tilde), axis=1)) 26 | d_tilde_inv_sqrt_diag = np.power(d_tilde_diag, -1/2) 27 | d_tilde_inv_sqrt = np.diag(d_tilde_inv_sqrt_diag) 28 | adj_norm = np.dot(np.dot(d_tilde_inv_sqrt, adj_tilde), d_tilde_inv_sqrt) 29 | adj_norm_tuple = us.sparse_to_tuple(scipy.sparse.coo_matrix(adj_norm)) 30 | 31 | # Features are just the identity matrix 32 | feat_x = np.identity(n=adj.shape[0]) 33 | feat_x_tuple = us.sparse_to_tuple(scipy.sparse.coo_matrix(feat_x)) 34 | 35 | # TensorFlow placeholders 36 | ph = { 37 | 'adj_norm': tf.sparse_placeholder(tf.float32, name="adj_mat"), 38 | 'x': tf.sparse_placeholder(tf.float32, name="x")} 39 | 40 | l_sizes = [4, 4, 2] 41 | 42 | o_fc1 = lg.GraphConvLayer( 43 | input_dim=feat_x.shape[-1], 44 | output_dim=l_sizes[0], 45 | name='fc1', 46 | activation=tf.nn.tanh)(adj_norm=ph['adj_norm'], x=ph['x'], sparse=True) 47 | 48 | o_fc2 = lg.GraphConvLayer( 49 | input_dim=l_sizes[0], 50 | output_dim=l_sizes[1], 51 | name='fc2', 52 | activation=tf.nn.tanh)(adj_norm=ph['adj_norm'], x=o_fc1) 53 | 54 | o_fc3 = lg.GraphConvLayer( 55 | input_dim=l_sizes[1], 56 | output_dim=l_sizes[2], 57 | name='fc3', 58 | activation=tf.nn.tanh)(adj_norm=ph['adj_norm'], x=o_fc2) 59 | 60 | sess = tf.Session() 61 | sess.run(tf.global_variables_initializer()) 62 | 63 | feed_dict = {ph['adj_norm']: adj_norm_tuple, 64 | ph['x']: feat_x_tuple} 65 | 66 | outputs = sess.run(o_fc3, feed_dict=feed_dict) 67 | x_min, x_max = outputs[:, 0].min(), outputs[:, 0].max() 68 | y_min, y_max = outputs[:, 1].min(), outputs[:, 1].max() 69 | 70 | node_pos_gcn = {n: tuple(outputs[j]) for j, n in enumerate(nx.nodes(g))} 71 | node_pos_ran = {n: (np.random.uniform(low=x_min, high=x_max), 72 | np.random.uniform(low=y_min, high=y_max)) 73 | for j, n in enumerate(nx.nodes(g))} 74 | 75 | all_node_pos = (node_pos_gcn, node_pos_ran) 76 | plot_titles = ('3-layer randomly initialised graph CNN', 'random') 77 | 78 | # Two subplots, unpack the axes array immediately 79 | f, axes = plt.subplots(nrows=2, ncols=1, sharey=True, sharex=True) 80 | 81 | for i, ax in enumerate(axes.flat): 82 | pos = all_node_pos[i] 83 | ax.set_title(plot_titles[i]) 84 | 85 | nx.draw( 86 | g, 87 | cmap=plt.get_cmap('jet'), 88 | node_color=np.log( 89 | list(nx.get_node_attributes(g, 'membership').values())), 90 | pos=pos, ax=ax) 91 | 92 | plt.show() 93 | -------------------------------------------------------------------------------- /examples/02_karate_semisupervised.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | import scipy.sparse 4 | 5 | import matplotlib.pyplot as plt 6 | import networkx as nx 7 | import numpy as np 8 | import tensorflow as tf 9 | 10 | import layers.graph as lg 11 | import utils.sparse as us 12 | 13 | g = nx.read_graphml('R/karate.graphml') 14 | 15 | # nx.draw( 16 | # g, 17 | # cmap=plt.get_cmap('jet'), 18 | # node_color=np.log(list(nx.get_node_attributes(g, 'membership').values()))) 19 | 20 | adj = nx.adj_matrix(g) 21 | 22 | # Get important parameters of adjacency matrix 23 | n_nodes = adj.shape[0] 24 | 25 | # Some preprocessing 26 | adj_tilde = adj + np.identity(n=adj.shape[0]) 27 | d_tilde_diag = np.squeeze(np.sum(np.array(adj_tilde), axis=1)) 28 | d_tilde_inv_sqrt_diag = np.power(d_tilde_diag, -1/2) 29 | d_tilde_inv_sqrt = np.diag(d_tilde_inv_sqrt_diag) 30 | adj_norm = np.dot(np.dot(d_tilde_inv_sqrt, adj_tilde), d_tilde_inv_sqrt) 31 | adj_norm_tuple = us.sparse_to_tuple(scipy.sparse.coo_matrix(adj_norm)) 32 | 33 | # Features are just the identity matrix 34 | feat_x = np.identity(n=adj.shape[0]) 35 | feat_x_tuple = us.sparse_to_tuple(scipy.sparse.coo_matrix(feat_x)) 36 | 37 | # Semi-supervised 38 | memberships = [m - 1 39 | for m in nx.get_node_attributes(g, 'membership').values()] 40 | 41 | nb_classes = len(set(memberships)) 42 | targets = np.array([memberships], dtype=np.int32).reshape(-1) 43 | one_hot_targets = np.eye(nb_classes)[targets] 44 | 45 | # Pick one at random from each class 46 | labels_to_keep = [np.random.choice( 47 | np.nonzero(one_hot_targets[:, c])[0]) for c in range(nb_classes)] 48 | 49 | y_train = np.zeros(shape=one_hot_targets.shape, 50 | dtype=np.float32) 51 | y_val = one_hot_targets.copy() 52 | 53 | train_mask = np.zeros(shape=(n_nodes,), dtype=np.bool) 54 | val_mask = np.ones(shape=(n_nodes,), dtype=np.bool) 55 | 56 | for l in labels_to_keep: 57 | y_train[l, :] = one_hot_targets[l, :] 58 | y_val[l, :] = np.zeros(shape=(nb_classes,)) 59 | train_mask[l] = True 60 | val_mask[l] = False 61 | 62 | 63 | # TensorFlow placeholders 64 | ph = { 65 | 'adj_norm': tf.sparse_placeholder(tf.float32, name="adj_mat"), 66 | 'x': tf.sparse_placeholder(tf.float32, name="x"), 67 | 'labels': tf.placeholder(tf.float32, shape=(n_nodes, nb_classes)), 68 | 'mask': tf.placeholder(tf.int32)} 69 | 70 | l_sizes = [4, 4, 2, nb_classes] 71 | 72 | o_fc1 = lg.GraphConvLayer( 73 | input_dim=feat_x.shape[-1], 74 | output_dim=l_sizes[0], 75 | name='fc1', 76 | activation=tf.nn.tanh)(adj_norm=ph['adj_norm'], x=ph['x'], sparse=True) 77 | 78 | o_fc2 = lg.GraphConvLayer( 79 | input_dim=l_sizes[0], 80 | output_dim=l_sizes[1], 81 | name='fc2', 82 | activation=tf.nn.tanh)(adj_norm=ph['adj_norm'], x=o_fc1) 83 | 84 | o_fc3 = lg.GraphConvLayer( 85 | input_dim=l_sizes[1], 86 | output_dim=l_sizes[2], 87 | name='fc3', 88 | activation=tf.nn.tanh)(adj_norm=ph['adj_norm'], x=o_fc2) 89 | 90 | o_fc4 = lg.GraphConvLayer( 91 | input_dim=l_sizes[2], 92 | output_dim=l_sizes[3], 93 | name='fc4', 94 | activation=tf.identity)(adj_norm=ph['adj_norm'], x=o_fc3) 95 | 96 | 97 | def masked_softmax_cross_entropy(preds, labels, mask): 98 | """Softmax cross-entropy loss with masking.""" 99 | loss = tf.nn.softmax_cross_entropy_with_logits(logits=preds, labels=labels) 100 | mask = tf.cast(mask, dtype=tf.float32) 101 | mask /= tf.reduce_mean(mask) 102 | loss *= mask 103 | return tf.reduce_mean(loss) 104 | 105 | 106 | def masked_accuracy(preds, labels, mask): 107 | """Accuracy with masking.""" 108 | correct_prediction = tf.equal(tf.argmax(preds, 1), tf.argmax(labels, 1)) 109 | accuracy_all = tf.cast(correct_prediction, tf.float32) 110 | mask = tf.cast(mask, dtype=tf.float32) 111 | mask /= tf.reduce_mean(mask) 112 | accuracy_all *= mask 113 | return tf.reduce_mean(accuracy_all) 114 | 115 | 116 | with tf.name_scope('optimizer'): 117 | loss = masked_softmax_cross_entropy( 118 | preds=o_fc4, labels=ph['labels'], mask=ph['mask']) 119 | 120 | accuracy = masked_accuracy( 121 | preds=o_fc4, labels=ph['labels'], mask=ph['mask']) 122 | 123 | optimizer = tf.train.AdamOptimizer(learning_rate=1e-2) 124 | 125 | opt_op = optimizer.minimize(loss) 126 | 127 | feed_dict_train = {ph['adj_norm']: adj_norm_tuple, 128 | ph['x']: feat_x_tuple, 129 | ph['labels']: y_train, 130 | ph['mask']: train_mask} 131 | 132 | feed_dict_val = {ph['adj_norm']: adj_norm_tuple, 133 | ph['x']: feat_x_tuple, 134 | ph['labels']: y_val, 135 | ph['mask']: val_mask} 136 | 137 | sess = tf.Session() 138 | sess.run(tf.global_variables_initializer()) 139 | 140 | epochs = 300 141 | save_every = 50 142 | 143 | t = time.time() 144 | outputs = {} 145 | # Train model 146 | for epoch in range(epochs): 147 | # Construct feed dictionary 148 | 149 | # Training step 150 | _, train_loss, train_acc = sess.run( 151 | (opt_op, loss, accuracy), feed_dict=feed_dict_train) 152 | 153 | if epoch % save_every == 0: 154 | # Validation 155 | val_loss, val_acc = sess.run((loss, accuracy), feed_dict=feed_dict_val) 156 | 157 | # Print results 158 | print("Epoch:", '%04d' % (epoch + 1), 159 | "train_loss=", "{:.5f}".format(train_loss), 160 | "train_acc=", "{:.5f}".format(train_acc), 161 | "val_loss=", "{:.5f}".format(val_loss), 162 | "val_acc=", "{:.5f}".format(val_acc), 163 | "time=", "{:.5f}".format(time.time() - t)) 164 | 165 | feed_dict_output = {ph['adj_norm']: adj_norm_tuple, 166 | ph['x']: feat_x_tuple} 167 | 168 | output = sess.run(o_fc3, feed_dict=feed_dict_output) 169 | outputs[epoch] = output 170 | 171 | node_positions = {o: {n: tuple(outputs[o][j]) 172 | for j, n in enumerate(nx.nodes(g))} 173 | for o in outputs} 174 | plot_titles = {o: 'epoch {o}'.format(o=o) for o in outputs} 175 | 176 | # Two subplots, unpack the axes array immediately 177 | f, axes = plt.subplots(nrows=2, ncols=3, sharey=True, sharex=True) 178 | 179 | e = list(node_positions.keys()) 180 | 181 | for i, ax in enumerate(axes.flat): 182 | pos = node_positions[e[i]] 183 | ax.set_title(plot_titles[e[i]]) 184 | 185 | nx.draw( 186 | g, 187 | cmap=plt.get_cmap('jet'), 188 | node_color=np.log( 189 | list(nx.get_node_attributes(g, 'membership').values())), 190 | pos=pos, ax=ax) 191 | 192 | plt.show() 193 | -------------------------------------------------------------------------------- /R/karate.graphml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | Zachary 12 | 13 | 1 14 | red 15 | 16 | 17 | 1 18 | red 19 | 20 | 21 | 1 22 | red 23 | 24 | 25 | 1 26 | red 27 | 28 | 29 | 2 30 | green 31 | 32 | 33 | 2 34 | green 35 | 36 | 37 | 2 38 | green 39 | 40 | 41 | 1 42 | red 43 | 44 | 45 | 3 46 | blue 47 | 48 | 49 | 3 50 | blue 51 | 52 | 53 | 2 54 | green 55 | 56 | 57 | 1 58 | red 59 | 60 | 61 | 1 62 | red 63 | 64 | 65 | 1 66 | red 67 | 68 | 69 | 3 70 | blue 71 | 72 | 73 | 3 74 | blue 75 | 76 | 77 | 2 78 | green 79 | 80 | 81 | 1 82 | red 83 | 84 | 85 | 3 86 | blue 87 | 88 | 89 | 1 90 | red 91 | 92 | 93 | 3 94 | blue 95 | 96 | 97 | 1 98 | red 99 | 100 | 101 | 3 102 | blue 103 | 104 | 105 | 4 106 | cyan 107 | 108 | 109 | 4 110 | cyan 111 | 112 | 113 | 4 114 | cyan 115 | 116 | 117 | 3 118 | blue 119 | 120 | 121 | 4 122 | cyan 123 | 124 | 125 | 4 126 | cyan 127 | 128 | 129 | 3 130 | blue 131 | 132 | 133 | 3 134 | blue 135 | 136 | 137 | 4 138 | cyan 139 | 140 | 141 | 3 142 | blue 143 | 144 | 145 | 3 146 | blue 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | --------------------------------------------------------------------------------