按数据框中的组划分不同组的中位数

时间:2016-03-01 12:57:16

标签: r

我有一个包含列group_IDclass的数据框,以及多个数字要素和一些字符元数据,即:

group_ID  class  var1  var2  var3  metadata
a         foo    1     324   3     cat
a         bar    1.3   34    53    dog
a         baz    31    34    5     elephant
b         foo    34    34    943   dolphin
b         bar    94    51    23    chipmunk
b         baz    985   595   43    badger
c         foo    43    93    23    tapir
c         bar    43    23    23    monkey
c         baz    40    53    512   duck

我想计算每个foo的课程group_ID的中位数,然后将每行除以与group_ID匹配的中位数。

在此示例中,每个foo只有1行,因此中位数与初始值相同,但实际上每个class和{{1}都有很多行}。

有一种简单的方法吗?我对far的最佳尝试包括为group_ID的中值创建一个单独的数据帧,然后通过group_ID进行拆分并在一个可怕的循环中进行扫描,但最终我失去了元数据列。这样做似乎很常见,我确定我错过了什么。

任何帮助都将不胜感激。

3 个答案:

答案 0 :(得分:7)

我们可以使用mutate_each中的dplyr除以条件。

library(dplyr)
df %>% group_by(group_ID) %>%
  mutate_each(funs(./median(.[class == "foo"])), var1:var3)
# Source: local data frame [9 x 6]
# Groups: group_ID
# 
#   group_ID class       var1       var2        var3 metadata
# 1        a   foo  1.0000000  1.0000000  1.00000000      cat
# 2        a   bar  1.3000000  0.1049383 17.66666667      dog
# 3        a   baz 31.0000000  0.1049383  1.66666667 elephant
# 4        b   foo  1.0000000  1.0000000  1.00000000  dolphin
# 5        b   bar  2.7647059  1.5000000  0.02439024 chipmunk
# 6        b   baz 28.9705882 17.5000000  0.04559915   badger
# 7        c   foo  1.0000000  1.0000000  1.00000000    tapir
# 8        c   bar  1.0000000  0.2473118  1.00000000   monkey
# 9        c   baz  0.9302326  0.5698925 22.26086957     duck

如果OP希望将这些列添加为新的/附加列并保持以前的数据不变,您可以将上述方法修改为:

df %>% 
  group_by(group_ID) %>%
  mutate_each(funs(./median(.[class == "foo"])), setNames(var1:var3, paste0("varN", 1:3)))

答案 1 :(得分:5)

这是一个data.table解决方案。我们转换了' data.frame'到' data.table' (setDT(df)),按' group_ID'分组,我们循环(使用lapply)通过以列名称开头的列子集' var' (使用grep我们进行子集化),将每列与该列的median子集相对应,该列对应于' foo' ' class'中的价值。这可以作为新列分配(:=),也可以将其分配回同一列以替换原始列。替换原始列的一个问题是我们应该将原始的class与替换的列匹配。如果是原来的' var'列为numeric,然后它将用作median计算,并将新列转换为numeric。如果原始列为integer类,则可能的选项是将类更改为numeric然后分配。

library(data.table)
setDT(df)[, paste0("varN", 1:3) := lapply(.SD[, 
     grep("^var", names(.SD)), with=FALSE], 
         function(x) x/median(x[class=="foo"])), group_ID]
df
# group_ID class  var1 var2 var3 metadata      varN1      varN2       varN3
#1:        a   foo   1.0  324    3      cat  1.0000000  1.0000000  1.00000000
#2:        a   bar   1.3   34   53      dog  1.3000000  0.1049383 17.66666667
#3:        a   baz  31.0   34    5 elephant 31.0000000  0.1049383  1.66666667
#4:        b   foo  34.0   34  943  dolphin  1.0000000  1.0000000  1.00000000
#5:        b   bar  94.0   51   23 chipmunk  2.7647059  1.5000000  0.02439024
#6:        b   baz 985.0  595   43   badger 28.9705882 17.5000000  0.04559915
#7:        c   foo  43.0   93   23    tapir  1.0000000  1.0000000  1.00000000
#8:        c   bar  43.0   23   23   monkey  1.0000000  0.2473118  1.00000000
#9:        c   baz  40.0   53  512     duck  0.9302326  0.5698925 22.26086957

答案 2 :(得分:3)

1)by 这是基础R解决方案:

do.call("rbind", by(DF, DF$group_ID, function(d)
      data.frame(d, sapply(d[3:5], function(x) x / median(x[d$class == "foo"])))
))

,并提供:

    group_ID class  var1 var2 var3 metadata     var1.1     var2.1      var3.1
a.1        a   foo   1.0  324    3      cat  1.0000000  1.0000000  1.00000000
a.2        a   bar   1.3   34   53      dog  1.3000000  0.1049383 17.66666667
a.3        a   baz  31.0   34    5 elephant 31.0000000  0.1049383  1.66666667
b.4        b   foo  34.0   34  943  dolphin  1.0000000  1.0000000  1.00000000
b.5        b   bar  94.0   51   23 chipmunk  2.7647059  1.5000000  0.02439024
b.6        b   baz 985.0  595   43   badger 28.9705882 17.5000000  0.04559915
c.7        c   foo  43.0   93   23    tapir  1.0000000  1.0000000  1.00000000
c.8        c   bar  43.0   23   23   monkey  1.0000000  0.2473118  1.00000000
c.9        c   baz  40.0   53  512     duck  0.9302326  0.5698925 22.26086957

2)by / sweep 使用sweep的替代方案,而且只有基本函数是:

do.call("rbind", by(DF, DF$group_ID, function(d) {
      med <- apply(subset(d, class == "foo")[3:5], 2, median)
      data.frame(d, sweep(as.matrix(d[3:5]), 2, med, "/"))
    }))

3)sapply / ave 另一个基本解决方案是将ave应用于每个var列:

data.frame(DF, sapply(names(DF[3:5]), function(j)
    ave(1:nrow(DF), DF$group_ID, FUN = function(i)
        DF[i, j] / median(subset(DF[i, ], class == "foo")[[j]]))
))

注意:可重复形式的输入DF为:

DF <- structure(list(group_ID = structure(c(1L, 1L, 1L, 2L, 2L, 2L, 
3L, 3L, 3L), .Label = c("a", "b", "c"), class = "factor"), class = structure(c(3L, 
1L, 2L, 3L, 1L, 2L, 3L, 1L, 2L), .Label = c("bar", "baz", "foo"
), class = "factor"), var1 = c(1, 1.3, 31, 34, 94, 985, 43, 43, 
40), var2 = c(324L, 34L, 34L, 34L, 51L, 595L, 93L, 23L, 53L), 
    var3 = c(3L, 53L, 5L, 943L, 23L, 43L, 23L, 23L, 512L), metadata = structure(c(2L, 
    4L, 7L, 5L, 3L, 1L, 9L, 8L, 6L), .Label = c("badger", "cat", 
    "chipmunk", "dog", "dolphin", "duck", "elephant", "monkey", 
    "tapir"), class = "factor")), .Names = c("group_ID", "class", 
"var1", "var2", "var3", "metadata"), class = "data.frame", row.names = c(NA, 
-9L))