R Nodes
R nodes let you use the entire Bioconductor and CRAN ecosystem inside your workflow. Here’s how they work.
The Simplest R Node
Here’s a complete, working R node:
library(DESeq2)
COUNTS_MATRIX <- "data/counts.csv"
SAMPLE_INFO <- "data/samples.csv"
OUTPUT_DIR <- "results/"
dir.create(OUTPUT_DIR, recursive = TRUE, showWarnings = FALSE)
dds <- DESeqDataSetFromMatrix(countData = read.csv(COUNTS_MATRIX),
colData = read.csv(SAMPLE_INFO),
design = ~condition)
dds <- DESeq(dds)
res <- results(dds)
write.csv(res, file.path(OUTPUT_DIR, "results.csv"))
return(res)This node has 3 inputs (COUNTS_MATRIX, SAMPLE_INFO, OUTPUT_DIR) and 1 output (res).
No Decorator Needed
Unlike Python, R nodes don’t need a decorator. You just write R code — the system figures out inputs and outputs automatically.
What Is an Input?
Inputs are UPPERCASE variable assignments at the top of your script.
COUNTS_MATRIX <- "data/counts.csv"
SAMPLE_INFO <- "data/samples.csv"
OUTPUT_DIR <- "results/"This creates three inputs: COUNTS_MATRIX, SAMPLE_INFO, and OUTPUT_DIR. When the workflow runs, values from upstream nodes replace these defaults automatically.
Use Empty Strings for File Inputs
For file paths and string inputs, use empty quotes as the default:
COUNTS_MATRIX <- "" # gets replaced by upstream connection
OUTPUT_DIR <- "" # gets replaced by upstream connectionFor numeric inputs, provide a sensible default:
ALPHA <- 0.05 # used if nothing is connectedWhy UPPERCASE?
The system only detects uppercase variables (like COUNTS_MATRIX, ALPHA) as inputs. Lowercase variables (like counts_matrix) are treated as intermediate work, not inputs.
This is the same convention as bash nodes — if you’ve used bash nodes, R inputs work the same way.
What Is an Output?
Outputs come from the return() statement.
res <- results(dds)
return(res)
# ↑
# output 1Whatever you pass to return() becomes available to downstream nodes.
Passing Multiple Values Downstream
R nodes support one output. If you need to pass multiple things downstream, combine them into a single variable first:
combined <- list(results = res, normalized = norm_counts)
return(combined)The downstream node receives combined as a list. It can then access combined$results and combined$normalized.
No return()?
If you don’t use return(), the system uses the last non-input variable you assigned as the output. But explicit return() is always clearer.
How Variables Arrive
When you connect an upstream node to your R node, the variable appears in the R environment automatically. Just use it by name:
# COUNTS_MATRIX arrives from upstream — just use it
dds <- DESeqDataSetFromMatrix(countData = read.csv(COUNTS_MATRIX), ...)No input$COUNTS_MATRIX or input[['COUNTS_MATRIX']] — just COUNTS_MATRIX.
A Complete Example
Here’s a real DESeq2 differential expression node:
library(DESeq2)
COUNTS_MATRIX <- ""
SAMPLE_INFO <- ""
DESIGN <- "~condition"
OUTPUT_DIR <- ""
REF_COL <- "condition"
REF_LEVEL <- "control"
ALPHA <- 0.05
dir.create(OUTPUT_DIR, recursive = TRUE, showWarnings = FALSE)
counts <- read.csv(COUNTS_MATRIX, row.names = 1)
samples <- read.csv(SAMPLE_INFO)
dds <- DESeqDataSetFromMatrix(countData = counts,
colData = samples,
design = as.formula(DESIGN))
dds <- DESeq(dds)
res <- results(dds, alpha = ALPHA)
write.csv(res, file.path(OUTPUT_DIR, "deseq2_results.csv"))
return(res)Good to Know
Verbose output? Use sink()
R packages like DESeq2 and Seurat can be very chatty, printing progress for every step. Redirect verbose output to a file:
sink("deseq2.log")
dds <- DESeq(dds)
sink() # restore stdout
print("DESeq2 complete. See deseq2.log for details.")
res <- results(dds)
return(res)Libraries go at the top
Always put library() calls at the top of your script. They run inline as part of your code, so anything declared before them won’t have access to the library:
library(DESeq2)
library(ggplot2)
# Your code here...Intermediate variables stay internal
When the R node finishes, only the variable in your return() is passed downstream. Your intermediate variables (dds, counts, samples) stay internal — they don’t clutter the workflow.
R runs in-process
R runs in-process via rpy2, sharing memory with the backend. For extremely large datasets, this may cause memory pressure. Consider splitting very large analyses into smaller steps.
No timeout
R nodes have no execution timeout. Long-running analyses are fine.
Quick Summary
| Concept | How it works |
|---|---|
| Decorator | Not needed — just write R code |
| Inputs | UPPERCASE variable assignments (VAR <- "value") |
| Empty defaults | Use "" for file/string inputs, numbers for numeric |
| Outputs | return(variable) |
| No return? | Last non-input variable becomes the output |
| Variable arrival | Automatic — just use the variable name |
| Intermediate vars | Stay internal, not passed downstream |
| Libraries | Use library() at the top |
| Verbose output | Use sink() to redirect to a log file |
| In-process | R runs in-process via rpy2 — watch memory for large data |
| No timeout | Nodes can run for hours or days |