最初の列が同じ複数列のすべての行の平均を見つける

最初の列が同じ複数列のすべての行の平均を見つける

投稿の延長線です最初の列が同じ平均行

入力ファイル:

a   12  13  14
b   15  16  17
a   21  22  23
b   24  25  26

希望の出力:

a   16.5  17.5  18.5
b   19.5  20.5  21.5

その投稿のawkコードは次のとおりです。

awk '
    NR>1{
        arr[$1]   += $2
        count[$1] += 1
    }
    END{
        for (a in arr) {
            print a "\t" arr[a] / count[a]
        }
    }
'

問題:このコードは最初の行でのみ機能します。このコードを複数の列に拡張するにはどうすればよいですか?

答え1

を使用すると、awkキー(最初の列値)と列インデックスで複合インデックスを構成して2D配列をシミュレートできます。

awk '
  {
  c[$1]++; 
  for (i=2;i<=NF;i++) {
    s[$1"."i]+=$i};
  } 
  END {
    for (k in c) {
      printf "%s\t", k; 
      for(i=2;i<NF;i++) printf "%.1f\t", s[k"."i]/c[k]; 
      printf "%.1f\n", s[k"."NF]/c[k];
    }
  }' file
  a       16.5    17.5    18.5
  b       19.5    20.5    21.5

perl配列のハッシュを使用して、同様のアプローチをより直接的に実装できます。


あるいは、GNU datamash(少なくともバージョン1.1.0以降)は非常にコンパクトにグループ平均化をサポートしています。

datamash --sort --whitespace groupby 1 mean 2-4 < file
a       16.5    17.5    18.5
b       19.5    20.5    21.5

FWIW ここでは、コメントで要求されたようにグローバル最大平均に正規化することを含む、Perl ソリューションに対する私の試みがあります。免責事項:私は初心者のPerlプログラマーなので、プログラムがうまくいかないかもしれません。

#!/usr/bin/perl

use strict;
use warnings;

use List::MoreUtils qw(pairwise minmax);
use Math::Round qw(nearest);

my @hdr;
my %sums = ();
my %count = ();
my $key;

while (defined($_ = <ARGV>)) {
  chomp $_;
  my @F = split(' ', $_, 0);

  # UGLY: hardcoded to expect exactly 1 header row
  if ($. == 1) {
    @hdr = @F;
    next;
  }

  # sum column-wise, grouped by first column
  $key = shift @F;
  if ( exists $sums{$key} ) {
    $sums{$key} = [ pairwise { $a + $b } @{ $sums{$key} }, @F];
  }
  else {
    $sums{$key} = \@F;
  }

  $count{$key}++;
}


my %avgs = ();
# NB should really initialize $maxavg to a suitably large NEGATIVE value
my $maxavg = 0.0;

# find the column averages, and the global max of those averages
for $key ( keys %sums ) {
  $avgs{$key} = [ map { $_ / $count{$key} } @{ $sums{$key} } ];
  # NB could use List::Util=max here, but we're alresdy using List::MoreUtils
  my ($kmin, $kmax) = minmax @{ $avgs{$key} };
  $maxavg = $kmax > $maxavg ? $kmax : $maxavg;
}

# normalize and print the results, rounded to nearest 0.01
print join "\t", @hdr, "\n";
for $key ( sort keys %avgs ) {
  print join "\t", $key, (map { nearest (0.01, $_ / $maxavg) } @{ $avgs{$key} }), "\n";
}

別の名前で保存しcolavgnorm.plて実行し、実行してください。

$ ./colavgnorm.pl file
K       C1      C2      C3
a       0.77    0.81    0.86
b       0.91    0.95    1

fileどこ

K   C1  C2  C3
a   12  13  14
b   15  16  17
a   21  22  23
b   24  25  26

関連情報