基于多个列的条件创建新列

4

我有以下数据集:

library(data.table)
set.seed(123)
dt <- data.table(x_1 = c(3,2,2,1,3,2,1,2,3,3),
                 x_2 = c(2,1,1,3,2,3,3,1,2,3),
                 x_3 = c(2,3,3,2,1,2,3,3,1,1),
                 y_1 = sample(2, 10, replace = T),
                 y_2 = sample(2, 10, replace = T),
                 y_3 = sample(2, 10, replace = T))

我想在x列上执行if else操作,如果任何一个x列等于1,则会创建一个新的列,并将值设置为相应的y列。例如,在第5行中,x_3 = 1,因此新列应返回对应于y_3的值。
我的想法是返回与条件匹配的x列的名称作为中间列,然后使用值后缀(1,2,3)从相应的y列中提取值。
但是,创建中间列的第一步是创建一个列表,不符合条件的行将返回character(0)。
cols <- c("x_1", "x_2", "x_3")
dt$int <- apply(dt[,..cols], 1, function(x) names(which(x == 1)))

我的期望输出:

x_1 x_2 x_3 y_1 y_2 y_3 new
3   2   2   1   2   2   NA
2   1   3   2   1   2   1
2   1   3   1   2   2   2
1   3   2   2   2   2   2
3   2   1   2   1   2   2
2   3   2   1   2   2   NA
1   3   3   2   1   2   2
2   1   3   2   1   2   1
3   2   1   2   1   1   1
3   3   1   1   2   1   1

有没有关于如何实现这一点的想法?基于数据表的解决方案将更为可取。


1
你的样本数据集的期望输出是什么? - chinsoon12
@chinsoon12 - 已编辑问题以反映此事。 - Debbie
1
每行中是否确保只有一个等于1的x列?或者,例如,如果x_1x_3都为1,那么预期结果是什么? - Uwe
你的输出与输入不一致。也许你不小心交换了几行的顺序? - Frank
1
@Frank - 感谢你指出这个问题。已经编辑了问题以进行更正。 - Debbie
显示剩余2条评论
3个回答

2

如果在同一行的x_*中没有任何1或多个1,您不确定希望如何处理。

这里是一个可能的方法,使用data.table :: melt转换为长格式,然后找到x_中第一个位置的1,然后访问y_值。

dt[, rn:=.I]
dt[melt(dt, id.vars="rn", meas=list(c("x_1", "x_2", "x_3"), c("y_1", "y_2", "y_3")))[,
    value2[which(value1==1L)[1L]], by=.(rn)], yval := V1, on=.(rn)]

输出:

    x_1 x_2 x_3 y_1 y_2 y_3 rn yval
 1:   3   2   2   1   2   2  1   NA
 2:   2   1   3   2   1   2  2    1
 3:   2   1   3   1   2   2  3    2
 4:   1   3   2   2   2   2  4    2
 5:   3   2   1   2   1   2  5    2
 6:   2   3   2   1   2   2  6   NA
 7:   1   3   3   2   1   2  7    2
 8:   2   1   3   2   1   2  8    1
 9:   3   2   1   2   1   1  9    1
10:   3   3   1   1   2   1 10    1

编辑:整合thelatemail的简洁版本,并处理多个版本。

编辑:将thelatemail的简洁版本整合进来,同时也处理了多个版本。

dt[, yval := 
    melt(dt, id.vars="rn", measure.vars=patterns("^x_", "^y_"))[value1==1L][
        dt, value2, on=.(rn), mult="first"]
]

3
同一概念的轻微变化 - dt[melt(dt, id.vars="rn", measure=patterns("x_", "y_"), value.name=c("x","y"))[x==1], on="rn", new := i.y] - thelatemail
@thelatemail - 对于我的数据来说完美无缺。最终我采用了你的建议,因为我在模式内使用正则表达式来提取实际数据中的变量。 - Debbie

2
另一个可能的解决方案:
ix <- dt[, max.col(.SD == 1) * NA^(!rowSums(.SD == 1)), .SDcols = 1:3]

dt[, newcol := as.matrix(.SD)[cbind(.I, ix)]
   , .SDcols = 4:6][]

这将会给出:

    x_1 x_2 x_3 y_1 y_2 y_3 newcol
 1:   3   2   2   1   2   2     NA
 2:   2   1   3   2   1   2      1
 3:   2   1   3   1   2   2      2
 4:   1   3   2   2   2   2      2
 5:   3   2   1   2   1   2      2
 6:   2   3   2   1   2   2     NA
 7:   1   3   3   2   1   2      2
 8:   2   1   3   2   1   2      1
 9:   3   2   1   2   1   1      1
10:   3   3   1   1   2   1      1

注意:

  • 您可以使用as.data.frame代替as.matrix
  • 如果您有多个等于1的x列,则需要使用max.colties.method参数。您可以在"random""first""last"之间选择。

如果您事先不知道列位置,可以将上述解决方案推广到:

xcols <- like(names(dt), "x")
ycols <- like(names(dt), "y")

ix <- dt[, max.col(.SD == 1) * NA^(!rowSums(.SD == 1)), .SDcols = xcols]

dt[, newcol := as.matrix(.SD)[cbind(.I, ix)]
   , .SDcols = ycols][]

0

这里有一个使用Map的选项。对于数据表(.SD)的's'和'y'列进行子集操作,创建一个逻辑向量来表示'x'列,并获取相应的'y'值,其中'x'为1,并使用pmin将其折叠为单个元素(假设每行中'x'列不超过1个)

dt[, new := do.call(pmin, c(Map(function(x, y) y * NA^(x != 1),
      .SD[, 1:3, with = FALSE], .SD[, 4:6, with = FALSE]), na.rm = TRUE)), ]
dt
#    x_1 x_2 x_3 y_1 y_2 y_3 new
# 1:   3   2   2   1   2   2  NA
# 2:   2   1   3   2   1   2   1
# 3:   2   1   3   1   2   2   2
# 4:   1   3   2   2   2   2   2
# 5:   3   2   1   2   1   2   2
# 6:   2   3   2   1   2   2  NA
# 7:   1   3   3   2   1   2   2
# 8:   2   1   3   2   1   2   1
# 9:   3   2   1   2   1   1   1
#10:   3   3   1   1   2   1   1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接