我々が日常的に利用する「データ」は多くの場合表形式 で2次元の形で表現できる。 これは全てのデータが同じ順番で同じ項目の順番で書かれたものという前提が置ける。
全ての項目が揃った1単位のデータのことをレコード、 レコードの中にあるひとつひとつの項目のことをフィールドという。 たとえば、次のような電話帳データを考える。
漢字氏名 カタカナ氏名 電話番号 メイルアドレスその1 住所
各項目がすべて揃ったものを「レコード」、 漢字氏名やカタカナ氏名などの最小項目単位を 「フィールド」という。表計算ソフトなどでは1レコードを1行に、 1フィールドを1セルに書き込んで表現する。表計算ソフトなどでは データの集合を保存するときにはデフォルトでそのソフト固有の保存形式で 記録している。このため、なんらかのデータを専用ソフトで作成して ファイルに保存したものは、その保存形式に対応したソフトを使わない 限り開くことができない。
これに対し、あらゆる種類のソフトウェアで読み書きできる形式がいくつかある。 そのうちの一つがCSV形式である。
複数のフィールドをフィールドごとにカンマ(,)で区切り、 1レコードを1行におさめたテキストファイル形式をCSV形式という(Comma Separated Value)。CSV 形式はアプリケーションに依存しない一般的なデータファイル形式で、 データ処理を行なうプログラムであればほぼ全てのソフトウェアから利用できる。 このため、誰にでも参照してもらいたいデータファイルは CSV 形式にして保存・受け渡しするのが基本である。また、Rubyには CSV ファイルの読み書きを容易に行なえるライブラリがあるため、 「データ入力を表計算ソフトで、計算処理をRubyプログラムで行なう」 といった効率的なデータ処理設計が可能である。
CSV形式は、値をカンマで区切って列挙する。各値は、
ようにする。フィールドに空白文字かカンマを含む文字列が来るときは 必ずダブルクォートで括る。たとえば、
という4つのフィールドをまとめたレコードを CSV で表すときは
Hello,123,"456","Hello, world"
のように記す。なお、文字列自身にダブルクォートが含まれるときは、 ダブルクォートを重ねて表す。たとえば「He said "Hello".」 という文字列は、
"He said ""Hello""."
となる。
Ruby付属の CSV ライブラリを利用すると、正しく記述された CSV ファイルを一括で読み込むことができる。
CSV ファイルからデータを読むには CSV.read メソッドを使う。
data = CSV.read(CSVファイル)
上記の例で結果を代入した data 変数には各行の各フィールドごとに配列化したものがさらに配列化されて入る。 たとえば
氏名,数学得点,英語得点 山田太郎,50,70 中町太郎,90,80
という内容の CSV ファイルを CSV.read で読み込むと、以下のような配列の配列が返る。
[["氏名", "数学得点", "英語得点"], ["山田太郎", "50", "70"], ["中町太郎", "90", "80"]]
実用的な観点では、CSV ファイルの1行目の見出しをキーとして値を取り出せる形式で読んだ方が使いやすい。
data = CSV.read(CSVファイル, headers: true)
のように headers: オプションを追加指定すると、行ごとに
{見出し1 => 値1, 見出し2 => 値2, ...}
という形式のハッシュのようにアクセスできる値の集合が得られる。実例を示す。
以下のような見出し行つきの CSV ファイルがある。
氏名,数学得点,英語得点 山田太郎,50,70 中町太郎,90,80 飯森花子,91,60 鶴岡一人,60,45 酒田三吉,52,82 三川一二三,12,98
メソッド呼び出しの引数位置に「キーワード: 値」のような形で書き、 細かな挙動を指定するためのものをキーワード引数 という。キーワード引数を受け付けるか、 またどのようなキーワードが使えるかは使用するメソッドによって決まっている
これを headers オプションつきで読み込んで処理する例を示す。
#!/usr/bin/env ruby # coding: utf-8 Encoding.default_external = 'utf-8' # CSVファイルがutf-8のとき require 'csv' # CSVライブラリ読み込み data = CSV.read("score2h.csv", headers: true) # 行ごとに取り出す data.each{|row| # rowには1行ずつ値が入り繰り返される printf("%sさん: 数学=%d, 英語=%d\n", row["氏名"], row["数学得点"].to_i, row["英語得点"].to_i) #↑は各々 row[0], row[1].to_i, row[2].to_i でもよい # なお、見出し一覧は data.headers で得られる # data.headers → ["氏名", "数学得点", "英語得点"] } # 列ごとに取り出す printf("数学得点は %s \n", data["数学得点"]) printf("英語得点は %s \n", data["英語得点"])
実行すると以下のようになる。
./read-score.rb
山田太郎さん: 数学=50, 英語=70
中町太郎さん: 数学=90, 英語=80
飯森花子さん: 数学=91, 英語=60
鶴岡一人さん: 数学=60, 英語=45
酒田三吉さん: 数学=52, 英語=82
三川一二三さん: 数学=12, 英語=98
数学得点は ["50", "90", "91", "60", "52", "12"]
英語得点は ["70", "80", "60", "45", "82", "98"]
末尾2行で分かるように、CSV 全体を保持する変数の添字に見出し項目を指定すると、 縦方向(列全体)の値すべてが配列として取得できるのも headers オプション指定時の利点である。なお、 読み込んだ各フィールド値は文字列となっている点には注意が必要で、 数値的に計算したいものがある場合は数値への変換が必要である。
以下のCSVファイルを自動的に開いて読み込み、read-score.rb のように行ごとに見出しと値を出力するプログラムを作成せよ。
果物,単価 りんご,150 梨,120 みかん,30 いちご,20
CSVへの書き出しは CSV.open メソッドにファイル書き込みオプション "w" または "a" などを与えて行なう。openメソッドと同じ要領で利用し、 フィールドの値を配列化したものを順次追加すればよい。 簡単な例を示す。
#!/usr/bin/env ruby # coding: utf-8 Encoding.default_external = 'utf-8' require 'csv' CSV.open("output.csv", "w") do |csv| # csv変数に値を追加すればよい csv << ["a", "b", "c"] # << で1行ぶんの配列を指定する csv << [1,2,3] csv << "あいう".split("") # ["あ", "い", "う"] の配列を追加 end
これを実行すると output.csv に以下の内容が書き出される。
a,b,c 1,2,3 あ,い,う
CSVは原則として1行1レコードの形式だが、 フィールドの文字列に改行文字が含まれる場合でも、それをダブルクォートで 括れば単一フィールドと見なして処理できる処理系があり、Ruby の CSV ライブラリやLibreOffice/Calc(表計算)でそれを正しく処理できる。
実験として、Calcを用いて次のようなデータを作成してみよう。 表計算ドキュメントを新規作成して以下のように入力する。
Calcでデータ入力をする前に以下の手順により クォートの自動置換機能を無効化する。
メニュー: ツール → オートコレクトオプション → 「言語固有のオプション(OOoは[ユーザ定義引用符)」 → ダブルクォーテーションマーク(二重引用符) の □置換 のチェックを外す
A | B | C | |
---|---|---|---|
1 | 列1 | 列2 | 列3 |
2 | 1 | 2 | 3 |
3 | foo | Hello, "world" | a b c |
C3セルの3行に渡るabcにある改行は、セル内で C-[Enter] をタイプすることで入力できる。
これをCSV形式で保存すると以下のようになる。
列1,列2,列3
1,2,3
foo,""Hello, ""world"""","a
b
c"
3行3列目の改行入りabcの部分が行分割されているのが分かる。Ruby の CSV ライブラリを用いて読み込み、行ごとにフィールドを 「…」 で括って表示する例を示す。
#!/usr/bin/env ruby # coding: utf-8 Encoding.default_external = 'utf-8' # CSVファイルがutf-8のとき require 'csv' # CSVライブラリ読み込み data = CSV.read("newline.csv", headers: true, encoding: 'utf-8') # 行ごとに取り出す n = 0 # 行をカウントする data.each{|row| # rowには1行ずつ値が入り繰り返される printf("=== %d行目 ===\n", n+=1) row.each {|k, v| # ハッシュの key,value のように取り出せる printf("%s=「%s」\n", k, v) } }
実行結果を以下に示す。
./read-newline-csv.rb
=== 1行目 ===
列1=「1」
列2=「2」
列3=「3」
=== 2行目 ===
列1=「foo」
列2=「Hello, "world"」
列3=「a
b
c」
このように、データ項目に改行を含むものも、 それを正しく読み書きできる処理系を適切に用いることで CSV 形式で効率よく取り扱うことができる。