在 dplyr 链中创建指标变量列
Creating indicator variable columns in dplyr chain
已更新:向那些回答我的人道歉,在我原来的例子中我忽略了一个事实 data.frame()
创建了 var
作为一个因素而不是作为一个字符向量,正如我所期望的那样。我已经更正了示例,这至少会破坏其中一个答案。
--原创--
我有一个数据框,我正在对其执行一系列 dplyr 和 tidyr 操作,我想添加将被编码为 0 或 1 的指示变量的列,并在 dplyr 链 中执行此操作 。因子的每个级别(目前存储为字符向量)应在单独的列中编码,列名是固定前缀与变量级别的串联,例如var
的级别为 a,新列 var_a
将为 1,而 var_a
的所有其他行将为 0。
下面这个使用 base R 的最小示例产生了我想要的结果(感谢 this blog post),但我想将它全部放入 dplyr 链,并不太清楚该怎么做。
library(dplyr)
df <- data.frame(var = sample(x = letters[1:4], size = 10, replace = TRUE), stringsAsFactors = FALSE)
for(level in unique(df$var)){
df[paste("var", level, sep = "_")] <- ifelse(df$var == level, 1, 0)
}
请注意,真实数据集包含多列,其中 none 应在创建指标变量时更改或删除,但列 var
除外,它可以转换为输入 因子.
不太漂亮,但这个功能应该可以用
dummy <- function(data, col) {
for(c in col) {
idx <- which(names(data)==c)
v <- data[[idx]]
stopifnot(class(v)=="factor")
m <- matrix(0, nrow=nrow(data), ncol=nlevels(v))
m[cbind(seq_along(v), as.integer(v))]<-1
colnames(m) <- paste(c, levels(v), sep="_")
r <- data.frame(m)
if ( idx>1 ) {
r <- cbind(data[1:(idx-1)],r)
}
if ( idx<ncol(data) ) {
r <- cbind(r, data[(idx+1):ncol(data)])
}
data <- r
}
data
}
这是一个示例 data.frame
dd <- data.frame(a=runif(30),
b=sample(letters[1:3],30,replace=T),
c=rnorm(30),
d=sample(letters[10:13],30,replace=T)
)
然后您将要扩展的列指定为字符向量。你可以做到
dd %>% dummy("b")
或
dd %>% dummy(c("b","d"))
一个函数成为 dplyr 管道的一部分的唯一要求是它需要一个数据帧作为输入,returns 一个数据帧作为输出。因此,利用 model.matrix
:
make_inds <- function(df, cols=names(df))
{
# do each variable separately to get around model.matrix dropping aliased columns
do.call(cbind, c(df, lapply(cols, function(n) {
x <- df[[n]]
mm <- model.matrix(~ x - 1)
colnames(mm) <- gsub("^x", paste(n, "_", sep=""), colnames(mm))
mm
})))
}
# insert into pipeline
data %>% ... %>% make_inds %>% ...
尽管确实需要 lapply
,但无需创建函数也是可能的。如果 var
是一个因素,您可以使用它的级别;我们可以将它的列绑定到 lapply
,它在 var
的级别上循环并创建值,用 setNames
命名它们,并将它们转换为 tbl_df
.
df %>% bind_cols(as_data_frame(setNames(lapply(levels(df$var),
function(x){as.integer(df$var == x)}),
paste0('var2_', levels(df$var)))))
returns
Source: local data frame [10 x 5]
var var_d var_c var2_c var2_d
(fctr) (dbl) (dbl) (int) (int)
1 d 1 0 0 1
2 c 0 1 1 0
3 c 0 1 1 0
4 c 0 1 1 0
5 d 1 0 0 1
6 d 1 0 0 1
7 c 0 1 1 0
8 c 0 1 1 0
9 d 1 0 0 1
10 c 0 1 1 0
如果 var
是一个字符向量,而不是一个因素,你可以做同样的事情,但使用 unique
而不是 levels
:
df %>% bind_cols(as_data_frame(setNames(lapply(unique(df$var),
function(x){as.integer(df$var == x)}),
paste0('var2_', unique(df$var)))))
两个注意事项:
- 无论数据类型如何,此方法都适用,但速度较慢。在您的数据足够大的情况下,无论如何将数据存储为
factor
可能是有意义的,因为它包含很多重复的级别。
- 两个版本都从
df$var
中提取数据,因为它存在于调用环境中,而不是因为它可能存在于更大的链中,并且假设 var
在传递的任何内容中都没有变化。就我所见,除了 dplyr
的正常 NSE 之外,引用 var
的动态值是相当痛苦的。
另一种更简单且 factor
不可知的替代方法,使用 reshape2::dcast
:
library(reshape2)
df %>% cbind(1 * !is.na(dcast(df, seq_along(var) ~ var, value.var = 'var')[,-1]))
它仍然从调用环境中提取 df
的版本,所以链实际上只决定了你加入的是什么。因为它使用 cbind
而不是 bind_cols
,结果也将是 data.frame
,而不是 tbl_df
,所以如果你想保留所有 tbl_df
(如果数据很大,则很聪明),您需要将 cbind
替换为 bind_cols(as_data_frame( ... ))
; bind_cols
似乎不想为您进行转换。
但是请注意,虽然此版本更简单,但速度相对较慢,两者都在 factor
数据上:
Unit: microseconds
expr min lq mean median uq max neval
factor 358.889 384.0010 479.5746 427.9685 501.580 3995.951 100
unique 547.249 585.4205 696.4709 633.4215 696.402 4528.099 100
dcast 2265.517 2490.5955 2721.1118 2628.0730 2824.949 3928.796 100
和字符串数据:
Unit: microseconds
expr min lq mean median uq max neval
unique 307.190 336.422 414.1031 362.6485 419.3625 3693.340 100
dcast 2117.807 2249.077 2517.0417 2402.4285 2615.7290 3793.178 100
对于小数据,这无关紧要,但对于更大的数据,可能值得忍受复杂性。
我首先进行此问答是因为我真的想将 model.matrix
放入 magrittr 管道工作流程中,或者仅使用 tidyverse 函数(抱歉,baseRs)生成等效输出。
后来,我找到了 ,它优雅地使用了我 认为 可能实现的功能(但我不是自己想出来的) ):
df <- data_frame(var = sample(x = letters[1:4], size = 10, replace = TRUE))
df %>%
mutate(unique_row_id = 1:n()) %>% #The rows need to be unique for `spread` to work.
mutate(dummy = 1) %>%
spread(var, dummy, fill = 0)
所以,我添加了一个 updated/modified 版本的链接解决方案,这样首先到达这里的人就不必继续寻找(就像我一样)。
已更新:向那些回答我的人道歉,在我原来的例子中我忽略了一个事实 data.frame()
创建了 var
作为一个因素而不是作为一个字符向量,正如我所期望的那样。我已经更正了示例,这至少会破坏其中一个答案。
--原创--
我有一个数据框,我正在对其执行一系列 dplyr 和 tidyr 操作,我想添加将被编码为 0 或 1 的指示变量的列,并在 dplyr 链 中执行此操作 。因子的每个级别(目前存储为字符向量)应在单独的列中编码,列名是固定前缀与变量级别的串联,例如var
的级别为 a,新列 var_a
将为 1,而 var_a
的所有其他行将为 0。
下面这个使用 base R 的最小示例产生了我想要的结果(感谢 this blog post),但我想将它全部放入 dplyr 链,并不太清楚该怎么做。
library(dplyr)
df <- data.frame(var = sample(x = letters[1:4], size = 10, replace = TRUE), stringsAsFactors = FALSE)
for(level in unique(df$var)){
df[paste("var", level, sep = "_")] <- ifelse(df$var == level, 1, 0)
}
请注意,真实数据集包含多列,其中 none 应在创建指标变量时更改或删除,但列 var
除外,它可以转换为输入 因子.
不太漂亮,但这个功能应该可以用
dummy <- function(data, col) {
for(c in col) {
idx <- which(names(data)==c)
v <- data[[idx]]
stopifnot(class(v)=="factor")
m <- matrix(0, nrow=nrow(data), ncol=nlevels(v))
m[cbind(seq_along(v), as.integer(v))]<-1
colnames(m) <- paste(c, levels(v), sep="_")
r <- data.frame(m)
if ( idx>1 ) {
r <- cbind(data[1:(idx-1)],r)
}
if ( idx<ncol(data) ) {
r <- cbind(r, data[(idx+1):ncol(data)])
}
data <- r
}
data
}
这是一个示例 data.frame
dd <- data.frame(a=runif(30),
b=sample(letters[1:3],30,replace=T),
c=rnorm(30),
d=sample(letters[10:13],30,replace=T)
)
然后您将要扩展的列指定为字符向量。你可以做到
dd %>% dummy("b")
或
dd %>% dummy(c("b","d"))
一个函数成为 dplyr 管道的一部分的唯一要求是它需要一个数据帧作为输入,returns 一个数据帧作为输出。因此,利用 model.matrix
:
make_inds <- function(df, cols=names(df))
{
# do each variable separately to get around model.matrix dropping aliased columns
do.call(cbind, c(df, lapply(cols, function(n) {
x <- df[[n]]
mm <- model.matrix(~ x - 1)
colnames(mm) <- gsub("^x", paste(n, "_", sep=""), colnames(mm))
mm
})))
}
# insert into pipeline
data %>% ... %>% make_inds %>% ...
尽管确实需要 lapply
,但无需创建函数也是可能的。如果 var
是一个因素,您可以使用它的级别;我们可以将它的列绑定到 lapply
,它在 var
的级别上循环并创建值,用 setNames
命名它们,并将它们转换为 tbl_df
.
df %>% bind_cols(as_data_frame(setNames(lapply(levels(df$var),
function(x){as.integer(df$var == x)}),
paste0('var2_', levels(df$var)))))
returns
Source: local data frame [10 x 5]
var var_d var_c var2_c var2_d
(fctr) (dbl) (dbl) (int) (int)
1 d 1 0 0 1
2 c 0 1 1 0
3 c 0 1 1 0
4 c 0 1 1 0
5 d 1 0 0 1
6 d 1 0 0 1
7 c 0 1 1 0
8 c 0 1 1 0
9 d 1 0 0 1
10 c 0 1 1 0
如果 var
是一个字符向量,而不是一个因素,你可以做同样的事情,但使用 unique
而不是 levels
:
df %>% bind_cols(as_data_frame(setNames(lapply(unique(df$var),
function(x){as.integer(df$var == x)}),
paste0('var2_', unique(df$var)))))
两个注意事项:
- 无论数据类型如何,此方法都适用,但速度较慢。在您的数据足够大的情况下,无论如何将数据存储为
factor
可能是有意义的,因为它包含很多重复的级别。 - 两个版本都从
df$var
中提取数据,因为它存在于调用环境中,而不是因为它可能存在于更大的链中,并且假设var
在传递的任何内容中都没有变化。就我所见,除了dplyr
的正常 NSE 之外,引用var
的动态值是相当痛苦的。
另一种更简单且 factor
不可知的替代方法,使用 reshape2::dcast
:
library(reshape2)
df %>% cbind(1 * !is.na(dcast(df, seq_along(var) ~ var, value.var = 'var')[,-1]))
它仍然从调用环境中提取 df
的版本,所以链实际上只决定了你加入的是什么。因为它使用 cbind
而不是 bind_cols
,结果也将是 data.frame
,而不是 tbl_df
,所以如果你想保留所有 tbl_df
(如果数据很大,则很聪明),您需要将 cbind
替换为 bind_cols(as_data_frame( ... ))
; bind_cols
似乎不想为您进行转换。
但是请注意,虽然此版本更简单,但速度相对较慢,两者都在 factor
数据上:
Unit: microseconds
expr min lq mean median uq max neval
factor 358.889 384.0010 479.5746 427.9685 501.580 3995.951 100
unique 547.249 585.4205 696.4709 633.4215 696.402 4528.099 100
dcast 2265.517 2490.5955 2721.1118 2628.0730 2824.949 3928.796 100
和字符串数据:
Unit: microseconds
expr min lq mean median uq max neval
unique 307.190 336.422 414.1031 362.6485 419.3625 3693.340 100
dcast 2117.807 2249.077 2517.0417 2402.4285 2615.7290 3793.178 100
对于小数据,这无关紧要,但对于更大的数据,可能值得忍受复杂性。
我首先进行此问答是因为我真的想将 model.matrix
放入 magrittr 管道工作流程中,或者仅使用 tidyverse 函数(抱歉,baseRs)生成等效输出。
后来,我找到了
df <- data_frame(var = sample(x = letters[1:4], size = 10, replace = TRUE))
df %>%
mutate(unique_row_id = 1:n()) %>% #The rows need to be unique for `spread` to work.
mutate(dummy = 1) %>%
spread(var, dummy, fill = 0)
所以,我添加了一个 updated/modified 版本的链接解决方案,这样首先到达这里的人就不必继续寻找(就像我一样)。