Conditional Logic
Turning numbers into labels is a constant task: bin an expression value into low, medium,
or high; flag a gene as housekeeping or a target; classify a result as up, down, or
unchanged. This is multi-branch conditional logic. dplyr writes it as case_when, Polars
as chained when().then() clauses ending in otherwise. Both test conditions top to
bottom and take the first that matches.
Every code block runs in a container in the companion repository, and the two languages produce the same labels and the same group sizes.
Bin a value into categories
Section titled “Bin a value into categories”Classify each measurement by its count: below 300 is low, below 1000 is medium, the rest high. The order matters, since the first matching branch wins.
expr <- expr |> mutate(level = case_when( counts < 300 ~ "low", counts < 1000 ~ "medium", TRUE ~ "high" ))count(expr, level) sample_id condition gene counts level1 S01 control TP53 393 medium2 S01 control EGFR 294 low3 S01 control MYC 247 low4 S01 control BRCA1 195 low5 S01 control GAPDH 3939 high
# count(expr, level) level n1 high 162 low 153 medium 17expr = expr.with_columns( pl.when(pl.col("counts") < 300).then(pl.lit("low")) .when(pl.col("counts") < 1000).then(pl.lit("medium")) .otherwise(pl.lit("high")) .alias("level"))expr.group_by("level").agg(pl.len().alias("n")).sort("level")shape: (3, 2)┌────────┬─────┐│ level ┆ n ││ --- ┆ --- ││ str ┆ u32 │╞════════╪═════╡│ high ┆ 16 ││ low ┆ 15 ││ medium ┆ 17 │└────────┴─────┘The TRUE ~ branch in dplyr and .otherwise(...) in Polars are the catch-all: anything
not matched above falls through to it. Forgetting the catch-all leaves unmatched rows as
missing, a common source of stray NAs.
Flag by set membership
Section titled “Flag by set membership”The condition can be any test, including “is this value in a set”. Here, mark the two housekeeping genes and call everything else a target.
expr <- expr |> mutate(role = case_when( gene %in% c("GAPDH", "ACTB") ~ "housekeeping", TRUE ~ "target" ))count(expr, role) role n1 housekeeping 162 target 32expr = expr.with_columns( pl.when(pl.col("gene").is_in(["GAPDH", "ACTB"])).then(pl.lit("housekeeping")) .otherwise(pl.lit("target")) .alias("role"))expr.group_by("role").agg(pl.len().alias("n")).sort("role")shape: (2, 2)┌──────────────┬─────┐│ role ┆ n ││ --- ┆ --- ││ str ┆ u32 │╞══════════════╪═════╡│ housekeeping ┆ 16 ││ target ┆ 32 │└──────────────┴─────┘Sixteen housekeeping rows, two genes across eight samples, and thirty-two targets. Both languages agree. Next, Missing Values handles the gaps these tests silently create.