我有一个包含不同列的输入文件,如下所示:
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
笔记:
- perl 数组从 0 开始,而不是 1,因此
$F[7]
每个输入行的第八个字段也是如此(相当于$8
awk 中的)。 $.
是输入行号,相当于NR
awk中的。- 如果您还需要在脚本中进行任意精度浮点计算,您可能应该看看 perl 的大::浮动模块。
答案4
使用乐(以前称为 Perl_6)
raku -e 'put get; for lines() {.put if .split(",").[7] >= 2.23e-308};'
上面是一个答案,它get
是标题行并put
打印(打印)它,然后 - 通过调用for lines()
- 按行处理文件的其余部分:
split
每个连续行都用逗号分隔,- 拉出零索引的
7
th 字段,并且 - 执行数字比较以查看哪些行满足
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 );'
另一种方法是使用 Rakulines
和grep
例程:
raku -e 'put get; .put for lines.grep( {.split(",").[7] >= 2.23E-308} );'
最后,如果您想要“简单”实现,请手动删除标头并运行以下代码:您将得到所需的数据行(大概您可以手动添加标头):
raku -ne '.put if .split(",").[7] >= 2.23e-308;'