2015-11-30 26 views
2

柱系列作爲後續行動,這個老但糖果事件:efficient row-wise operations on a data.table按行首/末從data.table

我有一些數據,(不幸)的樣子:

library('data.table') 
set.seed(1234) 
m <- 5 
n <- 7 
rb <- function() runif(m,1000,2000) * rbinom(m,1,0.5) 
series_col_nms <- paste0('YearNo',1:n)  
rev <- data.table(cust_id = paste0('CustNo',1:m), 
       other_stuff = sample(letters,m, replace=TRUE)) 
for(col in series_col_nms){ 
    set(rev, j=col, value=rb()) 
} 
setkey(rev, cust_id) 

每個客戶有一行,每個客戶有不同的欄目,包括第1,2年的年度收入...

我想獲得每個客戶的收入和年份的年份指數。

我能產生期望的結果,而是帶着幾分哈克加入:

years_active <- rev[, which(.SD>0), .SDcols = series_col_nms, 
        keyby=cust_id][, .(min_year_active = min(V1), 
             max_year_active = max(V1)), keyby=cust_id] 
years_active[rev] 

這些嘗試獲得最小索引失敗:

rev[, apply(.SD, 1, function(x) min(which(x>0))), .SDcols=series_col_nms, by=cust_id] # returns data type error  
rev[, do.call(pmin, lapply(.SD, function(x) which(x>0))), .SDcols=series_col_nms, by=cust_id] # returns empty 

什麼data.table辦法做到這一點?

回答

3

當您想要在許多列上逐行操作時,通常會先將您的數據設置爲melt,然後再對單個列進行操作。

在你的情況下,一個相對簡單的解決辦法是像

res <- melt(rev, id = 1:2)[, 
     as.list({ 
      temp <- value != 0 
      if (any(temp)) range(which(temp)) else rep(NA_integer_, 2) 
     }), 
     by = cust_id] 

rev[, c("Min", "Max") := res[, .(V1, V2)]] 
rev 
# cust_id other_stuff YearNo1 YearNo2 YearNo3 YearNo4 YearNo5 YearNo6 YearNo7 Min Max 
# 1: CustNo1   c 1640.311  0 0.000 1759.671 0.000 1503.933 0.000 1 6 
# 2: CustNo2   q 1009.496  0 0.000 1201.248 0.000 0.000 1308.095 1 7 
# 3: CustNo3   p 0.000  0 0.000 0.000 1484.991 0.000 0.000 5 5 
# 4: CustNo4   q 1666.084  0 1831.345 1992.150 1243.929 0.000 1051.647 1 7 
# 5: CustNo5   w 0.000  0 0.000 0.000 0.000 0.000 0.000 NA NA 

一個清潔的版本,但帶有警告可能是

melt(rev, id = 1:2)[, as.list(as.integer(range(which(value != 0)))), by = cust_id] 
+0

我不會打電話*帶有警告*版本的清潔器,但是很髒的;) – jangorecki

2

重塑我將數據存儲在長格式:

mrev = melt(rev, 
    id=c("cust_id","other_stuff"), 
    variable.name="YearNo", 
    value.name="revenue")[revenue > 0] 

你失去了客戶5的revenue > 0條件,但我懷疑這一點。

然後收集彙總統計需要:

mrev[ , list(first = YearNo[1], last = YearNo[.N]), by=cust_id] 

# cust_id first last 
# 1: CustNo1 YearNo1 YearNo6 
# 2: CustNo2 YearNo1 YearNo7 
# 3: CustNo4 YearNo1 YearNo7 
# 4: CustNo3 YearNo5 YearNo5 

從您一直在使用字符串解析出的數字是簡單的,當然。


max.col我認爲這是一個不好的雜牌組裝電腦,但......

max.col(rev[,!c("cust_id","other_stuff"),with=FALSE] > 0, "first") 
max.col(rev[,!c("cust_id","other_stuff"),with=FALSE] > 0, "last") 

你得回去補全零的特殊情況(客戶5)分別。

+0

非常感謝Frank和@DavidArenburg - 這很好地證實了我的原創方法,雖然也許明確的融化速度更快 - 您是否看到任何這種方式使用行操作('pmin','.SD'等)? – C8H10N4O2

+0

@ C8H10N4O2我認爲,如果速度是一個問題,你應該堅持熔化/長數據,data.table更好。 (這也可以幫助您避免笨拙地將數據放入列名。)'pmin'恰好是可用於鏈接問題的黑客,但我不打賭通常可用的快捷方式。 – Frank

+0

非常感謝(和+1) - 如果我能弄清楚一個行方式的解決方案,我會對它進行基準測試,但很高興知道我在正確的軌道上。我將接受@DavidArenburg的解決方案,用'range()'來解決他的想法(否則它是一條平行線) – C8H10N4O2