前一段时间一直在做电影数据方面的工作,在做可视化的时候我们用的是matplotlib,但matplotlib比较适合绘制静态图片,如果要交互式的可视化我们一般都使用plotly,但经常在两者之间切换非常麻烦,笔者就在想matplotlib能不能也支持交互式的用法,于是就在网上查了一下,在网上中文相关的资料很少,最后还是在官网找到了(英文版的),于是就找了些数据,写了个简单的例子,给大家分享一下。
就以我做的电影数据的为例,主要内容就是爬取某一周7天的北美票房每日数据,这个数据分为每日总票房和每日具体票房,每日总票房是一张数据表(数据从今年1月1日到当前日期),每日具体票房是一张数据表,其列出了当日上映所有电影的票房数据信息。一周有七天,那么就有七个每日具体票房数据表和一个每日总票房数据表,一共八张表,而这八张表都可以从Box Office Mojo(以下简称BOM)网站上获取,一共是爬取八个网页。下面是相关网页的两张截图。
图1. BOM网站每日总票房网页截图
图2. BOM网站每日具体票房网页截图
笔者用的是Anaconda最新版,系统是Win7,代码部分可以在jupyter notebook中运行,也可以在qtconsole中运行。下面就以2月24日到3月1日的票房数据为例,演示一下matplotlib的交互图制作。下面是代码部分。首先导入各种需要的包。
import requests
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib as mpl
%matplotlib
这个%matplotlib
命令要单独输入,不能和上面的各种import命令一起使用,否则会出错。这个命令的主要作用是使用交互式的后端,在作图时弹出一个独立窗口,这样才能交互操作。下面是爬取八个网页的代码。
mpl.rcParams['font.sans-serif'] = ['SimHei'] #用来显示中文,防止乱码
mpl.rcParams['font.family'] = 'sans-serif' #用来显示中文,防止乱码
file = r'C:\Users\BoxOffice\data.xlsx' #存放数据的文件,用户可自行设置文件位置
url_list = [r'https://www.boxofficemojo.com/date',
r'https://www.boxofficemojo.com/date/2020-02-24',
r'https://www.boxofficemojo.com/date/2020-02-25',
r'https://www.boxofficemojo.com/date/2020-02-26',
r'https://www.boxofficemojo.com/date/2020-02-27',
r'https://www.boxofficemojo.com/date/2020-02-28',
r'https://www.boxofficemojo.com/date/2020-02-29',
r'https://www.boxofficemojo.com/date/2020-03-01'
]
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36',
'accept': '*/*',
'accept-encoding': 'gzip, deflate, br',
'accept-language': 'en-US,en;q=0.9',
'content-type': 'text/plain;charset=UTF-8'}
sheet_names = ['overview', '02-24', '02-25', '02-26', '02-27', '02-28', '02-29', '03-01']
writer = pd.ExcelWriter(file, engine='openpyxl')
for i in range(len(url_list)):
res = requests.get(url_list[i], headers=headers)
text =res.text
table = pd.read_html(text)
table = table[0]
table.to_excel(writer, sheet_name=sheet_names[i])
writer.save()
writer.close()
以上是爬取网页内容的代码,这里仅介绍一下简单的思路。我们创建一个名为data.xlsx
的Excel文件,用于保存我们爬取的数据内容。url_list
是我们要爬取的url列表,共包含8个url,其中第一个url是每日总票房数据,后面每一个url是每日具体票房数据。爬取之后放在data.xlsx
文件中,每个数据表放在一个Excel表中,即一个sheet中,sheet的名字为sheet_names
这个list中包含的八个数据,和url_list
中的顺序对应。好了,爬虫部分就介绍到此。这部分代码实际上完全可以省略,毕竟就这8个网页,直接复制粘贴也快得很,重点还是在后面的交互式作图。这里放上一张爬好的数据截图。
图3. 所爬取数据的部分截图
接下来是交互式操作的代码,是一个函数。
def click(event):
if event.artist != art:
return
ind = event.ind[0]
day_data = data_list[ind]
titles = title_list[ind]
ax1.cla() #清除之前所绘制图形
ax1.barh(range(len(day_data)), day_data) #绘制横向柱状图
ax1.set_yticklabels(titles)
ax1.set_xlabel('票房/万美元')
ax1.set_yticks(range(len(day_data)))
ax.set_title('This is the top10 of %s' % sheet_names[1:][ind])
fig.canvas.draw()
这个函数我们命名为click,意思是点击,当然这个函数可以任意命名,其有一个参数event,这个event后面会介绍到。click函数后面还会详细叙述一下。接下来是数据准备部分。
overview_table = pd.read_excel(file, sheet_name='overview')
overview_data = overview_table['Top 10 Gross'][1:8]
overview_data = overview_data.str.replace('$', '') #去掉其中的$符号
overview_data = overview_data.str.replace(',', '') #去掉其中的逗号
overview_data = overview_data.astype(int, copy=False)/100000 #转换成整数类型
overview_data = overview_data.round(2) #保留两位小数
overview_data = overview_data[::-1].to_list() #转换成list
data_list = []
title_list = []
for date in sheet_names[1:]:
day_table = pd.read_excel(file, sheet_name=date)
day_data = day_table['Daily'][:10]
day_data = day_data.str.replace('$', '')
day_data = day_data.str.replace(',', '')
day_data = day_data.astype(int, copy=False)/10000
day_data = day_data.round(2)
day_data = day_data.to_list()
day_title = day_table['Release'][:10] #读取前10名电影的名字
day_title = day_title.to_list()
data_list.append(day_data)
title_list.append(day_title)
overview_table
是一个pandas的dataframe,内容和图1中基本相同,其“Top 10 Gross”列是我们需要的,“Top 10 Gross”的意思是每日前10名电影的票房总和,我们这里就把它当成每日票房总和,这列中的数据也就是保存在Excel文件中的数据是string格式的,我们要把其中的“$”和“,”都去掉,然后转换成整数格式,同时再除以100000,这样便于作图,最后再转换成一个list,就是overview_data
了。而接下来的那个循环中,和这个是类似的,不过处理的是每日具体票房数据,即以“day_”开头的这些变量,刚才说了,每日总票房数据表中每个数据是每天票房的总和(准确来说是前10名电影的票房总和),那么这7天中,每天就有10部电影(排名前10的电影),每部电影一个数据,每天就有10个数据,这10个数据组成一个list,一共7个list,放在一个名为data_list
的变量中,另外一个title_list
是其对应的电影名字的变量,和data_list
结构一样,不过放了对应的电影的名字。
好了所有数据处理完,就开始作图了。
fig, (ax, ax1) = plt.subplots(2, 1) #一共上下两个子图
ax.set_xticks(range(len(overview_data)))
ax.set_ylim(0,400) #设置Y轴的范围
ax.set_title('Click on point to plot the date')
ax.set_ylabel('票房/十万美元')
art, = ax.plot(overview_data, marker='o', picker=5) #绘制上面的子图
ax.set_xticklabels(sheet_names[1:]) #设置上图的x轴坐标标签
plt.subplots_adjust(left=0.3, hspace=0.4) #设置子图间的距离和周边空白
fig.canvas.mpl_connect('pick_event', click) #交互操作函数
plt.show()
生成图片整体效果如下。
图4. 最终静态效果图
这里只有一张大图,但包含两个上下子图,上面的子图我们显示每日总票房的数据,一共7个数据点,我们点击每个数据点,在下面的子图中就显示出当日的票房排名前10的电影,同时上面的标题也会显示点击的是哪一天,这就是我们要做的交互式操作。两个子图,上面那个命名为ax,下面是ax1,这里因为每部电影的名字是英文的,比较长,所以用了plt.subplots_adjust(left=0.3, hspace=0.4)
命令来设置子图的左侧空白,用来显示电影名称,hspace是两图之间的距离属性。这里的重点就是fig.canvas.mpl_connect('pick_event', click)
这行代码,这个mpl_connect
方法有两个参数,第一个是一个string格式的参数,必须按照系统指定的这么几种参数来写,包括'button_press_event'
、'button_release_event'
、'draw_event'
、'key_press_event'
、'key_release_event'
、'motion_notify_event'
、'pick_event'
、'resize_event'
、'scroll_event'
、'figure_enter_event'
、'figure_leave_event'
、'axes_enter_event'
、'axes_leave_event'
和'close_event'
这14个参数,从它们的名字我们就能大致判断出其作用,我们用到的pick_event意思就是“选择事件”,即点击数据点就触发该事件(event)。我们再回过头来看前面的那个click函数,也就是这里的第二个参数,click函数以event为参数,当触发点击事件后,该event参数就传递给click函数,event有一个artist属性,我们可以为其指定具体对象,这里我们把上图中的图形指定给artist。而event还有一个属性是ind,这是另一个重点,这个ind我在官方文档中找了大半天也未找到其具体用法和解释,最后只能自己摸索,在尝试了多次之后大概摸清了其用法,ind全称是index,即索引,这个index就是我们所绘制图形中数据点的index,因为我们绘制的所有图形都是有一个一个数据点组成的,每个数据点都是按照顺序来绘制,比如plt.plot( [[1, 1], [2, 2], [3, 3]] )中,[1, 1]就是第一个点,其index是0,因为Python中第一个数是从0开始的,[2, 2]是第二个点,其index是1,以此类推。把这个ind弄清楚了,基本上所有问题迎刃而解,我们就知道点击的是哪个点,然后就可以在下面的子图中绘制相应的图形,而click的最后一行代码fig.canvas.draw()是用来显示刚刚绘制的图形,这个必须加上,否则无法显示图形。最后再放上一张动态效果图。
图5. 最终动态效果图
到这里,这个小例子就算结束了,写了这么一大堆,其实重点就两个,一个是mpl_connect
这个方法,一个是event.ind
这个属性,这两个弄清楚所有问题就清楚了。通过这个例子我们也了解了matplotlib的交互式绘图功能,其整体效果还算不错,但笔者在用了一段时间后也发现了一些问题,这里给大家总结一下,主要有以下几点:
在处理大量数据时该交互式操作速度跟不上,有性能瓶颈。笔者曾用matplotlib处理过一个地图数据,本想做成交互式,结果卡得不行,延迟太严重,所以不建议用matplotlib交互式方法处理大量数据。
编程太费劲,封装不够。这代码写了一大堆,还要自己写一个操作函数,明显不够pythonic,毕竟Python程序猿是世界上最懒的程序猿(估计有人不同意),大家凡事都想import,你想让这群货们自己动手丰衣足食,那还不如指望中国队进世界杯。
这部分属于matplotlib高级用法,需要对matplotlib有较深了解,而其官方文档又没有足够详细的说明,这会让部分用户望而却步。通过这个例子我们了解了matplotlib的交互式操作,虽然其存在一些不足,但也给我们提供了一个新思路,至少我们能够摆脱浏览器的局限,不用只在浏览器使用,大家可以根据自己的情况酌情来使用。本文所有数据和代码都放在笔者的gitee网站上,网址是
https://gitee.com/leonmovie/interactive-matplotlib-chart-for-box-office-analysis
,如有需要可自行下载。
作者简介:Mort,数据分析爱好者,擅长数据可视化,比较关注机器学习领域,希望能和业内朋友多学习交流。
赞 赏 作 者
Python中文社区作为一个去中心化的全球技术社区,以成为全球20万Python中文开发者的精神部落为愿景,目前覆盖各大主流媒体和协作平台,与阿里、腾讯、百度、微软、亚马逊、开源中国、CSDN等业界知名公司和技术社区建立了广泛的联系,拥有来自十多个国家和地区数万名登记会员,会员来自以工信部、清华大学、北京大学、北京邮电大学、中国人民银行、中科院、中金、华为、BAT、谷歌、微软等为代表的政府机关、科研单位、金融机构以及海内外知名公司,全平台近20万开发者关注。
推荐阅读:
▼ 点击阅读原文,即享阿里云产品0.9折优惠起