使用相同的 mapply 函数创建几个新变量

Use the same mapply function to create several new variables

我有一个数据框 ("dat"),其中每一行代表一项研究的一名参与者。对于每个参与者 ("code"),我有一个列显示他们的性别 ("sex") 和年龄 ("age"),还有几个包含测试结果的列 ("v.1" 等) .数据框看起来像这样:

> dat
   code sex age v.1 v.2
1  A1   m   8   4   9
2  B2   f   12  7   2

对于每一列测试结果,我需要在数据框外的相应向量中查找值(例如,"v.1.m.8" 表示 8 岁男性参与者,"v.1.f.12" 表示 12 岁女性参与者)并将该向量的值插入数据框中的新列("v.1.t")。对于男性和女性参与者以及不同年龄组,有不同的向量。向量看起来像这样:

v.1.m.8 <- c(4, 5, 2, 8, 2, ...)
v.2.m.8 <- c(3, 2, 2, 1, 8, ...)
v.1.m.12 <- c(...)
v.2.m.12 <- c(...)
v.1.f.8 <- c(...)
v.2.f.8 <- c(...)
v.1.f.12 <- c(...)
v.2.f.12 <- c(...)

对我来说,在向量中查找值的逻辑上最直接的方法是带有嵌套 if 语句的 for 循环。排序或像这样:

for (i in nrow(dat)) {
    if (dat[i, ]$age < 8 | dat[i, ]$age > 18) {
        dat[i, ]$v.1.t <- NA
        dat[i, ]$v.2.t <- NA
    } else if (dat[i, ]$age < 12) {
        if (dat[i, ]$dat.sex == "m") {
            dat[i, ]$v.1.t <- v.1.m.8[dat[i, ]$v.1]
            dat[i, ]$v.2.t <- v.2.m.8[dat[i, ]$v.2]
        } else {
            dat[i, ]$v.1.t <- v.1.f.8[dat[i, ]$v.1]
            dat[i, ]$v.2.t <- v.2.f.8[dat[i, ]$v.2]
        }
    } else {
        if (dat[i, ]$dat.sex == "m") {
            dat[i, ]$v.1.t <- v.1.m.12[dat[i, ]$v.1]
            dat[i, ]$v.2.t <- v.2.m.12[dat[i, ]$v.2]
        } else {
            dat[i, ]$v.1.t <- v.1.f.12[dat[i, ]$v.1]
            dat[i, ]$v.2.t <- v.2.f.12[dat[i, ]$v.2]
        }
    }
}

为了避免循环,我可能会像这样使用 mapply():

dat$v.1.t <- mapply(
    function(a, b, c) {
        if (a < 8 | a > 18) {
            NA
        } else if (a < 12) {
            if (b == "m") {
                v.1.m.8[c]
            } else {
                v.1.f.8[c]
            }
        } else {
            if (b == "m") {
                v.1.m.12[c]
            } else {
                v.1.f.12[c]
            }
        }
    },
    dat$age,
    dat$dat.sex,
    dat$v.1
)

dat$v.2.t <- mapply(
    function(a, b, c) {
        if (a < 8 | a > 18) {
            NA
        } else if (a < 12) {
            if (b == "m") {
                v.2.m.8[c]
            } else {
                v.2.f.8[c]
            }
        } else {
            if (b == "m") {
                v.2.m.12[c]
            } else {
                v.2.f.12[c]
            }
        }
    },
    dat$age,
    dat$dat.sex,
    dat$v.2
)

第二个解决方案的问题是我必须为每个要分配的变量重复整个代码。

有更好的解决方案吗?

在我的实际代码中,我必须在 44 个向量中查找 11 列以创建 11 个新列。

我更喜欢以 R 为基础的解决方案。

假设您的数据如下所示:

dat <- data.frame(code = paste0(LETTERS[1:24], 1:24), sex=c("m", "f"), age=c(8,12, 12, 8), v.1 = sample(1:10, 24, replace=T), v.2 = sample(1:10, 24, replace=T))

根据性别和年龄的组合进行拆分,并为每个拆分调出 v.1 值:

lapply(split(dat, list(dat$sex, dat$age)), '[[', "v.1")

$f.12
[1]  1  9  2  3  3 10

$f.8
[1] 8 3 7 7 3 8

$m.12
[1] 10  3  2  2  4  1

$m.8
[1]  8 10  1  9  5  7

根据性别和年龄的组合进行拆分,并为每个拆分调出 v.2 值:

lapply(split(dat, list(dat$sex, dat$age)), '[[', "v.2")

$f.12
[1] 10  3  5  8  9  2

$f.8
[1] 2 3 4 8 2 5

$m.12
[1] 9 7 1 1 1 2

$m.8
[1]  5  2  1  5  9 10

编辑:感谢@Sotos 指出按两个变量拆分

这应该很简单 ifelse()

以下示例仅适用于一个新变量:

数据示例(感谢@Adam Quek):

