Как передать многомерную векторную функцию (с выводом переменной длины) в агрегат - PullRequest
0 голосов
/ 13 февраля 2019

У меня есть фрейм данных в 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 голосов
/ 13 февраля 2019

Вот подход 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

Я бы провел каждую линию трубы отдельно.По сути, создайте переменную changed_state, проверив, соответствует ли состояние "this" последнему состоянию lag(state, 1).Поскольку мы заботимся только о том, когда это происходит, мы filter где это TRUE (более подробная строка будет filter(changed_state == TRUE). Функция toString по желанию сворачивает энергетические ряды, и мы уже "сгруппированы" по particle.

0 голосов
/ 14 февраля 2019

Другой возможный 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
0 голосов
/ 13 февраля 2019

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
...