dividir columnas de caracteres y obtener nombres de campo en cadena

11

Necesito dividir una columna que contiene información en varias columnas.
Lo usaría, tstrsplitpero el mismo tipo de información no está en el mismo orden entre las filas y necesito extraer el nombre de la nueva columna dentro de la variable. Importante saber: puede haber muchas piezas de información (campos para convertirse en nuevas variables) y no las conozco todas, por lo que no quiero una solución "campo por campo".

A continuación se muestra un ejemplo de lo que tengo:

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

Y me gustaría obtener:

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

¡La forma más directa de conseguir eso sería muy apreciada! ( Nota: no estoy dispuesto a seguir una forma dplyr / tidyr )

Cath
fuente

Respuestas:

5

Usando regexy los stringipaquetes:

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

Editar: para obtener el tipo correcto parece que (?) type.convert()Se puede usar:

myDT[, (fields) := lapply(patterns, function(x) type.convert(stri_extract(regex = x, info), as.is = TRUE))]
sindri_baldur
fuente
Recibo una advertencia muy larga "Invalid .internal.selfref detectado y reparado al tomar una copia (superficial) de data.table ..."
Moody_Mudskipper
También el tipo y el final son de carácter aquí, no estoy seguro si eso es lo esperado
Moody_Mudskipper
1
@Moody_Mudskipper Gracias por comentar. (1) (Esta advertencia es (creo) causada por la creación de la tabla data.He structure()actualizado la respuesta para evitar este problema (2) Son caracteres a propósito ... Sentí que analizarlos correctamente sería difícil . y una pregunta separada parece que resolvió que aunque en su respuesta y voy a echar un vistazo y ver si puedo aprender algo nuevo.
sindri_baldur
4

Supongo que sus datos provienen de un archivo VCF , si es así hay una herramienta dedicada para tales problemas: bcftools .

Creemos un archivo VCF de ejemplo para probar:

# 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

Ahora podemos usar bcftools . Aquí, como ejemplo, estamos subconjustando AF y DP de la columna 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

Consulte el manual para más opciones de consulta .

zx8754
fuente
3

Podríamos dividir y ";"luego cambiar de forma de ancho a largo, luego dividir nuevamente "="y luego volver a cambiar de largo a ancho:

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

Una versión mejorada / más legible:

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
fuente
@Jaap Gracias, sabía que había una mejor manera DT de encadenar las cosas.
zx8754
3

Por ahora, logré obtener lo que quiero con el siguiente 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

Dos opciones para mejorar las líneas anteriores, gracias a @ A5C1D2H2I1M1N2O1R2T1 (que las dio en los comentarios):

. con un doble cSplitantes de dcast:

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

. con cSplit/ trstrplity en dcastlugar de reshape:

cSplit(myDT, "info", ";", "long")[, c("t1", "t2") := tstrsplit(info, "=", fixed = TRUE)][, dcast(.SD, chr + pos ~ t1, value.var = "t2")]
Cath
fuente
1
Me gustaría hacer un doble cSplit, así: cSplit(cSplit(myDT, "info", ";", "long"), "info", "=")[, dcast(.SD, chr + pos ~ info_1, value.var = "info_2")].
A5C1D2H2I1M1N2O1R2T1
1
O bien, el mismo concepto: 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 ¡Muchas gracias! Ambos son geniales, con un especial para la cSplitopción doble :-)
Cath
2

Así es como lo haría:

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

Creado el 29/11/2019 por el paquete reprex (v0.3.0)

Moody_Mudskipper
fuente
No tengo la necesidad de cambiar ";" en "," y no le gusta eval(parse(text=...))... pero gracias de todos modos por su respuesta
Cath
1
No puedo discutir con el gusto personal, pero parsetiene una mala reputación porque a menudo se usa por una razón incorrecta, este es precisamente su caso de uso apropiado, yendo de cadena a código. Ha formateado el texto, pero no formateado para R, y ha nombrado listas, por lo que mi primera línea lo codifica para una lista R, cambiando "a; b" en "lista (a, b)". Luego lo evaluamos y hacemos una tabla con él.
Moody_Mudskipper
1

Puede usar llamadas separadas subpara cada campo extraído deseado, por ejemplo, para type:

myDT$type <- sub("^.*\\btype=([^;]+)\\b.*$", "\\1", myDT$info)
Tim Biegeleisen
fuente
No sé todos los archivos que ocurrirán y pueden ser muchos, así que esta no es una opción
Cath
1
Lo suficientemente justo; No sabía esto cuando publiqué esta respuesta.
Tim Biegeleisen
Lo agregaré (por cierto, no das el resultado deseado, tu respuesta pierde algunas líneas ...)
Cath