文章目录
- 1 制作交易收盘价走势图:JSON格式
- 1.1 下载收盘价数据
- 1.2 提取相关的数据
- 1.3 将字符串转换为数字值
- 1.4 绘制收盘价折线图
- 1.5 时间序列特征初探
- 1.6 收盘价均值
- 1.6.1 月日均值
- 1.6.2 周日均值
- 1.6.3 每周中各天的均值
- 1.7 收盘价数据仪表盘
1 制作交易收盘价走势图:JSON格式
- 这部分中,下载JSON格式的交易收盘价数据,并使用模块json处理它们
- Pygal提供一个适合初学者的绘图工具,将使用它来可视化收盘价数据
1.1 下载收盘价数据
- 收盘价数据文件位于https://raw.githubusercontent.com/muxuezi/btc/master/btc_close_2017.json
- 可以直接
将文件btc_close_2017.json下载到本章程序所在的文件夹中
,也可以用Python 3.x标准库中模块urllib的函数urlopen
来做,还可以通过Python的第三方模块requests(下一章节将学习)下载数据
btc_close_2017.json
[{
"date": "2017-01-01",
"month": "01",
"week": "52",
"weekday": "Sunday",
"close": "6928.6492"
},
--SNIP--
{
"date": "2017-12-12",
"month": "12",
"week": "50",
"weekday": "Tuesday",
"close": "113732.6745"
}]
- 可以看出,这实际上是一个很长的Python列表,其中每个元素都是一个包含五个键的字典:统计日期、月份、周数、周几和收盘价
- 由于2017-1-1是周日,作为2017的第一个week实在太短,因此计入2016年的第52周
- 如果使用
函数urlopen
来下载数据呢:
btc_close_2017.py
from __future__ import (absolute_import, division,
print_function, unicode_literals)
from urllib.request import urlopen
import json
json_url = 'https://raw.githubusercontent.com/muxuezi/btc/master/btc_close_2017.json'
response = urlopen(json_url)
# 读取数据
req = response.read()
# 将数据写入文件
with open('btc_close_2017_urllib.json', 'wb') as f:
f.write(req)
# 加载json格式
file_urllib = json.loads(req)
print(file_urllib)
- 上面urlopen的代码稍微复杂一些,
第三方模块requests
封装了许多常用的方法,让数据下载和读取方式显得更简单:
--snip--
import requests
json_url = 'https://raw.githubusercontent.com/muxuezi/btc/master/btc_close_2017.json'
response = urlopen(json_url)
# 读取数据
req = response.read()
# 将数据写入文件
with open('btc_close_2017_urllib.json', 'wb') as f:
f.write(req)
# 加载json格式
file_urllib = json.loads(req)
req = requests.get(json_url)
# 将数据写入文件
with open('btc_close_2017_request.json', 'w') as f:
f.write(req.text)
file_requests = req.json()
requests通过get方法向Github服务器发送请求
req.text熟悉可以直接读取文件数据,返回格式是字符串
直接用req.json()就可以将btc_close_2017.json文件的数据转换成Python列表file_requests,与之前的file_urllib内容相同
print(file_requests == file_urllib)
- 输出结果:
True
1.2 提取相关的数据
- 提取btc_close_2017.json文件中的相关信息
btc_close_2017.py
import json
# 将数据加载到一个列表中
filename = 'btc_close_2017.json'
with open(filename) as f:
btc_data = json.load(f)
# 打印每一天的信息
for btc_dict in btc_data:
date = btc_dict['date']
month = btc_dict['month']
week = btc_dict['week']
weekday = btc_dict['weekday']
close = btc_dict['close']
print("{} is month {} week {}, {}, the close price is {}RMB".format(date,
month, week, weekday, close))
- 导入模块json,将数据存储在btc_data
- 遍历btc_data的每个元素,每个元素都是一个包含5个键-值对的字典
- btc_dict存储字典中的每个键-值对
- 取出所有键的值,并打印每一天的数据:
1.3 将字符串转换为数字值
- btc_close_2017.json中每个键和值都是字符串
- 使用函数int()将month、week、close字符串转换为数值
btc_close_2017.py
--snip--
# 打印每一天的信息
for btc_dict in btc_data:
date = btc_dict['date']
month = int(btc_dict['month'])
week = int(btc_dict['week'])
weekday = btc_dict['weekday']
close = int(btc_dict['close'])
print("{} is month {} week {}, {}, the close price is {}RMB".format(date, month, week, weekday, close))
- 运行程序,出现异常:
- 这里的原因在于,
Python不能直接将包含小数点的字符串'6928.6492'转换为整数
- 那么,
需要先将字符串转换为float,再将float转换为int
btc_close_2017.py
--snip--
# 打印每一天的信息
for btc_dict in btc_data:
--snip--
close = int(float(btc_dict['close']))
print("{} is month {} week {}, {}, the close price is {}RMB".format(date, month, week, weekday, close))
- 再次运行程序,异常消除
- 收盘价去掉了小数部分,month中1~9月前面的数字0也都消失了,week也转换成了整数
1.4 绘制收盘价折线图
- 前面的章节学习了用Pygal绘制条形图(bar chart),也学习了用matplotlib绘制折线图(line chart)
- 下面用Pygal实现收盘价的折现图
- 绘制之前,先获取x、y轴数据
btc_close_2017.py
--snip--
# 创建5个列表,分别存储日期和收盘价
dates = []
months = []
weeks = []
weekdays = []
close = []
# 打印每一天的信息
for btc_dict in btc_data:
dates.append(btc_dict['date'])
months.append(int(btc_dict['month']))
weeks.append(int(btc_dict['week']))
weekdays.append(btc_dict['weekday'])
closes.append(int(float(btc_dict['close'])))
- 由于数据点较多,x轴要显示346个日期,在有限的屏幕上显得十分拥挤
- 利用Pygal的配置参数,对图形进行适当的调整
btc_close_2017.py
--snip--
import pygal
line_chart = pygal.Line(x_label_rotation=20, show_minor_x_labels=False)
line_chart.title = '收盘价 (¥)'
line_chart.x_labels = dates
N = 20 # x轴坐标每隔20天显示一次
line_chart.x_labels_major = dates[::N]
line_chart.add('收盘价', close)
line_chart.render_to_file('收盘价折线图 (¥).svg')
- 导入模块
pygal
- 创建Line实例时:
x_label_rotation=20——让x轴上的日期标签顺时针旋转20°
;show_minor_x_labels=False——图形不用显示所有的x轴标签
- 配置
x_labels_major
属性,让x轴坐标每隔20天显示一次,这样x轴不会拥挤
1.5 时间序列特征初探
- 进行时间序列分析总是期望发现趋势trend、周期性seasonality和噪声noise,从而描述事实、预测未来、做出决策
- 从收盘价的折线图可以看出,2017年总体趋势是非线性的,而且增长幅度不断增大,似乎呈指数分布
- 但是还发现,每个季度末(3、6、9月)似乎有一些相似的波动,其中也许有周期性
- 为了验证周期性的假设,需要首先将非线性的趋势消除
- 对数交换(log transformation)是常用的处理方法之一
- 用Python标准库的数学模块math来解决这个问题
- math有很多常用的数学函数,这里用以10为底的对数函数math.log10计算收盘价,日期仍然保持不变——这种方式称为半对数(semi-logarithmic)变换
btc_close_2017.py
--snip--
import pygal
import math
line_chart = pygal.Line(x_label_rotation=20, show_minor_x_labels=False)
line_chart.title = '收盘价对数变换 (¥)'
line_chart.x_labels = dates
N = 20 # x轴坐标每隔20天显示一次
line_chart.x_labels_major = dates[::N]
close_log = [math.log10(_) for _ in close]
line_chart.add('log收盘价', close_log)
line_chart.render_to_file('收盘价对数变换折线图 (¥).svg')
- 现在,对数变换剔除非线性趋势之后,整体上涨的趋势更接近线性增长
- 可以看出,收盘价在每个季度末似乎有显著的周期性——3、6、9月都出现了剧烈的波动
- 那么,12月是不是会再现这一场景?
- 下面再看看收盘价的月日均值&周日均值
1.6 收盘价均值
- 绘制2017年前11个月的日均值、前49周(2017-01-02~2017-12-10)的日均值,以及每周中各天(Monday-Sunday)的日均值
1.6.1 月日均值
btc_close_2017.py
from itertools import groupby
def draw_line(x_data, y_data, title, y_legend):
xy_map = []
for x, y in groupby(sorted(zip(x_data, y_data)), key=lambda _: _[0]):
y_list = [v for _, v in y]
xy_map.append([x, sum(y_list) / len(y_list)])
x_unique, y_mean = [*zip(*xy_map)]
line_chart = pygal.Line()
line_chart.title = title
line_chart.x_labels = x_unique
line_chart.add(y_legend, y_mean)
line_chart.render_to_file(title + '.svg')
return line_chart
- 将数据按月份、周数、周几分组,再计算每组的均值,因此
导入Python标准库中模块itertools的函数groupby
- 然后将x轴与y轴的数据合并、排序,再用函数groupby分组
- 分组后,求出每组的均值,存储到xy_map变量中
- 最后将xy_map中存储的x轴与y轴数据分离,就可以像之前那样用Pygal画图了
- 下面画出收盘价月日均值
- 12月数据不完整,只取1~11月的数据
def draw_line(x_data, y_data, title, y_legend):
xy_map = []
for x, y in groupby(sorted(zip(x_data, y_data)), key=lambda _: _[0]):
y_list = [v for _, v in y]
xy_map.append([x, sum(y_list) / len(y_list)])
x_unique, y_mean = [*zip(*xy_map)]
line_chart = pygal.Line()
line_chart.title = title
line_chart.x_labels = x_unique
line_chart.add(y_legend, y_mean)
line_chart.render_to_file(title + '.svg')
return line_chart
idx_month = dates.index('2017-12-01')
line_chart_month = draw_line(
months[:idx_month], close[:idx_month], '收盘价月日均值(¥)', '月日均值')
line_chart_month
- 现在运行程序,发现报错:
line_chart.x_labels = x_unique
# 横坐标必须是字符串类型,而这里x_unique的类型为int,所以引发了异常:TypeError: object of type 'int' has no len()
# 下面遍历x_unique将其转换为str类型
new_x_unique = []
for x in x_unique:
new_x_unique.append(str(x))
line_chart.x_labels = new_x_unique
- 我一开始按照书上的代码运行,发现总是不行,后来在网上搜索一下这个问题,发现大部分都遇到了这个问题
- 再次运行程序,问题解决√
1.6.2 周日均值
- 下面再来绘制前49周(2017-01-02~2017-12-10)的日均值
- 因为不带2017-01-01(2016年的第52周),取数时需要将第一天去掉
idx_week = dates.index('2017-12-11')
line_chart_week = draw_line(
weeks[1:idx_week], close[1:idx_week], '收盘价周日均值(¥)', '周日均值')
line_chart_week
1.6.3 每周中各天的均值
- 为了使用完整的时间段,还像前面取前49周的数据
- 由于这里的周几是字符串,按周一到周日顺序排列,而不是单词首字母的顺序,绘图时x轴标签的顺序会有问题
- 原来的周几都是英文单词,可以将其调整为中文
idx_week = dates.index('2017-12-11')
wd = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
weekdays_int = [wd.index(w) + 1 for w in weekdays[1:idx_week]]
line_chart_weekday = draw_line(weekdays_int, close[1:idx_week], '收盘价星期均值(¥)', '星期均值')
line_chart_weekday.x_labels = ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
line_chart_weekday.render_to_file('收盘价星期均值(¥).svg')
- 首先列出一周七天的英文单词,将weekdays的内容替换为1~7的整数
- 这样,函数draw_line()在处理数据时按周几的顺序排列,就会将周一放在列表的第一位,周日放在列表的第七位
- 图形生成之后,再将图形的x轴标签替换为中文
1.7 收盘价数据仪表盘
- 前面已为交易收盘价绘制了五幅图
- 每个SVG文件打开之后都是独立的页面
- 现在将这些图整合起来,做一个收盘价数据仪表盘
btc_close_2017.py
--snip--
with open('收盘价Dashboard.html', 'w', encoding='utf8') as html_file:
html_file.write(
'<html><head><title>收盘价Dashboard</title><meta charset = "utf-8"></head><body>\n')
for svg in [
'收盘价折线图 (¥).svg', '收盘价对数变换折线图(¥).svg',
'收盘价月日均值(¥).svg', '收盘价周日均值(¥).svg',
'收盘价星期均值(¥).svg',
]:
html_file.write(
' <object type="image/svg+sml" data="{0}"height=500></object>\n'.format(svg))
html_file.write('</body></html>')