dat <- data.frame(code = paste0(LETTERS[1:24], 1:24), sex=c("m", "f"), 
                  age=c(8,12, 12, 8), v.1 = sample(1:10, 24, replace=T),
                  v.2 = sample(1:10, 24, replace=T))

矢量示例:

v.1.m.8 <- c(21:30)
v.1.f.8 <- c(31:40)
v.1.m.12 <- c(41:50)
v.1.f.12 <- c(51:60)

新变量的代码 v.1.t:

dat$v.1.t <- with(dat, ifelse(!(age %in% c(8,12)), NA, 
                          ifelse(age == 8 & sex == "m", v.1.m.8[v.1], 
                                 ifelse(age == 8 & sex == "f", v.1.f.8[v.1],
                                        ifelse(age == 12 & sex == "m", v.1.m.12[v.1],
                                               v.1.f.12[v.1])))))

可以轻松编辑年龄限制以包含更多类别并分支出可能的向量。

输出:

   code sex age v.1 v.2 v.1.t
1    A1   m   8  10   1    30
2    B2   f  12   6   5    56
3    C3   m  12  10   3    50
4    D4   f   8   7  10    37
5    E5   m   8   5   4    25
6    F6   f  12   6   9    56
7    G7   m  12   2   9    42
8    H8   f   8   2   3    32
9    I9   m   8   4   1    24
10  J10   f  12   7   4    57
11  K11   m  12   7   4    47
12  L12   f   8   9  10    39
13  M13   m   8   9   2    29
14  N14   f  12   5   8    55
15  O15   m  12   1  10    41
16  P16   f   8   8   4    38
17  Q17   m   8   6   7    26
18  R18   f  12   4  10    54
19  S19   m  12  10   1    50
20  T20   f   8   9   6    39
21  U21   m   8   9   8    29
22  V22   f  12  10   2    60
23  W23   m  12   6   6    46
24  X24   f   8   6   7    36

如果您不想为 11 个变量中的每一个都写 ifelse(),请将向量放入具有两层的列表(11 个列表的列表,每个列表有 4 个向量)和 mapply() 在你的变量和矢量列表列表上。

编辑:

我考虑过 mapply() 的实现,我认为简单的 for() 循环更容易。

下面应该这样做(示例有两个变量和每个 4 个向量(m8、f8、m12、f12)):

向量:

v.1.m.8 <- c(21:30)
v.1.f.8 <- c(31:40)
v.1.m.12 <- c(41:50)
v.1.f.12 <- c(51:60)
v.2.m.8 <- c(61:70)
v.2.f.8 <- c(71:80)
v.2.m.12 <- c(81:90)
v.2.f.12 <- c(91:100)

向量列表:

myvectors <- list("v.1" = list(v.1.m.8, v.1.f.8, v.1.m.12, v.1.f.12), 
                  "v.2" = list(v.2.m.8, v.2.f.8, v.2.m.12, v.2.f.12))

for()-循环(只循环遍历列表的名称,所以 ic("v.1", "v.2")):

for(i in names(myvectors)){
  dat[, paste(i, "t", sep = ".")] <- with(dat, ifelse(!(age %in% c(8,12)), NA, 
              ifelse(age == 8 & sex == "m", myvectors[[i]][[1]][eval(parse(text = i))], 
                ifelse(age == 8 & sex == "f", myvectors[[i]][[2]][eval(parse(text = i))],
                  ifelse(age == 12 & sex == "m", myvectors[[i]][[3]][eval(parse(text = i))],
                    myvectors[[i]][[4]][eval(parse(text = i))])))))
}

输出:

   code sex age v.1 v.2 v.1.t v.2.t
1    A1   m   8   3   2    23    62
2    B2   f  12   7  10    57   100
3    C3   m  12   2   3    42    83
4    D4   f   8   7   6    37    76
5    E5   m   8   2  10    22    70
6    F6   f  12   1   9    51    99
7    G7   m  12  10   6    50    86
8    H8   f   8   4   6    34    76
9    I9   m   8   3   1    23    61
10  J10   f  12   5   4    55    94
11  K11   m  12   5   5    45    85
12  L12   f   8   3   8    33    78
13  M13   m   8  10   9    30    69
14  N14   f  12   3   4    53    94
15  O15   m  12   6   2    46    82
16  P16   f   8   8   3    38    73
17  Q17   m   8   9   5    29    65
18  R18   f  12   5   6    55    96
19  S19   m  12   6   4    46    84
20  T20   f   8   2   9    32    79
21  U21   m   8   5   1    25    61
22  V22   f  12   2   1    52    91
23  W23   m  12   3  10    43    90
24  X24   f   8   2   9    32    79

有了这个,您唯一需要准备的就是第一层带有正确命名子列表的向量列表列表(所以 "v.1""v.11" 如上所示 "v.1""v.2"。确保子列表中 4 个向量的顺序始终相同!在我的示例中,顺序是 m8、f8、m12、f12。希望对您有所帮助!