【问题标题】:Split string column to create new binary columns拆分字符串列以创建新的二进制列
【发布时间】:2015-02-22 04:46:47
【问题描述】:

我的数据有一列,我正在尝试使用行中每个“/”之后的内容创建其他列。以下是数据的前几行:

> dput(mydata)
structure(list(ALL = structure(c(1L, 4L, 4L, 3L, 2L), .Label = c("/
ca/put/sent_1/fe.gr/eq2_on/eq2_off",
"/ca/put/sent_1/fe.gr/eq2_on/eq2_off/cbr_LBL", "/ca/put/sent_1/fe.g
r/eq2_on/eq2_off/cni_at.p3x.4",
"/ca/put/sent_1/fe.gr/eq2_on/eq2_off/hi.on/hi.ov"), class = "factor
")), .Names = "ALL", class = "data.frame", row.names = c(NA,
-5L))

结果应如下所示(数据框),如果变量出现在行中,则新列中为“1”,否则为“0”:

> dput(Result)
structure(list(ALL = structure(c(1L, 4L, 5L, 3L, 2L), .Label = c("/ca
/put/sent_1/fe.gr/eq2_on/eq2_off",
"/ca/put/sent_1/fe.gr/eq2_on/eq2_off/cbr_LBL", "/ca/put/sent_1/fe.gr/
eq2_on/eq2_off/cni_at.p3x.4",
"/ca/put/sent_1/fe.gr/eq2_on/eq2_off/hi.on/hi.ov", "/ca/put/sent_1fe.
gr/eq2_on/eq2_off/hi.on/hi.ov"
), class = "factor"), ca = c(1L, 1L, 1L, 1L, 1L), put = c(1L,
1L, 1L, 1L, 1L), sent_1 = c(1L, 1L, 1L, 1L, 1L), fe.gr = c(1L,
1L, 1L, 1L, 1L), eq2_on = c(1L, 1L, 1L, 1L, 1L), eq2_off = c(1L,
1L, 1L, 1L, 1L), hi.on = c(0L, 1L, 1L, 0L, 0L), hi.ov = c(0L,
1L, 1L, 0L, 0L), cni_at.p3x.4 = c(0L, 0L, 0L, 1L, 0L), cbr_LBL = c(0L
,
0L, 0L, 0L, 1L)), .Names = c("ALL", "ca", "put", "sent_1", "fe.gr",
"eq2_on", "eq2_off", "hi.on", "hi.ov", "cni_at.p3x.4", "cbr_LBL"
), class = "data.frame", row.names = c(NA, -5L))

我尝试了很多功能,包括 strsplit 和 sapply:

sapply(strsplit(as.character(mydata$ALL), “\\/”), “[[“, 2) #returns "ca"s only

sapply(strsplit(as.character(mydata$ALL), "\\/"), "[[", 3) #returns "put"s only

有数百万行,我非常感谢任何快速高效的东西。

【问题讨论】:

    标签: r split vectorization


    【解决方案1】:

    使用我维护的 qdapTools 包中的mtabuate:

    library(qdapTools)
    mtabulate(strsplit(as.character(dat[[1]]), "/"))
    
    ##   V1 ca cbr_LBL cni_at.p3x.4 eq2_off eq2_on fe.gr hi.on hi.ov put sent_1 sent_1fe.gr
    ## 1  1  1       0            0       1      1     1     0     0   1      1           0
    ## 2  1  1       0            0       1      1     1     1     1   1      1           0
    ## 3  1  1       0            0       1      1     0     1     1   1      0           1
    ## 4  1  1       0            1       1      1     1     0     0   1      1           0
    ## 5  1  1       1            0       1      1     1     0     0   1      1           0
    

    【讨论】:

    • 更多测试表明这个答案是最快的。谢谢泰勒!
    • 我不明白你的意思。它们已经是整数值或数字。这通常是虚拟变量。你有替代 0/1 的替代品吗?
    【解决方案2】:

    您可以使用我的“splitstackshape”包中的cSplit_e:

    library(splitstackshape)
    cSplit_e(mydata, "ALL", "/", type = "character", fill = 0)
    #                                                ALL ALL_ca ALL_cbr_LBL
    # 1              /ca/put/sent_1/fe.gr/eq2_on/eq2_off      1           0
    # 2  /ca/put/sent_1/fe.gr/eq2_on/eq2_off/hi.on/hi.ov      1           0
    # 3   /ca/put/sent_1fe.gr/eq2_on/eq2_off/hi.on/hi.ov      1           0
    # 4 /ca/put/sent_1/fe.gr/eq2_on/eq2_off/cni_at.p3x.4      1           0
    # 5      /ca/put/sent_1/fe.gr/eq2_on/eq2_off/cbr_LBL      1           1
    #   ALL_cni_at.p3x.4 ALL_eq2_off ALL_eq2_on ALL_fe.gr ALL_hi.on ALL_hi.ov ALL_put
    # 1                0           1          1         1         0         0       1
    # 2                0           1          1         1         1         1       1
    # 3                0           1          1         0         1         1       1
    # 4                1           1          1         1         0         0       1
    # 5                0           1          1         1         0         0       1
    #   ALL_sent_1 ALL_sent_1fe.gr
    # 1          1               0
    # 2          1               0
    # 3          0               1
    # 4          1               0
    # 5          1               0
    

    (注意:我认为dput 的第 3 行存在问题,这就是它与您想要的输出不匹配的原因。请注意,第 3 行中的第三项是“sent_1fe.gr”,没有“/”之间。)

    【讨论】:

    • 谢谢,现在添加了“/”。 “ALL_”出现在新列的所有标题中。有没有办法防止将其粘贴到每个变量?
    • @bma92,不。这是设计的,以防用户拆分多个列。不过,删除它应该不会太难——只需使用“data.table”中的setnames 并执行setnames(out, gsub("ALL_", "", names(out)) 之类的操作(假设您将输出存储为“out”)。然后,这将重命名这些列而不复制数据,因此建议在处理大型数据集时使用。
    【解决方案3】:

    这样的事情怎么样

    spt <- strsplit(as.character(mydata$ALL),"/", fixed=T)
    do.call(rbind, lapply(lapply(spt, factor, levels=unique(unlist(spt))), table))
    

    返回

           ca put sent_1 fe.gr eq2_on eq2_off hi.on hi.ov sent_1fe.gr cni_at.p3x.4 cbr_LBL
    [1,] 1  1   1      1     1      1       1     0     0           0            0       0
    [2,] 1  1   1      1     1      1       1     1     1           0            0       0
    [3,] 1  1   1      1     0      1       1     1     1           1            0       0
    [4,] 1  1   1      1     1      1       1     0     0           0            1       0
    [5,] 1  1   1      1     1      1       1     0     0           0            0       1
    

    【讨论】:

    • 这会返回一个列表。它需要返回一个数据框。还有什么更快,使用这种或矢量化的方法?
    • 对我来说它返回一个矩阵,如果你需要一个data.frame,请使用as.data.frame()。如果您想知道速度,您将不得不在您自己的计算机上使用您自己的数据进行测试。
    • as.data.frame(spt) 返回“data.frame 中的错误(c(””、“ca”、“put”、“sent_1”、“fe.gr”、“eq2_on” , "eq2_off" : 参数暗示不同的行数:7, 9, 8
    • 你必须对do.call的结果执行 as.data.frame()
    【解决方案4】:

    其他选项是将melt list 中的split 字符串转换为long 形式,然后使用table

    library(reshape2)
    as.data.frame.matrix(table(melt(strsplit(as.character(
                                       mydata[[1]]), "/"))[2:1]))[,-1]
    #    ca eq2_off eq2_on fe.gr put sent_1 hi.on hi.ov sent_1fe.gr cni_at.p3x.4
    #1  1       1      1     1   1      1     0     0           0            0
    #2  1       1      1     1   1      1     1     1           0            0
    #3  1       1      1     0   1      0     1     1           1            0
    #4  1       1      1     1   1      1     0     0           0            1
    #5  1       1      1     1   1      1     0     0           0            0
    #  cbr_LBL
    #1       0
    #2       0
    #3       0
    #4       0
    #5       1
    

    【讨论】:

      【解决方案5】:

      tidyverse 解决方案

      library(tidyverse)
      
      mydata %>%
        rownames_to_column() %>%
        mutate(key = strsplit(levels(ALL)[ALL],"/"),value=1) %>%
        unnest %>%
        spread(key,value,0) %>%
        select(-rowname)
      
      #   ALL   ca cbr_LBL cni_at.p3x.4 eq2_off eq2_on fe.gr hi.on hi.ov put sent_1
      # 1   1 1  1       0            0       1      1     1     0     0   1      1
      # 2   4 1  1       0            0       1      1     1     1     1   1      1
      # 3   4 1  1       0            0       1      1     1     1     1   1      1
      # 4   3 1  1       0            1       1      1     1     0     0   1      1
      # 5   2 1  1       1            0       1      1     1     0     0   1      1
      

      数据

      mydata <- structure(list(ALL = structure(c(1L, 4L, 4L, 3L, 2L), .Label = c(
        "/ca/put/sent_1/fe.gr/eq2_on/eq2_off",
        "/ca/put/sent_1/fe.gr/eq2_on/eq2_off/cbr_LBL",
        "/ca/put/sent_1/fe.gr/eq2_on/eq2_off/cni_at.p3x.4",
        "/ca/put/sent_1/fe.gr/eq2_on/eq2_off/hi.on/hi.ov"), class = "factor
        ")), .Names = "ALL", class = "data.frame", row.names = c(NA,-5L))
      

      【讨论】:

        【解决方案6】:

        这是一个使用 dplyr 和 tidyr 的解决方案(注意:我清理了似乎是 您的样本数据的第三行中缺少 /):

        ## Input
        
        input <- structure(
          list(ALL = structure(c(1L, 4L, 5L, 3L, 2L),
               .Label = c("/ca/put/sent_1/fe.gr/eq2_on/eq2_off",
                          "/ca/put/sent_1/fe.gr/eq2_on/eq2_off/cbr_LBL",
                          "/ca/put/sent_1/fe.gr/eq2_on/eq2_off/cni_at.p3x.4",
                          "/ca/put/sent_1/fe.gr/eq2_on/eq2_off/hi.on/hi.ov",
                          "/ca/put/sent_1/fe.gr/eq2_on/eq2_off/hi.on/hi.ov"),
               class = "factor")),
          .Names = "ALL", class = "data.frame", row.names = c(NA, -5L))
        
        
        ## Solution
        
        require(dplyr)
        require(tidyr)
        solution <- input  %>%
          mutate(temp = sub("^/", "", ALL)) %>%
          separate(temp,
                   c("ca", "put", "sent_1", "fe.gr", "eq2_on", "eq2_off",
                     "hi.on", "hi.ov", "cni_at.p3x.4", "cbr_LBL"),
                   "/", extra="merge") %>%
          mutate_each(funs(as.numeric(!is.na(.))), -ALL)
        

        【讨论】:

        • mutate_each 已弃用,mutate_at(vars(ALL), ...) 可能是您想要的。
        【解决方案7】:

        我自己遇到了类似但更复杂的问题,我想出了以下功能方式,该方式允许以“一种热”方式单独转换列的字符串分隔字符值以及分类变量本身:

        oneHotOnText <- function(datatable, columns, seperator=", "){ #argument columns is  character vector or numeric vector
          if(! "data.table" %in% .packages()) if(!require(data.table)) { install.packages("data.table"); library(data.table) }
          if(! "data.table" %in% class(datatable)) TempDT <- as.data.table(datatable) else TempDT <- copy(datatable)
          for(i in TempDT[, columns, with = F]){
            if(class(i) != "character") i <- as.character(i)
            uniqueValues <- unique(unlist(strsplit(unique(i), split=seperator)))
            if(any(uniqueValues %in% names(TempDT))) { print("Value/s of the selected column/s is/are present as variables name/s. Rename it/them.")
                                                     rm(TempDT)
                                                     break }
            for(j in uniqueValues) TempDT[, (j) := ifelse(grepl(j, i), 1L, 0L)]
          }
          if(exists("TempDT")) return(TempDT)
        }
        
        DF = data.frame(
          aColumn=rep(c("f", "b", "c"), 100000),
          xColumn=rep(c("N/W", "W", "R"), 100000), 
          yColumn=rep(c("A/B", "A/V", "B/G"), 100000),
          zColumn=rep(20:22, 100000))
        
        str(DF) #factors are present in the data.frame
        
        oneHotOnText(DF, columns = c("aColumn", "xColumn", "yColumn"), seperator="/")[] #applies the function, returns a data.table and prints the result
        
        #        aColumn xColumn yColumn zColumn f b c N W R A B V G
        #     1:       f     N/W     A/B      20 1 0 0 1 1 0 1 1 0 0
        #     2:       b       W     A/V      21 0 1 0 0 1 0 1 0 1 0
        #     3:       c       R     B/G      22 0 0 1 0 0 1 0 1 0 1
        #     4:       f     N/W     A/B      20 1 0 0 1 1 0 1 1 0 0
        #     5:       b       W     A/V      21 0 1 0 0 1 0 1 0 1 0
        #    ---                                                    
        #299996:       b       W     A/V      21 0 1 0 0 1 0 1 0 1 0
        #299997:       c       R     B/G      22 0 0 1 0 0 1 0 1 0 1
        #299998:       f     N/W     A/B      20 1 0 0 1 1 0 1 1 0 0
        #299999:       b       W     A/V      21 0 1 0 0 1 0 1 0 1 0
        #300000:       c       R     B/G      22 0 0 1 0 0 1 0 1 0 1
        

        同样适用于 OP 的问题:

        input <- data.frame(ALL = c("/ca/put/sent_1/fe.gr/eq2_on/eq2_off",
                                    "/ca/put/sent_1/fe.gr/eq2_on/eq2_off/hi.on/hi.ov",
                                    "/ca/put/sent_1/fe.gr/eq2_on/eq2_off/hi.on/hi.ov",
                                    "/ca/put/sent_1/fe.gr/eq2_on/eq2_off/cni_at.p3x.4",
                                    "/ca/put/sent_1/fe.gr/eq2_on/eq2_off/cbr_LBL"
                                    ))
        
        oneHotOnText(input, columns = "ALL", seperator = "/")[] 
        

        【讨论】:

          【解决方案8】:

          这里还有一个tidyverse 解决方案

          library(tidyverse)
          
          nn <- max(str_count(df$ALL, "/"), na.rm = T)+1
          
          df %>% mutate(row_id =row_number()) %>%
            select(row_id, everything()) %>%
            separate(ALL, into = paste("tag", 1:nn), sep = "/") %>%
            pivot_longer(cols = paste("tag", 2:nn), names_to = "name", values_to = "val") %>%
            filter(!is.na(val)) %>%
            select(-`tag 1`) %>%
            mutate(new=1) %>%
            #group_by(row_id) %>%
            pivot_wider(id_cols = row_id, names_from = val, values_from = new, values_fill =0)
          
          
          # A tibble: 5 x 11
            row_id    ca   put sent_1 fe.gr eq2_on eq2_off hi.on hi.ov cni_at.p3x.4
             <int> <dbl> <dbl>  <dbl> <dbl>  <dbl>   <dbl> <dbl> <dbl>        <dbl>
          1      1     1     1      1     1      1       1     0     0            0
          2      2     1     1      1     1      1       1     1     1            0
          3      3     1     1      1     1      1       1     1     1            0
          4      4     1     1      1     1      1       1     0     0            1
          5      5     1     1      1     1      1       1     0     0            0
          # ... with 1 more variable: cbr_LBL <dbl>
          

          【讨论】:

            【解决方案9】:

            对于多列用逗号分隔的多项目热编码:

            library(qdapTools)
            
            #hot-encoding 
            mtabulate(apply(dataset[3451:3457], 1, function(x) strsplit(x, ",")))
            
            cbind(dataset, mtabulate(strsplit(mydf$Info, ", ")))
            

            【讨论】:

              猜你喜欢
              • 2022-11-06
              • 2021-11-18
              • 1970-01-01
              • 2015-08-07
              • 2022-08-10
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2023-03-30
              相关资源
              最近更新 更多