【问题标题】:Dictionary style replace multiple items字典样式替换多个项目
【发布时间】:2011-11-24 17:52:33
【问题描述】:

我有一个大的字符数据数据框架,我想根据其他语言中通常称为字典的内容进行转换。

目前我是这样处理的:

foo <- data.frame(snp1 = c("AA", "AG", "AA", "AA"), snp2 = c("AA", "AT", "AG", "AA"), snp3 = c(NA, "GG", "GG", "GC"), stringsAsFactors=FALSE)
foo <- replace(foo, foo == "AA", "0101")
foo <- replace(foo, foo == "AC", "0102")
foo <- replace(foo, foo == "AG", "0103")

这很好用,但每次我想替换 data.frame 中的一项时,重复 replace 语句显然不漂亮而且似乎很愚蠢。

我有一个包含大约 25 个键/值对的字典,有没有更好的方法来做到这一点?

【问题讨论】:

标签: r dataframe bioinformatics


【解决方案1】:

这里有一些简单的东西可以完成这项工作:

key <- c('AA','AC','AG')
val <- c('0101','0102','0103')

lapply(1:3,FUN = function(i){foo[foo == key[i]] <<- val[i]})
foo

 snp1 snp2 snp3
1 0101 0101 <NA>
2 0103   AT   GG
3 0101 0103   GG
4 0101 0101   GC

lapply 在这种情况下会输出一个我们实际上并不关心的列表。如果您愿意,您可以将结果分配给某些东西,然后丢弃它。我在这里迭代索引,但您可以轻松地将键/值放在列表中并直接迭代它们。注意&lt;&lt;- 使用全局赋值。

我用mapply 修改了一种方法来做到这一点,但我的第一次尝试没有奏效,所以我换了。不过,我怀疑mapply 的解决方案是可能的。

【讨论】:

  • 我不建议使用全局赋值运算符&lt;&lt;-
  • @Ramnath 同意,&lt;&lt;- 可能有风险,但本质上并不坏。
  • 这是唯一可以处理原始键为 0:2 且任务是转换为等效字符值的变体的答案。投票最高的答案失败,因为 0 不是可接受的索引。 Ramnaths 和 c.gutierrez 的答案在我手中也失败了。 (我没有测试所有答案。)这是问题的链接:stackoverflow.com/questions/49504035/…
【解决方案2】:

这是一个快速的解决方案

dict = list(AA = '0101', AC = '0102', AG = '0103')
foo2 = foo
for (i in 1:3){foo2 <- replace(foo2, foo2 == names(dict[i]), dict[i])}

【讨论】:

  • 我喜欢这个答案,因为它将键和值保持在一起。将键和值放在单独的字符向量中意味着如果您将其中一个向量的顺序弄错了,您的字典会默默地错误标记所有顺序不正确的条目。
  • 我建议的唯一区别是在第 3 行使用 R 的矢量化符号,例如: sapply(1:3, function(i) replace(foo2, foo2 == names(dict[i]) , dict[i]))
  • *apply 函数与矢量化函数不同。
【解决方案3】:
map = setNames(c("0101", "0102", "0103"), c("AA", "AC", "AG"))
foo[] <- map[unlist(foo)]

假设map 涵盖foo 中的所有情况。如果foo 是一个矩阵(字符()),那么这将不像“黑客”并且在空间和时间上都更有效,那么

matrix(map[foo], nrow=nrow(foo), dimnames=dimnames(foo))

当存在数百万个 SNP 和数千个样本时,矩阵和数据框变体都违反了 R 的 2^31-1 向量大小限制。

【讨论】:

  • 仅供参考 - 如果您使用 tidyverse 并将 foo 作为 tibble,则必须在分配 map[unlist(foo)] 之前将其强制转换为 data.frame,否则分配的行数与现有数据会有所不同。
【解决方案4】:

如果您愿意使用软件包,plyr 是一个非常受欢迎的软件包,并且有这个方便的 mapvalues() 函数可以满足您的需求:

foo <- mapvalues(foo, from=c("AA", "AC", "AG"), to=c("0101", "0102", "0103"))

请注意,它适用于各种数据类型,而不仅仅是字符串。

