【问题标题】:AWK: multi-step filtering of data based on the selected columnAWK:根据所选列对数据进行多步过滤
【发布时间】:2021-06-23 08:24:37
【问题描述】:

我正在处理固定格式排列的多列CSV的后处理:第一列对应行号(ID),第二列包含其人口(POP,样本数落入此ID) 和第三列 (dG) 表示此 ID 的某些固有值(始终为负):

ID, POP, dG
1, 7, -9.6000
2, 3, -8.7700
3, 6, -8.6200
4, 4, -8.2700
5, 6, -8.0800
6, 10, -8.0100
7, 9, -7.9700
8, 8, -7.8400
9, 16, -7.8100
10, 2, -7.7000
11, 1, -7.5600
12, 2, -7.5200
13, 9, -7.5100
14, 1, -7.5000
15, 2, -7.4200
16, 1, -7.3300
17, 1, -7.1700
18, 4, -7.1300
19, 3, -6.9200
20, 1, -6.9200
21, 2, -6.9100
22, 2, -6.8500
23, 10, -6.6900
24, 2, -6.6800
25, 1, -6.6600
26, 20, -6.6500
27, 1, -6.6500
28, 5, -6.5700
29, 3, -6.5500
30, 2, -6.4600
31, 2, -6.4500
32, 1, -6.3000
33, 7, -6.2900
34, 1, -6.2100
35, 1, -6.2000
36, 3, -6.1800
37, 1, -6.1700
38, 4, -6.1300
39, 1, -6.1000
40, 2, -6.0600
41, 3, -6.0600
42, 8, -6.0200
43, 2, -6.0100
44, 1, -6.0100
45, 1, -5.9800
46, 2, -5.9700
47, 1, -5.9300
48, 6, -5.8800
49, 4, -5.8300
50, 4, -5.8000
51, 2, -5.7800
52, 3, -5.7200
53, 1, -5.6600
54, 1, -5.6500
55, 4, -5.6400
56, 2, -5.6300
57, 1, -5.5700
58, 1, -5.5600
59, 1, -5.5200
60, 1, -5.5000
61, 3, -5.4200
62, 4, -5.3600
63, 1, -5.3100
64, 5, -5.2500
65, 5, -5.1600
66, 1, -5.1100
67, 1, -5.0300
68, 1, -4.9700
69, 1, -4.7700
70, 2, -4.6600

为了减少行数,我过滤了这个 CSV,目的是在第二列 (POP) 中搜索编号最高的行,使用以下 AWK 表达式:

# search CSV for the line with the highest POP and save all linnes before it, while keeping minimal number of the linnes (3) in the case if this line is found at the begining of CSV.
awk -v min_lines=3 -F ", " 'a < $2 {for(idx=0; idx < i; idx++) {print arr[idx]} print $0; a=int($2); i=0; printed=NR} a > $2 && NR > 1 {arr[i]=$0; i++}END{if(printed <= min_lines) {for(idx = 0; idx <= min_lines - printed; idx++){print arr[idx]}}}' input.csv > output.csv

对于简单的情况,当具有最大 POP 的字符串位于第一行时,脚本将保存该行 (POP max) +2 行(=min_lines=3)。

对于更复杂的情况,如果具有 POP max 的行位于 CSV 的中间,脚本会检测该行 + 从 CSV 开头的所有前面的行,并在新的 CSV 中列出它们,保持原始顺序.但是,在这种情况下 output.csv 将包含太多行,因为搜索字符串(具有最高 POP)位于第 26 行:

ID, POP, dG
1, 7, -9.6000
2, 3, -8.7700
3, 6, -8.6200
4, 4, -8.2700
5, 6, -8.0800
6, 10, -8.0100
7, 9, -7.9700
8, 8, -7.8400
9, 16, -7.8100
10, 2, -7.7000
11, 1, -7.5600
12, 2, -7.5200
13, 9, -7.5100
14, 1, -7.5000
15, 2, -7.4200
16, 1, -7.3300
17, 1, -7.1700
18, 4, -7.1300
19, 3, -6.9200
20, 1, -6.9200
21, 2, -6.9100
22, 2, -6.8500
23, 10, -6.6900
24, 2, -6.6800
25, 1, -6.6600
26, 20, -6.6500

为了将输出 CSV 中的总行数减少到 3-5 行,如何自定义我的过滤器以仅保存差异很小的行(例如pop 列应该匹配 (POP >0.5 max(POP)) ,同时将每一行与 POP 列中具有最大值的行进行比较?最后,我需要始终保留第一行以及输出中具有最大值的行。所以 AWK 的解决方案应该按以下方式过滤多字符串 CSV(请忽略#中的注释):

ID, POP, dG 
1, 7, -9.6000    
9, 16, -7.8100
26, 20, -6.6500 # this is POP max detected over all lines

【问题讨论】:

  • 但是为什么6, 10, -8.0100 不在输入中,因为10 正好是20 的50%?
  • 问题似乎不一致或缺乏细节。您究竟希望结果在 3-5 行中如何?保留 POP ≤ 0.5 max(POP) 的记录不会导致您发布的输出,因为它只会删除第 9 条记录。
  • 感谢指正!事实上,我的意思是我需要在输出中保留 pop D 接近 pop MAX (POP > 0.5 max(POP)) 的行,所以它不适合 POP= 10 的行,但肯定会占用第 9 行( POP=16),如预期输出所示。
  • 请将您的示例简化为minimal reproducible example。任何带有滚动条的示例输入/输出都不是最小的,因此浪费我们的时间试图理解它。使用 10 行输入而不是 70 行输入,您可能会演示您寻求帮助的任何问题。

标签: csv awk


【解决方案1】:

这两个阶段awk 应该适合你:

awk -F ', ' -v n=2 'NR == 1 {next}
FNR==NR { if (max < $2) {max=$2; if (FNR==n) n++} next}
FNR <= n || $2 > (.5 * max)' file file

ID, POP, dG
1, 7, -9.6000
9, 16, -7.8100
26, 20, -6.6500

【讨论】:

  • 效果很好!但是当 POP max 的行是第一行时存在一个错误(无论值如何,都应该在输出中使用)它需要一些 POP 远低于 50% 的行
  • 是的,这行得通!抱歉,更准确一点-如果在第一行找到 pop(max) in,是否可以像现在一样将前两行保留在输出中而不是第一行? :-)
  • 像这样,无论 popMAX 的位置如何,它总是占用前两行,而我只会在第一行找到 popMAX 的情况下这样做(所以只占用前两行在这种情况下忽略 POP 列)...
  • 抱歉,这就是不鼓励在 cmets 中添加要求的原因。请为此使用示例输入和预期输出更新问题,然后我可以相应地修改答案。
  • 没问题,实际上现在的版本也很酷,所以我接受了!再次感谢你。干杯
猜你喜欢
  • 2011-03-24
  • 2021-06-19
  • 2021-07-03
  • 2021-03-29
  • 1970-01-01
  • 1970-01-01
  • 2023-01-26
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多