Запишите результаты в отдельные текстовые файлы и используйте\input

Запишите результаты в отдельные текстовые файлы и используйте\input

Мы часто оказываемся в следующей ситуации: длительный (анализ данных)вычислениевыплевывает кучузначения результата. Представьте себе Python или скрипт или скрипт R, выполняющий некоторые ресурсоемкие вычисления. Затем мы пишем статью или отчет, в котором хотим описать результаты и перечислить значения результатов в тексте или таблицах. Копирование и вставка значений подвержены ошибкам и требуют много работы, поэтому было бы хорошоавтоматически включать значения.

Пример:Все числовые значения здесь являются результатами скриптов и должны быть включены в статью тем или иным образом:

Пример, взятый из научной статьи

Каков наилучший способ этого добиться?

Примечание:Вычисление может длиться много часов — возможно даже на отдельном вычислительном кластере. Поэтому вычисление результатов должно быть отдельным шагом от включения результатов в документ LaTeX и его компиляции.

Требование к бонусу:было бы неплохо видеть значения, отображаемые в редакторе Latex (например, Overleaf), а не только команду include. Это может быть очень полезно при использовании редактора Wysiwyg, например, на Overleaf, но я сомневаюсь, что это можно сделать без копирования и вставки значений или предварительной обработки файла 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}

Подводные камни и недостатки

  1. Пакет siunitx не работает с командой highlevel \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}}%
}
  1. Некоторые журналы ограничивают количество файлов в процессе отправки. Использование этого метода включения значений через отдельные файлы означает, что вы можете легко получить 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"))

  1. Путь к папке с файлами включаемых значений необходимо указать дважды — один раз в коде Python и затем еще раз в заголовке LaTeX.

решение2

Используйте Knitr, если вы используете R

Книтрпозволяет вам выполнять фрагменты кода R в документе LaTeX. Если вычисления сохраняют результаты в файлах (это может быть также двоичный файл, такой как NetCDF или CSV-файл), вы можете использовать Knitr и код 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))
@

(Пример взят изКарл Броман)

Теоретически Knitr также поддерживает Python, но кажется странным использовать R для выполнения фрагментов Python внутри LaTeX. Поэтому я бы не советовал использовать Knitr, если вы не используете R.

Минимальный пример

\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.Rnwtex-файл и создается с помощью:

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

Текстовый файл выглядит так:

\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На оборотеподдерживаетKnitr из коробки. Просто переименуйте ваш файл LaTeX в *.Rtex.

К сожалению, проверка синтаксиса, похоже, не поддерживает синтаксис Knitr: Скриншот Overleaf с фрагментами кода Knitr

Недостатки

  • Необходим дополнительный шаг при компиляции файла LaTeX.
  • Код для включения одного значения довольно длинный.

Больше ресурсов

Вот хороший и короткий уроко том, как использовать Knitr с LaTeX.

Хороший обзор Knitr доступен вдокументация на обороте.

ЭтотВопросы и ответыобсуждается, как избежать использования cat(), что необходимо для скрытия выходного префикса [1].

решение3

Вы ищете концепциюграмотное программирование, который на самом деле не ограничивается LaTeX с R через knitr, но, вероятно, стал самым успешным доказательством концепции за последние годы.

Но для общего грамотного программирования, не ограничиваясь R, Python и LateX, я предлагаюКвартоQuarto может создавать динамический контент с помощью Python, R, Julia и Observable для создания PDF-файлов через LaTeX или ConTeXt (и несколько других форматов, но это не по теме...).

Quarto будет использовать по умолчанию движок Knitr, если есть фрагмент R, но Jupyter — исполняемый код на другом языке (python, julia, bash и т. д.). Смотритездесь для получения подробной информации о выборе двигателя.

Наконец, Knitr поддерживает Python имного других языков, не только "теоретически". Не является смертным грехом запускать Python через R, пока он работает. Более того, это может иметь некоторые преимущества, поскольку позволяет запускать фрагменты обоих языков в одном документе и даже передавать переменные из одного языка в другой, например:

мве

---
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в заголовок , чтобы избежать использования knitr (код R будет только отображаться, но не выполняться) или просто удалить последнюю часть (от \newpageи до конца), чтобы автоматически переключиться на Jupyter, который будет запускать python3 без R и knitr.

Но если этот рабочий процесс по какой-то причине вас беспокоит, есть такжеПакеты LaTeX для запуска Pythonнапрямую.

решение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}

Полученный результат выглядит так:

Скриншот отрендеренного примера Latex

Связанный контент