RMarkdown: верстаем красивое

Антон Ангельгардт

31.08.2022

RMarkdown — это пакет, который позволяет соединять текст, исполняемый код и результаты исполнения кода в единый документ. Это очень полезно для представления данных. На выходе мы можем получить файлы .html, .pdf — в том числе презентации форматов .html и .pdf1.docx, .pptx. Более того, можно связать несколько HTML-документов и получить сайт или книгу. Ну, гениально же!

Мы сосредоточимся на HTML-документах сегодня, потому что это проще всего и, как показывает практика, полезнее всего.

Установка

Если вдруг ещё не, надо поставить R (Win, Mac, Linux) и RStudio.

Устанавливается RMarkdown как обычный пакет:

install.packages("rmarkdown")

Есть ещё пакет, который не обязательно устанавливать сразу, но он нам пригодится, чтобы делать красивое:

install.packages("prettydoc")

Структура документа

  • YAML-шапка
  • самый обычный текст (как в блокноте) с форматированием от Markdown, расширенным с помощью Pandoc
  • блоки кода (в целом, на любом языке программирования)
  • формулы в \(\LaTeX\)’e

Чё внутри происходит?

Кратко взглянем на то, как из .rmd получается .html.

  1. Мы пишем руками .rmd.шку (RMarkdown).
  2. Отправляем её пакету knitr, который выполняет чанки кода и делаем .mdшку (Markdown).
  3. Дальше .mdшку берёт Pandoc, который есть конвертер, и делает окончательный файл, который мы пожелали — например, htmlку или .pdf.ку, призывая \(\LaTeX\).

Источник

Первый .rmd-файл

Как создать .rmdшку? Надо проследовать по пути File > New File > RMarkdown..., оставить все настройки по умолчанию и жмакнуть OK.

Возникнет дефолтный .rmd-файл.

---
title: "Untitled"
output: html_document
---

```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE)
```

## R Markdown

This is an R Markdown document. Markdown is a simple formatting syntax for authoring HTML, PDF, and MS Word documents. For more details on using R Markdown see <http://rmarkdown.rstudio.com>.

When you click the **Knit** button a document will be generated that includes both content as well as the output of any embedded R code chunks within the document. You can embed an R code chunk like this:

```{r cars}
summary(cars)
```

## Including Plots

You can also embed plots, for example:

```{r pressure, echo=FALSE}
plot(pressure)
```

Note that the `echo = FALSE` parameter was added to the code chunk to prevent printing of the R code that generated the plot.

Его можно скомпилировать либо с помощью функции rmarkdown::render("file.Rmd"), либо жмакнув кнопку Knit вверху текстового редактора.

Если по дороге ничего не сломалось, то должно получиться что-то такое.


Создайте и скомпилируйте дефолтный .rmd-файл.


Markdown & Pandoc

  • Markdown — это универсальный язык разметки, использующийся очень много где.
  • Pandoc — конвертер файлов между различными форматами. А ещё он дает немного плюшек для форматирования (индексы, нумерованные примеры, сноски).

Заголовки

Обозначаются хэштегами, после которых надо обязательно поставить пробел, иначе получится ссылка.

# Заголовок 1

## Заголовок 2

### Заголовок 3

#### Заголовок 4

##### Заголовок 5

###### Заголовок 6

На заголовках можно оставлять теги — они не отображаются — чтобы организовывать навигацию по документу:

# Заголовок 1 {#tag1}

## Заголовок 2 {#header2}

### Заголовок 3 {#wow_third_level_header}

Форматирование

  • Курсив / наклон*курсив* или _курсив_
  • Полужирный**полужирный** или __полужирный__
  • Зачеркнутый~~зачеркнутый~~

Индексы

  • Нижние индексы — CH3CH2OH — в тильдах — CH~2~CH~3~OH
  • Верхние индексы — К+, Cr2O72- — в шапочках — К^+^, Cr~2~O~7~^2-^

Списки

Есть несколько вариантов и все рабочие:

Ненумерованные списки

* овощи
    * томаты
    * все остальные
* фрукты
    * обыкновенные
        * яблоко
        * груша
    * тропические
        * папайя
        * маракуйя
