プロが教えるわが家の防犯対策術!

お世話になります。
rubyのプログラムを組んだのですが、出力に時間がかかるため、どうにか高速化できないかという相談です。rubyを使用して
acc_x.csv  acc_y.csv 
という2つの数値解析ファイルから列ごとにテキストファイルを出力させたいです。さらにそして、acc_xとacc_yから合成した合成加速度も計算させ、それも追加させた状態で出力させたいです。
それぞれのファイルを一部抽出したものを以下に記載させて頂きます。

acc_x.csv
____________________
time,1,2,5,10,13
0.01,0.785958008,-0.274499466,-0.04875417,0.277856228,-0.166021321
0.02,0.500327211,-0.061038414,0.580773155,0.397613981,0.260582952
0.03,-0.108020639,-0.273086752,0.160661689,0.241324649,0.028759969
0.04,0.310627648,0.001429172,0.091403594,-0.305568004,0.826153173
0.05,-0.257493861,-0.475250007,0.186605183,0.031059334,0.845045269
0.06,0.163644999,-0.384227085,-0.165840021,0.275938253,0.644296185
0.07,0.055154002,-0.568280995,0.184234876,-0.572951645,0.102156244
0.08,-0.350023989,-0.11692816,0.262549184,0.293178365,0.439308897
0.09,-0.091233971,-0.184686171,0.755416196,0.18408131,0.67840418
0.1,-0.220665702,0.395159564,-0.076555628,-0.283248903,-0.117443896
以下略
____________________

acc_y.csv
____________________
time,1,2,5,10,13
0.01,-0.595558906,0.07294466,-0.106053852,-0.041407645,0.78285035
0.02,-0.2567976,-0.352352662,0.449620458,-0.572029331,-0.519173866
0.03,0.37704531,-0.338795392,0.025760981,-0.890864656,-0.01955793
0.04,-0.711042009,-0.055702349,0.658077034,0.342903809,0.203497863
0.05,0.656198235,-0.243704778,0.345243588,0.224393765,-0.542427698
0.06,-0.544045512,-0.692627935,-0.58506749,-0.317839259,0.313767218
0.07,0.458234249,-0.339504431,0.35664675,-0.653384157,-0.09970446
0.08,-0.001167412,-0.254440685,-0.850123892,0.373257703,-0.540415036
0.09,-0.661368491,-0.080536849,-0.320307516,0.286827238,0.658116411
0.1,0.161428938,-0.328336073,0.10393651,-0.320717467,-0.556967052
以下略
____________________

上記のファイルの一行目の1,2,5,10,13というのは地点名称であり一列目は観測時間であります。
列ごとの観測結果の単位がm/s2であり、必要なのはcm/s2という単位系なので、観測結果の値を100倍してやりそれぞれの地点ごとに
acc_1.dat
acc_2.dat
acc_5.dat
のようなテキストファイルで保存したいと思っております。
結果のイメージは

acc_1.dat
____________________
time X_acc(gal) Y_acc(gal) resultant_acc(gal)
0.01 7.8596e+01 -5.9556e+01 9.8611e+01
0.02 5.0033e+01 -2.5680e+01 5.6238e+01
0.03 -1.0802e+01 3.7705e+01 3.9221e+01
0.04 3.1063e+01 -7.1104e+01 7.7593e+01
0.05 -2.5749e+01 6.5620e+01 7.0491e+01
0.06 1.6364e+01 -5.4405e+01 5.6812e+01
0.07 5.5154e+00 4.5823e+01 4.6154e+01
0.08 -3.5002e+01 -1.1674e-01 3.5003e+01
0.09 -9.1234e+00 -6.6137e+01 6.6763e+01
0.1 -2.2067e+01 1.6143e+01 2.7341e+01
以下略
____________________

といった感じです。コードも組んではみました。出力ファイルの文字間の区切り文字は「タブ」であります。

# coding: cp932
require 'csv'

Encoding.default_external=__ENCODING__
Encoding.default_internal=__ENCODING__
Ix=IO.readlines("acc_x.csv",chomp:true)
Iy=IO.readlines("acc_y.csv",chomp:true)

