Partitioning data.frame according to condition

137 views Asked by At

I have a data.frame shaped like:

c <- data.frame(name=c("a", "a", "b", "b", "c", "c","d","d"), value=c(1,3,2,4,5,3,4,5), address=c("rrrr","rrrr","zzzz","aaaa","ssss","jjjj","qqqq","qqqq"))
> c
  name value address
1    a     1    rrrr
2    a     3    rrrr
3    b     2    zzzz 
4    b     4    aaaa
5    c     5    ssss
6    c     3    jjjj
7    d     4    qqqq
8    d     5    qqqq 

I am trying to split this data frame into two separate data frames according to one simple rule: group together people who didn't change address and group together people that changed address. Any hint on how to accomplish the task?

So far I am playing, with no avail, with:

for(i in seq(1,8, by=2)){
    print(i)
    print(unlist(c[which(c[i,3]==c[(i+1),3]),]))    
}
3

There are 3 answers

2
IRTFM On BEST ANSWER

This counts the number of addresses and splits on that basis. There is a hurdle to get over and it related to always getting <NA> from ave until using as.character. There was a warning message from which I'm copying the beginning, so searchers might be able to find this:

Warning messages:
1: In `[<-.factor`(`*tmp*`, i, value = c(1L, 1L)) :

The successful version (using a data-object named cc):

 split(cc,  ave(as.character(cc$address), cc$name, FUN=function(x) sum(!duplicated(x)) ) )

$`1`
  name value address
1    a     1    rrrr
2    a     3    rrrr
7    d     4    qqqq
8    d     5    qqqq

$`2`
  name value address
3    b     2    zzzz
4    b     4    aaaa
5    c     5    ssss
6    c     3    jjjj

If you really wanted a bipartite split then convert to logical with > 1:

 split(cc, ave(as.character(cc$address), cc$name, FUN=function(x) sum(!duplicated(x)) ) >1)

$`FALSE`
  name value address
1    a     1    rrrr
2    a     3    rrrr
7    d     4    qqqq
8    d     5    qqqq

$`TRUE`
  name value address
3    b     2    zzzz
4    b     4    aaaa
5    c     5    ssss
6    c     3    jjjj

I don't understand the comment. This is what I get as str(dat):

List of 2
 $ FALSE:'data.frame':  4 obs. of  3 variables:
  ..$ name   : Factor w/ 4 levels "a","b","c","d": 1 1 4 4
  ..$ value  : num [1:4] 1 3 4 5
  ..$ address: Factor w/ 6 levels "aaaa","jjjj",..: 4 4 3 3
 $ TRUE :'data.frame':  4 obs. of  3 variables:
  ..$ name   : Factor w/ 4 levels "a","b","c","d": 2 2 3 3
  ..$ value  : num [1:4] 2 4 5 3
  ..$ address: Factor w/ 6 levels "aaaa","jjjj",..: 6 1 5 2
1
jeremycg On

using dplyr:

library(dplyr)
z<-c %>% group_by(name) %>% 
         mutate(changed = n_distinct(address))
split(z, z$changed)

Thanks to @akrun for reminding me of n_distinct

2
cr1msonB1ade On

@jeremycg's answer is great and I am trying to learn dplyr, but here is the non-dplyr version as well.

numAddresses <- sapply(split(c, c$name), function(x)
    length(unique(x$address)))
split(c, numAddresses[c$address])