Vectorization vs. Loops - R Programming

  1. Which implementation is generally preferred for clarity and performance?
    Answer: Use rowMeans(m, na.rm = TRUE) because it is a specialized vectorized function designed for this row reduction. When working with matrices in R, you'll frequently need to reduce data along a specific dimension — either collapsing rows into single values or columns into single values. The key distinction to internalize is that R provides both specialized functions (like rowMeans and colMeans) and general-purpose tools (like apply), and knowing when to prefer each is a common exam and real-world concern. rowMeans(m, na.rm = TRUE) is the correct choice here. It is a built-in vectorized function written in optimized C code specifically to compute one mean per row while excluding NA values. It returns a named numeric vector with exactly one value per row — precisely what the problem requires. This makes C the answer. Each distractor contains a specific misconception worth understanding. A is flatly wrong: mean(m, na.rm = TRUE) collapses the entire matrix into a single scalar value, completely ignoring row structure. R treats the matrix as one long vector here. B confuses the axis: colMeans() computes one mean per column, not per row — the naming tells you the direction of the output, not the direction of reduction. If you want one value per row, think rowMeans. D is tempting but misleading: apply(m, 1, mean, na.rm = TRUE) does produce correct results, but it is slower and more verbose than rowMeans. The claim that apply handles missing values "more reliably" is false — both handle NA identically when na.rm = TRUE is passed. A useful study tip: in R, prefer named specialized functions (rowMeans, colMeans, rowSums, colSums) over apply whenever they exist — they're faster, cleaner, and signal intent immediately to anyone reading your code.
  2. Which implementation strategy is most appropriate in base R when no specialized financial function is being used?
    Answer: Preallocate the balance vector and use a loop because each new balance depends on the preceding computed balance. When a computation is path-dependent — meaning each step requires the result of the previous step — you cannot collapse it into a single vectorized expression. That's the core concept being tested here. In this problem, each period's balance follows: Bt=Bt−1×(1+rt)−wtB_t = B_{t-1} \times (1 + r_t) - w_t Because BtB_t depends on Bt−1B_{t-1}, which itself was computed from Bt−2B_{t-2}, the values form a chain. A loop that preallocates the balance vector and iterates forward — updating each element before moving to the next — correctly captures this dependency. That's why A is right: preallocating avoids repeated memory reallocation, and the loop enforces the sequential dependency. B is tempting because cumprod and cumsum sound like they handle compounding and withdrawals. But the formula only works when there are no withdrawals reducing the base before the next period's interest is applied. Once withdrawals occur mid-sequence, the two components cannot be accumulated independently — the withdrawal in period 1 changes the base that gets compounded in period 2, breaking the separation. C incorrectly assumes interest is always earned on the initial balance. In reality, interest compounds on the running balance, which shrinks after each withdrawal. D mistakes elementwise vector arithmetic for sequential updating. R applies initial_balance * (1 + rate) - withdrawal to the original scalar across all periods simultaneously — it doesn't "carry forward" any computed balance. Study tip: Whenever you see a recurrence relation where step tt explicitly depends on step t−1t-1's output, that's your signal that a loop is necessary — no vectorized shortcut preserves that dependency.
  3. Which expression correctly replaces the loop without relying on unintended recycling?
    Answer: Use value > threshold[group] because indexing expands the appropriate threshold to each observation's position. When vectorizing a loop in R, your goal is to replicate exactly what the loop does — element by element — using index alignment rather than recycling. Here, the loop says: for each position i, compare value[i] to threshold[group[i]]. Notice that group[i] is used to index into threshold, not into value. So the vectorized form needs to expand threshold according to the group vector. D is correct because threshold[group] uses the group vector as an index, pulling the appropriate threshold for each observation in one shot. The result is a vector the same length as value and group, with each element being the threshold corresponding to that observation's group — exactly mirroring threshold[group[i]] across all i. A is wrong because value > threshold relies on R's recycling rules, which repeat threshold in order (1, 2, 3, 1, 2, 3, ...) regardless of what group contains. This only works correctly if groups happen to be perfectly sequential, which is not guaranteed. B reverses the comparison entirely — threshold > value[group] both flips the inequality and indexes value with group, which doesn't match the loop's logic on either count. C makes a symmetric mistake: it indexes value with group (value[group]) instead of indexing threshold with group. This reorders the observations rather than selecting the correct thresholds, producing a misaligned comparison. A useful pattern to remember: when a loop uses shortVector[indexVector[i]], the vectorized equivalent is shortVector[indexVector] — index the shorter structure, not the longer one.
  4. Which approach best fits this task?
    Answer: Use an explicit loop because retries, delays, and later actions depend on the outcome of each individual submission. When a task requires tracking the outcome of each step before deciding what to do next — retries, delays, conditional logic — you're dealing with sequential, stateful control flow. That's the key signal in this question. An explicit loop (D) is the right tool here because each iteration can inspect the server's response, decide whether to retry, pause if a delay is requested, and only then move to the next file. The loop body has full access to mutable state (the collected identifiers, retry counters, etc.) and executes exactly one submission at a time, which matches the service's constraint perfectly. Choice A misrepresents what Vectorize() actually does. It doesn't batch calls into a single request — it's a wrapper that applies a scalar function element-by-element, essentially building a loop for you. It also gives you no mechanism to inspect intermediate results, handle retries, or introduce conditional delays between calls. Choice B contains two false claims about vapply(). First, vapply() does not run submissions simultaneously — R is single-threaded by default and vapply() processes elements sequentially. Second, even if parallelism were involved, that would be exactly wrong for a service requiring one-at-a-time submissions with response-dependent behavior. Choice C is simply false. Passing a vector to a function does not automatically "distribute" it into multiple service calls; the function receives the entire vector as one argument unless it's explicitly written to loop internally. As a study tip: whenever a question describes side effects that depend on prior outcomes (retries, waits, conditional branching), an explicit loop is almost always the answer over functional-style apply tools.
  5. Which replacement best retains vectorized processing while avoiding evaluation of log() at invalid positions?
    Answer: Preallocate an NA_real_ result, form a valid-position logical index, and assign log(x[valid]) only at those positions. When working with vectorized operations in R, the key tension is between where a function is called and where its result is used. ifelse() looks conditional, but it fully evaluates both the true and false expressions across the entire vector before selecting results — meaning log(x) runs on negative and zero elements regardless, triggering warnings. The cleanest solution is B: preallocate a vector of NA_real_ with the same length as x, compute a logical index valid <- x > 0, then assign result[valid] <- log(x[valid]). This is genuinely vectorized — log() receives a contiguous numeric vector of only valid values in a single call — and it never touches invalid positions. Positional alignment is preserved because you're assigning back into the original index slots. A is a trap for students who confuse "hiding a problem" with "solving it." suppressWarnings() just silences the output; log() still runs on invalid elements, producing NaN values that could corrupt downstream analysis without any signal. C looks tempting because log(x[x > 0]) does avoid invalid inputs, but it returns a shorter vector stripped of invalid positions. You lose length and positional alignment entirely — a major problem if you need results to correspond to the original indices of x. D is simply false. Logical indexing like x[x > 0] is one of R's core vectorized tools, implemented in C under the hood. Replacing it with a loop sacrifices R's performance advantages for no benefit. As a study tip: whenever you see ifelse() with a function that has a restricted domain, remember that both branches evaluate eagerly — reach for index-and-assign instead.
  6. Which implementation is generally preferred if the complete vector of running totals is required?
    Answer: Use cumsum(sales) because it is a specialized vectorized operation implementing the required cumulative dependency. When you see a question about cumulative computations in R, ask yourself two things: does a built-in vectorized function exist for this exact operation, and does it correctly handle sequential dependencies? That framing points directly to the right answer here. cumsum() is R's purpose-built function for computing running totals. Given sales = c(10, 20, 30), it returns c(10, 30, 60) — exactly the vector of intermediate totals the passage describes. It is vectorized, meaning R executes it in optimized compiled code rather than interpreted R loops, making it both faster and cleaner than a manual loop. This makes C the correct and preferred choice. A is wrong because sum(sales) performs a reduction — it collapses the entire vector into a single scalar (the grand total), discarding every intermediate value. It returns nothing resembling a running total vector. B contains a plausible-looking formula, but it is mathematically incorrect. Adding each sale to the immediately preceding sale (not the accumulated total) does not produce cumulative sums. For c(10, 20, 30), it yields c(10, 30, 50) instead of c(10, 30, 60) — off as soon as more than two elements exist. D represents a common misconception: that cumulative dependency automatically requires a loop. R's cumsum() proves this false — specialized vectorized functions can encode sequential logic internally. As a study tip, remember that R has a family of cumulative functions — cumsum(), cumprod(), cummax(), cummin() — and exam questions will test whether you recognize them as superior alternatives to explicit loops.
  7. Which approach is most appropriate when early candidates frequently exceed the limit?
    Answer: Use a loop that evaluates candidates in order and exits with break immediately after the first qualifying score. When a function is expensive and you only need the first result that meets a condition, the key concept is lazy evaluation through early exit — doing only as much work as necessary and stopping the moment the goal is achieved. A loop with break (choice B) is the right tool here. You evaluate score() one candidate at a time in priority order, and the instant a score exceeds the limit, you record that position and exit the loop immediately. If early candidates frequently exceed the limit, you may only need to call score() once or twice before stopping — dramatically reducing computation compared to any approach that processes the full vector. Choice A fails precisely because it ignores the expensive nature of score(). Calling score(candidates) on the entire vector forces R to evaluate every candidate regardless of when the first qualifying one appears. You do far more work than necessary, which the problem explicitly warns you to avoid. Choice C misunderstands what which.max() does — it finds the position of the largest score, not the first score that crosses a threshold. A candidate with a moderate score that just barely exceeds the limit would be the correct answer, but which.max() would skip it in favor of a higher score elsewhere in the vector. Choice D introduces a subtle trap: sorting the candidates destroys their original priority order. Even if you then find the first qualifying score after sorting, that position no longer corresponds to the priority ranking the problem requires you to respect. Your study tip: whenever a question mentions an "expensive" function alongside an ordered stopping condition, that's a signal to reach for a loop with break rather than any vectorized approach that processes everything upfront.
  8. Given this measured memory constraint, which response is most appropriate?
    Answer: Compute the mean once, preallocate the output, and fill it in a loop to reduce the number of full-length temporary allocations. When you see a memory-constraint question in R, shift your thinking from speed to peak allocation — ask how many full-length copies of the data exist simultaneously at any point in execution. The direct expression (x - mean(x))^2 is elegant but memory-hungry: R creates a temporary vector for x - mean(x) and another for the squared result, so you briefly hold two or three full-length vectors alongside the original. When x is massive, that multiplication of allocations is exactly what crashes the job. The fix in C — computing mean(x) once, preallocating out <- numeric(length(x)), then filling element-by-element in a loop — keeps only the output vector and the scalar mean alive. Each iteration overwrites a single slot, so peak memory stays near one full-length vector rather than three. A is a tempting trap because vectorized code is generally faster, but "vectorized" does not mean "memory-minimal." Vectorized operations still create intermediate full-length objects; that's precisely the problem described. B is the worst of all options: growing a vector with c(out, value) inside a loop copies the entire existing vector at each step, causing both quadratic time complexity and escalating memory use — the opposite of what you want. D is doubly flawed: sapply calls mean(x) on the full vector for every single element, an O(n²) disaster in both time and repeated work, and it offers no memory advantage over the direct expression. For exam questions involving large data and memory limits, always prioritize how many full-length copies a solution creates — that number, not vectorization alone, determines peak allocation.
  9. Which implementation is generally preferred when the complete matrix is required?
    Answer: Use outer(x, y, FUN = "-") because it directly applies subtraction to every pair of elements. When you need to compute every pairwise combination of two vectors — subtracting, adding, or applying any function — think about the outer product family of functions in R. These are designed precisely for creating matrices where each cell represents some operation between one element from each vector. outer(x, y, FUN = "-") is the correct and preferred approach here. It systematically applies the subtraction function to every (xix_i, yjy_j) pair, returning a matrix with length(x) rows and length(y) columns, exactly matching the described requirement. It's readable, general-purpose, and efficient — making A the right answer. B is false and represents a common misconception. Nested loops can solve this problem, but they are slower and more verbose than vectorized alternatives. The claim that "operations involving two indices cannot be expressed by vectorized base R functions" is simply wrong — outer() exists precisely for this purpose. C is a classic recycling trap. x - y does trigger R's recycling rules, but recycling operates within a single vector dimension, not across two dimensions. The result is a vector, not a matrix, and the recycling pattern may not correspond to the intended pairwise subtraction at all. D misrepresents the %o% operator. x %o% y is equivalent to outer(x, y, FUN = "*") — it computes pairwise multiplication, not subtraction. Using it here would produce the wrong values entirely. As a study tip: whenever a question asks about applying an operation to every combination of two vectors in R, outer() with a custom FUN argument should be your first instinct.
  10. Which assessment of replacing the loop with z <- (x - mu) / sigma is most accurate?
    Answer: Use the vectorized expression because scalar arithmetic is applied elementwise and missing values propagate to the same positions. When you see a question about replacing a loop with a vectorized expression in R, ask yourself two things: does the vectorized version produce the same arithmetic, and does it handle edge cases (like NA) the same way? R's vectorized arithmetic applies scalar operations elementwise automatically. Writing (x - mu) / sigma subtracts mu from every element of x and divides by sigma — exactly what the loop does one index at a time. Crucially, R propagates NA values through arithmetic operations by design: any calculation involving NA returns NA. So if x[i] is NA, then x[i] - mu is NA, and NA / sigma is NA. The vectorized expression preserves missing positions without any explicit is.na() check, making B correct. A is wrong because it assumes vectorized arithmetic silently drops or mishandles NA values. In reality, NA propagation is a core feature of R's arithmetic — no special guarding is needed. C is wrong because subtracting a scalar from a vector is standard, intentional recycling in R. The scalar mu is reused for each element, which is exactly the desired behavior — not a distortion of the calculation. D is wrong because it prescribes unnecessary extra work. Removing NAs, computing, and reinserting them would be cumbersome and error-prone, and it's completely unnecessary since propagation handles it automatically. A useful mental rule: in R, if an operation works correctly for one element, vectorization makes it work correctly for all elements — including missing ones — without modification.

Start this quiz