在dplyr链中创建指标变量列

时间:2016-03-11 15:02:01

标签: r dplyr tidyr

已更新:对于回复的人道歉,在我的原始示例中,我忽略了data.frame()创建var作为因素而不是字符向量的事实,我曾打算过。我已经纠正了这个例子,这至少会打破其中一个答案。

- 原 -

我有一个数据框,我正在执行一系列 dplyr tidyr 操作,我想为要编码的指标变量添加列为0或1,并在dplyr链中执行 。因子的每个级别(当前存储为字符向量)应该在单独的列中编码,并且列名称是具有可变级别的固定前缀的串联,例如, var的级别为 a ,新列var_a将为1,var_a的所有其他行将为0。

使用基数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)
}

请注意,实际数据集包含多个列,创建指示符变量时不应更改或删除任何列,但列var除外,可以转换为类型 factor < / em>的

4 个答案:

答案 0 :(得分:5)

它不漂亮,但这个功能应该有效

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"))

答案 1 :(得分:3)

函数作为dplyr管道的一部分的唯一要求是它将数据帧作为输入,并返回数据帧作为输出。因此,利用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 %>% ...

答案 2 :(得分:2)

虽然确实需要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)))))

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中提取数据,因为它存在于调用环境中,而不是在更大的链中存在,并且假设var在传递的任何内容中都保持不变。除了dplyr正常的NSE之外,引用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

和字符串数据:

import java.util.Scanner;

public class NestedTree
{
    public static void main(String[] args)
    {
        Scanner scan = new Scanner(System.in);
        System.out.println("Enter the size of the tree you would like");

        int size = scan.nextInt(); // Get the size of the tree

        for (int i = 0; i < size; i++) {
            int spaces = size - i;

            for (int s = 0; s < spaces; s++) { // Print spaces
                System.out.print(" ");
            }

            for (int r = 0; r <= i; r++) { // Print stars
                System.out.print("* ");
            }

            System.out.print("\n"); // new line
        }
    }
}

对于小数据而言无关紧要,但对于更大的数据,可能值得采用并发症。

答案 3 :(得分:0)

我之所以参加此问答,是因为我真的想将model.matrix放在magrittr管道工作流程中,或者只用tidyverse函数(对不起,baseRs)产生等效的输出。

后来,我登陆this solution,它优雅地使用了我可能的功能(但我不是一个人想):

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)

因此,我要添加链接解决方案的更新/修改版本,这样首先来到这里的人们就不必继续寻找(就像我一样)。