Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Coming from R or Python

If you already write dplyr or pandas, you know most of BioLang’s table vocabulary. The verbs were chosen to match what you type today, so this chapter is a lookup table rather than a tutorial: find the thing you already know, read across, keep working.

The pipe is |>. It does what %>% does in magrittr and what method chaining does in pandas: the value on the left becomes the first argument on the right.

let summary = read_csv("counts.csv")
    |> filter(|row| row.padj < 0.05)
    |> mutate(|row| {gene: row.gene, lfc: row.log2FoldChange})
    |> arrange("-lfc")
    |> head(20)

Table verbs

dplyrpandasBioLang
filter()df[mask] / df.query()filter()
select()df[[cols]]select()
mutate()df.assign()mutate()
rename()df.rename()rename()
arrange()df.sort_values()arrange() — prefix - for descending
group_by()df.groupby()group_by()
summarise().agg()summarize()
count().value_counts()count_by() or value_counts()
distinct().drop_duplicates()distinct()
slice_head(n).head(n)head(n)
slice_tail(n).tail(n)tail(n)
bind_rows()pd.concat()concat()
bind_cols()pd.concat(axis=1)bind_cols()
left_join().merge(how="left")left_join()
inner_join().merge()inner_join()
anti_join()anti_join()
pivot_longer().melt()pivot_longer()
pivot_wider().pivot()pivot_wider()
across().apply()map() over columns

Inspecting a table

RPythonBioLang
glimpse() / str()df.info()glimpse()
summary()df.describe()describe()
head()df.head()head()
dim()df.shapenrow() and ncol()
nrow() / ncol()len(df) / df.shape[1]nrow() / ncol()
names()df.columnscolnames()

glimpse() prints one line per column with its type and first values, the way dplyr::glimpse does. describe() gives the per-column dtype, null count, and five-number summary that pandas.describe gives. Printing a table shows column types under the headers, like a tibble.

Two names look familiar but mean something else. BioLang’s str() converts a value to a string — it is not R’s structure display; use glimpse() for that. dim() is for matrices; for tables use nrow() and ncol().

Reading and writing

RPythonBioLang
read.csv()pd.read_csv()read_csv()
readr::read_tsv()pd.read_table()read_tsv()
write.csv().to_csv()write_csv()
Biostrings::readDNAStringSet()SeqIO.parse()read_fasta()
ShortRead::readFastq()SeqIO.parse(..., "fastq")read_fastq()
VariantAnnotation::readVcf()pysam.VariantFile()read_vcf()
rtracklayer::import()pybedtools.BedTool()bed() / gff()

Things that work differently

Sequences are a type, not a string. dna"ACGT" is a first-class value with gc_content, reverse_complement, and translate defined on it. You do not need Biostrings or Biopython to do the obvious things.

There is no <-. Bindings use let, and const for values that must not change.

Anonymous functions use |x|. Where R writes \(x) x + 1 or ~ .x + 1, and Python writes lambda x: x + 1, BioLang writes |x| x + 1.

Indices start at 0, as in Python, not at 1 as in R.

Missing values are nil, not NA or NaN. Comparisons against nil are explicit rather than contagious.

Bringing existing code

bl import converts Python, R, Jupyter notebooks, and R Markdown:

bl import analysis.R --output analysis.bl
bl import pipeline.ipynb --output pipeline.bln

Anything the converter cannot translate is left in place with a # TODO: marker naming what is missing, and the workbench lists those markers as problems so you can see exactly how much is left to port.

Keeping the parts that are not ported yet

You do not have to move everything at once. py() and r() evaluate a snippet in a real Python or R process and bring the result back:

let counts = read_csv("counts.csv")
let shrunk = r("DESeq2::lfcShrink(dds, coef=2, type='apeglm')", {dds: counts})
let embedding = py("import scanpy as sc; sc.tl.umap(adata)", {adata: cells})

Bindings are a record — {dds: counts} — and each key becomes a variable in the other runtime. The value of the last expression comes back, which is what a notebook cell does in Python and what eval does in R.

Values cross the boundary as JSON, so tables, records, lists, numbers, strings, and booleans travel in both directions. A BioLang table arrives as a list of records in Python and as a data frame in R. Objects that only exist inside the other runtime — a Seurat object, an open file handle — do not, and you will get a clear error rather than a silent truncation. Keep those calls narrow: pass data in, get data out.

This is meant as a bridge. Delete the escape hatches as BioLang grows the equivalents, and call interop_status() to see which interpreters were found. r() needs the jsonlite package, and says so if it is missing.