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 )

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

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 .


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

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

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)


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)

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
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.