【问题标题】:Combining scenario to Replace Medians by Groups in R组合场景以在 R 中按组替换中位数
【发布时间】:2018-09-13 10:53:20
【问题描述】:

我有数据集

mydat <- 
structure(list(code = structure(c(1L, 1L, 1L, 1L, 1L, 1L, 1L, 
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), .Label = c("52382MCK", 
"52499MCK"), class = "factor"), item = c(11709L, 11709L, 11709L, 
11709L, 11708L, 11708L, 11708L, 11710L, 11710L, 11710L, 11710L, 
11710L, 11710L, 11710L, 11710L, 11710L, 11710L, 11710L, 11710L, 
11710L, 11710L, 11710L, 11710L, 11710L, 11710L, 11710L, 11710L, 
11710L, 11202L, 11203L, 11203L, 11204L, 11204L, 11205L, 11205L
), sales = c(30L, 10L, 20L, 15L, 2L, 10L, 3L, 30L, 10L, 20L, 
15L, 2L, 10L, 3L, 30L, 10L, 20L, 15L, 2L, 10L, 3L, 30L, 10L, 
20L, 15L, 2L, 10L, 3L, 2L, 2L, 2L, 2L, 2L, 2L, 2L), action = c(0L, 
1L, 0L, 0L, 0L, 1L, 0L, 0L, 0L, 0L, 1L, 0L, 0L, 0L, 0L, 0L, 0L, 
1L, 0L, 0L, 0L, 0L, 0L, 0L, 1L, 0L, 0L, 0L, 0L, 0L, 1L, 0L, 0L, 
1L, 1L)), row.names = c(NA, -35L), class = "data.frame")
# coerce to data.table
setDT(mydat)

使用此数据集,执行多个操作。

1. selecting scenario by groups.

所以有操作栏。它只能有两个值零(0)或一(1)。

场景是第一个动作类别之前的零类别动作的数量和一个类别动作之后的零类别的数量。

For example
52382МСК    11709

这是当我们有 1 个零类别的动作 col 时的场景。在第一类动作 col 之前,在第一类动作 col 之后有两个零。注意:当我们有 2 个零类别的动作 col 时可能会出现这种情况。在第一类动作 col 之前,在第一类动作 col 之后 1 个零。

mydat1

code    item    sales   action
52382МСК    11709   30  0
52382МСК    11709   10  1
52382МСК    11709   20  0
52382МСК    11709   15  0

为了检测这种情况,我使用了这个脚本/ 这个脚本很好用,感谢@Uwe

library(data.table)
library(magrittr)

max_zeros <- 3
zeros <- sapply(0:max_zeros, stringr::str_dup, string = "0")
names(zeros) <- as.character(nchar(zeros))
sc <- CJ(zeros.before = zeros, zeros.after = zeros)[
  , scenario.name := paste(nchar(zeros.before), nchar(zeros.after), sep = "-")][
    , action.pattern := sprintf("%s1+(?=%s)", zeros.before, zeros.after)][]
# special case: all zero
sc0 <- data.table(
  zeros.before = NA,
  zeros.after = NA, 
  scenario.name = "no1", 
  action.pattern = "^0+$")
sc <- rbind(sc0, sc)

然后

setDT(mydat)
class <- mydat[, .(scenario.name = sc$scenario.name[
  paste(action, collapse = "") %>% 
    stringr::str_count(sc$action.pattern) %>%
    is_greater_than(0) %>% 
    which() %>% 
    max()
  ]),
  by = .(code, item)][]

class
mydat[class, on = .(code, item)]

所以我得到了场景类的数据。

2.operation it is replace median.

按零类别计算每个场景的中位数。

我需要按动作列通过 1 个前面的零类别计算中值​​,即在一个类别的动作列之前,以及在一个类别之后的动作列的 2 个零。 仅对第一类操作列执行的中值替换 按销售列。 如果中位数大于销售额,则不要替换它。

为此,我使用脚本

sales_action <- function(DF, zeros_before, zeros_after) {
  library(data.table)
  library(magrittr)
  action_pattern <- 
    do.call(sprintf, 
            c(fmt = "%s1+(?=%s)", 
              stringr::str_dup("0", c(zeros_before, zeros_after)) %>% as.list()
            ))
  message("Action pattern used: ", action_pattern)
  setDT(DF)[, rn := .I]
  tmp <- DF[, paste(action, collapse = "") %>% 
              stringr::str_locate_all(action_pattern) %>% 
              as.data.table() %>% 
              lapply(function(x) rn[x]),
            by = .(code, item)][
              , end := end + zeros_after]
  DF[tmp, on = .(code, item, rn >= start, rn <= end), 
     med := as.double(median(sales[action == 0])), by = .EACHI][
       , output := as.double(sales)][action == 1, output := pmin(sales, med)][
         , c("rn", "med") := NULL][]
}

然后

sales_action(mydat, 1L, 2L)

所以我得到了结果。

问题基于以下内容

每次我必须手动输入场景以替换为中位数

sales_action(mydat, 1L, 2L)
sales_action(mydat, 3L, 1L)
sales_action(mydat, 2L, 2L)

等等。

如何在所有可能的情况下自动执行替换中位数 这样我就不会每次都写 sales_action(mydat, .L, .L)

输出示例

code    i    tem    sales   action  output  pattern
52382MCK    11709   30        0       30    01+00
52382MCK    11709   10        1       10    01+00
52382MCK    11709   20        0       20    01+00
52382MCK    11709   15        0       15    01+00
52382MCK    1170    8         0        8    01+00
52382MCK    1170    10        1        8    01+00
52382MCK    1170    2         0        2    01+00
52382MCK    1170    15        0        15   01+00

【问题讨论】:

  • 我正在努力从您的帖子中提取关键信息,因为这里有很多文本/代码。如果您要压缩您的帖子,使其更简洁,并且只保留代码的关键相关部分,这将有所帮助。
  • @MauritsEvers,是的,有很多信息,因为这篇文章是我以前的结果,但 Uwe 帮助了我。以后我会尽量简短

标签: r dplyr data.table lapply


【解决方案1】:

如果我理解正确,OP 希望通过将操作期间的 sales 数字与销售操作之前和之后的销售中位数进行比较来分析销售操作的成功。 p>

有一些挑战:

  1. 每个codeitem 组可能有不止一项销售操作。
  2. 可用数据可能涵盖少于要求的 3 个销售活动前后各三天。

恕我直言,引入场景是处理问题 2 的弯路。

下面的方法

  • 标识每个codeitem 组内的销售行为,
  • 每个销售操作之前最多选择三个零操作行,之后最多三个行,
  • 计算这些行的销售额中位数,并
  • 更新output,以防销售操作中的销售数字超过周围零操作行的中位数。

OP 创造了术语 category 来区分销售活动时期(action == 1L 的连续条纹)和之前和之后的零活动时期。

library(data.table)
# coerce to data.table and create categories
setDT(mydat)[, cat := rleid(action), by = .(code, item)][]

# extract action categories, identify preceeding & succeeding zero action categories
mycat <- mydat[, .(action = first(action)), by = .(code, item, cat = cat)][
  , `:=`(before = cat - 1L, after = cat + 1L)][action == 1L]

mycat

       code  item cat action before after
1: 52382MCK 11709   2      1      1     3
2: 52382MCK 11708   2      1      1     3
3: 52382MCK 11710   2      1      1     3
4: 52382MCK 11710   4      1      3     5
5: 52382MCK 11710   6      1      5     7
6: 52499MCK 11203   2      1      1     3
7: 52499MCK 11205   1      1      0     2

请注意,52382MCK, 11710 组包含三个单独的销售操作。 beforeafter 可能指向不存在的 cat,但这将在后续连接过程中自动纠正。

# compute median of surrouding zero action categories
action_cat_median <- 
  rbind(
    # get sales from up to 3 zero action rows before action category
    mydat[mycat, on = .(code, item, cat = before), 
          .(sales = tail(sales, 3), i.cat), by =.EACHI],
    # get sales from up to 3 zero action rows after action category
    mydat[mycat, on = .(code, item, cat = after), 
          .(sales = head(sales, 3), i.cat), by =.EACHI]
  )[
    # remove empty groups
    !is.na(sales)][
      # compute median for each action category
      , .(med = as.double(median(sales))), by = .(code, item, cat = i.cat)]

action_cat_median
       code  item cat  med
1: 52382MCK 11709   2 20.0
2: 52382MCK 11708   2  2.5
3: 52382MCK 11710   2 10.0
4: 52382MCK 11710   4 10.0
5: 52382MCK 11710   6 10.0
6: 52499MCK 11203   2  2.0
# prepare result
mydat[, output := as.double(sales)][
  # update join
  action_cat_median, on = .(code, item, cat), output := pmin(sales, med)]

