-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathanimate.py
executable file
·179 lines (153 loc) · 7.4 KB
/
animate.py
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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
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
#!/usr/bin/env python3
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.animation as animation
# below is a patch for a problem with matplotlib. You can ignore this code
# see: https://stackoverflow.com/questions/17558096/animated-title-in-matplotlib
def _blit_draw(self, artists, bg_cache):
# Handles blitted drawing, which renders only the artists given instead
# of the entire figure.
updated_ax = []
for a in artists:
# If we haven't cached the background for this axes object, do
# so now. This might not always be reliable, but it's an attempt
# to automate the process.
if a.axes not in bg_cache:
# bg_cache[a.axes] = a.figure.canvas.copy_from_bbox(a.axes.bbox)
# change here
bg_cache[a.axes] = a.figure.canvas.copy_from_bbox(a.axes.figure.bbox)
a.axes.draw_artist(a)
updated_ax.append(a.axes)
# After rendering all the needed artists, blit each axes individually.
for ax in set(updated_ax):
# and here
# ax.figure.canvas.blit(ax.bbox)
ax.figure.canvas.blit(ax.figure.bbox)
# MONKEY PATCH!!
matplotlib.animation.Animation._blit_draw = _blit_draw
# patch ends here
import networkx as nx
import datetime
import pandas
import scipy.stats as sts
import loader
import analyze
# helper function for smooth animation - calculates nodes positions in transition frames
def averageDict(d1: dict, d2: dict, alpha):
return {n: (1-alpha)*d1[n] + alpha*d2[n] for n in d1.keys()}
# reduces the graph
def mst(G):
return nx.algorithms.tree.mst.minimum_spanning_tree(G)
# load the data from downloaded stooq files and construct correlation networks (for now, full networks, no mst applied)
data = loader.loadCountAndClean("gpw_2007_list.txt", 30)
networks = analyze.createNetworksSeries(data, start_date=datetime.date(year=2007, month=12, day=1), end_date=datetime.date(year=2008, month=12, day=1))
dates = pandas.DatetimeIndex(networks.index)
# prepare subplots for plotting. ax_graph is the area where we visualize the graph (top). ax_plot is the area for plotting how measured values change (bottow)
fig = plt.figure(figsize=(10,10))
gs = matplotlib.gridspec.GridSpec(4, 1, height_ratios = [3, 1,1,1], hspace=0.5)
ax_graph = fig.add_subplot(gs[0])
ax_plot_e = fig.add_subplot(gs[1])
ax_plot_e2 = fig.add_subplot(gs[2])
ax_plot_m = fig.add_subplot(gs[3])
gs.tight_layout(fig)
#values
ent = [] #entropy
ent_eff = [] # entropy_efficient
mol = [] #mol
# this means, transition between two steps (two networks) takes 10 frames of animation
FRAMES_PER_TRANSITION=10
# this method will be called every frame by FuncAnimation to update the plot
def animate(i):
global legend
# we'll be changing some global variables, so we need to declare them here
global G_old
global G_new
global pos_old
global pos_new
global alpha
global date_old
global date_new
global widths_new
global widths_old
global ent
global ent_eff
global mol
if(i == 0):
G_new = mst(networks[0])
# spring_layout generates positions for the nodes of the graph such that the structure of the graph is best visible
pos_new = nx.spring_layout(G_new, k=2./np.sqrt(len(G_new.nodes)))
# we want links representing short distances (strong connection) to be thick. We create array of widths of all edges to pass it to nx.draw
widths_new = [1/G_new[u][v]['weight'] for u, v in G_new.edges()]
# at every step (once in every 10 frames): update the network, store the previous one in G_old
if(i%FRAMES_PER_TRANSITION == 0):
network_index = i//FRAMES_PER_TRANSITION
# date_old and date_new are used to display the current date on the top of the animation
date_old = dates[network_index].date()
date_new = dates[min(network_index+1, len(dates)-1)].date()
G_old = G_new
pos_old = pos_new
widths_old = widths_new
# at the very end, network_index == len(networks)-1, we use this to land within array bounds
G_new = mst(networks[min(network_index+1, len(networks)-1)])
# we create new positions starting with pos_old. This way the graph won't just spin randomly
pos_new = nx.spring_layout(G_new, k=2./np.sqrt(len(G_new.nodes)), pos=pos_old)
widths_new = [1/G_new[u][v]['weight'] for u, v in G_new.edges()]
# alpha measures the stage of transition. It goes from 0 to 1. during each transition
alpha = 0.
#zwykła entropia
network2 = G_new
apex1 = []
apex2 = []
for l in nx.degree(network2):
apex1.append(l[1])
ent.append(pandas.Series([sts.entropy(apex1)], index = [date_new]))
#mol
for l in nx.degree(network2):
apex2.append(l[0])
center = ['',0]
for r in network2.nodes():
if nx.degree(network2,r) > center[1]:
center = [r,nx.degree(network2,r)]
way = 0
for e in apex2:
way += len(nx.shortest_path(network2,center[0],e)) -1
mol.append(pandas.Series([way/len(apex2)], index = [date_new]))
#entropy_efficient
ent_eff.append(pandas.Series([sts.entropy(widths_new)], index = [date_new]))
ax_graph.clear()
date_str = str(date_old) if i%FRAMES_PER_TRANSITION<(FRAMES_PER_TRANSITION/2) else str(date_new)
# draw the current date just over the graph
ttl = ax_graph.text(.5, 1.05, date_str, transform = ax_graph.transAxes, va='center')
# for the first part of transition we draw G_old and widths_old, for the second part _new
G_to_draw = G_old if i%FRAMES_PER_TRANSITION<(FRAMES_PER_TRANSITION/2) else G_new
pos_to_draw = averageDict(pos_old, pos_new, alpha)
widths_to_draw = widths_old if i%FRAMES_PER_TRANSITION<(FRAMES_PER_TRANSITION/2) else widths_new
# workaround for bug in nx.draw
plt.sca(ax_graph)
# we draw nodes, edges and labels separately so it's easier to manage the attributes
nx.draw_networkx_nodes(G_to_draw, pos=pos_to_draw, ax=ax_graph, node_size=500, node_color='lightgreen', edgecolor='black')
nx.draw_networkx_edges(G_to_draw, pos=pos_to_draw, ax=ax_graph, edge_color='green', width=10*widths_to_draw)
nx.draw_networkx_labels(G_to_draw, pos=pos_to_draw, ax=ax_graph, font_weigth='bold')
ax_plot_e.plot(pandas.concat(ent), 'r')
ax_plot_e.title.set_text("Entropy")
ax_plot_m.plot(pandas.concat(mol), 'g')
ax_plot_m.title.set_text("Mol")
ax_plot_e2.plot(pandas.concat(ent_eff), 'b')
ax_plot_e2.title.set_text("Edges entropy")
for ax_plot in (ax_plot_e, ax_plot_e2, ax_plot_m):
ax_plot.set_xlim(dates[0], dates[-1])
ax_plot.get_xaxis().set_visible(True)
ax_plot.get_yaxis().set_visible(True)
# update alpha
alpha +=1./FRAMES_PER_TRANSITION
# if blit is True, we have to return objects to redraw in each frame
return (ax_plot_e, ax_plot_m, ax_plot_e2, ax_graph, ttl)
# this starts the animation in a window
ani = animation.FuncAnimation(fig, animate, frames=len(networks)*FRAMES_PER_TRANSITION,
interval=10, blit=True, repeat=False)
plt.show()
# if you want to save the animation to gif, use this lines instead (blit has to be False, so it takes longer)
#ani = animation.FuncAnimation(fig, animate, frames=len(networks)*FRAMES_PER_TRANSITION,
# interval=10, blit=False, repeat=False)
#ani.save('gif/animation.gif', writer='imagemagick', fps=60)