寻找一种方法让 awk 迭代循环文件(在 bash 中创建 PERCENTRANK 函数)

Looking for a way to have awk iteratively loop through a file (to create PERCENTRANK function in bash)

我的文件 data.txt 包含以下具有数百万行的记录结构:

13
12
11
8
4
3
2
1
1
1

对于该列的每个值,我需要计算它的 PERCENTRANK(这是一个值在数据集中的排名,作为数据集的百分比)。

计算数据集中任意值X的PERCENTRANK的公式为

= number of values less than X / (Number of values less than X + Number of Values greater than X)

因此,对于数据集中的每个值 X,程序必须遍历所有记录以找出同一数据集中小于和大于 X 的值的数量。

如何使用 'awk' 重复遍历文件来计算所有 X 值的 PERCENTRANK?

预期输出:

X    PERCENTRANK
13   1.0000
12   0.8888
11   0.7777
8    0.6666
4    0.5555
3    0.4444
2    0.3333
1    0.0000
1    0.0000
1    0.0000

PERCENTRANK of 2 为 0.333,因为集合中有三个值小于 2,六个大于 2。PERCENTRANK OF 2 = 3 / (3 + 6) = 3/9 = 0.3333.

同样,4 的 PERCENTRANK 为 0.5555,因为有五个值小于 4,四个大于 4。 4 的百分比 = 5 / (5 + 4) = 5 / 9 = 0.5555.

我正在避免嵌套 'while..do' 循环,因为它在遍历包含数百万条记录的文件时非常慢。

我对 awk 在许多其他迭代计算场景中的惊人速度感到满意,例如:计算平均值、标准差、按总和分组等,因此,我更希望使用 'awk' 来解决这个用例。

GNU awk

gawk '
    {count[]++}
    END {
        print "X\tPERCENTRANK"
        PROCINFO["sorted_in"] = "@ind_num_desc"
        gt = 0
        total = NR
        for (x in count) {
            lt = total - count[x] - gt
            pr = lt/(gt+lt)
            for (i=1; i<=count[x]; i++)
                printf "%d\t%.4f\n", x, pr
            gt += count[x]
        }
    }
' data.txt
X       PERCENTRANK
13      1.0000
12      0.8889
11      0.7778
8       0.6667
4       0.5556
3       0.4444
2       0.3333
1       0.0000
1       0.0000
1       0.0000

即使对于大型数据集,这也应该非常有效:没有嵌套循环。

这依赖于 GNU awk 来设置遍历 count 数组的顺序:按数组索引排序,按数字降序排列。由于我们强制执行该顺序,因此我们可以简单地计算有多少记录大于我们当前正在查看的记录。

遵循更简单的 sortawk 方法也可能对您有所帮助(尽管我没有在数百万行上测试它,因为我没有它)。

解决方案 1: 这将不会在输出中显示重复项的排名,例如--> 示例中的数字 1。

sort -nr Input_file | awk '
function sum(array){
  tot="";
  for(i in array){
    tot+=array[i]};
  return tot}
{
  a[FNR]=[=10=];
  b[[=10=]]++
}
END{
  for(j=1;j<=FNR;j++){
    if(b[a[j]]){
      val=b[a[j]];
      delete b[a[j]];
      printf("%d %0.4f\n",a[j],sum(b)/(sum(d)+sum(b)));
      d[a[j]]=val;}
}}
'

输出如下。

13 1.0000
12 0.8889
11 0.7778
8 0.6667
4 0.5556
3 0.4444
2 0.3333
1 0.0000

解决方案 2: 添加解决方案(与第一个略有不同),这将在输出中提供重复项的等级,如下所示.

sort -nr Input_file | awk '
function sum(array){
  tot="";
  for(i in array){
    tot+=array[i]};
  return tot}
{
  a[FNR]=[=12=];
  b[[=12=]]++
}
END{
  for(j=1;j<=FNR;j++){
    if(b[a[j]]){
      val=val1=b[a[j]];
      delete b[a[j]];
      while(val1>0){
      printf("%d %0.4f\n",a[j],sum(b)/(sum(d)+sum(b)));
      val1--}
      d[a[j]]=val;}
}}
'
13 1.0000
12 0.8889
11 0.7778
8 0.6667
4 0.5556
3 0.4444
2 0.3333
1 0.0000
1 0.0000
1 0.0000