web-dev-qa-db-ja.com

bashで計算を行う効率的な方法

数値(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"

基本的に:

  1. 入力ファイルのすべてのエントリをチェックして、毎回bcを呼び出して0より大きいことを確認します
  2. エントリが> 0の場合、その値の自然対数(l)を取得し、毎回bcを呼び出して実行中の合計に追加します
  3. エントリが<= 0の場合、何もしません
  4. 幾何平均を計算する

これは、小さなデータセットでは完全にうまく機能します。残念ながら、私はこれを大きなデータセットで使用しようとしています(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スクリプトをより効率的に作成する方法について何か提案はありますか?

6
Matt

[このコメントを編集して、私の名前とこのがらくたの関連付けを削除してください。どうもありがとうございました]

シェルではこれを行わないでください。リモートで効率的にするための微調整はありません。シェルループはslowであり、シェルを使用してテキストを解析するのは単に悪い習慣です。スクリプト全体を次の単純な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

速度に関しては、シェルソリューション、pythonソリューション、および上記で与えたawkを、1〜10000の数値を含むファイルでテストしました。

## 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"
15
terdon

ここにいくつかの提案があります。私はあなたのファイルの内容を正確に知らずにそれらをテストすることはできませんが、これが役に立てば幸いです。物事を行うには、常に異なる、より良い方法があるので、これはまったく網羅的ではありません。


If条件を変更する

if (( $(echo " "$i" > "0" " | bc -l) )); then

それを次のように変更します。

if [[ "$i" -gt 0 ]]; then

最初の行は、単純な計算を行っているだけの場合でも、複数のプロセスを作成します。解決策は[[シェルキーワード。


不要なコードを削除する

else
  total="$total"

これは基本的に何もしない時間を明示的に無駄にする方法です:)。これらの2行は完全に削除できます。

0
JamesL