【问题标题】:r - apply function to each row of a data.tabler - 将函数应用于 data.table 的每一行
【发布时间】:2015-08-11 05:01:18
【问题描述】:

我希望使用data.table 来提高给定函数的速度,但我不确定我是否以正确的方式实现它:

数据

给定两个data.tables(dtdt_lookup

library(data.table)
set.seed(1234)
t <- seq(1,100); l <- letters; la <- letters[1:13]; lb <- letters[14:26]
n <- 10000
dt <- data.table(id=seq(1:n), 
                 thisTime=sample(t, n, replace=TRUE), 
                 thisLocation=sample(la,n,replace=TRUE),
                 finalLocation=sample(lb,n,replace=TRUE))
setkey(dt, thisLocation)

set.seed(4321)
dt_lookup <- data.table(lkpId = paste0("l-",seq(1,1000)),
                        lkpTime=sample(t, 10000, replace=TRUE),
                        lkpLocation=sample(l, 10000, replace=TRUE))
## NOTE: lkpId is purposly recycled
setkey(dt_lookup, lkpLocation)

我有一个函数可以找到包含thisLocationfinalLocationlkpId,并且具有“最近的”lkpTime(即thisTime - lkpTime 的最小非负值)

功能

## function to get the 'next' lkpId (i.e. the lkpId with both thisLocation and finalLocation,
## with the minimum non-negative time between thisTime and dt_lookup$lkpTime)
getId <- function(thisTime, thisLocation, finalLocation){

  ## filter lookup based on thisLocation and finalLocation,
  ## and only return values where the lkpId has both 'this' and 'final' locations
  tempThis <- unique(dt_lookup[lkpLocation == thisLocation,lkpId])
  tempFinal <- unique(dt_lookup[lkpLocation == finalLocation,lkpId])
  availServices <- tempThis[tempThis %in% tempFinal]

  tempThisFinal <- dt_lookup[lkpId %in% availServices & lkpLocation==thisLocation, .(lkpId, lkpTime)]

  ## calcualte time difference between 'thisTime' and 'lkpTime' (from thisLocation)
  temp2 <- thisTime - tempThisFinal$lkpTime

  ## take the lkpId with the minimum non-negative difference
  selectedId <- tempThisFinal[min(which(temp2==min(temp2[temp2>0]))),lkpId]
  selectedId
}

尝试解决方案

我需要为dt 的每一行获取lkpId。因此,我最初的直觉是使用*apply 函数,但是当n/nrow &gt; 1,000,000 时(对我来说)太长了。所以我尝试实现data.table 解决方案,看看它是否更快:

selectedId <- dt[,.(lkpId = getId(thisTime, thisLocation, finalLocation)),by=id]

但是,我对data.table 还很陌生,与*apply 解决方案相比,这种方法似乎没有任何性能提升:

lkpIds <- apply(dt, 1, function(x){
  thisLocation <- as.character(x[["thisLocation"]])
  finalLocation <- as.character(x[["finalLocation"]])
  thisTime <- as.numeric(x[["thisTime"]])
  myId <- getId(thisTime, thisLocation, finalLocation)
})

对于 n = 10,000,两者都需要约 30 秒。

问题

有没有更好的方法使用data.tablegetId 函数应用于dt 的每一行?

2015 年 12 月 8 日更新

感谢来自@eddi 的指针,我重新设计了我的整个算法并使用了滚动连接(a good introduction),从而正确使用了data.table。稍后我会写一个答案。

【问题讨论】:

  • 我建议尽量减少示例数据,如果您设法在 10-20 行上显示问题,您将让更多用户能够调查问题。此外,您当前的解决方案在我的机器上引发了多个警告。因此,拥有少量示例数据,您还可以发布预期的输出。
  • @jangorecki 我的问题不是关于代码或函数的问题本身,而是询问是否有更好的方法在大型数据集上使用data.table .对于这个例子,警告可以被忽略(它们是函数找不到答案的地方——没关系)。
  • data.table 不会神奇地加速完全相同的循环。相反,您应该重新考虑您的算法 - 查找最近的时间很容易通过滚动连接完成,但我不确定您的初始“过滤”操作是什么。
  • @eddi 我想最初的“过滤器”是最初尝试减少在@987654348 的每次迭代中“查找”(并最初合并)的数据量的挂起@。感谢有关加入的提示;我会重新考虑整个算法

标签: r data.table


【解决方案1】:

自从提出这个问题以来,我一直在研究what data.table has to offer,研究data.table 加入感谢@eddi 的指针(例如Rolling join on data.tableinner join with inequality),我想出了一个解决方案。

其中一个棘手的部分是摆脱“将函数应用于每一行”的想法,并重新设计使用联接的解决方案。

而且,毫无疑问会有更好的编程方法,但这是我的尝试。

## want to find a lkpId for each id, that has the minimum difference between 'thisTime' and 'lkpTime'
## and where the lkpId contains both 'thisLocation' and 'finalLocation'

## find all lookup id's where 'thisLocation' matches 'lookupLocation'
## and where thisTime - lkpTime > 0
setkey(dt, thisLocation)
setkey(dt_lookup, lkpLocation)

dt_this <- dt[dt_lookup, {
  idx = thisTime - i.lkpTime > 0
  .(id = id[idx],
    lkpId = i.lkpId,
    thisTime = thisTime[idx],
    lkpTime = i.lkpTime)
},
by=.EACHI]

## remove NAs
dt_this <- dt_this[complete.cases(dt_this)]

## find all matching 'finalLocation' and 'lookupLocaiton'
setkey(dt, finalLocation)
## inner join (and only return the id columns)
dt_final <- dt[dt_lookup, nomatch=0, allow.cartesian=TRUE][,.(id, lkpId)]

## join dt_this to dt_final (as lkpId must have both 'thisLocation' and 'finalLocation')
setkey(dt_this, id, lkpId)
setkey(dt_final, id, lkpId)

dt_join <- dt_this[dt_final, nomatch=0]

## take the combination with the minimum difference between 'thisTime' and 'lkpTime'
dt_join[,timeDiff := thisTime - lkpTime]

dt_join <- dt_join[ dt_join[order(timeDiff), .I[1], by=id]$V1]  

## equivalent dplyr code
# library(dplyr)
# dt_this <- dt_this %>%
#   group_by(id) %>%
#   arrange(timeDiff) %>%
#   slice(1) %>%
#   ungroup 

【讨论】:

    猜你喜欢
    • 2013-03-18
    • 2023-03-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-10-15
    • 1970-01-01
    相关资源
    最近更新 更多