Python编程从入门到实践:制作交易收盘走势图代码详解
《python编程从入门到实践》教材的第十六章下载数据的第二个项目,下载json格式数据并进行绘图,绘制月均值的部分代码没有相似注释,导致代码晦涩难懂,通过我自己的刻苦钻研,跟大家详细分解一下这个程序
程序的整体代码并不难理解,主要是其中的draw_line()函数过于精简,导致许多初学者理解时会很困难
教材提供的源代码如下
# coding: utf-8
# ## 16.2 制作收盘价走势图:JSON 格式
# 在本节中,你将下载JSON格式的收盘价数据,并使用`json`模块来处理它们。
# Pygal提供了一个适合初学者使用的绘图工具,可以用它对收盘价数据进行可视化,以探索价格的特征。
from __future__ import (absolute_import, division, print_function,
unicode_literals)
try:
# Python 2.x 版本
from urllib2 import urlopen
except ImportError:
# Python 3.x 版本
from urllib.request import urlopen # 1
import json
import requests
import pygal
import math
from itertools import groupby
json_url = 'https://raw.githubusercontent.com/muxuezi/btc/master/btc_close_2017.json'
response = urlopen(json_url) # 2
# 读取数据
req = response.read()
# 将数据写入文件
with open('btc_close_2017_urllib.json', 'wb') as f: # 3
f.write(req)
# 加载json格式
file_urllib = json.loads(req.decode('utf8')) # 4
print(file_urllib)
json_url = 'https://raw.githubusercontent.com/muxuezi/btc/master/btc_close_2017.json'
req = requests.get(json_url) # 1
# 将数据写入文件
with open('btc_close_2017_request.json', 'w') as f:
f.write(req.text) # 2
file_requests = req.json() # 3
print(file_urllib == file_requests)
# 将数据加载到一个列表中
filename = 'btc_close_2017.json'
with open(filename) as f:
btc_data = json.load(f) # 1
# 打印每一天的信息
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(float(btc_dict['close'])) # 1
print("{} is month {} week {}, {}, the close price is {} RMB".format(
date, month, week, weekday, close))
# 创建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'])
close.append(int(float(btc_dict['close'])))
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')
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')
line_chart
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]): # 2
y_list = [v for _, v in y]
xy_map.append([x, sum(y_list) / len(y_list)]) # 3
x_unique, y_mean = [*zip(*xy_map)] # 4
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
idx_week = dates.index('2017-12-11')
line_chart_week = draw_line(
weeks[1:idx_week], close[1:idx_week], '收盘价周日均值(¥)', '周日均值')
line_chart_week
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')
line_chart_weekday
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+xml" data="{0}" height=500></object>\n'.format(svg)) # 1
html_file.write('</body></html>')
重点
程序的大部分代码在教材中都有详细的注释,并不难理解,主要的难点在下面这部分代码块,主要讲解这一部分语句
源代码提供的内容
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]): # 2
y_list = [v for _, v in y]
xy_map.append([x, sum(y_list) / len(y_list)]) # 3
x_unique, y_mean = [*zip(*xy_map)] # 4
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
经过我刻苦钻研之后的内容
#将绘制图形的代码进行简单封装,传入参数为x轴数据,y轴数据,文件名,y轴名
def draw_line(x_data, y_data, title, y_legend):
#定义一个空的列表,用来存储分组取平均后的值
xy_map = []
#zip()函数将传入的日期列表和收盘价列表按顺序各取一个元素打包成元组列表组合成一个新的迭代器——zip类,sorted()函数用于对元组列表
#按照收盘价进行排序,lambda是匿名方法,key属性是分组依据,_是临时变量,整条语句表达的意思是按照传入的第一个参数对元组列表进行分类,
#即按照月份对列表进行分类
for x, y in groupby(sorted(zip(x_data, y_data)), key=lambda _: _[0]): # 2
y_list = [v for _, v in y]
xy_map.append([x, sum(y_list) / len(y_list)]) # 3
x_unique, y_mean = [*zip(*xy_map)] # 4
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('./btc_svg/'+title + '.svg')
return line_chart
这段封装代码的功能就是将传入的日期参数x_data和收盘价参数y_data组装成一个数据为元组的列表,并按照日期参数进行分组,然后求得每一组的平均值,进行绘图,后两个参数是标题tittle和Y轴标签y_legend,作用不大
本段代码详细解析
1、调用函数后,首先定义一个空列表勇于储存数据,for循环条件中,zip(x_data, y_data)表示将传入的日期列表months和收盘价列表closes,分别按顺序各取一个元素打包成元组列表组合成一个新的迭代器——zip类,即[(1,6928), (1,7070) …… (11,65583)],紧接着第二层的sorted函数对元组列表进行排序,得到了按照月份从小到大,同一月份的收盘价从小到大排序的元组列表[(1, 5383), (1,5566) …… (1,6928), (1,7070) …… (11,65583)]。第三层的groupby函数,是一个分组聚合函数,key=lambda _: _[0]是用匿名函数Lambda(Lambda表达式基于数学中的λ演算得名)表示的条件,key是groupby()函数的一个参数,作用是在此处定义分组的依据。
2、lambda函数:
语法为
lambda argument_list: expression
表示将传入的参数列表argument_list按照表达式expression进行运算,并将结果返回
其中,argument_list是参数列表,其形式多样,可以传递一个参数,也可以传递两个参数,还可以不传递参数;expression是一个表达式,表达式中出现的参数需要在argument_list中有定义,而且必须是单行。
key=lambda : [0]就表示取列表中索引为[0]的值,并将返回值赋给key(下划线表示临时变量,仅用一次之后销毁)
左边的下划线’'表示经过zip()函数和sorted()函数处理之后的元组,即(1,5383),(1,5566)这种数据,右边的下划线‘[0]’表示取元组中的第一个元素,即取出月份1,2,3,这样的数据
key这个表达式表示groupby分组的依据,意味着按照元组列表的第一个元素进行分类。每循环一次,得到一组数据,x就是分类的key值。最后循环十一次,x=1~11,y则是对应的元组列表,得到:
1 [(1,5383), (1,5566) …… (1,7835)]
2 [(2,6793), (2,6811) …… (2,8206)]
……3、y_list = [v for _, v in y]
用列表生成式的方式将元组列表中的值一一取出,因为在这里y表示的数据仍然是一个元组,(1,5383), (1,5566) …… (1,7835),所以遍历y的时候回返回两个元素,将第0个元素赋给临时变量,v取得的是元组中的第一个元素,形成新的列表赋给y_list。
4、x_unique, y_mean = [*zip(*xy_map)]
**zip(iterable)函数是zip函数的逆过程,可将zip函数处理后的结果恢复为之前的样子,所以它将xy_map中每个元组中的第一个元素全部取出,赋给x_unique,得到x_unique=(1,2,3,4,5,6,7,8,9,10,11),第二个元素全部取出,赋给y_mean,得到y_mean=(6285.870967741936, 7315.714285714285, 7789.032258064516, 8390.466666666667, 12963.935483870968, 18092.166666666668, 17146.16129032258, 26092.645161290322, 26865.633333333335, 35460.67741935484, 51436.166666666664)。
结尾
python的语法和其他语言相比差别很大,有些代码可以简化到极致,程序中使用到的一个列表生成式
y_list = [v for _, v in y]
#这段代码就是python中的列表生成式
#转换成普通的列表生成方法就是
y_list1=[]
for a,b in y:
y_list1.append(b)
这种可以简化到极致的代码可能就是很多人热衷python的原因,虽然对于初学者来说并不是很友好
但困难都是暂时的,深入了解python之后你就会发现python的独特魅力
刚刚入坑的程序猿一枚,在博客分享一下学习过程中遇到的各种问题,之前一直都有打算写博客,因为个人比较懒一直没有行动,这次是第一次写博客,以后会定期更新,水平有限,期待各位大神多多交流。