What this quiz covers
This quiz focuses on Data Frames, giving you a quick way to practice the rules, question types, and explanations that matter most for R Programming.
Consider these data frames with row names in different orders:
left <- data.frame(x = c(1, 2), row.names = c('r1', 'r2'))
right <- data.frame(y = c(20, 10), row.names = c('r2', 'r1'))
out <- cbind(left, right)
Which description of out is correct?
r1 has x = 1, y = 10, because the rows are aligned by row name.r1 has x = 1, y = 20, because the columns are combined by row position.r2 has x = 1, y = 20, because the right row names replace the left names.R Programming Quiz
Practice Data Frames 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 Data Frames, 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.
Consider these data frames with row names in different orders:
left <- data.frame(x = c(1, 2), row.names = c('r1', 'r2'))
right <- data.frame(y = c(20, 10), row.names = c('r2', 'r1'))
out <- cbind(left, right)
Which description of out is correct?
r1 has x = 1, y = 10, because the rows are aligned by row name.r1 has x = 1, y = 20, because the columns are combined by row position. (correct answer)r2 has x = 1, y = 20, because the right row names replace the left names.cbind() in R, the key thing to understand is that it combines objects purely by position, not by matching row names or any other metadata. This is a common point of confusion because functions like merge() do align by key values — but cbind() is not merge().
Here's what happens step by step: left has rows in order r1, r2 with x = 1, 2. right has rows in order r2, r1 with y = 20, 10. When cbind() runs, it simply places column y next to column x based on row position. Position 1 of left (r1, x=1) gets paired with position 1 of right (r2, y=20). The result keeps left's row names, so r1 ends up with x = 1, y = 20. That confirms C is correct.
A is wrong because cbind() doesn't validate or require matching row-name order — it won't throw an error. B describes behavior you'd expect from merge() with a key column, not cbind(). The idea that rows are "aligned by row name" is the most tempting distractor, so watch out for it. D is wrong on two counts: the right-side row names don't replace the left-side names (left's names are preserved), and the values described don't match what positional binding produces.
A useful rule of thumb: whenever you see cbind(), think "zip together by position, like a zipper." If you need row-aware alignment, you need merge() instead.Examine the following sequence of operations:
d <- data.frame(x = 1:3, y = c(10, 20, 30))
d$flag <- FALSE
d$sum <- d$x + d$y
d$y <- NULL
Which statement is correct after all operations have completed?
d has 3 rows and 3 columns, and d$sum becomes NULL with d$y.d has 3 rows and 2 columns, and d$sum is c(11, 22, 33).d has 1 row and 3 columns, and d$sum is c(11, 22, 33).d has 3 rows and 3 columns, and d$sum is c(11, 22, 33). (correct answer)d <- data.frame(x = 1:3, y = c(10, 20, 30)), you have 3 rows and 2 columns. Adding d$flag <- FALSE appends a third column (R recycles FALSE across all rows), giving you 3 rows and 3 columns. Then d$sum <- d$x + d$y computes element-wise addition — 1+10, 2+20, 3+30 — storing c(11, 22, 33) as a fourth column. Finally, d$y <- NULL drops the y column entirely, leaving you with x, flag, and sum — still 3 rows, now 3 columns. That makes D correct.
A is wrong on two counts: it incorrectly claims d$sum becomes NULL when y is removed. Since d$sum was already computed and stored as its own column before y was deleted, it persists independently. Dropping y has no retroactive effect on sum.
B gets d$sum right but miscounts the columns. After removing y, you still have x, flag, and sum — that's 3 columns, not 2.
C is entirely off — no operation here collapses rows. Row count never changes unless you explicitly subset or aggregate the data frame.
As a study tip, remember that in R, d$sum <- d$x + d$y captures the values at that moment — it's not a live formula. Deleting a source column afterward never affects already-computed columns.Two data frames have the same column names in different orders:
first <- data.frame(id = c('a', 'b'), value = c(1, 2))
extra <- data.frame(value = 3, id = 'c')
out <- rbind(first, extra)
Which statement correctly describes out?
id, value, and its third row contains 3, 'c'.extra lists the matching columns in a different order.id, value, and its third row contains 'c', 3. (correct answer)value, id, and its first two rows are reordered to match extra.rbind() to combine data frames in R, the function matches columns by name, not by position. This means the order of columns in the second data frame doesn't need to match the first — R will automatically align them before binding the rows.
Here's what happens step by step: first has columns in order id, value. When extra (which has columns value, id) is bound using rbind(), R reorders extra's columns to match first's column order before appending the row. So the resulting data frame out keeps the original column order id, value, and the third row correctly contains 'c' in the id column and 3 in the value column — making C the correct answer.
A is wrong in a subtle but important way: it says the third row contains 3, 'c', which implies value comes before id. That would only be true if extra's original column order were preserved unchanged, which it isn't — R reorders to match first.
B is incorrect because rbind() does not require columns to be in the same order. It only requires that both data frames share the same column names, and R handles the alignment automatically.
D is wrong because rbind() never reorders the first data frame's columns to match the second. The column structure of the output is always anchored to the first argument.
As a study tip, remember: rbind() is name-aware, not position-aware — the first data frame's column order always wins.A data frame is subset using repeated, nonsequential row positions:
d <- data.frame(item = c('a', 'b', 'c'), qty = c(5, 6, 7))
out <- d[c(3, 1, 1), c('item', 'qty')]
Which statement correctly describes out?
item is c('a', 'c') and qty is c(5, 7).item is c('c', 'a', 'a') and qty is c(7, 5, 5). (correct answer)item is c('a', 'a', 'c') and qty is c(5, 5, 7).item is c('c', 'a', 'a') and qty is c(5, 7, 7).d[c(3, 1, 1), c('item', 'qty')] asks for row 3 first, then row 1 twice. Looking at the original data frame: row 1 is ('a', 5), row 2 is ('b', 6), and row 3 is ('c', 7). So the output has three rows in this order: row 3 → ('c', 7), row 1 → ('a', 5), row 1 again → ('a', 5). That gives item = c('c', 'a', 'a') and qty = c(7, 5, 5), confirming B is correct.
A is wrong because it returns only 2 rows, as if R deduplicated the index — but R never silently removes duplicate row requests. C makes the mistake of sorting the row indices before applying them (1, 1, 3 instead of 3, 1, 1), producing the right values but in the wrong order. D scrambles the qty column, pairing row 3's index with row 1's quantity and vice versa — the column values must always stay aligned with their rows.
A good study tip: whenever you see a row-index vector like c(3, 1, 1), trace through it left to right, one element at a time, and remember that R honors both the order and any repetitions you specify — no reordering, no deduplication.The following data frame is inspected with str(d):
d <- data.frame(active = c(TRUE, FALSE, TRUE), count = c(2L, 5L, 1L), label = c('p', 'q', 'r'), stringsAsFactors = FALSE)
Which description is consistent with the structure reported by str(d)?
str() on a data frame in R, it reports three key things: the number of observations (rows), the number of variables (columns), and the data type of each column. Understanding how R stores different kinds of data is essential here.
Look at how d is constructed. The active column uses TRUE/FALSE values, making it logical. The count column uses the L suffix (e.g., 2L), which explicitly declares integer storage — distinct from the default numeric (double) type. The label column uses character strings with stringsAsFactors = FALSE, so R stores them as character, not factor. The data frame has 3 rows and 3 columns, so str(d) will report 3 observations and 3 variables. This confirms A as correct.
Choice B is wrong because it misidentifies two columns: count is integer, not numeric (doubles), and label is character, not factor — the stringsAsFactors = FALSE argument explicitly prevents that conversion. Choice C is incorrect on both counts: the dimensions are inverted (3 columns, not 9 observations), and the types are not all character — R preserves each column's declared type. Choice D swaps the meanings of "variables" and "columns," which are the same thing in a data frame context, and gets the dimension labels backwards regardless.
A useful habit: whenever you see the L suffix in R code, mentally flag it as "integer, not numeric." Similarly, always check whether stringsAsFactors is set, since it controls whether character columns become factors — a distinction that frequently appears on R exams.Suppose d is created as follows:
d <- data.frame(code = c('x', 'y', 'z'), score = c(4, 7, 9))
Which statement about extracting data from d is correct?
d[2] returns a one-column data frame containing the score column. (correct answer)d[, 2] returns a one-column data frame containing the score column.d[[2]] returns a one-column data frame containing the score column.d[2, ] returns the second column as an atomic vector.d[2], treats the data frame like a list and returns a subset of the data frame — meaning the result is still a data frame, just with fewer columns. So d[2] returns a one-column data frame containing the score column. That makes A correct.
Here's where the traps come in. B uses d[, 2], which is two-dimensional indexing: leaving the row position blank (selecting all rows) and specifying column 2. This actually returns an atomic vector, not a data frame — R drops the data frame structure by default when you select a single column this way. C uses double brackets d[[2]], which also extracts the underlying column as an atomic vector, not a data frame — double brackets always strip the container structure. So both B and C have the return type exactly backwards from what the question states. D is doubly wrong: d[2, ] selects the second row (not column), and the result is a one-row data frame, not an atomic vector.
A handy mental rule: single brackets [] preserve the data frame structure; double brackets [[]] and two-dimensional indexing [, i] extract the raw vector inside. When you see extraction questions, ask yourself: "Does this return a data frame or a vector?" — the answer almost always hinges on which bracket style is used.Consider the following R code:
d <- data.frame(id = 1:4, group = c('A', 'B'))
Which statement correctly describes d?
group contains 'A', 'B', 'A', 'B'. (correct answer)id is truncated to contain only 1, 2.group contains 'A', 'A', 'B', 'B'.data.frame(), R doesn't throw an error — it recycles the shorter vector to match the length of the longer one. This recycling behavior is the core concept being tested here.
In this code, id = 1:4 produces four values (1, 2, 3, 4), while group = c('A', 'B') produces only two. Because 4 is an exact multiple of 2, R cleanly recycles c('A', 'B') twice, filling group with 'A', 'B', 'A', 'B'. The result is a data frame with 4 rows, making A the correct answer.
Answer B describes the opposite of what recycling does — R expands the shorter vector to match the longer one, not truncates the longer one to match the shorter. Your id column keeps all four values. Answer C reflects a common assumption that mismatched lengths always cause an error. They do in some contexts, but data.frame() permits recycling as long as the longer length is an exact multiple of the shorter one (if it's not, R will throw a warning or error). Answer D describes a "block" arrangement ('A', 'A', 'B', 'B') — that's not how recycling works. R cycles element-by-element through the shorter vector, not in chunks, so you get alternating values.
A handy rule to remember: recycling goes element-by-element, always restarting from the beginning of the short vector. Watch for questions involving vectors or data frames with mismatched lengths — recycling quietly happens more often than beginners expect.A data frame is created with a non-syntactic column name:
d <- data.frame('unit price' = c(2.5, 3), qty = c(4L, 2L), check.names = FALSE)
Which expression returns the numeric vector c(10, 6)?
d[['unit price']] * d[['qty']] (correct answer)d[['unit.price']] * d[['qty']]d$unit price * d$qtyd['unit price'] * d['qty']$, single-bracket [ ], and double-bracket [[ ]] subsetting.
The data frame was created with check.names = FALSE, which means R preserves the column name exactly as "unit price" — with the space intact. Double-bracket notation [[ ]] accepts a character string and returns the underlying vector directly. So d[['unit price']] correctly retrieves the vector c(2.5, 3), and d[['qty']] retrieves c(4L, 2L). Multiplying them element-wise gives c(10, 6). A is correct.
Choice B fails because d[['unit.price']] looks for a column literally named "unit.price" (with a dot), which doesn't exist — it returns NULL, and multiplying NULL by anything gives NULL, not a numeric vector.
Choice C is a syntax error. The $ operator cannot handle names with spaces unquoted — d$unit price is invalid R code and will throw a parse error before it even runs.
Choice D uses single-bracket notation d['unit price'], which returns a data frame with one column, not a vector. Multiplying two single-column data frames with * doesn't produce the simple vector c(10, 6) — you'd get a data frame, not a numeric vector.
Study tip: Remember that [[ ]] extracts a vector (one level down), while [ ] keeps the data frame structure. For non-syntactic names, always use [[ ]] with a quoted string.Consider the following filtering operation:
d <- data.frame(id = c('a', 'b', 'c', 'd'), score = c(80, NA, 60, 90))
kept <- d[d$score >= 70, ]
What is the value of kept$id?
c(NA, 'a', 'd'), because unknown row indices are moved before true indices.c('a', 'd'), because an unknown comparison is automatically treated as false.c('a', 'b', 'd'), because the original row is retained with its identifier.c('a', NA, 'd'), because the unknown comparison produces an unknown row index. (correct answer)NA values — this is one of R's most common sources of subtle bugs.
When R evaluates d$score >= 70, it produces a logical vector for each row. The scores are 80, NA, 60, and 90, so the result is c(TRUE, NA, FALSE, TRUE). Notice the NA doesn't become FALSE — R genuinely doesn't know whether an unknown value is ≥ 70, so it preserves that uncertainty. When you use this vector to index the data frame, TRUE rows are kept, FALSE rows are dropped, and NA rows are included but with all their values replaced by NA. That's why kept$id becomes c('a', NA, 'd') — confirming that D is correct.
A is wrong because R doesn't reorder rows by moving unknowns before true indices. Row order is always preserved; the NA simply appears in the position of the original NA-comparison row.
B is wrong because R does not silently coerce NA to FALSE during logical indexing. If it did, NA >= 70 would filter out that row entirely, but R intentionally keeps it as ambiguous.
C is wrong because retaining the row with its original 'b' identifier would imply R confirmed the row belongs in the result — it hasn't. The id becomes NA precisely because the entire row is uncertain.
A helpful rule of thumb: NA is contagious. Any operation involving NA produces NA, and logical indexing is no exception. When you need to drop NA rows intentionally, use na.omit() or filter with !is.na() first.Consider the following subset, for which no row satisfies the condition:
d <- data.frame(a = 1:3, b = 4:6, c = 7:9)
z <- d[d$a > 10, c('a', 'c')]
Which set of inspection results is correct?
nrow(z) is 0, ncol(z) is 0, and length(z) is 0.nrow(z) is 0, ncol(z) is 2, and length(z) is 2. (correct answer)nrow(z) is 3, ncol(z) is 2, and length(z) is 2.nrow(z) is 0, ncol(z) is 2, and length(z) is 0.d$a > 10 matches no rows (since a contains only 1, 2, 3), so z is a data frame with zero rows. However, the column selection c('a', 'c') is still applied, meaning z retains exactly 2 columns. This makes B correct: nrow(z) is 0, ncol(z) is 2, and length(z) is 2.
That last point — length(z) being 2 — is the key insight. For data frames, length() returns the number of columns, not the number of rows or total elements. Since z has 2 columns, length(z) is 2, regardless of how many rows exist.
A is wrong because it assumes the entire structure collapses to nothing. R preserves column definitions even with zero matching rows. C is wrong because it claims nrow(z) is 3 — no rows satisfy d$a > 10, so you get zero rows, not the original three. D is a subtle trap: it gets nrow and ncol right but incorrectly sets length(z) to 0, confusing length() with row count.
Remember: for data frames, length() = number of columns, and an empty row filter never destroys column structure.