質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
Ruby

Rubyはプログラミング言語のひとつで、オープンソース、オブジェクト指向のプログラミング開発に対応しています。

Q&A

解決済

2回答

346閲覧

rubyを使った3つのCSVファイルから列ごとにまとめたテキストデータ出力の高速化

sakuya_izayoi

総合スコア19

Ruby

Rubyはプログラミング言語のひとつで、オープンソース、オブジェクト指向のプログラミング開発に対応しています。

0グッド

1クリップ

投稿2018/02/21 12:01

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

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


acc_z.csv


time,1,2,5,10,13
0.01,0.124781351,0.139025231,-0.249542182,0.250675217,-0.261333158
0.02,0.058236711,0.788391173,-0.045054373,0.913832437,-0.520917922
0.03,-0.425799344,0.06491633,-0.220356549,0.010808446,-0.012345955
0.04,-0.106479085,0.069571527,-0.819504969,-0.680273688,-0.476894582
0.05,-0.028521568,0.481298624,0.211427059,-0.406888097,-0.348843099
0.06,-0.1131719,0.397351719,0.23308442,-0.042568532,0.201701772
0.07,0.58261087,0.169149533,0.412807036,0.073169201,0.948218341
0.08,-0.321402494,0.261607689,-0.113240059,-0.564063202,-0.134732901
0.09,-0.097148189,0.60216685,0.177890054,0.168810598,-0.014560554
0.1,-0.377921037,0.492026522,-0.791015033,0.759523057,0.56215856


上記のファイルの一行目の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) Z_acc(gal)
0.01 7.8596e+01 -5.9556e+01 1.2478e+01
0.02 5.0033e+01 -2.5680e+01 5.8237e+00
0.03 -1.0802e+01 3.7705e+01 -4.2580e+01
0.04 3.1063e+01 -7.1104e+01 -1.0648e+01
0.05 -2.5749e+01 6.5620e+01 -2.8522e+00
0.06 1.6364e+01 -5.4405e+01 -1.1317e+01
0.07 5.5154e+00 4.5823e+01 5.8261e+01
0.08 -3.5002e+01 -1.1674e-01 -3.2140e+01
0.09 -9.1234e+00 -6.6137e+01 -9.7148e+00
0.1 -2.2067e+01 1.6143e+01 -3.7792e+01


のようなものであります。
上記の仕様を一応は満足できるようにrubyを使って組んでみました。

ruby

1# coding: cp932 2Encoding.default_external=__ENCODING__ 3Encoding.default_internal=__ENCODING__ 4Ix=IO.readlines("acc_x.csv",chomp:true) 5Iy=IO.readlines("acc_y.csv",chomp:true) 6Iz=IO.readlines("acc_z.csv",chomp:true) 7comma="," 8tab="\t" 9str="%.4e" 10h=1 #地点名称存在行 11r=5 #繰り返し回数(row) 12tx=ty=tz=100 #係数倍_mからcm/s2への変換 13input=[] #いつもながらなぜか必要な物。なぜ必要なのかよく分かってません。 14r.times{|n| #繰り返し 15 x=n+1 #一列目が時刻歴だから+1しておく 16 y=x+r+1 #acc_xファイルの後にacc_yを追加する。一列目は時刻歴だから+1しておく 17 z=y+r+1 #acc_yファイルの後にacc_zを追加する。一列目は時刻歴だから+1しておく 18 s=[Ix[h-1],Iy[h-1],Iz[h-1]].map{|a| #地点名以下を選択する。その配列はaとする 19 a.split(comma)}.flatten #コンマ区切りのファイルだからコンマで分割。 20 input<<(outputfile="acc_%s.dat"%s[x]) #acc_xファイルの地点名をファイルに付加。やっぱりinput<<がいる。何をしているのだろう? 21 22 open(outputfile,"wt"){|b| #ファイル書き込み処理。変数はbとする。 23 b.puts ["time","X_acc(gal)","Y_acc(gal)","Z_acc(gal)"].join(tab) #変数bのファイルにヘッダーを付ける。 24 (h...Ix.size).each{|c| #行数取得 25 s=[Ix[c],Iy[c],Iz[c]].map{|e| #行数分の数値を入手する 26 e.split(comma).map(&:to_f)}.flatten 27 b.puts [s[0],str%(tx*s[x]),str%(ty*s[y]),str%(tz*s[z])].join(tab) #係数倍をしてコンマ4桁で出力してタブ区切り 28} } } 29input.each{|n| 30 puts n #列数分繰り返し 31 puts IO.readlines(n,chomp:true) 32}

上記のようにプログラムを組んだところ、どうにか出力までは漕ぎつけられました。
今回の例題のようなわずかな行列数ではすぐに出力が終わるのですが、それが6000列×12000行という莫大な数になると、終了まで8時間以上かかってしまいました。
何とか早くしたいのですが、私ではこれ以上何ともなりませんでした。
上記プログラムには、自分の理解していることをメモとして残してあるのですが、覚え違いなどがあればそれもぜひご指摘お願いいたします。
それと、現在は0.0000e+00となっているのですが、できれば0.0000e+000の形式で表せれたらとも思っています。
組んだプログラムとテストファイルを置いてある外部サイトも掲載しておきます。
よろしくお願いします。
リンク内容

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

全ファイルの全レコードをメモリ上に読み込むようになっているので、メモリを大量に消費してページングが多発しているんだと思います。

ファイルを一気に読まずに、1行ずつ読むように変えるのがいいかと思います。

#サンプル追記
3ファイルを読んで、横につないで表示する。

Ruby

1fx = open("ファイル名X") 2fy = open("ファイル名Y") 3fz = open("ファイル名Z") 4 5# ヘッダだけ特別処理をする場合 6head_x = fx.gets(chomp: true) 7head_y = fy.gets(chomp: true) 8head_z = fz.gets(chomp: true) 9 10puts "ヘッダは #{head_x},#{head_y},#{head_z}" 11 12while date_x = fx.gets(chomp: true) and 13 date_y = fy.gets(chomp: true) and 14 date_z = fz.gets(chomp: true) 15 puts "#{data_x},#{data_y},#{data_z}" 16end 17fx.close 18fy.close 19fz.close

普通は、openにはブロックを与えますが、わかりやすくするために単独で使っています。

投稿2018/02/21 12:27

編集2018/02/22 05:08
otn

総合スコア84423

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

sakuya_izayoi

2018/02/21 15:26

Ix=IO.readlines("acc_x.csv",chomp:true) という読みこませ方だと、メモリー上にファイル内の全部を一気に展開してしまっている事になっているから、そこが問題だという事ですか。 すみません。今回のように複数のファイルを読みこむ場合、どのように変数に一行ずつ入れたらよいのでしょうか? 一列ずつのやり方が分からずに、今回はacc_x.csvの最終列以降にacc_y.csvファイルを追加して、acc_z.csvファイルをさらに追加した仮想的なファイルをメモリー内に作り、そこから列を抽出して転記すればできるのかなというイメージで上記のを作成しました。
otn

2018/02/21 15:38

> 今回のように複数のファイルを読みこむ場合、どのように変数に一行ずつ入れたらよいのでしょうか? openして、getsです。 f=open("~~") line = f.gets.chomp
sakuya_izayoi

2018/02/21 15:52

otn様 ファイルの読み込みを以下のようにしてみましたが、失敗となってしまいました。 Ix=open("acc_x.csv",chomp:true) line=Ix.gets.chomp Iy=open("acc_y.csv",chomp:true) line=Iy.gets.chomp Iz=open("acc_z.csv",chomp:true) line=Iz.gets.chomp こういう意味合いではないのでしょうか?
scivola

2018/02/21 23:22

open の chomp: true は削除しましょう。 ところで,どう失敗したのでしょうか。
sakuya_izayoi

2018/02/22 00:35

scivola様 chomp: true というのはいる物だと思い込んでおりました。いらない時もあるのか、逆に、いらないのですかね? エラー内容なのですが、 exam.rb:21:in `block in <main>': undefined method `[]' for #<File:acc_x.csv> (NoMethodError) from exam.rb:17:in `times' from exam.rb:17:in `<main>' となってしまいました。 参照リンクを下記に記載します。 よろしくお願いします。 https://paiza.io/projects/LLl2x4yvH5R6X_8Sw68oHQ?language=ruby
scivola

2018/02/22 03:30

えっとまず chomp: true のほうですが,これは open メソッドに与えるものではありません。 IO#gets,IO.foreach,String#each_line などに与えるもので,末尾の改行が取れた状態の行・文字列が得られます。 なので,gets.chomp を gets(chomp: true) と書くこともできます。
scivola

2018/02/22 03:32

エラーの方ですが,otn さんがおっしゃっているのは,ループの中で 1 行ずつ取得するところで Ix.gets.chomp としましょう,ということです。 ファイルをオープンしたときに一度だけ gets するのとは違います。 ちょっとこのコメント欄で詳しく書くのは難しいので,えっと,どうしようかな。
sakuya_izayoi

2018/02/22 04:13

scivola様 chomp: trueというのはどんな時にもいれる物だと思い込んでおりました。 ファイルを取り込むときにいる物なのですね。ありがとうございます。 ループ中にIx.gets.chompですか。 すみません。正直、今の状態からどうやって弄ったら良いのか想像がつかないです。
guest

0

ベストアンサー

とりあえず input に関して。

まず

rb

1input = []

で空配列を用意してますね。

rb

1input << (outputfile = "acc_%s.dat" % s[x])

は,まず "acc_%s.dat" % s[x] でファイル名らしき文字列を作成し,それを outputflie に代入しています。

そしてそれ(作成したファイル名)を配列 input の末尾に追加しています。

r.times のループが終了すると,input には全ファイルのファイル名の配列が入っているわけですね。

最後の

rb

1input.each{ |n| 2 puts n #列数分繰り返し 3 puts IO.readlines(n,chomp:true) 4}

は,その配列の各要素について,その値(ファイル名)を表示し,さらに当該ファイルの中身をも表示しています。

ブロックパラメーター n にはファイル名が入ります。

処理本体もさることながら,このファイルの全表示も結構時間がかかっていませんか。
画面の大きなテキストを表示するのって,意外と高コストなんですよ。
本当に必要でしょうか?

投稿2018/02/22 15:40

scivola

総合スコア2108

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

sakuya_izayoi

2018/02/23 01:24

scivola様 お返事ありがとうございます。 返信おくれてしまい、申し訳ありません。 現在、インフルエンザにかかってしまい、ほとんど考えられていない状態です。(昨日の昼ぐらいからなんだか怠いなと思っていたら、まさかの高熱へ) 携帯端末から返信しているので、改行とかがおかしくなっていたらすみません。 テキスト全文を表示する必要は、正直なところ、全く利点も何もになく、scivola様の仰るように、読み込みもすごく時間がかかり(しばらくたたないと、acc_1.datというファイルが出力されてこない。)ますし、otn様の仰っているような一行ずつというのが負荷の少なく動作が軽快だと思います。 しかし、一行ずつ読み込んで、書き込ませるというループの作り方が分からなかったため、苦肉の策で、全部ファイルをつなげるように展開してから、ファイルの分割をするための必要回数分のループを回してファイルを出力というという方法をとっておりました。 上記で教えて頂いた構文も、こういうものなんだという感覚で使っていたため、詳しい解説が聞けてありがたく思います。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問