0

If I have a data set laid out like:

Cohort Food1 Food2 Food 3 Food 4
--------------------------------
Group   1     1     2       3
 A      1     1     0       1
 B      0     0     1       0
 C      1     1     0       1
 D      0     0     0       1

I want to sum each row, where I can define food groups into different categories. So I would like to use the Group row as the defining vector.

Which would mean that food1 and food2 are in group 1, food3 is in group 2, food 4 is in group 3.

Ideal output something like:

Cohort Group1 Group2 Group3
 A      2       0      1
 B      0       1      0
 C      2       0      1
 D      0       0      1

I tried using this rowsum() based functions but no luck, do I need to use ddply() instead?

Example data from comment:

dat <-
structure(list(species = c("group", "princeps", "bougainvillei", 
"hombroni", "lindsayi", "concretus", "galatea", "ellioti", "carolinae", 
"hydrocharis"), locust = c(1L, 0L, 0L, 1L, 0L, 0L, 0L, 0L, 0L, 
0L), grasshopper = c(1L, 0L, 0L, 1L, 0L, 0L, 1L, 0L, 1L, 0L), 
    snake = c(2L, 0L, 0L, 0L, 0L, 1L, 0L, 0L, 0L, 0L), fish = c(2L, 
    1L, 0L, 1L, 1L, 0L, 1L, 0L, 1L, 0L), frog = c(2L, 0L, 0L, 
    0L, 0L, 0L, 0L, 1L, 0L, 0L), toad = c(2L, 0L, 0L, 0L, 0L, 
    1L, 0L, 0L, 0L, 0L), fruit = c(3L, 0L, 0L, 0L, 0L, 1L, 1L, 
    0L, 0L, 0L), seed = c(3L, 0L, 0L, 0L, 0L, 1L, 0L, 0L, 0L, 
    0L)), .Names = c("species", "locust", "grasshopper", "snake", 
"fish", "frog", "toad", "fruit", "seed"), class = "data.frame", row.names = c(NA, 
-10L))
TARehman
  • 6,659
  • 3
  • 33
  • 60
Nick Crouch
  • 301
  • 3
  • 14

2 Answers2

2

There are most likely more direct approaches, but here is one you can try:

  1. First, create a copy of your data minus the second header row.

    dat2 <- dat[-1, ]
    
  2. melt() and dcast() and so on from the "reshape2" package don't work nicely with duplicated column names, so let's make the column names more "reshape2 appropriate".

    Seq <- ave(as.vector(unlist(dat[1, -1])), 
               as.vector(unlist(dat[1, -1])), 
               FUN = seq_along)
    names(dat2)[-1] <- paste("group", dat[1, 2:ncol(dat)], 
                             ".", Seq, sep = "")
    
  3. melt() the dataset

    m.dat2 <- melt(dat2, id.vars="species")
    
  4. Use the colsplit() function to split the columns correctly.

    m.dat2 <- cbind(m.dat2[-2], 
                    colsplit(m.dat2$variable, "\\.", 
                             c("group", "time")))
    head(m.dat2)
    #         species value  group time
    # 1      princeps     0 group1    1
    # 2 bougainvillei     0 group1    1
    # 3      hombroni     1 group1    1
    # 4      lindsayi     0 group1    1
    # 5     concretus     0 group1    1
    # 6       galatea     0 group1    1
    
  5. Proceed with dcast() as usual

    dcast(m.dat2, species ~ group, sum)
    #         species group1 group2 group3
    # 1 bougainvillei      0      0      0
    # 2     carolinae      1      1      0
    # 3     concretus      0      2      2
    # 4       ellioti      0      1      0
    # 5       galatea      1      1      1
    # 6      hombroni      2      1      0
    # 7   hydrocharis      0      0      0
    # 8      lindsayi      0      1      0
    # 9      princeps      0      1      0
    

Note: Edited because original answer was incorrect.

Update: An easier way in base R

This problem is much more easily solved if you start by transposing your data.

dat3 <- t(dat[-1, -1])
dat3 <- as.data.frame(dat3)
names(dat3) <- dat[[1]][-1]
t(do.call(rbind, lapply(split(dat3, as.numeric(dat[1, -1])), colSums)))
#               1 2 3
# princeps      0 1 0
# bougainvillei 0 0 0
# hombroni      2 1 0
# lindsayi      0 1 0
# concretus     0 2 2
# galatea       1 1 1
# ellioti       0 1 0
# carolinae     1 1 0
# hydrocharis   0 0 0
A5C1D2H2I1M1N2O1R2T1
  • 190,393
  • 28
  • 405
  • 485
  • Melting, aggregating, and re-casting is more straightforward than the approach I was trying to work up. – Brian Diggs Oct 08 '12 at 19:12
  • Does this actually give the answer requested? As far as I can tell, these sums are not correct... – TARehman Oct 08 '12 at 20:07
  • @TARehman, the error was already noted (see comment under the main question) and now corrected, if you care to correct the downvote ;) – A5C1D2H2I1M1N2O1R2T1 Oct 08 '12 at 20:13
  • @BrianDiggs, unfortunately, my original answer was wrong because of what happens when melting with duplicated column names. I've updated it now, though, and also provided a more direct base R solution. Congrats on your 10k, by the way! – A5C1D2H2I1M1N2O1R2T1 Oct 09 '12 at 04:56
1

You can do this using base R fairly easily. Here's an example.

First, figure out which animals belong in which group:

groupings <- as.data.frame(table(as.numeric(dat[1,2:9]),names(dat)[2:9]))

attach(groupings)
grp1 <- groupings[Freq==1 & Var1==1,2]
grp2 <- groupings[Freq==1 & Var1==2,2]
grp3 <- groupings[Freq==1 & Var1==3,2]
detach(groupings)

Then, use the groups to do a rowSums() on the correct columns.

dat <- cbind(dat,rowSums(dat[as.character(grp1)]))
dat <- cbind(dat,rowSums(dat[as.character(grp2)]))
dat <- cbind(dat,rowSums(dat[as.character(grp3)]))

Delete the initial row and the intermediate columns:

dat <- dat[-1,-c(2:9)]

Then, just rename things correctly:

row.names(dat) <- rm()
names(dat) <- c("species","group_1","group_2","group_3")

And you ultimately get:

      species group_1 group_2 group_3
bougainvillei       0       0       0
    carolinae       1       1       0
    concretus       0       2       2
      ellioti       0       1       0
      galatea       1       1       1
     hombroni       2       1       0
  hydrocharis       0       0       0
     lindsayi       0       1       0
     princeps       0       1       0

EDITED: Changed sort order to alphabetical, like other answer.

TARehman
  • 6,659
  • 3
  • 33
  • 60