dividere le colonne di caratteri e ottenere i nomi dei campi nella stringa


11

Devo dividere una colonna che contiene informazioni in più colonne.
Vorrei usare tstrsplitma lo stesso tipo di informazioni non è nello stesso ordine tra le righe e devo estrarre il nome della nuova colonna all'interno della variabile. Importante da sapere: possono esserci molte informazioni (campi per diventare nuove variabili) e non le conosco tutte, quindi non voglio una soluzione "campo per campo".

Di seguito è riportato un esempio di ciò che ho:

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 vorrei ottenere:

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

Un modo molto semplice per ottenerlo sarebbe molto apprezzato! ( Nota: non sono disposto ad andare con un modo dplyr / tidyr )

Risposte:


5

Utilizzo regexe stringipacchetti:

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

Modifica: per ottenere il tipo corretto sembra (?) type.convert()Può essere utilizzato:

myDT[, (fields) := lapply(patterns, function(x) type.convert(stri_extract(regex = x, info), as.is = TRUE))]

Ricevo un lunghissimo avvertimento "Rilevato .internal.selfref non valido e risolto prendendo una copia (superficiale) di data.table ..."
Moody_Mudskipper

anche il tipo e la fine sono caratteri qui, non sono sicuro se questo è previsto
Moody_Mudskipper

1
@Moody_Mudskipper Grazie per aver commentato. (1) (Questo avviso è (credo) causato dalla data.table creata da structure()Ho aggiornato la risposta per evitare questo problema (2) Sono personaggi apposta ... Ho pensato che analizzarli correttamente sarebbe stato difficile e una domanda separata. Sembra che tu l'abbia risolto nella tua risposta e io darò un'occhiata e vedrò se posso imparare qualcosa di nuovo.
sindri_baldur

4

Immagino che i tuoi dati provengano da un file VCF , in tal caso esiste uno strumento dedicato per tali problemi: bcftools .

Creiamo un file VCF di esempio per il test:

# 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

Ora possiamo usare bcftools . Qui a titolo di esempio stiamo eseguendo la sottosezione di AF e DP dalla colonna 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

Consulta il manuale per ulteriori opzioni di query .


3

Potremmo dividere su ";"poi rimodellare da largo a lungo, quindi dividere di nuovo su "=", quindi rimodellare di nuovo da lungo a largo:

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 versione migliorata / più leggibile:

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")]

@Jaap Grazie, sapevo che esisteva un modo DT migliore per concatenare le cose.
zx8754,

3

Per ora, sono riuscito a ottenere quello che voglio con il seguente codice:

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

Due opzioni per migliorare le righe sopra, grazie a @ A5C1D2H2I1M1N2O1R2T1 (che le ha fornite nei commenti):

. con un doppio cSplitprima di dcast:

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

. con cSplit/ trstrplite dcastinvece di reshape:

cSplit(myDT, "info", ";", "long")[, c("t1", "t2") := tstrsplit(info, "=", fixed = TRUE)][, dcast(.SD, chr + pos ~ t1, value.var = "t2")]

1
Farei un doppio cSplit, in questo modo: cSplit(cSplit(myDT, "info", ";", "long"), "info", "=")[, dcast(.SD, chr + pos ~ info_1, value.var = "info_2")].
A5C1D2H2I1M1N2O1R2T1

1
In alternativa, lo stesso concetto: cSplitseguito da tstrsplit, seguita da dcast: cSplit(myDT, "info", ";", "long")[, c("t1", "t2") := tstrsplit(info, "=", fixed = TRUE)][, dcast(.SD, chr + pos ~ t1, value.var = "t2")].
A5C1D2H2I1M1N2O1R2T1

@ A5C1D2H2I1M1N2O1R2T1 Grazie mille! Entrambi sono fantastici, con uno speciale per la doppia cSplitopzione :-)
Cath

2

Ecco come lo farei:

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

Creato il 29-11-2019 dal pacchetto reprex (v0.3.0)


Non ho la necessità di cambiare ";" in "," e non affezionato a eval(parse(text=...))... ma grazie comunque per la risposta
Cath

1
Non posso discutere con gusti personali ma parseha una cattiva reputazione perché è spesso usato per motivi sbagliati, ecco esattamente il suo caso d'uso appropriato, che va da stringa a codice. Hai testo formattato, ma non formattato per R, e hai elenchi con nome, quindi la mia prima riga lo rende codice per un elenco R, cambiando "a; b" in "elenco (a, b)". Quindi lo valutiamo e ne ricaviamo un tavolo.
Moody_Mudskipper,

1

È possibile utilizzare chiamate separate per subper ciascun campo estratto desiderato, ad esempio per type:

myDT$type <- sub("^.*\\btype=([^;]+)\\b.*$", "\\1", myDT$info)

Non conosco tutti i fileds che accadranno e possono essere molti, quindi questa non è un'opzione
Cath

1
Giusto; Non lo sapevo quando ho pubblicato questa risposta.
Tim Biegeleisen,

Lo aggiungerò (a meno che tu non dia l'output desiderato, la tua risposta perde alcune righe ...)
Cath
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.