{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "##
Plotly viz of a 3d igraph graph defined from a scipy sparse adjacency matrix read from a MTX file
##" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "University of Florida provides a large sparse matrix collection: [https://sparse.tamu.edu/](https://sparse.tamu.edu/). \n", "Most of these matrices are adjacency matrices of some graphs. Here we illustrate how such a matrix can be converted to an `igraph.Graph` instance, with a 3d layout, plotted via Plotly." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import igraph as ig\n", "from scipy.io import mmread\n", "from scipy.sparse import issparse\n", "import plotly.graph_objs as go" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Function that returns an `igraph.Graph` from a sparse matrix:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "def igraph_from_sparse(A):\n", " # Generate an undirected igraph.Graph from a symmetric sparse matrix read from a\n", " # file (mtx extension)\n", " \n", " if not issparse(A):\n", " raise ValueError('Your matrix is not sparse')\n", " \n", " n_rows, n_cols = A.shape\n", " if n_rows != n_cols:\n", " raise ValueError('The adjacency matrix should be a square matrix ') \n", " \n", " G = ig.Graph(n=n_rows, directed=False) \n", " for i, row in enumerate(A.tolil().rows):\n", " J = list(filter((i).__le__, row))\n", " G.add_edges([(i,j) for j in J]) \n", " return G" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Define a function that associates the nodes' distances to the mean position. These numerical values are then mapped to a color in a colorscale:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "def dist_mean(position, axis=1):\n", " # returns the distance of each node position to the mean position\n", " \n", " position=np.asarray(position)\n", " pos_mean = np.mean(position, axis=0)\n", " return np.sum(np.sqrt((position - pos_mean) ** 2), axis=1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The following three functions extract data for plotting the graph nodes and edges as `scatter3d` Plotly plots:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "def get_plotly_data(E, coords):\n", " # E is the list of tuples representing the graph edges\n", " # coords is the list of node coordinates \n", " \n", " r, c = np.asarray(coords).shape\n", " if c != 3:\n", " raise ValueError('Node coordinates are not 3d')\n", " \n", " Xnodes=[coords[k][0] for k in range(r)]# x-coordinates of nodes\n", " Ynodes=[coords[k][1] for k in range(r)]# y-coordnates of nodes\n", " Znodes=[coords[k][2] for k in range(r)]# z-coords of nodes\n", " \n", " Xedges=[]\n", " Yedges=[]\n", " Zedges=[]\n", " for e in E:\n", " Xedges.extend([coords[e[0]][0], coords[e[1]][0], None]) \n", " Yedges.extend([coords[e[0]][1], coords[e[1]][1], None]) \n", " Zedges.extend([coords[e[0]][2], coords[e[1]][2], None]) \n", " \n", " return Xnodes, Ynodes, Znodes, Xedges, Yedges, Zedges\n", "\n", "def get_node_trace(x, y, z, labels=None, marker_size=4, marker_color= 'blue', colorscale='Viridis', \n", " reversescale=False, line_color='rgb(50,50,50)', line_width=0):\n", " if isinstance(marker_color, str):\n", " colorscale=None\n", " showscale=False\n", " return dict(type='scatter3d',\n", " x=x,\n", " y=y,\n", " z=z,\n", " mode='markers',\n", " marker=dict(size=marker_size, \n", " color= marker_color,\n", " colorscale=colorscale,\n", " reversescale=reversescale,\n", " showscale=showscale,\n", " colorbar=dict(thickness=20, ticklen=4, len=0.65),\n", " line=dict(color=line_color, \n", " width=line_width)),\n", " text=labels,\n", " hoverinfo='text') \n", " \n", "\n", "def get_edge_trace(x, y, z, linecolor='rgb(150,150,150)', linewidth=1.5):\n", " \n", " return dict(type='scatter3d',\n", " x=x,\n", " y=y,\n", " z=z,\n", " mode='lines',\n", " line=dict(color=linecolor, \n", " width=linewidth),\n", " hoverinfo='none') " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Read the adjacency matrix from a MTX file (the MTX file format is presented here: http://networkrepository.com/mtx-matrix-market-format.html)). \n", "\n", "This MTX (or MM) file was derived from a FEM (Finite Element) problem, by the [AG-Monien group](https://www.cise.ufl.edu/research/sparse/mat/AG-Monien/README.txt). \n", "\n", "The 3d data set of node positions associated by a 3d graph layout can be used for its topological data analysis, and manifold recognition [https://arxiv.org/abs/1806.08460](https://arxiv.org/abs/1806.08460)." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "scipy.sparse.coo.coo_matrix" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "A = mmread('Data/airfoil1.mtx') \n", "type(A)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Define an `igraph.Graph` from this sparse matrix:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "4253" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "H = igraph_from_sparse(A)\n", "len(H.vs)" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "nodes = list(range(len(H.vs)))\n", "edges= [e.tuple for e in H.es]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Associate node locations, via a 3d `igraph.Layout`, like Kamada-Kawai - `'kk3d'` \n", "or Fruchtenberg-Reingold - `'fr3d'` layout: " ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "#pos = H.layout('kk3d') # pos is a list of 3 lists\n", "#pos =np.asarray(pos)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Scale the node positions, dividing each coordinate by max coordinates, and save `pos` as a npy file to avoid graph layout computations at each notebook run:" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "#pos=pos/np.max(pos, axis=0) \n", "#np.save('airfoil1-kk3d.npy', pos)" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [], "source": [ "pos=np.load('Data/airfoil1-kk3d.npy') " ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(4253, 3)" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "pos.shape" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Define the Plotly layout of the plot:" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [], "source": [ "width = 850\n", "height = 850\n", "title = \"3d network from a sparse matrix, derived from a FEM problem\"+\\\n", " \"
Data: [1]\"\n", "\n", "axis = dict(showbackground=True, \n", " backgroundcolor=\"rgb(230, 230,230)\",\n", " gridcolor=\"rgb(255, 255, 255)\", \n", " zerolinecolor=\"rgb(255, 255, 255)\")\n", "\n", "\n", "x_eye, y_eye, z_eye = 1.45, 1.45, 0.85 #camera eye position\n", "\n", "layout3d = dict(title=title,\n", " font= dict(family='Balto'),\n", " showlegend=False,\n", " autosize=False,\n", " width=width,\n", " height=height,\n", " scene=dict(xaxis= dict(axis),\n", " yaxis=dict(axis), \n", " zaxis=dict(axis),\n", " aspectratio=dict(x=1., y=1., z=1),\n", " camera=dict(eye=dict(x=x_eye, y=y_eye, z=z_eye))),\n", " hovermode='closest',\n", " )" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [], "source": [ "Xn, Yn, Zn, Xe, Ye, Ze = get_plotly_data(edges, pos)\n", "trace1 = get_edge_trace(Xe, Ye, Ze)\n", "trace2 = get_node_trace(Xn, Yn, Zn, nodes, marker_size=2.5)" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [], "source": [ "fw = go.FigureWidget(data=[trace1, trace2], layout=layout3d)\n", "#fw" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import plotly.plotly as py\n", "py.sign_in('empet','api-key')\n", "py.iplot(fw, filename='airfoil1-blue')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let us update the `FigureWidget` to color the graph nodes according to distance to mean:" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [], "source": [ "pl_brewer = [[0.0, '#006837'], #from green to red http://colorbrewer2.org/#type=diverging&scheme=RdYlGn&n=11\n", " [0.1, '#1a9850'],\n", " [0.2, '#66bd63'],\n", " [0.3, '#a6d96a'],\n", " [0.4, '#d9ef8b'],\n", " [0.5, '#ffffbf'],\n", " [0.6, '#fee08b'],\n", " [0.7, '#fdae61'],\n", " [0.8, '#f46d43'],\n", " [0.9, '#d73027'],\n", " [1.0, '#a50026']]" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [], "source": [ "fw.data[1].marker.update(color= dist_mean(pos),#\n", " colorscale=pl_brewer,\n", " showscale=True) \n", "#fw " ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "py.iplot(fw, filename='airfoil1kk')" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n" ], "text/plain": [ "" ] }, "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from IPython.core.display import HTML\n", "def css_styling():\n", " styles = open(\"./custom.css\", \"r\").read()\n", " return HTML(styles)\n", "css_styling()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.4" } }, "nbformat": 4, "nbformat_minor": 2 }