《Python編程:從入門到實踐》筆記。html
本篇是Python數據處理的第二篇,本篇將使用網上下載的數據,對這些數據進行可視化。python
本篇將訪問並可視化以兩種常見格式存儲的數據:CSV和JSON:編程
csv
模塊來處理以CSV(逗號分隔的值)格式存儲的天氣數據,找出兩個不一樣地區在一段時間內的最高溫度和最低溫度;json
模塊來訪問以JSON格式存儲的交易收盤價數據。本文數據都可從圖書官網下載。json
新建一個項目,將文件death_valley_2014.csv
複製到項目根目錄,並新建highs_lows.py
文件,改程序讀取加州死亡谷2014年的溫度數據,提取出天天的最高和最低氣溫,並繪製出折線圖:數組
import csv
from datetime import datetime
from matplotlib import pyplot as plt
filename = "death_valley_2014.csv"
with open(filename) as f:
reader = csv.reader(f)
header_row = next(reader)
dates, highs, lows = [], [], []
for row in reader:
try:
current_date = datetime.strptime(row[0], "%Y-%m-%d")
high = int(row[1])
low = int(row[3])
except ValueError:
print(current_date, "missing data")
else:
dates.append(current_date)
highs.append(high)
lows.append(low)
fig = plt.figure(dpi=141, figsize=(10, 6))
# 繪製最高氣溫折線圖
plt.plot(dates, highs, c="red")
# 繪製最低氣溫折線圖
plt.plot(dates, lows, c="blue")
# 填充兩個折現之間的空間,alpha爲透明度,0爲全透明,1爲不透明
plt.fill_between(dates, highs, lows, facecolor="blue", alpha=0.1)
plt.title("Daily high and low temperatures - 2014\nDeath Valley, CA", fontsize=20)
plt.xlabel("", fontsize=16)
# 自動排版x軸的日期數據,避免重疊
fig.autofmt_xdate()
plt.ylabel("Temperature(F)", fontsize=16)
plt.tick_params(axis="both", which="major", labelsize=16)
plt.show()
複製代碼
代碼現將文件打開,而後經過csv.reader()
函數建立一個CSV文件閱讀器,參數就是剛纔打開的文件;經過next()
函數讀取文件的一行,並自動將數據轉換爲列表;而後經過一個for
循環讀取所有數據。for
循環中還添加了錯誤檢查,以防文件中數據丟失等問題形成程序終止。咱們還經過fill_between()
函數將兩個折現之間的區域着色。最後獲得的圖像以下:瀏覽器
同時咱們還獲得了一條信息輸出:微信
2014-02-16 00:00:00 missing data
複製代碼
即該日的數據丟失了。app
現將將btc_close_2017.json
拷貝到項目根目錄下。本節中將繪製5幅圖像:收盤折線圖,收盤價對數變換,收盤價月日均值,收盤價週日均值,收盤價星期均值。均使用Pygal
繪製。svg
import json
import pygal
# 將數據加載到一個列表中,列表中的元素是字典
filename = "btc_close_2017.json"
with open(filename) as f:
btc_data = json.load(f)
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"])))
# x軸座標上的刻度順時針旋轉20度
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")
複製代碼
最後獲得的圖像以下:函數
從上圖能夠看出,收盤價基本呈指數增加,但其中有一些類似的波動(3,6,9月)。儘管這些波動被增加的趨勢掩蓋了,但也許其中有周期性。爲了驗證週期性的假設,須要首先將非線性的趨勢消除。對數變換是經常使用的處理方法之一。咱們使用Python標準庫中的math
模塊來解決此問題。
-- snip --
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月都出現了劇烈的波動。下面再看看收盤價的月日均值和週日均值。
在繼續新的代碼以前,須要補充一些知識: 對於zip()
函數,它將多個列表按照元素的位置組成新的列表,而新列表的元素是元組。以下:
# 代碼
a = [1, 2, 3]
b = [4, 5, 6]
c = [7, 8, 9, 10]
zipped_1 = zip(a,b)
zipped_2 = zip(a, b, c)
print(zipped_1)
print(list(zipped_1))
print(list(zipped_2))
# 結果
<zip object at 0x0000021D732DCDC8>
[(1, 4), (2, 5), (3, 6)]
[(1, 4, 7), (2, 5, 8), (3, 6, 9)]
複製代碼
在python2中,zip()
直接返回一個列表,但在python3中,zip()
返回一個可迭代的zip
對象,這裏咱們將其轉化爲列表。也在前面加星號對zip
對象進行「解壓」(解包):
# 代碼:
print(*zipped_1)
# 結果:
(1, 4) (2, 5) (3, 6)
複製代碼
星號不止能對zip
對象進行解包,還能夠對list
等類型進行解包。
咱們還會用到groupby()
函數,但在使用該函數以前,須要對列表進行排序。咱們使用sorted()
函數進行排序,python3中sorted()
函數默認按照元素順序進行比較,好比這裏的列表的元素是元組,則sorted()
先比較元組中第一個元素的值,再比較第二個元素的值,以下:
# 代碼:
test = [(1, 5), (1, 4), (1, 3), (1, 2), (2, 3)]
print(sorted(test))
# 結果:
[(1, 2), (1, 3), (1, 4), (1, 5), (2, 3)]
複製代碼
接下來經過groupby()
函數對這些數據進行分組,經過關鍵字參數key=itemgetter(0)
指定根據列表元素(即元組)的第一個值進行分組。也能夠將這裏的itemgetter()
函數替換爲lambda
表達式,如等價的lambda
表達式爲lambda x: x[0]
。在python3中,groupby()
返回一個可迭代的groupby
對象,若是將其轉換成list
,list
中的每一個元素的第二個值也是個可迭代對象:
# 代碼:
test = [(1, 5), (1, 4), (1, 3), (1, 2), (2, 4), (2, 3), (3, 5)]
temp = groupby(sorted(test), key=itemgetter(0))
print(temp)
print(list(temp))
for a, b in temp:
print(list(b))
# 結果:
<itertools.groupby object at 0x0000013CD9A4D458>
[(1, <itertools._grouper object at 0x0000013CE8AAE160>),
(2, <itertools._grouper object at 0x0000013CE8AAE128>),
(3, <itertools._grouper object at 0x0000013CE8AAE198>)]
[(1, 2), (1, 3), (1, 4), (1, 5)]
[(2, 3), (2, 4)]
[(3, 5)]
複製代碼
從上面的for
循環的結果來看,能夠將groupby()
返回的對象看作一個字典,該字典的鍵爲上面的key
的值,該字典的值爲還沒分組時列表中的部分元素(可能組成了列表,也可能組成了元組)。
如今言歸正傳,回到主線。
繪製2017年前11個月的日均值,前49周的日均值,以及每週中各天(Monday~Sunday)的日均值。首先咱們須要封裝一些代碼:
from itertools import groupby
from operator import itemgetter
def draw_line(x_data, y_data, title, y_legend):
xy_map = []
# 本段見後面解釋
for x, y in groupby(sorted(zip(x_data, y_data)), key=itemgetter(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
複製代碼
本段代碼有些繞。 從前面的介紹能夠知道,for
循環中的變量y
至關於一個list
,這個list
的元素是tuple
,tuple
的第一個元素是x_data
中的值,再也不重複須要,因此取第二個值組成list
,即第8行代碼。xy_map
是個list
對象,而它的元素也是list
,即它是一個二維數組。注意第10行的操做,*xy_map
將list
進行解包,zip()
函數將解包後的元素再次打包成一個zip
對象,若是將其看作list
對象,則這個對象含有兩個tuple
元素,而後將這個zip
對象也解包,最外面再套一層list
,獲得一個含兩個tuple
元素的list
,最後再平行賦值。爲了更具體的體現這段操做,下面用一些簡單數據進行模擬:
# 代碼:
temp = [[1, 2], [3, 4], [5, 6]]
x, y = [*zip(*temp)]
print(x)
print(y)
# 結果:
(1, 3, 5)
(2, 4, 6)
複製代碼
最後,終於到了畫圖階段:
-- 讀取文件內容的代碼和前面同樣 --
idx_month = dates.index("2017-12-01")
line_chart_month = draw_line(months[:idx_month], close[:idx_month],
"收盤價月日均值(¥)", "月日均值")
複製代碼
獲得的結果以下:
2017年的第一週從2017年1月2日開始,第49週週日是2017年12月10日。
-- 讀取文件內容的代碼和前面同樣 --
idx_week = dates.index("2017-12-11")
line_chart_week = draw_line(weeks[1:idx_week], close[1:idx_week],
"收盤價週日均值(¥)", "週日均值")
複製代碼
結果以下:
若是直接用weekdays
這個列表生成圖表,因爲該列表存儲的是字符串,排序的時候是按ASCII
碼進行排序,最後生成的圖表星期的順序會出錯,因此將其轉換成數字。
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")
複製代碼
最後的結果以下:
最後咱們將五張表整合到一個文件中,作成一個儀表盤:
with open('收盤價Dashboard.html', 'w', encoding='utf8') as html_file:
title = '<html><head><title>收盤價Dashboard</title><meta charset="utf-8"></head><body>\n'
html_file.write(title)
for svg in [
'收盤價折線圖(¥).svg', '收盤價對數變換折線圖(¥).svg', '收盤價月日均值(¥).svg',
'收盤價週日均值(¥).svg', '收盤價星期均值(¥).svg'
]:
html_file.write(
' <object type="image/svg+xml" data="{0}" height=500></object>\n'.format(svg))
html_file.write('</body></html>')
複製代碼
效果以下:
這是將瀏覽器放大後的效果,默認100%的話這五張圖都在同一行,且很是小。
本篇中主要內容有:
matplotlib
來處理以往的天氣數據,包括如何使用datetime
模塊,以及如何在同一個圖表中繪製多個數據系列;json
模塊來訪問JSON格式存儲的交易收盤價數據,並使用Pygal
繪製圖形以探索價格變化的週期性,以及如何將Pygal
圖形組合成數據儀表盤。下一篇將從網上採集數據並對其進行可視化。
迎你們關注個人微信公衆號"代碼港" & 我的網站 www.vpointer.net ~