Python學習之路15-下載資料

VPointer發表於2018-05-29

《Python程式設計:從入門到實踐》筆記。

本篇是Python資料處理的第二篇,本篇將使用網上下載的資料,對這些資料進行視覺化。

1. 前言

本篇將訪問並視覺化以兩種常見格式儲存的資料:CSV和JSON:

  • 使用Python的csv模組來處理以CSV(逗號分隔的值)格式儲存的天氣資料,找出兩個不同地區在一段時間內的最高溫度和最低溫度;
  • 使用json模組來訪問以JSON格式儲存的交易收盤價資料。

本文資料均可從圖書官網下載。

2. CSV檔案格式

新建一個專案,將檔案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()函式將兩個折現之間的區域著色。最後得到的影象如下:

Python學習之路15-下載資料

同時我們還得到了一條資訊輸出:

2014-02-16 00:00:00 missing data
複製程式碼

即該日的資料丟失了。

3. 製作交易收盤價走勢圖:JSON格式

現將將btc_close_2017.json拷貝到專案根目錄下。本節中將繪製5幅影象:收盤折線圖,收盤價對數變換,收盤價月日均值,收盤價週日均值,收盤價星期均值。均使用Pygal繪製。

3.1 繪製收盤價折線圖

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")
複製程式碼

最後得到的影象如下:

Python學習之路15-下載資料

3.2 收盤價對數變換

從上圖可以看出,收盤價基本呈指數增長,但其中有一些相似的波動(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")
複製程式碼

得到了如下影象:

Python學習之路15-下載資料

可以看出,3,6,9月都出現了劇烈的波動。下面再看看收盤價的月日均值和週日均值。

3.3 收盤價均值

3.3.1 月日均值

在繼續新的程式碼之前,需要補充一些知識: 對於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物件,如果將其轉換成listlist中的每個元素的第二個值也是個可迭代物件:

# 程式碼:
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的元素是tupletuple的第一個元素是x_data中的值,不再重複需要,所以取第二個值組成list,即第8行程式碼。xy_map是個list物件,而它的元素也是list,即它是一個二維陣列。注意第10行的操作,*xy_maplist進行解包,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], 
                             "收盤價月日均值(¥)", "月日均值")
複製程式碼

得到的結果如下:

Python學習之路15-下載資料

3.3.2 週日均值

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], 
                            "收盤價週日均值(¥)", "週日均值")
複製程式碼

結果如下:

Python學習之路15-下載資料

3.3.3 每週中各天的均值

如果直接用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")
複製程式碼

最後的結果如下:

Python學習之路15-下載資料

3.4 收盤價資料儀表盤

最後我們將五張表整合到一個檔案中,做成一個儀表盤:

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>')
複製程式碼

效果如下:

Python學習之路15-下載資料

這是將瀏覽器放大後的效果,預設100%的話這五張圖都在同一行,且非常小。

4. 小結

本篇中主要內容有:

  • 如何使用網上的資料集;
  • 如何處理CSV和JSON檔案,以及如何提取你感興趣的資料;
  • 如何使用matplotlib來處理以往的天氣資料,包括如何使用datetime模組,以及如何在同一個圖表中繪製多個資料系列;
  • 如何json模組來訪問JSON格式儲存的交易收盤價資料,並使用Pygal繪製圖形以探索價格變化的週期性,以及如何將Pygal圖形組合成資料儀表盤。

下一篇將從網上採集資料並對其進行視覺化。


迎大家關注我的微信公眾號"程式碼港" & 個人網站 www.vpointer.net ~

Python學習之路15-下載資料

相關文章