我的Python心路历程 第十二期 (12.6 指数实战可视化之两只产品类比时日期等数据合并、对齐显示问题的解决)
背景
当前,直接取条目多的日期做图后,效果对比截图如下所示:
很明显,从左侧两个图的x坐标可以看出来日期不一致,所以导致右图类比显示时后半部分一只产品图断了,但按理说应该是前面断裂而不是后面断裂,这里就涉及到数据治理的事情了,校正数据对其是业务层需求。
那么,接下来要解决的问题就是数据治理,将数据缺失在对的地方。最原始的方法就是做个算法将缺失数据补位为Nan;但是可以优先看看pandas自身的方法merge,研究了一下其函数原版定义。
分析
从参数说明来看,on是这里特别关注的,看看官网例子:
方案
结合业务实质调试ok后的代码为:
# 基金名称及代码
name1 = jsContent1.eval('fS_name')
code1 = jsContent1.eval('fS_code')
# 累计净值走势=单位净值+累计分红
ACWorthTrendData1 = jsContent1.eval('Data_ACWorthTrend')
ACWorthTrend1 = [v[1] for v in ACWorthTrendData1]
# 基金名称及代码
name2 = jsContent2.eval('fS_name')
code2 = jsContent2.eval('fS_code')
# 累计净值走势=单位净值+累计分红
ACWorthTrendData2 = jsContent2.eval('Data_ACWorthTrend')
ACWorthTrend2 = [v[1] for v in ACWorthTrendData2]
# x轴:日期。因为系统是毫秒级timeStamp故此需要除以1000取秒级timeStamp,否则转换后的日期不对
# vtimeStamp = [int(v[0] / 1000) for v in ACWorthTrendData1] # // 算法符号的结果可以直接取整
# xs = pd.to_datetime(vtimeStamp, unit='s') # , 看源码设定对应的unit,'s'类同format='%Y-%m-%dT%H:%M:%S'但其结果会出人意料的差
# x轴:日期。获取年月日需要除以(1000 *24*3600),因为系统是毫秒级timeStamp。
vtimeStamp = [int(v[0] / (1000 *24*3600)) for v in ACWorthTrendData1] # // 算法符号的结果可以直接取整
xs1 = pd.to_datetime(vtimeStamp, unit='D') # , 看源码设定对应的unit,'s'类同format='%Y-%m-%dT%H:%M:%S'但其结果会出人意料的差
# x轴:日期。获取年月日需要除以(1000 *24*3600),因为系统是毫秒级timeStamp。
vtimeStamp = [int(v[0] / (1000 * 24 * 3600)) for v in ACWorthTrendData2] # // 算法符号的结果可以直接取整
xs2 = pd.to_datetime(vtimeStamp, unit='D') # , 看源码设定对应的unit,'s'类同format='%Y-%m-%dT%H:%M:%S'但其结果会出人意料的差
# 定义字段名
colSet = ['Date', 'ACWorth']
# 提取x、y1、y2轴数据并整合, pd.merge适用于Series数据类型
# 设置y轴数据
sACWorthTrend1 = pd.Series(ACWorthTrend1, name=colSet[1])
sACWorthTrend2 = pd.Series(ACWorthTrend2, name=colSet[1])
# 设置x轴数据
stimeStamp = pd.Series(xs1, name=colSet[0])
pdACWorthTrend1 = pd.merge(stimeStamp, sACWorthTrend1, left_index=True, right_index=True)
# 设置x轴数据
stimeStamp = pd.Series(xs2, name=colSet[0])
pdACWorthTrend2 = pd.merge(stimeStamp, sACWorthTrend2, left_index=True, right_index=True)
# 合并两个pandas数据
tech_df = pd.merge(pdACWorthTrend1, pdACWorthTrend2, how='outer', on=colSet[0], suffixes=('_'+code1, '_'+code2))
merge操作完成后查看数据的确已经合并完成,跟预想一致,左右双方缺失数据补位为nan。调试截图如下所示:
但是,接下来又出现日期作为y轴列值被图表化显示。
代码为:
# tech_df.reset_index()
print(tech_df.head())
tech_df.set_index(colSet[0])
print(tech_df.head())
运行效果如下图所示:
效果
数据打印效果如下图所示:
定睛一看,设置index前后打印数据一样,看来是日期设置index未生效导致的。
这个图肯定也不是我想要的,日期作为x轴且仅作为x轴才是想要的最终效果。
经过查阅大量资料,研究pandas在日期方面的索引机制。
优化后代码为:
# tech_df.reset_index()
print(tech_df.head())
# tech_df.set_index(colSet[0])
# 把 date 用作索引时,类型需要是 DatetimeIndex。这就是为啥此处将Date直接设置为index未生效的根本原因所在
tech_df.set_index(colSet[0], inplace=True)
tech_df.index = pd.DatetimeIndex(tech_df.index)
print(tech_df.head())
最终预想效果如下图所示:
设置索引有效后数据打印效果如下图所示:
附(代码)
最后,照旧奉上所有代码:
# 比较两只基金趋势,日期不一样时有做优化整合
def compare2fs(fscode1, fscode2):
# 获取绝对路径,funddata为当前文件夹
curpath = os.path.join(os.path.dirname(__file__), 'funddata')
fileName = fscode1 + 'content.js'
file_object_path = os.path.join(curpath, fileName)
f = open(file_object_path, 'r', encoding='utf-8')
content = f.read()
f.close()
jsContent1 = execjs.compile(content)
# 获取绝对路径,funddata为当前文件夹
curpath = os.path.join(os.path.dirname(__file__), 'funddata')
fileName = fscode2 + 'content.js'
file_object_path = os.path.join(curpath, fileName)
f = open(file_object_path, 'r', encoding='utf-8')
content = f.read()
f.close()
jsContent2 = execjs.compile(content)
# 基金名称及代码
name1 = jsContent1.eval('fS_name')
code1 = jsContent1.eval('fS_code')
# 累计净值走势=单位净值+累计分红
ACWorthTrendData1 = jsContent1.eval('Data_ACWorthTrend')
ACWorthTrend1 = [v[1] for v in ACWorthTrendData1]
# 基金名称及代码
name2 = jsContent2.eval('fS_name')
code2 = jsContent2.eval('fS_code')
# 累计净值走势=单位净值+累计分红
ACWorthTrendData2 = jsContent2.eval('Data_ACWorthTrend')
ACWorthTrend2 = [v[1] for v in ACWorthTrendData2]
# x轴:日期。因为系统是毫秒级timeStamp故此需要除以1000取秒级timeStamp,否则转换后的日期不对
# vtimeStamp = [int(v[0] / 1000) for v in ACWorthTrendData1] # // 算法符号的结果可以直接取整
# xs = pd.to_datetime(vtimeStamp, unit='s') # , 看源码设定对应的unit,'s'类同format='%Y-%m-%dT%H:%M:%S'但其结果会出人意料的差
# x轴:日期。获取年月日需要除以(1000 *24*3600),因为系统是毫秒级timeStamp。
vtimeStamp = [int(v[0] / (1000 *24*3600)) for v in ACWorthTrendData1] # // 算法符号的结果可以直接取整
xs1 = pd.to_datetime(vtimeStamp, unit='D') # , 看源码设定对应的unit,'s'类同format='%Y-%m-%dT%H:%M:%S'但其结果会出人意料的差
# x轴:日期。获取年月日需要除以(1000 *24*3600),因为系统是毫秒级timeStamp。
vtimeStamp = [int(v[0] / (1000 * 24 * 3600)) for v in ACWorthTrendData2] # // 算法符号的结果可以直接取整
xs2 = pd.to_datetime(vtimeStamp, unit='D') # , 看源码设定对应的unit,'s'类同format='%Y-%m-%dT%H:%M:%S'但其结果会出人意料的差
# 定义字段名
colSet = ['Date', 'ACWorth']
# 提取x、y1、y2轴数据并整合, pd.merge适用于Series数据类型
# 设置y轴数据
sACWorthTrend1 = pd.Series(ACWorthTrend1, name=colSet[1])
sACWorthTrend2 = pd.Series(ACWorthTrend2, name=colSet[1])
# 设置x轴数据
stimeStamp = pd.Series(xs1, name=colSet[0])
pdACWorthTrend1 = pd.merge(stimeStamp, sACWorthTrend1, left_index=True, right_index=True)
# 设置x轴数据
stimeStamp = pd.Series(xs2, name=colSet[0])
pdACWorthTrend2 = pd.merge(stimeStamp, sACWorthTrend2, left_index=True, right_index=True)
# 合并两个pandas数据
tech_df = pd.merge(pdACWorthTrend1, pdACWorthTrend2, how='outer', on=colSet[0], suffixes=('_'+code1, '_'+code2))
# tech_df.reset_index()
print(tech_df.head())
# tech_df.set_index(colSet[0])
# 把 date 用作索引时,类型需要是 DatetimeIndex。这就是为啥此处将Date直接设置为index未生效的根本原因所在
tech_df.set_index(colSet[0], inplace=True)
# tech_df.index = pd.DatetimeIndex(tech_df.index)
print(tech_df.head())
# 主次轴差异化显示
# tech_df.plot(secondary_y=colSet[2], grid=True)
# 主轴显示
tech_df.plot()
ax = plt.gca() # 表明设置图片的各个轴,plt.gcf()表示图片本身
ax.xaxis.set_major_formatter(mdate.DateFormatter(
'%Y-%m-%d')) # 横坐标标签显示的日期格式;注意,如果不加语句plt.gca().xaxis.set_major_formatter(mdate.DateFormatter('%Y-%m-%d')),则横坐标只显示年份
plt.xlabel(u'日期')
plt.ylabel(u'累计净值')
plt.title(name1 + ':' + code1 + ' vs ' + name2 + ':' + code2)
# plt.text('2017-01-01', 0.65, showStr)
plt.gcf().autofmt_xdate() # 自动旋转日期标记
# plt.tight_layout()
plt.show()
return