Skip to contents

You can use Python with examiner, thanks to the support for Python in RStudio and library(knitr) via library(reticulate). Just load reticulate and start Python chunks with ```{python} instead of the usual ```{r}.

This article assumes you have managed to install and use Python as described in the reticulate documentation.

You may need an R wrapper to display some kinds of rich content. Below we illustrate a few.

To test this on your system, make an examiner project and create a question Rmd from the template New File > R Markdown > From Template > demo_python.

Making Python available to reticulate

Read the reticulate docs on how to make this work. Replace the use_condaenv() call with whatever works for you. Here I use an env with packages for graphics, tables, and animated images.

library(reticulate)
if (!("examiner" %in% conda_list()$name)) {
  conda_create("examiner", c("pandas", "matplotlib", "ffmpeg", "tabulate"))
}
use_condaenv("examiner")

Plain text Python output

The following chunk begins with ```{python}, so it will be run with the python engine.

{word: len(word) for word in "This is a fancy test".split() if word != "fancy"}
## {'This': 4, 'is': 2, 'a': 1, 'test': 4}

Matplotlib graphics

Figures will display automatically if they are open when the chunk ends, even if you don’t call the usual plt.show().

Python figures cannot be sized via knitr chunk options. Instead, plt.figure(figsize = ..., dpi = ...) or fig.set_size_inches() or fig.set_dpi(), see below.

import matplotlib.pyplot as plt
plt.figure(figsize = (2, 2), dpi = 72)
plt.plot(range(5))

Pandas data frames

The default plain text display works as expected.

import pandas as pd

data = {
    'Column 1': ['Row1-Col1', 'Row2-Col1'],
    'Column 2': ['Row1-Col2', 'Row2-Col2'],
    'Column 3': ['Row1-Col3', 'Row2-Col3']
}

table = pd.DataFrame(data, index=['Row 1', 'Row 2'])
table
##         Column 1   Column 2   Column 3
## Row 1  Row1-Col1  Row1-Col2  Row1-Col3
## Row 2  Row2-Col1  Row2-Col2  Row2-Col3

For Pandas’ HTML format (ugly) or Markdown (slightly prettier), store the generated markup in a Python string, which you then display from an R chunk using htmltools::HTML(). (Printing from the Python chunk will wrap the output in <pre><code> tags so the markup doesn’t render.)

Python chunk:

table_html = table.to_html()
table_md = table.to_markdown()

R chunks:

htmltools::HTML(py$table_html)
Column 1 Column 2 Column 3
Row 1 Row1-Col1 Row1-Col2 Row1-Col3
Row 2 Row2-Col1 Row2-Col2 Row2-Col3

To output generated markdown, use the chunk option results="asis".

cat(py$table_md)
Column 1 Column 2 Column 3
Row 1 Row1-Col1 Row1-Col2 Row1-Col3
Row 2 Row2-Col1 Row2-Col2 Row2-Col3

Matplotlib animations

Canvas has a 16 kB limit on question size, and WISEflow does not allow inline images. Hence, animated gifs or mp4 videos must be saved by your code and then included with Markdown or knitr::include_graphics(). With this workaround, animation in Matplotlib works well.

import matplotlib.pyplot as plt
import numpy as np

import matplotlib.animation as animation

fig, ax = plt.subplots()
fig.set_size_inches(3, 2)
fig.set_dpi(72)

x = np.arange(0, 2*np.pi, 0.01)
line, = ax.plot(x, np.sin(x))


def animate(i):
    line.set_ydata(np.sin(x + 2 * np.pi * i / 50))  # update the data.
    return line,


ani = animation.FuncAnimation(fig, animate, interval=20, blit=True, save_count=50)

# pillow is the "python imaging library", included with matplotlib
ani.save("animation_python.gif", writer = "pillow", fps = 20)

# Default writer is ffmpeg
ani.save("animation_python.mp4", fps = 20)

# Close the figure to prevent an image of the last frame from displaying automatically
plt.close(fig)

Animated GIF (loops by default)

knitr::include_graphics("animation_python.gif")

MPEG (does not loop in Canvas)

knitr::include_graphics("animation_python.mp4")

While we can set loop and autoplay attributes as shown below, Canvas will strip them from the question html. Thus, the following will loop if you knit it separately, but not in Canvas.

library(htmltools)
# Named argument with an NA value becomes a boolean HTML attribute, see
# https://rstudio.github.io/htmltools/reference/builder.html#arguments-1
tags$video(
  controls = NA,
  loop = NA,
  autoplay = NA,
  tags$source(
    src = "animation_python.mp4",
    type = "video/mp4"
  )
)