Como juntar dois arquivos com diferentes números de linhas no shell?

7

Eu tenho file1 assim:

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    C    T           55/250/317   0.4019   0.4113       0.5596
   1                 rs12103      AFF    C    T                0/0/0      nan      nan           NA
   1                 rs12103    UNAFF    C    T                0/0/0      nan      nan           NA
   1         rs12103_1247494      ALL    C    T           55/250/321   0.3994   0.4097       0.5581
   1         rs12103_1247494      AFF    C    T                0/0/0      nan      nan           NA
   1         rs12103_1247494    UNAFF    C    T                0/0/0      nan      nan           NA

E arquivo2 como:

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

Eu gostaria de mesclar file2 com file1 com base no nome do SNP e no TEST == ALL e manter o CHR, SNP, P e MAF no arquivo de saída3. Alguém sabe o melhor caminho para isso em shell terminal (Unix)?

Um resultado desejado seria:

  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
    
por Dadong Zhang 06.02.2014 / 18:56

4 respostas

6

Com a ajuda de esta resposta

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

Para obter o cabeçalho também, basta adicionar isso ao início do script:

 BEGIN{print "CHR SNP MAF P"}

Explicação:

Primeiro, quando dois arquivos são passados para o awk, eles são processados um após o outro. Há duas variáveis importantes aqui: NR é o número da linha desde o início do comando awk e FNR é o número da linha desde o início do arquivo atual. Ou seja, quando o primeiro arquivo é processado (aqui file2), NR e FNR têm o mesmo valor, que é o valor da linha atualmente processada. Mas quando o awk passa para o segundo arquivo, FNR é redefinido para 1, então NR e FNR não são mais os mesmos. Para que o teste FNR==NR seja um truque para saber se o arquivo processado é o primeiro ou não.

Então, vamos ver o código. A condição FNR==NR && FNR>1 testa se estamos processando o primeiro arquivo e não a primeira linha. Se for o caso, armazenamos o valor da quinta coluna ( MAF ) em uma matriz indexada pela segunda ( SNP ) e, em seguida, a instrução next diz para passar para a linha a seguir.

Quando o awk processa o segundo arquivo (que é file1), o primeiro teste é falso, de forma que o awk tenta o segundo teste: FNR > 1 && ($2 in a) && $3 == "ALL" , ou seja: não a primeira linha do arquivo + segundo valor da coluna ( SNP ) existe na tabela a + terceiro valor da coluna ( TEST ) é "ALL" . Se for o caso, ele imprime a coluna 1 ( CHR ) e dois ( SNP ), obtém o valor MAF da matriz com a[$2] e, em seguida, imprime a coluna nove ( P ). / p>

Adicionar uma instrução BEGIN{...} no início adiciona um comando que é executado somente antes da primeira linha ser processada.

    
por 06.02.2014 / 19:45
5

terdon menciona fazê-lo apenas com coreutils, uma sugestão humilde:

(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

saída:

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
    
por 06.02.2014 / 22:03
4

Aqui está uma maneira de fazer isso (copiar / colar isso diretamente no seu 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" -v p=$p -v OFS="\t" '$2==snp{print $1,$2,$5,p}' file2;
  done
) > file3

A saída é assim:

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

Notas:

  • Tenho 99% de certeza de que você pode fazer isso de alguma forma usando coreutils , talvez uma combinação de sort , paste e join , mas não consegui descobrir.
  • Isso não é eficiente e pode demorar um pouco se você estiver lidando com arquivos enormes.
  • Isso altera os separadores de campo para guias e remove todos os casos de -v OFS="\t" , se você não quiser isso.
  • Como você pode ver, os campos não estão mais alinhados. Como eles são todos separados por guias, isso não é um problema para nenhum programa que lerá o arquivo, é apenas um problema se você tentar lê-lo.

Explicação

  • A primeira linha awk apenas imprime o primeiro, segundo e quinto campo do cabeçalho do arquivo 2 seguido por P . Este é o cabeçalho do novo arquivo.
  • O segundo awk imprime os campos segundo e último ( $NF ) de cada linha do arquivo1, em que o terceiro campo é ALL .
  • Isso é transmitido por meio de um loop while do shell, que lê os dois campos nas variáveis $snp e $p .
  • Estes são dados à última awk usando a opção -v e, em seguida, para cada linha de arquivo2, se o segundo campo for $snp , imprime os campos 1,2,5 e o valor atual de $p .

Aqui está um script perl que faz o mesmo:

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
    
por 06.02.2014 / 19:38
1

Aqui está outra maneira de fazer isso:

{ 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

saída:

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

Como funciona:

grep -F ALL file1 | sort -k2 imprime as linhas correspondentes à string ALL , que são sort ed no 2º campo:

   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    C    T           55/250/321   0.3994   0.4097       0.5581
   1                 rs12103      ALL    C    T           55/250/317   0.4019   0.4113       0.5596

isto é então juntado no 2º campo ( -j2 ) com o arquivo2 (também ordenado no 2º campo) usando -o 2.1,2.2,2.5,1.9 para a saída do 1º, 2º e 5º campo do arquivo2 e 9º campo do arquivo processado1:

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

Como isso foi agrupado {...} com printf %s\n "CHR SNP MAF P" , que imprime o cabeçalho, a saída inteira é então refinada com column -t .
Observe que, com essa solução, a ordem das linhas (do arquivo2) não é preservada na saída. Acontece que o seu arquivo2 já foi classificado no 2º campo, mas se não foi, por exemplo:

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

e se você quiser preservar a ordem das linhas, você pode numerar as linhas no arquivo2 com nl -ba -nrz file2 :

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

antes de classificar & unindo e ajustando o comando para: unir no 2º campo de (processado) arquivo1 e 3º campo de (processado) arquivo2 e imprimir o campo de número de linha + os mesmos campos da primeira solução join -1 2 -2 3 -o 2.1,2.2,2.3,2.6,1.9 e então reordenar a saída com sort -k1n e elimine o primeiro campo com cut -d' ' -f2- antes de enviar toda a saída para 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

saída:

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

Observe que as duas soluções assumem que há apenas uma ocorrência de ALL em cada linha, ou seja, o terceiro campo (por isso, o grep -F ALL ). Se não for esse o caso, você terá que usar um regex para filtrar apenas as linhas relevantes.

    
por 29.04.2015 / 01:14