编辑:或者,对pmin() 的调用可以替换为非等值连接,它只更新销售额超过中位数的行:

# prepare result, alternative approach
mydat[, output := as.double(sales)][
  # non-equi update join
  action_cat_median, on = .(code, item, cat, output > med), output := med]


mydat
        code  item sales action cat output
 1: 52382MCK 11709    30      0   1   30.0
 2: 52382MCK 11709    10      1   2   10.0
 3: 52382MCK 11709    20      0   3   20.0
 4: 52382MCK 11709    15      0   3   15.0
 5: 52382MCK 11708     2      0   1    2.0
 6: 52382MCK 11708    10      1   2    2.5
 7: 52382MCK 11708     3      0   3    3.0
 8: 52382MCK 11710    30      0   1   30.0
 9: 52382MCK 11710    10      0   1   10.0
10: 52382MCK 11710    20      0   1   20.0
11: 52382MCK 11710    15      1   2   10.0
12: 52382MCK 11710     2      0   3    2.0
13: 52382MCK 11710    10      0   3   10.0
14: 52382MCK 11710     3      0   3    3.0
15: 52382MCK 11710    30      0   3   30.0
16: 52382MCK 11710    10      0   3   10.0
17: 52382MCK 11710    20      0   3   20.0
18: 52382MCK 11710    15      1   4   10.0
19: 52382MCK 11710     2      0   5    2.0
20: 52382MCK 11710    10      0   5   10.0
21: 52382MCK 11710     3      0   5    3.0
22: 52382MCK 11710    30      0   5   30.0
23: 52382MCK 11710    10      0   5   10.0
24: 52382MCK 11710    20      0   5   20.0
25: 52382MCK 11710    15      1   6   10.0
26: 52382MCK 11710     2      0   7    2.0
27: 52382MCK 11710    10      0   7   10.0
28: 52382MCK 11710     3      0   7    3.0
29: 52499MCK 11202     2      0   1    2.0
30: 52499MCK 11203     2      0   1    2.0
31: 52499MCK 11203     2      1   2    2.0
32: 52499MCK 11204     2      0   1    2.0
33: 52499MCK 11204     2      0   1    2.0
34: 52499MCK 11205     2      1   1    2.0
35: 52499MCK 11205     2      1   1    2.0
        code  item sales action cat output

以下行已更新:

mydat[output != sales]
       code  item sales action cat output
1: 52382MCK 11708    10      1   2    2.5
2: 52382MCK 11710    15      1   2   10.0
3: 52382MCK 11710    15      1   4   10.0
4: 52382MCK 11710    15      1   6   10.0

【讨论】:

  • 非常漂亮和干净的代码。一些补充。如何在单独的数据集中分离场景 3L - 3L?所以它将有两个数据集 1. 没有 3l3l 的所有场景和 2. 只有场景 3l-3l 的数据集
  • @D.Joe,谢谢。请,您能否更详细地指定应该将哪些行移动到每个子集,尤其是对于边缘情况?组52382MCK, 11710的所有行都属于3L-3L场景(000100000010000001000)。但是0001000100000001000000010000000100000010 是什么?
  • 抱歉,我的互联网连接有任何问题。上不了网。现在好了。你的问题呢。感谢您最深入的分析技能,所以我想,让我们像您发现的这种奇怪的情况,放入新的单独数据集,我将在 sql 中考虑如何处理这些销售。是否可以将奇怪的案例放在一个单独的数据集中?
  • @D.Joe,我更新了我的答案here,以展示如何将mydat 拆分为每个场景的单独数据集。如果这不符合您的要求,我建议发布一个新问题。
  • 这个字符串 'sales_action(mydat, 1L, 2L)' 和这篇文章中的字符串 stackoverflow.com/questions/51899131/… 'split(mydat[class, on = .(code, item)], by = "场景.name")' 一切都很好。我只想这样做'sales_action(mydat,1L,2L)'仅适用于除3-3之外的拆分数据集。说得好像R是男人'' sales_action(mydat,split(mydat[class, on = .(code, item)], by = "scenario.name") where class is not 3-3) 怎么办?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-05-03
  • 1970-01-01
  • 1970-01-01
  • 2020-09-07
  • 2022-10-04
  • 1970-01-01
相关资源
最近更新 更多