2017-10-04 60 views
4

我在R中有一個大數據框,並且我想根據現有列創建一些新列。但是,對於每一行,新值還取決於其他一些行。來自依賴子集的大數據框中每行的多個新聚合

下面是一些虛擬的數據

colnames <- c('date', 'docnr', 'clientid', 'values') 
docnr <- c(1,2,3,4,5,6) 
dates <- c('2017-01-01', '2017-02-01', '2017-03-01', '2017-04-01','2017-01-05', '2017-02-05') 
clients <- c(1,1,1,1,2,2) 
values <- c(10,14,4,7,9,19) 
df <- data.frame(cbind(dates, docnr, clients, values)) 
names(df) <- colnames 
df$date <- as.Date(df$date, format = "%Y-%m-%d") 

df 
     date docnr clientid values 
1 2017-01-01  1  1  10 
2 2017-02-01  2  1  14 
3 2017-03-01  3  1  4 
4 2017-04-01  4  1  7 
5 2017-01-05  5  2  9 
6 2017-02-05  6  2  19 

我想要做的,是每一行(由docnr唯一標識)採取的日期和客戶ID,並找到所有有其他行的同一個clientid,和更早的日期。

然後,我想從這個子集計算一些東西。例如,我想要這個子集中的總行數以及這個子集的所有值的總和。

所以在這個例子中的數據,我希望:

 date docnr clientid values counts totals 
1 2017-01-01  1  1  10  0  0 
2 2017-02-01  2  1  14  1  10 
3 2017-03-01  3  1  4  2  24 
4 2017-04-01  4  1  7  3  28 
5 2017-01-05  5  2  9  0  0 
6 2017-02-05  6  2  19  1  9 

目前,我使用了一個for循環:

counts <- numeric(0) 
totals <- numeric(0) 
for (i in 1:nrow(df)) { 
    tmp <- df[df$date< df$date[i] & df$clientid== df$clientid[i], 
       c("date", "docnr","value")] 
    cnt <- nrow(tmp) 
    tot <- sum(tmp$value) 
    counts[i] <- res 
    totals[i] <- tot 
} 
df$counts <- counts 
df$totals <- totals 

這個循環顯然是非常慢爲700K行的數據幀(尚未完成)。 doSNOW的並行實現看起來好像不太好。

我試過使用帶有sqldf的sql查詢,但是子查詢一次只能返回1個值,這意味着要遍歷每一個我想定義的新列的查詢(並且我想添加更多後來派生列)。

我遇到了SQL對象(Is it possible to get multiple values from a subquery?)的解決方案,但對象在R的sqldf中不起作用。使用連接不起作用,因爲第二個查詢需要來自第一個查詢的信息。

我剛剛從R開始(我對sql也不太熟悉),所以如果有人知道更有效的方法來做這件事,我會非常感激。

+0

不要使用''數據的內部cbind' .frame「,因爲它將所有元素都強制轉換爲字符。而是直接使用'data.frame':'df < - data.frame(日期,docnr,客戶端,值)' – lmo

回答

4

以下是兩行基本R代碼,使用ave進行分組。

# get counts 
df$counts <- ave(df$docnr, df$clientid, FUN=seq_along) - 1L 
# get lagged cumulative sum 
df$totals <- ave(df$values, df$clientid, FUN=function(x) c(0, head(cumsum(x), -1))) 

這將返回

df 
     date docnr clientid values counts totals 
1 2017-01-01  1  1  10  0  0 
2 2017-02-01  2  1  14  1  10 
3 2017-03-01  3  1  4  2  24 
4 2017-04-01  4  1  7  3  28 
5 2017-01-05  5  2  9  0  0 
6 2017-02-05  6  2  19  1  9 

我懷疑上面的代碼西港島線執行速度不夠快爲您所描述的數據。然而,data.table是一個推薦的包,用於處理可能有數十億行的數據庫。在data.table上述代碼的模擬將

library(data.table) 
setDT(df)[, c("counts", "totals") := .(seq_len(.N) - 1L, shift(cumsum(values), fill=0)), 
      by=clientid] 

其中seq_len(.N)填充的seq_alongshift作用填充的c(0, head(cumsum(x), -1))在前面的代碼中的作用。

這將返回一個data.table,其值與上面相同。

df 
     date docnr clientid values counts totals 
1: 2017-01-01  1  1  10  0  0 
2: 2017-02-01  2  1  14  1  10 
3: 2017-03-01  3  1  4  2  24 
4: 2017-04-01  4  1  7  3  28 
5: 2017-01-05  5  2  9  0  0 
6: 2017-02-05  6  2  19  1  9 

數據

df <- 
structure(list(date = structure(c(17167, 17198, 17226, 17257, 
17171, 17202), class = "Date"), docnr = c(1, 2, 3, 4, 5, 6), 
    clientid = c(1, 1, 1, 1, 2, 2), values = c(10, 14, 4, 7, 
    9, 19)), .Names = c("date", "docnr", "clientid", "values" 
), row.names = c(NA, -6L), class = "data.frame") 
+0

謝謝!對於我來說最快的解決方案是使用'data.table',只要新派生列的數量是2或更多。 3.5秒內有700k行對於一個新的專欄,「ave」解決方案更快。 – Desolane

1

這與現在dplyr

This works 

df$values <- as.numeric(as.character(df$values)) 
df1 <- df %>% 
    arrange(clientid, date) %>% 
    group_by(clientid) %>% 
    mutate(counts = row_number()-1, 
     total = lag(cumsum(values),k=1, default=0)) %>% 
    ungroup() 

DF1工作容易包含的數據你想

+0

的確如此,我喜歡可讀的代碼。這很快,但不及@lmo的其他答案。在700k行上,這個解決方案需要23秒鐘,而使用'setDT'的時間爲3.5秒。 – Desolane

相關問題