comma=","
tab="\t"
str="%.4e"
h=1 #地点名称存在行
r=5 #繰り返し回数(row)
tx=ty=100 #係数倍_mからcm/s2への変換
input=[] #ファイル名作成用に空ファイル準備
r.times{|n| #繰り返し
x=n+1 #一列目が時刻歴だから+1しておく
y=x+r+1 #acc_xファイルの後にacc_yを追加する。一列目は時刻歴だから+1しておく

s=[Ix[h-1],Iy[h-1]].map{|a| #地点名をとりあえず格納する。その配列はaとする。
a.split(comma)}.flatten #コンマ区切りだからコンマで分割。
input<<(outputfile="acc_%s.dat"%s[x]) #acc_xファイルの地点名をoutputファイルに付加。

open(outputfile,"wt"){|b| #ファイル書き込み処理。変数はbとする。
b.puts ["time","X_acc(gal)","Y_acc(gal)","resultant_acc(gal)"].join(tab) #変数bのファイルにヘッダーを付ける。
(h...Ix.size).each{|c| #行数取得
d=[Ix[c],Iy[c]].map{|e| #行数分の数値を入手する
e.split(comma).map(&:to_f)}.flatten
b.puts [d[0],str%(tx*d[x]),str%(ty*d[y]),str%(Math.sqrt(((tx*d[x])**2)+((ty*d[y])**2)))].join(tab) #係数倍をし、合成加速度求めてコンマ4桁で出力してタブ区切り
} } }
input.each{|n|
puts n #列数分繰り返し
puts IO.readlines(n,chomp:true)
}

どうにか出力までは漕ぎつけられましたが、今回の例題のようなわずかな行列数ではすぐに出力が終わるのですが、それが6000列×12000行という莫大な数になると、終了まで数時間かかってしまいました。何とか早くしたいのですが、私ではこれ以上何ともなりませんでした。
上記プログラムには、自分の理解していることをメモとして残してあるのですが、覚え違いなどがあればそれもぜひご指摘お願いいたします。

おそらく、一行ずつデータを取得して回す、eachline や while do構文を使えば早くなるような気もするのですが、何分、触り始めて間もないため、強引にファイルを全部展開して、列ごとに張り付けるという事しかできませんでした。識者の方、助けてください。

上記のコードですが外部になりますが
https://paiza.io/projects/LLl2x4yvH5R6X_8Sw68oHQ
にサンプルファイルごとアップロードしてあります。
よろしくお願いします。

質問者からの補足コメント

  • うーん・・・

    おかげさまで前より早くなりました。
    ご指摘いただいたように、毎回ファイルを開いていたようです。サブモニターの隅の方でテキストエディタが開いておりました。

    しかしながら、やはりファイルが重たいせいか、遅いです。メモリーは十分(64GB)で使用が8GBで余裕があるはずなのに、書き出しが遅い。
    自分のやり方ですと、ファイルを一度全部展開して、そこから書き出すという手段をとっているので、ネックはここなのですかね?

    複数のファイルを読むときに、一行ずつ読み込んだのちに、似たような処理というのは可能なのでしょうか?逆に、毎回読み込みなおす方が負荷がかかるのでしょうか?

      補足日時:2018/02/27 18:19

A 回答 (8件)

こんにちは


ちがう考え方で作成してみました。よろしければお試しください。

※変数rの値は、データ数から読み取っていますのでプログラム内での設定は不要です。
※新しい機能は使用していないので、ご利用のrubyのバージョンでも動くと思います。


-----ここから
#coding: cp932

require 'matrix'

tx = 100
ty = 100

# 各転置行列の行データを元にファイル出力を行う
def output_file(row_x, row_y, time_gauge, tx, ty)
# 出力ファイル名
filename = "acc_" + "%d"%row_x[0] + ".dat"
# 各列を計算してファイル出力する
File.open(filename, 'w') do |f|
# ヘッダ出力
f.puts "time\tX_acc(gal)\tY_acc(gal)\tresultant_acc(gal)"
# 各列のデータを計算して出力
(1..time_gauge.size-1).each do |i|
x = tx * row_x[i]
y = ty * row_y[i]
resultant = Math.sqrt( x**2 + y**2 )
f.printf( "%g\t%.4e\t%.4e\t%.4e\n", time_gauge[i], x, y, resultant)
end
end
end


#
# MAIN
#

