如何在使用ddply或aggregate时,使用返回向量的函数(例如fivenum)?

8

我想要使用几列数据将我的数据框拆分,并在每个组上调用fivenum

aggregate(Petal.Width ~ Species, iris, function(x) summary(fivenum(x)))

返回的值是一个只有两列的数据框,第二列是一个矩阵。如何将其转换为数据框的普通列?
更新: 我希望使用更少的代码得到以下类似的结果,使用 fivenum
ddply(iris, .(Species), summarise,
      Min = min(Petal.Width),
      Q1 = quantile(Petal.Width, .25),
      Med = median(Petal.Width),
      Q3 = quantile(Petal.Width, .75),
      Max = max(Petal.Width)
      )

返回值是一个包含七列的data.frame。它与matrix无关。也许如果您展示一下您期望的结果,回答这个问题会更容易些。 - nograpes
2
@nograpes 试着用 length() 包装示例代码行。 - mlt
4个回答

11

这里有一个使用 data.table 的解决方案(虽然没有明确要求,但它显然是 aggregateddply 的明显补充或替代品)。除了代码稍微长一些之外,反复调用 quantile 也会效率低下,因为每次调用都需要对数据进行排序。

library(data.table)
Tukeys_five <- c("Min","Q1","Med","Q3","Max") 

IRIS <- data.table(iris)
# this will create the wide data.table
lengthBySpecies <- IRIS[,as.list(fivenum(Sepal.Length)), by = Species]

# and you can rename the columns from V1, ..., V5 to something nicer

setnames(lengthBySpecies, paste0('V',1:5), Tukeys_five)


lengthBySpecies



      Species Min  Q1 Med  Q3 Max
1:     setosa 4.3 4.8 5.0 5.2 5.8
2: versicolor 4.9 5.6 5.9 6.3 7.0
3:  virginica 4.9 6.2 6.5 6.9 7.9

或者,使用适当的prob参数一次调用quantile函数。

IRIS[,as.list(quantile(Sepal.Length, prob = seq(0,1, by = 0.25))), by = Species]


       Species  0%   25% 50% 75% 100%
1:     setosa 4.3 4.800 5.0 5.2  5.8
2: versicolor 4.9 5.600 5.9 6.3  7.0
3:  virginica 4.9 6.225 6.5 6.9  7.9  
请注意,创建的列名在语法上无效,尽管您可以使用类似的重命名方法使用 setnames 进行更改。

编辑

有趣的是,如果设置names = TRUEquantile将设置结果向量的名称,并且这将复制(减慢计算速度和消耗内存-甚至在帮助文件中发出警告!)

因此,您应该使用

 IRIS[,as.list(quantile(Sepal.Length, prob = seq(0,1, by = 0.25), names = FALSE)), by = Species]

或者,如果您想返回指定的列表而不是在 R 内部进行复制

IRIS[,{quant <- as.list(quantile(Sepal.Length, prob = seq(0,1, by = 0.25), names = FALSE))
       setattr(quant, 'names', Tukeys_five)
       quant}, by = Species]

5
您可以使用do.call对每个矩阵元素递归调用data.frame,以获得具有向量元素的数据框架:
dim(do.call("data.frame",dfr))
[1] 3 7

str(do.call("data.frame",dfr))
'data.frame':   3 obs. of  7 variables:
 $ Species            : Factor w/ 3 levels "setosa","versicolor",..: 1 2 3
 $ Petal.Width.Min.   : num  0.1 1 1.4
 $ Petal.Width.1st.Qu.: num  0.2 1.2 1.8
 $ Petal.Width.Median : num  0.2 1.3 2
 $ Petal.Width.Mean   : num  0.28 1.36 2
 $ Petal.Width.3rd.Qu.: num  0.3 1.5 2.3
 $ Petal.Width.Max.   : num  0.6 1.8 2.5

4
据我所知,没有确切的方法可以做到你要求的事情,因为你正在使用的函数(fivenum)不会以可以轻松绑定到'ddply'函数内的列的方式返回数据。 不过,这很容易以编程方式清理。
第一步:使用'ddply'函数对每个'Species'值执行'fivenum'函数。
data <- ddply(iris, .(Species), summarize, value=fivenum(Petal.Width))

