Primer 4 · The dplyr verbs

Five functions that do most of the work

ImportantIf the cells below never become ready

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:

Warningfilter() 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 that NA rows vanish from both sides.
  • Keep or drop columns with select().
  • Add a computed column with mutate(), using if_else() or case_when().
  • Produce a grouped table with group_by() and summarise(), always including n().
  • Get a risk from mean() of a logical column, and a rate by dividing by person-time instead.

→ Primer 5: your first plot

Back to top