【讨论】:

  • 不幸的是,这会在 plyr::mapvalues(foo, from = c("AA", "AC", "AG"), to = c("0101", : x 必须是原子向量。 这也记录在 ?mapvalues 中。
  • 这绝对好用!谢谢 c.gutierrez。
【解决方案5】:

上面使用了@Ramnath 的答案,但从文件中读取(要替换的内容和要替换的内容)并使用 gsub 而不是替换。

hrw <- read.csv("hgWords.txt", header=T, stringsAsFactor=FALSE, encoding="UTF-8", sep="\t") 

for (i in nrow(hrw)) 
{
document <- gsub(hrw$from[i], hrw$to[i], document, ignore.case=TRUE)
}

hgword.txt 包含以下制表符分隔

"from"  "to"
"AA"    "0101"
"AC"    "0102"
"AG"    "0103" 

【讨论】:

  • 看起来对我来说是最好的选择,但由于某种原因我无法让它发挥作用。输出没什么意义。
【解决方案6】:

注意此答案最初是为了解决How to replace all values in data frame with a vector of values? 中发布的更简单的问题。不幸的是,这个问题作为实际问题的重复而被关闭。因此,我将尝试在这两种情况下基于替换因子水平提出解决方案。


如果只有一个向量(或一个数据框列) 其值需要替换并且没有反对使用因子我们可以将向量强制转换为因子并根据需要更改因子水平:

x <- c(1, 1, 4, 4, 5, 5, 1, 1, 2)
x <- factor(x)
x
#[1] 1 1 4 4 5 5 1 1 2
#Levels: 1 2 4 5
replacement_vec <- c("A", "T", "C", "G")
levels(x) <- replacement_vec
x
#[1] A A C C G G A A T
#Levels: A T C G

使用forcatspackage 可以一次性完成:

x <- c(1, 1, 4, 4, 5, 5, 1, 1, 2)
forcats::lvls_revalue(factor(x), replacement_vec)
#[1] A A C C G G A A T
#Levels: A T C G

如果需要替换数据框多列的所有值,可以扩展该方法。

foo <- data.frame(snp1 = c("AA", "AG", "AA", "AA"), 
                  snp2 = c("AA", "AT", "AG", "AA"), 
                  snp3 = c(NA, "GG", "GG", "GC"), 
                  stringsAsFactors=FALSE)

level_vec <- c("AA", "AC", "AG", "AT", "GC", "GG")
replacement_vec <- c("0101", "0102", "0103", "0104", "0302", "0303")
foo[] <- lapply(foo, function(x) forcats::lvls_revalue(factor(x, levels = level_vec), 
                                                       replacement_vec))
foo
#  snp1 snp2 snp3
#1 0101 0101 <NA>
#2 0103 0104 0303
#3 0101 0103 0303
#4 0101 0101 0302

注意level_vecreplacement_vec 的长度必须相等。

更重要的是,level_vec 应该是 complete ,即在原始数据框的受影响列中包含所有可能的值。 (使用unique(sort(unlist(foo))) 进行验证)。否则,任何缺失值都将被强制转换为&lt;NA&gt;。请注意,这也是Martin Morgans's answer 的要求。

因此,如果只有几个不同的值需要替换,那么使用其他答案之一可能会更好,例如,Ramnath's

【讨论】:

    【解决方案7】:

    自从上次回答以来已经有几年了,今晚出现了一个关于这个主题的新问题并且版主关闭了它,我将在此处添加它。发帖人有一个大数据框,包含0、1、2,想把它们分别改成AA、AB、BB。

    使用plyr:

    > df <- data.frame(matrix(sample(c(NA, c("0","1","2")), 100, replace = TRUE), 10))
    > df
         X1   X2   X3 X4   X5   X6   X7   X8   X9  X10
    1     1    2 <NA>  2    1    2    0    2    0    2
    2     0    2    1  1    2    1    1    0    0    1
    3     1    0    2  2    1    0 <NA>    0    1 <NA>
    4     1    2 <NA>  2    2    2    1    1    0    1
    ... to 10th row
    
    > df[] <- lapply(df, as.character)
    

    使用revalue 在数据框上创建一个函数来替换多个术语:

    > library(plyr)
    > apply(df, 2, function(x) {x <- revalue(x, c("0"="AA","1"="AB","2"="BB")); x})
          X1   X2   X3   X4   X5   X6   X7   X8   X9   X10 
     [1,] "AB" "BB" NA   "BB" "AB" "BB" "AA" "BB" "AA" "BB"
     [2,] "AA" "BB" "AB" "AB" "BB" "AB" "AB" "AA" "AA" "AB"
     [3,] "AB" "AA" "BB" "BB" "AB" "AA" NA   "AA" "AB" NA  
     [4,] "AB" "BB" NA   "BB" "BB" "BB" "AB" "AB" "AA" "AB"
    ... and so on
    

    【讨论】:

    • 看起来你的输入是一个data.frame,你的输出是一个矩阵。不过,我想你可以在最后强制回来。
    【解决方案8】:

    使用 dplyr::recode:

    library(dplyr)
    
    mutate_all(foo, funs(recode(., "AA" = "0101", "AC" = "0102", "AG" = "0103",
                                .default = NA_character_)))
    
    #   snp1 snp2 snp3
    # 1 0101 0101 <NA>
    # 2 0103 <NA> <NA>
    # 3 0101 0103 <NA>
    # 4 0101 0101 <NA>
    

    【讨论】:

      【解决方案9】:

      我们也可以使用dplyr::case_when

      library(dplyr)
      
      foo %>%
         mutate_all(~case_when(. == "AA" ~ "0101", 
                               . == "AC" ~ "0102", 
                               . == "AG" ~ "0103", 
                               TRUE ~ .))
      
      #  snp1 snp2 snp3
      #1 0101 0101 <NA>
      #2 0103   AT   GG
      #3 0101 0103   GG
      #4 0101 0101   GC
      

      检查条件,如果条件为TRUE,则替换为相应的值。如果需要,我们可以添加更多条件,如果没有任何条件匹配,我们可以使用TRUE ~ . 保持值不变。如果我们想将它们更改为NA,我们可以删除最后一行。

      foo %>%
        mutate_all(~case_when(. == "AA" ~ "0101", 
                              . == "AC" ~ "0102", 
                              . == "AG" ~ "0103"))
      
      #  snp1 snp2 snp3
      #1 0101 0101 <NA>
      #2 0103 <NA> <NA>
      #3 0101 0103 <NA>
      #4 0101 0101 <NA>
      

      如果上述条件都不满足,这会将值更改为NA


      另一种仅使用基数 R 的选项是使用旧值和新值创建 lookup 数据框,unlist 数据框,match 使用旧值,获取相应的新值并替换。

      lookup <- data.frame(old_val = c("AA", "AC", "AG"), 
                           new_val = c("0101", "0102", "0103"))
      
      foo[] <- lookup$new_val[match(unlist(foo), lookup$old_val)]
      

      【讨论】:

        【解决方案10】:

        用字典替换字符串或字符串向量中的值最易读的方法之一是stringr::str_replace_all,来自stringr 包。 str_replace_all 需要的模式可以是字典,例如,

        # 1. Made your dictionnary
        dictio_replace= c("AA"= "0101", 
                          "AC"= "0102",
                          "AG"= "0103") # short example of dictionnary.
        
         # 2. Replace all pattern, according to the dictionary-values (only a single vector of string, or a single string)
         foo$snp1 <- stringr::str_replace_all(string = foo$snp1,
                                              pattern= dictio_replace)  # we only use the 'pattern' option here: 'replacement' is useless since we provide a dictionnary.
        

        使用 foo$snp2 和 foo$snp3 重复第 2 步。如果您有更多要转换的向量,最好使用另一个 func',以便替换数据框中每个列/向量中的值而无需重复。

        【讨论】:

          【解决方案11】:

          不要过于原始,但应该提供一个直观的界面来完成替换 Base R 中的多个值:

          # Function performing a mapping replacement:
          # replaceMultipleValues => function() 
          replaceMultipleValues <- function(df, mapFrom, mapTo){
            # Extract the values in the data.frame: 
            # dfVals => named character vector
            dfVals <- unlist(df)
            
            # Get all values in the mapping & data 
            # and assign a name to them: tmp1 => named character vector 
            tmp1 <- c(
              setNames(mapTo, mapFrom), 
              setNames(dfVals, dfVals)
            )
            
            # Extract the unique values: 
            # valueMap => named character vector
            valueMap <- tmp1[!(duplicated(names(tmp1)))]
            
            # Recode the values in data.frame: res => data.frame
            res <- data.frame(
                matrix(
                  valueMap[dfVals], 
                  nrow = nrow(df),
                  ncol = ncol(df),
                  dimnames = dimnames(df)
              )
            )
            
            # Explicitly define the returned object: data.frame => env
            return(res)
          }
          
          # Recode values in data.frame: 
          # res => data.frame
          res <- replaceMultipleValues(
            foo, 
            c("AA", "AC", "AG"), 
            c("0101", "0102", "0103")
          )
          
          # Print data.frame to console: 
          # data.frame => stdout(console)
          res
          

          数据:

          # Import data: foo => data.frame
          foo <- data.frame(snp1 = c("AA", "AG", "AA", "AA"), snp2 = c("AA", "AT", "AG", "AA"), snp3 = c(NA, "GG", "GG", "GC"), stringsAsFactors=FALSE)
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2021-10-14
            • 2020-07-23
            • 2018-05-12
            • 1970-01-01
            • 2021-11-16
            相关资源
            最近更新 更多