forked from rstudio/rmarkdown-cookbook
-
Notifications
You must be signed in to change notification settings - Fork 0
/
15-languages.Rmd
393 lines (279 loc) · 20.6 KB
/
15-languages.Rmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
# Other Languages {#other-languages}
Besides the R language, many other languages are supported in R Markdown through the **knitr** package. The language name is indicated by the first word in the curly braces after the three opening backticks. For example, the little `r` in ```` ```{r}```` indicates that the code chunk contains R code, and ```` ```{python}```` is a Python code chunk. In this chapter, we show a few languages that you may not be familiar with.
In **knitr**, each language is supported through a language engine. Language engines are essentially functions that take the source code and options of a chunk as the input, and return a character string as the output. They are managed through the object `knitr::knit_engines`. You may check the existing engines via:
```{r, eval=FALSE}
names(knitr::knit_engines$get())
```
```{r, echo=FALSE}
# remove engines registered by bookdown
engs = names(knitr::knit_engines$get())
i = match('theorem', engs)
if (!is.na(i)) engs = head(engs, i - 1)
engs
```
At the moment, most code chunks of non-R languages are executed independently. For example, all `bash` code chunks in the same document are executed separately in their own sessions, so a later `bash` code chunk cannot use variables created in a previous `bash` chunk, and the changed working directory (via `cd`) will not be persistent across different `bash` chunks. Only R, Python, and Julia code chunks are executed in the same session. Please note that all R code chunks are executed in the same R session, and all Python code chunks are executed in the same Python session, etc. The R session and the Python session are two different sessions, but it is possible to access or manipulate objects of one session from another session (see Section \@ref(eng-python)).
[Section 2.7](https://bookdown.org/yihui/rmarkdown/language-engines.html) of the _R Markdown Definitive Guide_ [@rmarkdown2018] shows examples of using Python, Shell, SQL, Rcpp, Stan, JavaScript, CSS, Julia, C, and Fortran code in R Markdown. In this chapter, we will show more language engines, and you may find more examples in the repository at https://github.com/yihui/knitr-examples (look for filenames that contain the word "engine").
First, let's reveal how a language engine works by registering a custom language engine.
## Register a custom language engine (\*) {#custom-engine}
You can register a custom language engine\index{language engine!custom} via the method `knitr::knit_engines$set()`\index{knitr!knit\_engines}. It accepts a function as its input, e.g.,
```{r, eval=FALSE}
knitr::knit_engines$set(foo = function(options) {
# the source code is in options$code;
# just do whatever you want with it
})
```
This registers the `foo` engine, and you will be able to use a code chunk that starts with ```` ```{foo}````.
The engine function has one argument, `options`, which is a list of chunk options of the code chunk. You can access the source code of the chunk as a character vector in `options$code`. For example, for the code chunk:
````md
```{foo}`r ''`
1 + 1
2 + 2
```
````
The `code` element of `options` would be a character vector `c('1 + 1', '2 + 2')`.
Language engines do not really have to deal with computer languages, but can process any text in a code chunk. First, we show a simple example of an engine that converts the content of a code chunk to uppercase:
```{r}
knitr::knit_engines$set(upper = function(options) {
code <- paste(options$code, collapse = '\n')
if (options$eval) toupper(code) else code
})
```
The key is that we apply the function `toupper` to the "code," and return the result as a single character string (by concatenating all lines of code by `\n`). Note that `toupper()` is applied only when the chunk option `eval = TRUE`\index{chunk option!eval}, otherwise the original string is returned. This shows you how to make use of chunk options like `eval` inside the engine function. Similarly, you may consider adding `if (options$results == 'hide') return()` to the function body to hide the output when the chunk option `results = 'hide'`\index{chunk option!results}. Below is an example chunk that uses the `upper` engine, with its output:
> ````md
> ```{upper}`r ''`
> Hello, **knitr** engines!
> ```
> ````
>
> ```{upper}
> Hello, **knitr** engines!
> ```
Next we show an example of an alternative Python engine^[In practice, you should use the built-in `python` engine instead, which is based on the **reticulate** package and supports Python code chunks much better (see Section \@ref(eng-python)).] named `py`. This engine is implemented by simply calling the `python` command via the R function `system2()`:
```{r, tidy=FALSE}
knitr::knit_engines$set(py = function(options) {
code <- paste(options$code, collapse = '\n')
out <- system2(
'python', c('-c', shQuote(code)), stdout = TRUE
)
knitr::engine_output(options, code, out)
})
```
To fully understand the above engine function, you need to know the following:
1. Given Python code as a character string (`code` in the above function), we can execute the code via a command-line call `python -c 'code'`. That is what `system2()` does. We collect the (text) output by specifying `stdout = TRUE` in `system2()`.
1. You can pass the chunk options, source code, and text output to the function `knitr::engine_output()`\index{knitr!engine\_output()} to generate the final output. This function deals with common chunk options like `echo = FALSE` and `results = 'hide'`, so you do not need to take care of these cases.
A lot of language engines in **knitr** are defined in this way (i.e., using `system2()` to execute commands corresponding to languages). If you are curious about the technical details, you may check out the source code of most language engines in the R source code here: https://github.com/yihui/knitr/blob/master/R/engine.R.
Now we can use the new engine `py`, e.g.,
> ````md
> ```{py}`r ''`
> print(1 + 1)
> ```
> ````
>
> ```{py, echo=FALSE}
> print(1 + 1)
> ```
You can even override existing language engines in **knitr** via `knitr::knit_engines$set()`, if you are sure that your versions are necessary or better than the existing ones. Usually we do not recommend that you do this because it may surprise users who are familiar with existing engines, but we want to make you aware of this possibility anyway.
## Run Python code and interact with Python {#eng-python}
We know you love Python, so let's make it super clear: R Markdown and **knitr** do support Python\index{language engine!python}\index{Python}.
To add a Python code chunk to an R Markdown document, you can use the chunk header ```` ```{python}````, e.g.,
````md
```{python}`r ''`
print("Hello Python!")
```
````
You can add chunk options to the chunk header as usual, such as `echo = FALSE` or `eval = FALSE`. Plots drawn with the **matplotlib** package in Python are also supported.
The Python support in R Markdown and **knitr** is based on the **reticulate** package\index{R package!reticulate} [@R-reticulate], and one important feature of this package is that it allows two-way communication between Python and R. For example, you may access or create Python variables from the R session via the object `py` in **reticulate**:
`r import_example('python.Rmd')`
For more information about the **reticulate** package, you may see its documentation at https://rstudio.github.io/reticulate/.
## Execute content conditionally via the `asis` engine {#eng-asis}
As its name indicates, the `asis` engine\index{language engine!asis} writes out the chunk content as is. The advantage of using this engine is that you can include some content conditionally---the display of the chunk content is decided by the chunk option `echo`. When `echo = FALSE`, the chunk will be hidden. Below is a simple example:
````md
```{r}`r ''`
getRandomNumber <- function() {
sample(1:6, 1)
}
```
```{asis, echo = getRandomNumber() == 4}`r ''`
According to https://xkcd.com/221/, we just generated
a **true** random number!
```
````
The text in the `asis` chunk will be displayed only if the condition `getRandomNumber() == 4` is (randomly) true.
## Execute Shell scripts {#eng-bash}
You can run Shell scripts via the `bash` or `sh` or `zsh` engine\index{language engine!bash}\index{language engine!sh}\index{language engine!zsh}, depending on which shell you prefer. Below is a `bash` example, with the chunk header ```` ```{bash}````:
```{bash}
ls *.Rmd | head -n 5
```
Please note that `bash` is invoked with the R function `system2()`. It will ignore profile files like `~/.bash_profile` and `~/.bash_login`, in which you may have defined command aliases or modified environment variables like the `PATH` variable. If you want these profile files to be executed just like when you use the terminal, you may pass the argument `-l` to `bash` via `engine.opts`, e.g.,
````md
```{bash, engine.opts='-l'}`r ''`
echo $PATH
```
````
If you want to enable the `-l` argument globally for all `bash` chunks, you may set it in the global chunk option in the beginning of your document:
```{r, eval=FALSE}
knitr::opts_chunk$set(engine.opts = list(bash = '-l'))
```
You can also pass other arguments to `bash` by providing them as a character vector to the chunk option `engine.opts`\index{chunk option!engine.opts}.
## Visualization with D3 {#d3}
The R package **r2d3** [@R-r2d3]\index{R package!r2d3} is an interface to D3 visualizations. This package can be used in R Markdown documents as well as other applications (e.g., Shiny). To use it in R Markdown, you can either call its function `r2d3()` in a code chunk, or use its `d3` engine\index{language engine!D3}\index{D3}\index{figure!D3}. The latter requires you to understand the D3 library and JavaScript, which are beyond the scope of this book, and we will leave it to readers to learn them. Below is an example of using the `d3` engine to draw a bar chart:
`r import_example('d3.Rmd')`
## Write the chunk content to a file via the `cat` engine {#eng-cat}
Sometimes it could be useful to write the content of a code chunk to an external file, and use this file later in other code chunks. Of course, you may do this via the R functions like `writeLines()`, but the problem is that when the content is relatively long, or contains special characters, the character string that you would pass to `writeLines()` may look awkward. Below is an example of writing a long character string to a file `my-file.txt`:
```{r, eval=FALSE}
writeLines("This is a long character string.
It has multiple lines. Remember to escape
double quotes \"\", but 'single quotes' are OK.
I hope you not to lose your sanity when thinking
about how many backslashes you need, e.g., is it
'\t' or '\\t' or '\\\\t'?",
con = "my-file.txt")
```
This problem has been greatly alleviated since R 4.0.0, because R started to support raw strings in `r"( )"` (see the help page `?Quotes`), and you do not need to remember all the rules about special characters. Even with raw strings, it can still be a little distracting for readers to see a long string written to a file explicitly in a code chunk.
The `cat` engine\index{language engine!cat} in **knitr** has provided a way for you to present text content in a code chunk and/or write it to an external file, without thinking about all the rules about R's character strings (e.g., you need double backslashes when you need a literal backslash).
To write the chunk content to a file, specify the file path in the chunk option `engine.opts`\index{chunk option!engine.opts}, e.g., `engine.opts = list(file = 'path/to/file')`. Under the hood, the list of values specified in `engine.opts` will be passed to the function `base::cat()`, and `file` is one of the arguments of `base::cat()`.
Next we will present three examples to illustrate the use of the `cat` engine.
### Write to a CSS file
As shown in Section \@ref(chunk-styling), you can embed a `css` code chunk in an Rmd document to style elements with CSS.\index{CSS}\index{language engine!css} An alternative way is to provide a custom CSS file to Pandoc via the `css` option of some R Markdown output formats such as `html_document`. The `cat` engine can be used to write this CSS file from Rmd.
This example below shows how to generate a file `custom.css` from a chunk in the
document, and pass the file path to the `css` option of the `html_document` format:
`r import_example("cat-css.Rmd")`
The only difference between the `css` code chunk approach and this approach is that the former approach writes the CSS code in place (i.e., in the place of the code chunk), which is inside the `<body>` tag of the output document, and the latter approach writes CSS to the `<head>` area of the output document. There will not be any practical visual differences in the output document.
### Include LaTeX code in the preamble
In Section \@ref(latex-preamble), we introduced how to add LaTeX code to the preamble, which requires an external `.tex` file. This file can also be generated from Rmd, and here is an example:
`r import_example("cat-latex.Rmd")`
In the LaTeX code in the `cat` code chunk above, we have defined the header and footer of the PDF document. If we also want to show the author name in the footer, we can append the author information to `preamble.tex` in another `cat` code chunk with options `engine.opts = list(file = 'preamble.tex', append = TRUE)` and `code = sprintf('\\fancyfoot[LO,RE]{%s}', rmarkdown::metadata$author)`. To understand how this works, recall that we mentioned earlier in this section that `engine.opts` is passed to `base::cat()` (so `append = TRUE` is passed to `cat()`), and you may understand the chunk option `code` by reading Section \@ref(option-code).
### Write YAML data to a file and also display it
By default, the content of the `cat` code chunk will not be displayed in the output document. If you also want to display it after writing it out, set the chunk option `class.source` to a language name. The language name is used for syntax highlighting. In the example below, we specify the language to be `yaml`:
````md
```{cat, engine.opts=list(file='demo.yml'), class.source='yaml'}`r ''`
a:
aa: "something"
bb: 1
b:
aa: "something else"
bb: 2
```
````
Its output is displayed below, and it also generated a file `demo.yml`.
```{cat, engine.opts=list(file='demo.yml'), class.source='yaml'}
a:
aa: "something"
bb: 1
b:
aa: "something else"
bb: 2
```
To show the file `demo.yml` is really generated, we can try to read it into R with the **yaml** package [@R-yaml]:
```{r}
xfun::tree(yaml::read_yaml('demo.yml'))
```
```{r, include=FALSE}
unlink('demo.yml')
```
## Run SAS code {#eng-sas}
You may run SAS (https://www.sas.com) code using the `sas` engine\index{language engine!SAS}. You need to either make sure the SAS executable is in your environment variable `PATH`, or (if you do not know what `PATH` means) provide the full path to the SAS executable via the chunk option `engine.path`\index{chunk option!engine.path}, e.g., `engine.path = "C:\\Program Files\\SASHome\\x86\\SASFoundation\\9.3\\sas.exe"`. Below is an example to print out "Hello World":
````md
```{sas}`r ''`
data _null_;
put 'Hello, world!';
run;
```
````
## Run Stata code {#eng-stata}
You can run Stata (https://www.stata.com) code with the `stata` engine\index{language engine!stata} if you have installed Stata. Unless the `stata` executable can be found via the environment variable `PATH`, you need to specify the full path to the executable via the chunk option `engine.path`\index{chunk option!engine.path}, e.g., `engine.path = "C:/Program Files (x86)/Stata15/StataSE-64.exe"`. The following is a quick example:
````md
```{stata}`r ''`
sysuse auto
summarize
```
````
The `stata` engine in **knitr** is quite limited. Doug Hemken has substantially extended it in the **Statamarkdown** package\index{R package!Statamarkdown}, which is available on GitHub at https://github.com/Hemken/Statamarkdown. You may find tutorials about this package by searching online for "Stata R Markdown."
## Create graphics with Asymptote {#eng-asy}
```{r include = FALSE}
eval_asy <- function() {
check_not_windows <- Sys.info()['sysname'] != 'Windows'
check_has_asymptote <- nzchar(Sys.which("asy"))
check_not_ci <- is.na(Sys.getenv('CI', NA))
eval_asy <- check_not_windows & check_has_asymptote & check_not_ci
if (!eval_asy)
warning("System set-up not compatible with Asymptote, so chunks with asy engine will be skipped.")
eval_asy
}
```
Asymptote (https://asymptote.sourceforge.io) is a powerful language for vector graphics. You may write and run Asymptote code in R Markdown with the `asy` engine\index{Asymptote}\index{language engine!asy} if you have installed Asymptote (see its website for instructions on the installation). Below is an example copied from the repository https://github.com/vectorgraphics/asymptote, and its output is shown in Figure \@ref(fig:elevation):
```{asy, elevation, fig.cap='A 3D graph made with Asymptote.', cache=TRUE, fig.retina=1, eval=eval_asy()}
import graph3;
import grid3;
import palette;
settings.prc = false;
currentprojection=orthographic(0.8,1,2);
size(500,400,IgnoreAspect);
real f(pair z) {return cos(2*pi*z.x)*sin(2*pi*z.y);}
surface s=surface(f,(-1/2,-1/2),(1/2,1/2),50,Spline);
surface S=planeproject(unitsquare3)*s;
S.colors(palette(s.map(zpart),Rainbow()));
draw(S,nolight);
draw(s,lightgray+opacity(0.7));
grid3(XYZgrid);
```
Note that for PDF output, you may need some additional LaTeX packages, otherwise you may get an error that looks like this:
```
! LaTeX Error: File `ocgbase.sty' not found.
```
If such an error occurs, please see Section \@ref(install-latex-pkgs) for how to install the missing LaTeX packages.
In the `asy` chunk above, we used the setting `settings.prc = false`. Without this setting, Asymptote generates an interactive 3D graph when the output format is PDF. However, the interactive graph can only be viewed in Acrobat Reader. If you use Acrobat Reader, you can interact with the graph. For example, you can rotate the 3D surface in Figure \@ref(fig:elevation) with your mouse.
### Generate data in R and read it in Asymptote
Now we show an example in which we first save data generated in R to a CSV file (below is an R code chunk):
```{r}
x = seq(0, 5, l = 100)
y = sin(x)
writeLines(paste(x, y, sep = ','), 'sine.csv')
```
Then read it in Asymptote, and draw a graph based on the data as shown in Figure \@ref(fig:sine-curve) (below is an `asy` code chunk):
```{asy, sine-curve, fig.cap='Pass data from R to Asymptote to draw a graph.', cache=TRUE, fig.retina=1, eval=eval_asy()}
import graph;
size(400,300,IgnoreAspect);
settings.prc = false;
// import data from csv file
file in=input("sine.csv").line().csv();
real[][] a=in.dimension(0,0);
a=transpose(a);
// generate a path
path rpath = graph(a[0],a[1]);
path lpath = (1,0)--(5,1);
// find intersection
pair pA=intersectionpoint(rpath,lpath);
// draw all
draw(rpath,red);
draw(lpath,dashed + blue);
dot("$\delta$",pA,NE);
xaxis("$x$",BottomTop,LeftTicks);
yaxis("$y$",LeftRight,RightTicks);
```
```{r, include=FALSE}
unlink('sine.csv')
```
## Style HTML pages with Sass/SCSS {#eng-sass}
Sass (https://sass-lang.com) is a CSS extension language\index{CSS!Sass}\index{Sass} that allows you to create CSS rules in much more flexible ways than you'd do with plain CSS. Please see its official documentation if you are interested in learning it.
The R package **sass** [@R-sass] \index{R package!sass}can be used to compile Sass to CSS. Based on the **sass** package, **knitr** includes two language engines: `sass`\index{language engine!sass} and `scss`\index{language engine!scss} (corresponding to the Sass and SCSS syntax, respectively) to compile code chunks to CSS. Below is a `scss` code chunk, with the chunk header ```` ```{scss}````:
```{scss}
$font-stack: "Comic Sans MS", cursive, sans-serif;
$primary-color: #00FF00;
.book.font-family-1 {
font: 100% $font-stack;
color: $primary-color;
}
```
You can also use the `sass` engine, and the Sass syntax is slightly different with the SCSS syntax, e.g.,
````md
```{sass}`r ''`
$font-stack: "Comic Sans MS", cursive, sans-serif
$primary-color: #00FF00
.book.font-family-1
font: 100% $font-stack
color: $primary-color
```
````
If you are reading [the HTML version of this section,](https://bookdown.org/yihui/rmarkdown-cookbook/eng-sass.html) you will notice that the font for this page has been changed to Comic Sans, which might be surprising, but please do not panic---[you are not having a stroke.](https://twitter.com/andrewheiss/status/1250438044542361600)
The `sass`/`scss` code chunks are compiled through the `sass::sass()` function. Currently you can customize the output style for the CSS code via the chunk option `engine.opts`, e.g., `engine.opts = list(style = "expanded")`. The default style is "compressed." If you are not sure what this means, please refer to the help page `?sass::sass_options` and look for the `output_style` argument.