Handling Missing Values in Reshaping - R Programming

  1. Which values are produced for the medium column and for id = 1 in the high column?
    Answer: The medium column contains zeros, and id = 1, high remains NA When working with pivot_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.
  2. Which pair of values appears in the widened row for group A and the widened row for group B, respectively?
    Answer: Group A, day 1 is NA; group B, day 2 is 0 When working with complete() 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.
  3. Which values appear in columns a and b for the two identifiers?
    Answer: id = 1: a = 2, b = NA; id = 2: a = 4, b = 0 When working with 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.
  4. Which pivot_longer() call converts the marker to a real missing value and removes it during the reshape?
    Answer: pivot_longer(d, q1:q2, values_to = 'response', values_transform = list(response = ~ na_if(.x, '-')), values_drop_na = TRUE) When reshaping survey data with placeholder markers like '-', 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.
  5. How does result differ from the original wide data?
    Answer: The two original missing values become zeros because their long rows were discarded When you chain pivot_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.
  6. Which pipeline achieves the goal?
    Answer: wide |> pivot_longer(x:y, names_to = 'variable', values_to = 'value', values_drop_na = TRUE) When working with pivot operations and missing data, the key question is: when should you handle 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.
  7. After running d |> pivot_wider(names_from = day, values_from = score, values_fill = 0), which description is correct?
    Answer: For id = 1, Mon is NA; for id = 2, Tue is 0 When working with pivot_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.
  8. What remains after the reshape?
    Answer: Only week w1, with temp = NA and humid = 40 When working with pivot_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.
  9. What happens when d is processed with pivot_wider(names_from = visit, values_from = score, values_fill = 0)?
    Answer: The missing id becomes an identifier row with pre = 3 and post = 0 When working with pivot_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.
  10. Which rows are produced by wide |> pivot_longer(m1:m2, names_to = 'measure', values_to = 'value', values_drop_na = TRUE)?
    Answer: Two rows: x, m2, 2 and y, m1, 3 When you see pivot_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.

Start this quiz