purrr map_dbl() in R: Return a Numeric Vector
The map_dbl() function in purrr applies a function to each element of a list or vector and returns a flat numeric (double) vector. It is the type-stable variant of map(): if the function returns anything other than a length-1 double, map_dbl() errors instead of failing silently.
map_dbl(x, mean) # apply, return doubles map_dbl(x, ~ .x ^ 2) # lambda formula map_dbl(x, \(v) v ^ 2) # native anonymous function map_dbl(x, mean, na.rm = TRUE) # pass extra arguments map_dbl(records, "score") # pluck a numeric element map_dbl(x, length) # numeric summary per element map_dbl(df, ~ sum(.x > 0)) # count matches per column
Need explanation? Read on for examples and pitfalls.
What map_dbl() does in one sentence
map_dbl(x, fn) applies fn to every element of x and returns a numeric vector. Each call to fn must produce a single double value, so the result is always a flat, predictable atomic vector rather than a list.
This type guarantee is the whole point. Plain map() always returns a list, and base R's sapply() guesses the output type, which can silently hand you a list, a matrix, or a vector depending on the data. map_dbl() removes the guessing.
Syntax
map_dbl(.x, .f, ...) takes three things: input, function, and pass-through arguments. .x is a list or atomic vector, .f is the function or lambda to apply, and ... are extra arguments forwarded to .f.
All three styles are equivalent. The ~ .x lambda is purrr shorthand; the \(v) form is base R syntax and works anywhere.
Worked examples
Each example below uses a different input shape. Together they cover the four situations where map_dbl() shows up most: summarising columns, measuring list elements, extracting a value, and handling missing data.
1. Summarise every column of a data frame. A data frame is a list of columns, so map_dbl() iterates column by column.
2. Measure each element of a list. When list elements have different lengths, length() reduces each to a single number.
3. Pluck a numeric field from nested records. Passing a string as .f extracts that element from each item, like a vectorised $.
4. Forward extra arguments with .... Anything after .f is passed straight to the function on every call.
map_dbl(x, mean) is cleanest for a single named function, but map_dbl(df, ~ sum(.x > 0)) lets you compute something inline without defining a helper.map_dbl() vs map() and the other variants
Pick the variant that matches the type you expect back. All map_* functions iterate the same way; they differ only in what they return and how strictly they enforce it.
| Function | Returns | Errors if element is not... |
|---|---|---|
map() |
list | never errors (any type allowed) |
map_dbl() |
double vector | a length-1 double |
map_int() |
integer vector | a length-1 integer |
map_chr() |
character vector | a length-1 string |
map_lgl() |
logical vector | a length-1 TRUE/FALSE |
sapply() |
guessed (base R) | does not error, simplifies inconsistently |
The decision rule: if you know the output is numeric, use map_dbl(). If the per-element result is a count of whole numbers, map_int() is stricter. When the shape is unpredictable, fall back to map(), which never simplifies.
sapply(), one odd element can change the return type and break code three steps later. map_dbl() fails at the exact line where the assumption broke, which makes debugging far faster.Common pitfalls
Most map_dbl() errors trace back to the function returning the wrong thing. The fix is almost always to switch variants or reshape the per-element result.
Pitfall 1: the function returns more than one value. map_dbl() demands exactly length 1 per call.
Fix: use map() if you genuinely need multiple values per element, or summarise inside the lambda (~ sum(c(.x, .x))).
Pitfall 2: the function returns text. A character result cannot be coerced to a double.
Fix: use map_chr() when the result is text.
Pitfall 3: missing values silently propagate. If an element contains NA and your function does not drop it, you get NA back, not an error.
Fix: pass na.rm = TRUE through ... so every call drops missing values.
Try it yourself
Try it: Use map_dbl() to get the maximum value of each numeric column in iris[1:4]. Save the result to ex_maxes.
Click to reveal solution
Explanation: iris[1:4] drops the non-numeric Species column, leaving a list of four numeric columns. map_dbl() applies max() to each and returns a named double vector.
Related purrr functions
map_dbl() is one node in the purrr map family. These siblings cover the cases it cannot:
- map(): returns a list when the per-element output is not a scalar.
- map2(): iterates over two vectors in lockstep.
- pmap(): iterates over any number of inputs, including data frame rows.
- imap(): iterates with an index or name available as
.y. - reduce(): collapses a list to a single value instead of one-per-element.
For the base R equivalents, see lapply() and the apply family and the broader Functional Programming in R guide.
FAQ
What is the difference between map_dbl() and map() in R?
map() always returns a list, regardless of what the applied function produces. map_dbl() returns a flat numeric vector and enforces that every per-element result is a single double. Use map() when results vary in length or type, and map_dbl() when you know each call yields one number and want a clean atomic vector to compute on directly.
Is map_dbl() faster than sapply()?
Speed is roughly comparable for typical workloads; both loop in R. The real advantage of map_dbl() is predictability, not raw speed. sapply() infers the return type and can hand back a list, vector, or matrix depending on the data, while map_dbl() always returns a double vector or errors clearly.
Why does map_dbl() say "Result must be length 1"?
The function you passed returned a vector with more than one element for at least one input. map_dbl() requires exactly one double per element so it can build a flat result. Either summarise the result inside the function, for example ~ sum(.x), or switch to map() if you actually need multiple values per element.
Can map_dbl() return integers?
It returns doubles even when every value is a whole number, because the output type is fixed as double. If you specifically need an integer vector, use map_int() instead. It applies the same type check but stores the result as integers, which is stricter and slightly more memory efficient.
How do I pass extra arguments to the function in map_dbl()?
Add them after the function in the ... slot. For example, map_dbl(x, mean, na.rm = TRUE) forwards na.rm = TRUE to every mean() call. This works for any named argument the applied function accepts, and is cleaner than wrapping the function in a lambda just to set one option.