Paula
Paula

Reputation: 107

Split dataframe into several data frames within a list, each column separately

I have a data frame df whose first column is a character vector and the rest numeric.

Example data frame:

df <- data.frame(my_names=sample(LETTERS,4,replace=F),
                 column2=sample(1.3:100.3,4,replace=T),
                 column3=sample(1.3:100.3,4,replace=T),
                 column4=sample(1.3:100.3,4,replace=T),
                 column5=sample(1.3:100.3,4,replace=T))
> df
  my_names column2 column3 column4 column5
1        A     8.3     1.3    19.3    91.3
2        E    18.3    42.3     8.3    76.3
3        O     6.3    46.3    26.3    91.3
4        M    73.3     6.3    59.3    93.3

Now I want to create 4 different data frames like this:

And store them into a list. d1 would look like:

> d1
  my_names column2
1        A     8.3
2        E    18.3
3        O     6.3
4        M    73.3

I have tried:

>the_list <- vector("list",ncol(df)-1)
> for(i in 1:length(the_list)){ for(j in 2:ncol(df)){
+   the_list[[i]] <- select(df, my_names,j)
+ }
+ }
Note: Using an external vector in selections is ambiguous.
ℹ Use `all_of(j)` instead of `j` to silence this message.

But I get a list where all data frames are with column5:

    > str(the_list)
List of 4
 $ :'data.frame':   4 obs. of  2 variables:
  ..$ my_names: chr [1:4] "A" "E" "O" "M"
  ..$ column5 : num [1:4] 91.3 76.3 91.3 93.3
 $ :'data.frame':   4 obs. of  2 variables:
  ..$ my_names: chr [1:4] "A" "E" "O" "M"
  ..$ column5 : num [1:4] 91.3 76.3 91.3 93.3
 $ :'data.frame':   4 obs. of  2 variables:
  ..$ my_names: chr [1:4] "A" "E" "O" "M"
  ..$ column5 : num [1:4] 91.3 76.3 91.3 93.3
 $ :'data.frame':   4 obs. of  2 variables:
  ..$ my_names: chr [1:4] "A" "E" "O" "M"
  ..$ column5 : num [1:4] 91.3 76.3 91.3 93.3

I take the recommendation from the error (using all_of(j)) and write:

> for(i in 1:length(the_list)){ 
  for(j in 2:ncol(df)){
    the_list[[i]] <- select(df, my_names,all_of(j))
  }
  }

But the result is the same as above.

I have read that one could use split, but I have nothing to group by, it´s each column separately. e.g this does not work:

new_list<-list(split(df, colnames(df))

I get a wird list of 1.

Upvotes: 3

Views: 1389

Answers (5)

tmfmnk
tmfmnk

Reputation: 39858

One option using purrr and dplyr could be:

map(2:length(df),
    ~ df %>%
     select(1, all_of(.x)))

[[1]]
  my_names column2
1        N    21.3
2        S    91.3
3        T    50.3
4        F    34.3

[[2]]
  my_names column3
1        N    84.3
2        S    20.3
3        T     1.3
4        F    61.3

[[3]]
  my_names column4
1        N     4.3
2        S     9.3
3        T    93.3
4        F    58.3

[[4]]
  my_names column5
1        N    33.3
2        S    61.3
3        T    12.3
4        F    91.3

If you are interested in a named list:

set_names(map(2:length(df),
              ~ df %>%
               select(1, all_of(.x))),
          paste0("df", 2:length(df) - 1))

Upvotes: 0

Darren Tsai
Darren Tsai

Reputation: 35554

A base solution with lapply().

lapply(seq_along(df)[-1], function(x) df[c(1, x)])

or

lapply(names(df)[-1], function(x) df[c("my_names", x)])

Afterward, you can use setNames() to assign names to the list.

Upvotes: 0

Ronak Shah
Ronak Shah

Reputation: 388982

Using lapply :

data <- lapply(seq_along(df[-1]), function(x) cbind(df[1], df[x+1]))

data would have list of dataframes. If you want them in separate dataframes, name them and use list2env.

names(data) <- paste0('d', seq_along(data))
list2env(data, .GlobalEnv)

Upvotes: 0

ThomasIsCoding
ThomasIsCoding

Reputation: 101335

Maybe you can try list2env

list2env(
  setNames(
    lapply(seq_along(df)[-1], function(k) cbind(df[c(1, k)])),
    paste0("d", seq_along(df[-1]))
  ),
  envir = .GlobalEnv
)

If you want a list of dataframes only, you can remove list2env, i.e.,

setNames(
  lapply(seq_along(df)[-1], function(k) cbind(df[c(1, k)])),
  paste0("d", seq_along(df[-1]))
)

which gives

$d1
  my_names column2
1        C    45.3
2        M    89.3
3        G    35.3
4        T    48.3

$d2
  my_names column3
1        C    41.3
2        M    56.3
3        G    34.3
4        T    95.3

$d3
  my_names column4
1        C    78.3
2        M     7.3
3        G    60.3
4        T    19.3

$d4
  my_names column5
1        C    76.3
2        M    51.3
3        G    96.3
4        T    96.3

Upvotes: 0

Duck
Duck

Reputation: 39595

Try this tidyverse approach. You can format your data to long to transform columns into rows. Then, with split() you can create a list based on the column name. Finally, you can apply a function to transform your data to wide at each dataframe in the list and reach the desired output. Here the code:

library(tidyverse)
#Data
df <- data.frame(my_names=sample(LETTERS,4,replace=F),
                 column2=sample(1.3:100.3,4,replace=T),
                 column3=sample(1.3:100.3,4,replace=T),
                 column4=sample(1.3:100.3,4,replace=T),
                 column5=sample(1.3:100.3,4,replace=T))
#Reshape to long
df2 <- df %>% pivot_longer(cols = -1)
#Split into a list
List <- split(df2,df2$name)
#Now reshape function for wide format
List2 <- lapply(List,function(x){x<-pivot_wider(x,names_from = name,values_from = value);return(x)})
names(List2) <- paste0('df',1:length(List2))

Output:

List2
$df1
# A tibble: 4 x 2
  my_names column2
  <fct>      <dbl>
1 N           21.3
2 H           35.3
3 X           42.3
4 U           89.3

$df2
# A tibble: 4 x 2
  my_names column3
  <fct>      <dbl>
1 N           94.3
2 H           54.3
3 X            2.3
4 U           38.3

$df3
# A tibble: 4 x 2
  my_names column4
  <fct>      <dbl>
1 N           75.3
2 H           94.3
3 X           87.3
4 U          100. 

$df4
# A tibble: 4 x 2
  my_names column5
  <fct>      <dbl>
1 N           60.3
2 H           88.3
3 X           14.3
4 U           99.3

Upvotes: 1

Related Questions