# acc_x.csvを取り込み転置行列を作成
mat_x = []
File.foreach("acc_x.csv"){|line| mat_x.push line.chomp.split(/,/).map(&:to_f) }
mat_x = Matrix[*mat_x].transpose.to_a

# acc_y.csvを取り込み転置行列を作成
mat_y = []
File.foreach("acc_y.csv"){|line| mat_y.push line.chomp.split(/,/).map(&:to_f) }
mat_y = Matrix[*mat_y].transpose.to_a

# 時間目盛取得
time_gauge = mat_x[0] # [0,0.01,0.02,0.03, …]

# 転置行列の行毎にファイル出力
(1..mat_x.size-1).each{|row| output_file(mat_x[row], mat_y[row], time_gauge, tx, ty) }

-----ここまで
    • good
    • 0
この回答へのお礼

・・・すみません。ちょっと言わせてください。
何ですかこのプログラム!!すっごい早い!!何がどうなっているのかよく分かりませんが、何を行ったんですか!!
取り乱しました。目的のファイル作成までに3分かからないとは・・・。

ちょっと教えてください。
①# 各転置行列の行データを元にファイル出力を行う
def output_file(row_x, row_y, time_gauge, tx, ty)
というところで、tx,tyと使っていますが、このtxは係数用の代数なのに、なぜここに置けるのでしょうか?

②ファイルの読み込みがMAIN以降になっている?頭からrubyは構文を書かなくてもOK?

③変数row_x,row_yのxyはどこから参照しているのです?x=やy=という構文は見当たりません。何故取得可能?

④高速な理由はpush lineに隠されている?ここで各列ごとに書き出している?それか転置行列にある?

⑤もとの計算ファイルなのですが

acc_x.csv(or acc_y.csv)
________
ダミー
ダミー
ダミー
time,1,2,5,10,13
0.01,0.785958008,-0.274499466,-0.04875417,0.277856228,-0.166021321
0.02,0.500327211,-0.061038414,0.580773155,0.397613981,0.260582952
________________
と本来は頭に3行分ダミーデータ(というか解析条件メモ書き)が入っているのですが、3行スルーさせて4行目から読むことも可能でしょうか?

ruby の速さ、全然自分は活かせてなかった…。恥ずかしい。正直、構文ほとんど理解できません。
非常に助かるプログラム構成、感謝いたします。

お礼日時:2018/02/27 20:48

No.7です。


想定より効果が出てこちらもオドロキました。


> 本来は頭に3行分ダミーデータ(というか解析条件メモ書き)が入っているのですが、3行スルーさせて

MAINの冒頭部分を以下の様に変更して下さい。CSVファイルを読み込む際に無視させます。

-----ここから
skip_line = 3

# acc_x.csvを取り込み転置行列を作成
mat_x = []
count = 0
File.foreach("acc_x.csv") do |line|
if count < skip_line
count += 1
else
mat_x.push line.chomp.split(/,/).map(&:to_f)
end
end
mat_x = Matrix[*mat_x].transpose.to_a

# acc_y.csvを取り込み転置行列を作成
mat_y = []
count = 0
File.foreach("acc_y.csv") do |line|
if count < skip_line
count += 1
else
mat_y.push line.chomp.split(/,/).map(&:to_f)
end
end
mat_y = Matrix[*mat_y].transpose.to_a
-----ここまで


高速化のポイントは繰り返し処理の中で重い処理を行わないことです。
できるだけ繰り返し処理の前に扱い易いデータに変換して、繰り返しの中では最低限必要な処理のみを行った方が良いです。
元のプログラムの中では、r.timesや(h...Ix.size).eachの繰り返し処理の中で
 e.split(comma).map(&:to_f)}.flatten
の様な処理を行っています。これは1行の中で配列(Arrayオブジェクト)を何度も生成させるという重い処理を行っているので、大きなサイズの配列で繰り返し行うと、影響がもろに出てきます。

No.7のプログラムでは、個々の出力ファイルを作成するにはCSVの行と列を入れ替えた方が簡単なのでその様に変換を行い、更に値を文字列(Stringオブジェクト)から数値(floatオブジェクト)に変換させデータを軽くさせました。これらの処理は繰り返し処理(最終行)の前に1回のみ行っています。

