R Programming Quiz: Vectorized Operations
10 questions · exam conditions
0:00
Vectorized OperationsQuestion 1 of 10

A programmer wants to replace only values outside the inclusive range from zero through three with NA:

x <- c(-2, 1, 4)

Which statement performs the intended elementwise replacement?

x[(x < 0) | (x > 3)] <- NA
x[(x < 0) & (x > 3)] <- NA
x[any((x < 0) | (x > 3))] <- NA
x[which((x >= 0) & (x <= 3))] <- NA
← Back to quizzes

R Programming Quiz

R Programming Quiz: Vectorized Operations

Practice Vectorized Operations in R Programming with focused quiz questions that help you check what you know, review explanations, and build confidence with test-style prompts.

What this quiz covers

This quiz focuses on Vectorized Operations, giving you a quick way to practice the rules, question types, and explanations that matter most for R Programming.

How to use this quiz

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.

All questions

Question 1

A programmer wants to replace only values outside the inclusive range from zero through three with NA:

x <- c(-2, 1, 4)

Which statement performs the intended elementwise replacement?

  1. x[(x < 0) | (x > 3)] <- NA (correct answer)
  2. x[(x < 0) & (x > 3)] <- NA
  3. x[any((x < 0) | (x > 3))] <- NA
  4. x[which((x >= 0) & (x <= 3))] <- NA
Explanation: When you need to replace elements that fall outside a range, you're combining two logical conditions with the right operator and feeding them into a subsetting bracket. The key question is: which logical operator correctly captures "less than zero OR greater than three"? For x <- c(-2, 1, 4), you want to flag elements where x < 0 or x > 3 — either condition alone is enough to disqualify a value. That means the pipe operator | (elementwise OR) is correct. x[(x < 0) | (x > 3)] <- NA evaluates both conditions across every element and returns TRUE for -2 (too low) and 4 (too high), replacing them with NA while leaving 1 untouched. This makes A the right answer. B fails because it uses & (AND), which requires both conditions to be true simultaneously. No number can be both less than 0 and greater than 3 at the same time, so this logical expression is always FALSE — nothing gets replaced. C uses any(...), which collapses the entire logical vector into a single TRUE or FALSE. If any out-of-range element exists, any(...) returns TRUE, and R interprets that as index 1, replacing only x[1] — not the intended elements. D inverts the logic entirely. (x >= 0) & (x <= 3) selects values inside the valid range, so this replaces the good value (1) with NA instead of the outliers. As a study tip: whenever a question asks you to flag values outside a range, reach for | with strict inequalities, and remember that any() and all() reduce vectors to scalars — they don't belong inside element-targeting brackets.

Question 2

A programmer uses this loop:

out <- numeric(length(x))

for (i in seq_along(x)) out[i] <- log(x[i])

Assume every element of x is positive. Which statement about replacing the loop is accurate?

  1. log(sum(x)) returns the same elements because logarithms distribute over vector addition
  2. lapply(x, log) returns the same numeric vector and is guaranteed to outperform log(x)
  3. log(x) returns the same numeric elements and usually avoids interpreted element-by-element iteration (correct answer)
  4. sum(log(x)) returns the same vector but performs the reduction after transformation
Explanation: When you see a question comparing a for-loop to vectorized alternatives in R, the core concept being tested is vectorization: R's built-in functions like log() operate on entire vectors internally using compiled C code, bypassing the slow interpreted loop that processes one element at a time. The loop in the question applies log() to each element individually and stores results in out. Option C is correct because writing log(x) does exactly the same thing — it returns a numeric vector of the same length with log applied element-wise — but it delegates that iteration to optimized compiled code rather than R's interpreter. This is almost always faster and is idiomatic R style. Option A is mathematically wrong. Logarithms do not distribute over addition — log(a + b) ≠ log(a) + log(b). So log(sum(x)) collapses the vector to a single scalar first, giving you an entirely different result. Option B contains two errors. First, lapply(x, log) returns a list, not a numeric vector, so it doesn't match the loop's output type without an additional unlist(). Second, claiming it is guaranteed to outperform log(x) is false — lapply still iterates element-by-element in R's interpreter and is generally slower than fully vectorized functions. Option D is wrong because sum(log(x)) applies log correctly but then reduces everything to a single number (the sum), not the full vector the loop produces. As a study tip, always distinguish between functions that transform vectors element-wise (log(x), sqrt(x)) and those that reduce them to a scalar (sum(), mean()). Mixing them up is a frequent trap in R exam questions.

Question 3

A nested loop creates z so that each entry is the sum of one element from each vector:

x <- 1:2

y <- c(10, 20, 30)

z <- matrix(0, nrow = length(x), ncol = length(y))

for (i in seq_along(x)) for (j in seq_along(y)) z[i, j] <- x[i] + y[j]

Which expression directly constructs the same matrix?

  1. x + y, relying on recycling to generate all pairwise combinations
  2. x + t(y), using transposition to force every pairwise combination
  3. cbind(x, y), binding corresponding values before applying addition
  4. outer(x, y, "+"), applying addition to every cross-vector pair (correct answer)
Explanation: When you see a nested loop filling a matrix with every combination of two vectors, you're looking at a cross-product (outer product) operation — each element from one vector is paired with every element from the other. outer(x, y, "+") does exactly this: it applies the "+" function to every possible (x[i], y[j]) pair, producing a matrix where row i and column j holds x[i] + y[j]. With x = 1:2 and y = c(10, 20, 30), the result is a 2×3 matrix matching the loop output precisely. D is correct. A is tempting but wrong. x + y uses R's recycling rules, which cycle the shorter vector element-by-element through the longer one — it produces a single vector of length 3, not all pairwise combinations. Recycling fills gaps sequentially, not exhaustively. B misleads with transposition. t(y) converts y into a 1×3 matrix, but adding a length-2 vector to a 1×3 matrix still triggers recycling, not a true 2×3 outer product. The result won't reliably match the loop. C is unrelated. cbind(x, y) binds x and y as columns of a matrix — it doesn't perform addition at all, just combines the raw values side by side. Study tip: Whenever a nested loop fills a matrix by combining every element of one vector with every element of another, think outer() immediately. It's R's dedicated tool for pairwise operations across two vectors, and it's far more readable than a double loop.

Question 4

The following loop stores a running total that begins at five:

x <- c(2, -1, 4)

total <- 5

out <- numeric(length(x))

for (i in seq_along(x)) { total <- total + x[i]; out[i] <- total }

Which vectorized expression produces the same out?

  1. cumsum(5 + x), which returns c(7, 11, 20)
  2. 5 + sum(x), which returns the single value 10
  3. cumsum(c(5, x)), which returns c(5, 7, 6, 10)
  4. 5 + cumsum(x), which returns c(7, 6, 10) (correct answer)
Explanation: When replacing a loop with a vectorized equivalent, your goal is to match the sequence of intermediate values, not just the final sum. The loop here maintains a running total starting at 5, so after each element of x, the cumulative result grows step by step — that's the signature of cumsum(). The correct answer is D. cumsum(x) computes c(2, 1, 5) (the running totals of x alone), and adding 5 shifts every element up by the starting value: c(2+5, 1+5, 5+5) = c(7, 6, 10). This exactly mirrors what the loop produces at each iteration. Here's why the other options miss the mark. A (cumsum(5 + x)) adds 5 to each element of x first, turning x into c(7, 4, 9), then accumulates those inflated values — producing c(7, 11, 20), which compounds the 5 repeatedly rather than applying it once. B (5 + sum(x)) collapses the entire vector into a single scalar 10, losing all intermediate steps entirely — it answers "what is the final total?" not "what are the running totals?" C (cumsum(c(5, x))) prepends 5 to x before accumulating, returning a four-element vector c(5, 7, 6, 10) — one element too long, and it includes the starting value itself as an output, which the loop does not. A useful rule of thumb: when a loop builds up results at each step using a growing total, reach for cumsum(). The key question is where the offset is applied — outside cumsum() means it shifts the output once; inside means it compounds.

Question 5

The following loop updates x sequentially:

x <- c(1, 2, 3, 4)

for (i in 2:length(x)) x[i] <- x[i] + x[i - 1]

A programmer instead runs:

x <- c(1, 2, 3, 4)

x[-1] <- x[-1] + x[-length(x)]

How do the final vectors compare?

  1. Both versions produce c(1, 3, 6, 10) because assignment proceeds element by element
  2. The loop produces c(1, 3, 6, 10), while the vectorized assignment produces c(1, 3, 5, 7) (correct answer)
  3. The loop produces c(1, 3, 5, 7), while the vectorized assignment produces c(1, 3, 6, 10)
  4. Both versions produce c(1, 3, 5, 7) because each uses adjacent original elements
Explanation: Whenever you see a question comparing a sequential loop to a vectorized operation in R, ask yourself: does each step depend on a previously updated value, or on the original values? That distinction is everything here. In the for loop, each iteration uses the already-modified vector. Starting with x <- c(1, 2, 3, 4):
  • i=2: x[2] = 2 + x[1] = 2 + 1 = 3 → x is now c(1, 3, 3, 4)
  • i=3: x[3] = 3 + x[2] = 3 + 3 = 6 → x is now c(1, 3, 6, 4)
  • i=4: x[4] = 4 + x[3] = 4 + 6 = 10 → final: c(1, 3, 6, 10)
