R - 从排序数据构建新变量

R - building new variables from sequenced data

这是 问题的更新/跟进。答案概述了他们不符合新要求。

我正在寻找一种有效的方法(data.table?)为每个 ID.

构造两个新度量

措施一和措施二需要满足以下条件:

条件一: 找出三行的序列:

措施 1 的条件 2:

措施 2 的条件 2:

数据:

df2 <- data.frame(ID = c(1,1,1,1,1,1,1,2,2,2,3,3,3,3),
              seqs = c(1,2,3,4,5,6,7,1,2,3,1,2,3,4),
              count = c(2,1,3,1,1,2,3,1,2,1,3,1,4,1),
              product = c("A", "B", "C", "A,C,E", "A,B", "A,B,C", "D", "A", "B", "A", "A", "A,B,C", "D", "D"),
              stock = c("A", "A,B", "A,B,C", "A,B,C,E", "A,B,C,E", "A,B,C,E", "A,B,C,D,E", "A", "A,B", "A,B", "A", "A,B,C", "A,B,C,D", "A,B,C,D"))

> df2
   ID seqs count product     stock
1   1    1     2       A         A
2   1    2     1       B       A,B
3   1    3     3       C     A,B,C
4   1    4     1   A,C,E   A,B,C,E
5   1    5     1     A,B   A,B,C,E
6   1    6     2   A,B,C   A,B,C,E
7   1    7     3       D A,B,C,D,E
8   2    1     1       A         A
9   2    2     2       B       A,B
10  2    3     1       A       A,B
11  3    1     3       A         A
12  3    2     1   A,B,C     A,B,C
13  3    3     4       D   A,B,C,D
14  3    4     1       D   A,B,C,D

所需的输出如下所示:

   ID seq1 seq2 seq3 measure1   measure2
1:  1    2    3    4   C         E 
2:  2    1    2    3    
3:  3    2    3    4   D

你会如何编码?

要做到这一点,您需要了解以下几点:

  • shift 比较组中值的函数
  • separate_rows 拆分字符串以获取规范化数据视图的函数。
library(data.table)
dt <- data.table(ID = c(1,1,1,1,1,1,1,2,2,2,3,3,3,3),
                  seqs = c(1,2,3,4,5,6,7,1,2,3,1,2,3,4),
                  count = c(2,1,3,1,1,2,3,1,2,1,3,1,4,1),
                  product = c("A", "B", "C", "A,C,E", "A,B", "A,B,C", "D", "A", "B", "A", "A", "A,B,C", "D", "D"),
                  stock = c("A", "A,B", "A,B,C", "A,B,C,E", "A,B,C,E", "A,B,C,E", "A,B,C,D,E", "A", "A,B", "A,B", "A", "A,B,C", "A,B,C,D", "A,B,C,D"))

dt[, count.2 := shift(count, type = "lead")]
dt[, count.3 := shift(count, n = 2, type = "lead")]

dt[, product.2 := shift(product, type = "lead")]
dt[, product.3 := shift(product, n = 2, type = "lead")]


dt <- dt[count > 0 & count.2 > 1 &  count.3 == 1]
dt <- unique(dt, by = "ID")

library(tidyr)
dt.measure <- separate_rows(dt, product.3, sep = ",")
dt.measure <- separate_rows(dt.measure, stock, sep = ",")
dt.measure <- separate_rows(dt.measure, product, sep = ",")

dt.measure[, measure.1 := (product.3 == product.2 & product.3 != stock)]
dt.measure[, measure.2 := (product.3 != product.2 & product.3 != stock)]
res <- dt.measure[, 
  .(
    measure.1 = max(ifelse(measure.1, product.3, NA_character_), na.rm = TRUE), 
    measure.2 = max(ifelse(measure.2, product.3, NA_character_), na.rm = TRUE)
  ),
  ID
]

dt <- merge(dt, res, by = "ID")
dt[, .(ID, measure.1, measure.2)]
# ID measure.1 measure.2
# 1:  1         C         E
# 2:  2      <NA>      <NA>
# 3:  3         D      <NA>

我不确定高效的标准是什么,但这里有一种使用 embedtidyverse 风格的方法。它过滤下来,所以你的工作越来越少。