あと小さなことかもしれませんが
 b.puts ["time","X_acc(gal)","Y_acc(gal)","resultant_acc(gal)"].join(tab)

 b.puts "time\tX_acc(gal)\tY_acc(gal)\tresultant_acc(gal)"
のように配列から文字列を作るという処理を行うよりも、最初から文字列をした方が速いですね。


その他のご質問については初歩的な内容となりますので、申し訳ありませんが、rubyのマニュアルや入門書等をご確認下さい。
    • good
    • 0
この回答へのお礼

色々とありがとうございました。感謝いたします。
おかげさまで作業がはかどります。
マニュアル(楽しいruby)を読みながら頑張ってみます。

お礼日時:2018/02/28 09:16

原因がわかりました。


最後のoutputfile="acc_%s.dat"%s[x]
の時に、改行コードが付加されている為、openで失敗ます。

これは、ruby2.4では
Ix=IO.readlines("acc_x.csv",chomp:true)
のchomp:trueをサポートしますが
ruby2.3では、
Ix=IO.readlines("acc_x.csv",chomp:true)
をサポートしない為です。

ruby2.4のreadlines
https://docs.ruby-lang.org/ja/2.4.0/method/IO/s/ …

ruby2.3のreadlines
https://docs.ruby-lang.org/ja/2.3.0/method/IO/s/ …

を参照ください。

対策としては
1案 ruby2.4以上を使用する。
2案 現行のままで使うなら、
outputfile="acc_%s.dat"%s[x] を
outputfile="acc_%s.dat"%s[x].chomp
に変えてください。(最後に.chompを付加する)
    • good
    • 0
この回答へのお礼

重ね重ねありがとうございます。
サポートするメソッド自体が変わるのですね。
普段、ファイルで散らかってしまわないように、web上でやっているためrubyのバージョンを気にしたことがありませんでした。
今回は勝手にバージョンを上げることが出来ないので、2案の方を使用させていただきます。
ありがとうございました。

お礼日時:2018/02/27 16:29

こちらもwindows-7 64bitです。


こちらのrubyのバージョンです。
D:\goo\ruby\goo11>ruby -v
ruby 2.4.1p111 (2017-03-22 revision 58053) [x64-mingw32]

こちらはruby2.4.1です。

念のため、ruby 1.9.3で実行したら、同様のエラーが発生しました。
以下、実行ログです。
D:\goo\ruby\goo11>ruby -v
ruby 1.9.3p551 (2014-11-13) [i386-mingw32]

D:\goo\ruby\goo11>dir
ドライブ D のボリューム ラベルがありません。
ボリューム シリアル番号は D8FF-F6E0 です

D:\goo\ruby\goo11 のディレクトリ

2018/02/27 15:46 <DIR> .
2018/02/27 15:46 <DIR> ..
2018/02/27 14:17 692 acc_x.csv
2018/02/27 14:17 696 acc_y.csv
2018/02/27 14:17 1,248 test.rb
3 個のファイル 2,636 バイト
2 個のディレクトリ 482,373,091,328 バイトの空き領域

