Дали агрегатът е правилната функция за използване тук?

разгледайте следния кадър от данни:

d <- data.frame(c1=c(rep("a",6),rep("b",6)), 
                c2=c("v1","v1","v2","v3","v3","v1", "v2","v3","v1","v2","v3","v2"), 
                c3=c(1.4,-1.2,1.5,1.6,-1.7,1.2, -1.1,-1.2,1.3,1.5,1.1,-1.9))

Искам да добавя 4-та колона c4, която брои колко положителни и отрицателни числа има за "a" и "b" в колона c1. Трябва обаче да се вземат предвид само онези стойности в c3, където c2 е равно на "v1". Освен това, ако има само положителни или отрицателни стойности, трябва да се отпечата празен низ

Така че за моя пример 4-тата колона трябва да изглежда така:

> d
   c1 c2   c3 c4
1   a v1  1.4 2/1
2   a v1 -1.2 2/1
3   a v2  1.5 2/1
4   a v3  1.6 2/1
5   a v3 -1.7 2/1
6   a v1  1.2 2/1
7   b v2 -1.1 " "
8   b v3 -1.2 " "
9   b v1  1.3 " "
10  b v2  1.5 " "
11  b v3  1.1 " "
12  b v2 -1.9 " "

за a се използва стойността 2/1, тъй като има две положителни числа и едно отрицателно число, където c2="v1"

В момента се доближих най-много с помощта на агрегатната функция, но все още не го разбирам правилно. Не сте сигурни дали има по-добра функция, която да използвате за такъв проблем?


person user969113    schedule 25.11.2012    source източник


Отговори (3)


За всичко, което използва повече от една колона (освен тази(ите), по която групирате), намирам plyr за по-удобно:

ddply(d, "c1", transform,
               c4 = { pos <- sum(c2 == "v1" & c3 >= 0)
                      neg <- sum(c2 == "v1" & c3 < 0)
                      ifelse(pos * neg == 0, ' ', paste(pos, neg, sep = '/')) })

#    c1 c2   c3  c4
# 1   a v1  1.4 2/1
# 2   a v1 -1.2 2/1
# 3   a v2  1.5 2/1
# 4   a v3  1.6 2/1
# 5   a v3 -1.7 2/1
# 6   a v1  1.2 2/1
# 7   b v2 -1.1    
# 8   b v3 -1.2    
# 9   b v1  1.3    
# 10  b v2  1.5    
# 11  b v3  1.1    
# 12  b v2 -1.9    
person flodel    schedule 25.11.2012
comment
Съжалявам, че се върнах при вас толкова късно. имах други неща за вършене между тях. Наистина оценявам работата ви, която сте свършили тук! Благодаря много! - person user969113; 26.11.2012

Ако искате да използвате обикновена R-база, aggregate трябва да бъде ваш приятел:

ag <- aggregate.data.frame(
  d$c3,
  by = list(d$c1, d$c2),
  FUN = function(x){ paste(sum(x < 0), sum(x>0), sep="/") }
)
> ag
  Group.1 Group.2   x
1       a      v1 1/2
2       b      v1 0/1
3       a      v2 0/1
4       b      v2 2/1
5       a      v3 1/1
6       b      v3 1/1

След това можете просто да merge обобщените данни във вашия оригинален data.frame:

d <- merge( d, ag, by.x = c( "c1", "c2" ), by.y = c( "Group.1", "Group.2" ), all.x = TRUE )

Въпреки това бих препоръчал да използвате ddply от plyr пакет поради неговата простота:

library("plyr")
d <- ddply( d, c("c1","c2"), function(x) {
  x$c4 <- paste(sum( x$c3 < 0), sum(x$c3 > 0), sep="/")
  return(x)
})

РЕДАКТИРАНЕ:

След като препрочетох въпроса, това трябва да е правилното решение с помощта на aggregate:

d.sub <- d[ d$c2 == "v1", , drop=FALSE ]
ag <- aggregate(
  d.sub$c3,
  by = list(d.sub$c1),
  FUN = function(x){ # taken from @flodel
    pos <- sum(x < 0);
    neg <- sum( x > 0 );
    ifelse( pos * neg == 0, "", paste( pos, neg, sep="/") )
  }
)
d <- merge( d, ag, by.x = "c1", by.y = "Group.1", all.x = TRUE  )

Относно решението на ddply @flodel е как бих го направил и аз.

person Beasterfield    schedule 25.11.2012
comment
Мисля, че не сте разбрали проблема, c1 трябва да е единствената групираща променлива. След това във всяка група резултатите се основават само на подмножество от данни, където c2 == "v1". Това е доста ясно от описанието на OP и очаквания резултат. - person flodel; 25.11.2012
comment
Смятам, че сте доказали, че aggregate може да не е най-добрият инструмент за тази конкретна задача, тъй като стъпката merge би била доста скъпа. Ако трябваше да използвам подход base тук, тогава може би split/lapply/rbind би бил по-добър. Което ddply прави за вас. - person flodel; 25.11.2012
comment
@flodel затова написа, че бих препоръчал използването на ddply. Но не аз поисках aggregate и понякога трябва да видиш грозното решение, за да оцениш простото. - person Beasterfield; 25.11.2012
comment
Искам също да ви благодаря за усилията ви да ни покажете, че ddply трябва да се използва вместо агрегатната функция за тази конкретна задача. страхотно благодаря - person user969113; 26.11.2012

Ето друго решение с ddply, използващо малко по-различен подход:

library(plyr)
ddply(d, .(c1), transform, c4 = {
                        tab <- table(factor(sign(c3[c2 == "v1"]), c(1, -1))); 
                        ifelse(any(tab == 0), " ", paste(tab, collapse = "/")) })



#    c1 c2   c3  c4
# 1   a v1  1.4 2/1
# 2   a v1 -1.2 2/1
# 3   a v2  1.5 2/1
# 4   a v3  1.6 2/1
# 5   a v3 -1.7 2/1
# 6   a v1  1.2 2/1
# 7   b v2 -1.1    
# 8   b v3 -1.2    
# 9   b v1  1.3    
# 10  b v2  1.5    
# 11  b v3  1.1    
# 12  b v2 -1.9
person Sven Hohenstein    schedule 25.11.2012
comment
IMHO, по-трудно за дешифриране. Също така не е много гъвкав около нули. - person flodel; 25.11.2012
comment
@flodel Защо мислиш, че не е много гъвкаво около нули? - person Sven Hohenstein; 25.11.2012
comment
Защото sign(0) е равно на 0, нали? Така че те се премахват от текущия ви код. Ако вместо това потребителят иска да брои нули към едното или другото (положителни или отрицателни), това няма да е много лесно. - person flodel; 25.11.2012
comment
@flodel Съгласен, но ако потребителят искаше да брои нули като отделна категория (напр. 2/0/1), модификациите бяха: (1) Добавете , 0 към факторните нива и (2) заменете any(tab == 0) с sum(tab == 0) > 1. - person Sven Hohenstein; 25.11.2012
comment
Благодаря и за вашето решение. Отбелязах първия отговор, тъй като мислех, че е малко по-четлив, въпреки че съм сигурен, че работи чудесно :-) благодаря! - person user969113; 26.11.2012