purrr map_int() in R: Return an Integer Vector

purrr map_int() applies a function to each element of a list or vector and returns an integer vector. It is the type-stable counterpart of map() for whole-number results, such as counts and lengths.

⚡ Quick Answer
map_int(x, length)                    # element count per list item
map_int(df, ~ sum(is.na(.x)))         # NA count per column
map_int(df, ~ length(unique(.x)))     # distinct values per column
map_int(words, nchar)                 # character count per string
map_int(x, \(v) v * 2L)               # anonymous function, integer result
map_int(x, length, .progress = TRUE)  # show a progress bar

Need explanation? Read on for examples and pitfalls.

📊 Is map_int() the right tool?
STARTcount things, want an integer vectormap_int(x, length)result is a decimal or averagemap_dbl(x, mean)result is textmap_chr(x, paste0)result is TRUE or FALSEmap_lgl(x, is.numeric)keep mixed results as a listmap(x, summary)two inputs in parallelmap2_int(x, y, `+`)

What map_int() does in one sentence

map_int() is a type-stable iterator. It loops over every element of .x, calls .f on each one, and binds the results into a plain integer vector the same length as the input. If any result cannot become an integer, the call fails loudly instead of returning a surprising type.

That strictness is the point. Base R's sapply() guesses the output type and can hand you a list when you expected a vector. map_int() removes the guess: the answer is always an integer vector, or it is an error you can see immediately.

Key Insight
Type stability is a contract, not a limitation. Because map_int() guarantees an integer vector, code downstream can rely on that type without defensive checks. The error you get on a bad result is cheaper than a silent type bug found in production.

Syntax

map_int() takes an input and a function. The two required arguments are .x, the list or vector to iterate over, and .f, the function applied to each element.

Run live
Run live, no install needed. Every R block on this page runs in your browser. Click Run, edit the code, re-run instantly. No setup.
RThe map_int signature
library(purrr) # map_int(.x, .f, ..., .progress = FALSE) # .x a list or atomic vector # .f a function, ~ formula, or \(x) lambda # ... extra arguments passed on to .f # .progress show a progress bar for slow loops map_int(list(1:3, 1:5, 1:10), length) #> [1] 3 5 10

  

You can write .f three ways: a named function (length), a purrr formula (~ sum(is.na(.x))) where .x is the current element, or a native lambda (\(v) length(v)). All three are equivalent; pick whichever reads best.

Worked examples

Counting is the canonical job for map_int(). Each example below returns one whole number per element, so the integer output type fits naturally.

Count missing values in every column of a data frame. A data frame is a list of columns, so map_int() iterates column by column.

RCount NAs per column
map_int(airquality, ~ sum(is.na(.x))) #> Ozone Solar.R Wind Temp Month Day #> 37 7 0 0 0 0

  

Count how many distinct values each column holds. This is a fast cardinality check before modelling.

RDistinct values per column
map_int(iris, ~ length(unique(.x))) #> Sepal.Length Sepal.Width Petal.Length Petal.Width Species #> 35 23 43 22 3

  

Apply a named function directly. Here nchar returns the character count of each string.

RCharacter count per string
map_int(c("apple", "fig", "banana"), nchar) #> [1] 5 3 6

  

Use a lambda when the result needs explicit integer literals. The L suffix marks 2L as an integer so the output type is exact.

RLambda returning an integer
map_int(1:4, \(v) v * 2L) #> [1] 2 4 6 8

  

map_int() vs map() and the other variants

Every map variant differs only in its return type. The loop is identical; the suffix picks the output. Choose the variant that matches the type each call to .f produces.

Function Returns Use when .f produces
map() list anything, including mixed types
map_int() integer vector whole numbers (counts, lengths)
map_dbl() double vector decimals (means, ratios)
map_chr() character vector strings
map_lgl() logical vector TRUE or FALSE

The decision rule is simple. If .f returns a count or a length, use map_int(). If it returns an average, a proportion, or any fractional number, use map_dbl(). When in doubt, use map() and inspect the list first.

Note
Coming from base R? map_int(x, f) is vapply(x, f, integer(1)). Both promise an integer vector, but map_int() supports formula and lambda shorthand and gives clearer error messages.

Common pitfalls

Most map_int() errors trace back to one rule: each result must coerce to a single integer. These three mistakes break that rule.

A function that returns a fractional double cannot become an integer. Averaging columns produces decimals, so map_int() rejects them.

RPitfall: fractional result
map_int(mtcars, mean) #> Error in `map_int()`: #> ! Can't coerce from a double vector to an integer vector.

  

The fix is to use map_dbl() whenever .f can return a decimal.

A function that returns more than one value per element also fails, because map_int() expects exactly length 1.

RPitfall: result longer than one
map_int(list(1:3, 4:6), range) #> Error in `map_int()`: #> ! Result 1 must be a single integer, not an integer vector of length 2.

  

Aggregate first so each call yields one number, for example ~ diff(range(.x)).

Warning
Character digits do not auto-convert. map_int(c("1", "2"), identity) errors even though the strings look numeric. Wrap the result yourself: map_int(c("1", "2"), as.integer).

Try it yourself

Try it: Use map_int() on the mtcars data frame to count how many distinct values appear in each column. Save the result to ex_distinct.

RYour turn: distinct values per column
# Try it: count distinct values per column of mtcars ex_distinct <- # your code here ex_distinct #> Expected: an integer vector with one count per column

  
Click to reveal solution
RSolution
ex_distinct <- map_int(mtcars, ~ length(unique(.x))) ex_distinct #> mpg cyl disp hp drat wt qsec vs am gear carb #> 25 3 27 22 22 29 30 2 2 3 6

  

Explanation: map_int() iterates over each column of mtcars. The formula ~ length(unique(.x)) returns one whole number per column, so the integer return type fits and the result is a named integer vector.

map_int() is one of a family. Reach for these when the return type changes:

  • map() returns a list for any result type.
  • map_dbl() returns a double vector for decimal results.
  • map_chr() returns a character vector for string results.
  • map_lgl() returns a logical vector for TRUE or FALSE results.
  • map2_int() iterates two inputs in parallel and returns an integer vector.

FAQ

What is the difference between map_int() and map() in R?

map() always returns a list, regardless of what .f produces. map_int() returns a plain integer vector and checks every result. Use map() when results vary in type or length, and map_int() when each result is a single whole number. The integer return type makes map_int() output ready for arithmetic, indexing, or comparison without any unlisting step.

Why does map_int() give a coercion error?

The error appears when .f returns something that cannot become an integer, most often a fractional double like a mean, or a value longer than one. map_int() enforces an integer-vector contract, so it stops instead of silently changing type. Switch to map_dbl() for decimal results, or aggregate the result down to a single whole number per element.

Can map_int() return NA values?

Yes. If .f returns NA_integer_ for an element, map_int() keeps it as a missing value in the integer output. A bare logical NA also coerces cleanly. What it will not accept is NULL or a zero-length result, since those cannot fill an integer slot and trigger a length error instead.

Is map_int() faster than sapply()?

Performance is roughly equal; both loop in C-backed code. The real gain from map_int() is reliability. sapply() may return a list, a vector, or a matrix depending on the data, while map_int() always returns an integer vector or errors. That predictability prevents type bugs that surface far from their cause.

Prefer to learn by doing? Every track on the learning roadmap opens with a free section of interactive lessons, where you write the R yourself and the page tells you when it is wrong.