D:\goo\ruby\goo11>ruby test.rb
test.rb:24:in `initialize': Invalid argument - acc_13 (Errno::EINVAL)
.dat
from test.rb:24:in `open'
from test.rb:24:in `block in <main>'
from test.rb:16:in `times'
from test.rb:16:in `<main>'

ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
rubyを最新バージョンにすれば、エラーは回避できると思います。
急ぎのようでしたら、rubyを最新バージョンにして、再実行してください。
私の方では、エラーが再現できましたので、エラー原因を調査します。
何か判れば、再度連絡します。
    • good
    • 0
この回答へのお礼

お手数おかけします。触り始めてまだわずかなため、このようなイレギュラーに対応ができていません。
言語プログラムのバージョン違いでのエラーというものはあるのですね。
RubyやPythonといったソフトのバージョン違いは、Office2010や2013のように、同じような別物と考えた方が良いのですね。

お礼日時:2018/02/27 16:04

>ファイルをgigafile便にアップロードしました。


>http://8.gigafile.nu/0306-cdd0ba68b498a9adc94111
上記のファイルをダウンロードして、こちらで実行してみました。
正常終了します。(ファイルの内容もざっとみましたが問題なさそうです)
以下、実行結果です。コマンドプロンプトで実行しています。
①実行前のファイルの確認
②rubyの実行
③実行結果の確認
が実行したコマンドです。
②でエラーにならず、③でacc_13.datが作成されていることが確認されました。

---------------------------------------------------------
D:\goo\ruby\goo11>dir  ・・・・①実行前のファイルの確認
ドライブ D のボリューム ラベルがありません。
ボリューム シリアル番号は D8FF-F6E0 です

D:\goo\ruby\goo11 のディレクトリ

2018/02/27 14:20 <DIR> .
2018/02/27 14:20 <DIR> ..
2018/02/27 14:17 692 acc_x.csv
2018/02/27 14:17 696 acc_y.csv
2018/02/27 14:17 1,248 test.rb
3 個のファイル 2,636 バイト
2 個のディレクトリ 482,372,567,040 バイトの空き領域

D:\goo\ruby\goo11>ruby test.rb ・・・②rubyの実行

D:\goo\ruby\goo11>dir  ・・・③実行結果の確認
ドライブ D のボリューム ラベルがありません。
ボリューム シリアル番号は D8FF-F6E0 です

D:\goo\ruby\goo11 のディレクトリ

2018/02/27 14:20 <DIR> .
2018/02/27 14:20 <DIR> ..
2018/02/27 14:20 447 acc_1.dat
2018/02/27 14:20 445 acc_10.dat
2018/02/27 14:20 444 acc_13.dat
2018/02/27 14:20 453 acc_2.dat
2018/02/27 14:20 443 acc_5.dat
2018/02/27 14:17 692 acc_x.csv
2018/02/27 14:17 696 acc_y.csv
2018/02/27 14:17 1,248 test.rb
8 個のファイル 4,868 バイト
2 個のディレクトリ 482,372,567,040 バイトの空き領域
---------------------------------------------------------------
もしかしたら、実行環境もしくは実行の方法に問題があるのかも知れません。
どのように実行していますか。
    • good
    • 0
この回答へのお礼

環境はwindows7 64bit
ruby は2.3.3
を使用しています。
実行方法としては
①変換したいファイルがあるフォルダにコマンドプロンプトでCDコマンドにて移動
②移動したフォルダ内で ruby test.rb
を実行しました。自分ももう一度アップロードしたファイルをダウンロードして実行したのですが、やはり同様のエラーでacc_13.datが出力されませんでした。
tatsu99様と同じようなログをこちらも記載します。
_____________________
D:\goo\goo11>dir  ・・・・①実行前のファイルの確認
ドライブ D のボリューム ラベルは Backup です
ボリューム シリアル番号は 36EC-6BA8 です
D:\goo\goo11 のディレクトリ

2018/02/27 14:53 <DIR>
2018/02/27 14:53 <DIR>
2018/02/27 14:53 692 acc_x.csv
2018/02/27 14:53 696 acc_y.csv
2018/02/27 14:53 1,248 test.rb
3 個のファイル 2,636 バイト
2 個のディレクトリ 1,798,000,640 バイトの空き領域

D:\goo\goo11>ruby test.rb ・・・②rubyの実行
test.rb:24:in `initialize': Invalid argument @ rb_sysopen - acc_13 (Errno::EINVA
L).dat
from test.rb:24:in `open'
from test.rb:24:in `block in <main>'
from test.rb:16:in `times'
from test.rb:16:in `<main>'

D:\goo\goo11>dir  ・・・③実行結果の確認
7 個のファイル 4,424 バイト
___________
となってます。rubyのバージョンの問題でしょうか?

お礼日時:2018/02/27 15:03

>現在、外で調査をしているため、投稿した分のデータしかないのですが、実行してみました。


>実行した結果、エラーが返ってはきました。acc_13.datが返ってきませんでした。他の、1,2,5,10の四つは結果は出力されております。
No2です。
こちらで、No2で記述したように修正して、実行したところ正常に終了しています。
acc_13.datも正しく作成されています。

1)テストデータに誤りはないですか?
2)修正個所は正しく修正していますか?
念のため、上記の確認をお願いします。
    • good
    • 0
この回答へのお礼

