【问题标题】:I need help writing an AWK Script for grouping by row and finding min/max/avg of values我需要帮助编写 AWK 脚本以按行分组并查找值的最小值/最大值/平均值
【发布时间】:2020-05-16 17:44:06
【问题描述】:

我正在使用 Bash 并尝试编写一个 Awk 脚本,该脚本从 CSV 文件中获取数据,按行对数据进行分组,然后获取值的最小值、最大值和平均值。

这是完整的 CSV 文件:

Student,Catehory,Assignment,Score,Possible
Chelsey,Homework,H01,90,100
Chelsey,Homework,H02,89,100
Chelsey,Homework,H03,77,100
Chelsey,Homework,H04,80,100
Chelsey,Homework,H05,82,100
Chelsey,Homework,H06,84,100
Chelsey,Homework,H07,86,100
Chelsey,Lab,L01,91,100
Chelsey,Lab,L02,100,100
Chelsey,Lab,L03,100,100
Chelsey,Lab,L04,100,100
Chelsey,Lab,L05,96,100
Chelsey,Lab,L06,80,100
Chelsey,Lab,L07,81,100
Chelsey,Quiz,Q01,100,100
Chelsey,Quiz,Q02,100,100
Chelsey,Quiz,Q03,98,100
Chelsey,Quiz,Q04,93,100
Chelsey,Quiz,Q05,99,100
Chelsey,Quiz,Q06,88,100
Chelsey,Quiz,Q07,100,100
Chelsey,Final,FINAL,82,100
Chelsey,Survey,WS,5,5
Sam,Homework,H01,19,100
Sam,Homework,H02,82,100
Sam,Homework,H03,95,100
Sam,Homework,H04,46,100
Sam,Homework,H05,82,100
Sam,Homework,H06,97,100
Sam,Homework,H07,52,100
Sam,Lab,L01,41,100
Sam,Lab,L02,85,100
Sam,Lab,L03,99,100
Sam,Lab,L04,99,100
Sam,Lab,L05,0,100
Sam,Lab,L06,0,100
Sam,Lab,L07,0,100
Sam,Quiz,Q01,91,100
Sam,Quiz,Q02,85,100
Sam,Quiz,Q03,33,100
Sam,Quiz,Q04,64,100
Sam,Quiz,Q05,54,100
Sam,Quiz,Q06,95,100
Sam,Quiz,Q07,68,100
Sam,Final,FINAL,58,100
Sam,Survey,WS,5,5
Andrew,Homework,H01,25,100
Andrew,Homework,H02,47,100
Andrew,Homework,H03,85,100
Andrew,Homework,H04,65,100
Andrew,Homework,H05,54,100
Andrew,Homework,H06,58,100
Andrew,Homework,H07,52,100
Andrew,Lab,L01,87,100
Andrew,Lab,L02,45,100
Andrew,Lab,L03,92,100
Andrew,Lab,L04,48,100
Andrew,Lab,L05,42,100
Andrew,Lab,L06,99,100
Andrew,Lab,L07,86,100
Andrew,Quiz,Q01,25,100
Andrew,Quiz,Q02,84,100
Andrew,Quiz,Q03,59,100
Andrew,Quiz,Q04,93,100
Andrew,Quiz,Q05,85,100
Andrew,Quiz,Q06,94,100
Andrew,Quiz,Q07,58,100
Andrew,Final,FINAL,99,100
Andrew,Survey,WS,5,5
Ava,Homework,H01,55,100
Ava,Homework,H02,95,100
Ava,Homework,H03,84,100
Ava,Homework,H04,74,100
Ava,Homework,H05,95,100
Ava,Homework,H06,84,100
Ava,Homework,H07,55,100
Ava,Lab,L01,66,100
Ava,Lab,L02,77,100
Ava,Lab,L03,88,100
Ava,Lab,L04,99,100
Ava,Lab,L05,55,100
Ava,Lab,L06,66,100
Ava,Lab,L07,77,100
Ava,Quiz,Q01,88,100
Ava,Quiz,Q02,99,100
Ava,Quiz,Q03,44,100
Ava,Quiz,Q04,55,100
Ava,Quiz,Q05,66,100
Ava,Quiz,Q06,77,100
Ava,Quiz,Q07,88,100
Ava,Final,FINAL,99,100
Ava,Survey,WS,5,5
Shane,Homework,H01,50,100
Shane,Homework,H02,60,100
Shane,Homework,H03,70,100
Shane,Homework,H04,60,100
Shane,Homework,H05,70,100
Shane,Homework,H06,80,100
Shane,Homework,H07,90,100
Shane,Lab,L01,90,100
Shane,Lab,L02,0,100
Shane,Lab,L03,100,100
Shane,Lab,L04,50,100
Shane,Lab,L05,40,100
Shane,Lab,L06,60,100
Shane,Lab,L07,80,100
Shane,Quiz,Q01,70,100
Shane,Quiz,Q02,90,100
Shane,Quiz,Q03,100,100
Shane,Quiz,Q04,100,100
Shane,Quiz,Q05,80,100
Shane,Quiz,Q06,80,100
Shane,Quiz,Q07,80,100
Shane,Final,FINAL,90,100
Shane,Survey,WS,5,5

