将函数应用于data.frame的每一行并保留列类

时间:2016-09-14 11:08:24

标签: r dataframe apply

我想知道是否有办法将函数应用于data.frame的每一行,以便保留列类?让我们看一个例子来澄清我的意思:

test <- data.frame(startdate = as.Date(c("2010-03-07", "2013-09-13", "2011-11-12")),
                   enddate = as.Date(c("2010-03-23", "2013-12-01", "2012-01-05")),
                   nEvents = c(123, 456, 789))

假设我希望通过在teststartdate之间插入所有日期来扩展data.frame enddate,并分配那些天的事件数。我的第一次尝试是这样的:

eventsPerDay1 <- function(row) {
    n_days <- as.numeric(row$enddate - row$startdate) + 1
    data.frame(date = seq(row$startdate, row$enddate, by = "1 day"),
               nEvents = rmultinom(1, row$nEvents, rep(1/n_days, n_days)))
}

apply(test, 1, eventsPerDay1)

但是,这是不可能的,因为apply会在as.matrix上调用test,因此会转换为字符矩阵并且所有列类都会丢失。

我已经找到了两个你可以在下面找到的解决方法,所以我的问题更多的是哲学性质。

library(magrittr)
############# Workaround 1
eventsPerDay2 <- function(startdate, enddate, nEvents) {
    n_days <- as.numeric(enddate - startdate) + 1
    data.frame(date = seq(startdate, enddate, by = "1 day"),
               nEvents = rmultinom(1, nEvents, rep(1/n_days, n_days)))
}

mapply(eventsPerDay2, test$startdate, test$enddate, test$nEvents, SIMPLIFY = F) %>%
    do.call(rbind, .)


############# Workaround 2
seq_along(test) %>%
    lapply(function(i) test[i, ]) %>%
    lapply(eventsPerDay1) %>%
    do.call(rbind, .)

我的问题&#34;解决方法如下:

  • 解决方法1:这可能不是最好的理由,但我根本不喜欢mapply。它具有与其他*apply函数不同的签名(因为参数的顺序不同),我总觉得for循环会更清晰。
  • 解决方法2:虽然非常灵活,但我认为目前尚不清楚发生了什么。

所以有人知道一个函数,其调用看起来像apply(test, 1, eventsPerDay1)并且可以工作吗?

3 个答案:

答案 0 :(得分:2)

我们可以使用data.table

执行此操作
library(data.table)
res <- setDT(test)[,n_days := as.numeric(enddate - startdate) + 1 
           ][, .(date = seq(startdate, enddate, by= "1 day"),
          nEvents = c(rmultinom(1, nEvents, rep(1/n_days, n_days)))),
        by =  1:nrow(test)][, nrow := NULL]
str(res)
#Classes ‘data.table’ and 'data.frame':  152 obs. of  2 variables:
# $ date   : Date, format: "2010-03-07" "2010-03-08" "2010-03-09" "2010-03-10" ...
# $ nEvents: int  5 9 7 11 6 6 10 7 12 3 ...

以上内容可以包含在函数

eventsPerDay <- function(dat){  
      as.data.table(dat)[, n_days:= as.numeric(enddate - startdate) + 1
       ][, .(date = seq(startdate, enddate, by= "1 day"),
    nEvents = c(rmultinom(1, nEvents, rep(1/n_days, n_days)))) , 1:nrow(dat)
        ][, nrow := NULL][]
  }

eventsPerDay(test)

答案 1 :(得分:2)

另一个想法:

library(dplyr)
library(tidyr)

test %>%
  mutate(id = row_number()) %>%
  group_by(startdate) %>%
  complete(startdate = seq.Date(startdate, enddate, 1), nesting(id)) %>%
  group_by(id) %>%
  mutate(nEvents = rmultinom(1, first(nEvents), rep(1/n(), n()))) %>%
  select(startdate, nEvents)

给出了:

#Source: local data frame [152 x 3]
#Groups: id [3]
#
#      id  startdate nEvents
#   <int>     <date>   <int>
#1      1 2010-03-07       6
#2      1 2010-03-08       6
#3      1 2010-03-09       6
#4      1 2010-03-10       7
#5      1 2010-03-11      12
#6      1 2010-03-12       5
#7      1 2010-03-13       8
#8      1 2010-03-14       5
#9      1 2010-03-15       5
#10     1 2010-03-16       9
## ... with 142 more rows

答案 2 :(得分:0)

我问过自己同样的问题。

我要么最终将df拆分成一个列表(基本方式)

xy <- data.frame()
xy.list <- split(xy, 1:nrow(xy))
out <- lapply(xy.list, function(x) ...)
answer <- unlist(out)

或尝试使用逐行的hadleyverse dplyr方法(黑盒方法)

xy %>%
rowwise() %>%
mutate(newcol = function(x) ....)

我同意它们应该是apply(xy,1,function(x))的基本实现,该实现不会强制转换为字符,但是我认为R古代人出于原始原因可以实现的高级原因而实现了矩阵转换不明白。