寻找一种方法让 awk 迭代循环文件(在 bash 中创建 PERCENTRANK 函数)
Looking for a way to have awk iteratively loop through a file (to create PERCENTRANK function in bash)
我的文件 data.txt 包含以下具有数百万行的记录结构:
13
12
11
8
4
3
2
1
1
1
对于该列的每个值,我需要计算它的 PERCENTRANK(这是一个值在数据集中的排名,作为数据集的百分比)。
计算数据集中任意值X的PERCENTRANK的公式为
= number of values less than X / (Number of values less than X + Number of Values greater than X)
因此,对于数据集中的每个值 X,程序必须遍历所有记录以找出同一数据集中小于和大于 X 的值的数量。
如何使用 'awk' 重复遍历文件来计算所有 X 值的 PERCENTRANK?
预期输出:
X PERCENTRANK
13 1.0000
12 0.8888
11 0.7777
8 0.6666
4 0.5555
3 0.4444
2 0.3333
1 0.0000
1 0.0000
1 0.0000
PERCENTRANK of 2 为 0.333,因为集合中有三个值小于 2,六个大于 2。PERCENTRANK OF 2 = 3 / (3 + 6) = 3/9 = 0.3333.
同样,4 的 PERCENTRANK 为 0.5555,因为有五个值小于 4,四个大于 4。 4 的百分比 = 5 / (5 + 4) = 5 / 9 = 0.5555.
我正在避免嵌套 'while..do' 循环,因为它在遍历包含数百万条记录的文件时非常慢。
我对 awk 在许多其他迭代计算场景中的惊人速度感到满意,例如:计算平均值、标准差、按总和分组等,因此,我更希望使用 'awk' 来解决这个用例。
GNU awk
gawk '
{count[]++}
END {
print "X\tPERCENTRANK"
PROCINFO["sorted_in"] = "@ind_num_desc"
gt = 0
total = NR
for (x in count) {
lt = total - count[x] - gt
pr = lt/(gt+lt)
for (i=1; i<=count[x]; i++)
printf "%d\t%.4f\n", x, pr
gt += count[x]
}
}
' data.txt
X PERCENTRANK
13 1.0000
12 0.8889
11 0.7778
8 0.6667
4 0.5556
3 0.4444
2 0.3333
1 0.0000
1 0.0000
1 0.0000
即使对于大型数据集,这也应该非常有效:没有嵌套循环。
这依赖于 GNU awk 来设置遍历 count
数组的顺序:按数组索引排序,按数字降序排列。由于我们强制执行该顺序,因此我们可以简单地计算有多少记录大于我们当前正在查看的记录。
遵循更简单的 sort
和 awk
方法也可能对您有所帮助(尽管我没有在数百万行上测试它,因为我没有它)。
解决方案 1: 这将不会在输出中显示重复项的排名,例如--> 示例中的数字 1。
sort -nr Input_file | awk '
function sum(array){
tot="";
for(i in array){
tot+=array[i]};
return tot}
{
a[FNR]=[=10=];
b[[=10=]]++
}
END{
for(j=1;j<=FNR;j++){
if(b[a[j]]){
val=b[a[j]];
delete b[a[j]];
printf("%d %0.4f\n",a[j],sum(b)/(sum(d)+sum(b)));
d[a[j]]=val;}
}}
'
输出如下。
13 1.0000
12 0.8889
11 0.7778
8 0.6667
4 0.5556
3 0.4444
2 0.3333
1 0.0000
解决方案 2: 添加解决方案(与第一个略有不同),这将在输出中提供重复项的等级,如下所示.
sort -nr Input_file | awk '
function sum(array){
tot="";
for(i in array){
tot+=array[i]};
return tot}
{
a[FNR]=[=12=];
b[[=12=]]++
}
END{
for(j=1;j<=FNR;j++){
if(b[a[j]]){
val=val1=b[a[j]];
delete b[a[j]];
while(val1>0){
printf("%d %0.4f\n",a[j],sum(b)/(sum(d)+sum(b)));
val1--}
d[a[j]]=val;}
}}
'
13 1.0000
12 0.8889
11 0.7778
8 0.6667
4 0.5556
3 0.4444
2 0.3333
1 0.0000
1 0.0000
1 0.0000
我的文件 data.txt 包含以下具有数百万行的记录结构:
13
12
11
8
4
3
2
1
1
1
对于该列的每个值,我需要计算它的 PERCENTRANK(这是一个值在数据集中的排名,作为数据集的百分比)。
计算数据集中任意值X的PERCENTRANK的公式为
= number of values less than X / (Number of values less than X + Number of Values greater than X)
因此,对于数据集中的每个值 X,程序必须遍历所有记录以找出同一数据集中小于和大于 X 的值的数量。
如何使用 'awk' 重复遍历文件来计算所有 X 值的 PERCENTRANK?
预期输出:
X PERCENTRANK
13 1.0000
12 0.8888
11 0.7777
8 0.6666
4 0.5555
3 0.4444
2 0.3333
1 0.0000
1 0.0000
1 0.0000
PERCENTRANK of 2 为 0.333,因为集合中有三个值小于 2,六个大于 2。PERCENTRANK OF 2 = 3 / (3 + 6) = 3/9 = 0.3333.
同样,4 的 PERCENTRANK 为 0.5555,因为有五个值小于 4,四个大于 4。 4 的百分比 = 5 / (5 + 4) = 5 / 9 = 0.5555.
我正在避免嵌套 'while..do' 循环,因为它在遍历包含数百万条记录的文件时非常慢。
我对 awk 在许多其他迭代计算场景中的惊人速度感到满意,例如:计算平均值、标准差、按总和分组等,因此,我更希望使用 'awk' 来解决这个用例。
GNU awk
gawk '
{count[]++}
END {
print "X\tPERCENTRANK"
PROCINFO["sorted_in"] = "@ind_num_desc"
gt = 0
total = NR
for (x in count) {
lt = total - count[x] - gt
pr = lt/(gt+lt)
for (i=1; i<=count[x]; i++)
printf "%d\t%.4f\n", x, pr
gt += count[x]
}
}
' data.txt
X PERCENTRANK
13 1.0000
12 0.8889
11 0.7778
8 0.6667
4 0.5556
3 0.4444
2 0.3333
1 0.0000
1 0.0000
1 0.0000
即使对于大型数据集,这也应该非常有效:没有嵌套循环。
这依赖于 GNU awk 来设置遍历 count
数组的顺序:按数组索引排序,按数字降序排列。由于我们强制执行该顺序,因此我们可以简单地计算有多少记录大于我们当前正在查看的记录。
遵循更简单的 sort
和 awk
方法也可能对您有所帮助(尽管我没有在数百万行上测试它,因为我没有它)。
解决方案 1: 这将不会在输出中显示重复项的排名,例如--> 示例中的数字 1。
sort -nr Input_file | awk '
function sum(array){
tot="";
for(i in array){
tot+=array[i]};
return tot}
{
a[FNR]=[=10=];
b[[=10=]]++
}
END{
for(j=1;j<=FNR;j++){
if(b[a[j]]){
val=b[a[j]];
delete b[a[j]];
printf("%d %0.4f\n",a[j],sum(b)/(sum(d)+sum(b)));
d[a[j]]=val;}
}}
'
输出如下。
13 1.0000
12 0.8889
11 0.7778
8 0.6667
4 0.5556
3 0.4444
2 0.3333
1 0.0000
解决方案 2: 添加解决方案(与第一个略有不同),这将在输出中提供重复项的等级,如下所示.
sort -nr Input_file | awk '
function sum(array){
tot="";
for(i in array){
tot+=array[i]};
return tot}
{
a[FNR]=[=12=];
b[[=12=]]++
}
END{
for(j=1;j<=FNR;j++){
if(b[a[j]]){
val=val1=b[a[j]];
delete b[a[j]];
while(val1>0){
printf("%d %0.4f\n",a[j],sum(b)/(sum(d)+sum(b)));
val1--}
d[a[j]]=val;}
}}
'
13 1.0000
12 0.8889
11 0.7778
8 0.6667
4 0.5556
3 0.4444
2 0.3333
1 0.0000
1 0.0000
1 0.0000