基本上,我有 5 个学生姓名,每个学生都完成了每个课程名称的测验、实验室、作业,以及调查和期末考试...

我要做的是按作业名称对其进行分组,并生成一份报告,显示该作业的最低分、最高分和平均分...

输出应该是:

Name     Low     High  Avg
H02      66       99   74.22
L07      47       88   66.30

并包括第 3 列中的每个单独的分配名称 ($3)。使用制表符 (/t) 格式化

我粘贴的代码已经在 avg 列中输出了标题和小数点后 2 位,但实际值不正确。

我真的只有两个问题:

  1. 我一辈子都无法获得各个分组的最小值或最大值。我知道如何获取最小值/最大值,甚至是它的基本语法,但是如何将它传递给各个组?

  2. 编写脚本。我在使用 bash 或任何 Linux 方面的经验非常有限,并且不熟悉 awk(尽管我现在正在学习很多)。

所以,为了让自己至少开始,我写了一个衬里来实现我正在寻找的分组和输出格式,但它只是对每个组的分数求和,平均值都搞砸了,因为我还有不知道如何获取分数以用作除数。

无论如何,这就是我所拥有的:

awk -F "," 'BEGIN{printf "Name\tLow\tHigh\tAvg\n"}
            NR>=2{a[$3]+=$4; b[$3]+=$4;c[$3]+=$4/FNR }
            END {for (i in b) printf "%-7s\t%d\t%d\t%.02f\n", i,a[i],b[i],c[i]}'  \
    score-data.csv

输出是完美的,因为它按分配名称分组,平均列中的 2 位小数和选项卡....尝试将总和除以 FNR。也尝试过 NF 和 NR... 没有运气。同样,我知道如何计数,但不知道如何在此处输入。

所以,如果有人可以帮助我处理 min/max/avg 以及将其用作脚本的语法,将不胜感激

由于某种原因我无法发表评论,但我已经搜索了谷歌并阅读了 man awk 的内容,并在我的浏览器中打开了两个不同的选项卡来查看关于 awk 的文档。他们都没有针对我的情况解决这个问题。

就数组命名而言,使用的都是同一个数组;使用第 3 列作为索引/键和第 4 列中的值作为键的值的关联数组。所有建议的搜索和链接都涉及列;我需要行。

【问题讨论】:

  • 感谢您在问题中展示您的代码工作。您能否在您的问题中简单地添加输入样本和预期输出样本,然后让我们知道。
  • 我建议对数组使用更具描述性的名称;不得不从这里来回跳转到另一个网站的电子表格已经够糟糕的了,然后必须弄清楚abc 指的是什么(并预计未来会出现一些混乱。 .. 几周?几个月?...当你回到这段代码并且不记得这些字母代表什么时,嗯)
  • 看看awk: find min and maxuse awk to find average 的一些想法;在awkminmaxavg 上进行谷歌搜索会得到很多点击
  • 为什么另一个 Gene 帐户试图使用只有原始发布者才能拥有的信息来编辑此内容?
  • 你想要的输出是错误的。对于H02,最低为47 (Andrew),最高为95 (Ava),平均为74.60。对于L07,最低为0(Sam),最高为86(Andrew),平均为64.80

标签: arrays bash shell awk scripting


【解决方案1】:

您的问题是您的 awk 脚本没有检查结果每个键

试试这个。

awk -F , 'NR>1 { if(!($3 in course)) { low[$3] = high[$3] = $4 }
        if ($4 < low[$3]) low[$3] = $4;
        if ($4 > high[$3]) high[$3] = $4;
        sum[$3] += $4;
        ++course[$3] }
    END { OFS="\t"; print "Name", "Low", "High", "Avg";
        for (k in course)
          print k, low[k], high[k], sum[k]/course[k] }' file.csv

样本数据的结果:

