How to Use map_dbl(), map_chr(), map_int(), and map_lgl() in R

purrr
purrr map_dbl()
Learn the typed map variants in purrr that return vectors instead of lists. Simplify your functional programming with map_dbl, map_chr, map_int, and map_lgl.
Published

April 3, 2026

Introduction

While map() always returns a list, the typed variants return atomic vectors of a specific type. This is cleaner when you know your function will return a single value of a known type for each element.

The typed map functions: - map_dbl() - returns a numeric (double) vector - map_chr() - returns a character vector - map_int() - returns an integer vector - map_lgl() - returns a logical vector

Getting Started

library(tidyverse)
library(palmerpenguins)

map_dbl(): Return Numeric Values

Use map_dbl() when your function returns a single number:

# Create a list of numeric vectors
numbers <- list(
  a = c(1, 2, 3, 4, 5),
  b = c(10, 20, 30),
  c = c(100, 200, 300, 400)
)

# Calculate mean of each - returns a named numeric vector
map_dbl(numbers, mean)

Practical example with data

# Get mean of each numeric column in penguins
penguins |>
  select(where(is.numeric), -year) |>
  map_dbl(\(x) mean(x, na.rm = TRUE))

Extract model statistics

# Fit models by species and extract R-squared
penguins |>
  drop_na() |>
  split(~species) |>
  map(\(df) lm(body_mass_g ~ flipper_length_mm, data = df)) |>
  map_dbl(\(m) summary(m)$r.squared)

map_chr(): Return Character Values

Use map_chr() when your function returns a single string:

# Get class of each column
penguins |>
  map_chr(class)

Extract names or labels

# Create named list
models <- list(
  simple = lm(mpg ~ wt, data = mtcars),
  multiple = lm(mpg ~ wt + hp, data = mtcars)
)

# Get formula as string
map_chr(models, \(m) deparse(formula(m)))

Format values as strings

# Format numbers with units
measurements <- list(height = 180, weight = 75, age = 30)
units <- c(height = "cm", weight = "kg", age = "years")

map_chr(names(measurements), \(name) {
  paste(measurements[[name]], units[name])
})

map_int(): Return Integer Values

Use map_int() when your function returns a single integer:

# Count elements in each list item
numbers <- list(
  a = 1:5,
  b = 1:10,
  c = 1:3
)

map_int(numbers, length)

Count observations by group

# Count rows in each nested data frame
penguins |>
  drop_na() |>
  split(~species) |>
  map_int(nrow)

Count unique values

# Count unique values in each column
penguins |>
  map_int(\(x) n_distinct(x, na.rm = TRUE))

map_lgl(): Return Logical Values

Use map_lgl() when your function returns TRUE or FALSE:

# Check which columns have missing values
penguins |>
  map_lgl(\(x) any(is.na(x)))

Filter columns programmatically

# Find numeric columns
penguins |>
  map_lgl(is.numeric)

# Use with select
penguins |>
  select(where(\(x) map_lgl(list(x), is.numeric)[[1]]))

Check conditions across list elements

# Check if all values are positive
numbers <- list(a = c(1, 2, 3), b = c(-1, 2, 3), c = c(5, 6, 7))

map_lgl(numbers, \(x) all(x > 0))

Choosing the Right Variant

Use this decision tree:

Does your function return a single value per element?
│
├── No → use map() (returns list)
│
└── Yes → What type?
    ├── Number (double) → map_dbl()
    ├── Integer → map_int()
    ├── Text → map_chr()
    ├── TRUE/FALSE → map_lgl()
    └── Data frame → map_dfr() or list_rbind()

list_rbind() and list_cbind() (purrr 1.0+)

Modern purrr (1.0+) recommends list_rbind() over map_dfr():

# Old way (still works)
map_dfr(1:3, \(i) tibble(x = i, y = i^2))

# New way (purrr 1.0+) - more explicit
map(1:3, \(i) tibble(x = i, y = i^2)) |> list_rbind()

# With ID column
map(1:3, \(i) tibble(x = i, y = i^2)) |>
  list_rbind(names_to = "id")

The new approach is clearer about what’s happening: first map, then combine.

# Column binding
map(1:3, \(i) tibble(!!paste0("col", i) := rnorm(5))) |>
  list_cbind()

Combining map_* with Data Frames

Row-binding results with map_dfr()

When each iteration returns a data frame, use map_dfr() to combine them:

# Get summary stats for each species
penguins |>
  drop_na() |>
  split(~species) |>
  map_dfr(\(df) {
    tibble(
      mean_mass = mean(df$body_mass_g),
      sd_mass = sd(df$body_mass_g),
      n = nrow(df)
    )
  }, .id = "species")

Column-binding with map_dfc()

# Less common, but binds results as columns
map_dfc(1:3, \(i) tibble(!!paste0("col", i) := rnorm(5)))

Common Mistakes

1. Type mismatch errors

# This will error - can't return character from map_dbl
# map_dbl(penguins, class)

# Use map_chr instead
map_chr(penguins, class)

2. Functions returning multiple values

Typed map functions expect exactly one value per element:

# This errors - range() returns 2 values
# map_dbl(numbers, range)

# Solution: return a single value or use map()
map_dbl(numbers, \(x) diff(range(x)))  # Returns range width
map(numbers, range)  # Returns list of ranges

3. NULL values causing errors

# If a function can return NULL, it won't work with typed maps
data_with_nulls <- list(a = 1:3, b = NULL, c = 4:6)

# This errors
# map_int(data_with_nulls, length)

# Handle NULLs first
map_int(data_with_nulls, \(x) if(is.null(x)) 0L else length(x))

Summary

Function Returns Use When
map() list Multiple values or unknown type
map_dbl() numeric vector Single number per element
map_chr() character vector Single string per element
map_int() integer vector Single integer per element
map_lgl() logical vector TRUE/FALSE per element
map_dfr() data frame Data frame per element (row-bind)
  • Typed variants are cleaner than map() |> unlist()
  • Ensure your function returns exactly one value of the correct type
  • Handle NULL and NA values explicitly