* ягоды
    * удобные для сбора
    * крыжовник
  • овощи
    • томаты
    • все остальные
  • фрукты
    • обыкновенные
      • яблоко
      • груша
    • тропические
      • папайя
      • маракуйя
  • ягоды
    • удобные для сбора
    • крыжовник

Ещё можно плюсами и минусами пользоваться, типа вот так

- овощи
    + томаты
    + все остальные
- фрукты
    + обыкновенные
        - яблоко
        - груша
    + тропические
        - папайя
        - маракуйя
- ягоды
    - удобные для сбора
    - крыжовник
  • овощи
    • томаты
    • все остальные
  • фрукты
    • обыкновенные
      • яблоко
      • груша
    • тропические
      • папайя
      • маракуйя
  • ягоды
    • удобные для сбора
    • крыжовник

Нумерованные списки

1. рыба
   1. карп
   3. треска
   2. камбала
2. мясо
    1. всякое
    2. разное
3. ни рыба ни мясо
   1. птица
      1. кура
      4. перепёлка
   2. инопланетное непонятно что
  1. рыба
    1. карп
    2. треска
    3. камбала
  2. мясо
    1. всякое
    2. разное
  3. ни рыба ни мясо
    1. птица
      1. кура
      2. перепёлка
    2. инопланетное непонятно что

Смотрите, даже если мы накосячили в нумерации, при компиляции всё поправилось, и нумерация в итоговом доке верная.

Нумерованные примеры

В списки можно собирать некоторые примеры:

(@) Парменид сказал, что «бытие есть --- небытия нет». Трудно поспорить.
(@socrates) Сократ заявил, что он «знает, что ничего не знает, но другие не знают даже этого». Ну, ок.
(@plato) Платон задвигал про мир идей.
(@aristotle) Аристотель нам основал все науки.
  1. Парменид сказал, что «бытие есть — небытия нет». Трудно поспорить.
  2. Сократ заявил, что он «знает, что ничего не знает, но другие не знают даже этого». Ну, ок.
  3. Платон задвигал про мир идей.
  4. Аристотель нам основал все науки.

Во-вервых, они будут автоматически нумероваться, а во-вторых, если к ним приписать лейблы (здесь: socrates, plato, arislotle). Можно будет ссылаться на примеры:

Считается, что (@plato) записывал за (@socrates).

Считается, что (3) записывал за (2).

Картинки

Очень похожи по синтаксису на ссылки, только перед квадратными скобками надо поставить !,а внутри них пишется либо ничего, либо альтернативный текст/название картинки. В круглых скобках пишется адрес картинки.

