Skip to Content
Course content

15: Subsetting and Indexing

Click on the "Edit" button in the top corner of the screen to edit your slide content.

A few years ago, I was reviewing a PR for a colleague who was analyzing a dataset of 50,000 sensor readings. They were trying to isolate all the "error" readings where the voltage spiked above a certain threshold. The code looked correct at a glance, but when they tried to run a summary function on the result, R threw a cryptic error saying the object wasn't a data frame anymore. It turned out they had subsetted the data down to a single column, and R—in its infinite, helpful wisdom—had automatically "simplified" that data frame into a plain vector. They spent half their afternoon chasing a bug that was actually just a quirk of how R handles indexing. It's a rite of passage for R developers, and it's exactly why we need to be precise about how we grab our data.

The Magic of Square Brackets

At its core, subsetting is just telling R, "I don't want the whole thing; I just want these specific pieces." For vectors, we use the square brackets []. If you pass an integer, you get the element at that position. But here is the first thing to remember: R is 1-indexed, not 0-indexed. If you're coming from Python or Java, this will feel wrong for a while. I still occasionally catch myself trying to start at 0.

# Imagine we have a vector of daily temperatures
temps <- c(72, 75, 68, 82, 90, 71)

# Grab the first and third days
temps[c(1, 3)] 

# Grab everything EXCEPT the fifth day using a negative index
temps[-5]

Negative indexing is a shorthand I use constantly. Instead of figuring out which indices you do want, it's often easier to just tell R which one to throw away.

Filtering with Logical Vectors

Hard-coding index numbers is rarely useful in the real world because your data changes. You don't want the "5th element"; you want the "elements where the value is greater than 80." This is where logical subsetting comes in. When you put a logical comparison inside the brackets, R evaluates it for every element and only returns the TRUE ones.

# Find all days where it was hotter than 80 degrees
hot_days <- temps[temps > 80]
# Result: 82, 90

This is incredibly powerful because you can combine conditions. Using & (AND) and | (OR), you can carve out very specific slices of your data. Just be careful with your parentheses; R's order of operations can sometimes lead to surprising results if you're mixing logical operators.

Navigating Data Frames and the Single-Column Trap

Data frames are just lists of vectors, which means they are two-dimensional. Consequently, the square brackets now expect two arguments: [row, column]. If you leave one blank, R assumes you want everything in that dimension.

# Let's use a small sales data frame
sales <- data.frame(
  item = c("Latte", "Mocha", "Espresso", "Tea"),
  price = c(4.50, 5.00, 3.00, 3.50),
  qty = c(10, 12, 20, 15)
)

# Get the 2nd row, all columns
sales[2, ]

# Get all rows, but only the 'price' column (column 2)
sales[, 2]

Now, here is that "trap" I mentioned earlier. When you subset a data frame to a single column using sales[, 2], R converts the result into a vector. This is called "dropping." If your subsequent code expects a data frame, it will crash. To prevent this, use drop = FALSE. It forces R to keep the result as a data frame, regardless of how small the subset is. It's a small addition to your code that saves hours of debugging.

# Keep it as a data frame, even though it's only one column
price_column <- sales[, 2, drop = FALSE]



📋 Practical Task

Filtering High-Value Coffee Orders

You have been handed a data frame containing a morning's worth of coffee shop transactions. Your goal is to extract a specific subset of this data for a manager's report.

# The Dataset
orders <- data.frame(
  order_id = 1:6,
  drink = c("Latte", "Cappuccino", "Flat White", "Latte", "Espresso", "Mocha"),
  amount = c(5.50, 4.75, 5.00, 5.50, 3.00, 6.25),
  customer_type = c("Member", "Guest", "Member", "Guest", "Member", "Member")
)

Your task: Create a new data frame called premium_members that contains only the rows where the customer_type is "Member" AND the amount is greater than 5.00. Ensure that your resulting object is still a data frame.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.