如何传递多元向量值函数(具有可变长度输出)进行汇总

时间:2019-02-13 19:58:13

标签: r dataframe data.table aggregate

我在R中有一个要汇总的数据框。我要应用于每个子集的摘要函数是一个自定义函数,该函数将几个变量(列)作为输入,并返回一个向量或可变长度的列表。作为输出,我希望有一个数据帧,其中包含分组变量的一列,而另一列包含输出矢量(长度可变)。

举一个模拟的例子,假设我有以下数据框:

df <- data.frame( particle = c(rep("X",5),rep("Y",3),rep("Z",4)),
 time = c(1:5,1:3,1:4), state = c(c("A","A","B","C","A"),c("A","B","B"),
 c("B","C","A","A")), energy = round(runif(12,0,10)))

> df
   particle time state energy
1         X    1     A      9
2         X    2     A      8
3         X    3     B      7
4         X    4     C      5
5         X    5     A      0
6         Y    1     A      1
7         Y    2     B      7
8         Y    3     B      7
9         Z    1     B      3
10        Z    2     C      9
11        Z    3     A      5
12        Z    4     A      6

我想为每个粒子获取它们每次改变状态时拥有的能量的列表。我正在寻找的输出是这样的:

>
   particle      energy
1         X      c(9,7,5,0)
2         Y      c(1,7)
3         Z      c(3,9,5)

为此,我将定义如下函数:

myfun <- function(state, energy){
   tempstate <- state[1]
   energyvec <- energy[1]
   for(i in 2:length(state)){
      if(state[i] != tempstate){
         energyvec <- c(energyvec, energy[i])
         tempstate <- state[i]
      }
   }
   return(energyvec)
}

并尝试通过某种方式将其汇总

我为此尝试过的两个数据结构是data.frame和data.table。

在data.frame中,使用返回矢量的自定义函数似乎可以提供我想要的正确输出格式,即输出列实际上是一个列表,并且每一行都包含一个列表,其中包含功能。但是,以这种方式进行聚合时,似乎无法将多个列传递给该函数。

使用data.table,当考虑多个变量的函数时,聚合更容易进行。但是,我似乎无法获得所需的输出。确实,

dt <- data.table(df)
dt[,myfun(state, energy), by= Particle]

仅返回energyvec的第一个元素(而不是矢量),并且

dt <- data.table(df)
dt[,as.list(myfun(state, energy)), by= Particle]

不起作用,因为输出的长度都不相同。

有没有其他方法可以做到这一点?

非常感谢您的所有帮助!

3 个答案:

答案 0 :(得分:1)

这是一种tidyverse的方法:

library(tidyverse)

df <- data.frame( particle = c(rep("X",5),rep("Y",3),rep("Z",4)),
                  time = c(1:5,1:3,1:4), state = c(c("A","A","B","C","A"),c("A","B","B"),
                                                   c("B","C","A","A")), energy = round(runif(12,0,10)))

# Hard-code energy to make this reproducible
df$energy <- c(9, 8, 7, 5, 0, 1, 7, 7, 3, 9, 5, 6)

df %>%
  group_by(particle) %>%
  mutate(
    changed_state = coalesce(state != lag(state, 1), TRUE)
  ) %>%
  filter(changed_state) %>%
  summarise(
    string = toString(energy)
  )
#> # A tibble: 3 x 2
#>   particle string    
#>   <fct>    <chr>     
#> 1 X        9, 7, 5, 0
#> 2 Y        1, 7      
#> 3 Z        3, 9, 5

我将分别运行管道的每一行。基本上,通过检查“ this”状态是否与最后一个状态changed_state相匹配来创建lag(state, 1)变量。由于我们只关心何时发生这种情况,因此我们在filterTRUE(更冗长的行将是filter(changed_state == TRUE)toString函数根据需要折叠能量行,我们已经被particle“分组”了。

答案 1 :(得分:0)

data.table方法

样本数据

#stolen from JasonAizkalns's answer
df <- data.frame( particle = c(rep("X",5),rep("Y",3),rep("Z",4)),
                  time = c(1:5,1:3,1:4), state = c(c("A","A","B","C","A"),c("A","B","B"),
                                                   c("B","C","A","A")), energy = round(runif(12,0,10)))

df$energy <- c(9, 8, 7, 5, 0, 1, 7, 7, 3, 9, 5, 6)

代码

library( data.table )
#create data.table
dt <- as.data.table(df)

#use `uniqlist` to get rownumbers where the value of `state` changes, 
# then get these rows into a subset
result <- dt[ data.table:::uniqlist(dt[, c("particle", "state")]), ]

#split the resulting `energy`-column by the contents of the `particle`-column
l <- split( result$energy, result$particle)
# $X
# [1] 9 7 5 0
# 
# $Y
# [1] 1 7
# 
# $Z
# [1] 3 9 5

#craete final output
data.table( particle = names(l), energy = l )
#    particle  energy
# 1:        X 9,7,5,0
# 2:        Y     1,7
# 3:        Z   3,9,5

答案 2 :(得分:0)

另一种可能的data.table方法

library(data.table)
setDT(DF)[, .(energy=.(.SD[, first(energy), by=.(rleid(state))]$V1)), by=.(particle)]

输出:

   particle  energy
1:        X 9,4,6,9
2:        Y     2,9
3:        Z   7,6,1

数据:

set.seed(0L)
DF <- data.frame( particle = c(rep("X",5),rep("Y",3),rep("Z",4)),
    time = c(1:5,1:3,1:4), state = c(c("A","A","B","C","A"),c("A","B","B"),
        c("B","C","A","A")), energy = round(runif(12,0,10)))
DF
#    particle time state energy
# 1         X    1     A      9
# 2         X    2     A      3
# 3         X    3     B      4
# 4         X    4     C      6
# 5         X    5     A      9
# 6         Y    1     A      2
# 7         Y    2     B      9
# 8         Y    3     B      9
# 9         Z    1     B      7
# 10        Z    2     C      6
# 11        Z    3     A      1
# 12        Z    4     A      2