我有一个包含 250k 行和 10 列的文件,例如:
img1 aa bb cc ...
img2 aa yy dd ...
img3 uu bb ee ...
img4 NA bb tt ...
我想要一个脚本将该文件转换为:
img1 1 1 1 ...
img2 1 2 2 ...
img3 2 1 3 ...
img4 0 1 4 ...
第一列之后的每一列中的唯一值应替换为从 0 开始的唯一标识符,其中 0 是为字符串“NA”保留的。
另外,对于每一列,我想生成一个包含映射的文件。例如,第二列的文件应为:
NA 0
aa 1
uu 2
谁能为此提出一个优雅的解决方案?任何帮助将不胜感激。
答案1
这是一个非常简单的方法。使用 gawk 3.1.7 对我来说效果很好。
#!/usr/bin/awk -f
{
for(x=2;x<=NF;x++) {
if(x$x in a) {
$x=a[x$x]
} else {
if($x=="NA") {
print $x,0 > "column"x
a[x$x]=0
$x="0"
} else {
m[x]++
print $x,m[x] > "column"x
a[x$x]=m[x]
$x=m[x]
}
}
}
print $0 > "results"
}
答案2
$ awk 'BEGIN { id["NA"] = ++n } { for (i=2; i<=NF; ++i) { id[$i] || id[$i] = ++n; $i = id[$i] - 1 } } { print } END { for (i in id) { print i, id[i] - 1 >"map" } }' file
img1 1 2 3
img2 1 4 5
img3 6 2 7
img4 0 2 8
img
这会为除第一个列( -column)之外的所有列中的每个值分配一个唯一的 ID 。我选择使 ID 全局唯一,而不是仅对列唯一,因为这会减少必须生成的所需映射文件的数量。
剧本揭晓:
BEGIN { id["NA"] = ++n }
{
for (i=2; i<=NF; ++i) {
id[$i] || id[$i] = ++n;
$i = id[$i] - 1
}
}
{ print }
END {
for (i in id) {
print i, id[i] - 1 >"map"
}
}
它首先为字符串分配NA
ID 1(输出前 ID 始终减 1),并将计数器更新n
为 1。 n
将始终是分配给前一个字符串的 ID。
对于每个输入行,我们迭代字段。如果没有为当前字段中的字符串分配 ID,我们会分配一个 ID 并就地修改该字段。
然后打印该行及其修改后的字段。
最后,所有字符串及其对应的 ID 都存储在名为map
.
对于给定的输入,该文件可能类似于
bb 2
ee 7
cc 3
NA 0
tt 8
dd 5
yy 4
aa 1
uu 6
答案3
GNUawk
解决方案(需要二维数组支持)。
awk '{
printf "%s ", $1;
for(i = 2; i <= NF; i++) {
filename = "column_" i - 1 "_mapping"
if(NR == 1) {
arr[i]["NA"] = 0;
print "NA 0" > filename;
}
if(! ($i in arr[i]) ) {
cnt[i]++;
arr[i][$i] = cnt[i];
print $i, cnt[i] > filename;
}
printf "%d ", arr[i][$i];
}
print "";
}' input.txt
输入
img1 aa bb cc
img2 aa yy dd
img3 uu bb ee
img4 NA bb tt
输出
img1 1 1 1
img2 1 2 2
img3 2 1 3
img4 0 1 4
映射文件的内容
tail -n +1 -- *_mapping
==> column_1_mapping <==
NA 0
aa 1
uu 2
==> column_2_mapping <==
NA 0
bb 1
yy 2
==> column_3_mapping <==
NA 0
cc 1
dd 2
ee 3
tt 4