
A menudo nos encontramos en la siguiente situación: un largo (análisis de datos)cálculoescupe un montón devalores de resultado. Imagine un Python, un script o un script de R realizando algunos cálculos que requieren muchos recursos. Luego escribimos un artículo o informe, donde queremos describir los resultados y enumerar los valores de los resultados en el texto o en tablas. Copiar y pegar los valores es propenso a errores y requiere mucho trabajo, por lo que sería buenoincluir automáticamente los valores.
Ejemplo:Todos los valores numéricos aquí son resultados de scripts y deben incluirse en el artículo de una forma u otra:
¿Cuál es la mejor manera de lograr esto?
Nota:El cálculo puede durar muchas horas, incluso en un grupo de cálculo independiente. Por lo tanto, calcular los resultados debería ser un paso separado de incluir los resultados en el documento LaTeX y compilarlo.
Requisito de bonificación:Sería bueno ver los valores mostrados en el editor Latex (por ejemplo, al dorso) y no solo el comando de inclusión. Esto podría ser muy útil cuando se utiliza un editor Wysiwyg como el que se muestra al dorso, pero dudo que se pueda hacer esto sin copiar y pegar los valores o preprocesar el archivo Latex de alguna manera.
Ver también:esta pregunta en reddit.
Respuesta1
Escriba los resultados en archivos de texto separados y utilícelos.\input
La solución más sencilla y directa es escribir cada valor en un archivo de texto durante el cálculo. El cálculo podría, por ejemplo, al final recopilar todos los valores necesarios en el documento LaTeX y escribir el tiempo en archivos de texto:
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")
Al llamar a la función se puede configurar el número de dígitos mostrados:
write_include_value("average_temperature", f"{average_temperature.values:.1f}")
Poner el siguiente fragmento en el preámbulo del documento LaTeX permite incluir fácilmente el valor en el texto:
\newcommand*{\includevalue}[1]{\input{../../data/output/include-values/#1}\unskip}
Luego se puede usar el valor usando el nuevo comando \includevalue
, por ejemplo:
\includevalue{average_temperature}
Escollos y desventajas
- El paquete siunitx no funciona con el comando de alto nivel
\input
, por lo tanto\includevalue
no se puede utilizar dentro de un\qty
comando. Por eso agregué un comando adicional para cantidades con unidades:
% 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}}%
}
- Algunas revistas limitan la cantidad de archivos durante el proceso de envío. Usar este método de incluir valores a través de archivos separados significa que puede terminar fácilmente con 100 archivos y que el portal de envío no le permitirá enviar su artículo. Utilicé este script de Python como solución alternativa para reemplazar todas las inclusiones con los valores reales. No es bueno porque agrega un paso adicional a la compilación del documento LaTeX, lo que hace que las cosas sean más propensas a errores, pero funciona.
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"))
- La ruta a la carpeta con los archivos de valores incluidos debe especificarse dos veces: una en el código Python y otra vez en el encabezado LaTeX.
Respuesta2
Utilice Knitr si está utilizando R
Tejedorle permite ejecutar fragmentos de código R en un documento LaTeX. Si el cálculo almacena los resultados en archivos (también puede ser un archivo binario como NetCDF o un archivo CSV), puede usar el código Knitr y R para cargar el valor que necesita e incluirlo en el archivo LaTeX:
<<results="asis",echo=FALSE>>=
cat(read.csv("a_csv_file.csv", sep=";")[1,2])
@
O una mesa:
<<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))
@
(Ejemplo tomado deKarl Broman)
En teoría, Knitr también es compatible con Python, pero resulta extraño usar R para ejecutar fragmentos de Python dentro de LaTeX. Por lo tanto, desaconsejaría Knitr si no está utilizando R.
Ejemplo mínimo
\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}
El código anterior se almacena como knitr_test.Rnw
y se crea un archivo tex usando:
R -e 'library(knitr);knit("knitr_test.Rnw")'
El archivo tex se ve así:
\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}
Este es el resultado renderizado:
Al dorso
El látex en líneaAl dorsoapoyaTejer fuera de la caja. Simplemente cambie el nombre de su archivo LaTeX a *.Rtex
.
Desafortunadamente, la verificación de sintaxis no parece admitir la sintaxis de Knitr:
Desventajas
- Es necesario un paso adicional durante la compilación del archivo LaTeX.
- El código para incluir un único valor es bastante largo.
Más recursos
Aquí tienes un bonito y breve tutorial.sobre cómo usar Knitr con LaTeX.
Hay una buena descripción general de Knitr disponible en eldocumentación al dorso.
EstePreguntas y respuestasanaliza cómo evitar el uso cat()
, que es necesario para ocultar el prefijo de salida [1]
.
Respuesta3
Estás buscando el concepto deprogramación alfabetizada, que en realidad no se limita a LaTeX con R a través de knitr, sino que probablemente ha sido la prueba de concepto más exitosa de los últimos años.
Pero para la programación alfabetizada en general, no limitada a R, ni Python, y no limitada a LateX, sugieroLibro en cuarto. Quarto puede crear contenido dinámico con Python, R, Julia y Observable para producir un PDF a través de LaTeX o ConTeXt (y varios otros formatos, pero eso está fuera de tema aquí...).
Quarto usará de forma predeterminada el motor Knitr si hay algún fragmento de R, pero Jupyter es el código ejecutable en otro lenguaje (python, julia, bash, etc.). Veraquí para el detalle de la selección del motor.
Finalmente, Knitr es compatible con Python ymuchos otros idiomas, no sólo "en teoría". No es pecado mortal ejecutar Python a través de R, mientras funciona. Además, esto podría tener algunas ventajas, ya que permite ejecutar snippets de ambos idiomas en un mismo documento e incluso pasar variables de un idioma a otro, por ejemplo:
---
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)
```
Puede agregar engine: jupyter
al encabezado para evitar el uso de knitr (el código R solo se mostrará pero no se ejecutará) o simplemente eliminar la última parte (desde \newpage
hasta el final) para cambiar automáticamente a Jupyter que ejecutará python3 sin R ni knitr.
Pero si este flujo de trabajo le molesta por algún motivo, también existenPaquetes LaTeX para ejecutar Pythondirectamente.
Respuesta4
Generar nuevos comandos Latex para resultados en un Latex
Puedes crear tus propios comandos Latex: un comando para cada resultado. La canalización de cálculo puede ejecutar un script que crea un archivo de encabezado Latex que incluye las definiciones de los comandos y los resultados. Este archivo luego se puede incluir en el documento de Latex.
Esto es algo similar a lasolución a través de archivos de texto y\input
. Gracias a @Teepeemm por sugerir esta variante de la solución en los comentarios.
Fragmento de código 1
Agregue algo como esto a su canal de cálculo y agregue todos los valores al diccionario que necesita en su documento de 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)
Fragmento de código 2
Esta función escribe el encabezado de 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")
Cómo utilizar en el documento Latex
Después de ejecutar el fragmento de código 1 anterior, result_values.tex
se crea un archivo:
\newcommand{\meaningoflife}{42}
\newcommand{\gravity}{\qty{9.81}{m/s^2}}
...luego se pueden usar los nuevos comandos de Latex para agregar los valores al texto:
\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}
El resultado renderizado se ve así: