在R中扩展数据集的快速有效方法

时间:2017-06-25 13:49:12

标签: r data.table dplyr

我尝试使用不同列(Key2 - KeyX)中的值扩展R中的数据集,然后使用公式中的列号来计算某个值。

我要扩展的数据集的一部分示例

Year Key2 Key3 Key4 Key5 ...
2001  150  105  140  140
2002  130   70   55   80
2003  590  375  355  385
...

首选结果。

  • i =索引号
  • col =列号(Key2 = 1,Key3 = 2等)
  • p =随机数
  • value =使用列号和p

    计算的值
    year   i col         p     value
    2001   1   1 0.7481282 4.0150810
    2001   2   1 0.8449366 2.0735090
    2001 ...   1 0.1906882 0.9534411
    2001 150   1 0.8030162 3.7406410
    2001   1   2 0.4147019 4.2246831
    2001   2   2 0.3716995 1.8584977
    2001 ...   2 0.5280272 2.6401361
    2001 105   2 0.8030162 3.7406410
    2001   1   3 0.7651376 3.8256881
    2001   2   3 0.2298984 1.1494923
    2001 ...   3 0.5607825 2.8039128
    2001 140   3 0.7222644 3.6113222
    etc.
    
    2002   1   1 0.1796613 0.8983065
    2002   2   1 0.6390833 3.1954165
    2002 ...   1 0.5280272 2.6401367
    2002 130   1 0.4238842 2.1194210
    2002   1   2 0.7651376 3.8256889
    2002   2   2 0.2298984 1.1494928
    2002 ...   2 0.5607825 2.8039125
    2002  70   2 0.7222644 3.6113227
    2002   1   3 0.7512801 3.7564000
    2002   2   3 0.4484248 2.2421240
    2002 ...   3 0.5662704 2.8313520
    2002  55   3 0.7685377 3.8426884
    etc.
    

我在R中使用以下代码,但是对于大型数据集来说速度非常慢。 我尝试使用rep()将循环的使用保持在最低限度,但我仍然需要在代码中使用for循环。

有更快/更有效的方法吗?使用data.table?

val <- c(); i <- c(); cols <- c(); p <- c(); year <- c()
for (year in 1:10) {
  for (n in 2:25) {
      c <- n-1
      pu <- runif(dataset1[[year, n]])
      p <- c(p, pu )
      tmp <- (c-1)*5 + 5*pu
      val <- c(val, tmp)
      ##
      i <- c(i, 1:dataset1[[year, n]])
      cols <- c(cols, rep(c, dataset1[[year, n]]) )
      year <- c(year, rep(dataset1[[year,1]], dataset1[[year, n]]) )
  }
}
res.df <- data.frame(year=year, i=i, cols=cols, p=p, val=val)
res.df <- setDT(res.df)

2 个答案:

答案 0 :(得分:3)

问题的核心是将Key列中的值扩展为i

以下是使用data.table的另一个melt()解决方案,但实施细节与David's comment不同:

library(data.table)
DT <- data.table(dataset1)
expanded <- melt(DT, id.vars = "Year", variable = "col")[, col := rleid(col)][
  , .(i = seq_len(value)), by = .(Year, col)]
expanded
      Year col   i
   1: 2001   1   1
   2: 2001   1   2
   3: 2001   1   3
   4: 2001   1   4
   5: 2001   1   5
  ---             
2571: 2003   4 381
2572: 2003   4 382
2573: 2003   4 383
2574: 2003   4 384
2575: 2003   4 385

剩余的计算可以像这样完成(如果我已经理解了OP的意图)

set.seed(123L) # make results reproducable
res.df <- expanded[, p := runif(.N)][, value := 5 * (col - 1L + p)][]
res.df
      Year col   i         p     value
   1: 2001   1   1 0.2875775  1.437888
   2: 2001   1   2 0.7883051  3.941526
   3: 2001   1   3 0.4089769  2.044885
   4: 2001   1   4 0.8830174  4.415087
   5: 2001   1   5 0.9404673  4.702336
  ---                                 
2571: 2003   4 381 0.4711072 17.355536
2572: 2003   4 382 0.5323359 17.661680
2573: 2003   4 383 0.3953954 16.976977
2574: 2003   4 384 0.4544372 17.272186
2575: 2003   4 385 0.1149009 15.574505

对不同方法进行基准测试

由于OP要求更快/更有效的方式,目前提出的三种不同方法正在进行基准测试:

基准代码

对于基准测试,使用microbenchmark包。

