ckluss
ckluss

Reputation: 1281

dplyr change many data types

I have a data.frame:

dat <- data.frame(fac1 = c(1, 2),
                  fac2 = c(4, 5),
                  fac3 = c(7, 8),
                  dbl1 = c('1', '2'),
                  dbl2 = c('4', '5'),
                  dbl3 = c('6', '7')
                  )

To change data types I can use something like

l1 <- c("fac1", "fac2", "fac3")
l2 <- c("dbl1", "dbl2", "dbl3")
dat[, l1] <- lapply(dat[, l1], factor)
dat[, l2] <- lapply(dat[, l2], as.numeric)

with dplyr

dat <- dat %>% mutate(
    fac1 = factor(fac1), fac2 = factor(fac2), fac3 = factor(fac3),
    dbl1 = as.numeric(dbl1), dbl2 = as.numeric(dbl2), dbl3 = as.numeric(dbl3)
)

is there a more elegant (shorter) way in dplyr?

thx Christof

Upvotes: 94

Views: 197458

Answers (8)

Leo
Leo

Reputation: 197

For future readers, if you are ok with dplyr guessing the column types, you can convert the col types of an entire df as if you were originally reading it in with readr and col_guess() with

library(tidyverse)
df %>% type_convert()

Upvotes: 2

Rafael Zayas
Rafael Zayas

Reputation: 2461

EDIT - The syntax of this answer has been deprecated, loki's updated answer is more appropriate.

ORIGINAL-

From the bottom of the ?mutate_each (at least in dplyr 0.5) it looks like that function, as in @docendo discimus's answer, will be deprecated and replaced with more flexible alternatives mutate_if, mutate_all, and mutate_at. The one most similar to what @hadley mentions in his comment is probably using mutate_at. Note the order of the arguments is reversed, compared to mutate_each, and vars() uses select() like semantics, which I interpret to mean the ?select_helpers functions.

dat %>% mutate_at(vars(starts_with("fac")),funs(factor)) %>%   
  mutate_at(vars(starts_with("dbl")),funs(as.numeric))

But mutate_at can take column numbers instead of a vars() argument, and after reading through this page, and looking at the alternatives, I ended up using mutate_at but with grep to capture many different kinds of column names at once (unless you always have such obvious column names!)

dat %>% mutate_at(grep("^(fac|fctr|fckr)",colnames(.)),funs(factor)) %>%
  mutate_at(grep("^(dbl|num|qty)",colnames(.)),funs(as.numeric))

I was pretty excited about figuring out mutate_at + grep, because now one line can work on lots of columns.

EDIT - now I see matches() in among the select_helpers, which handles regex, so now I like this.

dat %>% mutate_at(vars(matches("fac|fctr|fckr")),funs(factor)) %>%
  mutate_at(vars(matches("dbl|num|qty")),funs(as.numeric))

Another generally-related comment - if you have all your date columns with matchable names, and consistent formats, this is powerful. In my case, this turns all my YYYYMMDD columns, which were read as numbers, into dates.

  mutate_at(vars(matches("_DT$")),funs(as.Date(as.character(.),format="%Y%m%d")))

Upvotes: 51

loki
loki

Reputation: 10350

Edit (as of 2021-03)

As also pointed out in Eric's answer, mutate_[at|if|all] has been superseded by a combination of mutate() and across(). For reference, I will add the respective pendants to the examples in the original answer (see below):

# convert all factor to character
dat %>% mutate(across(where(is.factor), as.character))

# apply function (change encoding) to all character columns 
dat %>% mutate(across(where(is.character), 
               function(x){iconv(x, to = "ASCII//TRANSLIT")}))

# subsitute all NA in numeric columns
dat %>% mutate(across(where(is.numeric), function(x) tidyr::replace_na(x, 0)))

Original answer

Since Nick's answer is deprecated by now and Rafael's comment is really useful, I want to add this as an Answer. If you want to change all factor columns to character use mutate_if:

dat %>% mutate_if(is.factor, as.character)

Also other functions are allowed. I for instance used iconv to change the encoding of all character columns:

dat %>% mutate_if(is.character, function(x){iconv(x, to = "ASCII//TRANSLIT")})

or to substitute all NA by 0 in numeric columns:

dat %>% mutate_if(is.numeric, function(x){ifelse(is.na(x), 0, x)})

Upvotes: 81

Eric Krantz
Eric Krantz

Reputation: 2199

Dplyr across function has superseded _if, _at, and _all. See vignette("colwise").

dat %>% 
mutate(across(all_of(l1), as.factor),
       across(all_of(l2), as.numeric))

Upvotes: 19

davsjob
davsjob

Reputation: 1960

Or mayby even more simple with convert from hablar:

library(hablar)

dat %>% 
  convert(fct(fac1, fac2, fac3),
          num(dbl1, dbl2, dbl3))

or combines with tidyselect:

dat %>% 
  convert(fct(contains("fac")),
          num(contains("dbl")))

Upvotes: 1

nexonvantec
nexonvantec

Reputation: 592

It's a one-liner with mutate_at:

dat %>% mutate_at("l1", factor) %>% mutate_at("l2", as.numeric)

Upvotes: 8

Nick
Nick

Reputation: 3384

A more general way of achieving column type transformation is as follows:

If you want to transform all your factor columns to character columns, e.g., this can be done using one pipe:

df %>%  mutate_each_( funs(as.character(.)), names( .[,sapply(., is.factor)] ))

Upvotes: 4

talat
talat

Reputation: 70336

You can use the standard evaluation version of mutate_each (which is mutate_each_) to change the column classes:

dat %>% mutate_each_(funs(factor), l1) %>% mutate_each_(funs(as.numeric), l2)

Upvotes: 60

Related Questions