简体   繁体   中英

Tidy method to split multiple columns using tidyr::separate

I have a data frame like so:

df <- structure(list(A = c("3 of 5", "1 of 2", "1 of 3", "1 of 3", 
"3 of 4", "2 of 7"), B = c("2 of 2", "2 of 4", "0 of 1", "0 of 0", 
"0 of 0", "0 of 0"), C = c("10 of 21", "3 of 14", "11 of 34", 
"10 of 35", "16 of 53", "17 of 62"), D = c("0 of 0", "0 of 0", 
"0 of 0", "0 of 0", "0 of 0", "0 of 0"), E = c("8 of 16", "3 of 15", 
"10 of 32", "6 of 28", "13 of 49", "9 of 48")), class = c("tbl_df", 
"tbl", "data.frame"), row.names = c(NA, -6L))

df

|A      |B      |C        |D      |E        |
|:------|:------|:--------|:------|:--------|
|3 of 5 |2 of 2 |10 of 21 |0 of 0 |8 of 16  |
|1 of 2 |2 of 4 |3 of 14  |0 of 0 |3 of 15  |
|1 of 3 |0 of 1 |11 of 34 |0 of 0 |10 of 32 |
|1 of 3 |0 of 0 |10 of 35 |0 of 0 |6 of 28  |
|3 of 4 |0 of 0 |16 of 53 |0 of 0 |13 of 49 |
|2 of 7 |0 of 0 |17 of 62 |0 of 0 |9 of 48  |

I want to split each column into 2, leaving me with something like this:

|A_attempted |A_landed |B_attempted |B_landed |C_attempted |C_landed |D_attempted |D_landed |E_attempted |E_landed |
|:-----------|:--------|:-----------|:--------|:-----------|:--------|:-----------|:--------|:-----------|:--------|
|3           |5        |2           |2        |10          |21       |0           |0        |8           |16       |
|1           |2        |2           |4        |3           |14       |0           |0        |3           |15       |
|1           |3        |0           |1        |11          |34       |0           |0        |10          |32       |
|1           |3        |0           |0        |10          |35       |0           |0        |6           |28       |
|3           |4        |0           |0        |16          |53       |0           |0        |13          |49       |
|2           |7        |0           |0        |17          |62       |0           |0        |9           |48       |

The method I am using so far is this:

df %>% 
  separate(A, sep = " of ", remove = T, into = c("A_attempted", "A_landed")) %>% 
  separate(B, sep = " of ", remove = T, into = c("B_attempted", "B_landed")) %>% 
  separate(C, sep = " of ", remove = T, into = c("C_attempted", "C_landed")) %>%  
  separate(D, sep = " of ", remove = T, into = c("D_attempted", "D_landed")) %>%  
  separate(E, sep = " of ", remove = T, into = c("E_attempted", "E_landed"))

Which is not great considering I have 15 variables. I would prefer a solution using map

There is an answer here: Apply tidyr::separate over multiple columns but that uses deprecated functions

Could try:

library(tidyverse)

names(df) %>%
  map(
    function(x) 
      df %>% 
      select(x) %>% 
      separate(x, 
               into = paste0(x, c("_attempted", "_landed")), 
               sep = " of ")
    ) %>%
  bind_cols()

Output:

# A tibble: 6 x 10
  A_attempted A_landed B_attempted B_landed C_attempted C_landed D_attempted D_landed E_attempted E_landed
  <chr>       <chr>    <chr>       <chr>    <chr>       <chr>    <chr>       <chr>    <chr>       <chr>   
1 3           5        2           2        10          21       0           0        8           16      
2 1           2        2           4        3           14       0           0        3           15      
3 1           3        0           1        11          34       0           0        10          32      
4 1           3        0           0        10          35       0           0        6           28      
5 3           4        0           0        16          53       0           0        13          49      
6 2           7        0           0        17          62       0           0        9           48      

As OP suggests we can indeed avoid the last step with map_dfc :

names(df) %>% 
  map_dfc(~ df %>% 
             select(.x) %>% 
             separate(.x, 
                      into = paste0(.x, c("_attempted", "_landed")), 
                      sep = " of ")
           )

Yet another tidyverse possibility

imap_dfc(df, ~ separate(tibble(.x), col = 1, 
                        paste0(.y, c("_attempted", "_landed")), 
                        sep = " of ", convert = TRUE))

# # A tibble: 6 x 10
#   A_attempted A_landed B_attempted B_landed C_attempted C_landed D_attempted D_landed E_attempted E_landed
#         <int>    <int>       <int>    <int>       <int>    <int>       <int>    <int>       <int>    <int>
# 1           3        5           2        2          10       21           0        0           8       16
# 2           1        2           2        4           3       14           0        0           3       15
# 3           1        3           0        1          11       34           0        0          10       32
# 4           1        3           0        0          10       35           0        0           6       28
# 5           3        4           0        0          16       53           0        0          13       49
# 6           2        7           0        0          17       62           0        0           9       48

One approach:

library(tidyverse)

df %>%
  rownames_to_column("id") %>%
  gather(group, value, -id) %>% 
  separate(value, into = c("attempted", "landed"), sep = " of ") %>%
  gather(key, value, -id, -group) %>%
  unite(new, group, key, sep = "_" ) %>%
  spread(new, value)

# A tibble: 6 x 11
  id    A_attempted A_landed B_attempted B_landed C_attempted C_landed D_attempted D_landed E_attempted E_landed
  <chr> <chr>       <chr>    <chr>       <chr>    <chr>       <chr>    <chr>       <chr>    <chr>       <chr>   
1 1     3           5        2           2        10          21       0           0        8           16      
2 2     1           2        2           4        3           14       0           0        3           15      
3 3     1           3        0           1        11          34       0           0        10          32      
4 4     1           3        0           0        10          35       0           0        6           28      
5 5     3           4        0           0        16          53       0           0        13          49      
6 6     2           7        0           0        17          62       0           0        9           48  

We can use cSplit

library(splitstackshape)

df1 <- cSplit(df, names(df), sep = "of", stripWhite = FALSE)
df1

#   A_1 A_2 B_1 B_2 C_1 C_2 D_1 D_2 E_1 E_2
#1:   3   5   2   2  10  21   0   0   8  16
#2:   1   2   2   4   3  14   0   0   3  15
#3:   1   3   0   1  11  34   0   0  10  32
#4:   1   3   0   0  10  35   0   0   6  28
#5:   3   4   0   0  16  53   0   0  13  49
#6:   2   7   0   0  17  62   0   0   9  48

We can rename it by

names(df1) <- c(outer(names(df), c("attempted", "landed"), paste, sep = "_"))

And we can always do things in base R

do.call(cbind.data.frame, 
     lapply(df, function(x) do.call(rbind, strsplit(x, " of "))))


#  A.1 A.2 B.1 B.2 C.1 C.2 D.1 D.2 E.1 E.2
#1   3   5   2   2  10  21   0   0   8  16
#2   1   2   2   4   3  14   0   0   3  15
#3   1   3   0   1  11  34   0   0  10  32
#4   1   3   0   0  10  35   0   0   6  28
#5   3   4   0   0  16  53   0   0  13  49
#6   2   7   0   0  17  62   0   0   9  48

We can rename the columns in similar fashion as shown above.

Just another tidyverse way:

purrr::map_dfc(names(df), function(i) {

 df %>% separate(i,
              sep = "of",
              remove = T,
              into = c(paste0(i, "_attempted"), paste0(i, "_landed")))

 }) %>% dplyr::select(., contains("_"))

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM