使用AWK合并两个数据集

时间:2012-05-09 15:47:47

标签: shell merge awk

我有2个数据文件:file01file02。在两个数据集中,字段是:(i)标识符; (ii)数字参考; (iii)经度; (iv)纬度。 对于file01中的每一行,我想使用相同的数字引用在file02中搜索数据,然后在file02中找到最接近{{1}中的标识符的标识符}。

如果我使用以下代码手动将值从file01传递到awk程序,我可以得到这个:

file01

如您所见,awk 'function acos(x) { return atan2(sqrt(1-x*x), x) } BEGIN {pi=3.14159; ndist=999999999.1; date=1001; lo1=-1.20; lg1=lo1*(pi/180); la1=30.31; lt1=la1*(pi/180) } {if($2==date) {ws=$1; lg2=$3*(pi/180); lt2=$4*(pi/180); dist= 6378.7 * acos( sin(lt1)*sin(lt2) + cos(lt1)*cos(lt2)*cos(lg2-lg1) ); if(dist < ndist) {ndist=dist; ws0=ws}}} END {print(ws0,ndist)}' file02语句中的datelo1la1BEGIN第一行中的值(有关数据,请参见下文)文件)。我的问题是我是否可以立即执行此操作,因此每次我在file01中读取一行时,我都会获得最近的标识符和距离,并附加到file01中的行数据。我不知道是否有一些shell命令可以更容易地实现这一点,也许使用管道。

这两个数据文件和所需输出的示例是:

=== file01 ===

file01

=== file02 ===

A 1001 -1.2 30.31
A 1002 -1.2 30.31
B 1002 -1.8 30.82
B 1003 -1.8 30.82
C 1001 -2.1 28.55

===输出文件===

ws1 1000 -1.3 29.01
ws1 1001 -1.3 29.01
ws1 1002 -1.3 29.01
ws1 1003 -1.3 29.01
ws1 1004 -1.3 29.01
ws1 1005 -1.3 29.01
ws2 1000 -1.5 30.12
ws2 1002 -1.5 30.12
ws2 1003 -1.5 30.12
ws2 1004 -1.5 30.12
ws2 1005 -1.5 30.12
ws3 1000 -1.7 29.55
ws3 1001 -1.7 29.55
ws3 1002 -1.7 29.55
ws3 1003 -1.7 29.55
ws3 1004 -1.7 29.55
ws3 1005 -1.7 29.55
ws4 1000 -1.9 30.33
ws4 1001 -1.9 30.33
ws4 1002 -1.9 30.33
ws4 1003 -1.9 30.33
ws4 1004 -1.9 30.33
ws4 1005 -1.9 30.33

编辑#1:考虑到@Eran的建议,我写了以下代码:

A 1001 -1.2 30.31 ws4 67.308
A 1002 -1.2 30.31 ws2 35.783
B 1002 -1.8 30.82 ws4 55.387
B 1003 -1.8 30.82 ws4 55.387
C 1001 -2.1 28.55 ws1 85.369

此脚本的输出为:

join -j 2 < (sort -k 2,2 file01) < (sort -k 2,2 file02) |
awk 'function acos(x) { return atan2(sqrt(1-x*x), x) }
     BEGIN {pi=3.14159}

     {if (last != $1 $2)
         {print NR, id,r,lon,lat,ws0,ndist;
          last = $1 $2;
          ndist=999999999.1

         } else {

          lg1=$3*(pi/180);
          lt1=$4*(pi/180);
          lg2=$6*(pi/180);
          lt2=$7*(pi/180);
          dist= 6378.7 * acos( sin(lt1)*sin(lt2) + cos(lt1)*cos(lt2)*cos(lg2-lg1) );
          if(dist< ndist) {ndist=dist; ws0=$5}
          id=$2;r=$1;lon=$3;lat=$4

          }
     }'

编辑#2 :使用@Dennis的建议(经过一些修改)我得到了所需的输出。 awk脚本如下:

1      
4  A 1001 -1.2 30.31 ws4 67.3078
7  C 1001 -2.0 28.55 ws3 115.094
11 A 1002 -1.2 30.31 ws2 35.7827
15 B 1002 -1.8 30.82 ws4 55.387

4 个答案:

答案 0 :(得分:2)

