Python/R Interop
BioLang interoperates with Python and R through subprocess execution, file-based data exchange, and JSON communication. This lets you leverage existing libraries like scanpy, DESeq2, or ggplot2 from BioLang scripts.
Calling Python
Running a Python script
# Run a Python script and capture output
let result = shell("python3 analyze.py --input data.csv")
print(f"Exit code: {result.exit_code}")
print(f"Output: {result.stdout}")
Inline Python with data exchange
# Prepare data in BioLang, process in Python, read results back
let counts = read_csv("data/counts.csv")
write_tsv(counts, "_temp_counts.tsv")
let metadata = read_csv("data/sample_sheet.csv")
write_tsv(metadata, "_temp_metadata.tsv")
# Run DESeq2-like analysis via Python
# Write Python script to a temp file and run it
let py_script = join([
"import pandas as pd",
"from pydeseq2.dds import DeseqDataSet",
"from pydeseq2.ds import DeseqStats",
"",
"counts = pd.read_csv('_temp_counts.tsv', sep='\\\\t', index_col=0)",
"metadata = pd.read_csv('_temp_metadata.tsv', sep='\\\\t', index_col=0)",
"",
"dds = DeseqDataSet(counts=counts, metadata=metadata, design_factors='condition')",
"dds.deseq2()",
"",
"stat_res = DeseqStats(dds, contrast=['condition', 'treated', 'control'])",
"stat_res.summary()",
"stat_res.results_df.to_csv('_temp_deseq_results.tsv', sep='\\\\t')"
], "\n")
write_text("_temp_deseq.py", py_script)
let result = shell("python3 _temp_deseq.py")
if result.exit_code != 0 {
print(f"Python error: {result.stderr}")
exit(1)
}
# Read results back into BioLang
let de_results = tsv("_temp_deseq_results.tsv")
let significant = de_results
|> filter(|r| float(r["padj"]) < 0.05)
|> filter(|r| abs(float(r["log2FoldChange"])) > 1.0)
print(f"Significant DE genes: {len(significant)}")
write_tsv(significant, "significant_genes.tsv")
# Clean up temp files
remove("_temp_counts.tsv")
remove("_temp_metadata.tsv")
remove("_temp_deseq_results.tsv")
remove("_temp_deseq.py")
Python via JSON exchange
# Pass structured data to Python via JSON
let params = {
input_file: "counts.csv",
min_counts: 10,
normalization: "TPM",
output_file: "normalized.csv"
}
write_json(params, "_params.json")
let result = shell("python3 normalize.py --params _params.json")
if result.exit_code == 0 {
let output = read_json("_params_result.json")
print(f"Normalized {output.n_genes} genes across {output.n_samples} samples")
} else {
print(f"Normalization failed: {result.stderr}")
}
Calling R
Running an R script
# Run R script for statistical analysis
let result = shell("Rscript analysis.R input.csv output.csv")
if result.exit_code != 0 {
print(f"R error: {result.stderr}")
exit(1)
}
let output = read_csv("output.csv")
print(f"R analysis produced {len(output)} results")
Inline R for DESeq2
# Prepare count matrix and run DESeq2 in R
let counts = read_csv("data/counts.csv")
write_csv(counts, "_deseq_input.csv")
let r_script = join([
"library(DESeq2)",
"",
"counts <- read.csv('_deseq_input.csv', row.names=1)",
"coldata <- data.frame(",
" condition = factor(c(rep('control', 3), rep('treated', 3)))",
")",
"",
"dds <- DESeqDataSetFromMatrix(countData=counts, colData=coldata, design=~condition)",
"dds <- DESeq(dds)",
"res <- results(dds, contrast=c('condition', 'treated', 'control'))",
"",
"write.csv(as.data.frame(res), '_deseq_output.csv')"
], "\n")
write_text("_deseq.R", r_script)
let result = shell("Rscript _deseq.R")
if result.exit_code == 0 {
let de = read_csv("_deseq_output.csv")
let sig = de |> filter(|r| float(r["padj"]) < 0.05)
print(f"DESeq2 found {len(sig)} significant genes")
write_csv(sig, "deseq2_significant.csv")
}
remove("_deseq.R")
remove("_deseq_input.csv")
remove("_deseq_output.csv")
R for plotting
# Generate publication-quality plots with ggplot2
let data = tsv("pca_coordinates.tsv")
write_tsv(data, "_plot_data.tsv")
let r_plot = join([
"library(ggplot2)",
"",
"data <- read.delim(\"_plot_data.tsv\")",
"p <- ggplot(data, aes(x=pc1, y=pc2, color=population)) +",
" geom_point(size=2, alpha=0.7) +",
" theme_minimal() +",
" labs(x=\"PC1\", y=\"PC2\", title=\"Population Structure\") +",
" scale_color_brewer(palette=\"Set2\")",
"",
"ggsave(\"pca_plot.png\", p, width=8, height=6, dpi=300)",
"cat(\"Plot saved\")"
], "\n")
write_text("_plot.R", r_plot)
shell("Rscript _plot.R")
print("PCA plot saved to pca_plot.png")
remove("_plot.R")
remove("_plot_data.tsv")
Bidirectional Pipes
Streaming data to external tools
# Write data then process with an external tool
write_fastq(read_fastq("data/reads.fastq"), "_temp.fq")
let result = shell("seqkit stats -T _temp.fq")
print(result.stdout)
remove("_temp.fq")
Using samtools via pipe
# Use samtools for operations BioLang does not natively support
let flagstat = shell("samtools flagstat aligned.bam")
print(flagstat.stdout)
# Filter BED then pass to samtools
let bed_regions = read_bed("data/regions.bed")
|> filter(|r| r.end - r.start > 100)
write_bed(bed_regions, "_filtered.bed")
let result = shell("samtools view -L _filtered.bed -b aligned.bam -o targeted.bam")
remove("_filtered.bed")
Data Format Exchange
AnnData exchange with Python
# Exchange data with scanpy via CSV/JSON
# Export from scanpy: adata.obs.to_read_csv("obs.csv")
let obs = read_csv("obs.csv")
# Do something in BioLang
let cluster_sizes = frequencies(map(obs, |row| row["leiden"]))
print(f"Cluster sizes: {to_string(cluster_sizes)}")
# Compute custom scores
let scores = map(obs, |cell| {
float(cell["n_genes"]) / float(cell["total_counts"])
})
write_json(scores, "custom_scores.json")
# Import back in scanpy: scores = json.load(open("custom_scores.json"))
Parquet for large datasets
# Use CSV/TSV for data exchange with Python/R
let data = read_csv("data/expression.csv")
# Write as TSV for efficient exchange
write_tsv(data, "data.tsv")
# Python can read it:
# pd.read_csv("data.tsv", sep="\t")
# Read TSV back
let loaded = tsv("data.tsv")
print(f"Loaded {len(loaded)} rows from TSV")
Best Practices
Temporary file management
# Use a temp directory for clean interop
let tmp = "_tmp_interop"
mkdir(tmp)
try {
let input_path = f"{tmp}/input.tsv"
let output_path = f"{tmp}/output.tsv"
write_tsv(data, input_path)
shell(f"python3 process.py {input_path} {output_path}")
let result = tsv(output_path)
write_tsv(result, "final_output.tsv")
} catch e {
print(f"Error: {to_string(e)}")
}
# Clean up
remove(tmp)