#The one-line difference
If you remember one thing, remember this: paste0(...) is paste(..., sep = ""). That is the whole story of r paste vs paste0. Both functions turn their arguments into character strings and glue them together. The difference is what sits between the pieces.
# paste puts a single space between arguments paste("Hello", "world") #> "Hello world" # paste0 puts nothing paste0("Hello", "world") #> "Helloworld"
That is it. Nothing deeper is hiding. Once you know the default separator flips, every other behavior lines up between the two because they share the same engine.
| paste() | paste0() | |
|---|---|---|
| Default sep | " " (one space) | "" (empty) |
| Takes collapse | Yes | Yes |
| Recycles vectors | Yes | Yes |
| Turns NA into "NA" | Yes | Yes |
| Added in | Always in base R | R 2.15.0 (2012) |
Both live in base R. No package needed.
#What paste() actually does
The signature is paste(..., sep = " ", collapse = NULL). The dots take any number of vectors. paste() converts each one to character, recycles the shorter vectors up to the length of the longest, joins the pieces position by position using sep, and returns a character vector.
# scalars: one string out paste("x", 1, "y") #> "x 1 y" # a vector: element-wise, sep between the two inputs paste(c("a", "b", "c"), 1:3, sep = "_") #> "a_1" "b_2" "c_3" # recycling: the short vector repeats paste("row", 1:3, sep = "-") #> "row-1" "row-2" "row-3"
The result here is a vector of length three, not one string. That trips people up. paste() joins across the arguments you pass, one output per position. To collapse the whole vector down into a single string, you need the collapse argument, which is a separate job covered below.
#What paste0() actually does
paste0() was added to base R in version 2.15.0 for one reason: people kept typing paste(x, y, sep = "") and it got old. Here is the real definition from base R, lightly trimmed:
paste0 <- function(..., collapse = NULL, recycle0 = FALSE)
paste(..., sep = "", collapse = collapse, recycle0 = recycle0)
So paste0() is not a separate implementation. It is paste() with the separator hard-set to empty. That is why the two behave identically everywhere except the space. Where paste0() earns its keep is anywhere a space would be wrong:
# numbered file names paste0("plot_", 1:3, ".png") #> "plot_1.png" "plot_2.png" "plot_3.png" # building a URL paste0("https://api.example.com/v1/users/", 42) #> "https://api.example.com/v1/users/42" # prefixing column names paste0("q", 1:4) #> "q1" "q2" "q3" "q4"
Every one of those would gain unwanted spaces with plain paste(). That is the daily case paste0() exists for.
#sep versus collapse, the real confusion
Most "why is my paste output weird" questions are not about paste vs paste0 at all. They are about mixing up sep and collapse. The two do different things and you can use both at once.
- sep goes between the arguments at each position. It does not change the length of the result.
- collapse runs after that and flattens the whole vector into one string, joining the elements with the collapse string.
x <- c("a", "b", "c")
paste(x, 1:3, sep = "-")
#> "a-1" "b-2" "c-3" (a vector of 3)
paste(x, 1:3, sep = "-", collapse = ", ")
#> "a-1, b-2, c-3" (one string)
paste(x, collapse = "")
#> "abc" (one string, no sep needed)
A clean way to hold it in your head: sep works sideways across arguments, collapse works downward across the vector. If your output has the wrong number of strings, the fix is almost always collapse, not sep.
#Which one should you use?
Pick by the default you want, so you type fewer arguments. A working rule:
- Use paste0() when the pieces should touch: file names, paths, URLs, SQL fragments, generated column or object names, HTML strings, any glue where a space is a bug.
- Use paste() when the output is meant to read like language: log messages, labels, sentences printed for a human. The space you get for free is the space you want.
- Use paste(..., sep = something) when the separator is neither empty nor a space, for example an underscore or a tab. paste0() cannot help you here since its separator is fixed.
One tip that outranks both for file paths: reach for file.path() instead of paste0(). It inserts the correct path separator for the operating system, so your code does not break when it moves between machines.
# fragile paste0("data", "/", "raw", "/", "file.csv") # portable file.path("data", "raw", "file.csv") #> "data/raw/file.csv"
#The gotchas both share
Because paste0() is paste(), these bite you either way.
NA becomes the string "NA". Neither function keeps a missing value missing. It gets coerced to the two characters N and A, which can silently pollute a column.
paste("value:", NA) #> "value: NA"
paste0("id", NA) #> "idNA"
Numbers get R's default formatting. If you paste a double, you get however R chooses to print it, which may not be the decimals you want. For money or fixed precision, format the number first with sprintf() or format().
paste0("Total: $", 5) #> "Total: $5"
sprintf("Total: $%.2f", 5) #> "Total: $5.00"
Empty input is not nothing. paste() with a zero-length vector and no collapse returns character(0), not "". If a downstream function expects a single string, that can throw an error you did not see coming. Setting collapse gives you back an empty string when that is what you meant.
For the common single-line case, sprintf() is often clearer than either paste function because the template shows the shape of the output at a glance: sprintf("user %s has %d posts", name, n).
#Pasting R code and output around
A small aside, since I do most of this on a Mac. The other kind of paste in an R workflow is the one where you copy a snippet out of the console and drop it into a script, or lift a data frame's output into a message to a colleague. If you have ever lost a chunk of code because the next copy overwrote it, a clipboard history helps more than it sounds like it would. I wrote up the basics in how to copy on Mac, and compared the dedicated tools in the Paste clipboard manager review and the Maccy write-up. None of that changes what paste() does in R, but it changes how much friction there is in getting code and results in and out of your editor.
#Frequently asked questions
What is the difference between paste and paste0 in R?
The only difference is the default separator. paste() joins its arguments with a single space between them, because its default is sep = " ". paste0() joins them with nothing, because it calls paste() with sep = "". Recycling, the collapse argument and NA handling are identical, because paste0() is a thin wrapper around paste().
What is paste0 in R?
paste0() is a shortcut for paste(..., sep = ""). It concatenates its arguments into character strings with no separator between them. It was added in R 2.15.0 so you would not have to type sep = "" every time you wanted glued-together strings, which is the common case for file names, URLs and column names.
How do I use the paste() function in R programming?
Pass paste() one or more vectors. It converts each to character, recycles the shorter ones to a common length, and joins the pieces with sep (a space by default), returning a vector the length of the longest input. Add collapse = "," to then flatten that vector into a single string joined by commas.
Is paste0 faster than paste in R?
Marginally, and never enough to matter. paste0() skips inserting a separator, so on very large vectors it can be a hair quicker, but the gap is negligible in real code. Choose between them by which default separator you want, not by speed.