Subsetting and Replacement
You are tutoring a small weekend R study group. Five friends, Mara, Dev, Ada, Theo and Iris, just took a quiz graded out of 100, and you have their scores: 58, 91, 73, 49 and 84. Almost everything you will ever do with data comes down to two moves. First, reaching in to pull out the pieces you want: the top scorers, the ones who need help, one person's result. Second, writing new values back in: fixing a mis-marked answer, applying a grade floor. Those two moves are subsetting (reading a part) and replacement (writing a part), and this lesson is about doing both cleanly.
By the end of this lesson you will be able to:
- Pull elements out of a vector with
[using positions, a "drop these" minus sign, a TRUE/FALSE condition, and names - Tell
[(keeps the container) apart from[[and$(hand you the one thing inside) - Subset a data frame by rows and columns, and overwrite exactly the values you choose
Prerequisites: you can build a vector, a list and a data frame, and you have met R's comparison operators like >= and ==.
The square bracket picks elements out
The single square bracket, [ ], is how you reach into a vector and pull elements out. You write the vector, then in the brackets you say which elements you want. The simplest "which" is a position number.
First, build the scores once. Each lesson runs in a fresh R session, so we create the data right here (run this):
These scores have names (the friends), printed above each value. Now pull Dev out. Dev is the 2nd element, so ask for position 2:
To pull out several at once, put a vector of positions inside the brackets. c(1, 3) gives the 1st and 3rd; the colon 2:4 is shorthand for "2, 3, 4":
scores[i] returns a vector, the same kind of thing you started with, just shorter. It can hold zero, one, or many elements depending on what you ask for. That "keeps the container" idea matters later when we meet the double bracket.Three more ways to say "which"
A position number says "give me this one." Three other kinds of index are far more useful in practice.
A minus sign drops positions. scores[-4] means "everything except the 4th." The minus is not a value to look up; it removes that position. This is how you say "all the others":
A TRUE/FALSE vector keeps the TRUEs. This is the workhorse. A comparison like scores >= 60 produces one TRUE or FALSE per element. Put that vector in the brackets and R keeps only the elements lined up with a TRUE:
A name selects by name. Because the scores are named, you can ask for "Dev" directly instead of counting to position 2:
scores >= 60) and it always lines up.Which line returns the passing scores?
You want the scores themselves of everyone who passed (60 or more), not just a column of TRUE/FALSE. Which line does that?
scores >= 60 first.Find the students who need help
The friends who scored below 60 need a follow-up session. Replace the blank so the line returns just their scores from scores. (You should get back Mara and Theo.)
Show answer
scores[scores < 60]
#> Mara Theo
#> 58 49Single bracket vs double bracket
So far [ ] always handed back a vector. But sometimes you want the one element inside, not a smaller container holding it. That is the difference between the single bracket [ ] and the double bracket [[ ]].
Think of a spice rack. rack[1] gives you the rack with only the first jar in it, still a rack. rack[[1]] reaches in and hands you the jar itself. Lists make the difference visible. Build one student's record, which mixes a name, a vector of three quiz attempts, and a TRUE/FALSE:
They look similar but are different types. Ask R what each one is:
The dollar sign $ is a friendly shorthand for [[ by name. These two lines are identical, and you can keep drilling, here taking the 2nd quiz attempt:
$ only works with a name you type literally. If the name is stored in a variable, $ will not use it, so reach for [[: with col <- "scores", use student[[col]], not student$col. Also, $ on a name that does not exist returns NULL with no error, an easy bug to miss.Subsetting a data frame
A data frame is the table you will spend most of your time in. It is subset with two indices inside one bracket pair: df[rows, cols], rows first, columns second. Build the gradebook, then take a slice:
Leave a slot blank to mean "all of them." gradebook[1, ] is the whole first row; gradebook[, "score"] is the whole score column. A single column comes back as a plain vector, while $ and [[ do the same:
The logical-index trick scales straight up to rows: a condition in the row slot filters the table. That is the line from the cover:
gradebook[, "score"] drops it to a vector. If you need it to stay a one-column data frame, say gradebook[, "score", drop = FALSE]. The single bracket gradebook["score"] (no comma) also keeps it a data frame.Plain vector, or one-column table?
From the gradebook data frame, which expression gives you the score column as a plain numeric vector, not a one-column data frame?
gradebook$score) reaches in and hands back the column itself, a numeric vector.drop = FALSE deliberately KEEPS it a one-column data frame, the opposite of what you want here.Replacement: a subset on the left of the arrow
Here is the move that ties everything together. Any subset you can read, you can also write to by putting it on the left of the assignment arrow <-. R writes the right-hand values into exactly the positions the subset picked out.
Start with a department rule: nobody scores below 60. Select the failing scores with the same logical index as before, but this time assign to them. The single value 60 on the right is recycled to fill every matched position:
Mara (58) and Theo (49) were both pulled up to 60; everyone else was untouched. You can also write to a single element by name, say Theo's quiz was re-marked to 67:
The same idea adds a new column: assign to a column name that does not exist yet and R creates it. Here ifelse() returns "pass" or "fail" per row:
Apply a grade floor to the table
The same 60-point floor now has to be applied to the **gradebook's score column**, not the standalone scores vector. Select the failing scores inside the column and assign 60 to them. Replace the blank, then check. (Mara and Theo should land on 60.)
Show answer
gradebook$score[gradebook$score < 60] <- 60
gradebook$score
#> [1] 60 91 73 60 84References
A few authoritative places to take subsetting further, all free:
- Advanced R (2e): Subsetting - the definitive treatment of
[,[[and$, including the spice-rack distinction. - An Introduction to R: Index vectors - the official manual on the four ways to select a subset.
- The R Language Definition: Indexing - the precise, formal rules for every index type and for replacement.
- Hands-On Programming with R: Modifying values - a gentle, beginner-first walk through
x[i] <- value.
Lesson 1 complete
You can now reach into any R object and change it. You pulled elements from a vector by position, with a "drop these" minus sign, with a TRUE/FALSE condition, and by name; you separated [ (which keeps the container) from [[ and $ (which hand you the one thing inside); you sliced a data frame by rows and columns; and you put any subset on the left of <- to overwrite exactly the values you chose, including a whole column at once.
Next, Lesson 2: Control Flow in R. Subsetting let you pick the right rows; control flow lets your code make decisions and repeat work, with if/else to branch and for/while loops to run a step many times, watched one iteration at a time.