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" ))head(expr)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 high6 S01 control ACTB 3117 high
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.head()expr.group_by("level").agg(pl.len().alias("n")).sort("level")shape: (5, 5)┌───────────┬───────────┬───────┬────────┬────────┐│ sample_id ┆ condition ┆ gene ┆ counts ┆ level ││ --- ┆ --- ┆ --- ┆ --- ┆ --- ││ str ┆ str ┆ str ┆ i64 ┆ str │╞═══════════╪═══════════╪═══════╪════════╪════════╡│ S01 ┆ control ┆ TP53 ┆ 393 ┆ medium ││ S01 ┆ control ┆ EGFR ┆ 294 ┆ low ││ S01 ┆ control ┆ MYC ┆ 247 ┆ low ││ S01 ┆ control ┆ BRCA1 ┆ 195 ┆ low ││ S01 ┆ control ┆ GAPDH ┆ 3939 ┆ high │└───────────┴───────────┴───────┴────────┴────────┘
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.