按值和R中的行数进行条件分组

时间:2019-07-23 05:28:18

标签: r dplyr data.table

原始数据:

> dt = data.table(v1 = c(3,1,1,5,6,12,13,11,10,0,2,1,3))
> dt
    v1
 1:  3
 2:  1
 3:  1
 4:  5
 5:  6
 6: 12
 7: 13
 8: 11
 9: 10
10:  0
11:  2
12:  1
13:  3

我想根据值将v1分为3组:

> dt %>%  mutate(group = case_when(v1 <5 ~ 1,
+                               v1 >=5 & v1 <10 ~ 2,
+                               v1 >= 10 ~3))
   v1 group
1   3  1
2   1  1
3   1  1
4   5  2
5   6  2
6  12  3
7  13  3
8  11  3
9  10  3
10  0  1
11  2  1
12  1  1
13  3  1

但是我还想添加一条规则,如果一个组中的行总数低于3,则取这些行的平均值,并将其与该组之前和之后的行(v1)进行比较,并且最接近平均值的那个值都会吸收该组。

在上面的示例中,组2仅具有2行,因此我取其平均值(5.5)并与上方(1)和下方(12)的值进行比较。由于较小的值更接近平均值,因此这些行将成为组1,从而使所需的输出如下所示:

   v1 group
1   3  1
2   1  1
3   1  1
4   5  1
5   6  1
6  12  3
7  13  3
8  11  3
9  10  3
10  0  1
11  2  1
12  1  1
13  3  1

我做了几次尝试都没有用,非常感谢dplyrdata.table解决方案。

3 个答案:

答案 0 :(得分:1)

使用check this code pen link的一个选项可能是创建一个新列,该列将保留一个dplyr的帐户,并比较那些较少的组上方和下方的一行的row_number值超过3行,并根据该行分配新的组。 v1是最终输出。

change

答案 1 :(得分:1)

首先,计算原始分组并汇总:

gDT = dt[, .(.N, m = mean(v1)), by=.(
  ct = ct <- cut(v1, c(-Inf, 5, 10, Inf), right=FALSE, labels=FALSE),
  g = rleid(ct)
)]

   ct g N         m
1:  1 1 3  1.666667
2:  2 2 2  5.500000
3:  3 3 4 11.500000
4:  1 4 4  1.500000

标记组以更改m并将其与上方和下方最接近的不变组进行比较:

gDT[, flag := N < 3]

gDT[, res := ct]
gDT[flag == TRUE, res := {
  ffDT = gDT[flag == FALSE]

  # nearest eligible rows going up and down -- possibly NA if at top or bottom
  w_dn = ffDT[.(g = .SD$g - 1L), on=.(g), roll=TRUE, which=TRUE]
  w_up = ffDT[.(g = .SD$g + 1L), on=.(g), roll=-Inf, which=TRUE]

  # diffs of the mean against eligible rows up and down
  diffs = lapply(list(dn = w_dn, up = w_up), function(w) abs(ffDT$m[w] - m))

  # if/else for whichever is nearer, ties broken in favor of up
  replace(ffDT$ct[w_dn], diffs$up < diffs$dn, ffDT$ct[w_up])
}]

   ct g N         m  flag res
1:  1 1 3  1.666667 FALSE   1
2:  2 2 2  5.500000  TRUE   1
3:  3 3 4 11.500000 FALSE   3
4:  1 4 4  1.500000 FALSE   1

像这样创建一个单独的表,可以很容易地检查工作(查看标记的组,检查Nct,将m与最近的未标记邻居进行比较,等等)。 / p>

要添加回原始表,一种方法是:

dt[, res := gDT$res[ rleid(cut(v1, c(-Inf, 5, 10, Inf), right=FALSE, labels=FALSE)) ] ]

    v1 ct res
 1:  3  1   1
 2:  1  1   1
 3:  1  1   1
 4:  5  2   1
 5:  6  2   1
 6: 12  3   3
 7: 13  3   3
 8: 11  3   3
 9: 10  3   3
10:  0  1   1
11:  2  1   1
12:  1  1   1
13:  3  1   1

详细信息:上面的步骤比@RonakShah的答案要复杂得多,因为我认为这里的“组”适用于连续的行:

  

但是我还想添加一条规则,如果一个组中的行总数低于3,则取这些行的平均值,并将其与该组之前和之后的行(v1)进行比较,并且最接近平均值的那个值都会吸收该组。

否则,标准的定义不明确-如果存在一组大小为2的组,但两行不是连续的,则没有“紧接在该组之前和之后”的比较。

答案 2 :(得分:1)

基于Frank的cutrleid(ct)

#from Frank's answer
dt[,
    c("ct", "g") := {
        ct <- cut(v1, c(-Inf, 5, 10, Inf), right=FALSE, labels=FALSE)
        .(ct, rleid(ct))
    }
]

#calculate mean
dt[, c("N", "m") := .(.N, m=mean(v1)), by=.(ct, g)]

#store last/first value from prev/next for rolling join later
ct_dt <- dt[, c(.(ct=ct, g=g), shift(.(v1, g), c(1L, -1L)))][,
    .(near_v1=c(V3[1L], V4[.N]), new_ct=c(V5[1L], V6[.N])), .(ct, g)]

#update join for those with less than 3 rows
dt[N<3L, ct := ct_dt[.SD, on=.(ct, g, near_v1=m), roll="nearest", new_ct]]

#delete unwanted columns
dt[, c("g","N","m") := NULL]

输出:

    v1 ct
 1:  3  1
 2:  1  1
 3:  1  1
 4:  5  1
 5:  6  1
 6: 12  3
 7: 13  3
 8: 11  3
 9: 10  3
10:  0  1
11:  2  1
12:  1  1
13:  3  1