dividir colunas de caracteres e obter nomes de campo na string

11

Eu preciso dividir uma coluna que contém informações em várias colunas.
Eu usaria, tstrsplitmas o mesmo tipo de informação não está na mesma ordem entre as linhas e preciso extrair o nome da nova coluna dentro da variável. Importante saber: pode haver muitas informações (campos para se tornarem novas variáveis) e eu não conheço todas elas, por isso não quero uma solução "campo por campo".

Abaixo está um exemplo do que tenho:

library(data.table)

myDT <- structure(list(chr = c("chr1", "chr2", "chr4"), pos = c(123L,
                  435L, 120L), info = c("type=3;end=4", "end=6", "end=5;pos=TRUE;type=2"
                  )), class = c("data.table", "data.frame"), row.names = c(NA,-3L))

#    chr pos                  info
#1: chr1 123          type=3;end=4
#2: chr2 435                 end=6
#3: chr4 120 end=5;pos=TRUE;type=2

E eu gostaria de obter:

#    chr pos end  pos type
#1: chr1 123   4 <NA>    3
#2: chr2 435   6 <NA> <NA>
#3: chr4 120   5 TRUE    2

Uma maneira mais simples de conseguir isso seria muito apreciada! ( Nota: não estou disposto a seguir o caminho dplyr / tidyr )

Cath
fonte

Respostas:

5

Usando regexe os stringipacotes:

setDT(myDT) # After creating data.table from structure()

library(stringi)

fields <- unique(unlist(stri_extract_all(regex = "[a-z]+(?==)", myDT$info)))
patterns <- sprintf("(?<=%s=)[^;]+", fields)
myDT[, (fields) := lapply(patterns, function(x) stri_extract(regex = x, info))]
myDT[, !"info"]

    chr  pos type end
1: chr1 <NA>    3   4
2: chr2 <NA> <NA>   6
3: chr4 TRUE    2   5

Edit: Para obter o tipo correto, parece que (?) type.convert()Pode ser usado:

myDT[, (fields) := lapply(patterns, function(x) type.convert(stri_extract(regex = x, info), as.is = TRUE))]
sindri_baldur
fonte
Recebo um aviso muito longo "Inválido .internal.selfref detectado e corrigido fazendo uma cópia (rasa) do data.table ..."
Moody_Mudskipper
Também digitar e final são de caráter aqui, não tenho certeza se isso é esperado
Moody_Mudskipper
11
@Moody_Mudskipper Obrigado por comentar. (1) (Este aviso é (eu acho) causado pelo data.table sendo criado por structure()Eu atualizei a resposta para evitar esse problema (2) Eles são caracteres de propósito ... Eu senti que analisá-los corretamente seria uma tarefa difícil e uma pergunta em separado.Parece que você resolveu isso na sua resposta e eu darei uma olhada e verei se consigo aprender algo novo #
sindri_baldur 29/11/19
4

Suponho que seus dados são provenientes de um arquivo VCF , se houver, existe uma ferramenta dedicada para esses problemas - o bcftools .

Vamos criar um arquivo VCF de exemplo para teste:

# subset some data from 1000genomes data
tabix -h ftp://ftp-trace.ncbi.nih.gov/1000genomes/ftp/release/20100804/ALL.2of4intersection.20100804.genotypes.vcf.gz 17:1471000-1472000 > myFile.vcf
# zip it and index:
bgzip -c myFile.vcf > myFile.vcf.gz
tabix -p vcf myFile.vcf.gz

Agora podemos usar o bcftools . Aqui, como exemplo, estamos configurando AF e DP da coluna INFO :

bcftools query -f '%CHROM %POS %INFO/AF %INFO/DP \n' myFile.vcf.gz 
17  1471199  1916 0.088
17  1471538  2445 0.016
17  1471611  2733 0.239
17  1471623  2815 0.003
17  1471946  1608 0.007
17  1471959  1612 0.014
17  1471975  1610 0.179

Veja o manual para mais opções de consulta .

zx8754
fonte
3

Poderíamos dividir ";"e remodelar de forma ampla para longa, depois dividir novamente "="e remodelar de novo para longa:

dcast(
  melt(dt[,  paste0("col", 1:3) := tstrsplit(info, split = ";") ],
       id.vars = c("chr", "pos", "info"))[, -c("info", "variable")][
         ,c("x1", "x2") := tstrsplit(value, split = "=")][
           ,value := NULL][ !is.na(x1), ],
  chr + pos ~ x1, value.var = "x2")

#     chr pos end  pos type
# 1: chr1 123   4 <NA>    3
# 2: chr2 435   6 <NA> <NA>
# 3: chr4 120   5 TRUE    2

Uma versão melhorada / mais legível:

dt[, paste0("col", 1:3) := tstrsplit(info, split = ";")
   ][, melt(.SD, id.vars = c("chr", "pos", "info"), na.rm = TRUE)
     ][, -c("info", "variable")
       ][, c("x1", "x2") := tstrsplit(value, split = "=")
         ][, dcast(.SD, chr + pos ~ x1, value.var = "x2")]
zx8754
fonte
@ Jaap Obrigado, eu sabia que havia uma maneira DT melhor de encadear as coisas.
Zx8754
3

Por enquanto, consegui o que quero com o seguinte código:

newDT <- reshape(splitstackshape::cSplit(myDT, "info", sep=";", "long")[, 
                  c(.SD, tstrsplit(info, "="))], 
                 idvar=c("chr", "pos"), direction="wide", timevar="V4", drop="info")
setnames(newDT, sub("V5\\.", "", names(newDT)))

newDT
#    chr pos type end  pos
#1: chr1 123    3   4 <NA>
#2: chr2 435 <NA>   6 <NA>
#3: chr4 120    2   5 TRUE

Duas opções para melhorar as linhas acima, graças a @ A5C1D2H2I1M1N2O1R2T1 (que as forneceu nos comentários):

. com um duplo cSplitantes de dcast:

cSplit(cSplit(myDT, "info", ";", "long"), "info", "=")[, dcast(.SD, chr + pos ~ info_1, value.var = "info_2")]

. com cSplit/ trstrplite em dcastvez de reshape:

cSplit(myDT, "info", ";", "long")[, c("t1", "t2") := tstrsplit(info, "=", fixed = TRUE)][, dcast(.SD, chr + pos ~ t1, value.var = "t2")]
Cath
fonte
11
Eu faria uma dupla cSplit, como este: cSplit(cSplit(myDT, "info", ";", "long"), "info", "=")[, dcast(.SD, chr + pos ~ info_1, value.var = "info_2")].
A5C1D2H2I1M1N2O1R2T1
11
Ou, o mesmo conceito: cSplitseguido de tstrsplit, seguido por dcast: cSplit(myDT, "info", ";", "long")[, c("t1", "t2") := tstrsplit(info, "=", fixed = TRUE)][, dcast(.SD, chr + pos ~ t1, value.var = "t2")].
A5C1D2H2I1M1N2O1R2T1
@ A5C1D2H2I1M1N2O1R2T1 Muito obrigado! Ambos são ótimos, com um especial para a cSplitopção dupla :-)
Cath
2

