Code
print("Hello from R!")Configuring R, Python, Julia, and Observable JS to coexist in a single rendered document
A practical guide to the configuration plumbing required to render a multi-language Quarto document from scratch on macOS.
Ronald ‘Ryy’ G. Thomas
April 30, 2026

Quarto provides a unified authoring framework for scientific and technical publishing.
The plumbing behind a multi-language Quarto document is more extensive than the Quarto website suggests. The documentation makes it look effortless: drop an R chunk here, a Python chunk there, maybe some Julia for good measure, and hit Render. The reality involves an afternoon of chasing PATH variables, fixing reticulate configurations, and troubleshooting why Julia refuses to find its own packages.
The problem is not that any single language is hard to install. Each one has a clean installer and decent documentation. The difficulty is making them all coexist inside a single rendering pipeline where R orchestrates Python through reticulate, Julia through JuliaCall, and Observable JS runs natively in the browser. Small misconfigurations in any layer can cascade into cryptic errors.
This post documents the lessons learned getting all four languages to cooperate in a single Quarto document on macOS, written from the perspective of working through the process rather than having mastered it.
More formally, this post documents the polyglot capability of the Document layer of the Workflow Construct described in post 52. Quarto is the construct’s recommended document layer, subsuming the roles previously divided between R Markdown, Jupyter, and standalone LaTeX. This post addresses the specific case in which a single document spans R, Python, Julia, and Observable, which is the configuration that surfaces the most layer-interaction subtle failures.
The following considerations motivated this exploration:
Errors and better approaches are welcome; see the Feedback section at the end.

Before starting, make sure you have the following available on your macOS system:
Background. This guide assumes familiarity with the macOS Terminal and basic comfort installing software from the command line. You do not need prior experience with all four languages — that is the whole point.
Quarto is an open-source scientific publishing system that extends the idea behind R Markdown to multiple languages. A single .qmd file can contain code chunks written in R, Python, Julia, and Observable JS. When you render the document, Quarto routes each chunk to the appropriate language engine, collects the output, and weaves everything into a unified HTML, PDF, or Word document.
The key insight is that R acts as the orchestration layer. Python chunks are executed through the reticulate package, Julia chunks through JuliaCall, and Observable JS chunks run natively in the browser when producing HTML output. This means your R installation needs to know where Python and Julia live on your system, and each language needs its own packages installed separately.
The setup proceeds in four stages. Completing and verifying each stage before moving to the next is recommended. Debugging a four-language environment is much harder than debugging a single-language problem.
Then install the R packages that bridge to other languages:
The first three packages are for data work. The last two are the bridge packages: reticulate connects R to Python, and JuliaCall connects R to Julia.
The output of py_config() should show your Anaconda Python path. If it does not, you need to tell reticulate where to look.
No separate installation is needed. Observable JS runs in the browser when Quarto produces HTML output. Your Quarto document must include the dependency declaration in the YAML header:
That is it. Observable chunks will render automatically when you build to HTML.

