Primer 4 · The dplyr verbs
Five functions that do most of the work
This page runs R inside your browser. It needs about 22 MB on first load (the R engine, the machinery that runs your code, and dplyr and tibble) and a minute or two on a slow connection. If nothing happens after that, your network is probably blocking it. Work through the R primer in RStudio instead.
There is no Stop button. If you write a loop that never ends, reload the page.
Five verbs. Almost every table you will ever need is some arrangement of these.
| Verb | Does what | Changes |
|---|---|---|
filter() |
keeps rows that match a condition | fewer rows |
select() |
keeps columns you name | fewer columns |
mutate() |
adds or changes a column | more columns |
group_by() |
splits into groups | nothing visible |
summarise() |
collapses each group to one row | far fewer rows |
The dataset
We use one small cohort throughout this page. Twelve people, followed up for a cardiovascular event.
Run the cells from the top. The first one builds the data that all the others use. If a later cell says object not found, you have skipped it.
years_fu is how long each person was followed. It is shorter for some people, because they had the event or were lost to follow-up.
Add those years up and you have person-time: the total time the group was actually watched. It is what you divide by when people were followed for different lengths. Primer 6 is about that.
filter(): keep rows
The condition goes inside. Note ==, two equals signs, for “is equal to”. One equals sign is assignment and will not work here.
Several conditions, separated by commas, means and:
smoker on its own works because it is already TRUE/FALSE. Writing smoker == TRUE is the same thing, more typing.
For or, use |.
For “is it one of these”, use %in%. It reads better than 3 |s in a row:
filter() drops NA rows without telling you
If age is missing for someone, age >= 50 is NA, not TRUE. That row disappears from filter(age >= 50) and from filter(age < 50).
Your 2 groups then do not add up to your total, and nothing warns you. Count your rows before and after every filter.
select(): keep columns
mutate(): add a column
The new column is computed from existing ones, for every row at once.
if_else() takes three things: the test, what to use when it is true, what to use when it is false. For more than two categories, case_when() does the same job with as many branches as you like:
Read each line as “if this, then that”. The first matching line wins, so order matters: a 35-year-old is caught by the first line and never reaches the second.
group_by() and summarise(): the pair that does the work
group_by() looks like it does nothing. summarise() then collapses each group to a single row.
Look at risk. event is TRUE/FALSE, so sum() counts the events and mean() gives the proportion who had one.
That proportion is the risk in each district. That is a stratified analysis, in 4 lines.
n() counts rows in the group and takes no arguments. Put it in every summarise() you write.
A mean over 3 people and a mean over 300 look identical on the page. The denominator is the only thing that tells them apart.
Putting it together
Two risks: one for smokers, one for non-smokers, among people aged 40 and over. Divide one by the other and you have a risk ratio. That is Primer 7.
What you should now be able to do
- Keep rows with
filter(), and remember thatNArows vanish from both sides. - Keep or drop columns with
select(). - Add a computed column with
mutate(), usingif_else()orcase_when(). - Produce a grouped table with
group_by()andsummarise(), always includingn(). - Get a risk from
mean()of a logical column, and a rate by dividing by person-time instead.