知道如何计算列每个部分的中值

知道如何计算列每个部分的中值

我有一个这样的数据:

111 5
111 6
111 1
222 8
222 9
222 1
222 3
555 9
555 7
555 6

对于 的每个值$1,如果可能的话,我想使用 AWK获取$2该值的所有值的中值。$1

期望的输出:

111 5 5
111 6 5
111 1 5
222 8 5.5
222 9 5.5
222 1 5.5
222 3 5.5
555 9 7
555 7 7
555 6 7

其中 5 是 5、6 和 1 的中值($1==的值111),5.5 是 8、9、1 和 3 的中值等。

答案1

在每个 UNIX 机器上的任何 shell 中使用任何 sort+awk:

$ cat tst.awk
$1 != prev { if (NR>1) prt(); prev=$1 }
{ vals[++cnt] = $2 }
END { prt() }

function prt(   i,med) {
    med = (vals[int((cnt+1)/2)] + vals[int((cnt/2)+1)]) / 2
    for (i=1; i<=cnt; i++) {
        print prev, vals[i], med
    }
    cnt = 0
}

$ sort -k1,1n -k2,2n file | awk -f tst.awk
111 1 5
111 5 5
111 6 5
222 1 5.5
222 3 5.5
222 8 5.5
222 9 5.5
555 6 7
555 7 7
555 9 7

$2上面的代码将当前的所有值存储$1在名为 的数组中vals[],然后当$1值更改或到达文件末尾时,它会调用prt()计算出该数组的中值,将其存储在名为 的变量中med,然后在循环中打印$1所有关联的$2s 和 的加法med

上面对输出行进行了重新排序。如果这是一个问题,我们可以首先装饰行以保存其原始顺序,然后执行上面的 sort+awk,然后再次排序回原始顺序,最后取消装饰。

如果你有 GNU awk 并且你的键值已经排序,那么你可以在函数asort()内部调用prt(),然后你不需要调用sortbefore awk。如果没有排序,您可以将所有内容存储在数组中,然后在 END 部分排序。但如图sort所示,首先调用是最清晰、最简单、最高效、最便携的。

答案2

假设数据已经在第一列上按字典顺序排序,并且您使用的 shell 可以理解进程替换<(...)

$ join file <( datamash -W groupby 1 median 2 <file )
111 5 5
111 6 5
111 1 5
222 8 5.5
222 9 5.5
222 1 5.5
222 3 5.5
555 9 7
555 7 7
555 6 7

这将使用命令的结果对文件的第一个字段执行关系 JOIN 操作

datamash -W groupby 1 median 2 <file

此命令计算第二个字段中每组值的中位数,并按第一个字段中的值分组。通过该-W选项,我们使 GNUdatamash将输入视为空格分隔。

该操作的结果是

111     5
222     5.5
555     7

将其与第一个字段的原始数据连接起来即可得到您想要的结果。

如果数据尚未在第一个字段上排序:

join <( sort -k 1,1n file ) <( datamash -s -W groupby 1 median 2 <file )

如果要确保具有相同第一个字段的行不会相对于彼此重新排序,请确保该sort file命令使用稳定的排序算法。大多数sort实现允许您使用非标准-s选项来执行此操作。


对于没有进程替换的 shell:

  • 数据已排序:

    datamash -W groupby 1 median 2 <file | join file -
    
  • 数据需要排序:

    sort -o file.sorted -k 1,1n file
    datamash -W groupby 1 median 2 <file.sorted | join file.sorted -
    rm -f file.sorted
    

答案3

使用 GNU awk 执行 asort() 函数,无论数据是否已排序,但都需要处理输入文件两次;在第一次处理时,我们将每个 Id 值分组为一个,然后计算每组的中位数,在第二次处理文件时,我们只是将计算出的中位数打印为最后一列id大批:

awk -v sep=, 'NR==FNR{ id[$1]=($1 in id ? id[$1] sep : "") $2; next }
     FNR==1 {
                for(x in id){
                    ln=split(id[x], tmp, sep); asort(tmp)
                    id[x]=(ln%2? tmp[int(ln/2)+1]: (tmp[ln/2]+ tmp[ln/2+1])/2 )
                }
            }
{ print $0, id[$1] }' infile infile

相关内容