【问题标题】:Merge two data sets using AWK使用 AWK 合并两个数据集
【发布时间】:2012-05-11 15:09:27
【问题描述】:

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

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

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 &lt ndist) {ndist=dist; ws0=ws}}}
END {print(ws0,ndist)}' file02

如您所见,BEGIN 语句中的datelo1la1file01 第一行中的值(有关数据文件,请参见下文)。我的问题是我是否可以一次做到这一点,所以每次我读取file01 中的一行时,我都会获得最近的标识符和距离,并附加到file01 中的行数据。我不知道某些 shell 命令是否可以以更简单的方式实现这一点,也许使用管道。

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

=== 文件01 ===

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

=== 文件02 ===

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

=== 输出文件 ===

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

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

join -j 2 &lt (sort -k 2,2 file01) &lt (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&lt ndist) {ndist=dist; ws0=$5}
          id=$2;r=$1;lon=$3;lat=$4

          }
     }'

这个脚本的输出是:

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

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


awk 'function acos(x) { return atan2(sqrt(1-x*x), x) }
     BEGIN {pi=3.14159}
     NR==FNR {c++; a1[c]=$1;a2[c]=$2;a3[c]=$3;a4[c]=$4; next}
             {d++; b1[d]=$1;b2[d]=$2;b3[d]=$3;b4[d]=$4}

     END {
     for(k=1;k&lt=c;k++) {
         lg1=a3[k]*(pi/180);
         lt1=a4[k]*(pi/180);
         ndist=999999999.1;
         for(l=1;l&lt=d;l++) {
             if(b2[l]==a2[k]) {kk=b2[l];
                lg2=b3[l]*(pi/180);
                lt2=b4[l]*(pi/180);
                dist= 6378.7 * acos( sin(lt1)*sin(lt2) + cos(lt1)*cos(lt2)*cos(lg2-lg1) );
                if(dist&ltndist) {ndist=dist; ws0=b1[l]}
             }
         }
         print a1[k],a2[k],a3[k],a4[k],ws0,ndist
     }
    }' file01 file02

【问题讨论】:

    标签: shell merge awk


    【解决方案1】:

    将您的值从 file01 读取到一个或多个数组中。您可以在BEGIN 块中使用getline,或者规范的方法是使用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++)

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

    【讨论】:

      【解决方案2】:

      有趣的挑战。由于您必须先读取 file02 并将 info 存储在数据结构中,所以我首先倾向于 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”距离与您建议的不同。

      【讨论】:

      • 感谢@glenn 的帮助。我对 perl 不熟悉,所以我需要更多时间来查看您的建议,但这似乎很有帮助。
      【解决方案3】:

      要立即执行,请运行

      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}}'
      

      【讨论】:

      • 您的尝试看起来很有希望。但是,我在尝试这样做时遇到了一个问题:因为我每次都计算最近的last != $1 $2,并且考虑到读取第一行时last$1$2 不同,我没有得到第一行的最近点,我在file01 的最后一行没有打印。请查看我的问题编辑以查看我的代码。
      • 你是对的。我写了一个“概念”awk 来展示这个想法。为了打印最后一行,您需要使用 END 子句。
      【解决方案4】:

      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
      

      【讨论】:

      • 什么是 TXR 脚本?我的 debian 发行版没有 TXR。
      • TxR 主页为:nongnu.org/txr。没有 Debian 软件包维护者。
      猜你喜欢
      • 2021-04-19
      • 1970-01-01
      • 1970-01-01
      • 2016-04-07
      • 1970-01-01
      • 2013-08-01
      • 1970-01-01
      相关资源
      最近更新 更多