Split df column of integers into individual digits in R

Viewed 121

I have a df where one variable is an integer. I'd like to split this column into it's individual digits. See my example below

Group Number
A     456
B     3
C     18

To

Group Number Digit1 Digit2 Digit3
A     456    4      5      6
B     3      3      NA     NA
C     18     1      8      NA
5 Answers

We can use read.fwf from base R. Find the max number of character (nchar) in 'Number' column (mx). Read the 'Number' column after converting to character (as.character), specify the 'widths' as 1 by replicating 1 with mx and assign the output to new 'Digit' columns in the data

mx <- max(nchar(df1$Number))
df1[paste0("Digit", seq_len(mx))] <- read.fwf(textConnection(
             as.character(df1$Number)), widths = rep(1, mx))

-output

df1
#  Group Number Digit1 Digit2 Digit3
#1     A    456      4      5      6
#2     B      3      3     NA     NA
#3     C     18      1      8     NA

data

df1 <- structure(list(Group = c("A", "B", "C"), Number = c(456L, 3L, 
18L)), class = "data.frame", row.names = c(NA, -3L))

Another base R option (I think @akrun's approach using read.fwf is much simpler)

cbind(
  df,
  with(
    df,
    type.convert(
      `colnames<-`(do.call(
        rbind,
        lapply(
          strsplit(as.character(Number), ""),
          `length<-`, max(nchar(Number))
        )
      ), paste0("Digit", seq(max(nchar(Number))))),
      as.is = TRUE
    )
  )
)

which gives

  Group Number Digit1 Digit2 Digit3
1     A    456      4      5      6
2     B      3      3     NA     NA
3     C     18      1      8     NA

Using splitstackshape::cSplit

splitstackshape::cSplit(df, 'Number', sep = '', stripWhite = FALSE, drop = FALSE)

#   Group Number Number_1 Number_2 Number_3
#1:     A    456        4        5        6
#2:     B      3        3       NA       NA
#3:     C     18        1        8       NA

Updated I realized I could use max function for counting characters limit in each row so that I could include it in my map2 function and save some lines of codes thanks to an accident that led to an inspiration by dear @ThomasIsCoding.

library(dplyr)
library(tidyr)
library(purrr)
library(stringr)

df %>%
  rowwise() %>%
  mutate(map2_dfc(Number, 1:max(nchar(Number)), ~ str_sub(.x, .y, .y))) %>%
  unnest(cols = !c(Group, Number)) %>%
  rename_with(~ str_replace(., "\\.\\.\\.", "Digit"), .cols = !c(Group, Number)) %>%
  mutate(across(!c(Group, Number), as.numeric, na.rm = TRUE))


# A tibble: 3 x 5
  Group Number Digit1 Digit2 Digit3
  <chr>  <dbl>  <dbl>  <dbl>  <dbl>
1 A        456      4      5      6
2 B          3      3     NA     NA
3 C         18      1      8     NA

Data

df <- tribble(
  ~Group, ~Number,
  "A",     456,
  "B",     3,
  "C",     18
)

Two base r methods:

no_cols <- max(nchar(as.character(df1$Number)))

# Using `strsplit()`: 
cbind(df1, setNames(data.frame(do.call(rbind,
  lapply(strsplit(as.character(df1$Number), ""),
    function(x) { 
      length(x) <- no_cols
      x 
      }
    )
  )
), paste0("Digit", seq_len(no_cols))))

# Using `regmatches()` and `gregexpr()`:
cbind(df1, setNames(data.frame(do.call(rbind, 
  lapply(regmatches(df1$Number, gregexpr("\\d", df1$Number)),
    function(x) {
      length(x) <- no_cols
      x
      }
    )
  )
), paste0("Digit", seq_len(no_cols))))
Related