【问题标题】:Why tibble performs slower than data.frame at row-wise comparison为什么 tibble 在逐行比较中的执行速度比 data.frame 慢
【发布时间】:2021-12-10 06:26:18
【问题描述】:

我正在将旧代码库转换为 tidyverse,我注意到某个特定步骤的性能下降;因为我现在使用readr (read_delim) 来读取我的数据,所以我最终得到了tibble,而不是之前的基本 R data.frame (read.delim) — 这很好。

无论如何,在逐行比较中使用tibble 时,与常规data.frame 相比,计算时间大约减少了10 倍。

这是我的代码:

library(tidyverse)

# Data
df <- tribble(
  ~x_pos, ~y_pos,
  0.0,  5.0,
  NA,   NA,
  0.1,  0.9,
  1.1,  1.5,
  1.7,  2.0,
  3.2,  1.0,
  4.0,  1.5,
  4.1,  5.0,
)

# Defining Regions of interest
roi_set_top <- list(
  roi_list = list(
    roi1 = list(
      hit_name = "left",
      x1 = 1.0,
      y1 = 1.0,
      x2 = 2.0,
      y2 = 2.0
    ),
    roi2 = list(
      hit_name = "right",
      x1 = 3.0,
      y1 = 1.0,
      x2 = 4.0,
      y2 = 2.0
    )
  )
)

# ⚡️ UNCOMMENT THIS LINE this line to convert the `tibble` to a `data.frame` and source the file again
# df <- as.data.frame(df)

start.time <- Sys.time()

for (bench in 1:1000) {
  roi_vector <- rep("NO EVAL", times = nrow(df))
  
  # loop over rows
  for (i in 1:nrow(df)) {
    
    # loop over the aoilist
    for (roi in roi_set_top$roi_list) {
      
      # check if either x or y is NA (or both) if so return NA
      if (is.na(df[i, "x_pos"]) || is.na(df[i, "y_pos"])) {
        roi_vector[i] <- "No X/Y"
        break
      }
      
      # check the hit area
      if (df[i, "x_pos"] >= roi$x1 && df[i, "y_pos"] >= roi$y1 &&
          df[i, "x_pos"] <= roi$x2 && df[i, "y_pos"] <= roi$y2) {
        roi_vector[i] <- roi$hit_name
        break
      }
      
      # Finally, if current row’s x and y is neither NA nor in hit range assign Outside ROI
      roi_vector[i] <- "Outside ROI"
    }
  }
}

end.time <- Sys.time()
time.taken <- end.time - start.time
print(time.taken)

比较

当您按原样获取代码时,与使用 ⚡️ 取消注释行并将其从 tibble 转换为 data.frame 时相比,所需时间大约长 10 倍。

如果我能像这样提取data.farme 的向量,我可以恢复我的表现:x_pos &lt;- df$x_pos; y_pos &lt;- df$x_pos 并在循环中使用向量而不是 df。但是,我有一个基本问题

问题

为什么 tibble 在逐行比较中的执行速度比基础 R data.frame 慢?

作为最佳实践风格的后续行动;当只需要使用向量时,使用 df 似乎是一种不好的做法。因此,应该不断地遍历向量而不是 df 中的列?

【问题讨论】:

    标签: r dataframe dplyr tidyverse tibble


    【解决方案1】:

    主要原因是 tibbles 在子集化时会返回 tibbles,而数据帧有时会返回向量。在您的示例中,这显示在评估 df[i, "x_pos"] 时,如果 df 是 tibble,它是一个 tibble,但如果 df 是一个数据框,它是一个数字标量。这使得像is.na(df[i, "x_pos"]) 这样的计算要慢得多。

    每次您确实需要向量或标量时,通过添加drop = TRUE 会加快速度(我看到所花费的时间减少了 25%),但更好的办法是转换为向量在循环之外,以避免 tibble 中的所有这些单独的访问。例如这段代码:

    start.time <- Sys.time()
    
    for (bench in 1:1000) {
      roi_vector <- rep("NO EVAL", times = nrow(df))
      # loop over rows
      x_pos <- df$x_pos
      y_pos <- df$y_pos
      for (i in 1:nrow(df)) {
        # loop over the aoilist
        for (roi in roi_set_top$roi_list) {
          # check if either x or y is NA (or both) if so return NA
          if (is.na(x_pos[i]) || is.na(y_pos[i])) {
            roi_vector[i] <- "No X/Y"
            break
          }
          # check the hit area
          if (x_pos[i] >= roi$x1 && y_pos[i] >= roi$y1 &&
              x_pos[i] <= roi$x2 && y_pos[i] <= roi$y2) {
            roi_vector[i] <- roi$hit_name
            break
          }
          # Finally, if current row’s x and y is neither NA nor in hit range assign Outside ROI
          roi_vector[i] <- "Outside ROI"
        }
      }
    }
    end.time <- Sys.time()
    time.taken <- end.time - start.time
    print(time.taken)
    

    比我系统上的原始代码快大约 60 倍。

    【讨论】:

    • 谢谢,小标题返回小标题非常有意义,与数据框返回的向量相比,它们在设计上很重。非常感谢!
    猜你喜欢
    • 1970-01-01
    • 2019-12-09
    • 1970-01-01
    • 1970-01-01
    • 2023-03-08
    • 2017-11-04
    • 2015-12-27
    • 1970-01-01
    • 2016-08-02
    相关资源
    最近更新 更多