library(magrittr)
bm <- microbenchmark::microbenchmark(
  david1 = {
    expanded_david1 <-
      setorder(
        melt(DT, id = "Year", value = "i", variable = "col")[rep(1:.N, i)], Year, col
      )[, i := seq_len(.N), by = .(Year, col)]
  },
  david2 = {
    expanded_david2 <-
      setorder(
        melt(DT, id = "Year", value = "i", variable = "col")[, col := as.integer(col)][
          rep(1:.N, i)], Year, col)[, i := seq_len(.N), by = .(Year, col)]
  },
  uwe = {
    expanded_uwe <- 
      melt(DT, id.vars = "Year", variable = "col")[, col := rleid(col)][
        , .(i = seq_len(value)), by = .(Year, col)]
  },
  ycw = {
    expanded_ycw <- DT %>%
      tidyr::gather(col, i, - Year) %>%
      dplyr::mutate(col = as.integer(sub("Key", "", col)) - 1L) %>%
      dplyr::rowwise() %>%
      dplyr::do(tibble::data_frame(Year = .$Year, col = .$col, i = seq(1L, .$i, 1L))) %>%
      dplyr::select(Year, i, col) %>%
      dplyr::arrange(Year, col, i)
  },
  times = 100L
)
bm

请注意,对tidyverse函数的引用是明确的,以避免由于名称空间混乱而导致名称冲突。修改后的david2变体将因子转换为级别数。

定时小样本数据集

使用OP提供的3年和4 Key列的小样本数据集,时间如下:

Unit: microseconds
   expr       min         lq        mean    median         uq        max neval
 david1   993.418  1161.4415   1260.4053  1244.320   1350.987   2000.805   100
 david2  1261.500  1393.2760   1624.5298  1568.097   1703.837   5233.280   100
    uwe   825.772   865.4175    979.2129   911.860   1084.226   1409.890   100
    ycw 93063.262 97798.7005 100423.5148 99226.525 100599.600 205695.817   100

即使对于这个小问题大小,data.table解决方案的速度比tidyverse方法快,但对解决方案uwe略有优势。

检查结果是否相等:

all.equal(expanded_david1[, col := as.integer(col)][order(col, Year)], expanded_uwe)
#[1] TRUE
all.equal(expanded_david2[order(col, Year)], expanded_uwe)
#[1] TRUE
all.equal(expanded_ycw, expanded_uwe)
#[1] TRUE

除了返回因子而不是整数和不同排序的david1之外,所有四个结果都是相同的。

更大的基准案例

表格OP的代码可以得出结论,他的生产数据集包含10年和24 Key列。在示例数据集中,Key值的总体平均值为215.使用这些参数,正在创建更大的数据集:

n_yr <- 10L
n_col <- 24L
avg_key <- 215L
col_names <- sprintf("Key%02i", 1L + seq_len(n_col))
DT <- data.table(Year = seq(2001L, by = 1L, length.out = n_yr))
DT[, (col_names) := avg_key]

较大的数据集返回51600行,其仍然是相当适中的大小但是比小样本大20倍。时间安排如下:

Unit: milliseconds
   expr         min          lq        mean      median          uq         max neval
 david1    2.512805    2.648735    2.726743    2.697065    2.698576    3.076535     5
 david2    2.791838    2.816758    2.998828    3.068605    3.075780    3.241160     5
    uwe    1.329088    1.453312    1.585390    1.514857    1.634551    1.995142     5
    ycw 1641.527166 1643.979936 1646.004905 1645.091158 1646.599219 1652.827047     5

对于此问题大小,uwe几乎是其他data.table实现的两倍。 tidyverse方法仍然较慢。

答案 1 :(得分:1)

这是一个想法。 df2包含展开的Yearcoli。您可以为p进一步创建valuedf2

# Load package
library(tidyverse)

# Create example data frame
dt <- read.table(text = "Year Key2 Key3 Key4 Key5
2001  150  105  140  140
                 2002  130   70   55   80
                 2003  590  375  355  385",
                 header = TRUE, stringsAsFactors = FALSE) 


# Expand the data frame
dt2 <- dt %>%
  gather(col, i, - Year) %>%
  mutate(col = as.numeric(sub("Key", "", col)) - 1) %>%
  rowwise() %>%
  do(data_frame(Year = .$Year, col = .$col, i = seq(1, .$i, 1))) %>%
  select(Year, i, col) %>%
  arrange(Year, col, i)

更新

tidyverse的另一种方法。

# Expand the data frame
dt2 <- dt %>%
  gather(col, i, - Year) %>%
  mutate(col = as.numeric(sub("Key", "", col)) - 1) %>%
  mutate(i = map2(1L, i, seq, by = 1)) %>%
  unnest() %>%
  select(Year, i, col) %>%
  arrange(Year, col, i)