Comment merge deux files avec un nombre de lignes différent en shell?

J'ai file1 comme ceci:

CHR SNP TEST A1 A2 GENO O(HET) E(HET) P 0 AFFX-SNP-000541 ALL 0 0 0/0/0 nan nan 1 0 AFFX-SNP-000541 AFF 0 0 0/0/0 nan nan NA 0 AFFX-SNP-000541 UNAFF 0 0 0/0/0 nan nan NA 0 AFFX-SNP-002255 ALL 0 0 0/0/0 nan nan 1 0 AFFX-SNP-002255 AFF 0 0 0/0/0 nan nan NA 0 AFFX-SNP-002255 UNAFF 0 0 0/0/0 nan nan NA 1 rs12103 ALL CT 55/250/317 0.4019 0.4113 0.5596 1 rs12103 AFF CT 0/0/0 nan nan NA 1 rs12103 UNAFF CT 0/0/0 nan nan NA 1 rs12103_1247494 ALL CT 55/250/321 0.3994 0.4097 0.5581 1 rs12103_1247494 AFF CT 0/0/0 nan nan NA 1 rs12103_1247494 UNAFF CT 0/0/0 nan nan NA 

Et file2 comme:

 CHR SNP A1 A2 MAF NCHROBS 0 AFFX-SNP-000541 0 0 NA 0 0 AFFX-SNP-002255 0 0 NA 0 1 rs12103 CT 0.2894 1244 1 rs12103_1247494 CT 0.2875 1252 

Je voudrais merge file2 avec file1 basé sur le nom SNP et le TEST == ALL et garder le CHR, SNP, P et MAF dans le file de sortie3. Est-ce que quelqu'un connaît la meilleure façon pour cela dans le terminal (Unix) shell?

Une sortie souhaitée serait:

  CHR SNP MAF P 0 AFFX-SNP-000541 NA 1 0 AFFX-SNP-002255 NA 1 1 rs12103 0.2894 0.5596 1 rs12103_1247494 0.2875 0.5581 

Avec l'aide de cette réponse

 awk 'FNR==NR && FNR>1 {a[$2] = $5; next} FNR > 1 && ($2 in a) && $3 == "ALL" { print $1 " " $2 " " a[$2] " " $9 }' file2 file1 

Pour get l'en-tête, ajoutez-le au début du script:

  BEGIN{print "CHR SNP MAF P"} 

Explication:

Tout d'abord, lorsque deux files sont transmis à awk, ils sont traités les uns après les autres. Il y a deux variables importantes ici: NR est le numéro de ligne du début de la command awk et FNR est le numéro de ligne du début du file courant. C'est-à-dire que lorsque le premier file est traité (ici file2), NR et FNR ont la même valeur, qui est la valeur de la ligne en cours de traitement. Mais quand awk passe au second file, le FNR est réinitialisé à 1, donc NR et FNR ne sont plus les mêmes. Pour que le test FNR==NR soit une astuce pour savoir si le file traité est le premier ou non.

Alors voyons le code. La condition FNR==NR && FNR>1 vérifie si nous traitons le premier file et non la première ligne. Si c'est le cas, nous stockons la valeur de la cinquième colonne ( MAF ) dans un tableau indexé par le second ( SNP ), puis l'instruction next indique de passer à la ligne suivante.

Lorsque awk traite le second file (qui est file1), le premier test est faux, de sorte qu'awk essaie le second test: FNR > 1 && ($2 in a) && $3 == "ALL" , c'est-à-dire pas la première ligne du file + deuxième valeur de colonne ( SNP ) existe dans la table a + la valeur de la troisième colonne ( TEST ) est "ALL" . Si c'est le cas, il imprime la colonne 1 ( CHR ) et deux ( SNP ), obtient la valeur MAF du tableau avec a[$2] , puis imprime la colonne 9 ( P ).

L'ajout d'une instruction BEGIN{...} au début ajoute une command exécutée uniquement avant le traitement de la première ligne.

terdon mentionne le faire avec coreutils seulement, une suggestion humble:

 (echo CHR SNP MAF P paste <(tail -n +2 file2) <(grep ALL file1) | while read -r chr snp _ _ maf _ _ _ _ _ _ _ _ _ p; do echo $chr $snp $maf $p; done) | column -t 

sortie:

 CHR SNP MAF P 0 AFFX-SNP-000541 NA 1 0 AFFX-SNP-002255 NA 1 1 rs12103 0.2894 0.5596 1 rs12103_1247494 0.2875 0.5581 

Voici une façon de le faire (copyr / coller directement dans votre terminal):

 ( awk -v OFS="\t" 'NR==1{print $1,$2,$5,"P"}' file2; awk '$3=="ALL"{print $2,$NF}' file1 | while read snp p; do awk -v snp="$snp" -vp=$p -v OFS="\t" '$2==snp{print $1,$2,$5,p}' file2; done ) > file3 

La sortie ressemble à ceci:

 CHR SNP MAF P 0 AFFX-SNP-000541 0 1 0 AFFX-SNP-002255 0 1 1 rs12103 C 0.5596 1 rs12103_1247494 C 0.5581 

Remarques:

  • Je suis sûr à 99% que vous pouvez le faire d'une façon ou d'une autre en utilisant coreutils , peut-être une combinaison de sort , paste et join mais je ne pouvais pas le comprendre.
  • Ce n'est pas efficace et peut prendre un certain time si vous avez affaire à d'énormes files.
  • Cela change les séparateurs de champs en tabs, supprimez tous les cas de -v OFS="\t" si vous ne le souhaitez pas.
  • Comme vous pouvez le voir, les champs ne sont plus alignés. Comme ils sont tous séparés par des tabulations, ce n'est pas un problème pour les programmes qui liront le file, ce n'est qu'un problème si vous essayez de le lire.

Explication

  • La 1ère ligne awk imprime juste le 1er, le 2ème et le 5ème champ de l'en-tête du file 2 suivi de P C'est l'en-tête du nouveau file.
  • La deuxième awk imprime les champs 2nd et last ( $NF ) de chaque ligne de file1 où le troisième champ est ALL .
  • Ceci est ensuite passé à travers un shell while loop qui lit les deux champs dans les variables $snp et $p .
  • Ceux-ci sont alors donnés au dernier awk utilisant l'option -v et ensuite, pour chaque ligne de file2, si le deuxième champ est $snp , il imprime les champs 1,2,5 et la valeur actuelle de $p .

Voici un script perl qui fait la même chose:

 perl -le 'print "CHR\tSNP\tMAF\tP"; open($f1, "$ARGV[0]"); while(<$f1>){ s/^\s+//; @f=split(/\s+/); $k{$f[1]}=$f[$#f] if $f[2] eq "ALL" } open($f2,"$ARGV[1]"); while(<$f2>){ s/^\s+//; @f=split(/\s+/); next unless defined($k{$f[1]}); print "$f[0]\t$f[1]\t$f[4]\t$k{$f[1]}" }' file1 file2 

Voici une autre façon de le faire:

 { printf %s\\n "CHR SNP MAF P"; grep -F ALL file1 | sort -k2,2 | \ join -j2 -o 2.1,2.2,2.5,1.9 - <(sort -k2,2 file2); } | column -t 

sortie:

 CHR SNP MAF P 0 AFFX-SNP-000541 NA 1 0 AFFX-SNP-002255 NA 1 1 rs12103 0.2894 0.5596 1 rs12103_1247494 0.2875 0.5581 

Comment ça marche:

grep -F ALL file1 | sort -k2 grep -F ALL file1 | sort -k2 les lignes correspondant à la string ALL , celles-ci sont ensuite grep -F ALL file1 | sort -k2 sur le 2ème champ:

  0 AFFX-SNP-000541 ALL 0 0 0/0/0 nan nan 1 0 AFFX-SNP-002255 ALL 0 0 0/0/0 nan nan 1 1 rs12103_1247494 ALL CT 55/250/321 0.3994 0.4097 0.5581 1 rs12103 ALL CT 55/250/317 0.4019 0.4113 0.5596 

il est ensuite joint sur le 2ème champ ( -j2 ) avec le file2 (également sortingé sur le 2ème champ) en utilisant -o 2.1,2.2,2.5,1.9 pour sortir le 1er, le 2ème et le 5ème champ du file2 et le 9ème champ du -o 2.1,2.2,2.5,1.9 :

 0 AFFX-SNP-000541 NA 1 0 AFFX-SNP-002255 NA 1 1 rs12103 0.2894 0.5596 1 rs12103_1247494 0.2875 0.5581 

Comme cela a été regroupé {...} avec printf %s\\n "CHR SNP MAF P" qui imprime l'en-tête, toute la sortie est ensuite prettifiée avec la column -t .
Notez qu'avec cette solution, l'ordre des lignes (à partir de file2) n'est pas conservé dans la sortie. Il arrive juste que votre file2 ait déjà été sortingé sur le 2ème champ mais si ce n'était pas par exemple:

 CHR SNP A1 A2 MAF NCHROBS 0 AFFX-SNP-000541 0 0 NA 0 1 rs12103 CT 0.2894 1244 0 AFFX-SNP-002255 0 0 NA 0 1 rs12103_1247494 CT 0.2875 1252 

et si vous vouliez préserver l'ordre des lignes, vous pouvez numéroter les lignes dans le file 2 avec nl -ba -nrz file2 :

 000001 CHR SNP A1 A2 MAF NCHROBS 000002 0 AFFX-SNP-000541 0 0 NA 0 000003 1 rs12103 CT 0.2894 1244 000004 0 AFFX-SNP-002255 0 0 NA 0 000005 1 rs12103_1247494 CT 0.2875 1252 

avant de sortinger et joindre et ajuster la command pour: joindre sur le 2ème champ du file (traité) 1 et 3 du file (traité) 2 et sortir le champ du numéro de ligne + les mêmes champs que la première join -1 2 -2 3 -o 2.1,2.2,2.3,2.6,1.9 puis sortinger à nouveau la sortie avec sort -k1n et supprimer le premier champ avec cut -d' ' -f2- avant de canaliser toute la sortie vers la column -t :

 { printf %s\\n "CHR SNP MAF P"; grep -F ALL file1 | sort -k2,2 | join \ -1 2 -2 3 -o 2.1,2.2,2.3,2.6,1.9 - <(sort -k3,3 <(nl -ba -nrz file2)) | \ sort -k1n | cut -d' ' -f2-; } | column -t 

sortie:

 CHR SNP MAF P 0 AFFX-SNP-000541 NA 1 1 rs12103 0.2894 0.5596 0 AFFX-SNP-002255 NA 1 1 rs12103_1247494 0.2875 0.5581 

Notez que les deux solutions supposent qu'il n'y a qu'une seule occurrence de ALL sur chaque ligne, à savoir le 3ème champ (d'où le grep -F ALL ). Si ce n'est pas le cas, vous devrez utiliser une regex pour filterr uniquement les lignes pertinentes.