Name    Low High    Avg
FINAL   58  99  85.6
L01 41  91  75
L02 0   100 61.4
L03 88  100 95.8
L04 48  100 79.2
L05 0   96  46.6
Q01 25  100 74.8
L06 0   99  61
Q02 84  100 91.6
L07 0   86  64.8
H01 19  90  47.8
WS  5   5   5
Q03 33  100 66.8
H02 47  95  74.6
Q04 55  100 81
H03 70  95  82.2
Q05 54  99  76.8
H04 46  80  65
Q06 77  95  86.8
H05 54  95  76.6
Q07 58  100 78.8
H06 58  97  80.6
H07 52  90  67

仅当您想要整个文件的平均值时才可以通过除以行号来计算平均值(当然,即使这样,如果您在开始时跳过某些行,也应该从除数中减去这些行)。

【讨论】:

    【解决方案2】:

    如果你想保持输出有序,你可以做类似的事情:

    awk -F, '
    BEGIN { printf "Name\tLow\tHigh\tAvg\n" }
    NR > 1 {
        if ($3 in low) {            # if assignment already initialized
            if ($4 < low[$3])       # check new low score
                low[$3] = $4
            if ($4 > hi[$3])        # check new high score
                hi[$3] = $4
            sum[$3] += $4           # add to assignment sum
            grades[$3]++            # add to assignment score count
        }
        else {                      # new assignment name
            name[n++] = $3          # keep indexed array of names (for order)
            low[$3] = $4            # initialize low for assignment
            hi[$3]  = $4            # initialize high for assignment
            sum[$3] = $4            # initialize sum for assignment
            grades[$3] = 1          # initialize score count for assignment
        }
    }
    END {
        for (i=0; i<n; i++)         # output informaton in order
            printf "%s\t%d\t%d\t%.2f\n", name[i], low[name[i]], hi[name[i]], sum[name[i]]/grades[name[i]]
    }' score-data.csv
    

    上面的索引数组names 用于按看到的顺序保存分配名称,然后按顺序遍历分配以输出:

    使用/输出示例

    Name    Low     High    Avg
    H01     19      90      47.80
    H02     47      95      74.60
    H03     70      95      82.20
    H04     46      80      65.00
    H05     54      95      76.60
    H06     58      97      80.60
    H07     52      90      67.00
    L01     41      91      75.00
    L02     0       100     61.40
    L03     88      100     95.80
    L04     48      100     79.20
    L05     0       96      46.60
    L06     0       99      61.00
    L07     0       86      64.80
    Q01     25      100     74.80
    Q02     84      100     91.60
    Q03     33      100     66.80
    Q04     55      100     81.00
    Q05     54      99      76.80
    Q06     77      95      86.80
    Q07     58      100     78.80
    FINAL   58      99      85.60
    WS      5       5       5.00
    

    【讨论】:

      【解决方案3】:

      这不是 awk,但GNU Datamash 是专为此类计算而设计的便捷工具:

      $ datamash -t, --header-in -g3 -s min 4 max 4 mean 4 < grades.csv \
        | awk 'BEGIN { FS=","; OFS="\t"; print "Name\tLow\tHigh\tAvg" } { $1=$1 } 1'
      Name    Low     High    Avg
      FINAL   58      99      85.6
      H01     19      90      47.8
      H02     47      95      74.6
      H03     70      95      82.2
      H04     46      80      65
      H05     54      95      76.6
      H06     58      97      80.6
      H07     52      90      67
      L01     41      91      75
      L02     0       100     61.4
      L03     88      100     95.8
      L04     48      100     79.2
      L05     0       96      46.6
      L06     0       99      61
      L07     0       86      64.8
      Q01     25      100     74.8
      Q02     84      100     91.6
      Q03     33      100     66.8
      Q04     55      100     81
      Q05     54      99      76.8
      Q06     77      95      86.8
      Q07     58      100     78.8
      WS      5       5       5
      

      好的,所以有一个 awk 位来打印所需的标题并从 CSV 转换为 TSV。

      这个调用说逗号是字段分隔符 (-t,),输入文件有一个标题行,它应该在第三列分组和排序 (-g3 -s;datamash 要求分组排序),并且对于每一组,应该计算第四列的最小值、最大值和平均值。

      【讨论】:

      • 我最近在很多答案中都使用了datamash...我发誓我不属于它;它非常适合这种难以抗拒的事情。
      • 是的,我们已经注意到了,而且答案非常巧妙。我没有玩太多,但是在你的例子之后,我的兴趣达到了顶峰,必须进一步试验它。 (很高兴看到答案匹配...)
      • 我也对 Datamash 产生了兴趣。
      猜你喜欢
      • 1970-01-01
      • 2021-01-15
      • 2023-03-15
      • 2014-06-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-09-04
      • 2021-06-18
      相关资源
      最近更新 更多