Python integration through reticulate is often the first multi-language feature Quarto users encounter.
Before combining all four languages in a single document, verify that each integration works independently. This saves considerable debugging time.
This confirms your R version and loaded packages. Look for the version number and ensure the base packages are listed.
Confirm that the Python path points to your Anaconda installation, not the system Python. The output should show the version number and the path to the Python binary.
This should print the Julia version and system information. If JuliaCall cannot find Julia, check that the Julia binary is on your PATH.
Once all four verifications pass, test a minimal multi-language document. Create a new .qmd file with these chunks:
If all four chunks produce output when you render, your environment is properly configured. If any chunk fails, go back to the verification step for that specific language.
Below is a complete, documented version of a multi-language visualization example. Each section creates the same scatterplot — bill depth versus bill length by species — in a different language, showcasing how the same data can flow through four ecosystems.
R loads the data and exports a CSV that the other languages can read:
R prepares and exports the clean data. The other languages read from this shared CSV, which ensures consistency across all four visualizations.
ggplot(data, aes(x = bill_length_mm,
y = bill_depth_mm,
color = species,
shape = species)) +
geom_point(size = 3, alpha = 0.7) +
geom_smooth(method = "lm",
se = FALSE,
linewidth = 0.7,
alpha = 0.5) +
scale_color_brewer(palette = "Set1") +
labs(
title = "Bill Depth vs Length (R)",
x = "Bill Length (mm)",
y = "Bill Depth (mm)",
color = "Species",
shape = "Species"
) +
theme_minimal() +
theme(
plot.title = element_text(
hjust = 0.5, size = 14, face = "bold"
),
legend.position = "bottom",
panel.grid.minor = element_blank(),
axis.text = element_text(size = 10),
axis.title = element_text(size = 12)
)The R version uses ggplot2 with a colorblind-friendly palette. The geom_smooth() layer adds per-species trend lines that reveal Simpson’s paradox: the overall negative correlation between bill length and depth reverses when you condition on species.
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
sns.set_theme(style="whitegrid")
sns.set_palette("Set1")
penguins = pd.read_csv("penguins.csv").dropna()
plt.figure(figsize=(10, 6))
sns.scatterplot(
data=penguins,
x='bill_length_mm',
y='bill_depth_mm',
hue='species',
style='species',
s=100,
alpha=0.7
)
sns.regplot(
data=penguins,
x='bill_length_mm',
y='bill_depth_mm',
scatter=False,
color='gray',
line_kws={'alpha': 0.5}
)
plt.title(
'Bill Depth vs Length (Python)',
pad=20, size=14, weight='bold'
)
plt.xlabel('Bill Length (mm)', size=12)
plt.ylabel('Bill Depth (mm)', size=12)
plt.legend(
title='Species',
bbox_to_anchor=(0.5, -0.15),
loc='upper center',
ncol=3
)
plt.tight_layout()
plt.show()The Python version uses Seaborn, which provides a statistical plotting interface on top of Matplotlib. The regplot() adds an overall trend line in gray, making Simpson’s paradox visible: the gray line slopes downward while species-specific patterns slope upward.
using UnicodePlots
using DataFrames
using CSV
using Statistics
penguins = CSV.read("penguins.csv", DataFrame)
dropmissing!(penguins)
plt = scatterplot(
penguins.bill_length_mm,
penguins.bill_depth_mm,
name = string.(penguins.species),
title = "Bill Depth vs Length (Julia)",
xlabel = "Bill Length (mm)",
ylabel = "Bill Depth (mm)",
canvas = DotCanvas
)
pltJulia’s UnicodePlots creates text-based scatter plots that render reliably in any terminal or HTML environment. The trade-off is fewer aesthetic options compared to ggplot2 or Seaborn, but the performance advantage for large datasets can be substantial.
import { Plot } from "@observablehq/plot"
penguins = FileAttachment("penguins.csv").csv()
Plot.plot({
color: {scheme: "category10"},
marks: [
Plot.dot(penguins, {
x: "bill_length_mm",
y: "bill_depth_mm",
stroke: "species",
fill: "species"
})
],
x: {label: "Bill Length (mm)"},
y: {label: "Bill Depth (mm)"},
title: "Bill Depth vs Length (Observable JS)"
})Observable JS produces interactive plots that respond to mouse hover and pan events natively in the browser. No server is required once the HTML is rendered.
Each language brings distinct strengths to the table:
After verifying the minimal example, check that all languages can access the same files and share data. Working directory consistency is the most common source of subtle bugs.
All languages need to access the same files. Check working directories in each:
If any of these return different directories, you will get “file not found” errors when one language tries to read data written by another.
Running four language runtimes simultaneously is memory-intensive. Monitor usage if you experience slowdowns:
Python path confusion. macOS ships with a system Python that is not the one you want. Always verify that reticulate points to your Anaconda or Miniconda installation, not /usr/bin/python3.
Julia first-run compilation. The first time you call julia_setup(), Julia compiles its package cache. This can take several minutes and may look like the session has frozen. Be patient.
Observable JS is HTML-only. Observable chunks do not execute when rendering to PDF or Word. If you need all four languages in PDF output, you will need a different approach for the JavaScript visualizations.
Working directory drift. Each language engine may start in a slightly different working directory. Use absolute paths or verify getwd() / os.getcwd() / pwd() in each language before reading shared files.
Memory pressure. Running R, Python, and Julia simultaneously in a single Quarto render can consume 4+ GB of RAM. Close other applications and monitor Activity Monitor if you experience crashes.
Once all four languages are configured, these commands become routine:
| Command | Action |
|---|---|
quarto render doc.qmd --to html |
Render multi-language document |
quarto preview |
Live preview with auto-reload |
Rscript -e "reticulate::py_config()" |
Verify Python binding |
Rscript -e "JuliaCall::julia_setup()" |
Verify Julia binding |
conda activate quarto-env |
Activate the Python environment |
To remove individual language integrations without affecting the others:
R and RStudio remain functional without any of the above.
{.img-fluid}
The best technical configurations, like the best libraries, are built on solid foundations.
Conceptual Understanding:
Technical Skills:
use_python() calls on most macOS systems.julia_setup() performs first-run compilation that can take several minutes; this is normal, not an error.{verbatim} chunk type in Quarto is useful for showing YAML and code examples without execution.Gotchas and Pitfalls:
renv.lock, requirements.txt, and Julia Project.toml files for one-command environment setup.Setting up a multi-language Quarto environment is less about any single installation and more about making four separate ecosystems aware of each other. R sits at the center, with reticulate and JuliaCall acting as bridges to Python and Julia, while Observable JS runs independently in the browser.
The process demonstrates that environment configuration is its own skill, distinct from programming in any one language. The time spent getting PATH variables, package versions, and working directories aligned pays off every time a document is rendered that would otherwise require four separate scripts and manual assembly.
For those starting from scratch, the strongest recommendation is to verify each language independently before combining them. Debugging a four-language pipeline is exponentially harder than debugging a single-language problem.
Main takeaways:
Related posts:
Key resources:
Have questions, suggestions, or spot an error? Let me know.
I would enjoy hearing from you if: