Python:下载数据——JSON格式

来源:《Python编程:从入门到实践》

文章目录

  • 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存储字典中的每个键-值对
  • 取出所有键的值,并打印每一天的数据:
    Python:下载数据——JSON格式_第1张图片

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:下载数据——JSON格式_第2张图片
  • 这里的原因在于,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也转换成了整数
    Python:下载数据——JSON格式_第3张图片

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轴不会拥挤
    Python:下载数据——JSON格式_第4张图片

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月是不是会再现这一场景?
  • 下面再看看收盘价的月日均值&周日均值
    Python:下载数据——JSON格式_第5张图片

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
  • 这里的相关知识(比如zip)推荐看一下这篇文章,说得比较详细易懂:https://segmentfault.com/a/1190000015098563
  • 现在运行程序,发现报错:
    Python:下载数据——JSON格式_第6张图片
    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
  • 我一开始按照书上的代码运行,发现总是不行,后来在网上搜索一下这个问题,发现大部分都遇到了这个问题
  • 再次运行程序,问题解决√
    Python:下载数据——JSON格式_第7张图片

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

Python:下载数据——JSON格式_第8张图片

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轴标签替换为中文Python:下载数据——JSON格式_第9张图片

1.7 收盘价数据仪表盘

  • 前面已为交易收盘价绘制了五幅图
  • 每个SVG文件打开之后都是独立的页面
  • 现在将这些图整合起来,做一个收盘价数据仪表盘

btc_close_2017.py

--snip--

with open('收盘价Dashboard.html', 'w', encoding='utf8') as html_file:
    html_file.write(
        '收盘价Dashboard\n')
    
    for svg in [
            '收盘价折线图 (¥).svg', '收盘价对数变换折线图(¥).svg', 
            '收盘价月日均值(¥).svg', '收盘价周日均值(¥).svg',
            '收盘价星期均值(¥).svg',
    ]:
        html_file.write(
            '   \n'.format(svg))
    html_file.write('')

Python:下载数据——JSON格式_第10张图片

你可能感兴趣的:(Python学习)