(在模仿中精進資料視覺化02) 溫室氣體排放來源視覺化

費弗裡發表於2020-09-14

本文完整程式碼已上傳至我的Github倉庫https://github.com/CNFeffery/FefferyViz

1 簡介

  交通是產生溫室氣體排放的主要來源之一,而本期作為(在模仿中精進資料視覺化)系列的第二期,將帶大家以純Python的方式對加拿大米西索加城市溫室氣體排放研究報告中的如圖1所示的視覺化作品進行復刻,它對溫室氣體排放來源中,交通方面的各排放源排放比例進行視覺化:

(在模仿中精進資料視覺化02) 溫室氣體排放來源視覺化
圖1

2.1 觀察原作品

  其實原作品整體構圖上比較直觀,主要由兩部分組成:

  • 1 左側柱狀圖部分

  左側的柱狀圖無需多言,就是一個簡單的堆疊柱狀圖,利用matplotlib構建起來非常方便。

  • 2 右側類桑基圖部分

  到了右側,也是這張圖中最有設計感的部分,它用類似桑基圖的方式,將左圖中交通下屬的分類溫室氣體排放比例構成進行視覺化,這也是本文的重點部分,我們可以利用matplotlib加上一點點簡單的數學知識來複刻它。

2.2 開始動手!

  在洞悉了原作品的主要視覺元素之後,接下來我們開始動手復刻它。

2.2.1 左側柱狀圖部分

  對於左側的堆疊柱狀圖,其本質其實是兩個堆疊起來的矩形,因此我們可以使用matplotlib.patches下的Rectangle來建立矩形。

  其使用方法非常簡單,只需要指定矩形左下角座標,再填寫矩形對應的即可自由建立矩形:

(在模仿中精進資料視覺化02) 溫室氣體排放來源視覺化
圖2

  我們參考原作品的背景色,以及左側矩形對應y軸的真實數值,先把左側的堆疊柱狀圖圖床背景色做好:

import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle

# 建立圖床
fig, ax = plt.subplots(figsize=(11, 6))

# 建立buildings對應矩形
ax.add_patch(Rectangle((0, 0), 3e6, 3e6, facecolor='#f38530'))

# 建立transportation對應矩形
ax.add_patch(Rectangle((0, 3e6), 3e6, 2.1e6, facecolor='#4abad0'))

# 設定x軸範圍
ax.set_xlim(-3e6, 1.7e7)
# 設定y軸範圍
ax.set_ylim(-4e6, 9e6)

# 設定背景色
fig.patch.set_facecolor('#efefea')
ax.set_facecolor('#efefea')

# 關閉座標軸
ax.axis('off');
(在模仿中精進資料視覺化02) 溫室氣體排放來源視覺化
圖3

  接著我們在上面程式碼的基礎上新增下列程式碼,順便把原作品中連線左右側的3條灰色線條新增上去:

# 新增連線線
ax.plot([3e6, 6e6], [3e6, 3e6-1.5e6], color='grey', linewidth=0.75)
ax.plot([3e6, 6e6], [5.1e6, 5.1e6+1.5e6], color='grey', linewidth=0.75)
ax.plot([6e6, 6e6], [3e6-1.5e6, 5.1e6+1.5e6], color='lightgrey', linewidth=1.5)
(在模仿中精進資料視覺化02) 溫室氣體排放來源視覺化
圖4

  這樣,我們就把最簡單的左半邊主要視覺元素組織好了。

2.2.2 右側類桑基圖部分

  到了本文的核心內容——構造右側類桑基圖部分,為了便於之後的幾何元素製作,我們先把原作品中右側涉及的資料構造到資料框中:

import pandas as pd

data = pd.DataFrame({
    '型別': ['Car', 'Freight', 'Street Lights', 'GO Train', 'BRT', 'Bus', 'Taxi', 'Motorcycle'],
    '份額': [0.8628, 0.0933, 0.0005, 0.001, 0.0121, 0.0133, 0.0165, 0.0005]
})

data['份額累加'] = data['份額'].cumsum()
data.head()
(在模仿中精進資料視覺化02) 溫室氣體排放來源視覺化
圖5

  其中份額累加列的新增是為了方便之後組合幾何元素。

  首先我們來繪製右側最上方的Car對應的矩形,因為這部分只是簡單的矩形,在上一步的繪圖程式碼中新增下列程式碼來更新影像:

height = 5.1e6 + 1.5e6 - (3e6 - 1.5e6)

# 右側圖形
# 最上方矩形
ax.add_patch(Rectangle((6e6, 3e6-1.5e6+0.1372*height),
                       0.8e7,
                       0.8628*height,
                       facecolor='#4ebcd1'))
(在模仿中精進資料視覺化02) 溫室氣體排放來源視覺化
圖6

  接下來我們來建立類桑基圖部分,思路其實很簡單,因為這部分內容與Sigmoid型函式對應的曲線是很接近的,譬如正弦函式在\(0.5\pi\)\(1.5\pi\)之間的曲線:

(在模仿中精進資料視覺化02) 溫室氣體排放來源視覺化
圖7

  根據這個特點,我們可以結合第1期中玩過的老把戲——線性變換,來輔助生成桑基條帶。

  我們從最上方矩形的下端開始,利用data中的份額份額累加,以及\(0.5\pi\)\(1.5\pi\)之間的標準正弦函式曲線,配合線性變換,來構造每個類別對應條帶的上下邊界,再配合matplotlib中的fill_between來完成條帶的繪製。

  首先我們來生成基礎正弦函式取樣點資料,以及線性變換函式:

