簡體   English   中英

計算r中data.table的每一行

[英]Calculations on each row of a data.table in r

我正在使用 R 為學校做環境建模,到目前為止,感謝 DataCamp 和 SO 上所有非常有用的線程,但我處於技能門檻和系統資源僵局。 建模森林站立,我已經編寫了代碼到 model 增長,現在需要計算這些時間步之間的其他資源移動。 為了提高資源效率,我必須將增長與資源移動分開。

我有成千上萬的林分在數百年的時間里生長,這些林分會因為受到干擾而分裂,所以這是一個相當大的數據集,但它被簡化為:

stands <- data.table(A=c(rep(1,3),rep(2,3),rep(3,3)),B=rep(1:3,3),C=round(runif(9),2),D=c(1.5,NA,NA,2.5,NA,NA,1.2,NA,NA),E=c(9.2,NA,NA,8.7,NA,NA,7.8,NA,NA))

   A B    C   D   E
1: 1 1 0.57 1.5 9.2
2: 1 2 0.82  NA  NA
3: 1 3 0.07  NA  NA
4: 2 1 0.13 2.5 8.7
5: 2 2 0.29  NA  NA
6: 2 3 0.04  NA  NA
7: 3 1 0.93 1.2 7.8
8: 3 2 0.01  NA  NA
9: 3 3 0.49  NA  NA

其中 A 是展位 ID (str),B 是時間步長 (num),C 是根據先前計算查找的值,D 和 E 是每個展位的第一個時間步長除外的計算變量,如上圖所示。 我的目標是填寫所有的 NA

D 和 E 的公式不同,並參考同一行中的其他列,以及同一站的前一個時間步長。 例如,計算stands[2,"D"]將需要引用stands[2,"C"]stands[1,"D"]

我繼承了一些使用 for 循環的代碼,主要是基於 r 代碼來計算基於同一行和前一行的引用。 例如:

for(i in 1:nrow(stands)) {
stands$D[i] <- 0.1234*stands$D[i-1]
}

這是完全可用的,但效率非常低,單次 model 的 1.3x10 6台運行估計需要一個月的時間。 一旦我進入完整的 model 運行,我的最終數據集將更接近 2.5-3.0x10 6 顯然,需要做一些工作。

我試驗了 data.table 並重寫了代碼以在 for 循環中使用引用替換。 我按如下方式處理計算所需的滯后值:

for(i in 1:nrow(stands)) {
stands[(i-1):i,lagD := shift(D)][i,D := 0.1234*lagD][,lagD := NULL]
}

(通過額外的步驟重新初始化循環以正確啟動每個支架)

這奏效了,它使 model 的運行時間減少到估計的 10 小時。 或每行值大約 0.02 秒(根據我運行的 Sys.time 測試),但我想看看我是否可以進一步推動它。 因為當我 go 運行完整的 model 時,如果我的處理時間線性擴展(應該如此),那將接近 20-25 小時,這可能是可以接受的。 但我希望能夠在一個工作日內看到運行。

我相信上面 data.table 代碼中最耗時的是將滯后的[i-1]值添加到當前正在計算的行[i] ,然后刪除。

我已經在 SO 論壇和其他來源上尋找有關這可能如何工作的提示,嘗試熔化,為此目的嘗試圍繞 apply 系列,等等,但我找不到進一步提高效率的下一步這里。

幫助!

編輯只是添加計算列的總數為 16,許多計算取決於其他列中的值(在同一行和滯后行中),這意味着我不能一次計算每一列。

Edit2:很抱歉缺乏具體性,我之前因為沒有使示例足夠通用而受到批評,所以我想我在這個問題上走得太遠了。 有道理的是,幫助解決更多細節會更容易!

計算值的原始代碼塊摘錄如下,其中stands[A:D]是開始計算前附在表上的值, stands[a:e]是要計算的值, lookup[a:d]是一個單獨的 dt,帶有提供的常量。

for(i in 1:nrow(stands)) {
stands$a[i] <- (stands$A[i]*123 + stands$b[i-1]) * (1-lookup$a)
stands$a2[i] <- stands$a[i] * 123
stands$b[i] <- stands$a[i] + stands$a2[i]
stands$b[i] <- (stands$B[i]*123 + stands$b[i-1]) * (1-lookup$b)
stands$b2[i] <- stands$b[i] * 123
stands$c[i] <- stands$c[i] + stands$c2[i]
stands$d[i] <- (stands$C[i]*123 + stands$D[i]*123 + stands$c[i-1]) * (1-lookup$d)
stands$e[i] <- (stands$D[i]*123 + stands$e[i-1]) * (1-lookup$e)
}

我認為您在這里提高效率的唯一希望是嘗試完全擺脫 for 循環並利用 R 的矢量化。 眾所周知,循環之所以慢,是因為它們通過停止計算和傳遞新值來破壞超快速計算的循環。 如果可能的話,如果您可以傳遞列(或滯后列)來一次計算所有行,它會工作得更快。