#       Species value
# 1      setosa   0.1
# 2      setosa   0.2
# 3      setosa   0.2
# 4      setosa   0.3
# 5      setosa   0.6
# 6  versicolor   1.0
# 7  versicolor   1.2
# 8  versicolor   1.3
# 9  versicolor   1.5
# 10 versicolor   1.8
# 11  virginica   1.4
# 12  virginica   1.8
# 13  virginica   2.0
# 14  virginica   2.3
# 15  virginica   2.5

现在,“fivenum”函数返回一个列表,因此我们每个物种都会得到5个行条目。这就是“fivenum”函数与我们作对的部分。
第二步:添加标签列。我们知道Tukey的五个数字是什么,所以我们按照“fivenum”函数返回它们的顺序依次进行调用。该列表将重复直到达到数据结尾。
Tukeys_five <- c("Min","Q1","Med","Q3","Max") 
data$label <- Tukeys_five

#       Species value label
# 1      setosa   0.1   Min
# 2      setosa   0.2    Q1
# 3      setosa   0.2   Med
# 4      setosa   0.3    Q3
# 5      setosa   0.6   Max
# 6  versicolor   1.0   Min
# 7  versicolor   1.2    Q1
# 8  versicolor   1.3   Med
# 9  versicolor   1.5    Q3
# 10 versicolor   1.8   Max
# 11  virginica   1.4   Min
# 12  virginica   1.8    Q1
# 13  virginica   2.0   Med
# 14  virginica   2.3    Q3
# 15  virginica   2.5   Max

步骤 3: 完成标签后,我们可以使用“reshape2”包中的“dcast”函数将此数据快速转换为新形状。

library(reshape2)
dcast(data, Species ~ label)[,c("Species",Tukeys_five)]

#      Species Min  Q1 Med  Q3 Max
# 1     setosa 0.1 0.2 0.2 0.3 0.6
# 2 versicolor 1.0 1.2 1.3 1.5 1.8
# 3  virginica 1.4 1.8 2.0 2.3 2.5

所有在末尾的垃圾只是指定列顺序,因为“dcast”函数会自动按字母顺序排列。希望这可以帮到您。
更新:我决定回来,因为我意识到还有另一种可用的选项。您始终可以将矩阵作为数据框定义的一部分绑定,因此您可以像这样解决您的“aggregate”函数。
data <- aggregate(Petal.Width ~ Species, iris, function(x) summary(fivenum(x))) 
result <- data.frame(Species=data[,1],data[,2])

#      Species Min. X1st.Qu. Median Mean X3rd.Qu. Max.
# 1     setosa  0.1      0.2    0.2 0.28      0.3  0.6
# 2 versicolor  1.0      1.2    1.3 1.36      1.5  1.8
# 3  virginica  1.4      1.8    2.0 2.00      2.3  2.5

我在考虑数据类型转换。通常我会使用reshape,但是看到它可以用plyr完成也很不错。你更新的答案本质上就是James建议的。我忘记了可以使用"cbind"将包括矩阵在内的data.frames隐式转换。 - mlt

0

这是我的解决方案:

ddply(iris, .(Species), summarize, value=t(fivenum(Petal.Width)))

它与Dinre所写的有何不同? - mlt
它简单、短小而流畅。在这里,“fivenum”的向量值形成一个矩阵。因此,结果有两列,一列是标签,另一列是五列的矩阵。 - pmjn6
当你调用 ncol 时,你会感到惊讶。 - mlt
我刚刚应用了 _ncol_,结果是2,就像我之前提到的那样。 - pmjn6
糟糕,我的错,我错过了你之前的确认。无论如何,讨论毫无意义,解决方案与Dinre步骤1没有什么不同。它有点不可用,因为存在太多间接层级,在大多数情况下需要被展平。 - mlt
要查看与步骤1的差异,您只需要尝试它们:将它们复制并粘贴到您的R控制台中并运行。我经常使用这种格式。例如,如果A是我上面代码的输出,则kmeans(A [,2],2)会根据其finvenum聚类人口。我不知道为什么您觉得有“太多级别的间接性”,我相信每个分析师都可以处理作用于向量的t()运算符。最后,我同意您的看法,认为这个讨论没有任何意义。 - pmjn6

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接