如何根据列中元素出现的频率来选择行

如何根据列中元素出现的频率来选择行

我有一个文件,如您所见,是制表符分隔的数据,大约 4,000 行,十列。

文件的第二列记录了不同的组织。

samples tissue_s tissue_e tissue_d tissue_category tissue_visa sex study tissue_f age
samples1  ear     CNS      ear       CNS            CNS        male  1   ear 365
samples2  ear     CNS      ear       CNS            CNS        male  1   ear 365
samples3  ear     CNS      ear       CNS            CNS        male  1   ear 365
samples4  ear     CNS      ear       CNS            CNS        male  1   ear 365
samples5  ear     CNS      ear       CNS            CNS        male  1   ear 365
samples6  stomach CNS      ear       CNS            CNS        male  1   ear 365
samples7  stomach CNS      ear       CNS            CNS        male  1   ear 365
samples8  stomach CNS      ear       CNS            CNS        male  1   ear 365
samples9  stomach CNS      ear       CNS            CNS        male  1   ear 365
...
...

希望能够将出现过十次以上的纸巾信息全部打印出来

但我认为这样做并生成中间文件效率很低。有没有更简洁高效的方法呢?

cat file | awk '{print $2}' | awk '{a[$0]++}END{for(i in a){if(a[i] > 10){print i}}}' > tmp.txt
grep -wFf tmp.txt file.txt > resule.txt

答案1

一种方法是处理输入文件两次:

awk -F'\t' -v frq=10 -v colId=2 '
  NR==FNR{ count[$colId]++; next }
  count[$colId] >frq
' infile infile

注意:用户定义的 awk 变量frq用于colId设置和指定应输出记录的目标 columnId 中元素的最小重复频率。


另一种方法是处理输入文件一次并且只有缓冲几行如果您的 ipnut 数据在第二个字段上排序,如下所示:

awk -F'\t' -v frq=10 -v colId=2 '
function prnt() { if(c>frq) printf("%s", buf); buf=c="" }

prev!=$colId{ prnt() }
{ c++; prev=$colId; buf = buf $0 ORS }

END{ prnt() }' infile

如果它没有在第二个字段上排序那么首先对其进行排序,然后将其传递给 awk。

<infile sort -t$'\t' -k2,2 |
awk -F'\t' -v frq=10 -v colId=2 '
function prnt() { if(c>frq) printf("%s", buf); buf=c="" }

prev!=$colId{ prnt() }
{ c++; prev=$colId; buf = buf $0 ORS }

END{ prnt() }'

答案2

使用(以前称为 Perl_6)

~$ raku -e 'my %h; do for lines.skip() {%h.push: .words.[1] => .words}; \
            for %h.kv -> $k,@v {(put $k; .put for @v) if @v.elems > 4};'  file

您可能有兴趣尝试 Perl 家族语言,即 Raku。优点之一是对内置 Unicode 的高级支持,如果您与使用不同语言排序规则的同事交换数据的话。

上面声明了一个散列,并对其进行了%h自动剪切linesskipping 标题行) ,以(第二列)作为键,(所有列)作为值。由于散列中不能存在重复的键,因此在第二列中找到的每个单独组织下添加行。处理完所有行后,散列将被键值处理为标量键和数组内的值。仅打印(即 OP 样本输入的 4 行以上)。push.words.[1].words%h%h.kv$k@a@v.elems > 4

输入示例:

samples tissue_s tissue_e tissue_d tissue_category tissue_visa sex study tissue_f age
samples1  ear     CNS      ear       CNS            CNS        male  1   ear 365
samples2  ear     CNS      ear       CNS            CNS        male  1   ear 365
samples3  ear     CNS      ear       CNS            CNS        male  1   ear 365
samples4  ear     CNS      ear       CNS            CNS        male  1   ear 365
samples5  ear     CNS      ear       CNS            CNS        male  1   ear 365
samples6  stomach CNS      ear       CNS            CNS        male  1   ear 365
samples7  stomach CNS      ear       CNS            CNS        male  1   ear 365
samples8  stomach CNS      ear       CNS            CNS        male  1   ear 365
samples9  stomach CNS      ear       CNS            CNS        male  1   ear 365

示例输出(来自上面的代码):

ear
samples1 ear CNS ear CNS CNS male 1 ear 365
samples2 ear CNS ear CNS CNS male 1 ear 365
samples3 ear CNS ear CNS CNS male 1 ear 365
samples4 ear CNS ear CNS CNS male 1 ear 365
samples5 ear CNS ear CNS CNS male 1 ear 365

调整输出以满足您的需要相当容易。put $k;如果您不想要单独的“组织”标题,请放弃呼叫。此外,将@a行输出更改为.join("\t").put for @v重新构成\t制表符分隔行。

请注意,上面的答案假设每个列条目都没有空格,因为.words在空格上分割(\t或不分割)。如果您不能保证每个列条目都是单个空格分隔的元素,请.split("\t")改为使用。将它们放在一起(提供与上面相同的输出,但现在以制表符分隔):

~$ raku -e 'my \%h; do for lines.skip() {\%h.push: .split("\t").[1] => .split("\t")}; \
            for \%h.kv -> $k,@v {($k.put; .join("\t").put for @v) if @v.elems > 4};'  file

https://docs.raku.org
https://raku.org

相关内容