library(tidyverse)Logical Operations and Conditionals
Big idea: Most PMx decisions are expressed as logical rules applied to entire vectors — not one value at a time.
Learning Objectives
By the end of this lesson, you will be able to:
- Use comparison operators (
==,!=,<,>,<=,>=). - Combine logical conditions with
&and|. - Use
%in%for multi-value checks. - Understand how
NAbehaves in logical expressions. - Use
ifelse()for vectorized conditional logic.
Setup
Logical Vectors
A logical vector contains TRUE, FALSE, or NA.
dv <- c(1.2, 3.5, 2.8)
dv > 2[1] FALSE TRUE TRUE
Logical vectors are the backbone of:
- filtering
- QC flags
- conditional transformations
Comparison Operators
dv == 2.8[1] FALSE FALSE TRUE
dv != 2.8[1] TRUE TRUE FALSE
dv >= 2[1] FALSE TRUE TRUE
dv < 3[1] TRUE FALSE TRUE
These comparisons are vectorized.
Combining Conditions
AND (&)
dv > 2 & dv < 3[1] FALSE FALSE TRUE
OR (|)
dv < 2 | dv > 3[1] TRUE TRUE FALSE
Use & and | for vectors.
Avoid && and || (they only check the first value).
%in% for Membership Checks
ids <- c(101, 102, 103, 104)
ids %in% c(101, 103)[1] TRUE FALSE TRUE FALSE
This is safer and clearer than chaining ==.
PMx Example: Flagging Observations
pk <- tibble(
ID = c(1, 1, 1, 2, 2, 2),
TIME = c(0.5, 1, 2, 0.5, 1, 2),
DV = c(2.1, 3.8, 0.2, 1.6, 2.9, 0.1)
)
pk# A tibble: 6 × 3
ID TIME DV
<dbl> <dbl> <dbl>
1 1 0.5 2.1
2 1 1 3.8
3 1 2 0.2
4 2 0.5 1.6
5 2 1 2.9
6 2 2 0.1
Flag low concentrations:
pk$low_conc <- pk$DV < 0.5
pk# A tibble: 6 × 4
ID TIME DV low_conc
<dbl> <dbl> <dbl> <lgl>
1 1 0.5 2.1 FALSE
2 1 1 3.8 FALSE
3 1 2 0.2 TRUE
4 2 0.5 1.6 FALSE
5 2 1 2.9 FALSE
6 2 2 0.1 TRUE
In a data frame or tibble, pk$DV means:
“Take the DV column from pk and return it as a vector.”
The result is not a data frame — it is a single vector.
Missing Values in Logic (NA)
dv_na <- c(1.2, NA, 2.8)
dv_na > 2[1] FALSE NA TRUE
NA propagates.
Safer logic:
!is.na(dv_na) & dv_na > 2[1] FALSE FALSE TRUE
Vectorized Conditionals with ifelse()
pk$BLQ <- ifelse(pk$DV < 0.5, TRUE, FALSE)
pk# A tibble: 6 × 5
ID TIME DV low_conc BLQ
<dbl> <dbl> <dbl> <lgl> <lgl>
1 1 0.5 2.1 FALSE FALSE
2 1 1 3.8 FALSE FALSE
3 1 2 0.2 TRUE TRUE
4 2 0.5 1.6 FALSE FALSE
5 2 1 2.9 FALSE FALSE
6 2 2 0.1 TRUE TRUE
Better (handles NA explicitly):
pk$BLQ <- ifelse(!is.na(pk$DV) & pk$DV < 0.5, TRUE, FALSE)Nested Logic (Keep It Simple)
Avoid deep nesting early on.
pk$flag <- ifelse(
pk$DV < 0.5, "BLQ",
ifelse(pk$DV > 3, "HIGH", "OK")
)
pk# A tibble: 6 × 6
ID TIME DV low_conc BLQ flag
<dbl> <dbl> <dbl> <lgl> <lgl> <chr>
1 1 0.5 2.1 FALSE FALSE OK
2 1 1 3.8 FALSE FALSE HIGH
3 1 2 0.2 TRUE TRUE BLQ
4 2 0.5 1.6 FALSE FALSE OK
5 2 1 2.9 FALSE FALSE OK
6 2 2 0.1 TRUE TRUE BLQ
If logic becomes hard to read, split it into steps or write a function.
PMx Rule Example: Dose vs Observation
ev <- tibble(
EVID = c(1, 0, 0, 1, 0),
AMT = c(100, NA, NA, 80, NA),
DV = c(NA, 2.1, 3.8, NA, 1.6)
)
ev$logic_ok <- (ev$EVID == 1 & !is.na(ev$AMT) & is.na(ev$DV)) |
(ev$EVID == 0 & is.na(ev$AMT) & !is.na(ev$DV))
ev# A tibble: 5 × 4
EVID AMT DV logic_ok
<dbl> <dbl> <dbl> <lgl>
1 1 100 NA TRUE
2 0 NA 2.1 TRUE
3 0 NA 3.8 TRUE
4 1 80 NA TRUE
5 0 NA 1.6 TRUE
This kind of rule shows up everywhere in PMx QC.
Strategies
- Think in terms of vectors, not single values.
- Handle
NAexplicitly. - Use
%in%for membership checks. - Keep logic readable; split into steps when needed.
- Turn repeated logic into a function.
Practice Problems
- Create a logical vector identifying
DV > 2. - Combine conditions to identify
DV > 2andTIME <= 1. - Use
%in%to flag IDs in a given set. - Create a BLQ flag using
ifelse(). - Write one PMx-style logical rule and explain it.
pk$DV > 2[1] TRUE TRUE FALSE FALSE TRUE FALSE
pk$DV > 2 & pk$TIME <= 1[1] TRUE TRUE FALSE FALSE TRUE FALSE
pk$ID %in% c(1, 2)[1] TRUE TRUE TRUE TRUE TRUE TRUE
pk$BLQ <- ifelse(!is.na(pk$DV) & pk$DV < 0.5, TRUE, FALSE)
ev$logic_ok[1] TRUE TRUE TRUE TRUE TRUE
Summary
You now know how to:
- express PMx rules as logical vectors
- combine conditions safely
- handle missing values explicitly
- use
ifelse()for vectorized conditionals
These skills underpin QC checks, data wrangling, and modeling logic.
- Use
&and|for vectors. - Always think about
NA. - Vectorized logic beats loops in most PMx cases.
- If logic feels messy, refactor it.