diviser les colonnes de caractères et obtenir les noms des champs en chaîne

11

J'ai besoin de diviser une colonne qui contient des informations en plusieurs colonnes.
J'utiliserais tstrsplitmais le même type d'informations n'est pas dans le même ordre parmi les lignes et j'ai besoin d'extraire le nom de la nouvelle colonne dans la variable. Important à savoir: il peut y avoir de nombreuses informations (des champs pour devenir de nouvelles variables) et je ne les connais pas tous, donc je ne veux pas d'une solution "champ par champ".

Voici un exemple de ce que j'ai:

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

Et j'aimerais avoir:

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

Une façon la plus simple de l'obtenir serait très appréciée! ( Remarque: je ne suis pas prêt à suivre un chemin dplyr / tidyr )

Cath
la source

Réponses:

5

Utilisation regexet stringipackages:

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: Pour obtenir le bon type, il semble (?) type.convert()Peut être utilisé:

myDT[, (fields) := lapply(patterns, function(x) type.convert(stri_extract(regex = x, info), as.is = TRUE))]
sindri_baldur
la source
J'obtiens un très long avertissement "Invalid .internal.selfref détecté et corrigé en prenant une copie (peu profonde) du data.table ..."
Moody_Mudskipper
le type et la fin sont également des caractères ici,
je
1
@Moody_Mudskipper Merci d'avoir commenté. (1) (Cet avertissement est (je pense) provoqué par la création de la table de données par structure()j'ai mis à jour la réponse pour éviter ce problème (2) Ce sont des caractères exprès ... J'ai senti que les analyser correctement serait difficile et une question séparée. Il semble que vous ayez résolu le problème dans votre réponse et je vais jeter un coup d'œil et voir si je peux apprendre quelque chose de nouveau.
sindri_baldur
4

Je suppose que vos données proviennent d'un fichier VCF , si c'est le cas, il existe un outil dédié à ces problèmes - bcftools .

Créons un exemple de fichier VCF pour tester:

# 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

Maintenant, nous pouvons utiliser bcftools . Ici, à titre d'exemple, nous sous-paramétrons AF et DP dans la colonne 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

Consultez le manuel pour plus d' options de requête .

zx8754
la source
3

Nous pourrions nous séparer ";"puis remodeler de large à long, puis à nouveau diviser "=", puis remodeler à long à large:

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

Une version améliorée / plus lisible:

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
la source
@Jaap Merci, je savais qu'il y avait une meilleure façon DT d'enchaîner les choses.
zx8754
3

Pour l'instant, j'ai réussi à obtenir ce que je veux avec le code suivant:

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

Deux options pour améliorer les lignes ci-dessus, grâce à @ A5C1D2H2I1M1N2O1R2T1 (qui les a données dans les commentaires):

. avec un double cSplitavant dcast:

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

. avec cSplit/ trstrplitet dcastau lieu de reshape:

cSplit(myDT, "info", ";", "long")[, c("t1", "t2") := tstrsplit(info, "=", fixed = TRUE)][, dcast(.SD, chr + pos ~ t1, value.var = "t2")]
Cath
la source
1
Je ferais un double cSplit, comme celui - ci: cSplit(cSplit(myDT, "info", ";", "long"), "info", "=")[, dcast(.SD, chr + pos ~ info_1, value.var = "info_2")].
A5C1D2H2I1M1N2O1R2T1
1
Ou, le même concept: cSplitsuivi tstrsplit, suivi par dcast: cSplit(myDT, "info", ";", "long")[, c("t1", "t2") := tstrsplit(info, "=", fixed = TRUE)][, dcast(.SD, chr + pos ~ t1, value.var = "t2")].
A5C1D2H2I1M1N2O1R2T1
@ A5C1D2H2I1M1N2O1R2T1 Merci beaucoup! Les deux sont super, avec une spéciale pour la double cSplitoption :-)
Cath
2

Voici comment je le ferais:

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

Créé le 2019-11-29 par le package reprex (v0.3.0)

Moody_Mudskipper
la source
Je n'ai pas besoin de changer ";" en "," et ne l'aime pas eval(parse(text=...))... mais merci quand même pour votre réponse
Cath
1
Je ne peux pas discuter avec le goût personnel mais j'ai parseun mauvais représentant car il est souvent utilisé pour une mauvaise raison, voici précisément son cas d'utilisation approprié, allant de la chaîne au code. Vous avez mis en forme du texte, mais pas pour R, et vous avez nommé des listes, donc ma première ligne en fait un code pour une liste R, en changeant "a; b" en "liste (a, b)". Ensuite, nous l'évaluons et en faisons un tableau.
Moody_Mudskipper
1

Vous pouvez utiliser des appels séparés à subpour chaque champ extrait souhaité, par exemple pour type:

myDT$type <- sub("^.*\\btype=([^;]+)\\b.*$", "\\1", myDT$info)
Tim Biegeleisen
la source
Je ne connais pas tous les dossiers qui se produiront et ils peuvent être nombreux, donc ce n'est pas une option
Cath
1
C'est suffisant; Je ne savais pas cela quand j'ai posté cette réponse.
Tim Biegeleisen
Je vais l'ajouter (btw vous ne donnez pas la sortie souhaitée, votre réponse manque quelques lignes ...)
Cath