将file01中的值读入一个或多个数组。您可以在getline块中使用BEGIN,或者规范的方法是使用FNR == NR循环作为主要块之一。

FNR == NR {array[$1] = $1; ...; next } # read file01 into some arrays
{ for (item in array) { ... }     # process each item in the array(s) against each line in file02

您的脚本将被调用为awk '...' file01 file02

您可以使用计数器array1[c] = $1; array2[c] = $2; c++对其进行索引,而不是使用in for (i=0; i<c; i++)进行迭代,而不是使用字段值对数组建立索引:

当然,您应该选择有意义的数组名称。

答案 1 :(得分:1)

有趣的挑战。由于您必须先读取file02并将数据存储在数据结构中,我首先要先了解Perl。

#!/usr/bin/perl
use strict;
use warnings;

# see http://perldoc.perl.org/Math/Trig.html
use Math::Trig qw(great_circle_distance deg2rad);
sub NESW {deg2rad($_[0]), deg2rad(90-$_[1])}

# read file02
my %data;
my $file2 = 'file02';
open my $fid, '<', $file2 or die "can't read $file2: $!\n";
while (<$fid>) {
    my ($id, $ref, $long, $lat) = split;
    push @{$data{$ref}}, [$id, $long, $lat];
}
close $fid;

$, = " ";

# process file01
my $file1 = 'file01';
open $fid, '<', $file1 or die "can't read $file1: $!\n";
while (<$fid>) {
    my ($id, $ref, $long, $lat) = split;
    my @here = NESW($long, $lat);
    my $min = 99_999_999;
    my (@min_id, $dist);

    while (my ($key, $listref) = each %data) {
        next unless $key == $ref;

        foreach my $trioref (@$listref) {
            my ($other_id, $other_long, $other_lat) = @$trioref;
            my @there = NESW($other_long, $other_lat);
            $dist = great_circle_distance(@here, @there, 6378.7);
            if ($dist < $min) {
                $min = $dist;
                @min_id = @$trioref;
            }
        }
    }

    printf "%s %d %s %s %s %6.3f\n", $id, $ref, $long, $lat, $min_id, $min;
}
close $fid;

此输出

A 1001 -1.2 30.31 ws4 67.308
A 1002 -1.2 30.31 ws2 35.783
B 1002 -1.8 30.82 ws4 55.387
B 1003 -1.8 30.82 ws4 55.387
C 1001 -2.1 28.55 ws1 93.361

我注意到“C”距离与您的建议距离不同。

答案 2 :(得分:1)

要立即执行,请运行

join -j 2 <(sort -k 2,2 file01) <(sort -k 2,2 file02)

并将其传递给一个awk,在每次参考更改时都会执行计算:

gawk '{if (last != $1 $2) {calc_nearest_on_array; last=$1 $2; add_point_to_array} else {add_point_to_array}}'

答案 3 :(得分:0)

TXR:

@(do
   (defvar pi 3.1415926535)
   (defvar earth-radius 6378.7)
   (defun rad (deg) (/ (* deg pi) 180))
   (defun sphere-distance (lat0 lon0 lat1 lon1)
     (let ((lat0 (rad lat0)) (lat1 (rad lat1))
           (lon0 (rad lon0)) (lon1 (rad lon1)))
       (* earth-radius (acos (+ (* (sin lat0) (sin lat1))
                                (* (cos lat0) (cos lat1) (cos (- lon1 lon0)))))))))
@(next "file01")
@(collect)
@id @ref @lon0 @lat0
@  (filter :tonumber lon0 lat0)
@  (next "file02")
@  (bind (min-dist ws) (1e99 nil))
@  (collect)
@ws1 @ref @lon1 @lat1
@    (filter :tonumber lon1 lat1)
@    (do (let ((d (sphere-distance lat0 lon0 lat1 lon1)))
           (cond ((< d min-dist)
                  (set min-dist d)
                  (set ws ws1)))))
@  (end)
@  (do (format t "~a ~a ~0,2f ~0,2f ~a ~0,3f\n" id ref lon0 lat0 ws min-dist))
@(end)

执行命令

$ txr dist.txr
A 1001 -1.20 30.31 ws4 67.308
A 1002 -1.20 30.31 ws2 35.783
B 1002 -1.80 30.82 ws4 55.387
B 1003 -1.80 30.82 ws4 55.387
C 1001 -2.10 28.55 ws1 93.361