輕鬆畫出有學術質感的圖表
前言
本篇示範一個用 matplotlib 畫一個基本的折線圖,在 ubuntu 跟 windows 10 都有試過,不過本篇以 windows 10 的指令為主。
雖然有想過寫成一個工具,使用者只要提供資料,就可以自動畫出圖。不過後來發現這個想法是不實際,因為每張圖幾乎都需要客製化,所以就改成提供一個模板,要做任何調整會比較方便。
安裝套件
Python3.9
Windows 直接到官網下載即可,Ubuntu 需要使用 PPA1。
示意圖
安裝 virtualenv 並進入虛擬環境
> pip install virtualenv
要進入虛擬環境時需要執行 active 的腳本,在 Ubuntu 就很方便執行 $ source venv/bin/activate
,但是 powershell 有權限問題,所以要先設定2。
> mkdir matplotlib_test
> cd matplotlib_test
> virtualenv --python python3.9 venv
> Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process -Force
> .\venv\Scripts\activate.ps1
(venv)> pip list
Matplotlib and SciencePlots
圖表樣式主要靠 SciencePlots3 來完成,所以我們只要專注使用 Matplotlib 來製作圖表即可。
(venv)> pip install matplotlib==3.4.2
(venv)> pip install SciencePlots==1.0.8
(venv)> pip list # 詳細版本資訊參考用
Package Version
--------------- -------
cycler 0.10.0
kiwisolver 1.3.1
matplotlib 3.4.2
numpy 1.21.1
Pillow 8.3.1
pip 21.2.3
pyparsing 2.4.7
python-dateutil 2.8.2
SciencePlots 1.0.8
setuptools 57.4.0
six 1.16.0
wheel 0.37.0
畫圖技巧
強烈建議先去看一下這篇 matplotlib:先搞明白plt. /ax./ fig再画。我原先用 matplotlib 也是亂用,看過之後了解大概的區塊是哪個模組在控制。
下圖的各個部位的名稱可以大概記一下,這樣在搜尋時會比較方便。主要中心思想就是關於實際畫圖的都使用 ax
,只有在建立跟輸出時才用 plt
。下方會有範例。
實際範例
n,A,B,C,D
10,1,2,1,6
100,3,3,6,6
1000,2,9,1,6
10000,3,7,7,6
100000,3,10,8,6
1000000,16,19,2,6
10000000,125,86,41,6
plot.py
:
|
|
- 程式碼解釋
- [10], [11] 會有兩個 name 主要是因為讀取時的欄位名稱不能有特殊符號,所以在繪製時才綁定顯示名稱 [31]
- [12] 某些資料要跳過但是檔案裡還是會有
- [13] 哪些需要標出資料標籤的數值
- [14] 哪些資料點需要特別用圓點標記
- [37]
f"{y:.1f}"
可設定資料標籤輸出格式,例如: 取到小數點第一位。x-0.0*x, y-0.2*y
後方有減去一個位移是用來避免線段與資料標籤重疊 - [46] 一般論文中的圖表標題是用 Latex 語法 (caption) 來定義,所以生成時不需要,不過若是要拿來做簡報的話有標題會比較好。
- [48] ~ [56] 設定 x、y 軸資訊
- [61] 存成 pdf
- 成果
[2021.08.13] 修正 linestyle
右軸 (twinx) 範例
[2024.02.26] 新增 (Windows 11, Python 3.12.2, matplotlib 3.8.3, numpy 1.26.4, SciencePlots 2.1.1, pillow 10.2.0)
A, B, C, D, E
7, 13, 5, 17, 10
50, 40, 30, 20, 10
import matplotlib.pyplot as plt
import numpy as np
import scienceplots
# File input/output
fin = "data.csv"
fout = "result.pdf"
# Read data from file
table = np.genfromtxt(fin, delimiter=",", names=True)
print(f"{table = }")
categories = list(table.dtype.names)
primary_values = list(table[0])
secondary_values = list(table[1])
# Data hardcode (optional)
# categories = ['A', 'B', 'C', 'D', 'E']
# primary_values = [7, 13, 5, 17, 10]
# secondary_values = [50, 40, 30, 20, 10]
# Plot the figure
plt.style.use(["science", "ieee"])
plt.rcParams.update(
{"text.usetex": True, "font.family": "serif", "font.serif": ["Lin Libertine"]}
)
# Create figure and axes
fig, ax_l = plt.subplots()
# Create a second y-axis sharing the same x-axis
ax_r = ax_l.twinx()
# Create bar graph for the primary data
bars1 = ax_l.bar(categories, primary_values, label="Primary", color="C2")
# Create a line plot for the secondary data
ax_r.plot(categories, secondary_values, "C1.-", label="Secondary")
# Show legend
ax_l.legend(loc="upper left")
ax_r.legend(loc="upper right")
# Rotate the text on the X axis
ax_l.tick_params(axis='x', labelrotation=60)
# Add labels and title
plt.xlabel("Categories")
ax_l.set_ylabel("Primary values (\\%)")
ax_r.set_ylabel("Secondary values (s)")
plt.title("Graph Title")
# Adjust ylim to add a gap between the highest bar and the top rule of the figure
ax_l.set_ylim(0, max(primary_values) * 1.2)
ax_r.set_ylim(0, max(secondary_values) * 1.2)
plt.tight_layout()
# plt.show()
plt.savefig(fout, bbox_inches="tight")
print("plot finished")
其他參考
離題
用 Matplotlib 在一個 Figure 中塞入多個 Axes 是可以的,不過我目前遇到的都是用 Latex 的 minipage 來完成,這樣就可以有各自的 label 可以分開 ref。如下,不過這就有點離題了。
\begin{table}[tbh!]
\centering
\begin{minipage}[t]{.47\linewidth}
\includegraphics[width=\linewidth]{figures/A.pdf}
\captionof{figure}{AAA}
\label{fig:A}
\end{minipage}
\qquad
\begin{minipage}[t]{.47\linewidth}
\includegraphics[width=\linewidth]{figures/B.pdf}
\captionof{figure}{BBB}
\label{fig:B}
\end{minipage}
\end{table}
錯誤排除
FileNotFoundError: missing font metrics file: rsfs10
Solution 6
> miktex-maketfm.exe rsfs10
PermissionError: [Errno 13] Permission denied: 'result.pdf'
輸出覆寫 pdf 時有視窗開著該檔案,把 result.pdf 關掉即可。