![删除总和为零的列](https://linux22.com/image/93565/%E5%88%A0%E9%99%A4%E6%80%BB%E5%92%8C%E4%B8%BA%E9%9B%B6%E7%9A%84%E5%88%97.png)
我有一个数字表,即所有单元格都有数字。它是一个制表符分隔的文件,具有非数字标题和行名称。我需要删除所有加起来为零的列。我想保留第一列(行名称)以及未删除的其余列的标题。
输入
a b c d
e 1 2 0
f 3 4 0
g 5 6 0
输出
a b c
e 1 2
f 3 4
g 5 6
类似的问题,但有行:删除总和为零的行
awk解决方案会很棒;我想避免在 R 中加载大文件。
答案1
去除柱子
awk:
{ for(i=1;i<=NF;i++) { line[NR][i]=$i ; col[i]+=$i ;} }
END {
for ( l=1 ; l<=NR ; l++ )
{
printf line[l][1] "\t" ;
for (c=2;c<=NF;c++) if (col[c]) printf line[l][c] "\t" ;
printf "\n" ;
}
}
在哪里
{ for(i=1;i<=NF;i++) { line[NR][i]=$i ; col[i]+=$i ;} }
存储所有行(包括列名称)。END
如果 count != 0 则子句打印所有列。- 请注意,所有数据都保存在内存中。
测试:
awk -f c.awk a
a b c
e 1 2
f 3 4
g 5 6
对于线路解决方案...
尝试
awk 'NR==1 {print } NR>1 { s=0 ; for(i=1;i<=NF;i++) s+=$i ; if (s) print ;}'
在哪里
NR==1 {print }
打印标题NR>1 { s=0 ; for(i=1;i<=NF;i++) s+=$i ; if (s) print ;}
测试是否为 0,如果不是则打印i=2
如果第一列是行名称,您可以从开始。- 小心浮点数,它们的和可能不等于 0。
请注意,这将输出行,而不是从原始文件中删除。
答案2
perl
如果你想保持间距,可能会更容易:
perl -lne '
$i = 0;
for (/\S+\s*/g) {
$cell[$.][$i] = $_;
$sum[$i++] += $_
}
END{
@keep=(0, grep {$sum[$_]} (1..$#sum));
print((@{$cell[$_]})[@keep]) for (1..$.)
}'
这会将整个文件加载到内存中。为了避免这种情况,您需要在文件中传递两次。
awk
这可以通过和 的组合来完成sed
:
awk '
NR>1{for (i=2; i<=NF; i++) sum[i]+=$i; if (NF>n) n = NF}
END {
for (;n>1;n--)
if (!sum[n])
print "s/[^[:blank:]]\\{1,\\}[[:blank:]]*//" n
}' < file | sed -f - file
awk
生成sed
脚本以删除总和为 0 的列。这些命令将删除这些列,同时保留其他列的间距,但这会非常昂贵,如果性能是一个问题,s/[^[:blank:]]\{1,\}[[:blank:]]*//3
sed
您可能需要进行剥离。perl
对于行来说,这要容易得多:
perl -MList::Util=sum -lane 'print if $. == 1 or sum @F'
答案3
由于这些值始终是整数,因此您可以执行以下操作:
cut $(awk 'NR>1{for(i=2;i<=NF;i++) s[i]+=$i}END{printf("%s", "-f 1");
for (i=2;i<=NF;i++) {if (s[i]) printf(",%s", i)}}' infile) infile
这会读取文件两次:awk
获取总和不为零的列号;然后使用它们来cut
仅打印所需的列。