![](cat.jpeg)
![Удивлённый кот Федя](https://cdnn21.img.ria.ru/images/07e5/06/18/1738448523_0:54:864:540_640x0_80_0_0_1909d455bf858de749abb1ae8dc540b7.jpg.webp)

Удивлённый кот Федя

Да, прямо веб-ссылку можно впихнуть туда.

Метки здесь так же работают. ![][cat_shocked]

[cat_shocked]: cat_shocked.png

Метки здесь также работают.

Лично я не большой фанат вставлять картинки средствами Markdown, поэтому использую HTML. Там больше возможностей кастомизации отображения.

Вот так делаю
<center>
<img src="path_to_pic">
</center>

Цитаты

Цитаты оформляются с помощью знака >:

> Это цитата, которая содержит глубокую философскую мысль.
> Тут мысль раскрывается --- это строка попадает в тот же фрагмент.

> А это уже _следующая_ цитата, в которой применяется __форматирование__.
>
> <p align="right">Автор цитаты</p>

Это цитата, которая содержит глубокую философскую мысль. Тут мысль раскрывается — это строка попадает в тот же фрагмент.

А это уже следующая цитата, в которой применяется форматирование.

Автор цитаты2

Разрыв страницы

Делается тремя звёздочками: ***. Выглядит так:


Таблицы

Делать руками таблицы — достаточно запарно. В общем-то, не только в Markdown, но и в HTML или \(\LaTeX\) тоже. К тому же, не то чтобы нам очень часто надо делать таблицы руками. Поэтому не будем тратить на это много времени и внимания, и просто сохраним ссылку на генератор таблиц.

Сноски

Делаются так:

Это сноска[^1].
Но лучше не номером, а как-то так[^another_footnote].

[^1]: Да, это сноска.
[^another_footnote]: Да, это тоже сноска, только не по номеру, а через лейбл.

Это сноска3. Но лучше не номером, а как-то так4.

Разницы, где оставлять содержание сноски, особой нет — можно сразу после абзаца, в котором сноска даётся, можно в конце документа.

Я предпочитаю первый вариант, чтобы можно было следовать за содержанием документа и не прыгать по нему, если нужны исправления.


Представим, что вам пора писать статью. Создайте шаблон для неё в RMarkdown.

Шаблон должен содержать:

  • заголовки основных разделов
  • пара строк текста в каждом разделе, о чем будет этот раздел
  • суперважная сноска в каком-нибудь из разделов
  • ссылка на какой-то внешний ресурс (например, откуда вы взяли стимульный материал)
  • цитата великого ученого, на работы которого вы опираетесь

HTML

Ежели вы монстр и постигли HTML, то можно прям на нём делать вставки в RMarkdown. Ну, скажем, список можно сделать и так:

<ul>
    <li>пункт номер раз</li>
    <li>пункт номер два</li>
    <li>пункт номер три</li>
</ul>
  • пункт номер раз
  • пункт номер два
  • пункт номер три

Кстати, HTML и Markdown можно комбинировать, и это будет работать:

Допустим,
<dl>
   <dt>__термин__</dt>
   <dd> и его определение, в котором нужно сделать _акцент_, либо <em>акцент</em>.</dd>
</dl>
Допустим,
термин
и его определение, в котором нужно сделать акцент, либо акцент.

\(\LaTeX\)

С помощью \(\LaTeX\)’а можно писать красивые формулы. Хорошо, конечно, если вы шарите за \(\LaTeX\), но если нет, можно посмотреть 100500 примеров тут.

Сверхкраткое введение в математику в \(\LaTeX\)’е

  • Есть формулы. Они бывают внутритекстовые, которые — шок! — идут внутри текста, и выключные, которые вынесены в отдельную строку.
  • Внтуритекстовые формулы оформляются с помощью одинарных знаков доллара $ formula $ примерно так:
Если верить тому, что $2 + 2 = 4$, то получается, что $22 + 22 = 44$.

Если верить тому, что \(2 + 2 = 4\), то получается, что \(22 + 22 = 44\).

  • Выключные формулы оформляются с помощью двойных знаков доллара $$ formula $$. Они всегда выравнены по центру.
Кажется, что если сложить несколько чисел и поделить на их количество, получится нечто, именуемое _средним арифметическим_:

$$
\bar x = \frac{1}{n} \sum_{i=1}^n x_i
$$
  
Будем думать, что это действительно так.

Кажется, что если сложить несколько чисел и поделить на их количество, получится нечто, именуемое средним арифметическим:

\[ \bar x = \frac{1}{n} \sum_{i=1}^n x_i \]

Будем думать, что это действительно так.

  • Есть куча разных операторов-функций типа \bar, \sum, \frac, \times и др. Они позволяют вводить всякие разные математические символы. Какие они есть и как их употреблять, можно опять же смотреть тут.

Давайте напишем какую-нибудь формулу в свою статью, что сделать вид, что мы жесть какие умные и что-то понимаем в том, что исследуем. Например, что-то умное про логистическую регрессию:

\[ p_i = \frac{e^{\beta_0 + \sum_{k=1}^p \beta_kx_{ik}}}{1 + e^{\beta_0 + \sum_{k=1}^p \beta_kx_{ik}}} \]

или просто что-то на статистическом про диперсию:

\[ \mathbb{D}(X) = \mathbb{E}(X^2) - \mathbb{E}^2(X) \]


Код

Код заключается в бэктики: `sum(1, 2, 3)`. Это если мы хотим код прямо в строке. Если хотим код отдельным чанком, то по три бэктика в начале и конце:

```
x <- c(1,3,5,7)
mean(x)
```

Правда такие чанки кода не исполняются. Нам такого не надо. Чтобы исполнить код при компиляции файла, надо указать, на каком языке мы написали заклинания.

Языки программирования

Это указывается после первых бэктиков (допустим, мы почему-то пишем на R):

```{r}
x <- c(1,3,5,7)
mean(x)
```
x <- c(1,3,5,7)
mean(x)
## [1] 4

Можно сделать и питон5:

```{python}
spisok = ["where", "is", "my", "beer"]
for word in spisok:
    print(word)
```
spisok = ["where", "is", "my", "beer"]
for word in spisok:
    print(word)
## where
## is
## my
## beer

Можно вытворить и нечто подобное:

echo "хало ворлд"
## хало ворлд

Если вы совсем странненький, то можно сделать и что-то такое:

#include <Rcpp.h>

// [[Rcpp::export]]
int fibonacci(const int x) {
    if (x == 0 || x == 1) return(x);
    return (fibonacci(x - 1)) + fibonacci(x - 2);
}
fibonacci(10L)
## [1] 55

Настройка чанков

У чанков достаточно много настроек. Самые важные — echo, eval и include.

Отображается и код, и аутпут:

```{r}
# Pharmacokinetics of Indomethacin
head(Indometh)
```

Отображается, но *не* исполняется:

```{r, eval=FALSE}
# это я просто покажу, как пакет c гитхаба подгрузить
# полезный пакет, кстати, там есть неплохие шаблоны
devtools::install_github("rstudio/radix")
```

*Не* отображается код, но выводится аутпут:

```{r, echo=FALSE}
print("о боже, как много настроек, я уже забыл, какая первая была")
```

*Не* отображается *ни* код, *ни* аутпут, но чанк исполняется[^include]:

```{r, include=FALSE}
vec <- 1:24
```

[^include]: Параметр `include` в основном используется для задания параметров верстки.

Это значит, что хоть мы и не видим, что вектор создан, мы можем к нему обратиться:

```{r}
vec
```

Отображается и код, и аутпут:

# Pharmacokinetics of Indomethacin
head(Indometh)
##   Subject time conc
## 1       1 0.25 1.50
## 2       1 0.50 0.94
## 3       1 0.75 0.78
## 4       1 1.00 0.48
## 5       1 1.25 0.37
## 6       1 2.00 0.19

Отображается, но не исполняется:

# это я просто покажу, как пакет c гитхаба подгрузить
# полезный пакет, кстати, там есть неплохие шаблоны
devtools::install_github("rstudio/radix")

Не отображается код, но выводится аутпут:

## [1] "о боже, как много настроек, я уже забыл, какая первая была"

Не отображается ни код, ни аутпут, но чанк исполняется6:

Это значит, что хоть мы и не видим, что вектор создан, мы можем к нему обратиться:

vec
##  [1]  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

Если и другие аргументы, их много. Кратко перечислю самые полезные:

  • error — отображать ли ошибки исполнения:
    • если FALSE, то верстка ломается на чанке, который не может выполниться
    • если TRUE, то верстка не ломается, а ошибка отображается в итоговом файле
  • warning — отображать ли предупреждения
  • message — отображать ли сообщения (например, при подключении пакетов)
  • comment — по дефолту результат работы кода предваряется знаком ##
    • чтобы его не было, надо прописать NA
  • cache — кэшировать ли результат работы фрагмента кода
    • Полезная фича, когда вы работаете с какими-либо сложными или большими операциями, занимающими много времени. Если результата закэшировн, он не будет пересчитываться при новой компиляции — значит, будет тратиться меньше времени.

Именование чанков

Каждому чанку можно дать собственное имя (без пробелов и точек внутри имени). Оно указывается после языка:

```{r my_super_fancy_graph}
library(tidyverse)
theme_set(theme_bw())
diamonds %>% 
  ggplot(aes(price)) +
  geom_histogram(data = diamonds %>% select(-color), fill="lightgray") +
  geom_histogram(aes(fill = color)) +
  facet_wrap(~ color)
```
library(tidyverse)
## ── Attaching packages ─────────────────────────────────────── tidyverse 1.3.0 ──
## ✓ ggplot2 3.3.3     ✓ purrr   0.3.4
## ✓ tibble  3.0.4     ✓ dplyr   1.0.2
## ✓ tidyr   1.1.2     ✓ stringr 1.4.0
## ✓ readr   1.4.0     ✓ forcats 0.5.0
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## x dplyr::filter() masks stats::filter()
## x dplyr::lag()    masks stats::lag()
theme_set(theme_bw())
diamonds %>% 
  ggplot(aes(price)) +
  geom_histogram(data = diamonds %>% select(-color), fill="lightgray") +
  geom_histogram(aes(fill = color)) +
  facet_wrap(~ color)
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

В целом, этого можно и не делать, однако есть несколько причин этого делать:

  • проще читать код
  • проще ориентироваться в том, что вы написали год назад
  • в случае ошибок при компиляции будет отображаться имя чанка, а не номер
  • если чанк закэширован, при добавлении нового перед ним не надо все опять пересчитывать
  • можно ссылаться в blogdown
  • при верстке книг в bookdown вы избежите косяков по типу того, что на картинке

Возьмите какой-либо встроенный датасет (library(help = "datasets")) и напишите код, который:

  • выведет описательные статистики по какой-то количественной переменной
  • визуализирует её распределение

YAML-шапка

Это то, что находится вверху документа и содержит какие-то метаданные о вашем файле. Например, вот

---
title: "RMarkdown: верстаем красивое"
author: "Антон Ангельгардт"
date: "31.08.202"
output:
  prettydoc::html_pretty:
    theme: architect
    highlight: github
    toc: yes
    toc_depth: 3
    css: "style.css"
---

Тип аутпута

Пожалуй, самое важное. Есть несколько вариантов:

  • output: html_document — это настроено по умолчанию
  • output: word_document
  • output: pdf_document (придется подружить с \(\LaTeX\)ом на вашем компе)
  • output: ioslides_presentation
  • output: slidy_presentation
  • output: slidy_presentation
  • output: beamer_presentation и т.д., и т. п., и пр., и др.

Библиография

Здесь мы упомянем BibTeX, который вообще-то хорошо дружит с \(\LaTeX\), но сюда прикрепить тоже можно. Это не единственный способ, но, наверное, самый универсальный.

  1. Создаем файл .bib со всей библиографией. GoogleScholar генерирует ссылки в формате BibTeX автоматически.
@book{xie2016bookdown,
  title={Bookdown: Authoring books and technical documents with R markdown},
  author={Xie, Yihui},
  year={2016},
  publisher={Chapman and Hall/CRC}
}

@book{xie2017blogdown,
  title={Blogdown: creating websites with R markdown},
  author={Xie, Yihui and Thomas, Amber and Hill, Alison Presmanes},
  year={2017},
  publisher={Chapman and Hall/CRC}
}
  1. Прописываем в шапке, какой файл отвечает за библиографию:
bibliography: references.bib
  1. Используем ссылки в тексте, как вот тут
* `bookdown` --- [расширение RMarkdown](https://bookdown.org/yihui/bookdown/) для написания книг, диссера, создания перезентаций и т.д. (@xie2016bookdown)
* `blogdown` --- сайты [можно пилить](https://bookdown.org/yihui/blogdown/) (@xie2017blogdown)
  1. Список литературы сам формируется в конце.

Оглавление

Есть несколько атрибутов, которые позволяют задать оформление оглавления:

  • toc — вставлять ли оглавление финальный док
  • toc_depth — сколько уровней иерархии отображать в оглавлении
  • toc_float — должно ли оглавление все время следовать за текстом
  • collapsed — должно ли оглавление быть все время полностью раскрыто
  • number_sections — автоматическая нумерация секций
  • code_folding (hide) — делать ли кнопочку, показывающую/скрывающую весь код

Отображение датафреймов

Мы уже выше видели, что будет, если попытаться вывести датафрейм — что-то такое:

head(diamonds)
## # A tibble: 6 x 10
##   carat cut       color clarity depth table price     x     y     z
##   <dbl> <ord>     <ord> <ord>   <dbl> <dbl> <int> <dbl> <dbl> <dbl>
## 1 0.23  Ideal     E     SI2      61.5    55   326  3.95  3.98  2.43
## 2 0.21  Premium   E     SI1      59.8    61   326  3.89  3.84  2.31
## 3 0.23  Good      E     VS1      56.9    65   327  4.05  4.07  2.31
## 4 0.290 Premium   I     VS2      62.4    58   334  4.2   4.23  2.63
## 5 0.31  Good      J     SI2      63.3    58   335  4.34  4.35  2.75
## 6 0.24  Very Good J     VVS2     62.8    57   336  3.94  3.96  2.48

Некрасиво.

Надо, чтобы было красиво.

Для этого есть удобненькая функция kable() в пакете knitr. Во-первых, она сделаем красивое сама, во-вторых, там можно переименовать колонки — а это важно, ибо переменные в датасете обычно не то чтобы очень понятно и понимабельно называются.

head(diamonds) %>% 
  kable(col.names=str_to_title(colnames(.)))
Carat Cut Color Clarity Depth Table Price X Y Z
0.23 Ideal E SI2 61.5 55 326 3.95 3.98 2.43
0.21 Premium E SI1 59.8 61 326 3.89 3.84 2.31
0.23 Good E VS1 56.9 65 327 4.05 4.07 2.31
0.29 Premium I VS2 62.4 58 334 4.20 4.23 2.63
0.31 Good J SI2 63.3 58 335 4.34 4.35 2.75
0.24 Very Good J VVS2 62.8 57 336 3.94 3.96 2.48

Давайте

  • добавим оглавнение в нашу статью
  • покажем, как выглядели данные (то есть, отобразим часть датафрейма, по которому считали описательные статистики, красиво.)

Дизигн

CSS

Если вы магистр всея Cascading Style Sheets, то можете полностью накастомизировать все, что хотите. Только подключите потом к самому файлу:

css: "style.css"

Макеты с темами

Мы в самом начале установили пакет prettydoc, который содержит в себе весьма pretty темы для RMarkdown-документов. Создать prettydoc-файл можно так:

File > New File > RMarkdown... > From Template > Lightweight and Pretty Document (HTML)

YAML-шапка генерируется автоматически, что хорошо. Если вам не нравится, какая тема применилась по умолчанию, можно выбрать другую.

theme: architect

Примените какую-нибудь тему из пакета prettydoc, чтобы статья стала неистово pretty.


Куда заливать HTML?

В качестве аутпута мы получаем файл .html, а значит, чтобы мир его увидел, он должен быть где-то в интернетах. Вариантов несколько:

  • вы завели собственный хост и живете прекрасно
  • вы завели GitHub и включили GitHub Pages и живете не менее прекрасно
  • вы пользуетесь бесплатным хостингом Rpubs

Что ещё?

Представление данных, конечно, не ограничивается RMarkdown. Вот некоторые штуки, которые также могут быть полезны:

  • bookdownрасширение RMarkdown для написания книг, диссера, создания перезентаций и т.д. (Xie (2016))
  • shiny — динамические сайты, на которых можно развернуть взамодействие с пользователем
  • flexdashboard — динамические дэшборды
  • posterdown — постеры в RMarkdown
  • pagedown — тут много всяких шаблонов примерно для всего (от книги, статьи и резюме до приглашения на свадьбу)
  • blogdown — сайты можно пилить (Xie, Thomas, and Hill (2017))

Список литературы

Xie, Yihui. 2016. Bookdown: Authoring Books and Technical Documents with r Markdown. Chapman; Hall/CRC.
Xie, Yihui, Amber Thomas, and Alison Presmanes Hill. 2017. Blogdown: Creating Websites with r Markdown. Chapman; Hall/CRC.

  1. Потребуется Beamer↩︎

  2. Здесь использован HTML-тег p для того, чтобы сделать выравнивание имени автора по правому краю. К сожалению, сам Markdown про выравнивание ничего не знает :(↩︎

  3. Да, это сноска.↩︎

  4. Да, это тоже сноска, только не по номеру, а через лейбл.↩︎

  5. Чтобы оно поехало, нало будет создать чанк, в котором подгружается пакет reticulate (стандартно, через library(reticulate)) и указывается, какой питон использовать при исполнении кода через use_python("path_to_python"). Путь к питону можно узнать через командную строку с помощью which python3.↩︎

  6. Параметр include в основном используется для задания параметров верстки.↩︎