从另一个 data.table 中查找间隔 - 按组

Look up intervals from another data.table - by group

我有一个由 3 个部分组成的 A 部分。 首先,我描述了我成功地做到了这一点。 那我是怎么加多Part都不成功的。

章节长度在 data.table d1.

library(data.table)

    d1 <- data.table(
            Part="A",
            Section=1:3,
            SecLen=c(10,30,9))
    d1
 #       Part Section SecLen
 #    1:    A       1     10
 #    2:    A       2     30
 #    3:    A       3      9

我在 d2 的部分也有一组位置。

d2 <- data.table(
        Part="A",
        PartLoc=c(0,7.5,10,20,35,45,49))
d2
#    Part PartLoc
# 1:    A     0.0
# 2:    A     7.5
# 3:    A    10.0
# 4:    A    20.0
# 5:    A    35.0
# 6:    A    45.0
# 7:    A    49.0  

我想添加每个位置所在的部分。

首先,我使用 cumsum

d1 中堆叠部分长度
d1[,CumLen:=cumsum(SecLen)]
d1
   #    Part Section SecLen CumLen
   # 1:    A       1     10     10
   # 2:    A       2     30     40
   # 3:    A       3      9     49

然后我使用findInterval来映射Section。请注意,我希望将位置 10 分配给第 1 部分,而不是第 2 部分。

d2[,Sec.fI:=findInterval(PartLoc,c(-1,d1$CumLen),left.open=TRUE)]
d2
    #    Part PartLoc Sec.fI
    # 1:    A     0.0      1
    # 2:    A     7.5      1
    # 3:    A    10.0      1
    # 4:    A    20.0      2
    # 5:    A    35.0      2
    # 6:    A    45.0      3
    # 7:    A    49.0      3

另一种方法使用 data.table 连接。

首先我添加每个部分的开始位置。

d1[,CumLen0:=c(-1,head(CumLen,-1))]
d1
    #    Part Section SecLen CumLen CumLen0
    # 1:    A       1     10     10      -1
    # 2:    A       2     30     40      10
    # 3:    A       3      9     49      40

然后查找该部分。

d2[,Sec.cs:=d1[d2,Section,on=.(CumLen0<PartLoc,CumLen>=PartLoc)]]
d2
    #    Part PartLoc Sec.fI Sec.cs
    # 1:    A     0.0      1      1
    # 2:    A     7.5      1      1
    # 3:    A    10.0      1      1
    # 4:    A    20.0      2      2
    # 5:    A    35.0      2      2
    # 6:    A    45.0      3      3
    # 7:    A    49.0      3      3

两种方法都行。

现在我尝试使用更多的零件。

D1 <- data.table(
 Part = c("A","A","A","B","B","C"),
 Section = c(1,2,3,1,2,1),
 SecLen = c(10,30,9,5,20,18) # incorrectly had 10 for the last value
)

D2 <- data.table(
 Part = c(rep("A",7),rep("B",3),rep("C",3)),
 PartLoc = c(0.0,7.5,10,20,35,45,49,1,12,25,0,9,18)
)

D1[,CumLen:=cumsum(SecLen),by=Part]
D1
#    Part Section SecLen CumLen
# 1:    A       1     10     10
# 2:    A       2     30     40
# 3:    A       3      9     49
# 4:    B       1      5      5
# 5:    B       2     20     25
# 6:    C       1     18     18

D2
    #     Part PartLoc
    #  1:    A     0.0
    #  2:    A     7.5
    #  3:    A    10.0
    #  4:    A    20.0
    #  5:    A    35.0
    #  6:    A    45.0
    #  7:    A    49.0
    #  8:    B     1.0
    #  9:    B    12.0
    # 10:    B    25.0
    # 11:    C     0.0
    # 12:    C     9.0
    # 13:    C    18.0

我试试findInterval.

D2[,Sec.fI:=findInterval(PartLoc,c(-1,D1$CumLen),left.open=TRUE),by=Part]
# Error in findInterval(PartLoc, c(-1, D1$CumLen), left.open = TRUE) : 
#        'vec' must be sorted non-decreasingly and not contain NAs

