Dates
Dates arrive as text and have to be parsed before you can do anything useful with them:
extract the month, compute how many days a sample waited to be processed, filter to a
batch. lubridate handles this in R, the .dt namespace in Polars. This page uses
sample_dates.csv, the collection and processing date of each sample.
Every code block runs in a container in the companion repository, and the two languages agree on the numbers.
Parse and extract
Section titled “Parse and extract”A string like 2026-01-05 is not a date until you parse it. Once parsed, the year and
month come out with a function call.
library(lubridate)
dates |> mutate( collection_date = ymd(collection_date), processing_date = ymd(processing_date), coll_year = year(collection_date), coll_month = month(collection_date, label = TRUE) ) sample_id collection_date processing_date coll_year coll_month1 S01 2026-01-05 2026-01-08 2026 Jan2 S02 2026-01-05 2026-01-09 2026 Jan3 S03 2026-01-06 2026-01-08 2026 Jan4 S04 2026-01-06 2026-01-12 2026 Jan5 S05 2026-02-10 2026-02-14 2026 Feb6 S06 2026-02-10 2026-02-15 2026 Febdates.with_columns( pl.col("collection_date").str.to_date(), pl.col("processing_date").str.to_date(),).with_columns( pl.col("collection_date").dt.year().alias("coll_year"), pl.col("collection_date").dt.month().alias("coll_month"),)shape: (5, 5)┌───────────┬─────────────────┬─────────────────┬───────────┬────────────┐│ sample_id ┆ collection_date ┆ processing_date ┆ coll_year ┆ coll_month ││ --- ┆ --- ┆ --- ┆ --- ┆ --- ││ str ┆ date ┆ date ┆ i32 ┆ i8 │╞═══════════╪═════════════════╪═════════════════╪═══════════╪════════════╡│ S01 ┆ 2026-01-05 ┆ 2026-01-08 ┆ 2026 ┆ 1 ││ S02 ┆ 2026-01-05 ┆ 2026-01-09 ┆ 2026 ┆ 1 ││ S03 ┆ 2026-01-06 ┆ 2026-01-08 ┆ 2026 ┆ 1 ││ S04 ┆ 2026-01-06 ┆ 2026-01-12 ┆ 2026 ┆ 1 ││ S05 ┆ 2026-02-10 ┆ 2026-02-14 ┆ 2026 ┆ 2 │└───────────┴─────────────────┴─────────────────┴───────────┴────────────┘lubridate prints the month as a label (Jan); Polars gives the number. Both are the same
month.
Date arithmetic
Section titled “Date arithmetic”Subtracting two dates gives a duration. Here, the days each sample waited between collection and processing, a useful QC signal.
dates |> mutate(days_to_process = as.integer(processing_date - collection_date)) sample_id collection_date processing_date days_to_process1 S01 2026-01-05 2026-01-08 32 S02 2026-01-05 2026-01-09 43 S03 2026-01-06 2026-01-08 24 S04 2026-01-06 2026-01-12 65 S05 2026-02-10 2026-02-14 46 S06 2026-02-10 2026-02-15 57 S07 2026-02-11 2026-02-13 28 S08 2026-02-11 2026-02-18 7dates.with_columns( (pl.col("processing_date") - pl.col("collection_date")) .dt.total_days().alias("days_to_process"))shape: (8, 4)┌───────────┬─────────────────┬─────────────────┬─────────────────┐│ sample_id ┆ collection_date ┆ processing_date ┆ days_to_process ││ --- ┆ --- ┆ --- ┆ --- ││ str ┆ date ┆ date ┆ i64 │╞═══════════╪═════════════════╪═════════════════╪═════════════════╡│ S01 ┆ 2026-01-05 ┆ 2026-01-08 ┆ 3 ││ S02 ┆ 2026-01-05 ┆ 2026-01-09 ┆ 4 ││ S03 ┆ 2026-01-06 ┆ 2026-01-08 ┆ 2 ││ S04 ┆ 2026-01-06 ┆ 2026-01-12 ┆ 6 ││ S05 ┆ 2026-02-10 ┆ 2026-02-14 ┆ 4 ││ S06 ┆ 2026-02-10 ┆ 2026-02-15 ┆ 5 ││ S07 ┆ 2026-02-11 ┆ 2026-02-13 ┆ 2 ││ S08 ┆ 2026-02-11 ┆ 2026-02-18 ┆ 7 │└───────────┴─────────────────┴─────────────────┴─────────────────┘Filter by a window
Section titled “Filter by a window”Parsed dates compare like numbers, so filtering to a batch is a normal filter. Four samples were collected in February.
dates |> filter(month(collection_date) == 2)dates.filter(pl.col("collection_date").dt.month() == 2)Both return S05 through S08. In this fixture the collection month lines up with the condition, controls in January and treated in February, exactly the kind of processing confound worth catching before it looks like biology.