Aqui está como eu faria isso:

library(data.table)

myDT <- structure(list(chr = c("chr1", "chr2", "chr4"), pos = c(123L,
                                                                435L, 120L), info = c("type=3;end=4", "end=6", "end=5;pos=TRUE;type=2"
                                                                )), class = c("data.table", "data.frame"), row.names = c(NA,-3L))

R_strings <- paste0("list(", chartr(";", ",", myDT$info),")")
lists <- lapply(parse(text=R_strings),eval)
myDT[,info:=NULL]
myDT <- cbind(myDT,rbindlist(lists, fill = TRUE))
myDT
#>     chr pos type end  pos
#> 1: chr1 123    3   4   NA
#> 2: chr2 435   NA   6   NA
#> 3: chr4 120    2   5 TRUE

Criado em 2019-11-29 pelo pacote reprex (v0.3.0)

Moody_Mudskipper
fonte
Não preciso mudar ";" em "" e não gostava de eval(parse(text=...))... mas graças no entanto pela sua resposta
Cath
11
Eu não posso argumentar com gosto pessoal, mas parsetem um péssimo representante, porque geralmente é usado por motivos errados; aqui está precisamente o seu caso de uso apropriado, passando de string para código. Você formatou o texto, mas não formatou para R, e nomeou listas; portanto, minha primeira linha o codifica para uma lista R, alterando "a; b" para "lista (a, b)". Depois, avaliamos e fazemos uma tabela.
Moody_Mudskipper
1

Você pode usar chamadas separadas subpara cada campo extraído desejado, por exemplo, para type:

myDT$type <- sub("^.*\\btype=([^;]+)\\b.*$", "\\1", myDT$info)
Tim Biegeleisen
fonte
Eu não sei todos os fileds que irão ocorrer e eles podem ser muito para que isso não é uma opção
Cath
11
Justo; Eu não sabia disso quando publiquei esta resposta.
Tim Biegeleisen 29/11/19
Vou adicioná-lo (btw você não dá saída desejada, sua resposta perde algumas linhas ...)
Cath