数字でいっぱいのファイル(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
シェルではこれを行わないでください。どんなに調整しても効率よく作れません。シェルループは遅いシェルを使用してテキストを解析するのは悪い習慣です。スクリプト全体をawk
次の簡単な1行に置き換えることができます。これははるかに高速です。
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までの数字を含むファイルに対して上記で提供されたシェルソリューション、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よりも高速で作成が簡単です。必要に応じてこれを「シェル」スクリプトにすることもできます。次のようになります。
#!/bin/awk -f
BEGIN{
E = exp(1);
}
$1>0{
tot+=log($1);
c++;
}
END{
m=tot/c; printf "%.2f\n", E^m
}
または、シェルスクリプトにコマンドを保存します。
#!/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"
これは基本的に何もせずに時間を無駄にする明確な方法です:)。これらの2行は直接削除できます。