
我們經常會遇到以下情況:冗長(數據分析)計算吐出一堆結果值。想像 Python 或腳本或 R 腳本正在執行一些資源密集型計算。然後,我們撰寫一篇文章或報告,在其中描述結果並在文字或表格中列出結果值。複製和貼上這些值很容易出錯且工作量很大,因此最好自動包含值。
例子:這裡的所有數值都是腳本的結果,需要以某種方式包含在文章中:
實現這一目標的最佳方法是什麼?
筆記:計算可能會運行幾個小時——甚至在單獨的計算集群上也是可能的。因此,計算結果應該是將結果包含在 LaTeX 文件中並對其進行編譯的分離步驟。
獎金需求:看到 Latex 編輯器(例如 Overleaf)中顯示的值而不僅僅是 include 命令可能會很高興。當使用像 Overleaf 上的 Wysiwyg 編輯器時,這可能非常有幫助,但我懷疑是否可以在不複製和貼上值或以某種方式預處理 Latex 檔案的情況下完成此操作。
也可以看看:reddit 上的這個問題。
答案1
將結果寫入單獨的文字檔案並使用\input
最直接、最簡單的解決方案是在計算過程中將每個值寫入文字檔案。例如,計算可以在最後收集 LaTeX 文件中所需的所有值並將時間寫入文字檔案:
def write_include_value(name, value):
"""Write a value to a text file for later use in a LaTeX document.
Parameters
----------
name : str
name of the file, also used in the LaTeX document as name
value : str
the value to be stored, passed as string so formatting (e.g. number of
digits) needs to be done before calling the function
"""
with open(OUTPUT_DIR / "include-values" / name, "w") as f:
f.write(value + "\n")
呼叫函數時,可以配置顯示的位數:
write_include_value("average_temperature", f"{average_temperature.values:.1f}")
將以下程式碼片段放入 LaTeX 文件的序言中可以輕鬆地將值包含在文字中:
\newcommand*{\includevalue}[1]{\input{../../data/output/include-values/#1}\unskip}
然後可以使用新命令來使用該值\includevalue
,例如:
\includevalue{average_temperature}
陷阱和缺點
- siunitx 套件不能與高級命令一起使用
\input
,因此\includevalue
不能在命令內部使用\qty
。因此,我為帶有單位的數量添加了一個附加命令:
% The package siunitx does not work with the highlevel command \input, therefore \includevalue
% cannot be used inside of a \qty command. Instead use: \qtyincludevalue{filename}{m/s^2}
% Copied and adapted from here: https://tex.stackexchange.com/a/108093/8964
\def\inputval{0}
\newread\inputFile
\newcommand*{\qtyincludevalue}[3][]{%
\IfFileExists{../../data/output/data-values/#2}{
\openin\inputFile=../../data/output/data-values/#2
\read\inputFile to \inputval
\closein\inputFile
\qty[#1]{\inputval}{#3}%
}{\qty[#1]{#2}{#3}}%
}
- 有些期刊在提交過程中限製文件數量。使用這種透過單獨文件包含值的方法意味著您可以輕鬆地獲得 100 個文件,並且提交入口網站不會讓您提交文章。我使用此 Python 腳本作為解決方法,將所有包含內容替換為實際值。這不太好,因為它為 LaTeX 文件的編譯添加了額外的步驟,這使得事情更容易出錯,但它確實有效。
import os
import re
import sys
def replace_placeholders(filename):
with open(filename, "r") as f:
contents = f.read()
pattern = r"\\(qty)?includevalue\{([\w-]+)\}"
matches = re.findall(pattern, contents)
for match in matches:
replace_string = ""
file_path = os.path.join("data", "output", "data-values", match[1])
with open(file_path, "r") as f:
replace_string = f.read().strip()
if match[0] == "qty":
replace_string = "\\qty{" + replace_string + "}"
contents = contents.replace(
"\\{}includevalue{{{}}}".format(match[0], match[1]), replace_string
)
return contents
if __name__ == "__main__":
print(replace_placeholders(f"{sys.argv[1]}.noreplace"))
- 包含值檔案的資料夾路徑必須指定兩次 - 一次在 Python 程式碼中,然後再次在 LaTeX 標頭中。
答案2
如果您使用 R,請使用 Knit
針織品允許您在 LaTeX 文件中執行 R 程式碼片段。如果計算結果儲存在檔案中(也可以是二進位文件,如 NetCDF 或 CSV 檔案),您可以使用 Knit 和 R 程式碼載入所需的值並將其包含在 LaTeX 檔案中:
<<results="asis",echo=FALSE>>=
cat(read.csv("a_csv_file.csv", sep=";")[1,2])
@
或一張桌子:
<<xtable, results="asis">>=
n <- 100
x <- rnorm(n)
y <- 2*x + rnorm(n)
out <- lm(y ~ x)
library(xtable)
xtable(summary(out)$coef, digits=c(0, 2, 2, 1, 2))
@
(範例取自卡爾布羅曼)
理論上,Knit 也支援 Python,但在 LaTeX 中使用 R 執行 Python 片段感覺很奇怪。因此,如果您不使用 R,我建議不要使用 Knit。
最小的例子
\documentclass{article}
\begin{document}
The meaning is:
<<results="asis",echo=FALSE>>=
cat(read.csv("a_csv_file.csv", sep=";")[1,2])
@
\section*{A table}
<<xtable, results="asis",echo=FALSE>>=
n <- 100
x <- rnorm(n)
y <- 2*x + rnorm(n)
out <- lm(y ~ x)
library(xtable)
xtable(summary(out)$coef, digits=c(0, 2, 2, 1, 2))
@
\end{document}
上面的程式碼儲存為knitr_test.Rnw
並使用以下命令建立 tex 檔案:
R -e 'library(knitr);knit("knitr_test.Rnw")'
tex 檔如下所示:
\documentclass{article}
% knitr inserts a lot of stuff in the header, omitted here for simplicity
\begin{document}
The meaning is:
42
\section*{A table}
% latex table generated in R 4.3.2 by xtable 1.8-4 package
% Wed Jan 17 17:53:32 2024
\begin{table}[ht]
\centering
\begin{tabular}{rrrrr}
\hline
& Estimate & Std. Error & t value & Pr($>$$|$t$|$) \\
\hline
(Intercept) & 0.24 & 0.08 & 2.8 & 0.01 \\
x & 2.02 & 0.09 & 21.3 & 0.00 \\
\hline
\end{tabular}
\end{table}
\end{document}
這是渲染的結果:
背頁
線上 LaTeX背頁支援針織開箱即用。只需將您的 LaTeX 檔案重新命名為*.Rtex
.
缺點
- 編譯 LaTeX 檔案期間需要執行額外的步驟。
- 包含單一值的代碼相當長。
更多資源
這是一個簡短的教程關於如何將 Knit 與 LaTeX 結合使用。
在背面的文檔。
這問答討論如何避免使用cat()
隱藏輸出前綴所必需的[1]
。
答案3
您正在搜尋的概念識字程式設計,這實際上不限於帶有 R 槽 knit 的 LaTeX,但可能是近年來最成功的概念證明。
但對於一般的文學編程,不限於 R,也不限於 Python,也不限於 LateX,我建議四開本。 Quarto 可以使用 Python、R、Julia 和 Observable 創建動態內容,以透過 LaTeX 或 ConTeXt(以及其他幾種格式,但這不是這裡的主題...)產生 PDF。
如果有任何 R 區塊,Quarto 將預設使用 Knit 引擎,但 Jupyter 是另一種語言(python、julia、bash 等)的可執行程式碼。看這裡 了解引擎選型的詳細資訊。
最後,Knit 支援 Python 和許多其他語言,不只是「理論上」。在 R 運行時透過 R 執行 Python 並不是一個大罪。此外,這可能有一些優點,因為允許在同一文件中運行兩種語言的片段,甚至將變數從一種語言傳遞到另一種語言,例如:
---
title : A minimal working example
format: pdf
classoption: twocolumn
header-includes: \columnsep1.5cm
---
```{r}
#| echo: false
library(reticulate)
```
##### This is Python in \LaTeX:
```{python}
#| echo: false
#| results: asis
import matplotlib.pyplot
import pylab
lst = [11.21,22.84,33.25,44.67,55.35]
lst2 = [5,6,7,12,24]
print("This is a plot of list")
print(lst)
print("and")
print(lst2)
print(":")
```
```{python}
#| echo: false
#| fig-cap: Pyhon plot
matplotlib.pyplot.scatter(lst,lst2)
```
\newpage
##### And this R using Python code
The values of python list "`lst`" are
`r knitr::combine_words(py$lst)` with a mean
of rougly `r round(mean(py$lst),1)`.
```{r}
#| echo: false
#| results: asis
#| fig.cap: R plot of Python lists
#| fig-height: 4
plot(py$lst,py$lst2,ylab="",
xlab="",col="blue",pch=19)
```
您可以添加engine: jupyter
到標頭以避免使用 knit(R 程式碼將僅顯示但不會執行),或者只需刪除最後一部分(從\newpage
到最後)以自動切換到Jupyter,它將在沒有R 和knit 的情況下運行python3。
但是,如果此工作流程因某種原因困擾您,還有用於運行 Python 的 LaTeX 套件直接地。
答案4
為 Latex 中的結果產生新的 Latex 指令
您可以建立自己的 Latex 命令:每個結果一個命令。計算管道可以運行一個腳本,該腳本創建一個 Latex 頭文件,包括命令定義和結果。然後可以將該文件包含在 Latex 文件中。
這有點類似透過文字檔案解決方案和\input
。感謝 @Teepeemm 在評論中建議解決方案的這種變體。
程式碼片段1
將類似的內容新增到您的計算管道中,並將所有值新增至 Latex 文件中所需的字典:
LATEX_FILE_NAME = 'result_values.tex'
result_values = {}
meaning_of_life = 42
result_values["meaningoflife"] = f"{meaning_of_life:d}"
# - A unit can be passed as string as second parameter: the LaTeX package siunitx will be used to display the quantity.
# - Use .2f for rounding to two decimals.
gravity_ms2 = 9.80665
result_values["gravity"] = f"{gravity_ms2:.2f}", "m/s^2"
write_result_values(result_values, LATEX_FILE_NAME)
程式碼片段2
此函數寫入 Latex 標頭:
import re
def format_latex_command(key, value, unit=None):
# test if key contains invalid characters for a latex command:
if not re.match(r"^[a-zA-Z]+$", key):
raise ValueError(f"Invalid key '{key}': not a valid latex command name")
if unit is not None:
value = f"\\qty{{{value}}}{{{unit}}}"
return f"\\newcommand{{\\{key}}}{{{value}}}"
def write_result_values(result_values, filename):
"""Write the result values to a latex header file creating new Latex command for each value.
Parameters
----------
result_values : dict
Results to be written to the Latex header file: keys of the dictionary are the names of the
latex commands the values are either a single value or a tuple containing the value and the
unit.
filename : str
The name of the Latex header file to write the result values to.
"""
result_values_flat = [
(key, *value) if isinstance(value, tuple) else (key, value)
for key, value in result_values.items()
]
latex_commands = [format_latex_command(*params) for params in result_values_flat]
with open(filename, "w") as f:
f.write("\n".join(latex_commands) + "\n")
Latex文件中如何使用
result_values.tex
運行上面的程式碼片段 1 後,將建立一個檔案:
\newcommand{\meaningoflife}{42}
\newcommand{\gravity}{\qty{9.81}{m/s^2}}
....然後可以使用新的 Latex 命令將值添加到文字中:
\documentclass{article}
\usepackage{siunitx}
\include{result_values}
\begin{document}
\section*{Example of including result values}
The gravity constant is \gravity. And the meaning of life is \meaningoflife.
\end{document}
渲染結果如下所示: