列の要素の発生頻度に基づいて行を選択する方法

列の要素の発生頻度に基づいて行を選択する方法

ご覧のとおり、タブで区切られたデータには約4,000行と10列のファイルがあります。

ファイルの2番目の列にはさまざまな組織が記録されます。

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
...
...

10回以上表示されるすべての組織情報を印刷できることを願っています。

しかし、このようにして中間ファイルを生成することは非効率的だと思います。もっと簡潔で効率的な方法がありますか?

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

1つの方法は、入力ファイルを2回処理することです。

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

注:カスタムawk変数は、レコードを出力する必要があるターゲット列IDの要素の最小繰り返し頻度を設定および指定するためfrqに使用されます。colId


別の方法は、入力ファイルを処理することです。一度そしてただ数行バッファリングipnutデータが次のように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() }' infile

2番目のフィールドでソートされていない場合は、まずソートしてから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を試すことに興味があるかもしれません。 1つの利点は、他の言語の組み合わせを使用している同僚とデータを交換するための組み込みの高度なUnicodeサポートです。

上記はハッシュを宣言し(2番目の列)をキーに、(すべての列)を値として使用して%h自動的にハッシュを切り取りますlines(ヘッダ行をping)。skipハッシュには重複キーが存在できないため、2番目の列の各個々の組織の下に行が追加されます。すべての行が処理された後、ハッシュは配列内のスカラーキーと値に入力されます。印刷のみ(例: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")に使用してください。これらを1つにまとめると(上記と同じ出力が提供されますが、今タブで区切られます):

~$ 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

関連情報