您在數據集中遇到的問題當然是有幾列取決於首先計算的前一行。 沒有明確的矢量化方法,因此每一行都在等待最后一行的計算完成,然后才知道它的起始值是導致它變慢的原因。

那么你最好的方法可能會采取幾個步驟:

  • 使用方程式來計算后面的值可能是什么,而不取決於正在計算的先前值
  • 盡可能一步計算每列的所有行
  • 分幾步添加列

要使用上面 D 列的簡單示例,但要在 100,000 行的data.table上嘗試:

library(data.table)

# Constructing a large table
stands <- data.table(
  A = rep(1:1000, each = 100),
  B = rep(1:100, times = 1000),
  C = round(runif(100000), 2)
)

# Adding a D column with only values for the B = 1
D_vals <- data.table(A = 1:1000, B = 1, D = runif(1000) + 1)
stands <- merge(stands, D_vals, all.x = TRUE)

# A *very* slow for loop
for(i in 2:nrow(stands)) {
  stands$D[i] <- 0.1234*stands$D[i-1]
}

這個循環需要很長時間才能運行。

但在這個例子中,每個 D 更新,從每個 ID 的第一行開始,具有唯一的非缺失 D 測量,有效地將開始 D 乘以 0.1234 B-1 倍。 因此,一種超快速的data.table方法是:

stands[,baseD := max(D, na.rm = TRUE), by = "A"
       ][,D := baseD * 0.1234 ^ (B - 1)
         ][,baseD := NULL]

... 只需幾分之一秒即可運行。 這可能會處理您的某些專欄。

上面代碼中的其他列可以在沒有 for 循環的情況下以類似的速度輕松添加。 從上面進行一些更簡單的計算:

stands[,`:=`(
  a2 = a * 123,
  b = a + a2,
  c = c + c2,
  # etc.
  )]

通過組合這些步驟,您可能會大大加快您的代碼速度,但它可能非常復雜,並且最終與您自己的數據/項目有關可以做什么。 抱歉,這還不能完全解決您的所有問題,但希望這是指向您想要 go 的方向的指針。

編輯 - 或使用Rcpp

綜上所述,我認為您可以通過在Rcpp function中重寫代碼來達到相當高的速度。 data.table傳入和傳出 function 調用的方式可能很復雜,但實際上在 Rcpp 中循環它而不是 R 可以實現您想要的所有部分,而不會像 R 循環那樣緩慢。

作為嘗試做一些我認為你的循環在 C++ 中正在做的事情,這里是你的循環的重寫(在函數中使用制作的查找表),它立即運行超過 100,000 行:

#include <Rcpp.h>
using namespace Rcpp;


// [[Rcpp::export]]
DataFrame updateDf(const DataFrame& df) {

  NumericVector A = df["A"];
  NumericVector B = df["B"];
  NumericVector C = df["C"];
  NumericVector D = df["D"];
  NumericVector E = df["E"];
  NumericVector a = df["a"];
  NumericVector a2 = df["a"];
  NumericVector b = df["b"];
  NumericVector b2 = df["b2"];
  NumericVector c = df["c"];
  NumericVector c2 = df["c"];
  NumericVector d = df["d"];
  NumericVector e = df["e"];
  
  int n = A.size();
  
    double lookup_a = 0.1;
    double lookup_b = 0.2;
    double lookup_d = 0.4;
    double lookup_e = 0.5;
    
  for(int i = 1; i < n; i++) {
    a[i] = (A[i]*123 + b[i-1]) * (1-lookup_a);
    a2[i] = a[i] * 123;
    b[i] = a[i] + a2[i];
    b[i] = (B[i]*123 + b[i-1]) * (1-lookup_b);
    b2[i] = b[i] * 123;
    c[i] = c[i] + c2[i];
    d[i] = (C[i]*123 + D[i]*123 + c[i-1]) * (1-lookup_d);
    e[i] = (D[i]*123 + e[i-1]) * (1-lookup_e);
  };
  
  DataFrame out = DataFrame::create(
  _["A"] = A,
  _["B"] = B,
  _["C"] = C,
  _["D"] = D,
  _["E"] = E,
  _["a"] = a,
  _["a2"] = a,
  _["b"] = b,
  _["b2"] = b2,
  _["c"] = c,
  _["c2"] = c,
  _["d"] = d,
  _["e"] = e
  );
  
  return out;
}

將其另存為單獨的文件(例如“updateDf.cpp”)並使用Rcpp::sourceCpp("updateDf.cpp")加載 function。然后應該可以在您的stands台上調用它 dataframe 以運行循環。 您需要試用它以使其符合您的期望。 我發現這個頁面很有幫助!

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM