【问题标题】:Rearanging data file in AWK providing subtotals and totals在 AWK 中重新排列数据文件,提供小计和总计
【发布时间】:2022-02-12 02:07:27
【问题描述】:

我有以下数据:

cat st_in.txt 
2015-01-01  2   A   FI
2015-02-03  4   B   VI
2015-03-01  6   A   FI
2015-01-08  -4  C   VE
2016-01-05  -3  B   VE
2016-02-03  -1  D   FE
2016-04-01  -2  B   FE
2016-06-13  -5  D   VE
2017-01-01  2   A   VI
2017-02-03  3   A   VI
2017-02-04  8   C   FI
2017-01-05  -1  B   FE

我想这样输出数据(当然不用cmets):

        2015    2016    2017    # ...

A       0       0       5       # $2>0 && $4~/VI/       Ordered alphabetically asc
B       4       0       0       #       .                       .
sumVI   4       0       5

A       8       0       0       # $2>0 && $4~/FI/               .
C       0       0       8       #       .                       .
sumFI   8       0       8

sumI    12      0       13      # sumI=sumFI+sumVI

B       0       -3      0       # $2<0 && $4~/VE/               .
C       -4      0       0       #       .                       .
D       0       -5      0       #       .                       .
sumVE   -4      -8      0

B       0       -2      -1      # $2<0 && $4~/FE/               .
sumFE   0       -2      -1      #       .

sumE    -4      -10     -1      # sumE=sumFE+sumVE

NET     8       -10     12      # NET=sumI+sumE

我是 awk 的新手,不知道如何处理这个问题。我在 gnu.org awk 上阅读了关于多维数组和数组数组的手册,我认为我会在这里需要,但不完全理解它们是如何工作的。我可以这样做一年,但不是多年。请注意,st_in.txt 非常大,并且比本示例中的跨度更长。还有一个很好的资源,您可以推荐学习如何在 awk 中透视数据表。

这是我迄今为止所尝试的。然而这不起作用:

cat trans1
#!/usr/bin/env bash

awk '
    BEGIN{OFS="\t"
    cat[$3]
    height[$4][$3] +=$2
    width[substr($1,1,4)][$4][$3] +=$2
    }

    END{
    PROCINFO["sorted_in"]="@ind_str_asc";
    for (width in height){
        for (cat in height[width]){
            if($2>0 && $4~/VI/)
                {print cat, height[width]}
            else if($2>0 && $4~/FI/)
                {print cat, height[width]}
            else if($2<0 && $4~/VE/)
                {print cat, height[width]}
            else {print cat, height[width]}}}

    }
' "${@:--}"

我收到以下错误:

