简体   繁体   中英

Subsetting by two factors, all levels, with a simple code

I'm aware that this question is simple, but couldn't find a solution without creating step objects, and I want a one-line code, or one as simplest as it could be.

Suppose I have a data frame called df with columns x , y , z :

x<-c(rep('place1',33),rep('place2',33),rep('place3',34))
y<-sample(c('type1','type2','type3','type4','type5'),100,replace=T)
z<-sample(40:80,100,replace=T)
df<-data.frame(x,y,z)

I would like to get all subsets possible of z for each combination of levels of x and y (type1 in place1, type2 in place1, type3 in place1...type4 in place3 and type5 in place3). Something like this:

[[place1]]
[type1]
[1] 57 73 74 47 52 61

[type2]
[1] 72 76 64 62 73 75
...

[type5]
...

[[place3]]
[type1]
...

[type5]

In the case this is possible, how could I access each subset?

I've tried a nested split inside an lapply , without success.

Sorry for this simple question, but couldn't find a suitable solution.

Any help would be appreciated.

Here is one way. You split your df using the variable, x. Then, you split each data frame using split again with the variable, y. In this way, you can subset your data in a way you want.I left a bit of trimmed outcome in the end.

lapply(split(df, f = df$x), function(x) split(x, f = x$y)

#$place1
#$place1$type1
#        x     y  z
#5  place1 type1 46
#7  place1 type1 41

#$place1$type2
#        x     y  z
#3  place1 type2 44
#4  place1 type2 59

If you just want the values for z, you can do something like this:

lapply(split(df, f = df$x), function(x) split(x$z, f = x$y))

#$place1
#$place1$type1
#[1] 46 41 50 59 54 51 66 70

#$place1$type2
#[1] 44 59 60 53 74 46 67 70

#$place1$type3
#[1] 63 70 80 44 73 74 58

#$place1$type4
#[1] 45 67 52 72 45 48 79 65

#$place1$type5
#[1] 75 54

EDIT

Seeing the link provided by @user295691, you could do the following as well.

split(df$z, interaction(df$x,df$y))

If you want each vector with z values, you could do:

list2env(split(df$z, interaction(df$x,df$y)), .GlobalEnv)

EDIT2

The OP wanted to run stats using this data. I, therefore, thought it would be a good idea to leave the following. If you need to create a data frame with different length of vectors in a list, you could do something like this. listvectors2df let you create a data frame with NA.

ana <- split(df$z, interaction(df$x,df$y))

# I used a good answer in this post and wrote the following.
#http://stackoverflow.com/questions/15201305/how-to-convert-a-list-consisting-of-vector-of-different-lengths-to-a-usable-data

listvectors2df <- function(l){

    n.obs <- sapply(l, length)
    seq.max <- seq_len(max(n.obs))
    mydf <- data.frame(sapply(l, "[", i = seq.max), stringsAsFactors = FALSE)

}

bob <- listvectors2df(ana)

Can also use split with interaction:

split(df, interaction(x,y))
$place1.type1
        x     y  z
6  place1 type1 57
25 place1 type1 55
27 place1 type1 55
28 place1 type1 75
29 place1 type1 54

$place2.type1
        x     y  z
36 place2 type1 70
42 place2 type1 69
45 place2 type1 78
57 place2 type1 79
59 place2 type1 46
60 place2 type1 45
63 place2 type1 73
64 place2 type1 79

$place3.type1
        x     y  z
85 place3 type1 54

To access each element:

> ll = split(df, interaction(x,y))
> 
> ll[[1]]
        x     y  z
6  place1 type1 57
25 place1 type1 55
27 place1 type1 55
28 place1 type1 75
29 place1 type1 54
> 
> ll[[2]]
        x     y  z
36 place2 type1 70
42 place2 type1 69
45 place2 type1 78
57 place2 type1 79
59 place2 type1 46
60 place2 type1 45
63 place2 type1 73
64 place2 type1 79

data.table can also be used:

library(data.table)
dtt = data.table(df)

dtt[order(x,y),list(meanz=mean(z), maxz=max(z), sumz=sum(z)),by=list(x,y)]
         x     y    meanz maxz sumz
 1: place1 type1 63.11111   80  568
 2: place1 type2 68.12500   79  545
 3: place1 type3 58.80000   76  294
 4: place1 type4 59.83333   79  359
 5: place1 type5 59.40000   80  297
 6: place2 type1 55.85714   69  391
 7: place2 type2 59.71429   71  418
 8: place2 type3 61.00000   76  305
 9: place2 type4 53.63636   71  590
10: place2 type5 44.66667   46  134
11: place3 type1 62.16667   74  373
12: place3 type2 63.42857   80  444
13: place3 type3 64.00000   77  384
14: place3 type4 61.28571   80  429
15: place3 type5 51.00000   60  408

There are a couple of solutions. The first is the lapply/split that jazzurro has provided. You could also combine the factors into a single factor, eg

> split(df, paste(df$x, df$y))
$`place1 type1`
        x     y  z
3  place1 type1 57
24 place1 type1 54

$`place1 type2`
        x     y  z
1  place1 type2 67
6  place1 type2 75
7  place1 type2 72
12 place1 type2 57
...

The other solution would be to use a library that has intrinsic support for multi-level grouping, like data.tables or plyr / dplyr . In dplyr , the operation would look like (including the summary, in this case the mean and max of the third column)

> df %>% group_by(x, y) %>% summarise(mean(z), max(z))
Source: local data frame [15 x 4]
Groups: x

        x     y  mean(z) max(z)
1  place1 type1 55.50000     57
2  place1 type2 65.50000     80
3  place1 type3 60.40000     78
4  place1 type4 57.12500     73
...

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