Class 12: Visualization 1 — Python#
Goal of today’s class:
Explore generic data visualization with matplotlib
Offer tips about network visualization with networkx and matplotlib
Visualize different network datasets from papers
Come in. Sit down. Open Teams.
Make sure your notebook from last class is saved.
Open up the Jupyter Lab server.
Open up the Jupyter Lab terminal.
Activate Conda:
module load anaconda3/2022.05
Activate the shared virtual environment:
source activate /courses/PHYS7332.202510/shared/phys7332-env/
Run
python3 git_fixer2.py
Github:
git status (figure out what files have changed)
git add … (add the file that you changed, aka the
_MODIFIED
one(s))git commit -m “your changes”
git push origin main
import numpy as np
import itertools as it
import requests
from bs4 import BeautifulSoup
import networkx as nx
import pandas as pd
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.patheffects as path_effects
from matplotlib.gridspec import GridSpec
from matplotlib import rc
# rc('font', **{'family':'serif','serif':['Palatino']}) # if you want serif fonts, uncomment this
# rc('text', usetex=True) # use latex fonts
rc('axes', axisbelow=True, fc='w')
rc('figure', fc='w')
rc('savefig', fc='w')
🚨 Starting the class with a challenge 🚨#
You have 10 minutes, starting now.
In the data folder, you’ll find data/mystery.pickle
. Working alone, import the data using the code below, and create some sort of visualization of the data. It can be a network or other types of data visualization.
import pickle
with open('data/mystery.pickle', 'rb') as handle:
mystery_network = pickle.load(handle)
# your code here!
pass
This task isn’t meant to evaluate how well you can visualize some random dataset… it’s designed to highlight what visual features you prioritize under time constraints. Things like:
Color
Text
Emphasis on specific features
Informativeness
Qualitative vs. quantitative features
etc.
Color#
Color is one of the most powerful tools in data visualization. It allows us to communicate complex information quickly and effectively, but it also carries significant responsibility. The choice of colors can greatly affect the clarity, accessibility, and impact of visualizations. When colors are misused, they can distort the message or exclude viewers, particularly those with color vision deficiencies. In the documentation for matplotlib
, there’s a great background on color!
In this lesson, we focus on the importance of using color thoughtfully in data visualizations, including:
Understand how different colors interact with one another.
Learn to choose colors that ensure accessibility for people with colorblindness.
Explore properties such as lightness, hue, and saturation to create visualizations that are aesthetically pleasing and easy to interpret.
Practice converting between color formats to work efficiently across various platforms and visualization tools.
By the end of this lesson, you will be able to apply these concepts to create more effective, inclusive, and professional data visualizations that enhance the storytelling power of your data.
Simulating colorblindness and color-anomalous viewers#
This section provides a tool for analyzing and visualizing colors. It lets you explore various color properties such as lightness, hue, and saturation. Also, this code will let you simulate how colors appear to individuals with different types of colorblindness, offering insights into accessibility and color design considerations.
namedColors = {'aliceblue':'#f0f8ff', 'antiquewhite':'#faebd7', 'aqua':'#0ff',
'aquamarine':'#7fffd4', 'azure':'#f0ffff', 'beige':'#f5f5dc', 'bisque':'#ffe4c4',
'black':'#000', 'blanchedalmond':'#ffebcd', 'blue':'#00f', 'blueviolet':'#8a2be2',
'brown':'#a52a2a', 'burlywood':'#deb887', 'cadetblue':'#5f9ea0', 'chartreuse':'#7fff00',
'chocolate':'#d2691e', 'coral':'#ff7f50', 'cornflowerblue':'#6495ed', 'cornsilk':'#fff8dc',
'crimson':'#dc143c', 'cyan':'#0ff', 'darkblue':'#00008b', 'darkcyan':'#008b8b',
'darkgoldenrod':'#b8860b', 'darkgray':'#a9a9a9', 'darkgrey':'#a9a9a9', 'darkgreen':'#006400',
'darkkhaki':'#bdb76b', 'darkmagenta':'#8b008b', 'darkolivegreen':'#556b2f', 'darkorange':'#ff8c00',
'darkorchid':'#9932cc', 'darkred':'#8b0000', 'darksalmon':'#e9967a', 'darkseagreen':'#8fbc8f',
'darkslateblue':'#483d8b', 'darkslategray':'#2f4f4f', 'darkslategrey':'#2f4f4f',
'darkturquoise':'#00ced1', 'darkviolet':'#9400d3', 'deeppink':'#ff1493', 'deepskyblue':'#00bfff',
'dimgray':'#696969', 'dimgrey':'#696969', 'dodgerblue':'#1e90ff', 'firebrick':'#b22222',
'floralwhite':'#fffaf0', 'forestgreen':'#228b22', 'fuchsia':'#f0f', 'gainsboro':'#dcdcdc',
'ghostwhite':'#f8f8ff', 'gold':'#ffd700', 'goldenrod':'#daa520', 'gray':'#808080', 'grey':'#808080',
'green':'#008000', 'greenyellow':'#adff2f', 'honeydew':'#f0fff0', 'hotpink':'#ff69b4',
'indianred':'#cd5c5c', 'indigo':'#4b0082', 'ivory':'#fffff0', 'khaki':'#f0e68c', 'lavender':'#e6e6fa',
'lavenderblush':'#fff0f5', 'lawngreen':'#7cfc00', 'lemonchiffon':'#fffacd', 'lightblue':'#add8e6',
'lightcoral':'#f08080', 'lightcyan':'#e0ffff', 'lightgoldenrodyellow':'#fafad2', 'lightgray':'#d3d3d3',
'lightgrey':'#d3d3d3', 'lightgreen':'#90ee90', 'lightpink':'#ffb6c1', 'lightsalmon':'#ffa07a',
'lightseagreen':'#20b2aa', 'lightskyblue':'#87cefa', 'lightslategray':'#789', 'lightslategrey':'#789',
'lightsteelblue':'#b0c4de', 'lightyellow':'#ffffe0', 'lime':'#0f0', 'limegreen':'#32cd32',
'linen':'#faf0e6', 'magenta':'#f0f', 'maroon':'#800000', 'mediumaquamarine':'#66cdaa',
'mediumblue':'#0000cd', 'mediumorchid':'#ba55d3', 'mediumpurple':'#9370d8', 'mediumseagreen':'#3cb371',
'mediumslateblue':'#7b68ee', 'mediumspringgreen':'#00fa9a', 'mediumturquoise':'#48d1cc',
'mediumvioletred':'#c71585', 'midnightblue':'#191970', 'mintcream':'#f5fffa', 'mistyrose':'#ffe4e1',
'moccasin':'#ffe4b5', 'navajowhite':'#ffdead', 'navy':'#000080', 'oldlace':'#fdf5e6', 'olive':'#808000',
'olivedrab':'#6b8e23', 'orange':'#ffa500', 'orangered':'#ff4500', 'orchid':'#da70d6',
'palegoldenrod':'#eee8aa', 'palegreen':'#98fb98', 'paleturquoise':'#afeeee', 'palevioletred':'#d87093',
'papayawhip':'#ffefd5', 'peachpuff':'#ffdab9', 'peru':'#cd853f', 'pink':'#ffc0cb', 'plum':'#dda0dd',
'powderblue':'#b0e0e6', 'purple':'#800080', 'rebeccapurple':'#639', 'red':'#f00', 'rosybrown':'#bc8f8f',
'royalblue':'#4169e1', 'saddlebrown':'#8b4513', 'salmon':'#fa8072', 'sandybrown':'#f4a460',
'seagreen':'#2e8b57', 'seashell':'#fff5ee', 'sienna':'#a0522d', 'silver':'#c0c0c0', 'skyblue':'#87ceeb',
'slateblue':'#6a5acd', 'slategray':'#708090', 'slategrey':'#708090', 'snow':'#fffafa',
'springgreen':'#00ff7f', 'steelblue':'#4682b4', 'tan':'#d2b48c', 'teal':'#008080', 'thistle':'#d8bfd8',
'tomato':'#ff6347', 'turquoise':'#40e0d0', 'violet':'#ee82ee', 'wheat':'#f5deb3', 'white':'#fff',
'whitesmoke':'#f5f5f5', 'yellow':'#ff0', 'yellowgreen':'#9acd32'}
colorblind_mappings = {'Protanopia':'Dichromacy',
'Deuteranopia':'Dichromacy',
'Tritanopia':'Dichromacy',
'Protanomaly':'Trichromacy',
'Deuteranomaly':'Trichromacy',
'Tritanomaly':'Trichromacy',
'Achromatopsia':'Monochromacy',
'Achromatomaly':'Monochromacy'}
all_vals = ['Original Color', 'Protanopia', 'Deuteranopia', #'Tritanopia',
'Protanomaly', 'Deuteranomaly', #'Tritanomaly',
'Achromatopsia', 'Achromatomaly', 'Grayscale']
colorblind_rates = {'Original Color':"",
'Protanopia':' - m: 2.4%; f: 0.02%',
'Deuteranopia':' - m: 1.2%; f: 0.01%',
# 'Tritanopia':' - m: 0.001%; f: 0.03%',
'Protanomaly':' - m: 1.3%; f: 0.02%',
'Deuteranomaly':' - m: 5.0%; f: 0.35%',
# 'Tritanomaly':' - m: 0.0001%; f: 0.0001%',
'Achromatopsia':' - ~0.003%',
'Achromatomaly':'',
'Grayscale':''}
# rc('font', **{'family':'serif','serif':['Palatino']}) # if you want serif fonts, uncomment this
# rc('text', usetex=True) # use latex fonts
rc('axes', axisbelow=True, fc='w')
rc('figure', fc='w')
rc('savefig', fc='w')
# set some image parameters
fs = 10.0
lw = 2.25
fig_w1 = 7.0
fig_h1 = 3.0
al = 0.9
labcol = '.1'
pe1 = [path_effects.Stroke(linewidth=lw*1.1, foreground='w',alpha=al), path_effects.Normal()]
pe2 = [path_effects.Stroke(linewidth=1.0, foreground='w'), path_effects.Normal()]
pe3 = [path_effects.Stroke(linewidth=0.2, foreground='.1'), path_effects.Normal()]
def get_colorblindness_colors(hex_col, colorblind_types='all'):
"""
Generates color representations for various types of colorblindness.
Parameters
----------
hex_col (str or tuple)
The color you wish to check, in hex code format e.g. "#ffffff" or rgb
format e.g. (1,255,20)
colorblind_types (str or list)
If "all", the function returns a dictionary with all of the following:
Protanopia - ("Dichromat" family)
The viewer sees no red.
Deuteranopia - ("Dichromat" family)
The viewer sees no green.
Tritanopia - ("Dichromat" family)
The viewer sees no blue.
Protanomaly - ("Anomalous Trichromat" family)
The viewer sees low amounts of red.
Deuteranomaly - ("Anomalous Trichromat" family).
The viewer sees low amounts of green.
Tritanomaly - ("Anomalous Trichromat" family).
The viewer sees low amounts of blue.
Achromatopsia - ("Monochromat" family)
The viewer sees no color at all.
Achromatomaly - ("Monochromat" family)
The viewer sees low amounts of color.
Returns
-------
colorblind_output (dict)
dictionary where the keys are the type of colorblindness and the values
are the re-colored version of your original hex_col. This also includes
a grayscale version of the color.
"""
all_vals = ['Original Color', 'Protanopia', 'Deuteranopia',
'Protanomaly', 'Deuteranomaly',
'Achromatopsia', 'Achromatomaly', 'Grayscale']
if type(hex_col)!=str:
if len(hex_col)!=3:
print('Input a hex color please.')
return ''
else:
hex_col = rgb_to_hex(hex_col)
else:
if "#" not in hex_col and len(hex_col)!=6:
try:
hex_col = namedColors[hex_col]
except:
print('Input a hex color please.')
return ''
base_url = 'https://convertingcolors.com/'
hex_url = base_url + 'hex-color-%s.html'%hex_col.replace("#",'')
print(hex_url)
reqs = requests.get(hex_url)
soup = BeautifulSoup(reqs.text, 'html.parser')
colorblind_sec = soup.find_all('details',{'id':'blindness-simulation'})[0]
colorblind_labels = [i.text for i in colorblind_sec.find_all('h3')]
# colorblind_colors = np.unique([i.text for i in colorblind_sec.find_all('div')])
tmp = np.unique([i.text for i in colorblind_sec.find_all('div')])
colorblind_colors = [i for i in tmp for x in all_vals[1:] if x in i and all_vals[0] not in i]
colorblind_output = {"Original Color":hex_col}
# for i in colorblind_mappings.keys():
# for j in colorblind_colors:
# # if i in j:
# hex_col_j = j
# colorblind_output[i] = hex_col_j
for i in colorblind_mappings.keys():
for j in colorblind_colors:
if i in j:
hex_col_j = "#"+j.split('%')[-1]
# hex_col_j = j.replace(i,'#')
colorblind_output[i] = hex_col_j
if colorblind_types!='all':
if type(colorblind_types) == str:
colorblind_types = [colorblind_types]
new_out = {'Original Color':hex_col}
for c in colorblind_types:
new_out[c] = colorblind_output[c]
colorblind_output = new_out
colorblind_output['Grayscale'] = hex_to_grayscale(hex_col)
for xx in all_vals:
if xx not in list(colorblind_output.keys()):
colorblind_output[xx] = hex_col
return {hex_col:colorblind_output}
def rgb_to_hsv(rgb):
"""
Converts an RGB color to HSV format.
Parameters
----------
rgb : tuple of ints
A tuple containing the RGB values (R, G, B) where each value is in the
range 0 to 255.
Returns
-------
numpy.ndarray
An array representing the HSV equivalent of the input RGB values.
"""
rgb = np.array(rgb)
rgb = rgb.astype('float')
maxv = np.amax(rgb)
maxc = np.argmax(rgb)
minv = np.amin(rgb)
minc = np.argmin(rgb)
hsv = np.zeros(rgb.shape, dtype='float')
hsv[maxc == minc, 0] = np.zeros(hsv[maxc == minc, 0].shape)
hsv[maxc == 0, 0] = (((rgb[..., 1] - rgb[..., 2]) * 60.0 /\
(maxv - minv + np.spacing(1))) % 360.0)[maxc == 0]
hsv[maxc == 1, 0] = (((rgb[..., 2] - rgb[..., 0]) * 60.0 /\
(maxv - minv + np.spacing(1))) + 120.0)[maxc == 1]
hsv[maxc == 2, 0] = (((rgb[..., 0] - rgb[..., 1]) * 60.0 /\
(maxv - minv + np.spacing(1))) + 240.0)[maxc == 2]
hsv[maxv == 0, 1] = np.zeros(hsv[maxv == 0, 1].shape)
hsv[maxv != 0, 1] = (1 - minv / (maxv + np.spacing(1)))[maxv != 0]
hsv[..., 2] = maxv/255
return hsv
def lightness(hex_col):
"""
Calculates the perceived lightness of a color.
Parameters
----------
hex_col : str
A hex code representing the color (e.g., "#ffffff").
Returns
-------
float
The perceived lightness of the color, ranging from 0 (dark) to 1 (light).
"""
rgb = hex_to_rgb(hex_col)
r,g,b = rgb
denominator = 255 * (0.299 + 0.587 + 0.111)**(0.5)
L = (0.299 * r**2 + 0.587 * g**2 + 0.111 * b**2)**(0.5) / denominator
return L
def saturation(hex_col):
"""
Calculates the saturation of a given hex color.
Parameters
----------
hex_col : str
A hex code representing the color (e.g., "#ffffff").
Returns
-------
float
The saturation value ranging from 0 (unsaturated, grayscale) to 1 (fully saturated).
"""
hsv = rgb_to_hsv(hex_to_rgb(hex_col))
return hsv[1]
def hue(hex_col):
"""
Calculates the hue of a given hex color.
Parameters
----------
hex_col : str
A hex code representing the color (e.g., "#ffffff").
Returns
-------
float
The hue value in degrees, ranging from 0 to 360.
"""
hsv = rgb_to_hsv(hex_to_rgb(hex_col))
return hsv[0]
def rgb_to_hex(rgb):
"""
Converts an RGB color to hex format.
Parameters
----------
rgb : tuple of ints
A tuple containing the RGB values (R, G, B) where each value is in the
range 0 to 255.
Returns
-------
str
The hex code representation of the RGB color.
"""
r,g,b=rgb
return '#%02x%02x%02x' % (r,g,b)
def hex_to_rgb(value):
"""
Converts a hex color code to an RGB tuple.
Parameters
----------
value : str
A hex code representing the color (e.g., "#ffffff").
Returns
-------
tuple of ints
A tuple containing the RGB values (R, G, B) where each value is in the range 0 to 255.
"""
value = value.lstrip('#')
lv = len(value)
return tuple(int(value[i:i+lv//3], 16) for i in range(0, lv, lv//3))
def hex_to_grayscale(hex_col):
"""
Converts a hex color code to its grayscale equivalent.
Parameters
----------
hex_col : str
A hex code representing the color (e.g., "#ffffff").
Returns
-------
str
The grayscale value of the color as a normalized float (0.0 to 1.0).
"""
img = hex_to_rgb(hex_col)
R, G, B = img
imgGray = 0.2989 * R + 0.5870 * G + 0.1140 * B
return '%.7f'%(imgGray/255)
all_vals = ['Original Color', 'Protanopia', 'Deuteranopia', 'Tritanopia',
'Protanomaly', 'Deuteranomaly', 'Tritanomaly',
'Achromatopsia', 'Achromatomaly', 'Grayscale']
cmap_name = 'Test Colormap 1'
cols1 = ['#DAAA62','#1E638F','#C3687D']
cols1_dict = {}
for col in cols1:
col1_dict = get_colorblindness_colors(col)
cols1_dict[col] = col1_dict[col]
https://convertingcolors.com/hex-color-DAAA62.html
---------------------------------------------------------------------------
IndexError Traceback (most recent call last)
Cell In[9], line 7
4 cols1_dict = {}
6 for col in cols1:
----> 7 col1_dict = get_colorblindness_colors(col)
8 cols1_dict[col] = col1_dict[col]
Cell In[7], line 61, in get_colorblindness_colors(hex_col, colorblind_types)
58 reqs = requests.get(hex_url)
59 soup = BeautifulSoup(reqs.text, 'html.parser')
---> 61 colorblind_sec = soup.find_all('details',{'id':'blindness-simulation'})[0]
62 colorblind_labels = [i.text for i in colorblind_sec.find_all('h3')]
63 # colorblind_colors = np.unique([i.text for i in colorblind_sec.find_all('div')])
IndexError: list index out of range
w = 2.0
h = 2.0
ncols = 3
nrows = 3
tups = list(it.product(range(nrows), range(ncols)))
tups = [(i[0]+2,i[1]) for i in tups]
fig = plt.figure(figsize=(7,3.25+len(cols1)*0.5),dpi=200)
plt.subplots_adjust(wspace=0.15,hspace=0.175+0.025*len(cols1))
gs = fig.add_gridspec(5,8,height_ratios=[1.25,0,1,1,1])
for i,cb in enumerate(all_vals):
if i == 0:
axi = fig.add_subplot(gs[0,0:5])
else:
axi = fig.add_subplot(gs[tups[i-1][0],(tups[i-1][1]*3):(tups[i-1][1]*3)+2])
if cb in ['Tritanopia','Tritanomaly']:
axi.set_axis_off()
continue
cols1_dict_cb = [cols1_dict[col][cb] for col in cols1]
rv0 = 1/len(cols1_dict_cb)
for li,rv in enumerate(np.linspace(rv0,1,len(cols1_dict_cb))):
lv = rv-rv0
axi.fill_between([0,1],lv,rv,lw=0,color=cols1_dict_cb[li])
if i == 0:
ttt = "Hex: %s"%cols1_dict_cb[li].replace('#','')
ttt = ttt + ' --- RGB: %s'%str(hex_to_rgb(cols1_dict_cb[li]))
axi.text(0.99,(lv+rv)/2,ttt,
color='w', fontsize='xx-small',ha='left',va='center')
else:
axi.text(0.99,(lv+rv)/2,"hex: %s"%cols1_dict_cb[li].replace('#',''),
color='w', fontsize='xx-small',ha='left',va='center')
axi.set_xticks([])
axi.set_yticks([])
axi.set_xlim(1,0)
axi.set_ylim(1,0)
axi.set_axis_off()
if i == 0:
axi.set_title(cb,ha='left',x=0,color=labcol,va='top',
y=1.01+len(cols1)*0.005,fontsize='small')
else:
axi.set_title(cb+colorblind_rates[cb],ha='left',x=0,color=labcol,
va='top',y=1.0,fontsize='xx-small')
lightn = [lightness(c) for c in cols1]
saturn = [saturation(c) for c in cols1]
huen = [hue(c) for c in cols1]
titles = ['Lightness','Saturation','Hue']
ylims = [(-0.05, 1.05),(-0.05, 1.05),(-10, 370)]
for vi,v in enumerate([lightn,saturn,huen]):
axi = fig.add_subplot(gs[0,vi+5])
axi.vlines(v,[i+0 for i in range(len(cols1))],
[i+1 for i in range(len(cols1))], color=cols1, lw=4)
axi.plot(v,[i+0.5 for i in range(len(cols1))],color=labcol,
lw=0.5, ls='--',marker='.',markersize=1)
axi.set_title(titles[vi],ha='left',x=0,color=labcol,va='top',
y=1.01+len(cols1)*0.005,fontsize='small')
axi.set_ylim(len(cols1),0)
axi.hlines(np.linspace(1,len(cols1),len(cols1)),
ylims[vi][0],ylims[vi][1], lw=0.2, color='.6')
axi.set_xlim(ylims[vi][0],ylims[vi][1])
axi.set_xticks([])
axi.set_yticks([])
axi.tick_params(pad=2, colors='.3', length=0)
for r in ['bottom','top','right','left']:
axi.spines[r].set_color('.8')
axi.spines[r].set_linewidth(0.5)
plt.savefig('images/pngs/color_explorer_%s.png'%cmap_name.replace(' ',''),
dpi=600,bbox_inches='tight')
plt.savefig('images/pdfs/color_explorer_%s.pdf'%cmap_name.replace(' ',''),
bbox_inches='tight')
plt.show()
![_images/dd41ac74649ee4c8e730e8130c01db18e07fa36d6c5367b42c2ca4fa35d6819a.png](_images/dd41ac74649ee4c8e730e8130c01db18e07fa36d6c5367b42c2ca4fa35d6819a.png)
cmap_name = 'Test Colormap 2'
cols1 = ['#008795', '#4092aa', '#629cc0', '#7fa6d6',
'#e69d93', '#dea8ab', '#d5b4c4', '#c8c0dd']
cols1_dict = {}
for col in cols1:
col1_dict = get_colorblindness_colors(col)
cols1_dict[col] = col1_dict[col]
https://convertingcolors.com/hex-color-008795.html
https://convertingcolors.com/hex-color-4092aa.html
https://convertingcolors.com/hex-color-629cc0.html
https://convertingcolors.com/hex-color-7fa6d6.html
https://convertingcolors.com/hex-color-e69d93.html
https://convertingcolors.com/hex-color-dea8ab.html
https://convertingcolors.com/hex-color-d5b4c4.html
https://convertingcolors.com/hex-color-c8c0dd.html
w = 2.0
h = 2.0
ncols = 3
nrows = 3
tups = list(it.product(range(nrows), range(ncols)))
tups = [(i[0]+2,i[1]) for i in tups]
fig = plt.figure(figsize=(7,3.25+len(cols1)*0.5),dpi=200)
plt.subplots_adjust(wspace=0.15,hspace=0.175+0.025*len(cols1))
gs = fig.add_gridspec(5,8,height_ratios=[1.25,0,1,1,1])
for i,cb in enumerate(all_vals):
if i == 0:
axi = fig.add_subplot(gs[0,0:5])
else:
axi = fig.add_subplot(gs[tups[i-1][0],(tups[i-1][1]*3):(tups[i-1][1]*3)+2])
if cb in ['Tritanopia','Tritanomaly']:
axi.set_axis_off()
continue
cols1_dict_cb = [cols1_dict[col][cb] for col in cols1]
rv0 = 1/len(cols1_dict_cb)
for li,rv in enumerate(np.linspace(rv0,1,len(cols1_dict_cb))):
lv = rv-rv0
axi.fill_between([0,1],lv,rv,lw=0,color=cols1_dict_cb[li])
if i == 0:
ttt = "Hex: %s"%cols1_dict_cb[li].replace('#','')
ttt = ttt + ' --- RGB: %s'%str(hex_to_rgb(cols1_dict_cb[li]))
axi.text(0.99,(lv+rv)/2,ttt,
color='w', fontsize='xx-small',ha='left',va='center')
else:
axi.text(0.99,(lv+rv)/2,"hex: %s"%cols1_dict_cb[li].replace('#',''),
color='w', fontsize='xx-small',ha='left',va='center')
axi.set_xticks([])
axi.set_yticks([])
axi.set_xlim(1,0)
axi.set_ylim(1,0)
axi.set_axis_off()
if i == 0:
axi.set_title(cb,ha='left',x=0,color=labcol,va='top',
y=1.01+len(cols1)*0.005,fontsize='small')
else:
axi.set_title(cb+colorblind_rates[cb],ha='left',x=0,color=labcol,
va='top',y=1.0,fontsize='xx-small')
lightn = [lightness(c) for c in cols1]
saturn = [saturation(c) for c in cols1]
huen = [hue(c) for c in cols1]
titles = ['Lightness','Saturation','Hue']
ylims = [(-0.05, 1.05),(-0.05, 1.05),(-10, 370)]
for vi,v in enumerate([lightn,saturn,huen]):
axi = fig.add_subplot(gs[0,vi+5])
axi.vlines(v,[i+0 for i in range(len(cols1))],
[i+1 for i in range(len(cols1))], color=cols1, lw=4)
axi.plot(v,[i+0.5 for i in range(len(cols1))],color=labcol,
lw=0.5, ls='--',marker='.',markersize=1)
axi.set_title(titles[vi],ha='left',x=0,color=labcol,va='top',
y=1.01+len(cols1)*0.005,fontsize='small')
axi.set_ylim(len(cols1),0)
axi.hlines(np.linspace(1,len(cols1),len(cols1)),
ylims[vi][0],ylims[vi][1], lw=0.2, color='.6')
axi.set_xlim(ylims[vi][0],ylims[vi][1])
axi.set_xticks([])
axi.set_yticks([])
axi.tick_params(pad=2, colors='.3', length=0)
for r in ['bottom','top','right','left']:
axi.spines[r].set_color('.8')
axi.spines[r].set_linewidth(0.5)
plt.savefig('images/pngs/color_explorer_%s.png'%cmap_name.replace(' ',''),
dpi=600,bbox_inches='tight')
plt.savefig('images/pdfs/color_explorer_%s.pdf'%cmap_name.replace(' ',''),
bbox_inches='tight')
plt.show()
![_images/98e6d491a206237288a6107013e2d19b725ce3000145c7e4f76224ea4de7bc71.png](_images/98e6d491a206237288a6107013e2d19b725ce3000145c7e4f76224ea4de7bc71.png)
Network visualization - Theory#
Plotting synthetic data: Network generative models#
Beyond nx.draw(G)
G = nx.karate_club_graph()
nx.draw(G)
![_images/f2d52172c6a6b71a2917ce950bd6f5a1adf558e27f4c7391762d41bd7bc949e8.png](_images/f2d52172c6a6b71a2917ce950bd6f5a1adf558e27f4c7391762d41bd7bc949e8.png)
Case study: I want to plot a pedagogical figure showing the effect that \(p\) has on the resulting graphs sampled from \(G(n,p)\). I’d like to visualize a number line, with some arrows to networks that are sampled with different parameterizations of \(p\).
How to do this? What would I need?
Subplots
Visualize networks under different parameters of \(p\) (maybe a loop?)
A horizontal line at the bottom of the figure
Arrows pointing to the right networks (subplots)
# let's start with some subplots
w = 2.0; h = 2.0
ncols = 7; nrows = 3
# put all those axis indices into a list called tups
tups = list(it.product(range(nrows), range(ncols)))
# define the figure!
fig, ax = plt.subplots(nrows,ncols,figsize=(14,4.5),dpi=150,
gridspec_kw={'height_ratios':[1.0, 1.0, 0.2]}) # set different height ratios of the rows
plt.show()
![_images/96f42b5902649d130324653caf42e53a8f92e7655f58c92defbc7b60f18c0d36.png](_images/96f42b5902649d130324653caf42e53a8f92e7655f58c92defbc7b60f18c0d36.png)
Hmmm… Okay how about I only visualize certain subplots? Perhaps at alternating heights to space the visualization out slightly.
If I want that, I first need to specifiy which tuples of subplots the networks will be plotted in.
# let's start with some subplots
w = 2.0; h = 2.0
ncols = 7; nrows = 3
# put all those axis indices into a list called tups
tups = list(it.product(range(nrows), range(ncols)))
### create a subset of tups that the networks will be plotted in
### let's say every other subplot (up/down/up/down etc.)
plot_tups = [i for i in tups[::2] if i[0]<(nrows-1)]
plot_tups = sorted(plot_tups, key=lambda x: x[1])
# define the figure!
fig, ax = plt.subplots(nrows,ncols,figsize=(14,4.5),dpi=150,
gridspec_kw={'height_ratios':[1.0, 1.0, 0.2]}) # set different height ratios of the rows
# try it out! set the axes off that you dont wanna see
for a in tups:
if a in plot_tups:
continue
else:
axi = ax[a]
axi.set_axis_off()
plt.show()
![_images/7b602322d37ea5da801ee1d1a15fe1a3e30bec2eb6fb51c2990024d6a04f66c0.png](_images/7b602322d37ea5da801ee1d1a15fe1a3e30bec2eb6fb51c2990024d6a04f66c0.png)
Ahhh but wait! Where did my little bottom row of short subplots go? Can I merge those all into a single long + thin subplot spanning the bottom?
Answer: Yes. Using gridspec
https://matplotlib.org/3.5.0/tutorials/intermediate/gridspec.html
# let's start with some subplots
w = 2.0; h = 2.0
ncols = 7; nrows = 3
# put all those axis indices into a list called tups
tups = list(it.product(range(nrows), range(ncols)))
# create a subset of tups that the networks will be plotted in
# let's say every other subplot (up/down/up/down etc.)
plot_tups = [i for i in tups[::2] if i[0]<(nrows-1)]
plot_tups = sorted(plot_tups, key=lambda x: x[1])
# define the figure!
fig, ax = plt.subplots(nrows,ncols,figsize=(14,4.5),dpi=150,
gridspec_kw={'height_ratios':[1.0, 1.0, 0.2]}) # set different height ratios of the rows
### create a new "gridspec" object, specified as follows
gs = fig.add_gridspec(nrows, ncols, height_ratios=[1,1,0.2])
### add the axis to the plot, based on the gridspec object you made
axi0 = fig.add_subplot(gs[(nrows-1),
0:(ncols)])
### customize your axis
axi0.set_xscale('log')
axi0.set_yticks([])
axi0.spines['top'].set_visible(False)
axi0.spines['right'].set_visible(False)
axi0.spines['left'].set_visible(False)
axi0.set_xlabel(r'$p$',fontsize='x-large')
# try it out! set the axes off that you dont wanna see
for a in tups:
if a in plot_tups:
continue
else:
axi = ax[a]
axi.set_axis_off()
plt.show()
![_images/b1a9d2ff9236a1437da0a6e97fe7d3622e0545a9f902b007364e6c4a3df8b5df.png](_images/b1a9d2ff9236a1437da0a6e97fe7d3622e0545a9f902b007364e6c4a3df8b5df.png)
Nice! I now have my number line back.
Note: In the final figure, I change the gridspec call slightly to better align with the network visualizations.
Last Major Step (before customizations): Plot some networks and add some arrows!
# let's start with some subplots
w = 2.0; h = 2.0
ncols = 7; nrows = 3
# put all those axis indices into a list called tups
tups = list(it.product(range(nrows), range(ncols)))
# create a subset of tups that the networks will be plotted in
# let's say every other subplot (up/down/up/down etc.)
plot_tups = [i for i in tups[::2] if i[0]<(nrows-1)]
plot_tups = sorted(plot_tups, key=lambda x: x[1])
# define the figure!
fig, ax = plt.subplots(nrows,ncols,figsize=(14,4.5),dpi=150,
gridspec_kw={'height_ratios':[1.0, 1.0, 0.2]}) # set different height ratios of the rows
# create a new "gridspec" object, specified as follows
plt.subplots_adjust(wspace=0.2,hspace=0.3)
gs = fig.add_gridspec(nrows, ncols*2, height_ratios=[1,1,0.2])
# add the axis to the plot, based on the gridspec object you made
axi0 = fig.add_subplot(gs[(nrows-1), 1:(ncols*2-1)])
# customize your axis
axi0.set_xscale('log')
axi0.set_yticks([])
axi0.spines['top'].set_visible(False)
axi0.spines['right'].set_visible(False)
axi0.spines['left'].set_visible(False)
axi0.set_xlabel(r'$p$',fontsize='x-large')
############ NETWORK PLOTTING TIME! ##############
N = 50
params = np.logspace(-2,0,7).round(4) # this will be my list of p to use
for i,a in enumerate(plot_tups):
axi = ax[a]
param = params[i]
G_i = nx.erdos_renyi_graph(N, param)
nx.draw(G_i, node_size=20, ax=axi)
axi.set_axis_off()
axi.set_title(r"$p=%0.3f$"%param)
# Add arrows from bottom subplot to network graphs
net_x = a[1] / (ncols-1) # Normalized position of the network subplot
# Create arrows from bottom plot to networks
net_y = 7.75 if a[0] == 0 else 1.75
net_y += np.random.rand()*0.5
axi0.annotate('', xy=(net_x, 0.1), xytext=(net_x, net_y),
xycoords='axes fraction', textcoords='axes fraction',
arrowprops=dict(width=0.75, headwidth=7.5, headlength=8))
axi0.set_xlim(1e-2, 1e0)
axi0.set_ylim(0,1)
# try it out! set the axes off that you dont wanna see
for a in tups:
if a in plot_tups:
continue
else:
axi = ax[a]
axi.set_axis_off()
plt.show()
![_images/5eeb2410b135c8fff7623dbf4c12bfb33f3add9f885b9ea1735e7a588015f04b.png](_images/5eeb2410b135c8fff7623dbf4c12bfb33f3add9f885b9ea1735e7a588015f04b.png)
We did it! See below for some fancier customization, but the gist of the visualization is done.
w = 2.0
h = 2.0
ncols = 7
nrows = 3
tups = list(it.product(range(nrows), range(ncols)))
fig, ax = plt.subplots(nrows,ncols,figsize=(14,4.5),gridspec_kw={'height_ratios':[1,1,0.2]},dpi=150)
fig.patch.set_facecolor('white')
for axi in ax.flat:
axi.set_facecolor('white')
plt.subplots_adjust(wspace=0.2,hspace=0.3)
gs = fig.add_gridspec(nrows, ncols*2, height_ratios=[1,1,0.2])
axi0 = fig.add_subplot(gs[(nrows-1), 1:(ncols*2-1)])
axi0.set_xscale('log')
axi0.set_yticks([])
axi0.spines['top'].set_visible(False)
axi0.spines['right'].set_visible(False)
axi0.spines['left'].set_visible(False)
axi0.set_xlabel(r'$p$',fontsize='x-large')
plot_tups = [i for i in tups[::2] if i[0]<(nrows-1)]
plot_tups = sorted(plot_tups, key=lambda x: x[1])
N = 50
params = np.logspace(-2,0,7).round(4)
cols = plt.cm.tab20b(np.linspace(0,1,len(params)))
i = 0
for a in plot_tups:
axi = ax[a]
param = params[i]
G_i = nx.erdos_renyi_graph(N, param)
nc = cols[i]
pos = nx.kamada_kawai_layout(G_i)
nx.draw_networkx_nodes(G_i, pos, node_size=20, node_color=[nc]*N, edgecolors='.2', ax=axi)
nx.draw_networkx_edges(G_i, pos, width=1.25, edge_color='.4', alpha=0.4, ax=axi)
axi.set_axis_off()
axi.set_title(r"$p=%0.3f$"%param)
# Add arrows from bottom subplot to network graphs
param_x = param # The x position in the bottom subplot corresponding to the parameter p
net_x = a[1] / (ncols-1) # Normalized position of the network subplot
# Create arrows from bottom plot to networks
net_y = 7.75 if a[0] == 0 else 1.75
net_y += np.random.rand()*0.5
axi0.annotate('', xy=(net_x, 0.1), xytext=(net_x, net_y), xycoords='axes fraction',
textcoords='axes fraction', arrowprops=dict(fc=nc, ec=nc, alpha=0.7, width=0.75,
headwidth=7.5, headlength=8))
i += 1
axi0.set_xlim(1e-2, 1e0)
axi0.set_ylim(0,1)
for a in tups:
if a in plot_tups:
continue
else:
axi = ax[a]
axi.set_axis_off()
plt.suptitle('Visualizing different parameterizations of Erdős-Rényi random graphs ($N=%i$)'%N,
fontsize='xx-large', fontweight='bold', y=1.05)
plt.savefig('images/pngs/er_span_viz.png',dpi=425,bbox_inches='tight')
plt.savefig('images/pdfs/er_span_viz.pdf',dpi=425,bbox_inches='tight')
plt.show()
![_images/e2adcfa443bd6c4bdb6656f68e079a7f970397f3940531c6cc275ea900dc07fa.png](_images/e2adcfa443bd6c4bdb6656f68e079a7f970397f3940531c6cc275ea900dc07fa.png)
What about the same figure, but with nonlinear preferential attachment?
def preferential_attachment_network(N, alpha=1.0, m=1):
r"""
Generates a network based off of a preferential attachment growth rule.
Under this growth rule, new nodes place their $m$ edges to nodes already
present in the graph, G, with a probability proportional to $k^\alpha$.
Parameters
----------
N (int): the desired number of nodes in the final network
alpha (float): the exponent of preferential attachment. When alpha is less
than 1.0, we describe it as sublinear preferential
attachment. At alpha > 1.0, it is superlinear preferential
attachment. And at alpha=1.0, the network was grown under
linear preferential attachment, as in the case of
Barabasi-Albert networks.
m (int): the number of new links that each new node joins the network with.
Returns
-------
G (nx.Graph): a graph grown under preferential attachment.
"""
G = nx.Graph()
G = nx.complete_graph(m+1)
for node_i in range(m+1, N):
degrees = np.array(list(dict(G.degree()).values()))
probs = (degrees**alpha) / sum(degrees**alpha)
eijs = np.random.choice(
G.number_of_nodes(), size=(m,),
replace=False, p=probs)
for node_j in eijs:
G.add_edge(node_i, node_j)
return G
w = 2.0
h = 2.0
ncols = 7
nrows = 3
tups = list(it.product(range(nrows), range(ncols)))
fig, ax = plt.subplots(nrows,ncols,figsize=(14,4.5),gridspec_kw={'height_ratios':[1,1,0.2]},dpi=150)
fig.patch.set_facecolor('white')
for axi in ax.flat:
axi.set_facecolor('white')
plt.subplots_adjust(wspace=0.2,hspace=0.3)
gs = fig.add_gridspec(nrows, ncols*2, height_ratios=[1,1,0.2])
axi0 = fig.add_subplot(gs[(nrows-1), 1:(ncols*2-1)])
axi0.set_yticks([])
axi0.spines['top'].set_visible(False)
axi0.spines['right'].set_visible(False)
axi0.spines['left'].set_visible(False)
axi0.set_xlabel(r'$\alpha$',fontsize='x-large')
plot_tups = [i for i in tups[::2] if i[0]<(nrows-1)]
plot_tups = sorted(plot_tups, key=lambda x: x[1])
N = 50
params = [-9.,-7.,-5.,-3.,-1.,1.,3.]
cols = plt.cm.tab20c(np.linspace(0,1,len(params)))
i = 0
for a in plot_tups:
axi = ax[a]
param = params[i]
G_i = preferential_attachment_network(N, param, 1)
nc = cols[i]
pos = nx.kamada_kawai_layout(G_i)
nx.draw_networkx_nodes(G_i, pos, node_size=20, node_color=[nc]*N, edgecolors='.1', ax=axi)
nx.draw_networkx_edges(G_i, pos, width=1.25, edge_color='.4', alpha=0.4, ax=axi)
axi.set_axis_off()
axi.set_title(r"$\alpha=%0.1f$"%param)
# Add arrows from bottom subplot to network graphs
param_x = param # The x position in the bottom subplot corresponding to the parameter p
net_x = a[1] / (ncols-1) # Normalized position of the network subplot
# Create arrows from bottom plot to networks
net_y = 7.75 if a[0] == 0 else 1.75
net_y += np.random.rand()*0.5
axi0.annotate('', xy=(net_x, 0.1), xytext=(net_x, net_y), xycoords='axes fraction',
textcoords='axes fraction', arrowprops=dict(fc=nc, ec=nc, alpha=0.7, width=0.75,
headwidth=7.5, headlength=8))
i += 1
axi0.set_xlim(-9,3)
axi0.set_ylim(0,1)
for a in tups:
if a in plot_tups:
continue
else:
axi = ax[a]
axi.set_axis_off()
plt.suptitle('Visualizing different parameterizations of nonlinear preferential attachment graphs ($N=%i$)'%N,
fontsize='xx-large', fontweight='bold', y=1.05)
plt.savefig('images/pngs/prefattach_span_viz.png',dpi=425,bbox_inches='tight')
plt.savefig('images/pdfs/prefattach_span_viz.pdf',dpi=425,bbox_inches='tight')
plt.show()
![_images/70cdb55f8d6a6f43f214a62e872cbe5fdf93b276d2fe6607c9453a25a5b266a0.png](_images/70cdb55f8d6a6f43f214a62e872cbe5fdf93b276d2fe6607c9453a25a5b266a0.png)
Network visualization - Data#
1. Mystery network from above: The U.S. Commute Network#
import pickle
with open('data/mystery.pickle', 'rb') as handle:
commute_network = pickle.load(handle)
G = nx.DiGraph()
for i in list(commute_network.keys()):
G.add_node(i, pos=commute_network[i]['pos'],
population=commute_network[i]['population'],
state=commute_network[i]['state'],
housing=commute_network[i]['housing'])
edge_data = commute_network[i]['edge_data']
for j in range(len(edge_data['edges'])):
G.add_edge(i, edge_data['edges'][j],
weight=edge_data['weight'][j],
margin=edge_data['margin'][j])
pos = dict(nx.get_node_attributes(G,'pos'))
xs = list(list(zip(*list(pos.values())))[0])
ys = list(list(zip(*list(pos.values())))[1])
gp = nx.to_undirected(G)
fig, ax = plt.subplots(1,1,figsize=(16,9),dpi=200)
ax.scatter(xs,ys,marker='.',lw=0,s=10)
plt.show()
![_images/e37d37668f99530a49a5bbe6cf968205025f061a7f6da56f6c3a64f0668f4476.png](_images/e37d37668f99530a49a5bbe6cf968205025f061a7f6da56f6c3a64f0668f4476.png)
## if you want state-based-coloring
statehood = nx.get_node_attributes(G,'state')
unique_states = np.unique(list(statehood.values()))
np.random.shuffle(unique_states)
nstates = len(unique_states)
cmap_cols = dict(zip(unique_states,plt.cm.tab20b(np.linspace(0,1,nstates))))
node_colors = [cmap_cols[com] for com in statehood.values()]
mult=1.0
fig, ax = plt.subplots(1,1,figsize=(16*mult,9*mult),dpi=200)
ns = 2.0
ew = 0.7
ec = '#666666'
nc = node_colors
nx.draw_networkx_edges(gp, pos, width=ew, edge_color=ec, ax=ax, alpha=0.2)
nx.draw_networkx_nodes(gp, pos, node_size=ns, node_color=nc, ax=ax, alpha=0.8,
edgecolors='w', linewidths=0.1)
ax.set_axis_off()
xdiff = max(xs)-min(xs)
ydiff = max(ys)-min(ys)
ax.set_ylim(min(ys)-ydiff*0.01, max(ys)+ydiff*0.05)
ax.set_xlim(min(xs)-xdiff*0.01, max(xs)+xdiff*0.01)
plt.savefig('images/pngs/USA_commutes.png', dpi=600, bbox_inches='tight')
plt.show()
![_images/061df060e63c7458c846e36a093cf74352efb2668ca3bec07604ce231b37156f.png](_images/061df060e63c7458c846e36a093cf74352efb2668ca3bec07604ce231b37156f.png)
2. Polysemy in Networks#
fn = 'data/univ_language.txt'
data = pd.read_csv(fn, sep=' ', skiprows=1)
data.head()
Language | Region | Globe | Abbreviation | Var1 | Var2 | Var3 | Meaning | Word | |
---|---|---|---|---|---|---|---|---|---|
0 | Khoisan | Southern_African | Southern | !Xóõ | nmn | -24.0 | 21.5 | CLOUD(S) | !kx'ôe_ǁnàn-sâ |
1 | Khoisan | Southern_African | Southern | !Xóõ | nmn | -24.0 | 21.5 | RIVER | !náu_'|nṵ́m |
2 | Khoisan | Southern_African | Southern | !Xóõ | nmn | -24.0 | 21.5 | watercourse | !náu_'|nṵ́m |
3 | Khoisan | Southern_African | Southern | !Xóõ | nmn | -24.0 | 21.5 | amniotic_fluid | !qhàa |
4 | Khoisan | Southern_African | Southern | !Xóõ | nmn | -24.0 | 21.5 | rain | !qhàa |
source = list(data.Meaning)
target = list(data.Word)
edgelist = list(zip(source, target))
G = nx.from_edgelist(edgelist)
import community
partition = community.best_partition(G)
comcols = ["#84e8d8","#ef7664","#59e19e","#e081d4","#71bc62","#a98ae5","#d3e671",
"#6c96ef","#96af39","#53a4e5","#dd973f","#63c4e1","#c8b147","#b1b9e9",
"#aca858","#ea7aa2","#bce59d","#d0a2d3","#71b990","#db9869","#799fc8",
"#e6d59f","#5db1aa","#d89a98","#a1a776"]
colorzz = [comcols[com] for com in partition.values()]
Warning – the next line takes a few minutes to run.
pos0 = nx.kamada_kawai_layout(G)
pos = nx.spring_layout(G, pos=pos0, iterations=1, k=0.05)
ews = 2.5
degs = np.array(list(dict(G.degree()).values()))
ns = degs*17 + 25
fig, ax = plt.subplots(1,1,figsize=(15,15.5),dpi=200)
nx.draw_networkx_nodes(G, pos, node_size=ns, node_color=colorzz, linewidths=1.5,
edgecolors='#f1f1f1', ax=ax)
nx.draw_networkx_edges(G, pos, edge_color="#999999", width=ews, alpha=0.35, ax=ax)
xx = [0.925,1.075]
ii = 0
for i in G.nodes():
if G.degree(i) > np.quantile(degs,0.9885):
if ii%2==0:
rr = 0
nudge = xx[rr]
lab = i.lower()
if lab == 'hi':
lab = ''
if lab == 'moon':
nudge = nudge*0.9
if lab == 'air':
nudge = nudge*0.9
if lab == 'sunlight':
nudge = nudge*1.02
if lab == 'ground':
nudge = nudge*1.05
if lab == 'country':
nudge = nudge*0.875
pos_i = {i:(pos[i][0]*nudge,pos[i][1]*nudge)}
nx.draw_networkx_labels(G, pos_i, labels={i:lab},
font_size=0.12*G.degree(i)+14)#, font_weight='bold')
ii += 1
rr = 1
posx = list(zip(*list(pos.values())))[0]
posy = list(zip(*list(pos.values())))[1]
xrange = np.abs(max(posx)) + np.abs(min(posx))
yrange = np.abs(max(posy)) + np.abs(min(posy))
plt.xlim(min(posx)-xrange*0.025, max(posx)+xrange*0.025)
plt.ylim(min(posy)-yrange*0.025, max(posy)+yrange*0.05)
plt.axis('off')
plt.savefig('images/pngs/UniversalLanguage.png', bbox_inches='tight', dpi=425)
plt.savefig('images/pdfs/UniversalLanguage.pdf', bbox_inches='tight')
plt.show()
![_images/b30fcc8660ee4f18489c26a6c15600d89fd53e222991f8e7e7b508b7d80e8666.png](_images/b30fcc8660ee4f18489c26a6c15600d89fd53e222991f8e7e7b508b7d80e8666.png)
Bonus - Multilayer Network Visualization#
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.patheffects as path_effects
from mpl_toolkits.mplot3d.art3d import Line3DCollection
# let's start with the important stuff. pick your colors.
cols = ['steelblue', 'darksalmon', 'mediumseagreen']
np.random.seed(1)
# Imagine you have three node-aligned snapshots of a network
G1 = nx.karate_club_graph()
G2 = nx.karate_club_graph()
G3 = nx.karate_club_graph()
pos3 = nx.spring_layout(G1) # assuming common node location
graphs = [G1,G2, G3]
w = 9
h = 6
fig, ax = plt.subplots(1, 1, figsize=(w,h), dpi=125, subplot_kw={'projection':'3d'})
for gi, G in enumerate(graphs):
# node positions
xs = list(list(zip(*list(pos3.values())))[0])
ys = list(list(zip(*list(pos3.values())))[1])
zs = [gi]*len(xs) # set a common z-position of the nodes
# node colors
cs = [cols[gi]]*len(xs)
# if you want to have between-layer connections
if gi > 0:
thru_nodes = np.random.choice(list(G.nodes()),10,replace=False)
lines3d_between = [(list(pos3[i])+[gi-1],list(pos3[i])+[gi]) for i in thru_nodes]
between_lines = Line3DCollection(lines3d_between, zorder=gi, color='.5',
alpha=0.4, linestyle='--', linewidth=1)
ax.add_collection3d(between_lines)
# add within-layer edges
lines3d = [(list(pos3[i])+[gi],list(pos3[j])+[gi]) for i,j in G.edges()]
line_collection = Line3DCollection(lines3d, zorder=gi, color=cols[gi], alpha=0.8)
ax.add_collection3d(line_collection)
# now add nodes
ax.scatter(xs, ys, zs, c=cs, edgecolors='.2', marker='s', alpha=1, zorder=gi+1)
# add a plane to designate the layer
xdiff = max(xs)-min(xs)
ydiff = max(ys)-min(ys)
ymin = min(ys)-ydiff*0.1
ymax = max(ys)+ydiff*0.1
xmin = min(xs)-xdiff*0.1 * (w/h)
xmax = max(xs)+xdiff*0.1 * (w/h)
xx, yy = np.meshgrid([xmin, xmax],[ymin, ymax])
zz = np.zeros(xx.shape)+gi
ax.plot_surface(xx, yy, zz, color=cols[gi], alpha=0.1, zorder=gi)
# add label
layertext = ax.text(0.0, 1.15, gi*0.95+0.5, "Layer %i"%(gi+1),
color='.95', fontsize='large', zorder=1e5, ha='left', va='center',
path_effects=[path_effects.Stroke(linewidth=3, foreground=cols[gi]),
path_effects.Normal()])
# set them all at the same x,y,zlims
ax.set_ylim(min(ys)-ydiff*0.1,max(ys)+ydiff*0.1)
ax.set_xlim(min(xs)-xdiff*0.1,max(xs)+xdiff*0.1)
ax.set_zlim(-0.1, len(graphs) - 1 + 0.1)
# select viewing angle
angle = 30
height_angle = 20
ax.view_init(height_angle, angle)
# Optionally, adjust the 3D figure's aspect ratio or other properties for zoom
ax.set_box_aspect([4,4,3.25]) # Modify aspect ratio if necessary
ax.set_axis_off()
plt.savefig('images/pngs/multilayer_network.png',dpi=425,bbox_inches='tight')
plt.show()
![_images/9eb8e89527fdc64c7213f48377d6bce65c7766370a6c4bf39faa5e9230819920.png](_images/9eb8e89527fdc64c7213f48377d6bce65c7766370a6c4bf39faa5e9230819920.png)
Next time…#
Intermediate Project Presentations!
References and further resources:#
Class Webpages
Jupyter Book: https://asmithh.github.io/network-science-data-book/intro.html
Syllabus and course details: https://brennanklein.com/phys7332-fall24
Github! Find papers with figures that you like and stalk the authors’ github pages! I’ll start with a few from my own github… jkbren/networks-and-dataviz. But send over any that you like and we’ll add them to the list!