awk: cmd. line:11: (FILENAME=st_in.txt FNR=12) fatal: attempt to use array `width' in a scalar context

【问题讨论】:

  • 您是否偶然看到了诸如this one 之类的现有问题?如果是这样,您可能会考虑编辑您的问题并提及您在搜索时发现的内容以及为什么它在您的情况下不起作用,或者类似的内容。此外,如果您进行了任何编码尝试,您可能会考虑包含您的代码,即使它不起作用。
  • 对不起,大卫,我一直很忙。我已经对我的代码进行了修正更新。

标签: awk


【解决方案1】:

在这一行:

width[substr($1,1,4)][$4][$3] +=$2

您将 width 声明为一个数组,因此您不能在此行使用相同的名称:

for (width in height){

作为标量(另一个数组的索引,height)。只需将第二个更改为wid 或其他名称即可消除错误消息。显然将width 更改为wid,其中也用作循环内height[] 的索引。


以此为起点,我选择了更能代表它们所包含内容的变量名称(尽管我不知道您的第 4 列代表什么,所以我只是将其命名为 box - 更改为有意义的名称)以尝试提供帮助作为调试和增强代码的第一步,您了解每个代码在整个代码中的含义:

$ cat trans1
#!/usr/bin/env bash

awk '
    BEGIN { OFS="\t" }
    {
        year   = substr($1,1,4)
        height = $2
        cat    = $3
        box    = $4

        cats[cat]
        boxCat_2_Heights[box][cat] += height
        yearBoxCat_2_Widths[year][box][cat] += height
    }

    END {
        PROCINFO["sorted_in"]="@ind_str_asc"
        for (box in boxCat_2_Heights) {
            for (cat in boxCat_2_Heights[box]) {
                height = boxCat_2_Heights[box][cat]

                if      (height>0 && box~/VI/) { type = "type1" }
                else if (height>0 && box~/FI/) { type = "type2" }
                else if (height<0 && box~/VE/) { type = "type3" }
                else                           { type = "type4" }

                print box, cat, height, type
            }
        }
    }
' "${@:--}"

$ ./trans1 st_in.txt
FE      B       -3      type4
FE      D       -1      type4
FI      A       8       type2
FI      C       8       type2
VE      B       -3      type3
VE      C       -4      type3
VE      D       -5      type3
VI      A       5       type1
VI      B       4       type1

我并不是说上面是你真正想要的,只是它做了你现有代码试图做的事情,但使用有意义的名称和有效的语法。这是你的起点。

【讨论】:

  • 我这样做了,但仍然收到相同的错误消息。
  • 您是否也没有更改在循环中使用它的索引名称,例如print cat, height[width] -> print cat, height[wid]?
  • 我已将 END 块中的所有“宽度”关闭为“宽度”,但仍然收到相同的错误消息。
  • 请在此处复制/粘贴确切的错误消息,坦率地说我不相信。
  • @EdMorton 在你的END 块中不应该是if (height&gt;0 &amp;&amp; box~/VI/) { type = "type1" } 而不是if (cat&gt;0 &amp;&amp; box~/VI/) { type = "type1" }?以下 2 行相同。
【解决方案2】:

不是完整的解决方案,而是更结构化的方法,需要最终格式化...

$ awk 'BEGIN {SUBSEP=FS} 
             {split($1,f1,"-"); 
              s=substr($4,2); y=f1[1]; 
              a[s,$4,y,$3]=+$2; 
              a[s,$4,y,"sum"$4]+=$2; 
              a[s,"+",y,"sum"s]+=$2;
              a["+","+",y,"NET"]+=$2} 
       END   {for(k in a) print k,a[k]}' file | 
 sort -k1,2r -k4,4 -k3,3

I VI 2017 A 3
I VI 2015 B 4
I VI 2015 sumVI 4
I VI 2017 sumVI 5
I FI 2015 A 6
I FI 2017 C 8
I FI 2015 sumFI 8
I FI 2017 sumFI 8
I + 2015 sumI 12
I + 2017 sumI 13
E VE 2016 B -3
E VE 2015 C -4
E VE 2016 D -5
E VE 2015 sumVE -4
E VE 2016 sumVE -8
E FE 2016 B -2
E FE 2017 B -1
E FE 2016 D -1
E FE 2016 sumFE -3
E FE 2017 sumFE -1
E + 2015 sumE -4
E + 2016 sumE -11
E + 2017 sumE -1
+ + 2015 NET 8
+ + 2016 NET -11
+ + 2017 NET 12

这会根据数组的键创建各种小计,最后打印整个数组(和小计)。通过仔细选择密钥,您可以计算出您需要什么。

s 是顶级类别,y 是年份。

              a[s,$4,y,$3]=+$2; 

总结所有重复的条目,因为使用了所有字段

              a[s,$4,y,"sum"$4]+=$2; 

基于字段 4 值(VI、FI 等)的组

              a[s,"+",y,"sum"s]+=$2;

基于顶级类别 (I,E) 的组

              a["+","+",y,"NET"]+=$2} 

这总结了基于年份的所有内容。终于

  END   {for(k in a) print k,a[k]}

在文件末尾,从数组中提取所有条目并打印。

  sort -k1,2r -k4,4 -k3,3

根据 I/E VI、FI A/B/.. 和年份进行排序。

例如,作为一个练习,您可以通过删除或将y 替换为常量(我使用+)来轻松添加总和。

【讨论】:

  • 感谢 karakfa 的努力。你能帮我理解这些数组是如何工作的吗?例如s 和 $4 不是一回事吗?当您在 a 中循环 k 时,是否会为文件中的每一行调用 a[s... 的每一行?抱歉,我正试图将注意力集中在多维数组上。您的代码有效,但我不确定如何获得每年的总和列。
  • 添加了解释。请注意,每年的总和已经带有“NET”标签。如果您不熟悉,请不要将它们视为多维数组。这就是保存(键,值)对,其中键是小计的唯一条目。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-06-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多