Skip to content

String Ops

Strings are where bioinformatics data is messiest. Gene symbols, sample IDs, chromosome labels: all text, all inconsistent across files. stringr in R and the .str namespace in Polars cover the same operations. The one that saves the most time is the last: cleaning sample names so a join stops silently failing.

Every code block runs in a container in the companion repository, and the two languages agree on the results.

Measure a string, pull a number out of it, and test for a pattern. Here, the chromosome number is extracted from labels like chr17 by removing the chr prefix.

library(stringr)
annotation |>
mutate(
gene_len = str_length(gene),
chrom_num = as.integer(str_remove(chromosome, "chr")),
is_chr17 = str_detect(chromosome, "chr17")
)
gene pathway chromosome gene_len chrom_num is_chr17
1 TP53 Apoptosis chr17 4 17 TRUE
2 EGFR Growth signaling chr7 4 7 FALSE
3 MYC Growth signaling chr8 3 8 FALSE
4 BRCA1 DNA repair chr17 5 17 TRUE
5 GAPDH Housekeeping chr12 5 12 FALSE
6 ACTB Housekeeping chr7 4 7 FALSE
7 KRAS Growth signaling chr12 4 12 FALSE

This is the one that bites everyone. The same sample arrives labelled s01, S_02, S-03 , s04: mixed case, a hyphen where another file used an underscore, a stray leading or trailing space. None of these match S01 on a join, and nothing warns you. The fix is three steps: strip whitespace, upcase, remove the separators.

messy <- read.csv("../fixtures/messy_samples.csv")
messy |>
mutate(sample_id = str_replace_all(str_to_upper(str_trim(sample_label)), "[-_ ]", ""))
sample_label rin sample_id
1 s01 9.1 S01
2 S_02 8.7 S02
3 S-03 9.4 S03
4 s04 8.2 S04
5 S05 9.0 S05
6 S 06 7.9 S06
7 s-07 8.8 S07
8 S08 9.3 S08

The payoff is concrete. Join the sample labels to a per-sample summary before cleaning, and only the one already-canonical label (S05) matches. After cleaning, all eight do.

raw_match <- inner_join(messy, sample_totals, by = c("sample_label" = "sample_id"))
clean_match <- inner_join(messy, sample_totals, by = "sample_id")
raw labels matched: 1 of 8
cleaned labels matched: 8 of 8

Seven samples silently dropped, with no error, until you clean the key. When a join returns fewer rows than you expect, the labels are the first thing to check.