使用Shell时不考虑缺失值的多个文件的平均值

时间:2015-07-21 04:37:06

标签: linux shell unix awk multiple-files

我有五个不同的文件。每个文件的一部分看起来像:

ifile1.txt  ifile2.txt  ifile3.txt  ifile4.txt ifile5.txt
   2           3           2           3          2
   1           2         /no value     2          3
 /no value     2           4           3        /no value
   3           1           0           0          1
 /no value   /no value   /no value   /no value  /no value 

我需要计算这五个文件的平均值而不考虑缺失值。即。

ofile.txt
  2.4
  2.0
  3.0
  1.0
  99999

Here 2.4 = (2+3+2+3+2)/5
     2.0 = (1+2+2+3)/4
     3.0 = (2+4+3)/3
     1.0 = (3+1+0+0+1)/5
     99999 = all are missing

我正在尝试以下方式,但不要觉得这是一种正确的方式。

paste ifile1.txt ifile2.txt ifile3.txt ifile4.txt ifile5.txt > ofile.txt
tr '\n' ' ' < ofile.txt > ofile1.txt
awk '!/\//{sum += $1; count++} {print count ? (sum/count) : count;sum=count=0}' ofile1.txt > ofile2.txt
awk '!/\//{sum += $2; count++} {print count ? (sum/count) : count;sum=count=0}' ofile1.txt > ofile3.txt
awk '!/\//{sum += $3; count++} {print count ? (sum/count) : count;sum=count=0}' ofile1.txt > ofile4.txt
awk '!/\//{sum += $4; count++} {print count ? (sum/count) : count;sum=count=0}' ofile1.txt > ofile5.txt
awk '!/\//{sum += $5; count++} {print count ? (sum/count) : count;sum=count=0}' ofile1.txt > ofile6.txt
paste ofile2.txt ofile3.txt ofile4.txt ofile5.txt ofile6.txt > ofile7.txt
tr '\n' ' ' < ofile7.txt > ofile.txt

2 个答案:

答案 0 :(得分:2)

以下script.awk将提供您想要的内容:

BEGIN {
    gap = -1;
    maxidx = -1;
}
{
    if (NR != FNR + gap) {
        idx = 0;
        gap = NR - FNR;
    }
    if (idx > maxidx) {
        maxidx = idx;
        count[idx] = 0;
        sum[idx] = 0;
    }
    if ($0 != "/no value") {
        count[idx]++;
        sum[idx] += $0;
    }
    idx++;
}
END {
    for (idx = 0; idx <= maxidx; idx++) {
        if (count[idx] == 0) {
            sum[idx] = 99999;
            count[idx] = 1;
        }
        print sum[idx] / count[idx];
    }
}

你用:

来称呼它
awk -f script.awk ifile*.txt

它允许任意数量的输入文件,每个文件具有任意数量的行。它的工作原理如下:

BEGIN {
    gap = -1;
    maxidx = -1;
}

此开始部分在处理任何行之前运行,并相应地设置当前间隙和最大索引。

差距是整个行号NR与文件行号FNR之间的差异,用于检测切换文件的时间,这在处理多个输入文件时非常方便

最大索引用于计算最大行数,以便在结尾输出正确数量的记录。

{
    if (NR != FNR + gap) {
        idx = 0;
        gap = NR - FNR;
    }
    if (idx > maxidx) {
        maxidx = idx;
        count[idx] = 0;
        sum[idx] = 0;
    }
    if ($0 != "/no value") {
        count[idx]++;
        sum[idx] += $0;
    }
    idx++;
}

以上代码是解决方案的核心,每行执行一次。第一个if语句用于检测您是否刚刚移入新文件,它只是这样做,因此它可以聚合每个文件中的所有关联行。我的意思是每个输入文件中的第一行用于计算输出文件第一行的平均值。

如果当前行号超出我们之前遇到的任何行号,则第二个if语句会调整maxidx。这适用于文件一可能有七行但文件二有九行的情况(在你的情况下不是这样,但无论如何都值得处理)。先前未被识别的行号也意味着我们将其总和和计数初始化为零。

如果该行包含除if以外的任何内容,则最终/no value语句只会更新总和并计数。

然后,当然,您需要调整下一次的行号。

END {
    for (idx = 0; idx <= maxidx; idx++) {
        if (count[idx] == 0) {
            sum[idx] = 99999;
            count[idx] = 1;
        }
        print sum[idx] / count[idx];
    }
}

在输出数据方面,通过数组并计算总和和计数的平均值是一件简单的事情。请注意,如果计数为零(所有相应的条目均为/no value),我们会调整总和和计数,以便获得99999。然后我们打印平均值。

因此,在输入文件上运行该代码会按要求提供:

$ awk -f script.awk ifile*.txt
2.4
2
3
1
99999

答案 1 :(得分:0)

使用bashnumaverage(忽略非数字输入),加pastesedtr(两者都用于清理,因为{{ 1}}需要单列输入,如果输入 100%文本,则抛出错误:

numaverage

输出:

paste ifile* | while read x ; do \
                   numaverage <(tr '\t' '\n' <<< "$x") 2>&1 | \
                   sed -n '1{s/Emp.*/99999/;p}' ; \
               done