podziel kolumny znaków i uzyskaj nazwy pól w łańcuchu

11

Muszę podzielić kolumnę zawierającą informacje na kilka kolumn.
Chciałbym użyć, tstrsplitale ten sam rodzaj informacji nie jest w tej samej kolejności między wierszami i muszę wyodrębnić nazwę nowej kolumny w zmiennej. Ważne, aby wiedzieć: może istnieć wiele informacji (pola stają się nowymi zmiennymi) i nie znam ich wszystkich, więc nie chcę rozwiązania typu „pole po polu”.

Poniżej znajduje się przykład tego, co mam:

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

I chciałbym uzyskać:

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

Doceniony zostanie najprostszy sposób, aby to osiągnąć! ( Uwaga: nie chcę iść drogą dplyr / tidyr )

Cath
źródło

Odpowiedzi:

5

Korzystanie regexi stringipakiety:

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

Edycja: Aby uzyskać poprawny typ, wydaje się (?) type.convert()Można użyć:

myDT[, (fields) := lapply(patterns, function(x) type.convert(stri_extract(regex = x, info), as.is = TRUE))]
sindri_baldur
źródło
Dostaję bardzo długie ostrzeżenie „Nieprawidłowy. Wewnętrzny. Wykryto i naprawiono, pobierając (płytką) kopię tabeli danych…”
Moody_Mudskipper
także typ i koniec są tutaj znakami, nie jestem pewien, czy jest to oczekiwane
Moody_Mudskipper
1
@Moody_Mudskipper Dziękujemy za komentowanie. (1) (To ostrzeżenie jest (wydaje mi się) spowodowane tym, że tworzony jest zestaw danych. structure()Zaktualizowałem odpowiedź, aby uniknąć tego problemu (2) Są to postacie celowo ... Czułem, że ich poprawna analiza byłaby trudna i osobne pytanie. Wydaje się, że rozwiązaliście to w swojej odpowiedzi, a ja przyjrzę się i zobaczę, czy mogę nauczyć się czegoś nowego
sindri_baldur
4

Zgaduję, że twoje dane pochodzą z pliku VCF , jeśli tak, istnieje dedykowane narzędzie do takich problemów - bcftools .

Utwórzmy przykładowy plik VCF do testowania:

# 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

Teraz możemy używać bcftools . Tutaj jako przykład dzielimy AF i DP z kolumny 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

Więcej informacji na temat zapytań znajduje się w instrukcji .

zx8754
źródło
3

Możemy podzielić na, ";"a następnie przekształcić z szerokiego na długi, a następnie ponownie podzielić "=", a następnie przekształcić z powrotem na długi i szeroki:

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

Ulepszona / bardziej czytelna wersja:

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
źródło
@Jaap Dziękuję, wiedziałem, że istnieje lepszy sposób łączenia łańcuchów w DT.
zx8754
3

Na razie udało mi się uzyskać to, czego chcę, za pomocą następującego kodu:

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

Dwie opcje ulepszenia powyższych linii, dzięki @ A5C1D2H2I1M1N2O1R2T1 (który dał je w komentarzach):

. z podwójnym cSplitprzed dcast:

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

. z cSplit/ trstrpliti dcastzamiast reshape:

cSplit(myDT, "info", ";", "long")[, c("t1", "t2") := tstrsplit(info, "=", fixed = TRUE)][, dcast(.SD, chr + pos ~ t1, value.var = "t2")]
Cath
źródło
1
Zrobiłbym podwójne cSplit, tak: cSplit(cSplit(myDT, "info", ";", "long"), "info", "=")[, dcast(.SD, chr + pos ~ info_1, value.var = "info_2")].
A5C1D2H2I1M1N2O1R2T1
1
Albo to samo pojęcie: cSplita następnie tstrsplit, po czym następuje dcast: cSplit(myDT, "info", ";", "long")[, c("t1", "t2") := tstrsplit(info, "=", fixed = TRUE)][, dcast(.SD, chr + pos ~ t1, value.var = "t2")].
A5C1D2H2I1M1N2O1R2T1
@ A5C1D2H2I1M1N2O1R2T1 Dziękuję bardzo! Oba są świetne, ze specjalnym rozwiązaniem dla cSplitopcji podwójnej :-)
Cath
2

Oto jak bym to zrobił:

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

Utworzono 29.11.2019 przez pakiet reprezentx (v0.3.0)

Moody_Mudskipper
źródło
Nie widzę potrzeby zmiany „;” na „,” i nie przepadam za eval(parse(text=...))... ale dziękuję za twoją odpowiedź
Cath
1
Nie mogę kłócić się z osobistym gustem, ale parsema złą reputację, ponieważ jest często używany z niewłaściwego powodu, oto dokładnie jego odpowiedni przypadek użycia, przechodząc od łańcucha do kodu. Sformatowałeś tekst, ale nie sformatowałeś dla R, i nazwałeś listy, więc mój pierwszy wiersz sprawia, że ​​koduje dla listy R, zmieniając „a; b” na „listę (a, b)”. Następnie oceniamy go i tworzymy z niego tabelę.
Moody_Mudskipper
1

Możesz użyć osobnych wywołań subdla każdego żądanego wyodrębnionego pola, np. Dla type:

myDT$type <- sub("^.*\\btype=([^;]+)\\b.*$", "\\1", myDT$info)
Tim Biegeleisen
źródło
Nie znam wszystkich plików, które się pojawią i może ich być dużo, więc nie jest to opcja
Cath
1
Słusznie; Nie wiedziałem tego, kiedy opublikowałem tę odpowiedź.
Tim Biegeleisen
Dodam to (przy okazji nie dajesz pożądanego wyniku, twoja odpowiedź pomija niektóre wiersze ...)
Cath