Logical operations

Comparisons, conditions, and filtering

Denis O’Meally

City of Hope

By the end

You should be able to:

  1. recognise that comparisons return TRUE, FALSE, or NA;
  2. identify missing measurements and combine logical conditions;
  3. use a logical condition to filter rows and inspect the selected IDs;
  4. use one logical value in a simple if/else decision;
  5. recognise how a working filter can become a small function; and
  6. follow a supplied loop that repeats the filter.

Mouse trial

Which treated mice gained at least 3 g?

We need three columns:

  • mouse_id
  • treatment
  • weight_change_g

Weight change

Which measurements are required?

mouse_trial |>
  select(
    mouse_id,
    treatment,
    baseline_weight_g,
    final_weight_g,
    weight_change_g
  ) |>
  slice(c(8, 12, 31, 37))
# A tibble: 4 × 5
  mouse_id treatment baseline_weight_g final_weight_g weight_change_g
  <chr>    <chr>                 <dbl>          <dbl>           <dbl>
1 M008     treatment              22.9           26.1            3.20
2 M012     control                NA             25             NA   
3 M031     treatment              25.6           NA             NA   
4 M037     treatment              28             34.1            6.1 

An unavailable baseline or final weight produces an unavailable change.

Missing values

Which records are missing a required weight?

trial_with_missing_check <- mouse_trial |>
  mutate(
    missing_required_weight = is.na(baseline_weight_g) |
      is.na(final_weight_g)
  )

trial_with_missing_check |>
  select(
    mouse_id,
    baseline_weight_g,
    final_weight_g,
    missing_required_weight
  ) |>
  slice(c(8, 12, 31, 37))
# A tibble: 4 × 4
  mouse_id baseline_weight_g final_weight_g missing_required_weight
  <chr>                <dbl>          <dbl> <lgl>                  
1 M008                  22.9           26.1 FALSE                  
2 M012                  NA             25   TRUE                   
3 M031                  25.6           NA   TRUE                   
4 M037                  28             34.1 FALSE                  

| means logical OR. The result is TRUE when either condition is true.

Filtering rows

How do we keep records with both weights?

complete_mouse_records <- trial_with_missing_check |>
  filter(!missing_required_weight)

complete_mouse_records |>
  summarise(rows = n())
# A tibble: 1 × 1
   rows
  <int>
1    46

! means logical NOT. It reverses TRUE and FALSE.

filter() retains TRUE rows. Rows with FALSE or NA are not retained.

Comparisons

What does “at least 3 g” return?

c(2.5, 3.0, 3.5, NA) >= 3
Weight change Result
2.5 g ?
3.0 g ?
3.5 g ?
unavailable ?

Comparisons

What does “at least 3 g” return?

c(2.5, 3.0, 3.5, NA) >= 3
[1] FALSE  TRUE  TRUE    NA
Weight change Result
2.5 g FALSE
3.0 g TRUE
3.5 g TRUE
unavailable NA

Comparisons

Does exactly 3.0 g meet the condition?

3 > 3
[1] FALSE
3 >= 3
[1] TRUE
3 < 3
[1] FALSE
3 <= 3
[1] TRUE

“At least 3.0 g” translates to >= 3.

Conditional execution

Which branch will run?

current_weight_change_g <- 3

if (current_weight_change_g >= 3) {
  "include"
} else {
  "exclude"
}

What will R return?

Conditional execution

Which branch will run?

current_weight_change_g <- 3

if (current_weight_change_g >= 3) {
  "include"
} else {
  "exclude"
}
[1] "include"

if evaluates one logical value and runs one branch.

Comparing text

Which complete mice received treatment?

mouse_conditions <- complete_mouse_records |>
  mutate(
    received_treatment = treatment == "treatment"
  )

mouse_conditions |>
  count(treatment, received_treatment)
# A tibble: 2 × 3
  treatment received_treatment     n
  <chr>     <lgl>              <int>
1 control   FALSE                 25
2 treatment TRUE                  21

== means equal to. != means not equal to.

Comparing numbers

Which complete mice gained at least 3 g?

mouse_conditions <- mouse_conditions |>
  mutate(
    gained_at_least_3_g = weight_change_g >= 3
  )

