我有一个这样的数据:
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
所有关联的$2
s 和 的加法med
。
上面对输出行进行了重新排序。如果这是一个问题,我们可以首先装饰行以保存其原始顺序,然后执行上面的 sort+awk,然后再次排序回原始顺序,最后取消装饰。
如果你有 GNU awk 并且你的键值已经排序,那么你可以在函数asort()
内部调用prt()
,然后你不需要调用sort
before 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