library(tidyverse)
library(tidystata)
gss24 <- readRDS("gss24.rds")13 Group variables
A questionnaire often asks several questions with the same response format. We may need to inspect the same feature of each answer. Writing the operation once reduces repetition, but choosing the columns remains a research decision: similar names alone do not establish that questions belong together.
By the end of this chapter, you will be able to:
- Apply one operation to a selected block of columns with
across(). - Keep the source columns and give the results informative names.
- Select observations meeting a condition in any or all selected columns.
- Distinguish a group of variables from a group of observations.
We use mutate() from Chapter 12, column selection from Chapter 3, and the familiar is.na() missingness test. Numeric recoding and factor construction are not prerequisites. Prepare the GSS extract following Appendix A, then restore it:
13.1 Begin with separate operations
Which confidence answers are missing for each respondent? Select confidence in major companies, education, and medicine. Each row continues to represent one respondent:
confidence <- gss24 |>
select(id, conbus, coneduc, conmedic)
head(confidence)# A tibble: 6 × 4
id conbus coneduc conmedic
<int> <fct> <fct> <fct>
1 1 a great deal a great deal a great deal
2 2 only some only some a great deal
3 3 only some a great deal a great deal
4 4 NA NA NA
5 5 only some hardly any only some
6 6 hardly any only some only some
First create one missingness flag per item using familiar commands. A flag is TRUE when the answer is missing and FALSE when it is recorded:
separate_flags <- confidence |>
mutate(
conbus_missing = is.na(conbus),
coneduc_missing = is.na(coneduc),
conmedic_missing = is.na(conmedic)
)
head(separate_flags)# A tibble: 6 × 7
id conbus coneduc conmedic conbus_missing coneduc_missing conmedic_missing
<int> <fct> <fct> <fct> <lgl> <lgl> <lgl>
1 1 a grea… a grea… a great… FALSE FALSE FALSE
2 2 only s… only s… a great… FALSE FALSE FALSE
3 3 only s… a grea… a great… FALSE FALSE FALSE
4 4 NA NA NA TRUE TRUE TRUE
5 5 only s… hardly… only so… FALSE FALSE FALSE
6 6 hardly… only s… only so… FALSE FALSE FALSE
The source answers remain beside three new logical columns. A flag describes whether an answer is available; it does not say why it is missing or whether the respondent has confidence in that institution. Some GSS items were not asked on every questionnaire.
13.2 Apply one operation across columns
Inside mutate(), across() selects columns and applies the supplied function to each one. Its first argument uses the same column-selection rules as select(). The range conbus:conmedic contains exactly the three adjacent items selected above. As with the predicate supplied to where() in Section 4.1, pass the function itself without parentheses. Here across() calls is.na separately on each column and returns a missingness flag for every entry. Unlike column selection, this transforms the selected columns rather than deciding which complete columns to retain.
Before running the block operation, choose names for the results. The .names argument supplies a naming pattern. In "{.col}_missing", {.col} stands for each source column name and _missing adds a descriptive suffix. This creates new columns beside the source answers. Without this new naming pattern, mutate(across()) would replace the selected answers with flags:
confidence_flags <- confidence |>
mutate(across(
conbus:conmedic,
is.na,
.names = "{.col}_missing"
))
head(confidence_flags)# A tibble: 6 × 7
id conbus coneduc conmedic conbus_missing coneduc_missing conmedic_missing
<int> <fct> <fct> <fct> <lgl> <lgl> <lgl>
1 1 a grea… a grea… a great… FALSE FALSE FALSE
2 2 only s… only s… a great… FALSE FALSE FALSE
3 3 only s… a grea… a great… FALSE FALSE FALSE
4 4 NA NA NA TRUE TRUE TRUE
5 5 only s… hardly… only so… FALSE FALSE FALSE
6 6 hardly… only s… only so… FALSE FALSE FALSE
The three added columns have the same meanings as the separate flags above. Their sources remain available alongside them. A six-row preview cannot show that every entry agrees. identical() receives two objects and returns one logical result: TRUE requires agreement of the complete objects, including values, row and column order, types, and attributes such as labels.
identical(confidence_flags, separate_flags)[1] TRUE
The check returns TRUE, so both methods produced the same complete table. A FALSE result would require checking more than the first few printed values.
There are still 3986 respondents. We grouped variables for an operation; we did not collect respondents into regions or reduce them to group summaries. Grouping observations is developed in Chapter 19.
13.2.1 Supply an unnamed function
Sometimes an operation needs an expression rather than a function name alone. \(x) defines a function without assigning it a name. The name x represents the input that the function will receive; the expression after it gives the returned result. Begin with the familiar missingness operation:
confidence_flags_function <- confidence |>
mutate(across(
conbus:conmedic,
\(x) is.na(x),
.names = "{.col}_missing"
))
identical(confidence_flags_function, confidence_flags)[1] TRUE
For each selected column, across() supplies that entire column as x. is.na(x) returns one flag per entry, so this function has exactly the same result as supplying is.na directly. The comparison is TRUE. The function argument and its input are local to this expression; writing x here does not refer to a column called x. Chapter 15 later uses the same syntax for a two-step scoring operation.
13.3 Count recorded answers
How many of the three confidence answers are recorded for each respondent? Tidystata’s row_non_miss() counts non-missing values across its supplied columns within each row. Name the three items explicitly so that id cannot contribute to the count:
confidence_coverage <- confidence |>
mutate(n_answered = row_non_miss(conbus, coneduc, conmedic))
tab(confidence_coverage, n_answered)# A tibble: 4 × 6
n_answered n percent cum valid valid_cum
<int> <int> <dbl> <dbl> <dbl> <dbl>
1 0 1351 33.9 33.9 33.9 33.9
2 1 10 0.251 34.1 0.251 34.1
3 2 73 1.83 36.0 1.83 36.0
4 3 2552 64.0 100 64.0 100
n_answered ranges from 0 through 3. Each frequency counts respondents with that many available answers; its percentage denominator includes all 3986 respondents because every row receives a count. Zero means none of these answers is recorded. It is not a confidence score of zero and does not identify refusal. A count of 3 marks complete responses. The item values remain unchanged; counting their availability does not require converting the categories to scores.
13.4 Conditions across several columns
To inspect respondents with at least one missing confidence answer, use if_any() inside filter(). It applies a condition to each selected column, then returns one logical result per row. With is.na, the result is TRUE if any selected answer is missing:
any_missing <- confidence |>
filter(if_any(conbus:conmedic, is.na))
head(any_missing)# A tibble: 6 × 4
id conbus coneduc conmedic
<int> <fct> <fct> <fct>
1 4 NA NA NA
2 10 NA NA NA
3 15 NA NA NA
4 16 NA NA NA
5 18 NA NA NA
6 20 NA NA NA
nrow(any_missing)[1] 1434
if_all() instead requires every selected column to meet the condition:
all_missing <- confidence |>
filter(if_all(conbus:conmedic, is.na))
head(all_missing)# A tibble: 6 × 4
id conbus coneduc conmedic
<int> <fct> <fct> <fct>
1 4 NA NA NA
2 10 NA NA NA
3 15 NA NA NA
4 16 NA NA NA
5 18 NA NA NA
6 20 NA NA NA
nrow(all_missing)[1] 1351
The first selection contains 1434 respondents; the second contains 1351. Every respondent in the second also belongs to the first. Neither result establishes refusal: the prepared extract records unavailable answers as NA without retaining every source reason.
To select complete responses, negate the result of if_any() with !:
complete_confidence <- confidence |>
filter(!if_any(conbus:conmedic, is.na))
nrow(complete_confidence)[1] 2552
nrow(complete_confidence) + nrow(any_missing)[1] 3986
The complete and any-missing selections partition all 3986 respondents. This is a check of coverage, not an instruction to discard incomplete records for every analysis. Later, Section 15.4 explicitly chooses a complete-response rule for a teaching score.
13.5 Review
across()applies one function to selected columns inside a table operation..namesgives derived columns informative names while retaining their sources.- An unnamed function supplies an expression for each selected column.
row_non_miss()counts available answers in each row; exclude identifiers and unrelated columns.if_any()andif_all()combine column conditions into one result per observation.- Check a block operation against separate operations before extending it.
- A block of variables differs from groups of observations; missingness flags do not explain missingness.
Chapter 14 next develops arithmetic and numeric recoding. Chapter 15 later applies a shared scoring rule to a questionnaire battery.
13.6 Exercises
- Create missingness flags for the three confidence items with separate operations and with
across(). Verify exact agreement and confirm that the original answers remain. - Select respondents with any missing answer, all three missing, and no missing answer. Explain the difference between the selections and check that the first and third cover the entire source. Count recorded answers per row, excluding
id, and tabulate these counts. Which count identifies each of the all-missing and complete selections? Does zero mean zero confidence? - A researcher writes
mutate(across(conbus:conmedic, is.na))and then treats the results as confidence responses. Diagnose the problem and repair the command. Would usingstarts_with("con")on the full GSS extract select the same three items?
Worked answers
Load the packages and saved extract in a fresh session:
library(tidyverse)
library(tidystata)
answer_gss24 <- readRDS("gss24.rds")answer_items <- answer_gss24 |>
select(id, conbus, coneduc, conmedic)
answer_separate <- answer_items |>
mutate(
conbus_missing = is.na(conbus),
coneduc_missing = is.na(coneduc),
conmedic_missing = is.na(conmedic)
)
answer_block <- answer_items |>
mutate(across(conbus:conmedic, \(x) is.na(x), .names = "{.col}_missing"))
identical(answer_separate, answer_block)[1] TRUE
identical(select(answer_block, id, conbus, coneduc, conmedic), answer_items)[1] TRUE
Both checks return TRUE, covering all rows rather than just the displayed preview. The original factors and their missing answers remain intact. The unnamed function receives each item column as x and returns its missingness flags, so it agrees with the named is.na operation.
library(tidyverse)
library(tidystata)
answer_gss24 <- readRDS("gss24.rds")answer_items <- answer_gss24 |>
select(id, conbus, coneduc, conmedic)
answer_any <- answer_items |>
filter(if_any(conbus:conmedic, is.na))
answer_all <- answer_items |>
filter(if_all(conbus:conmedic, is.na))
answer_complete <- answer_items |>
filter(!if_any(conbus:conmedic, is.na))
nrow(answer_any)[1] 1434
nrow(answer_all)[1] 1351
nrow(answer_complete)[1] 2552
nrow(answer_any) + nrow(answer_complete) == nrow(answer_items)[1] TRUE
answer_coverage <- answer_items |>
mutate(n_answered = row_non_miss(conbus, coneduc, conmedic))
tab(answer_coverage, n_answered)# A tibble: 4 × 6
n_answered n percent cum valid valid_cum
<int> <int> <dbl> <dbl> <dbl> <dbl>
1 0 1351 33.9 33.9 33.9 33.9
2 1 10 0.251 34.1 0.251 34.1
3 2 73 1.83 36.0 1.83 36.0
4 3 2552 64.0 100 64.0 100
The final check is TRUE. All-missing records are part of the any-missing selection, so adding those two counts would double-count some respondents. These counts describe answer availability. n_answered == 0 identifies all three missing; n_answered == 3 identifies complete responses. Counts below 3 identify the any-missing selection. Zero available answers supplies no information about a person’s confidence level.
library(tidyverse)
library(tidystata)
answer_gss24 <- readRDS("gss24.rds")answer_flags <- answer_gss24 |>
mutate(across(conbus:conmedic, is.na, .names = "{.col}_missing"))
answer_gss24 |>
select(starts_with("con")) |>
ds()[1] "conbus" "coneduc" "conmedic" "conpress" "consci" "conlegis"
identical(answer_flags$conbus, answer_gss24$conbus)[1] TRUE
New names keep the original responses. The prefix selects six confidence items in the full extract, including confidence in the press, science, and the legislature. A convenient name pattern is not a substitute for specifying the intended battery.