计算 R 中 data.table 两列之间转移概率的简洁方法
Clean way to compute transition probabilities between two columns of a data.table in R
玩具示例:
library(data.table)
set.seed(1)
n_people <- 100
groups <- c("A", "B", "C")
example_table <- data.table(person_id=seq_len(n_people),
group_2010=sample(groups, n_people, TRUE),
group_2011=sample(groups, n_people, TRUE))
## Error-prone and requires lots of typing -- programmatic alternative?
transition_probs <- example_table[, list(pr_A_2011=mean(group_2011=="A"),
pr_B_2011=mean(group_2011=="B"),
pr_C_2011=mean(group_2011=="C")),
by=group_2010]
transition_probs # Essentially a transition matrix giving Pr[group_2011 | group_2010]
# group_2010 pr_A_2011 pr_B_2011 pr_C_2011
# 1: A 0.1481481 0.5185185 0.3333333
# 2: B 0.3684211 0.3947368 0.2368421
# 3: C 0.3142857 0.3142857 0.3714286
当组为 A、B、C 时,上面的 "manual" 方法很好,但如果有更多组(或者如果我们只有 groups
向量但没有提前知道它包含什么)。
在我上面的示例代码中是否有 "data.table way" 来计算 transition_probs 对象? list(pr_A_2011=...) 可以用程序化的东西代替吗?
我担心的是,如果我添加 D 组,我将不得不在多个地方编辑代码,特别是输入 pr_D_2011=mean(group_2011=="D")
。
我愿意
lvls = example_table[, sort(unique(c(group_2010, group_2011))) ]
x = dcast(example_table, group_2010~group_2011)[, N := Reduce(`+`,.SD), .SDcols=lvls]
# group_2010 A B C N
# 1: A 6 9 15 30
# 2: B 15 4 12 31
# 3: C 11 11 17 39
从这里开始,如果您想要转换概率,只需除以 N
:
x[, (lvls) := lapply(.SD,`/`, x$N), .SDcols=lvls]
# or, with data.table 1.9.7+
x[, (lvls) := lapply(.SD,`/`, N), .SDcols=lvls]
# group_2010 A B C N
# 1: A 0.1481481 0.5185185 0.3333333 27
# 2: B 0.3684211 0.3947368 0.2368421 38
# 3: C 0.3142857 0.3142857 0.3714286 35
data.table
的设计是为了与 data.frames
上的操作兼容,因此除非您可以 (a) 证明此操作是一个巨大的瓶颈,并且 (b) 证明替代解决方案明显更快,为什么不坚持简洁明了:
prop.table(table(example_table[,2:3,with=FALSE]),1)
group_2011
group_2010 A B C
A 0.1481481 0.5185185 0.3333333
B 0.3684211 0.3947368 0.2368421
C 0.3142857 0.3142857 0.3714286
我看到当前的两个答案都很好地解决了您的问题。我会回答然后以更通用的方式处理它。
如果您想要真正的编程能力,您可以使用 computing on the language R 语言功能。
R belongs to a class of programming languages in which subroutines have the ability to modify or construct other subroutines and evaluate the result as an integral part of the language itself.
library(data.table)
set.seed(1)
n_people <- 100
groups <- c("A", "B", "C")
example_table <- data.table(person_id=seq_len(n_people),
group_2010=sample(groups, n_people, TRUE),
group_2011=sample(groups, n_people, TRUE))
f = function(data, groups, years) {
stopifnot(is.data.table(data), length(groups) > 0L, length(years) == 2L, paste0("group_", years) %in% names(data))
j.names = sprintf("pr_%s_%s", c(groups), years[2L])
j.vals = lapply(setNames(groups, j.names), function(group) call("mean", call("==", as.name(sprintf("group_%s", years[2L])), group)))
jj = as.call(c(list(as.name(".")), j.vals))
data[, eval(jj), by = c(sprintf("group_%s", years[1L]))]
}
f(example_table, groups, 2010:2011)
# group_2010 pr_A_2011 pr_B_2011 pr_C_2011
#1: A 0.1481481 0.5185185 0.3333333
#2: B 0.3684211 0.3947368 0.2368421
#3: C 0.3142857 0.3142857 0.3714286
少数地方无需替换代码,只需将参数传递给函数即可。
玩具示例:
library(data.table)
set.seed(1)
n_people <- 100
groups <- c("A", "B", "C")
example_table <- data.table(person_id=seq_len(n_people),
group_2010=sample(groups, n_people, TRUE),
group_2011=sample(groups, n_people, TRUE))
## Error-prone and requires lots of typing -- programmatic alternative?
transition_probs <- example_table[, list(pr_A_2011=mean(group_2011=="A"),
pr_B_2011=mean(group_2011=="B"),
pr_C_2011=mean(group_2011=="C")),
by=group_2010]
transition_probs # Essentially a transition matrix giving Pr[group_2011 | group_2010]
# group_2010 pr_A_2011 pr_B_2011 pr_C_2011
# 1: A 0.1481481 0.5185185 0.3333333
# 2: B 0.3684211 0.3947368 0.2368421
# 3: C 0.3142857 0.3142857 0.3714286
当组为 A、B、C 时,上面的 "manual" 方法很好,但如果有更多组(或者如果我们只有 groups
向量但没有提前知道它包含什么)。
在我上面的示例代码中是否有 "data.table way" 来计算 transition_probs 对象? list(pr_A_2011=...) 可以用程序化的东西代替吗?
我担心的是,如果我添加 D 组,我将不得不在多个地方编辑代码,特别是输入 pr_D_2011=mean(group_2011=="D")
。
我愿意
lvls = example_table[, sort(unique(c(group_2010, group_2011))) ]
x = dcast(example_table, group_2010~group_2011)[, N := Reduce(`+`,.SD), .SDcols=lvls]
# group_2010 A B C N
# 1: A 6 9 15 30
# 2: B 15 4 12 31
# 3: C 11 11 17 39
从这里开始,如果您想要转换概率,只需除以 N
:
x[, (lvls) := lapply(.SD,`/`, x$N), .SDcols=lvls]
# or, with data.table 1.9.7+
x[, (lvls) := lapply(.SD,`/`, N), .SDcols=lvls]
# group_2010 A B C N
# 1: A 0.1481481 0.5185185 0.3333333 27
# 2: B 0.3684211 0.3947368 0.2368421 38
# 3: C 0.3142857 0.3142857 0.3714286 35
data.table
的设计是为了与 data.frames
上的操作兼容,因此除非您可以 (a) 证明此操作是一个巨大的瓶颈,并且 (b) 证明替代解决方案明显更快,为什么不坚持简洁明了:
prop.table(table(example_table[,2:3,with=FALSE]),1)
group_2011 group_2010 A B C A 0.1481481 0.5185185 0.3333333 B 0.3684211 0.3947368 0.2368421 C 0.3142857 0.3142857 0.3714286
我看到当前的两个答案都很好地解决了您的问题。我会回答然后以更通用的方式处理它。
如果您想要真正的编程能力,您可以使用 computing on the language R 语言功能。
R belongs to a class of programming languages in which subroutines have the ability to modify or construct other subroutines and evaluate the result as an integral part of the language itself.
library(data.table)
set.seed(1)
n_people <- 100
groups <- c("A", "B", "C")
example_table <- data.table(person_id=seq_len(n_people),
group_2010=sample(groups, n_people, TRUE),
group_2011=sample(groups, n_people, TRUE))
f = function(data, groups, years) {
stopifnot(is.data.table(data), length(groups) > 0L, length(years) == 2L, paste0("group_", years) %in% names(data))
j.names = sprintf("pr_%s_%s", c(groups), years[2L])
j.vals = lapply(setNames(groups, j.names), function(group) call("mean", call("==", as.name(sprintf("group_%s", years[2L])), group)))
jj = as.call(c(list(as.name(".")), j.vals))
data[, eval(jj), by = c(sprintf("group_%s", years[1L]))]
}
f(example_table, groups, 2010:2011)
# group_2010 pr_A_2011 pr_B_2011 pr_C_2011
#1: A 0.1481481 0.5185185 0.3333333
#2: B 0.3684211 0.3947368 0.2368421
#3: C 0.3142857 0.3142857 0.3714286
少数地方无需替换代码,只需将参数传递给函数即可。