简体   繁体   中英

How to group a vector into a list of vectors?

I have some data which looks like this (fake data for example's sake):

dressId        color 
6              yellow 
9              red
10             green 
10             purple 
10             yellow 
12             purple 
12             red 

where color is a factor vector. It is not guaranteed that all possible levels of the factor actually appear in the data (eg the color "blue" could also be one of the levels).

I need a list of vectors which groups the available colors of each dress:

[[1]]
yellow  

[[2]] 
red    

[[3]] 
green purple yellow 

[[4]] 
purple red 

Preserving the IDs of the dresses would be nice (eg a dataframe where this list is the second column and the IDs are the first), but not necessary.

I wrote a loop which goes through the dataframe row for row, and while the next ID is the same, it adds the color to a vector. (I am sure that the data is sorted by ID). When the ID in the first column changes, it adds the vector to a list:

result <- NULL 
while(blah blah) 
{
    some code which creates the vector called "colors" 
    result[[dressCounter]] <- colors 
    dressCounter <- dressCounter + 1
}

After wrestling with getting all the necessary counting variables correct, I found out to my dismay that it doesn't work. The first time, colors is

[1] yellow
Levels: green yellow purple red blue

and it gets coerced into an integer, so result becomes 2 .

In the second loop repetition, colors only contains red, and result becomes a simple integer vector, [1] 2 4 .

In the third repetition, colors is a vector now,

[1] green  purple yellow
Levels: green yellow purple red blue 

and I get

result[[3]] <- colors

Error in result[[3]] <- colors :
more elements supplied than there are to replace

What am I doing wrong? Is there a way to initialize result so it doesn't get converted into a numeric vector, but becomes a list of vectors?

Also, is there another way to do the whole thing than "roll my own"?

split.data.frame is a good way to organize this; then extract the color component.

d <- data.frame(dressId=c(6,9,10,10,10,12,12),
               color=factor(c("yellow","red","green",
                              "purple","yellow",
                              "purple","red"),
                 levels=c("red","orange","yellow",
                          "green","blue","purple")))

I think the version you want is actually this:

ss <- split.data.frame(d,d$dressId)

You can get something more like the list you requested by extracting the color component:

lapply(ss,"[[","color")

In addition to split , you should consider aggregate . Use c or I as the aggregation function to get your list column:

out <- aggregate(color ~ dressId, mydf, c)
out
#   dressId                 color
# 1       6                yellow
# 2       9                   red
# 3      10 green, purple, yellow
# 4      12           purple, red
str(out)
# 'data.frame': 4 obs. of  2 variables:
#  $ dressId: int  6 9 10 12
#  $ color  :List of 4
#   ..$ 0: chr "yellow"
#   ..$ 1: chr "red"
#   ..$ 2: chr  "green" "purple" "yellow"
#   ..$ 3: chr  "purple" "red"
out$color
# $`0`
# [1] "yellow"
# 
# $`1`
# [1] "red"
# 
# $`2`
# [1] "green"  "purple" "yellow"
# 
# $`3`
# [1] "purple" "red" 

Note : This works even if the "color" variable is a factor , as in Ben's sample data (I missed that point when I posted the answer above) but you need to use I as the aggregation function instead of c :

out <- aggregate(color ~ dressId, d, I)
str(out)
# 'data.frame': 4 obs. of  2 variables:
#  $ dressId: num  6 9 10 12
#  $ color  :List of 4
#   ..$ 0: Factor w/ 6 levels "red","orange",..: 3
#   ..$ 1: Factor w/ 6 levels "red","orange",..: 1
#   ..$ 2: Factor w/ 6 levels "red","orange",..: 4 6 3
#   ..$ 3: Factor w/ 6 levels "red","orange",..: 6 1
out$color
# $`0`
# [1] yellow
# Levels: red orange yellow green blue purple
# 
# $`1`
# [1] red
# Levels: red orange yellow green blue purple
# 
# $`2`
# [1] green  purple yellow
# Levels: red orange yellow green blue purple
# 
# $`3`
# [1] purple red   
# Levels: red orange yellow green blue purple

Strangely, however, the default display shows the integer values:

out
#   dressId   color
# 1       6       3
# 2       9       1
# 3      10 4, 6, 3
# 4      12    6, 1

Assuming your data frame is saved in a variable called df , then you can use simply group_by and summarize with list function of dplyr package like this

library('dplyr')

df %>%
  group_by(dressId) %>%
  summarize(colors = list(color))

Applied to your example:

df <- tribble(
  ~dressId, ~color,
         6, 'yellow',
         9, 'red',
        10, 'green',
        10, 'purple',
        10, 'yellow',
        12, 'purple',
        12, 'red'
)

df %>%
  group_by(dressId) %>%
  summarize(colors = list(color))

# dressId                colors
#       6                yellow
#       9                   red
#      10 green, purple, yellow
#      12           purple, red

I am afraid that the answer should be a little different, you should use the following code to accomplish the requested behaviour

df %>%
group_by(dressId) %>%
summarize(colors = toString(unique(color)))

All the other answers do the job and I'm slightly late to the party, but some have used dplyr, and I always try to stay away from tidyverse if possible, and for this problem one can use the base R without tidyverse bloat. Some others have solved this through making a dataframe and that is not what the title says :)

let's create the vectors as OP didn't provide us the code (note that OP wants vector and not a dataframe although you can do this with a dataframe with a minor modification):

dressId <- c(6, 9, 10, 10, 10, 12, 12)
color <- c("yellow", "red", "green", "purple", "yellow", "purple", "red")

Now let's get to the business and calculate what OP asked for:

I need a list of vectors which groups the available colors of each dress:

result <- split(x = color, f = dressId)

result

which will output:

 $`6` [1] "yellow" $`9` [1] "red" $`10` [1] "green" "purple" "yellow" $`12` [1] "purple" "red"

This is very simple and straight forward. Now, if you have more than one pair, for instance if you have another "red" for the dressID of 12 , then you can pass the result of split() to unique() :

result <- lapply(result, unique)

If you have the color as a factor, technically it should also work but it will make every item of the result a factor. to mitigate that, simply use unfactor() from varhandle package to convert your factor to a non-factor vector.

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