3

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
ThomasIsCoding
  • 96,636
  • 9
  • 24
  • 81

5 Answers5

6

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))
akrun
  • 874,273
  • 37
  • 540
  • 662
4

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
Anoushiravan R
  • 21,622
  • 3
  • 18
  • 41
ThomasIsCoding
  • 96,636
  • 9
  • 24
  • 81
  • I am terribly sorry Mr. Thomas as I was about to add a sample data to my answer and accidentally edited yours. I just removed it. So sorry. – Anoushiravan R Apr 21 '21 at 22:56
  • 1
    @AnoushiravanR No worries. – ThomasIsCoding Apr 21 '21 at 22:57
  • By the way I was reviewing other answers to learn alternative ways and after reviewing your code, I realized I could use `max` to set the character limit in one single move. I would like to thank you for that. Best wishes. – Anoushiravan R Apr 21 '21 at 23:09
3

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
Ronak Shah
  • 377,200
  • 20
  • 156
  • 213
2

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
)
Anoushiravan R
  • 21,622
  • 3
  • 18
  • 41
0

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))))
hello_friend
  • 5,682
  • 1
  • 11
  • 15