This is a cumulative sum because each step feeds into the next. In the vectorized assignment x[-1] <- x[-1] + x[-length(x)], R evaluates the entire right-hand side before any assignment occurs. So x[-1] + x[-length(x)] is computed as c(2,3,4) + c(1,2,3) = c(3,5,7) using the original values throughout. The result is c(1, 3, 5, 7) — making B the correct answer. A is wrong because vectorized assignment does not proceed element by element; the right side is fully evaluated first. C swaps the two results — the loop gives the cumulative sum, not the vectorized version. D is wrong because while the vectorized version does use original adjacent elements, the loop does not. Study tip: In R, vectorized operations always snapshot the right-hand side before writing results — sequential dependency requires an explicit loop.

Question 6

Suppose the following vectors are defined:

a <- c(1, 5, 3)

b <- c(2, 4, 6)

A loop sets each output element to max(a[i], b[i]). Which expression reproduces that elementwise result?

  1. max(a, b), producing the vector c(2, 5, 6)
  2. pmax(a, b), producing the vector c(2, 5, 6) (correct answer)
  3. pmin(a, b), producing the vector c(1, 4, 3)
  4. max(cbind(a, b)), producing the vector c(2, 5, 6)
Explanation: When working with elementwise operations in R, the key distinction is between functions that collapse all values into a single result versus functions that operate position-by-position across vectors. pmax() and pmin() are the parallel versions of max() and min(). For each index i, pmax(a, b) returns whichever value is larger between a[i] and b[i]. With a <- c(1, 5, 3) and b <- c(2, 4, 6), this compares position by position: max(1,2)=2, max(5,4)=5, max(3,6)=6, yielding c(2, 5, 6). This exactly mirrors what the loop does, making B the correct answer. Choice A uses max(a, b), which treats both vectors as a single pool of values and returns one scalar — the global maximum, which would be 6, not a three-element vector. This is the most common trap on questions like this. Choice C uses pmin(a, b), which is parallel but returns the minimum at each position — c(1, 4, 3) — the opposite of what's needed. Choice D uses cbind(a, b) to form a matrix, then passes it to max(), which again collapses everything to a single scalar (6). The framing makes it look like a clever workaround, but max() never returns a vector regardless of its input structure. A useful memory trick: the "p" prefix means "parallel"pmax and pmin process vectors side-by-side. Whenever a question describes a loop comparing two vectors elementwise, reach for pmax or pmin first.

Question 7

Consider the following matrix operation:

m <- matrix(1:6, nrow = 2)

result <- m * c(10, 100)

Which expression represents result?

  1. matrix(c(10, 20, 30, 40, 50, 60), nrow = 2)
  2. matrix(c(10, 20, 300, 400, 50, 60), nrow = 2)
  3. matrix(c(10, 200, 30, 400, 50, 600), nrow = 2) (correct answer)
  4. matrix(c(10, 30, 50, 200, 400, 600), nrow = 2)
Explanation: When R multiplies a matrix by a vector, it doesn't multiply column-by-column or row-by-row in the way you might expect — it uses element-wise recycling along the matrix's underlying column-major storage. This means R first "unrolls" the matrix into a flat vector, applies the recycling rule, then reshapes it back. For m <- matrix(1:6, nrow = 2), the matrix is stored internally as [1, 2, 3, 4, 5, 6] (filled column by column), giving you a 2×3 matrix where column 1 is [1,2], column 2 is [3,4], and column 3 is [5,6]. When you multiply by c(10, 100), R recycles that two-element vector across all six positions: positions 1,3,5 get multiplied by 10, and positions 2,4,6 get multiplied by 100. That yields [10, 200, 30, 400, 50, 600], which reshaped into a 2×3 matrix is exactly answer C. Answer A is wrong because it implies multiplying every element by 10 — it ignores the second value (100) in the vector entirely. Answer B suggests the vector was applied column-by-column (first column ×10, second column ×100, third column left as-is), which isn't how R handles this. Answer D rearranges the correct values but places them in the wrong positions — perhaps from assuming row-major rather than column-major order. The key study tip: whenever you see matrix arithmetic with a short vector in R, always think column-major recycling. Unroll the matrix top-to-bottom, left-to-right by columns, apply the repeating vector, then re-roll it back.

Question 8

Consider the following R expression:

c(2, 4, 6, 8, 10) + c(1, 3)

Which result is produced?

  1. c(3, 7, 7, 11, 11), together with a warning about incompatible lengths (correct answer)
  2. c(3, 7, 7, 11, 11), with no warning because recycling is always silent
  3. c(3, 7, 9, 11, 13), together with a warning about incompatible lengths
  4. An error occurs before any result because the operands have different lengths
