Escreva os resultados em arquivos de texto separados e use\input

Escreva os resultados em arquivos de texto separados e use\input

Muitas vezes estamos na seguinte situação: uma longa (análise de dados)computaçãocospe um monte devalores de resultado. Imagine um Python ou script ou um script R fazendo alguns cálculos que consomem muitos recursos. Em seguida, escrevemos um artigo ou relatório, onde queremos descrever os resultados e listar os valores dos resultados no texto ou em tabelas. Copiar e colar os valores é propenso a erros e dá muito trabalho, então seria bomincluir automaticamente os valores.

Exemplo:todos os valores numéricos aqui são resultados de scripts e precisam ser incluídos no artigo de uma forma ou de outra:

Exemplo retirado de um artigo científico

Qual a melhor maneira de alcançar isto?

Observação:A computação pode ser executada por muitas horas - possível até mesmo em um cluster de computação separado. Calcular os resultados deve, portanto, ser uma etapa separada da inclusão dos resultados no documento LaTeX e de sua compilação.

Requisito de bônus:pode ser bom ver os valores exibidos no editor Latex (por exemplo, Overleaf) e não apenas o comando include. Isso pode ser muito útil ao usar um editor Wysiwyg como o do Overleaf, mas duvido que seja possível fazer isso sem copiar e colar os valores ou pré-processar o arquivo Latex de alguma forma.

Veja também:essa pergunta no reddit.

Responder1

Escreva os resultados em arquivos de texto separados e use\input

A solução mais direta e simples é escrever cada valor em um arquivo de texto durante o cálculo. A computação poderia, por exemplo, no final coletar todos os valores necessários no documento LaTeX e gravar o tempo em arquivos 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")

Ao chamar a função, o número de dígitos exibidos pode ser configurado:

write_include_value("average_temperature", f"{average_temperature.values:.1f}")

Colocar o seguinte trecho no preâmbulo do documento LaTeX permite incluir facilmente o valor no texto:

\newcommand*{\includevalue}[1]{\input{../../data/output/include-values/#1}\unskip}

Pode-se então usar o valor usando o novo comando \includevalue, por exemplo:

\includevalue{average_temperature}

Armadilhas e desvantagens

  1. O pacote siunitx não funciona com o comando de alto nível \input, portanto \includevaluenão pode ser utilizado dentro de um \qtycomando. Portanto adicionei um comando adicional para quantidades com 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}}%
}
  1. Algumas revistas limitam o número de arquivos durante o processo de submissão. Usar este método de inclusão de valores por meio de arquivos separados significa que você pode facilmente acabar com 100 arquivos e que o portal de submissão não permitirá que você envie seu artigo. Usei este script Python como solução alternativa para substituir todas as inclusões pelos valores reais. Não é legal porque adiciona uma etapa extra à compilação do documento LaTeX, o que torna as coisas mais propensas a erros, mas 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"))

  1. O caminho para a pasta com arquivos de valor incluído deve ser especificado duas vezes - uma vez no código Python e novamente no cabeçalho LaTeX.

Responder2

Use Knitr se você estiver usando R

Tricôpermite executar trechos de código R em um documento LaTeX. Se o cálculo armazenar resultados em arquivos (também pode ser um arquivo binário como NetCDF ou um arquivo CSV), você pode usar o código Knitr e R para carregar o valor necessário e incluí-lo no arquivo LaTeX:

<<results="asis",echo=FALSE>>=
cat(read.csv("a_csv_file.csv", sep=";")[1,2])
@

Ou uma 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))
@

(Exemplo retirado deCarlos Broman)

Em teoria, o Knitr também suporta Python, mas é estranho usar R para executar trechos de Python dentro do LaTeX. Então eu desaconselho o Knitr, se você não estiver usando R.

Exemplo 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}

O código acima é armazenado como knitr_test.Rnwe um arquivo tex é criado usando:

R -e 'library(knitr);knit("knitr_test.Rnw")'

O arquivo tex fica assim:

\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 é o resultado renderizado:

Captura de tela do LaTeX renderizado

No verso

O LaTeX on-lineNo versoapoiaTricô fora da caixa. Simplesmente renomeie seu arquivo LaTeX para *.Rtex.

Infelizmente, a verificação de sintaxe não parece suportar a sintaxe do Knitr: Captura de tela do Overleaf com trechos de código Knitr

Desvantagens

  • É necessária uma etapa adicional durante a compilação do arquivo LaTeX.
  • O código para incluir um único valor é bastante longo.

Mais recursos

Aqui está um tutorial curto e agradávelsobre como usar o Knitr com LaTeX.

Há uma boa visão geral do Knitr disponível nodocumentação do verso.

EssePerguntas e respostasdiscute como evitar o uso cat(), que é necessário para ocultar o prefixo de saída [1].

Responder3

Você está pesquisando o conceito deprogramação alfabetizada, que na verdade não se limita ao LaTeX com R através do knitr, mas provavelmente foi a prova de conceito de maior sucesso nos últimos anos.

Mas para programação alfabetizada em geral, não limitada a R, nem Python, e não limitada a LateX, sugiroQuarto. Quarto pode criar conteúdo dinâmico com Python, R, Julia e Observable para produzir um PDF via LaTeX ou ConTeXt (e vários outros formatos, mas isso está fora do assunto aqui...).

Quarto usará por padrão o mecanismo Knitr se houver algum pedaço R, mas Jupyter for o código executável em outra linguagem (python, julia, bash, etc.). Veraqui para detalhes da seleção do motor.

Finalmente, Knitr suporta Python emuitas outras línguas, não apenas "em teoria". Não é pecado mortal executar Python via R enquanto ele funciona. Além disso, isso pode trazer algumas vantagens, já que permite executar trechos de ambas as linguagens no mesmo documento e até mesmo passar variáveis ​​de uma linguagem para outra, por exemplo:

eu

---
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)
```

Você pode adicionar engine: jupyterao cabeçalho para evitar o uso de knitr (o código R será apenas mostrado, mas não executado) ou simplesmente remover a última parte (do \newpagefinal) para mudar automaticamente para Jupyter que executará python3 sem R nem knitr.

Mas se esse fluxo de trabalho incomoda você por algum motivo, também existemPacotes LaTeX para executar Pythondiretamente.

Responder4

Gere novos comandos Latex para resultados em um Latex

Você pode criar seus próprios comandos Latex: um comando para cada resultado. O pipeline de computação pode executar um script que cria um arquivo de cabeçalho Latex incluindo as definições de comando e os resultados. Este arquivo pode então ser incluído no documento Latex.

Isto é um pouco semelhante aosolução via arquivos de texto e\input. Obrigado a @Teepeemm por sugerir esta variante da solução nos comentários.

Trecho de código 1

Adicione algo assim ao seu pipeline de cálculo e adicione todos os valores ao dicionário que você precisa em seu documento 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)

Trecho de código 2

Esta função escreve o cabeçalho 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")

Como usar no documento Latex

Depois de executar o trecho de código 1 acima, um arquivo result_values.texé criado:

\newcommand{\meaningoflife}{42}
\newcommand{\gravity}{\qty{9.81}{m/s^2}}

...pode-se então usar os novos comandos Latex para adicionar os valores ao 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}

O resultado renderizado fica assim:

Captura de tela do exemplo de látex renderizado

informação relacionada