基于两个字段连接两个文件

时间:2012-07-22 11:27:54

标签: perl join awk

我在一周之前发布了一个问题,答案很简单(使用加入):

join <(sort file1) <(sort file2) >output

加入具有常见内容的文件,通常是第一个字段。

我有以下两个文件:

genes.txt

ENSG001 ENSG002
ENSG002 ENSG001
ENSG003 ENSG004

features.txt

ENSG001 400
ENSG002 350
ENSG003 210
ENSG004 100

我需要加入这两个文件:

output.txt的

ENSG001 400 ENSG002 350
ENSG002 350 ENSG001 400
ENSG003 210 ENSG004 100

我知道答案是在join命令中,但我无法弄清楚如何基于两个字段加入。我试过了

join -j 1 <(sort genes.txt) <(sort features.txt) >attempt1.txt

但结果将如下所示:

attempt1.txt

ENSG001 ENSG002 400
ENSG002 ENSG001 350
ENSG003 ENSG004 210

然后我尝试了

join -j 2 <(sort -k 2 genes.txt) <(sort -k 2 features.txt) >attempt2.txt

attempt2.txt为空

(join)是否能够基于两个字段连接两个文件?如果不是那我该怎么办?

6 个答案:

答案 0 :(得分:3)

%features;
open $fd, '<', 'features.txt' or die $!;
while (<$fd>) {
    ($k, $v) = split;
    $features{$k} = $v;
}
close $fd or die $!;

open $fd, '<', 'genes.txt' or die $!;
while (<$fd>) {
    s/(\w+)/$1 $features{$1}/g;
    print;
}
close $fd or die $!;

答案 1 :(得分:3)

据我所知,加入并不支持这一点。请参阅join manpage

但是,您可以通过两种方式完成此任务:

  • 将文件中的第一个空格/制表符转换为插入符号(或文件中永远不会看到的其他字符),然后像以前一样使用连接,将前2个字段视为1个字段:

    perl -pi -e 's/^(\S+)\s+/$1#/' file1
    perl -pi -e 's/^(\S+)\s+/$1#/' file2
    join <(sort file1) <(sort file2) >output
    tr "#" " " output > output.final
    
  • 在Perl中完成。你可以做到

    • 直言不讳的方法(perreal的回答:同时在2个文件中啜饮);如果两个文件都很大,这会占用大量内存

    • 更多的内存保存方法(cdtits的答案:在较小的文件中啜食,存储在哈希中,然后将查找应用于第二个文件的逐行读取)

    • 对于真正的gynormous文件,做一个线性方法:

      对两个文件进行排序,读取每个文件的1行;如果匹配,打印匹配;如果不;在ID较小的文件中跳过1行。

答案 2 :(得分:3)

谢谢所有我通过欺骗问题设法回答的人。

首先我正常加入文件,然后我改变了第一个和第二个字段的位置,然后我再次使用功能加入了修改后的输出文件,最后我再次切换了字段的位置。

join <(sort genes.txt) <(sort features.txt) >tmp

cat tmp | awk '{ print $2, $1, $3 }' >tmp2

join <(sort tmp2) <(sort features.txt) >tmp3

cat tmp3 | awk '{ print $2, $3, $1, $4 }' >output.txt

答案 3 :(得分:1)

如果features.txt中的“ENST”为“ENSG”,则此处是 awk 解决方案,该解决方案在给定示例中运行良好:

awk 'BEGIN {while(getline <"features.txt") f[$1]=$2} {print $1,f[$1],$2,f[$2]}' < genes.txt

如果需要,我可以详细解释。

答案 4 :(得分:1)

使用perl:

use strict;
use warnings;
open GIN, "<genes.txt"    or die("genes");
open FIN, "<features.txt" or die("features");
my %relations;
my %values;
while (<GIN>) {
  my ($r1, $r2) = split;
  $relations{$r1} = $r2;
}
while (<FIN>) {
  my ($k, $v) = split;
  $values{$k} = $v;
}
for my $r1 (sort keys %relations) {
  my $r2 = $relations{$r1};
  print "$r1 $values{$r1} $r2 $values{$r2}\n"; 
}
close FIN; close GIN;

答案 5 :(得分:1)

您的方法通常是正确的。它应该可以通过像

这样的东西来实现
join -o '1.1 2.2 1.2 1.3' <(
    join -o '1.1 1.2 2.2' -1 2 <(sort -k 2 genes.txt) <(sort features.txt) |
    sort
) <(sort features.txt)

如果我将ENSG004代替ENST004放入features.txt,我会得到您正在寻找的内容:

$ join -o '1.1 2.2 1.2 1.3' <(
      join -o '1.1 1.2 2.2' -1 2 <(sort -k 2 genes.txt) <(sort features.txt) |
      sort
  ) <(sort features.txt)
ENSG001 400 ENSG002 350
ENSG002 350 ENSG001 400
ENSG003 210 ENSG004 100

版本较少,但跟踪字段的难度较大:

join -o '1.2 2.2 1.1 1.3' -1 2 <(
    join -1 2 <(sort -k 2 genes.txt) <(sort features.txt) |
    sort -k 2
) <(sort features.txt)

如果你要处理非常大的数据,它应该对数十GB有效(如果features.txtgenes.txt的大小相当,那么它应该比大多数RDBMS更好):< / p>

TMP=`mktemp`
sort features.txt > "$TMP"
sort -k 2 genes.txt | join -o '1.1 1.2 2.2' -1 2 - "$TMP" | sort |
    join -o '1.1 2.2 1.2 1.3' - "$TMP"
rm "$TMP"