使用相同的 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()
-循环(只循环遍历列表的名称,所以 i
是 c("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。希望对您有所帮助!
我有一个数据框 ("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()
-循环(只循环遍历列表的名称,所以 i
是 c("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。希望对您有所帮助!