我正在尝试计算充满数字的文件(1 列)的几何平均值。
几何平均值的基本公式是所有值的自然对数(或对数)的平均值,然后将 e(或以 10 为底)提高到该值。
我当前仅使用 bash 的脚本如下所示:
# Geometric Mean
count=0;
total=0;
for i in $( awk '{ print $1; }' input.txt )
do
if (( $(echo " "$i" > "0" " | bc -l) )); then
total="$(echo " "$total" + l("$i") " | bc -l )"
((count++))
else
total="$total"
fi
done
Geometric_Mean="$( printf "%.2f" "$(echo "scale=3; e( "$total" / "$count" )" | bc -l )" )"
echo "$Geometric_Mean"
本质上:
- 检查输入文件中的每个条目,确保每次调用 bc 都大于 0
- 如果条目 > 0,我会取该值的自然对数 (l),并将其添加到每次调用 bc 的累计总数中
- 如果条目 <=0,我什么都不做
- 计算几何平均值
这对于小数据集来说效果非常好。不幸的是,我试图在大型数据集上使用它(input.txt 有 250,000 个值)。虽然我相信这最终会奏效,但速度非常慢。我从来没有足够的耐心让它完成(45 分钟以上)。
我需要一种更有效地处理该文件的方法。
还有其他方法,例如使用 Python
# Import the library you need for math
import numpy as np
# Open the file
# Load the lines into a list of float objects
# Close the file
infile = open('time_trial.txt', 'r')
x = [float(line) for line in infile.readlines()]
infile.close()
# Define a function called geo_mean
# Use numpy create a variable "a" with the ln of all the values
# Use numpy to EXP() the sum of all of a and divide it by the count of a
# Note ... this will break if you have values <=0
def geo_mean(x):
a = np.log(x)
return np.exp(a.sum()/len(a))
print("The Geometric Mean is: ", geo_mean(x))
我想避免使用 Python、Ruby、Perl ...等。
关于如何更有效地编写 bash 脚本有什么建议吗?
答案1
请不要在 shell 中执行此操作。无论进行多少调整都无法使其变得高效。 Shell 循环是慢的使用 shell 来解析文本只是一种不好的做法。您的整个脚本可以用这个简单的awk
单行代码替换,这将快几个数量级:
awk 'BEGIN{E = exp(1);} $1>0{tot+=log($1); c++} END{m=tot/c; printf "%.2f\n", E^m}' file
例如,如果我在包含 1 到 100 之间的数字的文件上运行该命令,我会得到:
$ seq 100 > file
$ awk 'BEGIN{E = exp(1);} $1>0{tot+=log($1); c++} END{m=tot/c; printf "%.2f\n", E^m}' file
37.99
在速度方面,我在包含 1 到 10000 数字的文件上测试了您的 shell 解决方案、您的 python 解决方案以及我上面给出的 awk:
## Shell
$ time foo.sh
3677.54
real 1m0.720s
user 0m48.720s
sys 0m24.733s
### Python
$ time foo.py
The Geometric Mean is: 3680.827182220091
real 0m0.149s
user 0m0.121s
sys 0m0.027s
### Awk
$ time awk 'BEGIN{E = exp(1);} $1>0{tot+=log($1); c++} END{m=tot/c; printf "%.2f\n", E^m}' input.txt
3680.83
real 0m0.011s
user 0m0.010s
sys 0m0.001s
正如您所看到的,它awk
比 python 更快,而且编写起来更简单。如果您愿意,您还可以将其制作成“shell”脚本。要么像这样:
#!/bin/awk -f
BEGIN{
E = exp(1);
}
$1>0{
tot+=log($1);
c++;
}
END{
m=tot/c; printf "%.2f\n", E^m
}
或者将命令保存在 shell 脚本中:
#!/bin/sh
awk 'BEGIN{E = exp(1);} $1>0{tot+=log($1); c++;} END{m=tot/c; printf "%.2f\n", E^m}' "$1"
答案2
以下是一些建议。如果不确切知道您的文件中的内容,我无法测试它们,但我希望这会有所帮助。总是有不同的、更好的方法来做事,所以这并不是详尽无遗的。
改变if条件
if (( $(echo " "$i" > "0" " | bc -l) )); then
将其更改为:
if [[ "$i" -gt 0 ]]; then
第一行创建了多个进程,即使它只是做简单的数学运算。解决方案是使用[[
shell 关键字。
删除不需要的代码
else
total="$total"
这基本上是一种明确浪费时间无所事事的方法:)。这两行可以直接删除。