从various sources中获取想法,并结合我自己的想法,我试图创建一个动画地图,根据我的数据中的某些值显示国家/地区的阴影。
基本流程如下:
这很好用。问题是它非常慢。我有可能超过100k的时间间隔(超过几个指标)我想要制作动画,并且我得到每帧的平均时间为15秒,并且它越多,帧越多。按照这个速度,我的计算机上的cpu和内存可能需要数周才能生成一个动画。
我知道matplotlib的速度并不快(例如:1和2)但是我读到人们以5 + fps生成动画的故事,并想知道我做错了什么
我做过的一些优化:
也许一个不太详细的shapefile会加速形状的着色,但正如我之前所说,每帧只有3s的改进。
这是代码(减去一些可识别的特征)
import pandas as pd
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import time
from math import pi
from sqlalchemy import create_engine
from mpl_toolkits.basemap import Basemap
from matplotlib.patches import Polygon
from matplotlib.collections import PatchCollection
from geonamescache import GeonamesCache
from datetime import datetime
def get_dataset(avg_interval, startTime, endTime):
### SQL query
# Returns a dataframe with fields [country, unixtime, metric1, metric2, metric3, metric4, metric5]]
# I use unixtime so I can group by any arbitrary interval to get sums and avgs of the metrics (hence the param avg_interval)
return df
# Initialize plot figure
fig=plt.figure(figsize=(11, 6))
ax = fig.add_subplot(111, axisbg='w', frame_on=False)
# Initialize map with Robinson projection
m = Basemap(projection='robin', lon_0=0, resolution='c')
# Load and read shapefile
shapefile = 'countries/ne_10m_admin_0_countries'
m.readshapefile(shapefile, 'units', color='#dddddd', linewidth=0.005)
# Get valid country code list
gc = GeonamesCache()
iso2_codes = list(gc.get_dataset_by_key(gc.get_countries(), 'fips').keys())
# Get dataset and remove invalid countries
# This one will get daily aggregates for the first week of the year
df = get_dataset(60*60*24, '2016-01-01', '2016-01-08')
df.set_index(["country"], inplace=True)
df = df.ix[iso2_codes].dropna()
num_colors = 20
# Get list of distinct times to iterate over in the animation
period = df["unixtime"].sort_values(ascending=True).unique()
# Assign bins to each value in the df
values = df["metric1"]
cm = plt.get_cmap('afmhot_r')
scheme= cm(1.*np.arange(num_colors)/num_colors)
bins = np.linspace(values.min(), values.max(), num_colors)
df["bin"] = np.digitize(values, bins) - 1
# Initialize animation return object
x,y = m([],[])
point = m.plot(x, y,)[0]
# Pre-zip country details and shap objects
zipped = zip(m.units_info, m.units)
tbegin = time.time()
# Animate! This is the part that takes a long time. Most of the time taken seems to happen between frames...
def animate(i):
# Clear the axis object so it doesn't draw over the old one
ax.clear()
# Dynamic title
fig.suptitle('Num: {}'.format(datetime.utcfromtimestamp(int(i)).strftime('%Y-%m-%d %H:%M:%S')), fontsize=30, y=.95)
tstart = time.time()
# Get current frame dataset
frame = df[df["unixtime"]==i]
# Loop through every country
for info, shape in zipped:
iso2 = info['ISO_A2']
if iso2 not in frame.index:
# Gray if not in dataset
color = '#dddddd'
else:
# Colored if in dataset
color = scheme[int(frame.ix[iso2]["bin"])]
# Get shape info for country, then color on the ax subplot
patches = [Polygon(np.array(shape), True)]
pc = PatchCollection(patches)
pc.set_facecolor(color)
ax.add_collection(pc)
tend = time.time()
#print "{}%: {} of {} took {}s".format(str(ind/tot*100), str(ind), str(tot), str(tend-tstart))
print "{}: {}s".format(datetime.utcfromtimestamp(int(i)).strftime('%Y-%m-%d %H:%M:%S'), str(tend-tstart))
return None
# Initialize animation object
output = animation.FuncAnimation(fig, animate, period, interval=150, repeat=False, blit=False)
filestring = time.strftime("%Y%m%d%H%M%S")
# Save animation object as m,p4
#output.save(filestring + '.mp4', fps=1, codec='ffmpeg', extra_args=['-vcodec', 'libx264'])
# Save animation object as gif
output.save(filestring + '.gif', writer='imagemagick')
tfinish = time.time()
print "Total time: {}s".format(str(tfinish-tbegin))
print "{}s per frame".format(str((tfinish-tbegin)/len(df["unixtime"].unique())))
P.S。我知道代码很草率,可以使用一些清理。我愿意接受任何建议,特别是如果清理会提高性能的话!
编辑1:这是输出的示例
2016-01-01 00:00:00: 3.87843298912s
2016-01-01 00:00:00: 4.08691620827s
2016-01-02 00:00:00: 3.40868711472s
2016-01-03 00:00:00: 4.21187019348s
Total time: 29.0233821869s
9.67446072896s per frame
前几行表示正在处理的日期,以及每帧的运行时间。我不知道为什么第一个重复。最后一行是程序的总运行时间除以帧数。请注意,平均时间是个别时间的2-3倍。这让我觉得在框架之间发生了一些正在耗费大量时间的事情。
编辑2:我运行了一些性能测试并确定生成每个附加帧的平均时间大于最后一帧,与帧数成比例,表明这是一个二次时间过程。 (或者它是指数吗?)无论哪种方式,我都很困惑,为什么这不是线性的。如果数据集已经生成,并且地图需要一个恒定的时间来重新生成,那么哪个变量导致每个额外的帧花费的时间比前一个更长?
编辑3:我刚认识到我不知道动画功能是如何工作的。 (x,y)和点变量取自刚绘制移动点的示例,因此在该上下文中有意义。地图......不是那么多。我尝试返回与animate函数相关的地图,并获得更好的性能。返回ax对象(return ax,
)会使过程以线性时间运行...但不会向gif写入任何内容。任何人都知道我需要从animate函数返回什么才能使它工作?
编辑4:每帧清除轴让帧以恒定速率生成!现在我只需要进行一般优化。我首先从ImportanceOfBeingErnest的建议开始。以前的编辑现在已经过时了。