2015-12-30 19 views
11

Tôi có một khung dữ liệu, khung dữ liệu đã được sắp xếp khi cần thiết, nhưng bây giờ tôi sẽ muốn "cắt nó" theo nhóm.Có điều kiện cumsum với thiết lập lại

nhóm này nên có một giá trị tích lũy tối đa của 10. Khi giá trị tích lũy là> 10, cần thiết lập lại số tiền tích lũy và bắt đầu lại một lần nữa

library(dplyr) 
id <- sample(1:15) 
order <- 1:15 
value <- c(4, 5, 7, 3, 8, 1, 2, 5, 3, 6, 2, 6, 3, 1, 4) 
df <- data.frame(id, order, value) 
df 

Đây là sản phẩm tôi đang tìm kiếm (tôi đã làm nó "bằng tay")

cumsum_10 <- c(4, 9, 7, 10, 8, 9, 2, 7, 10, 6, 8, 6, 9, 10, 4) 
group_10 <- c(1, 1, 2, 2, 3, 3, 4, 4, 4, 5, 5, 6, 6, 6, 7) 
df1 <- data.frame(df, cumsum_10, group_10) 
df1 

Vì vậy, tôi đang gặp 2 vấn đề

  1. Làm thế nào để tạo một biến tích lũy mà reset mọi lúc nó đi một giới hạn trên (10 trong trường hợp này)
  2. Làm thế nào để đếm/nhóm mỗi nhóm

Đối với phần đầu tiên tôi đã cố gắng một số kết hợp của group_by và cumsum không có may mắn

df1 <- df %>% group_by(cumsum(c(False, value < 10))) 

tôi muốn một ống (%>%) giải pháp thay vì một vòng lặp for

Cảm ơn

+1

Phần thứ hai sẽ là tầm thường ('group_by') nếu bạn có thể tìm ra điểm đầu tiên. Tôi nghĩ sẽ rất khó để làm việc đầu tiên mà không có vòng lặp 'for', trừ khi ai đó cực kỳ thông minh. Bạn có muốn ống cho hiệu quả, sang trọng, ...? Nếu có một vòng lặp for ẩn trong một hàm trợ giúp thì có được không? –

+0

kiểm tra xem http://stackoverflow.com/questions/29054459/how-to-speed-up-or-vectorize-a-for-loop/29055443#29055443 – Khashaa

+2

'group_by (bin (giá trị, 10))%>% mutate (cumsum (value)) 'sử dụng hàm' bin' trong liên kết – Khashaa

Trả lời

7

tôi nghĩ rằng đây không phải là dễ dàng voctorizabl e .... ít nhất tôi không biết làm thế nào.

Bạn có thể làm điều đó by hand qua:

my_cumsum <- function(x){ 
    grp = integer(length(x)) 
    grp[1] = 1 
    for(i in 2:length(x)){ 
    if(x[i-1] + x[i] <= 10){ 
     grp[i] = grp[i-1] 
     x[i] = x[i-1] + x[i] 
    } else { 
     grp[i] = grp[i-1] + 1 
    } 
    } 
    data.frame(grp, x) 
} 

Đối với dữ liệu của bạn này cho phép:

> my_cumsum(df$value) 
    grp x 
1 1 4 
2 1 9 
3 2 7 
4 2 10 
5 3 8 
6 3 9 
7 4 2 
8 4 7 
9 4 10 
10 5 6 
11 5 8 
12 6 6 
13 6 9 
14 6 10 
15 7 4 

Cũng cho tôi "phản ví dụ" điều này mang lại:

> my_cumsum(c(10,6,4)) 
    grp x 
1 1 10 
2 2 6 
3 2 10 

Như @ Khashaa chỉ ra điều này có thể được implementet hiệu quả hơn thông qua Rcpp. Ông liên kết với câu trả lời này How to speed up or vectorize a for loop? mà tôi thấy rất hữu ích

+0

Cảm ơn! Điều này hoạt động hoàn hảo! –

3

Hàm bên dưới sử dụng đệ quy để xây dựng vectơ có độ dài của mỗi nhóm. Nó nhanh hơn vòng lặp cho các vectơ dữ liệu nhỏ (chiều dài nhỏ hơn khoảng một trăm giá trị), nhưng chậm hơn cho các giá trị dài hơn. Phải mất ba đối số:

