如何基于分离包含键值对的值来有效地派生新变量?

时间:2016-10-05 09:53:38

标签: r performance tidyr

问题

如何根据包含键值对的数据框的值(例如ensembl_end_phase=-1)有效地派生新变量?

变量的名称应该是键,内容应该是值。我寻找像tidyr::separate这样的东西,其中into: Names of new variables to create as character vector向量是根据观察到的密钥动态创建的。

应用程序:从基因注释文件中获取整洁的数据框

我想为遗传分析中使用的GTF/GFF3 gene files提供一个整洁的R数据框。

以下是此类文件(制表符分隔符)的示例:

seqname source  feature start   end score   strand  frame   attribute
1   havana  three   191356960   191356995   .   -   .   Parent=transcript:ENSMUST00000191925
1   havana  exon    191357792   191357851   .   -   .   Parent=transcript:ENSMUST00000191925;Name=ENSMUSE00001336486;constitutive=0;ensembl_end_phase=-1;ensembl_phase=-1;exon_id=ENSMUSE00001336486;rank=4;version=1
1   havana  three   191357792   191357851   .   -   .   Parent=transcript:ENSMUST00000191925
1   havana  three   191358925   191359076   .   -   .   Parent=transcript:ENSMUST00000191925
15  ensembl CDS__   98186839    98187790    .   -   1   ID=CDS:ENSMUSP00000132237;Parent=transcript:ENSMUST00000165379;protein_id=ENSMUSP00000132237

由于这些文件很容易包含数百万行,因此有效的方法至关重要。

使用read_delim包中的readr可以有效地读取文件。现在,我很难根据attribute列中的键值对创建变量。

我通常的方法是应用tidyr::separate这样:

annotation %>% separate(attribute, into, sep = ";")

这会失败,因为行之间的键值对不同。因此,必须为每一行动态创建 into 向量。由于密钥在不同的GTF / GFF文件源之间也没有标准化,因此在处理密钥名称之前无法知道密钥名称。

我想做tidyr::separate之类的事情,但是使用从值的第一个单词(键)派生的变量名,这是通过将“em>属性分离在”;“。< / p>

你能否告诉我如何做到这一点?使用for循环单独解析所有值 way 太慢。

2 个答案:

答案 0 :(得分:1)

你可以尝试

library(tidyr)
lst <- lapply(strsplit(df$attribute, ";", T), strsplit, "=", T)
df$att <- lapply(lst, function(x) setNames(do.call(rbind.data.frame, x), c("key", "value")) )
df <- unnest(df)

str(df)
# 'data.frame': 14 obs. of  11 variables:
#  $ seqname  : int  1 1 1 1 1 1 1 1 1 1 ...
#  $ source   : chr  "havana" "havana" "havana" "havana" ...
#  $ feature  : chr  "three" "exon" "exon" "exon" ...
#  $ start    : int  191356960 191357792 191357792 191357792 191357792 191357792 191357792 191357792 191357792 191357792 ...
#  $ end      : int  191356995 191357851 191357851 191357851 191357851 191357851 191357851 191357851 191357851 191357851 ...
#  $ score    : chr  "." "." "." "." ...
#  $ strand   : chr  "-" "-" "-" "-" ...
#  $ frame    : chr  "." "." "." "." ...
#  $ attribute: chr  "Parent=transcript:ENSMUST00000191925" "Parent=transcript:ENSMUST00000191925;Name=ENSMUSE00001336486;constitutive=0;ensembl_end_phase=-1;ensembl_phase=-1;exon_id=ENSMU"| __truncated__ "Parent=transcript:ENSMUST00000191925;Name=ENSMUSE00001336486;constitutive=0;ensembl_end_phase=-1;ensembl_phase=-1;exon_id=ENSMU"| __truncated__ "Parent=transcript:ENSMUST00000191925;Name=ENSMUSE00001336486;constitutive=0;ensembl_end_phase=-1;ensembl_phase=-1;exon_id=ENSMU"| __truncated__ ...
#  $ key      : chr  "Parent" "Parent" "Name" "constitutive" ...
#  $ value    : chr  "transcript:ENSMUST00000191925" "transcript:ENSMUST00000191925" "ENSMUSE00001336486" "0" ...

答案 1 :(得分:1)

您可以使用splitstackshape包,

library(splitstackshape)
fun1 <- function(x){
  dd1 <- cSplit(x, 'attribute', sep = ';', 'long')
  dd2 <- cSplit(dd1, 'attribute', sep = '=', 'wide', drop = FALSE)
  return(dd2)
}

fun1(df)

比较system.time

df1 <- df[rep(row.names(df), 100000),]

fun_lukeA <- function(x, var) {
  lst <- lapply(strsplit(var, ";", T), strsplit, "=", T)
  x$att <- lapply(lst, function(x) setNames(do.call(rbind.data.frame, x), c("key", "value")))
  unnest(x)
}

system.time(fun_lukeA(df1, df1$attribute))
#   user  system elapsed 
# 296.89    0.36  298.15 

system.time(fun1(df1))
#   user  system elapsed 
#   9.16    0.00    9.20