Explanation: When you perform arithmetic between two vectors of different lengths in R, the language applies vector recycling: the shorter vector is repeated (recycled) from its beginning until it matches the length of the longer vector. Understanding both how recycling works and when R warns you about it is exactly what this question tests. Here, c(1, 3) gets recycled to match length 5, becoming c(1, 3, 1, 3, 1). The addition then proceeds element-wise: 2+1, 4+3, 6+1, 8+3, 10+1=3,7,7,11,112+1,\ 4+3,\ 6+1,\ 8+3,\ 10+1 = 3, 7, 7, 11, 11 Crucially, because 5 is not a multiple of 2, R cannot recycle cleanly — and it issues a warning: "longer object length is not a multiple of shorter object length." That makes A correct. B is wrong because recycling is not always silent. R only suppresses the warning when the longer vector's length is an exact multiple of the shorter one (e.g., length 4 recycling length 2). A non-multiple length always triggers the warning. C describes a sequential addition — as if R simply added 1 to every element — which is not how recycling works. The result 3, 7, 9, 11, 13 would only appear if c(1, 3) were treated as c(1, 1, 1, 1, 1) or similar, which it isn't. D is wrong because R does not throw an error for length mismatches between vectors — it recycles and warns, but always produces a result. Study tip: Remember the recycling rule in two parts: (1) shorter vector repeats from position 1, and (2) a warning fires only when lengths are non-multiples. Both parts are frequently tested together.

Question 9

Given the following code, what is assigned to result?

x <- c(-2, 0, NA, 3)

result <- ifelse(x > 0, $x^2$, -x)

  1. c(2, 0, NA, 9), because an unknown test produces an unknown result (correct answer)
  2. c(2, 0, 0, 9), because the false branch is used for an unknown test
  3. c(-4, 0, NA, 6), because both branches are combined element by element
  4. c(4, 0, NA, 9), because both negative and positive values are squared
Explanation: When working with ifelse() in R, the key concept to understand is that it operates element-wise across a vector, applying the condition, true branch, and false branch to each element independently — including how it handles NA values. For x <- c(-2, 0, NA, 3), the condition x > 0 evaluates to c(FALSE, FALSE, NA, TRUE). Here's the element-wise breakdown: -2 > 0 is FALSE, so -(-2) = 2; 0 > 0 is FALSE, so -(0) = 0; NA > 0 is NA (unknown); and 3 > 0 is TRUE, so 3^2 = 9. This gives c(2, 0, NA, 9), confirming that A is correct — an unknown test (NA) produces an unknown result. Choice B is tempting but wrong. It assumes NA in the condition defaults to the false branch, returning -NA = 0. R doesn't make that assumption; it preserves uncertainty. Choice C reflects a fundamental misunderstanding — ifelse() does not combine both branches mathematically. Each element gets either the true result or the false result, never both. Choice D incorrectly applies the true branch (x2`x^2) to negative values, squaring -2to get4, which would only happen if the condition for -2` were TRUE — it isn't. A useful rule of thumb: NA is contagious in logical tests. Whenever a condition can't be evaluated, ifelse() returns NA for that position, regardless of what either branch would return.

Question 10

Consider these named vectors:

x <- c(a = 10, b = 20)

y <- c(b = 1, a = 2)

What does x + y return?

  1. c(a = 12, b = 21), because matching names determine which elements are added
  2. c(a = 11, b = 22), because elements are added by position and names come from x (correct answer)
  3. c(b = 11, a = 22), because elements are added by position and names come from y
  4. An error, because vectorized arithmetic requires identical name ordering in both operands
Explanation: When R performs arithmetic on named vectors, it operates by position, not by name matching — and it inherits the names from the left-hand operand. This surprises many students who assume R aligns elements by their names the way it does in some other operations. Here's what actually happens with x + y: R adds element 1 of x (which is a = 10) to element 1 of y (which is b = 1), giving 11. Then it adds element 2 of x (b = 20) to element 2 of y (a = 2), giving 22. The result takes its names from x, producing c(a = 11, b = 22) — confirming that B is correct. Choice A describes name-based matching, which would pair a with a and b with b, yielding c(a = 12, b = 21). This is a natural assumption, but it's not how vectorized arithmetic works in R — name matching behavior belongs to operations like merging or indexing, not +. Choice C gets the positional logic right but incorrectly assumes names come from the right-hand operand y; R always pulls names from the left side. Choice D is flatly wrong — R does not throw an error when name orderings differ, it simply ignores the name mismatch and proceeds positionally. A useful rule of thumb: position drives the math, left operand drives the names. When you see named vector arithmetic on the exam, ask yourself where the names originate (always the left vector) and remember that order in memory — not name labels — determines which values get paired.