What this quiz covers
This quiz focuses on Handling Missing Values In Reshaping, giving you a quick way to practice the rules, question types, and explanations that matter most for R Programming.
Character survey responses use '-' as a missing-value marker: d <- tibble(id = c(1, 2), q1 = c('-', 'no'), q2 = c('yes', '-')). The desired long result should contain only the two substantive responses, yes and no.
Which pivot_longer() call converts the marker to a real missing value and removes it during the reshape?
pivot_longer(d, q1:q2, names_to = 'question', names_transform = list(question = ~ na_if(.x, '-')), values_drop_na = TRUE)pivot_longer(d, q1:q2, values_to = 'response', values_ptypes = list(response = character()), values_drop_na = TRUE)pivot_longer(d, q1:q2, values_to = 'response', values_transform = list(response = ~ na_if(.x, '-')), values_drop_na = TRUE)pivot_longer(d, q1:q2, values_to = 'response', values_drop_na = TRUE) |> replace_na(list(response = '-'))R Programming Quiz
Practice Handling Missing Values In Reshaping in R Programming with focused quiz questions that help you check what you know, review explanations, and build confidence with test-style prompts.
This quiz focuses on Handling Missing Values In Reshaping, giving you a quick way to practice the rules, question types, and explanations that matter most for R Programming.
Try each quiz question before looking at the correct answer. Use the explanations to review missed ideas, then come back to similar questions until the pattern feels familiar.
Character survey responses use '-' as a missing-value marker: d <- tibble(id = c(1, 2), q1 = c('-', 'no'), q2 = c('yes', '-')). The desired long result should contain only the two substantive responses, yes and no.
Which pivot_longer() call converts the marker to a real missing value and removes it during the reshape?
pivot_longer(d, q1:q2, names_to = 'question', names_transform = list(question = ~ na_if(.x, '-')), values_drop_na = TRUE)pivot_longer(d, q1:q2, values_to = 'response', values_ptypes = list(response = character()), values_drop_na = TRUE)pivot_longer(d, q1:q2, values_to = 'response', values_transform = list(response = ~ na_if(.x, '-')), values_drop_na = TRUE) (correct answer)pivot_longer(d, q1:q2, values_to = 'response', values_drop_na = TRUE) |> replace_na(list(response = '-'))'-', you need two things to happen during pivot_longer(): convert the marker string into a true NA, then drop those NA rows. The key is knowing which argument controls which part of the reshaped tibble.
The values_transform argument applies a transformation function to the values as they're collected into the long column. In option C, values_transform = list(response = ~ na_if(.x, '-')) tells pivot_longer() to run each value through na_if(), replacing any '-' with NA on the fly. Then values_drop_na = TRUE removes those newly created NA rows, leaving only "yes" and "no". This is exactly the right tool for the job — C is correct.
Option A misapplies the transformation to names_transform, which processes the column names (q1, q2), not the cell values. Your marker '-' lives in the values, not the names, so this does nothing useful to clean the responses.
Option B uses values_ptypes, which enforces a type constraint on values but performs no transformation. It won't replace '-' with NA — it just checks that the values are character type, which they already are. The markers survive untouched.
Option D has the logic backwards: replace_na(list(response = '-')) fills NA values with '-', the opposite of what you want. It converts missing values into the marker string rather than the other way around.
A useful rule of thumb: in pivot_longer(), names_* arguments always operate on column names, values_* arguments always operate on cell values — keep that distinction clear and you'll avoid most of these traps.Consider the following R code: wide <- tibble(id = c('x', 'y'), m1 = c(NA, 3), m2 = c(2, NA)).
Which rows are produced by wide |> pivot_longer(m1:m2, names_to = 'measure', values_to = 'value', values_drop_na = TRUE)?
x, m2, 2 and y, m1, 3 (correct answer)x, m1, NA and y, m2, NAvalue is NApivot_longer() with values_drop_na = TRUE, your job is to mentally "unpivot" the data first, then filter out any rows where the value column would be NA.
Start with the wide tibble: row x has m1 = NA and m2 = 2; row y has m1 = 3 and m2 = NA. Without any filtering, pivot_longer(m1:m2) would produce four rows — one for each id-measure combination: (x, m1, NA), (x, m2, 2), (y, m1, 3), and (y, m2, NA). The values_drop_na = TRUE argument then removes any row where value is NA, eliminating (x, m1, NA) and (y, m2, NA). That leaves exactly two rows: x, m2, 2 and y, m1, 3 — confirming A is correct.
B is wrong because it describes the two rows that get dropped, not kept — it's the exact opposite of what values_drop_na = TRUE does. C is wrong because it describes the intermediate result before the NA-dropping step, ignoring the effect of values_drop_na = TRUE entirely. D reflects a fundamental misunderstanding: values_drop_na operates row-by-row on the long format output, not on the original wide rows — it doesn't discard an entire original row just because one of its measure columns is NA.
A good study habit: mentally execute pivot_longer() in two steps — first expand to long format, then apply any filtering arguments like values_drop_na. Separating these steps prevents you from conflating the original structure with the transformed output.A data set is defined as d <- tibble(id = c('A', 'A', NA), visit = c('pre', 'post', 'pre'), score = c(1, 2, 3)).
What happens when d is processed with pivot_wider(names_from = visit, values_from = score, values_fill = 0)?
id row is dropped, leaving one row for Aid becomes an identifier row with pre = 3 and post = 0 (correct answer)id is merged with A, producing duplicate values in preid becomes an identifier row with pre = 3 and post = NApivot_wider(), it helps to think of it in two steps: first, R identifies the id columns (everything not named in names_from or values_from), then it spreads the remaining columns into a wider format. Crucially, NA values in an id column are treated as legitimate identifiers — not as missing data to be discarded.
In your tibble, id is the only id column. R sees three distinct id values: "A", "A", and NA. The two "A" rows get pivoted into a single row with pre = 1 and post = 2. The NA row has only a pre entry (score = 3), so after pivoting, its post value would normally be NA — but because you specified values_fill = 0, that missing cell is filled with 0. This gives you the row NA | pre = 3 | post = 0, confirming B is correct.
A is wrong because pivot_wider() does not drop NA ids by default. R treats NA like any other group key, so it produces a row for it rather than ignoring it. C is wrong because NA is never merged with "A" — they are distinct id values in R's grouping logic, so no duplication occurs. D is tempting but misses the effect of values_fill = 0: without that argument, post would indeed be NA, but the argument explicitly replaces implicit missing values with 0.
A good rule of thumb: values_fill only fills implicitly missing combinations — cells that don't exist in the data. Always trace what id groups exist before assuming any row will be dropped or merged.Suppose wide <- tibble(id = c(1, 2), x = c(NA, 3), y = c(2, 4)). The goal is to produce long data containing all three observed measurements, without creating a value to replace the missing x measurement.
Which pipeline achieves the goal?
wide |> drop_na() |> pivot_longer(x:y, names_to = 'variable', values_to = 'value')wide |> pivot_longer(x:y, names_to = 'variable', values_to = 'value') |> drop_na(id)wide |> replace_na(list(x = 0, y = 0)) |> pivot_longer(x:y, names_to = 'variable', values_to = 'value')wide |> pivot_longer(x:y, names_to = 'variable', values_to = 'value', values_drop_na = TRUE) (correct answer)NA values, and how? Here, you want to keep all three real measurements (id=1's y=2, id=2's x=3, and id=2's y=4) while dropping only the structurally missing x for id=1 — not fabricating a replacement.
pivot_longer() has a built-in argument values_drop_na = TRUE that drops rows where the pivoted value is NA during the pivot itself. This is exactly what you need: after pivoting, the row corresponding to id=1's x would have value = NA, and setting values_drop_na = TRUE removes it cleanly, leaving you with three rows. That makes D the correct pipeline.
A uses drop_na() before pivoting, which removes the entire row for id=1 (both x and y) because x is NA. You lose id=1's valid y=2 measurement — dropping four values instead of one.
B pipes into drop_na(id), which drops rows where id is NA. Since id is never NA in this data, nothing gets dropped, and the resulting long data still contains the unwanted NA row for id=1's x.
C uses replace_na() to substitute 0 for the missing x. This invents a fake measurement (x=0 for id=1), which directly violates the goal of not creating a replacement value.
As a study tip, remember that pivot_longer() arguments like values_drop_na let you handle NAs surgically during reshaping — always check pivot function arguments before resorting to separate pre- or post-processing steps.Suppose wide <- tibble(id = c(1, 2), a = c(NA, 4), b = c(8, NA)). It is transformed with long <- wide |> pivot_longer(a:b, values_drop_na = TRUE) and then with result <- long |> pivot_wider(names_from = name, values_from = value, values_fill = 0).
How does result differ from the original wide data?
wide, because widening restores the original explicit missing valuesa and b become list-columns because observations were discardedpivot_longer into pivot_wider, it's tempting to assume the round-trip restores your original data — but that's only true if no information was lost along the way. Here, information was lost, and that's the core concept being tested.
Start by tracing what happens step by step. The original wide has id=1 with a=NA, b=8 and id=2 with a=4, b=NA. When pivot_longer runs with values_drop_na = TRUE, it silently discards any row where the value is NA. So id=1, a=NA and id=2, b=NA are both dropped. The resulting long has only two rows: id=1, b=8 and id=2, a=4. When pivot_wider then reconstructs the wide format, it has no data for id=1's a column or id=2's b column — so values_fill = 0 fills those gaps with 0 instead of NA. The NAs have been permanently replaced by zeros, confirming B is correct.
A is wrong because widening cannot restore what was already discarded during lengthening — the missing rows are gone, not stored anywhere. C is wrong because both id values survive; neither identifier is removed, since every id still has at least one non-missing observation in the long format. D is wrong because pivot_wider produces regular scalar columns here, not list-columns; list-columns only arise when multiple values map to the same id-name combination.
A useful rule of thumb: any pivot_longer option that filters rows (like values_drop_na) makes the transformation non-invertible — widening back will silently substitute fill values for what was dropped.A long data set is created by d <- tibble(id = c(1, 1, 2), day = c('Mon', 'Tue', 'Mon'), score = c(NA, 5, 7)).
After running d |> pivot_wider(names_from = day, values_from = score, values_fill = 0), which description is correct?
id = 1, Mon is 0; for id = 2, Tue is 0id = 1, Mon is NA; for id = 2, Tue is 0 (correct answer)id = 1, Mon is NA; for id = 2, Tue is NAid = 1, Mon is 0; for id = 2, Tue is NApivot_wider(), you need to distinguish between two very different situations: a value that is explicitly NA in your source data, versus a value that is implicitly missing because no row exists for that combination.
Here's the data: id = 1 has entries for both Mon (score = NA) and Tue (score = 5). id = 2 only has an entry for Mon (score = 7) — there is no row at all for id = 2, Tue.
The values_fill = 0 argument fills in implicitly missing combinations — cells that don't exist in the long data. It does not overwrite values that are explicitly NA in the source. So when id = 1's Mon score is NA, that NA is a real, recorded value; values_fill leaves it alone. Meanwhile, id = 2 has no Tue row at all, so that gap gets filled with 0. This makes B correct: id = 1 Mon is NA, id = 2 Tue is 0.
A is wrong because it claims id = 1 Mon becomes 0 — but values_fill never overwrites an explicit NA. C is wrong because it treats id = 2's missing Tue as NA, ignoring that values_fill = 0 fills implicit gaps. D inverts the logic entirely, wrongly replacing the explicit NA with 0 while leaving the implicit gap as NA.
A helpful mental rule: think of values_fill as a default for absent rows, not a replacement for NA values. If you want to replace NAs too, you'd need an additional replace_na() or mutate() step after pivoting.A one-row data set contains id = 1, temp_w1 = NA, humid_w1 = 40, temp_w2 = NA, and humid_w2 = NA. It is reshaped with pivot_longer(cols = -id, names_to = c('.value', 'week'), names_sep = '_', values_drop_na = TRUE).
What remains after the reshape?
w1, with temp = NA and humid = 40 (correct answer)w1, with temp = 0 and humid = 40temp value is missing in bothpivot_longer and the .value sentinel in names_to, you need to understand two things happening simultaneously: how the data is restructured, and what values_drop_na = TRUE actually drops.
The .value + names_sep combination splits column names like temp_w1 into a value column (temp) and a grouping variable (week). So your original four columns produce two reshaped rows — one for w1 (temp = NA, humid = 40) and one for w2 (temp = NA, humid = NA). The critical detail is what values_drop_na = TRUE eliminates: it drops rows where ALL pivoted value columns are NA. For w1, at least one value (humid = 40) is non-missing, so that row survives. For w2, both temp and humid are NA, so that row is dropped entirely. This confirms A — only w1 remains, and its temp stays as NA because only fully-missing rows are removed.
Choice B is wrong because pivot_longer never substitutes 0 for NA — that's a fabricated behavior with no basis in how R handles missing values. Choice C is wrong because having two value columns per week doesn't protect a row from being dropped; w2 is eliminated precisely because both its value columns are NA. Choice D misapplies the rule — it assumes temp = NA in w1 is enough to drop that row, but values_drop_na requires every value column in that row to be NA.
A reliable rule of thumb: values_drop_na = TRUE in pivot_longer drops a reshaped row only when all its spread values are missing — not just one.Consider d <- tibble(id = c(1, 1, 1, 2), key = c('a', 'a', 'b', 'a'), value = c(2, NA, NA, 4)). The data are widened using pivot_wider(names_from = key, values_from = value, values_fn = list(value = ~ if (all(is.na(.x))) NA_real_ else mean(.x, na.rm = TRUE)), values_fill = 0).
Which values appear in columns a and b for the two identifiers?
id = 1: a = 2, b = NA; id = 2: a = 4, b = NAid = 1: a = NA, b = NA; id = 2: a = 4, b = 0id = 1: a = 2, b = 0; id = 2: a = 4, b = 0id = 1: a = 2, b = NA; id = 2: a = 4, b = 0 (correct answer)pivot_wider, you need to track two separate mechanisms: values_fn (how to aggregate multiple values into one cell) and values_fill (what to substitute when a combination simply doesn't exist in the data).
Here's the logic for each cell. For id = 1, column a: there are two entries — 2 and NA. The custom function checks all(is.na(.x)); since 2 is present, not all values are NA, so it returns mean(c(2, NA), na.rm = TRUE) = 2. For id = 1, column b: there is one entry — NA. Now all(is.na(.x)) is TRUE, so the function explicitly returns NA_real_. For id = 2, column a: one entry, 4, so the function returns 4. For id = 2, column b: this combination never appears in the data at all — values_fill kicks in and substitutes 0.
This confirms D: id = 1 gets a = 2, b = NA; id = 2 gets a = 4, b = 0.
Choice A is wrong because it gives id = 2 a b = NA, but since that cell is structurally absent (not explicitly NA), values_fill = 0 applies. Choice B incorrectly collapses id = 1's a to NA — it ignores that the function skips NAs when at least one real value exists. Choice C wrongly assigns b = 0 for id = 1, confusing an all-NA aggregation result with a missing combination.
The key distinction to remember: values_fn handles cells that exist but need aggregating, while values_fill handles cells that are entirely absent from the source data — these are two different situations and they behave independently.Consider d <- tibble(group = c('A', 'A', 'B'), day = c(1, 2, 1), score = c(NA, 5, 7)). A programmer runs d |> complete(group, day, fill = list(score = 0), explicit = FALSE) |> pivot_wider(names_from = day, values_from = score).
Which pair of values appears in the widened row for group A and the widened row for group B, respectively?
A, day 1 is NA; group B, day 2 is 0 (correct answer)A, day 1 is 0; group B, day 2 is 0A, day 1 is NA; group B, day 2 is NAA, day 1 is 0; group B, day 2 is NAcomplete() and pivot_wider() together, the key concept to master is the explicit argument in complete(). By default, complete() fills in both implicitly missing combinations (rows that never existed) and explicitly missing values (NAs already present in the data). Setting explicit = FALSE changes this: the fill value only applies to newly created rows, leaving pre-existing NAs untouched.
Here's the logic: your original tibble has group A, day 1 with score = NA — that NA was explicit (it was already there). Group B, day 2 is implicitly missing (that combination never appeared). With complete(group, day, fill = list(score = 0), explicit = FALSE), the pre-existing NA for group A/day 1 stays as NA, while the new row for group B/day 2 gets filled with 0. After pivot_wider(), group A's day 1 column shows NA and group B's day 2 column shows 0 — confirming answer A.
Answer B is wrong because it assumes explicit = FALSE still overwrites pre-existing NAs, which it doesn't — that would be the behavior without the argument or with explicit = TRUE. Answer C incorrectly assumes the fill is never applied, ignoring that it does apply to implicitly missing combinations. Answer D reverses the logic entirely, applying the fill to the explicit NA and leaving the implicit gap unfilled.
A useful tip: think of explicit = FALSE as a "new rows only" policy — fill values are reserved for combinations that complete() creates from scratch, never for NAs that already lived in your data.In d, the variable status is a factor with levels low, medium, and high. The observed rows are id = 1, low, 2; id = 1, high, NA; id = 2, low, 4; and id = 2, high, 8. A programmer runs pivot_wider(d, names_from = status, values_from = value, names_expand = TRUE, values_fill = 0).
Which values are produced for the medium column and for id = 1 in the high column?
medium column contains zeros, and id = 1, high becomes zeromedium column contains NAs, and id = 1, high becomes zeromedium column contains zeros, and id = 1, high remains NA (correct answer)medium column is created, and id = 1, high remains NApivot_wider, you need to track two separate mechanisms: names_expand controls which columns get created, while values_fill controls how missing combinations are handled — but only for combinations that were simply absent, not for values that were explicitly recorded as NA.
names_expand = TRUE tells tidyr to generate columns for every level of the status factor, including levels that never appear in the data. Since medium is a factor level but has no observed rows, it gets created as a column — and because no real data exists for it, those cells are treated as missing combinations, so values_fill = 0 fills them with zeros. This confirms the first part of answer C.
For id = 1, high, however, the row does exist in the original data — it was explicitly recorded with value = NA. This is a crucial distinction: values_fill only fills in structurally absent combinations, not cells where NA was the actual observed value. So that NA survives the pivot unchanged, confirming the second part of C.
Answer A is wrong because it incorrectly claims id = 1, high becomes zero — that NA was observed, not absent. Answer B is wrong on both counts: medium gets zeros (not NAs) because it's a missing combination, and id = 1, high stays NA for the reason above. Answer D is wrong because names_expand = TRUE specifically exists to create columns for unobserved factor levels.
Remember this rule: values_fill only touches implicitly missing cells — if a NA was in your raw data, it stays a NA after pivoting.