1列の値に基づいて行をマージしたいと思います。私のデータは次のとおりです(タブで区切られた列)。
OG FC_AG_NICO FC_AG_ZEA FC_AG_BRAS FC_MB_NICO FC_MB_ZEA FC_MB_BRAS FC_TN_NICO FC_TN_ZEA FC_TN_BRAS FC_SL_NICO FC_SL_ZEA FC_SL_BRAS FC_SE_NICFC_SE_ZEA FC_SE_BRAS
OG0004400 -0.787302663 -0.710790578 0.663333543
OG0004400 -1.659046364 -1.019969932 0.588969542
OG0004400 -0.373838773 0.277055943 0.481626213
OG0004400 -0.360799687 -0.0958126 0.056722264
OG0004400 -1.77626686 -0.971114297 0.707963822
OG0004402 -0.304209641 -0.259080399 0.44366888
OG0004402 0.442748804 0.042958499 -0.316412832
OG0004402 -0.274550145 0.1933262 0.374095809
OG0004402 0.253000346 0.338511357 -0.121760564
したがって、同じOG番号を共有する行は1つの行にマージする必要があります。列ごとに1つの値しかないため、倍精度に問題はありません。
いくつかの異なる投稿で同様の問題を扱っており、この回答が非常に役に立つと思って少し編集しました。ただし、ここではすべての値が互いに直後に記録されます。ただし、列値は同じ位置に維持されることが重要です。
awk '{if(NR!=1){a[$1]=$2"\t"a[$1]} else print $0} END {n = asorti(a, b); for (n in b) {print b[n],a[b[n]]}}'
誰でも上記のコードを編集するのに役立ちますか?
答え1
awk 'BEGIN{FS="\t"} NR==1{print; next} {a[$1]=$1; for(i=2; i<=NF; i++){if($i!="") {f[$1,i]=$i; if(i>last[$1]){last[$1]=i}}} } END{for(j in a){printf("%s", a[j]); for(k=2; k<=last[j]; k++){printf("%s%s", FS, f[j,k])} print ""}}' file
awk 'BEGIN{FS="\t"} # use tab as field separator
NR==1{print; next} # print header
{
a[$1]=$1 # save first column in current row
for(i=2; i<=NF; i++){ # loop with all columns but first
if($i!=""){ # if column not empty
f[$1,i]=$i # save content to array
if(i>last[$1]){
last[$1]=i # save number of last element in current row
}
}
}
}
END{
for(j in a){
printf("%s", a[j]) # print first element
for(k=2; k<=last[j]; k++){ # print second to last element
printf("%s%s", FS, f[j,k])
}
print ""
}
}' file
配列にはa
最初の列が含まれています。配列には、f
最初の列がない行が含まれています。配列には、last
現在の行の最後の要素の位置が含まれます。
まだテストされていません。
答え2
1つの方法は次のとおりです。
$ awk -F"\t" '{if(NR==1){ cols=NF; print; } else{for(i=2;i<=NF;i++){if(length($i)>0){data[$1][i]=$i}}}}END{for(id in data){ printf "%s",id; for(i=2;i<=cols;i++){printf "\t%s", data[id][i]} print ""}}' file
OG FC_AG_NICO FC_AG_ZEA FC_AG_BRAS FC_MB_NICO FC_MB_ZEA FC_MB_BRAS FC_TN_NICO FC_TN_ZEA FC_TN_BRAS FC_SL_NICO FC_SL_ZEA FC_SL_BRAS FC_SE_NICFC_SE_ZEA FC_SE_BRAS
OG0004400 -0.787302663 -0.710790578 0.663333543 -0.360799687 -0.0958126 0.056722264 -1.77626686 -0.971114297 0.707963822 -0.373838773 0.277055943 0.481626213 -1.659046364 -1.019969932
OG0004402 -0.304209641 -0.259080399 0.44366888 0.253000346 0.338511357 -0.121760564 -0.274550145 0.1933262 0.374095809 0.442748804 0.042958499
または読みやすくなります。
awk -F"\t" '{
## Print the headers and store the number of columns.
if(NR==1){
cols=NF;
print;
}
else{
## Iterate over all columns, starting from the 2nd.
for(i=2;i<=NF;i++){
## If this one isn't empty, store it.
if(length($i)>0){
data[$1][i]=$i
}
}
}
}
## After reading everything, print.
END{
for(id in data){
printf "%s",id;
for(i=2;i<=cols;i++){
printf "\t%s", data[id][i]
}
print ""
}
}' file
これは、各ID(最初のフィールド)に行の各列の値があり、1行にのみ値があると想定します。空のIDを持つ列がある場合は、少し異なるアプローチが必要です。
答え3
完了したら、より多くの内容がありますawk
。
連想配列を繰り返すと、フィールド出力がめちゃくちゃになるのではないかと思いましたが、for (f in fields)
最大20フィールドまでいくつかのテストを実行した後はそうではありません。
ヘッダーが行1にあると仮定すると、データが投稿に基づいてソートされ、同時にメモリに保存したくない大量のデータがあります。
awk 'BEGIN{getline; split($0,out,"\t"); old=$1}
old!=$1{for (o in out) printf "%s\t", out[o]; print""; delete out;old=$1}
{split($0,tmp,"\t"); for (t in tmp) out[t]=(t==1)?tmp[t]:out[t]+tmp[t]}
END{for (o in out) printf "%s\t", out[o];}' file
この入力
head c1 c2 c3
H1 -0.71
H1 2
H1 3
H2 11 12
H2 13
与える
head c1 c2 c3
H1 -0.71 2 3
H2 11 12 13
牙
最初の行をつかみ、最初のフィールドを覚えてください。old
awk 'BEGIN{getline; split($0,out,"\t"); old=$1}
次のレコードの最初のフィールドが最後のフィールド(新しいヘッダー)と異なる場合は、最後の集計が完了したため、印刷して集計配列を消去してout
新しいコレクションにあることを確認してください。old=$1
old!=$1{for (o in out) printf "%s\t", out[o]; print""; delete out;old=$1}
$0
配列に分割して値のみを取得する場合は、ヘッダー列以外の値を追加tmp
して配列を繰り返します。tmp
out
{split($0,tmp,"\t"); for (t in tmp) out[t]=(t==1)?tmp[t]:out[t]+tmp[t]}
次に保存された最後のout
履歴セットを消去します。END
END{for (o in out) printf "%s\t", out[o];}' file