显然它不起作用,因为我没有按部分分组 D1$CumLen,所以它是非递减的。

我试试加入。

D1[,CumLen0:=c(-1,head(CumLen,-1)),by=Part]

D2[,Sec.cs:=D1[D2,Section,on=.(CumLen0<PartLoc,CumLen>=PartLoc),by=Part]]
# Error in vecseq(f__, len__, if (allow.cartesian || notjoin || !anyDuplicated(f__,  : 
#   Join results in 31 rows; more than 19 = nrow(x)+nrow(i).
#   Check for duplicate key values in i each of which join to the same group in x over and over again. 
#   If that's ok, try by=.EACHI to run j for each group to avoid the large allocation. 
#   If you are sure you wish to proceed, rerun with allow.cartesian=TRUE. 
#   Otherwise, please search for this error message in the FAQ, Wiki, Stack Overflow and data.table issue tracker for advice.

在这里,我 运行 进入了我对联接的新生知识的极限。我按照建议尝试 allow.cartesian=TRUE,但我只是确认我以某种方式得到了 31 行,但没有我想要的。

D1[D2,Section,on=.(CumLen0<PartLoc,CumLen>=PartLoc),by=Part,allow.cartesian=TRUE]
#     Part Section
#  1:    A       1
#  2:    A       1
#  3:    A       1
#  4:    A       2
#  5:    A       2
#  6:    A       3
#  7:    A       3
#  8:    A       1
#  9:    A       2
# 10:    A       2
# 11:    A       1
# 12:    A       1
# 13:    A       2
# 14:    B       1
# 15:    B       2
# 16:    B       2
# 17:    B       2
# 18:    B       1
# 19:    B       2
# 20:    B       2
# 21:    B       1
# 22:    B       2
# 23:    B       2
# 24:    C       1
# 25:    C       1
# 26:    C       1
# 27:    C       1
# 28:    C       1
# 29:    C       1
# 30:    C       1
# 31:    C       1
# Part Section

如果能帮助我在 data.table 中工作,我将不胜感激。

编辑 我对@Ian Campbell 的 findInterval 解决方案感到满意。我仍然有兴趣了解如何使连接工作。

您可以使用 .BY 特殊符号对 D1 进行子集化:

library(data.table)
D2[,Sec.fI:=findInterval(PartLoc,c(-1,D1[Part == .BY,CumLen]),left.open=TRUE),by=Part][]
    Part PartLoc Sec.fI
 1:    A     0.0      1
 2:    A     7.5      1
 3:    A    10.0      1
 4:    A    20.0      2
 5:    A    35.0      2
 6:    A    45.0      3
 7:    A    49.0      3
 8:    B     1.0      1
 9:    B    12.0      2
10:    B    25.0      2
11:    C     0.0      1
12:    C     9.0      1
13:    C    18.0      2

有关详细信息,请参阅 help("special-symbols")

你的 OP 有点迷失。至少基于前 3 句话,这里有一个使用 rolling join 的选项:

d1[, LastPage := cumsum(SecLen)]
d2[, Section := 
  d1[.SD, on=.(Part, LastPage=PartLoc), roll=-Inf, Section]
]

输出:

   Part PartLoc Section
1:    A     0.0       1
2:    A     7.5       1
3:    A    10.0       1
4:    A    20.0       2
5:    A    35.0       2
6:    A    45.0       3
7:    A    49.0       3

OP编辑:

我可以确认这对我的案例有更多的部分。

D2[, Sec.rJ := 
       D1[.SD, on=.(Part, CumLen=PartLoc), roll=-Inf, Section]
   ][]
    #     Part PartLoc Sec.rJ
    #  1:    A     0.0      1
    #  2:    A     7.5      1
    #  3:    A    10.0      1
    #  4:    A    20.0      2
    #  5:    A    35.0      2
    #  6:    A    45.0      3
    #  7:    A    49.0      3
    #  8:    B     1.0      1
    #  9:    B    12.0      2
    # 10:    B    25.0      2
    # 11:    C     0.0      1
    # 12:    C     9.0      1
    # 13:    C    18.0      1