2017-09-01 57 views
1

考慮下面的數據幀的每個索引:樣品n個連續的日期從隨機起始日期爲數據幀

DF = structure(list(c_number = c(1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 
1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 2L, 2L, 2L, 
2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 3L, 3L, 
3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 
3L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 5L, 5L, 5L, 5L, 
5L, 5L, 5L, 5L, 5L), date = c("2001-01-06", "2001-01-07", "2001-01-08", 
"2001-01-09", "2001-01-10", "2001-01-11", "2001-01-12", "2001-01-13", 
"2001-01-14", "2001-01-15", "2001-01-16", "2001-01-17", "2001-01-18", 
"2001-01-19", "2001-01-20", "2001-01-21", "2001-01-22", "2001-01-23", 
"2001-01-24", "2001-01-25", "2001-01-26", "2001-01-11", "2001-01-12", 
"2001-01-13", "2001-01-14", "2001-01-15", "2001-01-16", "2001-01-17", 
"2001-01-18", "2001-01-19", "2001-01-20", "2001-01-21", "2001-01-22", 
"2001-01-23", "2001-01-24", "2001-01-25", "2001-01-26", "2001-01-27", 
"2001-01-28", "2001-01-12", "2001-01-13", "2001-01-14", "2001-01-15", 
"2001-01-16", "2001-01-17", "2001-01-18", "2001-01-19", "2001-01-20", 
"2001-01-21", "2001-01-22", "2001-01-23", "2001-01-24", "2001-01-25", 
"2001-01-26", "2001-01-27", "2001-01-28", "2001-01-29", "2001-01-30", 
"2001-01-21", "2001-01-22", "2001-01-23", "2001-01-24", "2001-01-25", 
"2001-01-26", "2001-01-27", "2001-01-28", "2001-01-29", "2001-01-30", 
"2001-01-31", "2001-01-24", "2001-01-25", "2001-01-26", "2001-01-27", 
"2001-01-28", "2001-01-29", "2001-01-30", "2001-01-31", "2001-02-01" 
), value = c(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)), .Names = c("c_number", 
"date", "value"), row.names = c(NA, -78L), class = "data.frame") 

我有5客戶連續日期的銷售數據;對於客戶1,我有連續21日的銷售數據....客戶#5,我已經連續9日的銷售數據...:

> table(DF[, 1]) 

1 2 3 4 5 
21 18 19 11 9 

對每一個客戶我想品嚐子DF (如果該客戶至少有15個連續日期)或該客戶的所有日期(如果我沒有爲該客戶連續15個日期),那麼這個連續15天的日期。

關鍵部分是在情況1(如果我有至少15個連續日期的情況下)那些連續15天應該有一個隨機的開始日期(例如,並非總是客戶的第一個或最後15個日期)避免在分析中引入偏見。

在純R I會做:

library(dplyr) 

slow_function <- function(i, DF, length_out = 15){ 
    sub_DF = DF[DF$c_number == i, ] 
    if(nrow(sub_DF) <= length_out){ 
    out_DF = sub_DF 
    } else { 
    random_start = sample.int(nrow(sub_DF) - length_out, 1) 
    out_DF = sub_DF[random_start:(random_start + length_out - 1), ] 
    } 
} 
a_out = lapply(1:nrow(a_1), slow_function, DF = DF, length_out = 15) 
a_out = dplyr::bind_rows(a_out) 


table(a_out[, 1]) 
1 2 3 4 5 
15 15 15 11 9 

但我的數據大得多,上面不能忍受緩慢的操作。在data.table/dplyr中獲得相同結果的方法有多快嗎?

編輯:生成數據的代碼。

num_customer = 10 
m = 2 * num_customer 
a_0 = seq(as.Date("2001-01-01"), as.Date("2001-12-31"), by = "day") 
a_1 = matrix(sort(sample(as.character(a_0), m)), nc = 2) 
a_2 = list() 
for(i in 1:nrow(a_1)){ 
    a_3 = seq(as.Date(a_1[i, 1]), as.Date(a_1[i, 2]), by = "day") 
    a_4 = data.frame(i, as.character(a_3), round(runif(length(a_3), 1))) 
    colnames(a_4) = c("c_number", "date", "value") 
    a_2[[i]] = a_4 
} 
DF = dplyr::bind_rows(a_2) 
dim(DF) 
table(DF[, 1]) 
dput(DF) 

EDIT2:

在100K客戶DF,克里斯托夫·沃爾克的解決方案是最快的。 接下來是G.GTothendieck的(大約4倍的時間),接下來是 Nathan Werth的(另一個比G格洛騰迪克慢2倍)。 其他解決方案明顯較慢。儘管如此,所有的提案都比我的試探性的'慢'功能更快,所以感謝大家!

+1

問題有點不清楚。對於每位員工,您想要選擇一個隨機起始日期,並在該起始點之後最多連續15天抽樣?或者,如果隨機選擇會導致員工少於15個數據點,那麼最後15個數據點? – jdobres

+0

@jdobres:謝謝你的提問。實際上,第二種解釋('如果隨機選擇會導致員工少於15個數據點,只需拿過去15個')就是我想要的。 – user189035

回答

2

試試這個:

sample15consecutive <- function(DF) { 
runs <- rle(DF$c_number)$lengths 
start <- ifelse(runs > 15, sapply(pmax(runs-15, 1), sample.int, size=1), 1) 
end <- ifelse(runs >= 15, 15, runs) 
previous <- cumsum(c(0, head(runs, -1))) 
DF[unlist(mapply(seq, previous + start, previous + start + end - 1), length),] 
} 

這是根據微基準快約4倍。 c_numbers和日期必須進行排序。

+0

謝謝(+1)!但是格洛騰迪克的答案不會更快(因爲他稱之爲更少的O(n)函數,所以費用更少)? – user189035

+1

我不能說。在我的微基準測試中,我的版本看起來更快一些,但我可能犯了一個錯誤,或者行爲可能取決於數據的具體情況 - 在樣本數據集上,所有方法都非常快。你可以在你的大數據集上進行測試。 –

+0

事實上,我再次嘗試,我在一個更大的數據集上獲得了4倍的提升。再次感謝您指出perf! – user189035

1

這與tidyverse包(即,dplyrtidyr)非常簡單。

library(tidyverse) 

df.sample <- arrange(DF, date) %>% 
    group_by(c_number) %>% 
    do(head(., 15)) 

輸出(第一個30行/ 2員工):

# A tibble: 65 x 3 
    c_number  date value 
     <int>  <chr> <dbl> 
1  1 2001-01-06  1 
2  1 2001-01-07  1 
3  1 2001-01-08  1 
4  1 2001-01-09  1 
5  1 2001-01-10  1 
6  1 2001-01-11  1 
7  1 2001-01-12  1 
8  1 2001-01-13  1 
9  1 2001-01-14  1 
10  1 2001-01-15  1 
11  1 2001-01-16  1 
12  1 2001-01-17  1 
13  1 2001-01-18  1 
14  1 2001-01-19  1 
15  1 2001-01-20  1 
16  2 2001-01-11  1 
17  2 2001-01-12  1 
18  2 2001-01-13  1 
19  2 2001-01-14  1 
20  2 2001-01-15  1 
21  2 2001-01-16  1 
22  2 2001-01-17  1 
23  2 2001-01-18  1 
24  2 2001-01-19  1 
25  2 2001-01-20  1 
26  2 2001-01-21  1 
27  2 2001-01-22  1 
28  2 2001-01-23  1 
29  2 2001-01-24  1 
30  2 2001-01-25  1 
# ... with 35 more rows 

編輯:以下選擇每個僱員的隨機開始日期,然後將隨機選擇的點之後,選擇最多連續15天數:

df.sample <- arrange(DF, date) %>% 
    group_by(c_number) %>% 
    mutate(date = as.Date(date), start = sample(date, 1)) %>% 
    filter(date >= start & date <= (start + 14)) 
+0

不一樣,因爲你總是爲每個客戶拿走最後15行。但我想從隨機開始日期開始連續30行。我重寫了標題以使其更加清晰(最大限度保存) – user189035

+0

已更新,可以更好地匹配您的意圖。不能保證它是最快的,但它非常可讀。 – jdobres

+0

這(編輯後)正是我的問題(+1)的答案。但是d.b和G.格洛騰迪克的回答顯示我沒有問正確的問題。 – user189035

2

在基礎R中加速的一種方法可能是在子集化之前只使用索引而不是整個數據框架。

output = DF[unlist(lapply(
      split(1:NROW(DF), DF$c_number), #Split indices along rows of DF 
      function(x){ 
       if(length(x) < 15){   #Grab all indices if there are less than 15 
        x 
       } else{ 
        #Grab an index randomly such that there will be 14 more left after it 
        x[sample(0:(length(x) - 15), 1) + sequence(15)] 
       } 
      })), 
      ] 

sapply(split(output, output$c_number), NROW) 
# 1 2 3 4 5 
#15 15 15 11 9 
+0

這是如何使用data.table/dplyr加快速度的?或者你是否認爲使用data.table/dplyr無法加速這個問題? – user189035

+0

'lapply'或多或少是標準R循環的包裝,它很慢。 dplyr和data.table包使用了很多更快的C代碼來加速基本操作。 – jdobres

+1

@jdobres - 'lapply'使用C循環。 'lapply'源代碼的最後一行顯示它被分派到內部C代碼中。 '.Internal(lapply(X,FUN))'正確使用時速度並不慢。 –

1

samp生成0(樣品)的1的矢量(在樣品)和,我們由該子集。我沒有對它進行基準測試,但它不會將DF分解爲子數據幀,但僅將c_number矢量分開,然後在原始DF上執行一個子集。

samp <- function(x) { 
    n <- length(x) 
    replace(0*x, seq(sample(max(n - 15, 1), 1), length = min(n, 15)), 1) 
} 
s <- subset(DF, ave(c_number, c_number, FUN = samp) == 1) 
+0

這是超級聰明。請讓我花幾分鐘時間嘗試一下。 – user189035

1

試試這個:

library(data.table) 

setDT(DF) 

DF[ 
    , 
    { 
    if (.N <= 15) { 
     # 15 or fewer rows? Grab them all. 
     .SD 
    } else { 
     # Grab a random starting row not too close to the end 
     random_start <- sample(seq_len(.N - 14), size = 1) 
     .SD[random_start + 0:14] 
    } 
    }, 
    by = c_number 
] 
+0

這正是我的問題(+1)的答案。但是d.b和G.格洛騰迪克的回答顯示我沒有問正確的問題。 – user189035