AWK:如何计算列对数?

AWK: How to get count of column couples?

我有一个由许多 列组成的 CSV 文件,每对有 code_###name_###

code_boat|name_boat|year|code_color|name_color|code_size|name_size
1|jeanneau|2000|#00f|blue|5|small
2|bavaria|2005|#00f|blue|10|big
1|jeanneau|2010|#f00|red|10|big
2|bavaria|2008|#000|white|5|small
3|fountaine-pajot|2005|#f00|red|5|small
1|jeanneau|2012|#000|white|5|small
code_boat │       name_boat │ year │ code_color │ name_color │ code_size │ name_size
──────────┼─────────────────┼──────┼────────────┼────────────┼───────────┼───────────
        1 │        jeanneau │ 2000 │       #00f │       blue │         5 │     small
        2 │         bavaria │ 2005 │       #00f │       blue │        10 │       big
        1 │        jeanneau │ 2010 │       #f00 │        red │        10 │       big
        2 │         bavaria │ 2008 │       #000 │      white │         5 │     small
        3 │ fountaine-pajot │ 2005 │       #f00 │        red │         5 │     small
        1 │        jeanneau │ 2012 │       #000 │      white │         5 │     small

我需要统计这些couples被使用了多少次,并保留couple索引:

couple_index │  code │            name │ count
─────────────┼───────┼─────────────────┼───────
           0 │     1 │        jeanneau │     3
           0 │     2 │         bavaria │     2
           0 │     3 │ fountaine-pajot │     1
           2 │  #000 │           white │     2
           2 │  #f00 │             red │     2
           2 │  #00f │            blue │     2
           4 │     5 │           small │     4
           4 │    10 │             big │     2
0|1|jeanneau|3
0|2|bavaria|2
0|3|fountaine-pajot|1
2|#000|white|2
2|#f00|red|2
2|#00f|blue|2
4|5|small|4
4|10|big|2

我知道如何与 awk 一对一地完成,但我想一次完成,因为 csv 文件非常大。

awk -F'|' '{c[" "]++} END{for (i in c) {if (c[i]>0) print i,c[i]}}' myfile.csv

我将按如下方式实现对计数,令 file.txt 内容为

boat_CODE |         boat_NAME | color_CODE | color_NAME | size_CODE | size_NAME
        1 |          jeanneau |       #00f |       blue |         5 |     small
        2 |           bavaria |       #00f |       blue |        10 |       big
        1 |          jeanneau |       #f00 |        red |        10 |       big
        2 |           bavaria |       #000 |      white |         5 |     small
        3 |   fountaine-pajot |       #f00 |        red |         5 |     small
        1 |          jeanneau |       #000 |      white |         5 |     small

然后

awk 'BEGIN{FPAT="[^[:space:]|]+"}NR>1{for(i=1;i<=NF;i+=2){c[$i" "$(i+1)]+=1}}END{for(i in c){printf "%-25s%s\n",i,c[i]}}' file.txt

输出

10 big                   2
#00f blue                2
#f00 red                 2
2 bavaria                2
5 small                  4
3 fountaine-pajot        1
#000 white               2
1 jeanneau               3

说明:我通知 GNU AWK 该字段由一个或多个 (+) 个字符组成,这些字符不是 (^) 白色 space ([:space:]) 和 |。然后对于第一行 (NR>1) 之后的每一行,我使用 for 循环进行迭代,步长为 2,并增加数组 c 中的值,键是此列值和 [=39= 的串联] 和下一列值。在处理所有行后,我 printf key-value 对来自数组 c,键在长度为 25 的字符串中左对齐(随意更改它以满足您的需要)。 免责声明:此解决方案假定永远没有白色space 内部值。

(在 gawk 4.2.1 中测试)

如果情侣总是挨着的,你可以很容易地用一个循环来做:

awk 'BEGIN{FS=OFS="|"}
     (FNR>2){for(i=1;i<=NF;i+=2) { k=$i OFS $(i+1); c[k]++; d[k] = i } }
     END{for (k in c) print d[k],k,c[k] }' file

这不会解决可能是结果错位或拼写错误的问题。

如果 table 有与当前问题无关的中间列,最重要的是首先处理 header:

awk 'BEGIN{FS=OFS="|"}
     (FNR==1) { for(i=1;i<=NF;++i) if ($i ~ /_CODE *$/) { idx[i] } }
     (FNR>2)  { for(i in idx) { k=$i OFS $(i+1); c[k]++; d[k] = i } }
     END{for (k in c) print d[k],k,c[k] }' file

Assumptions/Understandings:

  • 根据 OP 的评论,实际数据文件是 pipe-delimited,字段中没有 leading/trailing 个空格(请参阅修改后的输入文件 - 下面)
  • 输出将以相同的格式生成(即,pipe-delimited,字段中没有 leading/trailing 个空格)

示例输入文件:

$ cat myfile.csv
boat_CODE|boat_NAME|color_CODE|color_NAME|size_CODE|size_NAME
1|jeanneau|#00f|blue|5|small
2|bavaria|#00f|blue|10|big
1|jeanneau|#f00|red|10|big
2|bavaria|#000|white|5|small
3|fountaine-pajot|#f00|red|5|small
1|jeanneau|#000|white|5|small

注意: 需要返回并根据文件中实际存在的头记录(如果有)修改代码


一个 GNU awk 使用数组的数组(又名 multi-dimensional 数组)的想法:

awk '
BEGIN { FS=OFS="|" }
NR>1  { for (i=1;i<=NF;i+=2)
            counts[(i-1)][$i][$(i+1)]++
      }
END   { print "couple_index","CODE","NAME","count"
        for (ndx=0;ndx<NF;ndx+=2)
            for (code in counts[ndx])
                for (name in counts[ndx][code])
                    print ndx,code,name,counts[ndx][code][name]
      }
' myfile.csv

这会生成:

couple_index|CODE|NAME|count
0|1|jeanneau|3
0|2|bavaria|2
0|3|fountaine-pajot|1
2|#000|white|2
2|#00f|blue|2
2|#f00|red|2
4|5|small|4
4|10|big|2

OP 在评论中提到它们在 macOS 上是 运行;假设 GNU awk 不可用,我们可以使用 multi-value 散列作为 single-dimensional 数组的索引,例如:

awk '
BEGIN { FS=OFS="|" }
NR>1  { for (i=1;i<=NF;i+=2)
            counts[(i-1) FS $i FS $(i+1)]++
      }
END   { print "couple_index","CODE","NAME","count"
        for (i in counts)
            print i,counts[i]
      }
' myfile.csv

这会生成:

couple_index|CODE|NAME|count
0|3|fountaine-pajot|1
2|#f00|red|2
4|5|small|4
0|1|jeanneau|3
4|10|big|2
2|#000|white|2
0|2|bavaria|2
2|#00f|blue|2

排序:

如果需要对结果进行排序,在 bash 中通过 sort 命令可能会更容易:

  • 从两个 awk 解决方案中删除 print "couple_index","CODE","NAME","count";而是将其移至命令行
  • awk 结果传送到 sort

一个想法:

echo "couple_index|CODE|NAME|count"                      > result.csv
awk '.....' myfile.csv | sort -t'|' -k1,1n -k2,2V -k3,3 >> result.csv

两个 awk 解决方案都会生成:

$ cat result.csv
couple_index|CODE|NAME|count
0|1|jeanneau|3
0|2|bavaria|2
0|3|fountaine-pajot|1
2|#000|white|2
2|#00f|blue|2
2|#f00|red|2
4|5|small|4
4|10|big|2