上一篇文章介绍了怎样使用tushare
金融数据源,统计近10天上映电影的观影人数走势图。程序员上手写Python,常带有Java风格。比如通过Dict字典模拟HashMap,定义各种数据结构,代码中随处可见多层for循环等,虽然运行速度确实快了点,但没有体现出Python长于验证想法的高生产力特质。
本文通过使用Pandas DataFrame对象内置的数据处理功能,以数据分析人员的视角编码(类似Matlab代码风格),将上一篇文章的代码行数压缩了50%,体现了Python和Pandas的表达能力。
为什么选择 DataFrame:
tushare
金融数据源接口返回的多数是Pandas的DataFrame对象,以电影票房接口为例,返回指定日期当天的DataFrame格式如下,包括在映所有影片的名称,票房,上映天数等信息。如果查询最近10天的票房信息,下面的DF对象会有10个:
Index | MovieName | BoxOffice | MovieDay |
---|---|---|---|
0 | 狮子王 | 1600 | 3 |
1 | 蜘蛛侠 | 1500 | 12 |
2 | 千与千寻 | 300 | 20 |
绘图函数Matplotlib.plt(xList,yList)的输入参数,横坐标是日期列表,纵坐标是对应日期的票房列表,就是按从左到右,从旧到新的顺序排列最近几天的票房数据,所以我希望的DataFrame格式如下表。只要按行遍历,就可以定位并取出DF对象的一行记录,作为绘图函数的纵坐标输入参数:
Index | MovieName | 07-11 | 07-12 | 07-13 |
---|---|---|---|---|
0 | 狮子王 | Nan | 900 | 1600 |
1 | 蜘蛛侠 | 1300 | 1200 | 1500 |
2 | 千与千寻 | 500 | Nan | 300 |
那么怎么跨越上面两个DF对象的格式鸿沟呢?根据官网描述,可使用DataFrame对象内置的列合并函数Merge实现:
DataFrame.merge(right, how=‘inner’, on=None, left_on=None, right_on=None, left_index=False, right_index=False, sort=False, suffixes=(’_x’, ‘_y’), copy=True, indicator=False, validate=None)
Merge DataFrame or named Series objects with a database-style join.
The join is done on columns or indexes. If joining columns on columns, the DataFrame indexes will be ignored. Otherwise if joining indexes on indexes or indexes on a column or columns, the index will be passed on.
官网介绍Merge函数的输入参数how,可以设置为inner, left, right, outer四种类型,完全等同我们所熟悉的SQL内连接,左连接,右连接和全连接。
how : {‘left’, ‘right’, ‘outer’, ‘inner’}, default ‘inner’
Type of merge to be performed.
left: use only keys from left frame, similar to a SQL left outer join; preserve key order.
right: use only keys from right frame, similar to a SQL right outer join; preserve key order.
outer: use union of keys from both frames, similar to a SQL full outer join; sort keys lexicographically.
inner: use intersection of keys from both frames, similar to a SQL inner join; preserve the order of the left keys.
根据介绍tushare接口返回的单日期多个DF对象,通过merge函数,指定连接方式how=outer外连接,关联字段on=MovieName,可以合并为多日期单个DF对象,用于Matplotlib画图。下面是按日期导出的2个DF对象:
Index | MovieName 【关联字段】 | 07-11 |
---|---|---|
0 | 爱宠大机密2 | 300 |
1 | 蜘蛛侠 | 1300 |
2 | 千与千寻 | 500 |
Index | MovieName 【关联字段】 | 07-12 |
---|---|---|
0 | 狮子王 | 900 |
1 | 蜘蛛侠 | 1200 |
2 | 扫毒2 | 800 |
然后用Merge函数把上面2个DF对象全连接成1个DF对象:
Index | MovieName | 07-11 | 07-12 |
---|---|---|---|
0 | 狮子王 | Nan | 900 |
1 | 蜘蛛侠 | 1300 | 1200 |
2 | 千与千寻 | 500 | Nan |
3 | 扫毒2 | Nan | 800 |
4 | 爱宠大机密2 | 300 | Nan |
需要注意的是,合并后在原来两个DF中不存在的Cell值,都会自动填上Nan。类似的DF合并函数还有按行合并的Concat,append,join等,具体可参考官方文档。
话不多说上代码:
下面代码用到DataFrame的6类数据处理函数,具体用法参考Pandas官网
- 行列数获取shape,
- 多列截取[‘colName1’,‘colName2’],
- 局部行列截取loc[index,[‘colName1’]],iloc[index, col_index],
- 元素提取at[index,[‘colName1’]],
- 指定列重名rename(columns={‘oldColName’:‘newColName’}),
- 多个DataFrame对象全连接merge
import tushare as ts, datetime
import pandas as pd
import matplotlib.pyplot as plt
def getDateList(days):
# 输入参数days表示指定向前取的天数
date_list = [] # 返回列表形如['2019-06-29',...]
for i in range(days,0,-1): #从days开始每次减1
strDate = (datetime.date.today() \
- datetime.timedelta(days=i)) \
.strftime('%Y-%m-%d')
# 日期格式:YYYY-MM-DD
date_list.append(strDate)
return date_list
def getMovieData(date_list):# 入参['2019-06-29',...]
firstDate = date_list[0] #得到最早一天
dfMovie = ts.day_boxoffice(firstDate) #得到票房
dfResult = dfMovie[['MovieName','BoxOffice']]
# 将dataframe字段BoxOffice重名为当天日期
dfResult = dfResult.rename(\
columns={'BoxOffice':firstDate})
# 遍历从第二天开始的日期列表
for date in date_list[1:]:
dfMovie = ts.day_boxoffice(date) # 取指定日票房
df = dfMovie[['MovieName','BoxOffice']]# 取2列
# 将dataframe字段BoxOffice重名为当天日期
df = df.rename(columns={'BoxOffice':date})
# 把遍历到的当天df全连接out_join到结果df中
dfResult = pd.merge(dfResult, df,\
how='outer', on='MovieName')# on是外键
return dfResult
# 根据入参dataframe对象绘图
def drawLine(dfResult,date_list):
plt.rcParams['font.sans-serif']\
=['SimHei'] # 用于正常显示中文标签
# 遍历结果dataframe的每一行,即遍历每部电影
for i in range(dfResult.shape[0]):# 0-行数 1-列数
movieName = dfResult.at[i,'MovieName']
# 取出第i行第0个name字段右侧的所有字段,转为list
boxOfficeList = dfResult.iloc[i,1:].tolist()
# 将list中的字符元素全部转换为float元素
boxOfficeList = list(\
map(float, boxOfficeList))
dateList = [d[5:] for d in date_list]# 剔除年份
# 最近上映的每一部电影都绘制一条折线
plt.plot(dateList, boxOfficeList,\
label=movieName)
plt.legend() # 添加折线说明,loc默认best
plt.ylabel('票房(万)')
plt.title('电影票房走势图')
plt.show() # 显示图形
if __name__ == '__main__':
days = 10 # 经测试旧版tushare不提供10天前数据
# 得到最近几天日期字符列表,格式:YYYY-MM-DD
date_list = getDateList(days)
# 返回dataframe对象,行是电影,列是指定日期票房
dfResult = getMovieData(date_list)
# 根据函数getMovieData返回的dataframe对象绘图
drawLine(dfResult, date_list)
代码行数从之前的115行减少到62行,运行后显示如下:
往期文章:
怎样让Python推荐电影 - 初试tushare
怎样让Python处理邮件 - 规则轮询
怎样让Python处理邮件 - 初试exchangelib