本篇繼續 OpenPyXL 套件的測試, 主題是統計圖表的繪製, 本系列之前的文章參考:
參考書籍如下 :
- Python 自動化的樂趣 (碁峰, 2017) 第 12 章
- Python 自動化的樂趣 (第二版) (碁峰, 2020) 第 13 章
- Python 入門邁向高手之路王者歸來 (深石, 2017) 第 19 章
- Python for Excel|自動化與資料分析的現代開發環境
- 超高效!Python × Excel資料分析自動化:輕鬆打造你的完美工作法!
本篇測試中用到的資料來自之前 Matplotlib 測試所使用的數據, 參考 :
首先歸納整理前一篇 (三) 關於儲存格格式化的指令範例 :
- 字型設定 :
from openpyxl.styles import Font
sheet['A2'].font=Font(name='Arial', size=14, bold=True, color='0000ff')
sheet['A3'].font=Font(name='微軟正黑體', size=12, italic=True, strike=True) - 列高與欄寬設定 :
sheet.row_dimensions[2].height=40 # 設定第 2 列列高
sheet.column_dimensions['A'].width=60 # 設定 A 欄欄寬 - 對齊設定 :
from openpyxl.styles import Alignment
sheet['A2'].alignment=Alignment(vertical='top', horizontal='center')
sheet['A3'].alignment=Alignment(vertical='center', horizontal='right')
vertical 參數 : 'top', 'center', 'bottom'
horizontal 參數 : 'left', 'center', 'right' - 框線設定 :
from openpyxl.styles import Border
from openpyxl.styles import Side
side=Side(color='FF0000', style='thick')
border=Border(left=side, right=side, top=side, bottom=side)
sheet['B3'].border=border
style 參數 : 'hair', 'thin', 'medium', 'double', 'thick', 'dotted', 'dashed', 'mediumDashed', 'dashdot', 'dashDotDot', 'slantDashDot', 'mediumDashDot', 'mediumDashDotDot' - 背景色設定 :
from openpyxl.styles import PatternFill
sheet['D3'].fill=PatternFill(start_color='FFFF00', end_color='FFFF00', fill_type='solid') #
fill_type 參數 : 'solid', 'gray0625', 'gray125', 'lightGray', 'mediumGray', 'darkGray', 'lightGrid', 'darkGrid', 'lightHorizontal', 'darkHorizontal', 'lightVertical', darkVertical', 'lightUp', 'darkUp', 'lightDown', 'darkDown', 'lightTrellis', 'darkTrellis' - 格式化條件 (背景色) :
from openpyxl.formatting.rule import CellIsRule
from openpyxl.styles import PatternFill
fill=PatternFill(start_color='FFFF00', end_color='FFFF00', fill_type='solid')
rule=CellIsRule(operator='lessThan', formula=[60], stopIfTrue=True, fill=fill)
sheet.conditional_formatting.add('B3:D7', rule)
8. 繪製統計圖表 :
OpenPyXL 套件的 openpyxl.chart 子模組中提供了可用來繪製統計圖表的類別 :
- BarChart (長條圖)
- LineChart (折線圖)
- BarChart (圓餅圖)
- AreaChart (區域圖)
- ScaterChart (散佈圖)
- BubbleChart (泡泡圖)
- RadarChart (雷達圖)
- StockChart (股票圖)
- BarChart3D (3D 直條圖)
- PieChart3D (3D 圓餅圖)
- AreaChart3D (3D 區域圖)
使用 OpenPyXL 繪製圖表除了要匯入這些類別外, 還需要匯入 openpyxl.chart.Reference 類別, 此類別用來將工作表的儲存格資料與軸的標籤資料引入圖表物件中, 呼叫此類別之建構子 Reference() 即可建立 Reference 物件 :
Reference(worksheet, min_col, maxcol, min_row, max_row)
參數 worksheet 為工作表物件, min_col 為儲存格範圍最左欄索引; max_col 為最右欄索引; min_row 為最上列索引; max_row 為最下列索引, 這些索引均為整數. 由於工作表第一列通常是欄位名稱, max_row 可以傳入工作表物件的 max_row 屬性值, 這樣就不需要去查最後一列索引了.
繪製圖表程序如下 :
- 建立圖表物件 (設定圖表標題與座標軸標題)
- 建立資料的 Reference 物件 (指定工作表中的儲存格範圍)
- 建立軸或標籤的 Reference 物件 (指定工作表中的儲存格範圍)
- 呼叫圖表物件的 add_data() 方法加入資料的 Reference 物件
- 呼叫圖表物件的 set_categories() 方法加入軸或標籤的 Reference 物件
- 呼叫工作表物件的 add_chart() 方法將圖表物件插入指定儲存格中
其中步驟 1, 2, 3 先後順序不重要.
官網教學文件參考 :
(1). 繪製長條圖 :
長條圖用來展示分類或分組資料的大小差異, 結構上有垂直與水平兩種類型, BarChart 類別預設是垂直長條圖. 官網範例參考 :
繪製長條圖須匯入 BarChart 與 Refernece 兩個類別 :
>>> import openpyxl as xl
>>> from openpyxl.chart import BarChart, Reference
>>> Reference
<class 'openpyxl.chart.reference.Reference'>
>>> BarChart
<class 'openpyxl.chart.bar_chart.BarChart'>
呼叫 BarChart() 建立一個 BarChart 圖表物件 :
>>> chart=BarChart()
>>> type(chart)
<class 'openpyxl.chart.bar_chart.BarChart'>
利用 eval() 與 dir() 來檢視 BarChart 物件的公開成員 :
>>> members=dir(chart)
>>> for mbr in members:
obj=eval('chart.' + mbr)
if not mbr.startswith('_'):
print(mbr, type(obj))
add_data <class 'method'>
anchor <class 'str'>
append <class 'method'>
axId <class 'list'>
barDir <class 'str'>
dLbls <class 'NoneType'>
dataLabels <class 'NoneType'>
display_blanks <class 'str'>
extLst <class 'openpyxl.descriptors.base.Typed'>
from_tree <class 'method'>
gapWidth <class 'float'>
graphical_properties <class 'NoneType'>
grouping <class 'str'>
height <class 'float'>
idx_base <class 'int'>
layout <class 'NoneType'>
legend <class 'openpyxl.chart.legend.Legend'>
mime_type <class 'str'>
namespace <class 'NoneType'>
overlap <class 'NoneType'>
path <class 'str'>
pivotFormats <class 'tuple'>
pivotSource <class 'NoneType'>
plot_area <class 'openpyxl.chart.plotarea.PlotArea'>
roundedCorners <class 'NoneType'>
ser <class 'list'>
serLines <class 'NoneType'>
series <class 'list'>
set_categories <class 'method'>
style <class 'NoneType'>
tagname <class 'str'>
title <class 'NoneType'>
to_tree <class 'method'>
type <class 'str'>
varyColors <class 'NoneType'>
visible_cells_only <class 'bool'>
width <class 'int'>
x_axis <class 'openpyxl.chart.axis.TextAxis'>
y_axis <class 'openpyxl.chart.axis.NumericAxis'>
常用的屬性如下表 :
BarChart 物件常用屬性 | 說明 |
title | 圖表之標題 |
x_axis | X 軸物件 (TextAxis), 可用其 title 屬性存取 X 軸標題文字 |
y_axis | Y 軸物件 (NumberAxis), 可用其 title 屬性存取 Y 軸標題文字 |
type | 圖表類型 : 'col'=垂直長條圖 (預設), 'bar'=水平長條圖 |
style | 樣式 (整數, 預設 None) |
width | 寬度 (整數, 預設 15) |
height | 高度 (整數, 預設 7.5) |
常用的方法如下表 :
BarChart 物件常用方法 | 說明 |
append(series) | 將一組資料 (Series 物件) 加入圖表中 |
add_data(y) | 將 Y 軸資料 (Reference 物件) 加入圖表物件中 |
add_categories(x) | 將 X 軸類別 (Reference 物件) 加入圖表物件中 |
注意, add_data() 還有一個備選參數 titles_from_data, 若設為 True 會將第一列視為圖例 (legend) 的來源, 這時在建立 Y 軸資料的 Reference 物件時就要納入第一列.
首先來繪製單組 (單變數) 的長條圖, 下面以台灣 2020 年總統大選得票數統計資料為例來繪製長條圖, 先建立一個空白將資料寫入工作表 :
>>> wb=xl.Workbook()
>>> ws=wb.active
>>> ws
<Worksheet "Sheet">
>>> votes=[['候選人', '得票數'],
['宋楚瑜', 608590],
['韓國瑜', 5522119],
['蔡英文', 8170231]]
>>> for row in votes: # 將串列逐列寫入工作表
ws.append(row)
>>> for row in ws.rows: # 檢視工作表內容
for cell in row:
print(cell. value, end='\t')
print()
候選人 得票數
宋楚瑜 608590
韓國瑜 5522119
蔡英文 8170231
接下來就可以根據這兩個範圍 X, Y 軸的 Reference 物件 :
>>> x=Reference(ws, min_col=1, max_col=1, min_row=2, max_row=ws.max_row)
>>> y=Reference(ws, min_col=2, max_col=2, min_row=1, max_row=ws.max_row)
>>> type(x)
<class 'openpyxl.chart.reference.Reference'>
>>> type(y)
<class 'openpyxl.chart.reference.Reference'>
此處因 X 軸分類都在第一欄, 故 min_col 與 max_col 均為 1, 分類內容從第二列至最後一列. Y 軸資料都在第二欄, 故 min_col 與 max_col 均為 2. 要注意的是, 雖然資料內容是從第二列至最後列, 但因為需要用第一列的欄名當圖例 (legend), 所以資料的 min_row 要從第一列開始, 這樣才會自動將欄名設為圖例.
接著便可將 X 軸與 Y 軸的 Reference 物件分別呼叫圖表物件的 set_categories() 與 add_data() 加入圖表中, 並且設定圖表標題與兩座標軸之標題 :
>>> chart.add_data(y, titles_from_data=True) # 將 Y 軸資料加入圖表
>>> chart.set_categories(x) # 將 X 軸類別加入圖表
>>> chart.title='2020 總統大選得票數' # 圖表標題
>>> chart.x_axis.title='候選人' # X 軸標題
>>> chart.y_axis.title='得票數' # Y 軸標題
注意, 此處 add_data() 的參數 titles_from_data 設為 True 表示要以第一列的欄名當圖例 (所以在上面在建立 Y 軸資料的 Reference 物件時 min_row 要設為 1). 最後將圖表物件插入到儲存格 :
>>> ws.add_chart(chart, 'C1')
>>> wb.save('2020_presidential.xlsx')
用 Excel 開啟檔案結果如下 :
茲將以上繪圖程式碼整理如下 :
範例 1 : 2020 總統大選得票數垂直長條圖 [原始碼]
import openpyxl as xl
from openpyxl.chart import BarChart, Reference
wb=xl.Workbook()
ws=wb.active
votes=[['候選人', '得票數'],
['宋楚瑜', 608590],
['韓國瑜', 5522119],
['蔡英文', 8170231]]
for row in votes:
ws.append(row)
chart=BarChart()
chart.title='2020 總統大選得票數'
chart.x_axis.title='候選人'
chart.y_axis.title='得票數'
x=Reference(ws, min_col=1, max_col=1, min_row=2, max_row=ws.max_row)
y=Reference(ws, min_col=2, max_col=2, min_row=1, max_row=ws.max_row)
chart.add_data(y, titles_from_data=True)
chart.set_categories(x)
ws.add_chart(chart, 'C1')
wb.save('2020_presidential_1.xlsx')
如果要繪製垂直長條圖的話, 只要將圖表物件的 type 屬性設為 'bar' 即可 :
chart.type='bar'
例如 :
範例 2 : 2020 總統大選得票數水平長條圖 [原始碼]
import openpyxl as xl
from openpyxl.chart import BarChart, Reference
wb=xl.Workbook()
ws=wb.active
votes=[['候選人', '得票數'],
['宋楚瑜', 608590],
['韓國瑜', 5522119],
['蔡英文', 8170231]]
for row in votes:
ws.append(row)
chart=BarChart()
chart.title='2020 總統大選得票數'
chart.x_axis.title='候選人'
chart.y_axis.title='得票數'
chart.type='bar'
x=Reference(ws, min_col=1, max_col=1, min_row=2, max_row=ws.max_row)
y=Reference(ws, min_col=2, max_col=2, min_row=1, max_row=ws.max_row)
chart.add_data(y, titles_from_data=True)
chart.set_categories(x)
ws.add_chart(chart, 'C1')
wb.save('2020_presidential_2.xlsx')
結果如下 :
繪製多組資料的長條圖作法相同, 差別只是 Y 軸資料是一欄以上而已,
範例 3 : 兩組資料之長條圖-ETF 殖利率比較 [原始碼]
import openpyxl as xl
from openpyxl.chart import BarChart, Reference
wb=xl.Workbook()
ws=wb.active
dividend=[['年份', '0056殖利率', '0050殖利率'],
['2015', 4.33, 3.01],
['2016', 5.67, 1.28],
['2017', 3.78, 6.1],
['2018', 5.62, 7.1],
['2019', 6.7, 7.22]]
for row in dividend:
ws.append(row)
chart=BarChart()
chart.title='0056 vs 0050 殖利率'
chart.x_axis.title='ETF'
chart.y_axis.title='殖利率'
x=Reference(ws, min_col=1, max_col=1, min_row=2, max_row=ws.max_row)
y=Reference(ws, min_col=2, max_col=3, min_row=1, max_row=ws.max_row)
chart.add_data(y, titles_from_data=True)
chart.set_categories(x)
ws.add_chart(chart, 'D1')
wb.save('ETF_dividend_yield.xlsx')
注意此處 Y 軸資料位於 B, C 兩欄, 故 max_col 設為 3, 對於多組資料 max_col 可以設為 ws.max_col.
結果如下 :
可見由於在 add_data() 中有傳入 titles_from_data=True, 且建立 Y 軸資料的 Reference 物件時 min_row 設為 1, 將第一列的欄位名稱納入資料中, 繪製圖表時就會自動以欄名作為圖例, 這使所繪製的多組資料很有可讀性.
只要將上面範例中匯入的 BarChart 類別改成 BarChart3D 就能繪製 3D 長條圖了, 例如 :
範例 4 : 兩組資料之 3D 長條圖-ETF 殖利率比較 [原始碼]
import openpyxl as xl
from openpyxl.chart import BarChart3D, Reference
wb=xl.Workbook()
ws=wb.active
dividend=[['年份', '0056殖利率', '0050殖利率'],
['2015', 4.33, 3.01],
['2016', 5.67, 1.28],
['2017', 3.78, 6.1],
['2018', 5.62, 7.1],
['2019', 6.7, 7.22]]
for row in dividend:
ws.append(row)
chart=BarChart3D()
chart.title='0056 vs 0050 殖利率'
chart.x_axis.title='ETF'
chart.y_axis.title='殖利率'
x=Reference(ws, min_col=1, max_col=1, min_row=2, max_row=ws.max_row)
y=Reference(ws, min_col=2, max_col=3, min_row=1, max_row=ws.max_row)
chart.add_data(y, titles_from_data=True)
chart.set_categories(x)
ws.add_chart(chart, 'D1')
wb.save('ETF_dividend_yield_3D.xlsx')
結果如下 :
(2). 繪製折線圖 :
折線圖是將各個資料點以直線連接的統計圖, 官網範例參考 :
以上面的 0056 vs 0050 殖利率為例, 只要匯入 LineChart 類別替換即可繪製折線圖 :
範例 5 : 兩組資料之折線圖-ETF 殖利率比較 [原始碼]
import openpyxl as xl
from openpyxl.chart import LineChart, Reference
wb=xl.Workbook()
ws=wb.active
dividend=[['年份', '0056殖利率', '0050殖利率'],
['2015', 4.33, 3.01],
['2016', 5.67, 1.28],
['2017', 3.78, 6.1],
['2018', 5.62, 7.1],
['2019', 6.7, 7.22]]
for row in dividend:
ws.append(row)
chart=LineChart()
chart.title='0056 vs 0050 殖利率'
chart.x_axis.title='ETF'
chart.y_axis.title='殖利率'
x=Reference(ws, min_col=1, max_col=1, min_row=2, max_row=ws.max_row)
y=Reference(ws, min_col=2, max_col=3, min_row=1, max_row=ws.max_row)
chart.add_data(y, titles_from_data=True)
chart.set_categories(x)
ws.add_chart(chart, 'D1')
wb.save('ETF_dividend_yield_linechart.xlsx')
結果如下 :
(3). 繪製圓餅圖 :
圓餅圖用來顯示一個數值資料相對於全部資料的大小相對關係, 只能用於單一變數 (一組序列資料) 繪圖. 官網範例參考 :
繪製圓餅圖須匯入 PieChart 與 Reference 類別, 其用法與上面直條圖/折線圖類似, 但 PieChart 物件沒有 x_axis 與 y_axis 屬性, 例如 :
範例 6 : 資產配置圓餅圖 [原始碼]
import openpyxl as xl
from openpyxl.chart import PieChart, Reference
wb=xl.Workbook()
ws=wb.active
dividend=[['資產項目', '配置比率'],
['股票', 60],
['基金', 20],
['現金', 10],
['黃金', 5],
['不動產', 5]]
for row in dividend:
ws.append(row)
chart=PieChart()
chart.title='資產配置比例'
x=Reference(ws, min_col=1, max_col=1, min_row=2, max_row=ws.max_row)
y=Reference(ws, min_col=2, max_col=2, min_row=1, max_row=ws.max_row)
chart.add_data(y, titles_from_data=True)
chart.set_categories(x)
ws.add_chart(chart, 'D1')
wb.save('asset_allocation.xlsx')
結果如下 :
只要將上面範例中的 PieChart 改成 PieChart3D 就可以繪製 3D 圓餅圖了, 例如 :
範例 7 : 資產配置 3D 圓餅圖 [原始碼]
import openpyxl as xl
from openpyxl.chart import PieChart3D, Reference
wb=xl.Workbook()
ws=wb.active
dividend=[['資產項目', '配置比率'],
['股票', 60],
['基金', 20],
['現金', 10],
['黃金', 5],
['不動產', 5]]
for row in dividend:
ws.append(row)
chart=PieChart3D()
chart.title='資產配置比例'
x=Reference(ws, min_col=1, max_col=1, min_row=2, max_row=ws.max_row)
y=Reference(ws, min_col=2, max_col=2, min_row=1, max_row=ws.max_row)
chart.add_data(y, titles_from_data=True)
chart.set_categories(x)
ws.add_chart(chart, 'C1')
wb.save('asset_allocation_3D.xlsx')
結果如下 :
如果要凸顯圓餅途中的某個分類之扇形, 讓它從圓餅中分離 (explosion) 開來, 這需要從 openpyxl.series 子模組中匯入 DataPoint 類別來建立一個 DataPoint 物件 :
>>> from openpyxl.series import DataPoint
>>> dir(DataPoint)
['__add__', '__attrs__', '__class__', '__copy__', '__delattr__', '__dict__', '__dir__', '__doc__', '__elements__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__lt__', '__module__', '__namespaced__', '__ne__', '__nested__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'bubble3D', 'explosion', 'extLst', 'from_tree', 'graphicalProperties', 'idx', 'idx_base', 'invertIfNegative', 'marker', 'namespace', 'pictureOptions', 'spPr', 'tagname', 'to_tree']
此類別的 idx 屬性用來指定要分離的類別索引 (注意, 這是 0 起始索引); 而 explosion 屬性則用來指定分離的程度 (0~100), 例如 :
>>> data_point=DataPoint(idx=1, explosion=20) # idx=1 為第 2 個類別要分離
>>> type(data_point)
<class 'openpyxl.chart.marker.DataPoint'>
此處 idx=1 表示是要分離第 2 個類別, 注意 idx 是 0 起始, 以上面資產配置比例為例, idx=1 是基金, 而 idx=0 是股票.
然後將圖表中的 Series[0] 物件之 data_points 屬性指定為此 DataPoint 串列即可 :
>>> type(chart.series[0])
<class 'openpyxl.chart.series.Series'>
>>> dir(chart.series[0])
['__add__', '__attrs__', '__class__', '__copy__', '__delattr__', '__dict__', '__dir__', '__doc__', '__elements__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__lt__', '__module__', '__namespaced__', '__ne__', '__nested__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'bubble3D', 'bubbleSize', 'cat', 'dLbls', 'dPt', 'data_points', 'errBars', 'explosion', 'extLst', 'from_tree', 'graphicalProperties', 'identifiers', 'idx', 'idx_base', 'invertIfNegative', 'labels', 'marker', 'namespace', 'order', 'pictureOptions', 'shape', 'smooth', 'spPr', 'tagname', 'title', 'to_tree', 'trendline', 'tx', 'val', 'xVal', 'yVal', 'zVal']
>>> chart.series[0].data_points=[data_point]
完整的程式碼如下 :
範例 8 : 資產配置圓餅圖 : 分離凸顯某類別 [原始碼]
import openpyxl as xl
from openpyxl.chart import PieChart, Reference
from openpyxl.chart.series import DataPoint
wb=xl.Workbook()
ws=wb.active
dividend=[['資產項目', '配置比率'],
['股票', 60],
['基金', 20],
['現金', 10],
['黃金', 5],
['不動產', 5]]
for row in dividend:
ws.append(row)
chart=PieChart()
chart.title='資產配置比例'
x=Reference(ws, min_col=1, max_col=1, min_row=2, max_row=ws.max_row)
y=Reference(ws, min_col=2, max_col=2, min_row=1, max_row=ws.max_row)
chart.add_data(y, titles_from_data=True)
chart.set_categories(x)
data_point=DataPoint(idx=1, explosion=20)
chart.series[0].data_points=[data_point]
ws.add_chart(chart, 'C1')
wb.save('asset_allocation_with_explosion.xlsx')
結果如下 :
將上面範例中的 PieChart 改成 PieChart3D 就可以繪製有凸顯分離效果的 3D 圓餅圖了, 例如 :
範例 9 : 資產配置 3D 圓餅圖 : 分離凸顯某類別 [原始碼]
import openpyxl as xl
from openpyxl.chart import PieChart3D, Reference
from openpyxl.chart.series import DataPoint
wb=xl.Workbook()
ws=wb.active
dividend=[['資產項目', '配置比率'],
['股票', 60],
['基金', 20],
['現金', 10],
['黃金', 5],
['不動產', 5]]
for row in dividend:
ws.append(row)
chart=PieChart3D()
chart.title='資產配置比例'
x=Reference(ws, min_col=1, max_col=1, min_row=2, max_row=ws.max_row)
y=Reference(ws, min_col=2, max_col=2, min_row=1, max_row=ws.max_row)
chart.add_data(y, titles_from_data=True)
chart.set_categories(x)
data_point=DataPoint(idx=1, explosion=20)
chart.series[0].data_points=[data_point]
ws.add_chart(chart, 'C1')
wb.save('asset_allocation_with_explosion_3D.xlsx')
結果如下 :
沒有留言 :
張貼留言