1) vec: Một vectơ giá trị mà chúng tôi muốn nhóm.

2) i: Chỉ mục của vị trí bắt đầu trong vec.

3) glv: Một vectơ có độ dài nhóm. Đây là giá trị trả về, nhưng chúng ta cần khởi tạo nó và chuyển nó qua mỗi lần đệ quy.

# Group a vector based on consecutive values with a cumulative sum <= 10 
gf = function(vec, i, glv) { 

    ## Break out of the recursion when we get to the last group 
    if (sum(vec[i:length(vec)]) <= 10) { 
    glv = c(glv, length(i:length(vec))) 
    return(glv) 
    } 

    ## Keep recursion going if there are at least two groups left 
    # Calculate length of current group 
    gl = sum(cumsum(vec[i:length(vec)]) <= 10) 

    # Append to previous group lengths 
    glv.append = c(glv, gl) 

    # Call function recursively 
    gf(vec, i + gl, glv.append) 
} 

Chạy hàm để trả về một vector có độ dài nhóm:

group_vec = gf(df$value, 1, numeric(0)) 
[1] 2 2 2 3 2 3 1 

Để thêm một cột để df với độ dài nhóm, sử dụng rep:

df$group10 = rep(1:length(group_vec), group_vec) 

Trong hình thức hiện tại của nó chức năng này sẽ chỉ hoạt động trên các vectơ không có bất kỳ giá trị nào lớn hơn 10 và việc nhóm theo số tiền < = 10 được mã hóa cứng. Tất nhiên, chức năng có thể được khái quát hóa để đối phó với những hạn chế này.

Chức năng có thể được tăng tốc một phần bằng cách thực hiện các khoản tiền tích lũy chỉ xem trước một số giá trị nhất định, thay vì độ dài còn lại của vectơ. Ví dụ: nếu giá trị luôn dương, bạn chỉ cần xem mười giá trị phía trước, vì bạn sẽ không bao giờ cần tổng hợp nhiều hơn mười số để đạt được giá trị 10. Điều này cũng có thể được tổng quát cho bất kỳ giá trị mục tiêu nào. Ngay cả với sửa đổi này, hàm vẫn chậm hơn một vòng lặp cho một vectơ với hơn một trăm giá trị.

Tôi chưa từng làm việc với các hàm đệ quy trong R trước và sẽ quan tâm đến bất kỳ nhận xét và đề xuất nào về việc liệu đệ quy có hợp lý với loại vấn đề này hay không và liệu nó có thể được cải thiện hay không.

1

Bạn có thể xác định chức năng của riêng bạn và sau đó sử dụng nó bên tuyên bố dplyr của mutate như sau:

df %>% group_by() %>% 
    mutate(
    cumsum_10 = cumsum_with_reset(value, 10), 
    group_10 = cumsum_with_reset_group(value, 10) 
) %>% 
    ungroup() 

Chức năng cumsum_with_reset() mất một cột và một giá trị ngưỡng mà resets tổng. cumsum_with_reset_group() tương tự nhưng xác định các hàng đã được nhóm lại với nhau. Định nghĩa như sau:

# group rows based on cumsum with reset 
cumsum_with_reset_group <- function(x, threshold) { 
    cumsum <- 0 
    group <- 1 
    result <- numeric() 

    for (i in 1:length(x)) { 
    cumsum <- cumsum + x[i] 

    if (cumsum > threshold) { 
     group <- group + 1 
     cumsum <- x[i] 
    } 

    result = c(result, group) 

    } 

    return (result) 
} 

# cumsum with reset 
cumsum_with_reset <- function(x, threshold) { 
    cumsum <- 0 
    group <- 1 
    result <- numeric() 

    for (i in 1:length(x)) { 
    cumsum <- cumsum + x[i] 

    if (cumsum > threshold) { 
     group <- group + 1 
     cumsum <- x[i] 
    } 

    result = c(result, cumsum) 

    } 

    return (result) 
} 

# use functions above as window functions inside mutate statement 
df %>% group_by() %>% 
    mutate(
    cumsum_10 = cumsum_with_reset(value, 10), 
    group_10 = cumsum_with_reset_group(value, 10) 
) %>% 
    ungroup() 
Các vấn đề liên quan