行式高效循环和purrr实现

时间:2019-06-02 20:58:34

标签: r

我正在根据与观察值i的关系,为每行按不同的子集过滤数据帧。具体来说,以观察为日期的观察是在观察数据i之前的特定天数。 用for循环解决了它,但是没有找到使用purrr的实现。似乎逐行解决方案的帖子可以处理更简单的问题。

1。purrr有解决方案吗?
2.代码可以更有效吗?

我使用for循环和使用foreach软件包的并行计算解决了这个问题。
数据如下:
每行都是具有丰富信息的贷款(24万个观察值)。一些借款人得到经纪人的帮助。 我在每个统计地理区域中计算每次观察前100天内的经纪贷款比例。 (该比例将成为选择经纪人的选择偏见问题的工具)

# define a toy data ----
n <- 10000
df <- data.frame(id = 1:n,
                 broker = rbinom(n,1,0.4) ,
                 date = Sys.Date() + sample(n/100, n, replace = T) ,
                 area = sample(n/200, n, replace = T))
# going parallel ====
# load packages ----
library(tidyverse)
library(parallel)
library(doSNOW) # working on windows platform
library(foreach)
library(progress)
# define cluster ----
n_cores <- detectCores()
cl <- makeCluster(n_cores - 1)
registerDoSNOW(cl)
# define progress bar ----
pb <- txtProgressBar(min=1, max=n/100, style=3)
progress <- function(n) setTxtProgressBar(pb, n)
opts <- list(progress=progress)
# calculate IV variable - count how many times brokered loan appear in borower's statistical zone in the previous 100 days ----
t_par <- Sys.time() # record start time
# to make things more efficient, split the data to small chunks by statistic area 
a <- df %>% split(df$area) 
# nested forloop - the outer loop is parallel, the inner is serial. 
d <- foreach(j = seq_along(a),.packages = "tidyverse",.options.snow=opts) %dopar% {
   setTxtProgressBar(pb, j)
# empty temporary data frame   
   y <- data.frame(n_area_date = numeric(length(nrow(a[[j]]))), 
                   sum_broker = numeric(length(nrow(a[[j]]))),
                   p_broker = numeric(length(nrow(a[[j]]))))
# the inner loop   
   for(i in 1:nrow(a[[j]])){
      y[i,] <-  a[[j]] %>% filter( date < a[[j]][i, "date"],
                                   date >=  a[[j]][i, "date"] -100 ) %>%
         summarise( n_area_date = n(),
                    sum_broker = sum(broker),
                    p_broker = sum_broker / n_area_date)
   }
   cbind(a[[j]], y)
}
# turn result back into a data.frame
e <- map_df(d, rbind) 
(t_par <- Sys.time() - t_par)
# closing ----
stopCluster(cl)

在强大的计算机上获得的结果在时间上令人满意。
但是,代码不像我想要的那样可读。很多次,purrr确实使我能够编写更优雅,更高效的代码。 for循环可以保留这种情况吗?

1 个答案:

答案 0 :(得分:0)

这似乎是具有过滤条件的交叉联接。如果您喜欢data.table,请研究非等额联接。

这不是100%相同,不符合条件的ID不会结转。使用anti_join和bind_rows可以很容易地弥补这一点。

您的方法在计算机上花费了大约20秒钟。此方法大约需要1秒钟。

df%>%
  inner_join(df, by = 'area')%>%
  filter(date.y < date.x
         , date.y >= date.x - 100)%>%
  group_by(id.x, broker.x, date.x, area)%>%
  summarize(n_area_date = n()
            , sum_broker = sum(broker.y)
            , p_broker = sum_broker / n_area_date)

编辑:这是data.table解决方案。它完成了140毫秒-比原始速度快150倍,比dplyr non-equi join快6倍。

dt[, .(area, date, broker)
   ][dt[, .(area, date, l_date = date - 100, id, broker)]
     ,on = .(area = area
             , date < date
             , date >= l_date)
     , .(id, i.broker, i.date, i.area, x.broker)
     , allow.cartesian = T
     ][, .(n_area_date = .N
           , sum_broker = sum(x.broker)
           , p_broker = sum(x.broker) / .N)
       , by = .(id, i.broker, i.date, i.area)]

性能:

Unit: milliseconds
                expr        min         lq       mean     median         uq       max neval
 dplyr_non_equi_join   781.3828   802.5404   837.2033   810.3655   847.3634  1032.001    10
         dt_non_equi   121.0912   125.1777   137.7371   138.7682   141.9835   175.763    10
            original 19986.1950 20047.2880 20356.4174 20160.2137 20900.4362 21097.170    10