%$% : upping your pipe game


What do I do when %>% doesn’t work?


I love the %>%  pipe. Originally from magrittr, it’s now characteristic of tidy code. Using %>% has revolutionized how I write code in R (pssst! coming soon: an interactive pipe tutorial!). But sometimes the basic pipe falls short.

table() is one of my favorite functions for exploring data in R: it creates a frequency table of values in a vector. I use table() to do sanity checks on my data, make sure that all factor levels are present, and generally get a sense of how my observations are distributed.

A while back, though, I noticed that table() didn’t play nice with the %>% pipe.

I’ve collected some data on my friends’ pets. Here it is (using pseudonyms, in case anyone has a secret pet they don’t want the world to know about…).

This is one of the cats in the data frame below. She would like to hold your hand.
# Load magrittr
> library(magrittr)

# Create data
> pets <- data.frame(friend = c("Mark", "Mark", "Kyle", "Kyle", "Miranda", "Kayla", "Kayla", "Kayla", "Adriana", "Adriana", "Alex", "Randy", "Nancy"),
                   pet = c("cat", "cat", "cat", "cat", "cat", "dog", "cat", "lizard", "cat", "cat", "dog", "dog", "woodpecker"),
                   main_pet_color = c("brown", "brown", "multi", "multi", "brown", "brown", "brown", "orange", "black", "white", "multi", "white", "multi"))

# Look at the data
> pets
    friend        pet main_pet_color
1     Mark        cat          brown
2     Mark        cat          brown
3     Kyle        cat          multi
4     Kyle        cat          multi
5  Miranda        cat          brown
6    Kayla        dog          brown
7    Kayla        cat          brown
8    Kayla     lizard         orange
9  Adriana        cat          black
10 Adriana        cat          white
11    Alex        dog          multi
12   Randy        dog          white
13   Nancy woodpecker          multi

Unsurprisingly, it looks like there are a lot of cats and dogs! There are also a lot of brown pets and a lot of multicolored ones. Let’s say I want to see a frequency table of the pet colors. I know that I can do this with table(), like so:

# Make a frequency table of pet colors
> table(pets$main_pet_color)
 black  brown  multi orange  white 
     1      5      4      1      2

But if I want to use tidy syntax, I’d try to do it this way instead:

# Make a frequency table of pet colors
> pets %>% table(main_pet_color)
Error in table(., main_pet_color) : object 'main_pet_color' not found

What’s up with this? The syntax should work. pet is definitely a valid variable name in the data frame pets, and if I had used a different function, like arrange(), I would have had no problems:

# Arrange the data frame by pet color
> pets %>% arrange(main_pet_color)
    friend        pet main_pet_color
1  Adriana        cat          black
2     Mark        cat          brown
3     Mark        cat          brown
4  Miranda        cat          brown
5    Kayla        dog          brown
6    Kayla        cat          brown
7     Kyle        cat          multi
8     Kyle        cat          multi
9     Alex        dog          multi
10   Nancy woodpecker          multi
11   Kayla     lizard         orange
12 Adriana        cat          white
13   Randy        dog          white

So why doesn’t this work with table()?? This problem has driven me crazy on several occasions. I always ended up reverting back to the table(pets$main_pet_color) syntax, but I was not happy about it.

Turns out, there’s a simple fix.


Introducing… a new pipe! %$% is called the “exposition pipe,” according to the magrittr package documentation, and it’s basically the tidy version of the with() function, which I wrote about previously.

If we simply swap out %>% for %$% in our failed code above, it works!

# Make a frequency table of pet types
> pets %$% table(main_pet_color)
 black  brown  multi orange  white 
     1      5      4      1      2

Important note: Make sure you have magrittr loaded if you want to use this pipe. dplyr  includes the basic %>%, but not the other magrittr pipes.

Why it works

The traditional pipe, %>%, works by passing a data frame or tibble into the next function. But that only works if the function you’re piping to is set up to take a data frame/tibble as an argument!

Functions in the tidyverse, like arrange(), are set up to take this kind of argument, so that piping works seamlessly. But many base R functions take vectors as inputs instead.

That’s the case with table(). When we write table(pets$main_pet_color), the argument pets$main_pet_color is a vector:

# This returns a vector
> pets$main_pet_color
 [1] brown  brown  multi  multi  brown  brown  brown  orange black  white  multi  white 
[13] multi 
Levels: black brown multi orange white

When we try to pass pets into table() with the pipe, table() expects a vector but gets a data frame instead, and it throws an error.

The %$% pipe  “exposes” the column names of the data frame to the function you’re piping to, allowing that function to make sense of the data frame that is passed to it.


The exposition pipe is great for integrating non-tidyverse functions into a tidy workflow. The outcome for me is that I can finally make frequency tables to my heart’s content, without “code switching” back from tidy to base R syntax.

Congrats, you made it to the end! Here are some more cats for you.


magrittr has a couple other pipes, too: %T% and %<>%. The package also has some nice aliases for basic arithmetic functions that allow them to be incorporated into a chain of pipes. To read more about these magrittr options, scroll to the bottom of the magrittr vignette. And maybe I’ll post about them later!

Note: The image at the top of this post was modified from the magrittr documentation.

17 thoughts on “%$% : upping your pipe game”

  1. Interesting, although do not that the pipe is not really dataframe-specific. Instead of coming up with a new operator you could simply:

    pets %>%

    Liked by 1 person

    1. Good point! All of these are valid solutions. I personally find %$% a little more compact and easier to read, but that’s a matter of personal preference. (Also note that I didn’t come up with the exposition pipe! All credit absolutely goes to the magrittr developers.)


  2. Didn’t know there was something like an ‘exposition pipe’ – awesome! Thanks for pointing it out.
    I stumbled upon the same problem some time ago and found yet another solution, without having to load magrittr: The dplyr package contains the function pull(), which extracts a single variable from a data frame and returns it as a vector. To achieve the same as in your example, you could use it like this:
    pets %>% pull(main_pet_color) %>% table()

    Liked by 1 person

      1. {} allows to put “.” in the place you want to. for example let f(x,y) be a function of 2 parameters, what if you want to pass y to f second argument with pipes? you use {}: y%>%{f(x,.)}
        I use {} and “.” here to access a column of the df that previously enter the pipe. I use this trick a lot in my scripting.

        Liked by 1 person

  3. Thank you! I needed this. Its really kind of pace-breaking to use the table function. The other uncomfortable vector craving bastard is the base plot function, but this works here aswell.

    mtcars %$% plot(cyl,disp)

    unfortunetly you understandably can’t pipe on fx:
    mtcars %$% plot(cyl,disp) %$% points(cyl,hp,col = “red”,pch = 2)

    however two rows work, which is ay-alright:
    mtcars %$% plot(cyl,disp)
    mtcars %$% points(cyl,hp,col = “red”,pch = 2)


    1. Oof, I’ll admit that I avoid base plotting like the plague. Never really learned how to do it, and my workflow is so dependent on ggplot now that I don’t think I’m going back. But I’m glad this works! I’m all for cross-compatibility.

      Liked by 1 person

  4. If you just give `table` a data.frame, it’ll make a table where each column of the data frame becomes a different dimension.

    For example, your table of colors could be made like this (looks a lot like Roman’s answer):

    pets %>% select(main_pet_color) %>% table()

    But you can also do cross-tabulation of friends and colors:

    pets %>% select(friend, main_pet_color) %>% table()


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s


Don't need to be cool to be kind.


coding, one acorn at a time


coding, one acorn at a time

Create your website with WordPress.com
Get started
%d bloggers like this: