- Which explanation most accurately describes the output when some departments have tied highest scores?
Answer: Every employee tied for the highest score in a department is returned, so some departments contribute multiple rows.
When working with slice_max() in dplyr, the key parameter to focus on is with_ties. By default — and explicitly here — with_ties = TRUE tells R to return all rows that share the maximum value, not just one arbitrarily chosen row. The grouping structure means this tie-breaking logic applies within each department separately, so if three employees in Marketing all score 95 (the department max), all three are returned.
This confirms B as correct. The pipeline groups by department, finds the highest score within each group, and because with_ties = TRUE, every employee matching that peak score is included. The final ungroup() simply removes the grouping metadata from the result — it has no effect on which rows were selected.
A is wrong because it describes behavior you'd get with with_ties = FALSE, which enforces a single row per group (using original row order as a tiebreaker). That's a different argument value entirely. C contains a subtle but important misconception: ungroup() is called after slice_max(), so it cannot retroactively change what was selected. The selection already happened within department boundaries; ungroup() just cleans up the result. D misreads how n = 1 works — it doesn't mean "consume one slot per tied employee." It defines how many distinct rank positions to include, so n = 1 always means first place only, regardless of how many employees share it.
A good rule of thumb: whenever you see with_ties in a slice_* function, ask yourself whether the question is testing the single-row versus all-tied-rows behavior — that distinction is a frequent exam trap.
- Which explanation most accurately describes how
rate and n are produced? Answer: Rows with missing delays are removed; rate is the proportion delayed among remaining flights, and n is their count for each carrier.
When reading a dplyr pipeline, trace each step in order — each transformation feeds directly into the next, so the meaning of your final summary depends entirely on what rows survived earlier steps.
Here, filter(!is.na(dep_delay)) runs first, permanently dropping every row where dep_delay is missing. Only flights with a recorded departure delay remain. Next, mutate(delayed = dep_delay > 15) creates a logical column — TRUE if the delay exceeded 15 minutes, FALSE otherwise — for every surviving row. After grouping by carrier, summarise(rate = mean(delayed), n = n()) computes two things: because mean() on a logical vector returns the proportion of TRUE values, rate is the fraction of non-missing flights that were delayed more than 15 minutes; n is simply the count of those remaining rows per carrier. That makes B the accurate description.
A is wrong because filter(!is.na(dep_delay)) does not treat missing delays as "not delayed" — it removes those rows entirely before any calculation happens. C misreads mutate(delayed = dep_delay > 15) as a filter that keeps only delayed flights; it actually tags all remaining rows with a TRUE/FALSE flag, and rate is a proportion, not an average delay in minutes. D inverts the pipeline order entirely — grouping and summarising happen after filtering, not before.
A reliable strategy: when a pipeline mixes filter(), mutate(), and summarise(), mentally "run" each verb in sequence and ask which rows still exist before interpreting what the summary statistics actually measure.
- Which statement would best document the transformation applied to
sales? Answer: Sales are standardized within each region, then all rows are sorted by the resulting scores and returned ungrouped.
When you see a dplyr pipeline, read it operation by operation — each verb transforms the result of the previous one, and grouping context determines where calculations happen.
Here, group_by(region) partitions the data by region before anything else runs. That means mutate(z_sales = (sales - mean(sales)) / sd(sales)) computes the mean and standard deviation within each region separately, not across all rows globally. This is the z-score formula z=σx−μ, applied group-wise. Next, arrange(desc(z_sales)) sorts all rows from highest to lowest standardized score — it doesn't average or aggregate anything. Finally, ungroup() removes the grouping structure. That sequence maps precisely onto answer C: within-region standardization → row-level sort by those scores → ungrouped result.
Answer A is wrong on two counts: the mean and SD are regional, not global, and arrange sorts individual rows, not regions by their averages. Answer B invents a ranking step that never appears in the code — mutate with the z-score formula is a direct transformation, not a rank-based one. Answer D reverses the actual order of operations; sorting happens after standardization, not before, and the grouping is by region, not established by row order.
A useful strategy: when you see group_by() followed by mutate() (rather than summarise()), remember that the calculation stays row-level but uses group-local statistics. If it were summarise(), rows would collapse. That distinction — mutate preserves rows, summarise collapses them — is a frequent exam focus in tidyverse questions. - Which explanation correctly describes the order and scope of the transformations?
Answer: Each column's missing values are replaced by its own observed median, then that completed column is standardized using its post-imputation mean and standard deviation.
When you see a pipeline with multiple mutate(across(...)) calls, think about two things: what each lambda does independently per column, and what data that lambda sees when it executes.
In R, across() applies a function to each specified column separately — not across columns simultaneously. So in the first mutate, ~ replace(.x, is.na(.x), median(.x, na.rm = TRUE)) runs on x alone, then on y alone. Each column's NAs are filled with that column's own median, computed from its non-missing values. The result is two fully-imputed columns. In the second mutate, ~ (.x - mean(.x)) / sd(.x) then standardizes each completed column using that column's own post-imputation mean and standard deviation — because .x now refers to the already-imputed version. This confirms C as correct.
A is wrong because it implies a pooled median across both columns. across() never merges columns — each lambda receives one column at a time as .x, so the median is always column-specific.
B reverses the order entirely. Standardization happens in the second mutate, not the first. The pipeline imputes first, then standardizes — not the other way around.
D claims missing values are replaced by each column's mean, but the code explicitly uses median(). It also incorrectly suggests only pre-imputation values drive the standard deviation; after imputation, the filled values participate in sd().
Study tip: When reading pipelines, trace what .x represents at each step — it reflects the column's current state after all prior mutations, not the original raw data.
- Which statement best explains the coefficient on the original price scale without overstating its meaning?
Answer: A one-year age increase is associated with multiplying fitted price by exp(−0.08), corresponding to about a 7.7% decrease.
Whenever you fit a log-linear model like lm(log(price) ~ age), you need to mentally "undo" the log transformation to interpret coefficients on the original price scale. The model says log(price^)=β0+β1⋅age, so a one-unit increase in age adds β1 to the log of price — which means price gets multiplied by eβ1 on its original scale.
With β^1=−0.08, a one-year age increase multiplies fitted price by e−0.08≈0.923, a reduction of roughly 7.7%. Answer A captures this correctly and uses careful language ("associated with") that avoids claiming causation — important when interpreting observational regression output.
B is wrong on two counts: multiplying by 100 only works as a rough approximation for very small coefficients, and saying "exactly 8%" overstates precision. The true multiplicative factor is e−0.08, not 1−0.08. C confuses the log-scale coefficient with a raw-scale subtraction — subtracting 0.08 currency units makes no sense here because the model was built on log(price), not price itself. D uses e+0.08 instead of e−0.08, reversing the sign and implying price increases with age, which contradicts the negative coefficient entirely.
A useful habit: whenever you see log(y) on the left side of a formula call, always exponentiate the coefficient before interpreting it, and describe changes as multiplicative, not additive. - Which explanation correctly communicates the possible effect of this join on the rows?
Answer: Every customer remains, customers with multiple orders can produce multiple rows, and customers without orders receive missing order fields.
When you see a join question in R, the key is understanding two things: which table drives the output and how row counts change when the relationship isn't one-to-one.
A left_join(x, y) guarantees every row in the left table (customers) appears in the output — no customer is ever dropped. From there, the row count depends on how many matches exist in the right table (orders). If a customer has three orders, that customer produces three rows in the output. If a customer has zero orders, they still appear once, but all columns sourced from orders fill with NA. This is exactly what A describes, making it the correct answer.
B is tempting but wrong — left_join never automatically collapses multiple matches into a list. That behavior would require a separate aggregation step (e.g., group_by + summarise or nest). C describes an inner_join, which only retains rows with matches in both tables, discarding customers who have never ordered. D describes a right_join or the mistaken idea that the right table drives which rows survive — in a left_join, the right side (orders) never determines whether a left-side row is kept; only the left table does.
A reliable mental model: the word "left" tells you the left table is protected. Every row there survives. The right table only controls how many times a left row repeats (once per match) and whether columns from the right are NA (no match). Keep this rule in mind whenever you encounter any of the four join types.
- Which statement best documents what this pipeline returns?
Answer: One row for each distinct expected identifier that does not occur among the distinct observed identifiers.
When you see a pipeline chaining distinct() with anti_join(), focus on two things: which table is on the left and **what anti_join actually filters**. The rule is simple — anti_join(x, y) returns rows from x that have no match in y. It never returns rows from y, and it never produces matches.
Here, the left-hand side is expected |> distinct(id), so the pipeline starts with unique identifiers from the expected table. Then anti_join(..., observed |> distinct(id), ...) filters those down to only the identifiers that don't appear in the observed table. The result is a set of expected IDs with no recorded observation — exactly what D describes: one row for each distinct expected identifier that does not occur among the distinct observed identifiers.
A is wrong because it describes a symmetric operation (rows from either source), which resembles a full join with filtering — not anti_join. B flips the tables entirely: it describes filtering observed IDs against expected ones, which would require observed |> distinct(id) |> anti_join(expected |> distinct(id), ...). C describes a join that retains matches and fills missing fields — that's a left join behavior, not an anti-join, which by definition returns no matched rows.
A reliable study tip: always identify the left table in anti_join(x, y) — that's the pool you're filtering from. The right table is only used to define what gets excluded. This asymmetry is the most commonly tested trap with filtering joins in R.
- Which explanation correctly describes both the split and the role of
set.seed()? Answer: The sampled rows form training data and the remaining rows form test data; the seed makes the sampling reproducible under the same R environment and RNG settings.
When working with train-test splits in R, focus on two things: what the indexing logic actually does and *what set.seed() guarantees (and doesn't guarantee)*.
The code samples 80% of row indices into idx, assigns those rows to train, and uses negative indexing (-idx) to assign the remaining rows to test. These two subsets are mutually exclusive — no row appears in both. This makes D the correct answer: the sampled rows form training data, the complement forms test data, and set.seed(42) ensures you get the same random sample every time you run the code — provided you're using the same R version and RNG algorithm. That last caveat is important.
Here's where the distractors mislead you. A correctly identifies the complement as test data but overclaims about set.seed() — seeds do not guarantee identical results across all R versions and platforms, because R's default RNG algorithm has changed between versions. B contains two errors: it wrongly suggests rows can overlap between subsets (negative indexing makes that impossible), and it misrepresents the seed as controlling "sampling frequencies" rather than reproducibility. C describes a completely different operation — sorting rows before slicing — which is not what sample() does. sample() draws random indices; it doesn't reorder the data frame before taking the first 80%.
As a study habit, always trace index operations carefully in R questions. When you see df[-idx, ], that's R's way of saying "everything except these rows" — a clean complement with zero overlap.
- Which explanation accurately describes the labels actually assigned by this code?
Answer: Scores of at least 80 receive Pass, including scores of at least 90; missing scores receive Missing, and lower scores receive Review.
When using case_when() in R, the critical rule is first-match wins — conditions are evaluated top to bottom, and once a row satisfies a condition, it gets that label and stops. No further conditions are checked. This question tests whether you understand that ordering determines outcomes.
Looking at the code, score >= 80 appears before score >= 90. A score of 95, for example, satisfies score >= 80 first, so it receives 'Pass' immediately — the Honors condition is never reached for that row. This means **every score of 80 or above gets labeled 'Pass'**, and the Honors branch is effectively dead code. After that, is.na(score) handles missing values, and TRUE (the catch-all) assigns 'Review' to anything remaining — scores below 80.
This confirms B is correct: scores ≥ 80 (including those ≥ 90) all receive 'Pass', missing scores receive 'Missing', and lower scores receive 'Review'.
A is wrong because it describes the intended behavior, not the actual behavior — it assumes Honors is checked before Pass, but the order is reversed in the code. C is wrong on two counts: rows never receive multiple labels in case_when(), and the missing value logic is misapplied. D is partially right that scores below 80 get 'Review', but wrong that NA scores go unlabeled — is.na(score) explicitly catches them before the TRUE fallback.
Your study tip: whenever you read a case_when() block, mentally trace through conditions in order from top to bottom. Overlap between conditions is almost always a bug, and exams love testing whether you can spot it.
- Which narrative most precisely explains the resulting data?
Answer: Each retained row represents a company-quarter with positive observed revenue; zero, negative, and missing revenue entries are all excluded.
When you see a pipeline combining pivot_longer() and filter(), pause and think through each transformation step-by-step before reading the answer choices.
pivot_longer(Q1:Q4, names_to = 'quarter', values_to = 'revenue') reshapes the wide data frame into a long format — each company now appears in four rows, one per quarter, with a quarter column holding "Q1"/"Q2"/etc. and a revenue column holding the corresponding value. Then filter(revenue > 0) keeps only rows where revenue is strictly greater than zero. This means rows with zero revenue, negative revenue, or NA revenue are all dropped — because NA > 0 evaluates to NA, which filter() treats as FALSE. Each surviving row uniquely identifies one company-quarter combination, making A the precise description.
B is wrong on two fronts: the result is still in long format (not wide with separate quarterly columns), and the filter operates row-by-row, not on annual totals. C is wrong because pivot_longer() does not aggregate across companies — each row is still a single company-quarter, not a quarter-level summary — and neither function converts any values to zero. D is tempting but subtly wrong: "nonnegative" would include zero, but the strict inequality > 0 excludes zeros, so they are removed just like negative values.
A good strategy: always trace what a single row represents after each transformation — shape first (pivot_longer changes row meaning), then filter. Misreading the unit of observation is the most common trap in tidy-data questions.