Creates stacked graphs (sometimes known as stream graphs, apparently) as recommended by Byron and Wattenberg, http://www.leebyron.com/else/streamgraph/download.php?file=stackedgraphs_byron_wattenberg.pdf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 | # Author: Anand Patil
# License: MIT License
import matplotlib.pyplot as pl
import numpy as np
def symmetric(sorted_streams, stream_bounds):
"""Symmetric baseline"""
lb, ub = np.min(stream_bounds[:,0,:],axis=0), np.max(stream_bounds[:,1,:],axis=0)
return .5*(lb+ub)
def pos_only(sorted_streams, stream_bounds):
"""Lumps will only be positive"""
lb, ub = np.min(stream_bounds[:,0,:],axis=0), np.max(stream_bounds[:,1,:],axis=0)
return lb
def zero(sorted_streams, stream_bounds):
"""Zero baseline"""
return np.zeros(stream_bounds.shape[2])
def min_weighted_wiggles(sorted_streams, stream_bounds):
"""Baseline recommended by Byron and Wattenberg"""
lb, ub = np.min(stream_bounds[:,0,:],axis=0), np.max(stream_bounds[:,1,:],axis=0)
weight = ub-lb
sorted_streams = np.abs(sorted_streams)
for i in xrange(len(sorted_streams)):
sorted_streams[i,:] *= (-1)**i
cusum_f = np.vstack((np.zeros(sorted_streams.shape[1]),
np.cumsum(sorted_streams[:-1,:], axis=0)))
f_prime = np.diff(sorted_streams, axis=1)
cusum_f_prime = np.diff(cusum_f, axis=1)
g_prime = np.hstack(([0],-np.sum((f_prime*.5 + cusum_f_prime)*sorted_streams[:,1:],axis=0) / weight[1:]))
g_prime[np.where(weight==0)] = 0
g = np.cumsum(g_prime)
return g
def stacked_graph(streams, cmap=pl.cm.bone, color_seq='linear', baseline_fn=min_weighted_wiggles):
"""
Produces stacked graphs using matplotlib.
Reference: 'Stacked graphs- geometry & aesthetics' by Byron and Wattenberg
http://www.leebyron.com/else/streamgraph/download.php?file=stackedgraphs_byron_wattenberg.pdf
Parameters:
- streams: A list of time-series of positive values. Each element must be of the same length.
- cmap: A matplotlib color map. Defaults to 'bone'.
- colo_seq: 'linear' or 'random'.
- baseline_fn: Current options are symmetric, pos_only, zero and min_weighted_wiggles.
"""
# Sort by onset times
onset_times = [np.where(np.abs(stream)>0)[0][0] for stream in streams]
order = np.argsort(onset_times)
streams = np.asarray(streams)
sorted_streams = streams[order]
t = np.arange(streams.shape[1])
# Establish bounds
stream_bounds = [np.vstack((np.zeros(streams.shape[1]), sorted_streams[0])),
np.vstack((-sorted_streams[1], (np.zeros(streams.shape[1]))))]
side = -1
for stream in sorted_streams[2:]:
side *= -1
if side==1:
stream_bounds.append(np.vstack((stream_bounds[-2][1], stream_bounds[-2][1]+stream)))
else:
stream_bounds.append(np.vstack((stream_bounds[-2][0]-stream, stream_bounds[-2][0])))
stream_bounds = np.array(stream_bounds)
# Compute baseline
baseline = baseline_fn(sorted_streams, stream_bounds)
# Choose colors
t_poly = np.hstack((t,t[::-1]))
if color_seq=='linear':
colors = np.linspace(0,1,streams.shape[1])
elif color_seq=='random':
colors = np.random.random(size=streams.shape[1])
else:
raise ValueError, 'Color sequence %s unrecognized'%color_seq
# Plot
pl.axis('off')
for i in xrange(len(stream_bounds)):
bound = stream_bounds[i]
color = cmap(colors[i])
pl.fill(t_poly, np.hstack((bound[0]-baseline,(bound[1]-baseline)[::-1])), facecolor=color, linewidth=0.,edgecolor='none')
# Demo
if __name__ == '__main__':
pl.clf()
N_dsets = 50
T = 100
amp = 1
fade = .15
dsets = []
for i in xrange(N_dsets):
this_dset = np.zeros(T)
t_onset = np.random.randint(.9*T)-T/3
if t_onset >= 0:
remaining_t = np.arange(T-t_onset)
else:
remaining_t = np.arange(T)-t_onset
this_dset[max(t_onset,0):]=np.exp(-.15*np.random.gamma(10,.1)*remaining_t)\
* remaining_t * np.random.gamma(6,.2)# * np.cos(-fade*remaining_t*np.random.gamma(10,.1))**2
dsets.append(this_dset)
stacked_graph(dsets, baseline_fn = min_weighted_wiggles, color_seq='random')
|
Requires matplotlib and numpy. Save the code as a script and run it to see a demo.
For those unfamiliar with matplotlib, displaying the output requires an extra line at the end of the demo routine. Add the following to view the graph:
pl.show()