mouse_conditions |>
  count(gained_at_least_3_g)
# A tibble: 2 × 2
  gained_at_least_3_g     n
  <lgl>               <int>
1 FALSE                  41
2 TRUE                    5

The comparison is now attached to each complete mouse record.

Combining conditions

When is AND true?

mouse_id Received treatment? Gained at least 3 g? Both?
M001 TRUE FALSE ?
M008 TRUE TRUE ?
M048 FALSE FALSE ?
received_treatment & gained_at_least_3_g

Combining conditions

When is AND true?

mouse_id Received treatment? Gained at least 3 g? Both?
M001 TRUE FALSE FALSE
M008 TRUE TRUE TRUE
M048 FALSE FALSE FALSE
received_treatment & gained_at_least_3_g

& means logical AND.

Combining conditions

How many complete records meet both?

mouse_conditions <- mouse_conditions |>
  mutate(
    meets_both_conditions = received_treatment &
      gained_at_least_3_g
  )

mouse_conditions |>
  count(meets_both_conditions)
# A tibble: 2 × 2
  meets_both_conditions     n
  <lgl>                 <int>
1 FALSE                    41
2 TRUE                      5

Five complete records meet both conditions.

Filtering rows

Which mice meet both conditions?

treated_mice_meeting_gain_threshold <- mouse_conditions |>
  filter(meets_both_conditions) |>
  select(mouse_id, treatment, weight_change_g)

treated_mice_meeting_gain_threshold
# A tibble: 5 × 3
  mouse_id treatment weight_change_g
  <chr>    <chr>               <dbl>
1 M008     treatment            3.20
2 M037     treatment            6.1 
3 M041     treatment            3.5 
4 M042     treatment            3.6 
5 M043     treatment            3.6 

The output contains five rows and five mouse IDs.

Functions

Which operation do we want to reuse?

mouse_trial |>
  dplyr::filter(
    treatment == "treatment",
    weight_change_g >= 3
  )

This is the filter we have already used.

dplyr::filter() explicitly identifies the tidyverse function.

Functions

How do we give the filter a reusable name?

filter_treated_mice_by_weight_gain <- function(
  mouse_data,
  minimum_weight_gain_g
) {
  mouse_data |>
    dplyr::filter(
      treatment == "treatment",
      weight_change_g >= minimum_weight_gain_g
    )
}

Functions

Does it reproduce the five-row result?

filter_treated_mice_by_weight_gain(
  mouse_trial,
  minimum_weight_gain_g = 3
) |>
  select(mouse_id, treatment, weight_change_g)
# A tibble: 5 × 3
  mouse_id treatment weight_change_g
  <chr>    <chr>               <dbl>
1 M008     treatment            3.20
2 M037     treatment            6.1 
3 M041     treatment            3.5 
4 M042     treatment            3.6 
5 M043     treatment            3.6 

The call reproduces the five-row result.

Iteration

What happens when the filter is repeated?

for (minimum_weight_gain_g in c(2, 3, 4)) {
  selected_mice <- filter_treated_mice_by_weight_gain(
    mouse_trial,
    minimum_weight_gain_g
  )

  print(nrow(selected_mice))
}

How many times will the body run? What should happen to the row count?

Iteration

What happens when the filter is repeated?

for (minimum_weight_gain_g in c(2, 3, 4)) {
  selected_mice <- filter_treated_mice_by_weight_gain(
    mouse_trial,
    minimum_weight_gain_g
  )

  print(nrow(selected_mice))
}
[1] 21
[1] 5
[1] 1

Hands-on exercise

How do you open the exercise?

In the BIOSCI504 course Project, run:

BIOSCI504::copy_template("explicit-analytical-rules")

Then open:

exercises/explicit-analytical-rules/analysis.qmd

Hands-on exercise

What will you do?

You will:

  1. inspect the relevant mouse-trial columns;
  2. create and inspect logical conditions;
  3. filter the treated mice meeting the gain threshold;
  4. complete the small filtering function; and
  5. run the supplied loop at three thresholds.

Restart R or RStudio, render again, and inspect analysis.html.

Questions

Further reading