正在加载数据和包(稍后注意 setdiffintersect 来自 dplry

library(purrr)
library(dplyr)

df1 <- data.frame(ID = c(1,1,1,1,1,1,1,2,2,2,3,3,3,3),
                  seqs = c(1,2,3,4,5,6,7,1,2,3,1,2,3,4),
                  count = c(2,1,3,1,1,2,3,1,2,1,3,1,4,1),
                  product = c("A", "B", "C", "A,C,E", "A,B", 
                              "A,B,C", "D", "A", "B", "A", "A", 
                              "A,B,C", "D", "D"),
                  stock = c("A", "A,B", "A,B,C", "A,B,C,E", "A,B,C,E", 
                            "A,B,C,E", "A,B,C,D,E", "A", "A,B", "A,B", "A", 
                            "A,B,C", "A,B,C,D", "A,B,C,D"),
                  stringsAsFactors = FALSE)

定义一个辅助函数来评估条件 1

meetsCond1 <- function(rseg) { 

  seg <- rev(rseg)

  all(seg[1] > 0, seg[2] > 1, seg[3] == 1)

}

embed 函数将时间序列扭曲成一个矩阵,其中基本上每一行都是感兴趣长度的 window。使用 apply,您可以过滤到哪些行开始相关序列。

cond1Match<- embed(df1$count, 3) %>%
  apply(1, meetsCond1) %>%
  which()

您可以将其转换回最终产品、之前的产品和感兴趣的库存行,以通过添加偏移来确定度量。将它们拆分为单个组件的列表。

finalProds <- df1$product[cond1Match + 2] %>%
  strsplit(",")
prevProds <- df1$product[cond1Match + 1] %>%
  strsplit(",")
initialStock <- df1$stock[cond1Match] %>%
  strsplit(",")

对于这两种措施,它们都不能入库。

notStock <- map2(finalProds, initialStock, ~.x[!(.x %in% .y)])

然后通过检索 window 的序列和 ID 值生成您的 data.frame。那么这些措施就是最终产品与前几行中的 intersectsetdiff

data.frame(ID = df1$ID[cond1Match],
           seq1 = df1$seqs[cond1Match], 
           seq2 = df1$seqs[cond1Match + 1],
           seq3 = df1$seqs[cond1Match + 2],
           measure1 = imap_chr(notStock, 
                               ~intersect(.x, prevProds[[.y]]) %>%
                               {if(length(.) == 0) "" else paste(., sep = ",")}

           ),
           measure2 = imap_chr(notStock, 
                               ~setdiff(.x, prevProds[[.y]]) %>%
                               {if(length(.) == 0) "" else paste(., sep = ",")}

           ),
           stringsAsFactors = FALSE
) %>%
  slice(match(unique(ID), ID))

这会产生所需的输出,似乎每个 ID 最多限制一行。在原来的 post 中,您指定要全部报告。移除 slice 调用将会产生

#>   ID seq1 seq2 seq3 measure1 measure2
#> 1  1    2    3    4        C        E
#> 2  1    6    7    1                  
#> 3  2    1    2    3                  
#> 4  2    3    1    2                 C
#> 5  3    2    3    4        D

如果您希望真正提高效率,您可以通过放置 finalProdsprevProdsinitialStock 的定义而不是将它们分配给首先是变量。我想除非你的比赛集真的很大,否则它可以忽略不计。

滚动 window 方法使用 data.tablej 中的基本 R 代码:

library(data.table)
cols <- c("product", "stock")
setDT(df2)[, (cols) := lapply(.SD, function(x) strsplit(as.character(x), split=",")), .SDcols=cols]

ans <- df2[, 
    transpose(lapply(1L:(.N-2L), function(k) {
        if(count[k]>0 && count[k+1L]>1 && count[k+2L]==1) {
            m1 <- setdiff(intersect(product[[k+2L]], product[[k+1L]]), stock[[k]])
            m2 <- setdiff(setdiff(product[[k+2L]], product[[k+1L]]), stock[[k]])
            c(seq1=seqs[k], seq2=seqs[k+1L], seq3=seqs[k+2L],
                measure1=if(length(m1) > 0) paste(m1, collapse=",") else "",
                measure2=if(length(m2) > 0) paste(m2, collapse=",") else "")
        }
    }), ignore.empty=TRUE),
    ID]
setnames(ans, names(ans)[-1L], c(paste0("seq", 1:3), paste0("measure", 1:2)))
ans

输出:

   ID seq1 seq2 seq3 measure1 measure2
1:  1    2    3    4        C        E
2:  2    1    2    3                  
3:  3    2    3    4        D