x, y = np.arange(0.5*np.pi, 1.5*np.pi, 0.001), np.sin(np.arange(0.5*np.pi, 1.5*np.pi, 0.001))

def scale(xlim, ylim):
    
    return (xlim[0] + (xlim[1] - xlim[0]) * (x - x.min()) / (x.max() - x.min()),
            ylim[0] + (ylim[1] - ylim[0]) * (y - y.min()) / (y.max() - y.min()))

  這樣我們就可以在給定x範圍,以及給定y範圍的基礎上,將標準的正弦函式曲線不同程度的“壓扁”,就像下面的例子一樣:

import numpy as np

x, y = np.arange(0.5*np.pi, 1.5*np.pi, 0.001), np.sin(np.arange(0.5*np.pi, 1.5*np.pi, 0.001))

def scale(xlim, ylim):
    
    return (xlim[0] + (xlim[1] - xlim[0]) * (x - x.min()) / (x.max() - x.min()),
            ylim[0] + (ylim[1] - ylim[0]) * (y - y.min()) / (y.max() - y.min()))

plt.plot(*scale((0, 1), (0, 1)))
plt.plot(*scale((0, 1), (0.25, 1)))
plt.plot(*scale((0, 1), (0.5, 1)))
plt.plot(*scale((0, 1), (0.75, 1)));
(在模仿中精進資料視覺化02) 溫室氣體排放來源視覺化
圖8

  按照這個思想,我們結合份額份額累加值,以兩種色彩交錯的方式構造條帶:

# 生成每個條帶的上下底
bands = [(scale(xlim=(6e6, 6e6+0.8e7), 
                ylim=(5.1e6+1.5e6-data.at[i, '份額累加']*height-(i+1)*7e8,
                      5.1e6+1.5e6-data.at[i, '份額累加']*height)), 
          scale(xlim=(6e6, 6e6+0.8e7),
                ylim=(5.1e6+1.5e6-data.at[i, '份額累加']*height-data.at[i+1, '份額']*height-(i+1)*7e8,
                      5.1e6+1.5e6-data.at[i, '份額累加']*height-data.at[i+1, '份額']*height)))
         for i in range(data.shape[0]-1)]

colors = ['#1b7d98', '#a5dce7']
for i, band in enumerate(bands):
    if i % 2 == 0:
        ax.fill_between(band[0][0], band[0][1], band[1][1], color='#1b7d98')
    else:
        ax.fill_between(band[0][0], band[0][1], band[1][1], color='#a5dce7')
(在模仿中精進資料視覺化02) 溫室氣體排放來源視覺化
圖9

  這樣子,我們就完成了原作品的主要視覺元素的復刻。

2.2.3 其他元素的補充

  接下來的內容就比較簡單,我們只需要把各種文字標註、分割線、刻度等小細節補上即可:

# 其它元素的補充

# y軸數值標籤
for y_, text in zip([0, 2e6, 4e6, 6e6], ['0', '2M', '4M', '6M']):
    ax.text(-1e6, y_, text, ha='right', color='grey', fontsize=8)

# 新增左側矩形內部標註
ax.text(1.5e6, 1.5e6, 'Buildings 3M', color='white', 
        ha='center', fontsize=7, fontweight='bold')
ax.text(1.5e6, 3e6+1.05e6, 'Transportation 2.1M', 
        color='white', ha='center', fontsize=7, fontweight='heavy')

# 新增黑色Today標註
ax.text(1.5e6, -5e5, 'Today', 
        color='black', ha='center', 
        fontsize=10, family='Times New Roman')

# 新增右側文字標註
ax.text(1.42e7, 4.5e6, 'Car 86.28%', fontsize=8, family='Times New Roman')
for i in range(data.shape[0]-1):
    ax.text(1.42e7, 5.1e6+1.5e6-data.at[i, '份額累加']*height-data.at[i+1, '份額']*0.5*height-(i+1)*7e5,
            '{} {}%'.format(data.at[i+1, '型別'], round(data.at[i+1, '份額']*100, 2)),
            va='center', fontsize=8, family='Times New Roman')

# 新增y軸標題
ax.text(-2.2e6, 3.3e6, 'Annual Metric Tons of $CO_{2}$eq Emissions', 
        rotation=90, va='center', fontsize=7.8)

# 上部分隔線
ax.plot([-2.3e6, 1.65e7], [9e6, 9e6], color='#327997', linewidth=0.7)

# 上部黑色說明文字
ax.text(-2.3e6, 
        7.9e6, 
        "40% of Mississauga's GHG emissions come from the transportation sector including freight. Less than 3% of these emissions currently come from \npublic transit.",
        fontsize=7.9,
        style='italic')

# 上部標題
ax.text(-2.3e6, 9.2e6, 'GHG Emissions - Transportation', 
        color='#327997', fontweight='heavy')

  經過這一番操作,最終的結果如圖10所示:

(在模仿中精進資料視覺化02) 溫室氣體排放來源視覺化
圖10

  而原作品中右側並沒有按照比例的降序排列,如果你想降序排列,只需要在建立data之後對資料框按照份額降序並重置index即可~,降序排列後再繪製的效果如圖11所示:

(在模仿中精進資料視覺化02) 溫室氣體排放來源視覺化
圖11

  是不是舒服自然了很多了呢~


  以上就是本文的全部內容,歡迎在評論區與我進行討論~

相關文章