上記にあるデータをwindows標準のメモ帳に張り付けて、acc_x.csvおよびacc_y.csvで保存しました。
プログラムは
# coding: cp932
require 'csv'

Encoding.default_external=__ENCODING__
Encoding.default_internal=__ENCODING__
Ix=IO.readlines("acc_x.csv",chomp:true)
Iy=IO.readlines("acc_y.csv",chomp:true)
comma=","
tab="\t"
str="%.4e"
h=1
r=5
tx=ty=100
input=[]
r.times{|n|
x=n+1
y=x+r+1
s=[Ix[h-1],Iy[h-1]].map{|a|
a.split(comma)}.flatten
outputfile="acc_%s.dat"%s[x]

open(outputfile,"wt"){|b|
b.puts ["time","X_acc(gal)","Y_acc(gal)","resultant_acc(gal)"].join(tab)
(h...Ix.size).each{|c|
d=[Ix[c],Iy[c]].map{|e|
e.split(comma).map(&:to_f)}.flatten
b.puts [d[0],str%(tx*d[x]),str%(ty*d[y]),str%(Math.sqrt(((tx*d[x])**2)+((ty*d[y])**2)))].join(tab)
} } }
#input.each{|n|
# puts n
# puts IO.readlines(n,chomp:true)
#}

のように設定しました。
その状態でも、同じエラーが返ってきました。
原因が分かりません。
ファイルをgigafile便にアップロードしました。
http://8.gigafile.nu/0306-cdd0ba68b498a9adc94111 …
ダウンロードキーは
1111
です。原因分かったら教えてください。

お礼日時:2018/02/27 13:13

画面表示は不要と理解しました。


1.最後の4行の
input.each{|n|
puts n #列数分繰り返し
puts IO.readlines(n,chomp:true)
}
をコメントにしてください。

2.下記の行
input<<(outputfile="acc_%s.dat"%s[x]) #acc_xファイルの地点名をoutputファイルに付加するための行。
これを
outputfile="acc_%s.dat"%s[x]
に変えてください。

それで実行しても、同じファイルが作成されるはずです。
それで、実行するとどうなりますか?
現状はinputに作成される配列が6000列×12000行=72,000,000個の要素を持つ配列になります。
これが大きすぎて遅くなっていると思われます。
    • good
    • 0
この回答へのお礼

ありがとうございます。
現在、外で調査をしているため、投稿した分のデータしかないのですが、実行してみました。
実行した結果、エラーが返ってはきました。acc_13.datが返ってきませんでした。他の、1,2,5,10の四つは結果は出力されております。
C:\Users\itoyori_kurome\Desktop\newnew>ruby test.rb
test.rb:24:in `initialize': Invalid argument @ rb_sysopen - acc_13 (Errno::EINVA
L).dat
from test.rb:24:in `open'
from test.rb:24:in `block in <main>'
from test.rb:16:in `times'
from test.rb:16:in `<main>'


使用したファイルはリンク先に投稿してあるファイルそのものなんですが、結構このような最終列エラーが起きるのです。この原因はなんでしょうか?

お礼日時:2018/02/27 12:00

最後に


input.each{|n|
puts n #列数分繰り返し
puts IO.readlines(n,chomp:true)
}
の行がありますが、これは、acc_1.dat、acc_2.dat、、、acc_13.datのファイルの内容を
画面に表示しているだけですよね。
それなら、この行を削除すれば、実行時間がかなり削減できると思いますが如何でしょうか。
画面に出力する必要はないと思いますが、如何でしょうか。(画面出力自体がかなり時間がかかります)
    • good
    • 0
この回答へのお礼

tatus99様
この構文、画面に表示するだけの構文だったのですか。
自分の環境ですと、画面に表示されてこないため、実際にはどこかで何かが起こっているという事ですか。
テキストファイルの出力時に、この構文が繰り返し分で必要なんだなと思っていただけで、全く気にしておりませんでした。
本やインターネットで見た感じで、これがいる物だと思って、書いた構文すべてに似たような内容のを書いていました。この構文はテスト用の出力構文でしょうか?
言われてみれば、一つ上の
b.puts [d[0],str%(tx*d[x]),~~~~~~~~
} } }
でひとくくりの終了はしているような感じもします。

お礼日時:2018/02/27 10:55

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!