眾所周知,我用Emacs
的ledger-mode
來記賬(參見以前的文章《程式設計師的記賬工具——ledger與ledger-mode》)。作為一個出色的命令列報表工具,ledger
的命令balance
和register
足以涵蓋大部分的使用場景:
balance
可以生成所有帳號的餘額的報表,用於每天與各個賬戶中的真實餘額進行比較;register
可以生成給定帳號的交易明細,用於在餘額不一致時與真實賬戶的流水一條條核對;
美中不足的是,ledger
的報表不夠直觀,因為它們是冷冰冰的文字資訊,而不是振奮人心的統計圖形。好在,正如ledger
不儲存資料,而只是一份份.ledger
檔案中的交易記錄的搬運工一樣,gnuplot
也是這樣的工具——它不儲存資料,它只負責將儲存在文字檔案的資料以圖形的形態呈現出來。
如何運用gnuplot
gnuplot
是很容易使用的。以最簡單的情況為例,首先將如下內容儲存到檔案/tmp/data.csv
中
-1 -1
0 0
1 1
然後在命令列中啟動gnuplot
,進入它的 REPL 中,並執行如下命令
plot "/tmp/data.csv"
即可得到這三組資料的展示
三組資料分別是座標為(-1, -1)
、(0, 0)
,以及(1, 1)
的點。
因此要讓gnuplot
繪製開銷的圖形,首先就是從賬本中提取出要繪製的資料,再決定如何用gnuplot
繪製即可。
用ledger
提取開銷記錄
儘管ledger
的子命令register
可以列印出給定帳號的交易明細,但此處更適合使用csv
子命令。例如,下列的命令可以將最早的10條、吃的方面的支出記錄,都以 CSV 格式列印出來
➜ Accounting ledger --anon --head 10 -f 2021.ledger csv 'Expense:Food'
"2019/09/10","","32034acc","efe2a5b9:c720f278:58a3cd91:0dc07b7b","A","20","",""
"2019/09/11","","a61b6164","5d45e249:fe84ca06:778d1855:daf61ede","A","5","",""
"2019/09/11","","674ec19f","5d018df1:ebf020db:29d43aba:d0c84127","A","15","",""
"2019/09/11","","e55ff018","370ca545:7d3aa2d0:86f5f330:1379261b","A","20","",""
"2019/09/12","","f6aa675c","08315491:4c8f1ee7:5eeaddf3:f879914e","A","10.5","",""
"2019/09/12","","139b790f","a137e4ee:9bc8ee49:7d7ccd8b:472d6007","A","23.9","",""
"2019/09/12","","b24b716d","de348971:5364622c:b2144d94:01e74ff3","A","148","",""
"2019/09/13","","e7c066fa","b418a3b2:a3e21e87:a32ee8ac:8716a847","A","3","",""
"2019/09/13","","9eb044fe","702a13e9:3de7f1bd:9b20a278:1d20668d","A","24","",""
"2019/09/13","","ba301270","d2b7eeb3:381f9473:54f86a33:391a8662","A","36","",""
--anon
選項可以將交易明細中的敏感資訊(如收款方、帳號)等匿名處理。
儘管ledger
列印出的內容有很多列,但只有第一列的日期,以及第六列的金額是我所需要的。同時,由於一天中可能會有多次吃的方面的開銷,因此同一天的交易也會有多筆,在繪圖之前,需要將同一天之中的開銷累加起來,只留下一個數字。這兩個需求,都可以用csvsql
來滿足。
用csvsql
聚合資料
以前文中的10條記錄為例,用如下的命令可以將它們按天聚合在一起
ledger --anon --head 10 -f 2021.ledger csv 'Expense:Food' | csvsql -H --query 'SELECT `a`, SUM(`f`) FROM `expense` GROUP BY `a` ORDER BY `a` ASC' --tables 'expense'
其中:
- 選項
-H
讓csvsql
知道從管道中輸入的資料沒有標題行。後續處理時,csvsql
會預設使用a
、b
、c
等作為列名; - 選項
--query
用於提交要執行的 SQL 語句; - 選項
--tables
用於指定表的名字,這樣在--query
中才能用 SQL 對其進行處理;
結果如下
➜ Accounting ledger --anon --head 10 -f 2021.ledger csv 'Expense:Food' | csvsql -H --query 'SELECT `a`, SUM(`f`) FROM `expense` GROUP BY `a` ORDER BY `a` ASC' --tables 'expense'
a,SUM(`f`)
2019-09-10,20
2019-09-11,40
2019-09-12,182.4
2019-09-13,63
用gnuplot
讀取資料並繪圖
用重定向將csvsql
的輸出結果儲存到檔案/tmp/data.csv
中,然後就可以用gnuplot
將它們畫出來
➜ Accounting ledger --anon --head 10 -f 2021.ledger csv 'Expense:Food' | csvsql -H --query 'SELECT `a`, SUM(`f`) FROM `expense` GROUP BY `a` ORDER BY `a` ASC' --tables 'expense' | tail -n '+2' > /tmp/data.csv
➜ Accounting cat /tmp/plot_expense.gplot
set format x '%y-%m-%d'
set style data boxes
set terminal png font '/System/Library/Fonts/Hiragino Sans GB.ttc'
set title '吃的開銷'
set output '/tmp/xyz.png'
set timefmt '%Y-%m-%d'
set xdata time
set xlabel '日期'
set xrange ['2019-09-10':'2019-09-13']
set ylabel '金額(¥)'
set yrange [0:200]
set datafile separator comma
plot '/tmp/data.csv' using 1:2
➜ Accounting gnuplot /tmp/plot_expense.gplot
生成的圖片檔案/tmp/xyz.png
如下
在指令碼檔案/tmp/plot_expense.gplot
中用到的命令都可以通過gnuplot
的線上手冊查閱到:
set format
命令用於設定座標軸的刻度的格式。set format x "%y-%m-%d"
意味著設定 X 軸的刻度為形如19-09-10
的格式;set style data
命令設定資料的繪製風格。set style data box
表示採用空心柱狀圖;set terminal
命令用於告訴gnuplot
該生成什麼樣的輸出。set terminal png font '/System/Library/Fonts/Hiragino Sans GB.ttc'
表示輸出結果為 PNG 格式的圖片,並且採用給定的字型;set title
命令控制輸出結果頂部中間位置的標題文案;set output
命令用於將原本輸出到螢幕上的內容重定向到檔案中;set timefmt
命令用於指定輸入的日期時間資料的格式。set timefmt '%Y-%m-%d'
意味著輸入的日期時間資料的為形如2019-09-10
的格式;set xdata
命令控制gnuplot
如何理解屬於 X 軸的資料。set xdata time
表示 X 軸上的均為時間型資料;set xlabel
命令控制 X 軸的含義的文案。set ylabel
與其類似,只是作用在 Y 軸上;set xrange
命令控制gnuplot
所繪製的圖形中 X 軸上的展示範圍;set datafile separator
命令控制gnuplot
讀取資料檔案時各列間的分隔符,comma
表示分隔符為逗號。
想要按周統計怎麼辦
假設我要檢視的是2021年每一週在吃的方面的總開支,那麼需要在csvsql
中將資料按所處的是第幾周進行聚合
➜ Accounting ledger -b '2021-01-01' -f 2021.ledger csv 'Expense:Food' | csvsql -H --query 'SELECT strftime("%W", `a`) AS `week`, SUM(`f`) FROM `expense` GROUP BY `week` ORDER BY `a` ASC' --tables 'expense' | tail -n '+2' > /tmp/expense_dow.csv
➜ Accounting head /tmp/expense_dow.csv
00,633.6
01,437.3
02,337.5
03,428.4
04,191.5
05,330.4
06,154.6
07,621.4
08,485.6
09,375.73
同時也需要調整gnuplot
的指令碼
set terminal png font '/System/Library/Fonts/Hiragino Sans GB.ttc'
set title '吃的開銷'
set output '/tmp/xyz2.png'
set xlabel '第幾周'
set xrange [0:54]
set ylabel '金額(¥)'
set yrange [0:1000]
set datafile separator comma
plot '/tmp/expense_dow.csv' using 1:2 with lines
結果如下
想要同時檢視兩年的圖形怎麼辦
gnuplot
支援同時繪製多條曲線,只要使用資料檔案中不同的列作為縱座標即可。假設我要對比的是2020年和2021年,那麼先分別統計兩年的開支到不同的檔案中
➜ Accounting ledger -b '2020-01-01' -e '2021-01-01' -f 2021.ledger csv 'Expense:Food' | csvsql -H --query 'SELECT strftime("%W", `a`) AS `week`, SUM(`f`) FROM `expense` GROUP BY `week` ORDER BY `a` ASC' --tables 'expense' | tail -n '+2' > /tmp/expense_2020.csv
➜ Accounting ledger -b '2021-01-01' -f 2021.ledger csv 'Expense:Food' | csvsql -H --query 'SELECT strftime("%W", `a`) AS `week`, SUM(`f`) FROM `expense` GROUP BY `week` ORDER BY `a` ASC' --tables 'expense' | tail -n '+2' > /tmp/expense_2021.csv
再將處於同一周的資料合併在一起
➜ Accounting csvjoin -H -c a /tmp/expense_2020.csv /tmp/expense_2021.csv | tail -n '+2' > /tmp/expense_2years.csv
最後,再讓gnuplot
一次性繪製兩條折線
set terminal png font '/System/Library/Fonts/Hiragino Sans GB.ttc'
set title '吃的開銷'
set output '/tmp/xyz2years.png'
set xlabel '第幾周'
set xrange [0:54]
set ylabel '金額(¥)'
set yrange [0:1000]
set datafile separator comma
plot '/tmp/expense_2years.csv' using 1:2 with lines title "2020", '/tmp/expense_2years.csv' using 1:3 with lines title "2021"
結果如下
後記
其實仍然是非常不直觀的,因為最終生成的是一張靜態的圖片,並不能做到將滑鼠挪到曲線上時就給出所在位置的縱座標的效果。