如何根据 Unix 中的阈值从文件中删除行?

如何根据 Unix 中的阈值从文件中删除行?

我有一个包含不同列的输入文件,如下所示:

VARIANT,SNP,chr,pos,A1,A2,BETA,P_value           
7:106350628_G_A,rs6977865,7,106350628,G,A,-0.0808873,8.6E-309
7:106353698_T_C,rs74804152,7,106353698,T,C,-0.0808701,9.3E-309
20:57674276_T_A,rs6026699,20,57674276,T,A,-0.0945835,6.0E-314
1:10177_A_AC,rs367896724,1,10177,A,AC,0.000264372,9.3E-01
1:10642_G_A,rs558604819,1,10642,G,A,0.0425225,7.0E-01
2:31467079_G_A,rs2295471,2,31467079,G,A,-0.0830949,8.6E-320

现在,我想删除 P 值小于 2.23E-308 的行,以获得以下输出文件:

VARIANT,SNP,chr,pos,A1,A2,BETA,P_value
1:10177_A_AC,rs367896724,1,10177,A,AC,0.000264372,9.3E-01
1:10642_G_A,rs558604819,1,10642,G,A,0.0425225,7.0E-01

我在 Unix shell 中运行了以下命令:

awk -F, '$8!"<2.23E-308"' input.file > output.file

但是,我仍然有第一个输入文件,其中包含所有行......

命令是不是错了?可能是设定的阈值识别有问题?

我正在使用Linux。

答案1

你的表达不太正确 - 应该是

a >= b

或(如果您愿意)

!(a < b)

而不是a!"<b"

然而,在您的特定情况下,存在一个更微妙的问题,即数值小于可表示为双精度(64 位)浮点数的最小值。

gawk如果您有使用 GNU MPFR/MP 库构建的GNU awk ( ) 版本,您可能需要通过-M--bignum命令行选项启用任意精度处理:

$ gawk -F, -M '$8 >= 2.23E-308' input.file
VARIANT,SNP,chr,pos,A1,A2,BETA,P_value
1:10177_A_AC,rs367896724,1,10177,A,AC,0.000264372,9.3E-01
1:10642_G_A,rs558604819,1,10642,G,A,0.0425225,7.0E-01

否则,一种可能的解决方法是在比较之前强制进行数字转换:

$ mawk -F, '$8+0 >= 2.23E-308' input.file
1:10177_A_AC,rs367896724,1,10177,A,AC,0.000264372,9.3E-01
1:10642_G_A,rs558604819,1,10642,G,A,0.0425225,7.0E-01

$ awk -F, '$8+0 >= 2.23E-308' input.file
1:10177_A_AC,rs367896724,1,10177,A,AC,0.000264372,9.3E-01
1:10642_G_A,rs558604819,1,10642,G,A,0.0425225,7.0E-01

但请注意,这将强制 IEEE double 范围之外的值为零(因为它们最初被转换为字符串,并且字符串的数值为 0)。

如果您还想要标题行,请将其添加为单独的逻辑测试:

awk -F, 'NR==1 || $8+0 >= 2.23E-308' input.file
VARIANT,SNP,chr,pos,A1,A2,BETA,P_value
1:10177_A_AC,rs367896724,1,10177,A,AC,0.000264372,9.3E-01
1:10642_G_A,rs558604819,1,10642,G,A,0.0425225,7.0E-01

答案2

这里我们将科学记数法的数字拆开,分别比较它们的指数和尾数,从而得出与阈值的比较。

awk -F ',' -v threshold=2.23E-308 '
BEGIN {
  split(threshold, t, /[Ee]/)
   pwrThreshold = t[2]
   numThreshold = t[1]
}
NR>1 {
  num = $8 ~ /[Ee]/ ? $8   \
      : sprintf("%0.6E", $8)
  split(num, a, /[Ee]/)
  pwr = a[2]
  num = a[1]
  gr8 = pwr > pwrThreshold ? 1 \
      : pwr < pwrThreshold ? 0 \
      : num > numThreshold ? 1 \
      : 0;
}
gr8||NR==1
' file.csv

结果:-

VARIANT,SNP,chr,pos,A1,A2,BETA,P_value
1:10177_A_AC,rs367896724,1,10177,A,AC,0.000264372,9.3E-01
1:10642_G_A,rs558604819,1,10642,G,A,0.0425225,7.0E-01
2:31467079_G_A,rs2295471,2,31467079,G,A,-0.0830949,8.60

答案3

使用perl而不是awk

$ perl -F, -lane 'print if ($F[7] >= 2.23E-308 || $. == 1)' input.csv 
VARIANT,SNP,chr,pos,A1,A2,BETA,P_value
1:10177_A_AC,rs367896724,1,10177,A,AC,0.000264372,9.3E-01
1:10642_G_A,rs558604819,1,10642,G,A,0.0425225,7.0E-01

笔记:

  1. perl 数组从 0 开始,而不是 1,因此$F[7]每个输入行的第八个字段也是如此(相当于$8awk 中的)。
  2. $.是输入行号,相当于NRawk中的。
  3. 如果您还需要在脚本中进行任意精度浮点计算,您可能应该看看 perl 的大::浮动模块。

答案4

使用(以前称为 Perl_6)

raku -e 'put get; for lines() {.put if .split(",").[7] >= 2.23e-308};' 

上面是一个答案,它get是标题行并put打印(打印)它,然后 - 通过调用for lines()- 按行处理文件的其余部分:

  • split每个连续行都用逗号分隔,
  • 拉出零索引的7th 字段,并且
  • 执行数字比较以查看哪些行满足if条件。

输入示例:

VARIANT,SNP,chr,pos,A1,A2,BETA,P_value           
7:106350628_G_A,rs6977865,7,106350628,G,A,-0.0808873,8.6E-309
7:106353698_T_C,rs74804152,7,106353698,T,C,-0.0808701,9.3E-309
20:57674276_T_A,rs6026699,20,57674276,T,A,-0.0945835,6.0E-314
1:10177_A_AC,rs367896724,1,10177,A,AC,0.000264372,9.3E-01
1:10642_G_A,rs558604819,1,10642,G,A,0.0425225,7.0E-01
2:31467079_G_A,rs2295471,2,31467079,G,A,-0.0830949,8.6E-320

示例输出:

VARIANT,SNP,chr,pos,A1,A2,BETA,P_value           
1:10177_A_AC,rs367896724,1,10177,A,AC,0.000264372,9.3E-01
1:10642_G_A,rs558604819,1,10642,G,A,0.0425225,7.0E-01

在 Raku 中还有一些其他方法可以获得所需的结果。下面是类似于 @cas 发布的 Perl(5) 解决方案的代码(注意,||交替元素的顺序相反,以避免错误Cannot convert string to number):

raku -ne 'state $i=0; ++$i; .put if ( $i == 1 || .split(",").[7] >= 2.23e-308 );' 

另一种方法是使用 Rakulinesgrep例程:

raku -e 'put get; .put for lines.grep( {.split(",").[7] >= 2.23E-308} );' 

最后,如果您想要“简单”实现,请手动删除标头并运行以下代码:您将得到所需的数据行(大概您可以手动添加标头):

raku -ne '.put if .split(",").[7] >= 2.23e-308;' 

https://raku.org

相关内容