gnuplotでの配列(array)の使用

gnuplot 5.2.0以降では(本物の)配列が使えるようになりました(5.0系以前のバージョンで配列もどきを使いたい場合の説明はこちら)。配列を使うことで、様々な数値の処理が非常に簡単に行えるようになるだけでなく、ファイル名を配列に入れることで複数のファイルの処理も簡単に行えたりするようになります。また、数値の配列は、ファイルのように「プロット」してグラフ化することができます。

配列の宣言と値の代入

配列はarrayキーワードを用いて宣言します。例えば、Aというサイズが5の配列は以下のように宣言します。

array A[5]

なお、gnuplotの配列の番号は1から開始されます。上の例では、配列AA[1]A[2]、…A[5]というデータを持ちます。一方、例えばデータ行の番号(つまりusing内で使う$0column(0))など、gnuplotでは0から始まるものもあるので、注意が必要です。

さて、このままでは、配列はデータを持っていないので、値を代入してやる必要があります。数値の代入は、通常の変数への値の代入と同じように行えます。

A[1] = 1
A[2] = 2
A[3] = 3
A[4] = 4
A[5] = 5

これだと面倒なので、do for等を用いたループを使うほうが便利な場合があります。

do for [i=1:5]{
    A[i] = i
    }

また、宣言と同時に値を代入することもできます。この場合は角かっこ[]とカンマ,を用います:

array A[5] = [1, 2, 3, 4, 5]

この方法で、代入の行が複数にわたる場合は\を改行の前に入れる必要があります。

array A[5] = [\
  1, 2, \
  3, 4, \
  5\
  ]

ちなみに、gnuplotの配列はデータの型に関する制限は無いので、必要であれば以下のように数値(実数、複素数、整数)と文字列を含むような配列を作ってもかまいません。(下記で、{2.0, 2.0}は複素数2 + 2iのことです。)

array B[4] = [\
   1.0, {2.0, 2.0}, "third element", 4 \
   ]

宣言した配列のサイズを超えて代入しようとしたり、正でない整数の添え字を使おうとすると、エラーが出ます。以下の2つのスクリプトは、

array B[4]
B[5] = "5th element"
array C[4]
C[0] = 0.0

いずれも下のようなエラーが出て実行が止まってしまいます。

"C:/..../xxxx.plt", line x: array index out of range

一方以下の場合は、

array A[4] = [1, 2, 3, 4, 5]

下のようなエラーになります。

"C:/..../xxxx.plt", line x: unexpected or unrecognized token

配列の使用

このように定義した配列Ai番目の値は、多くのプログラミング言語と同様にA[i]という形で参照することができます。例えば、以下のようなスクリプトでは、配列の内容をprintコマンドでコンソールに出力します。

array A[4] = [1.0, {2.0, 2.0}, "third element", 4]

print A[1]
print A[2]
print A[3]
print A[4]

これを実行すると、

1.0
{2.0, 2.0}
third element
4

のような出力が得られます。

ここでさらに

print A[5]

として、宣言した範囲外の要素を呼び出そうとすると、

"C:/..../xxxx.plt", line 7: array index out of range

というエラーが出てしまいます。A[0]A[-1]など、正でない整数の添え字を用いてしまった場合でも同じエラーが出ます。

一方、以下のように、宣言だけして、値を代入しなかった配列の要素を呼び出すと、

array B[4]

print B[1]
print B[2]
print B[3]
print B[4]

出力は以下のようになり、値の入っていない配列要素の呼び出しでは<undefined>という値が返されることがわかります。

<undefined>
<undefined>
<undefined>
<undefined>

データファイルから配列に数値を代入する方法

データファイルのデータを丸ごと配列に入れることができれば、データにいろいろな演算をすることができて便利なケースがあります。ここでは、まず、以下のような簡単なデータファイルarray_data_1.datを配列に読み込むことを考えます。

1	3
3	8
5	11
6	12
7	6

1つのデータ列を配列へと格納

ファイルから数値を配列に代入するには、以下のようにstatsコマンドと、累次代入演算子(,)を使います。

# データサイズの取得
stats "array_data_1.dat" using 1 nooutput
N = STATS_records   # データの行数

# 配列の宣言
array A[N]

# データの配列への保存
stats "array_data_1.dat" using (A[$0+1] = $1, 0) nooutput

# 配列の内容の表示
do for [i=1:N]{
     print A[i]
     }

1行目のstatsコマンドでは、データの数を取得しています。そのデータの数Nだけのサイズの配列A[N]を宣言し、その次のstatsコマンドのところで、1コラム目のデータ$1を配列要素A[$0+1]に代入してから0という値を評価しています。配列の番号が$0+1となっているのは、先にも述べた、「gnuplotでは行番号は0から始まるのに、配列の番号は1から始まる」という微妙な(しかし結構厄介な)違いのためです。また、累次代入演算子(,)の最後に評価する0は、他の値を使ってもかまいません。

このスクリプトを実行すると、以下のような出力が得られます。きちんとデータファイルの内容が配列に入れられていることがわかります。

1.0
3.0
5.0
6.0
7.0

2つのデータ列を配列へと格納

累次代入演算子の中身を増やせば、1列目のデータを配列Xへ、2列目のデータを配列Yへ格納したりすることもできます:

# データサイズの取得
stats "array_data_1.dat" using 1 nooutput
N = STATS_records   # データの行数

# 配列の宣言
array X[N]
array Y[N]

# データの配列への保存
stats "array_data_1.dat" using (X[$0+1] = $1, Y[$0+1] = $2, 0) nooutput

# 配列の内容の表示
do for [i=1:N]{
     print X[i], Y[i]
     }

このスクリプトを実行すれば、以下のようになり、1列目のデータが配列Xへ、2列目のデータが配列Yへ、それぞれ格納出来ていることがわかります。

1.0 3.0
3.0 8.0
5.0 11.0
6.0 12.0
7.0 6.0

任意の列数のデータ全体を配列へと格納

これを繰り返せばどんなサイズのデータファイルも配列に格納できますが、データの列数が多くなってくると、累次代入演算子の部分を書くのが大変になってきます。例えば、以下のような10行×10列のarray_data_2.datのすべてのデータを配列に入れることを考えます。

1	2	3	4	5	6	7	8	9	10
11	12	13	14	15	16	17	18	19	20
21	22	23	24	25	26	27	28	29	30
31	32	33	34	35	36	37	38	39	40
41	42	43	44	45	46	47	48	49	50
51	52	53	54	55	56	57	58	59	60
61	62	63	64	65	66	67	68	69	70
71	72	73	74	75	76	77	78	79	80
81	82	83	84	85	86	87	88	89	90
91	92	93	94	95	96	97	98	99	100

これを読み込むために、例えばA1[10]A2[10]、…A10[10]という10の配列を定義して、累次代入演算子をつかって代入していってもいいのですが、それだと面倒ですし、列の数が増減したときにはスクリプトを大きく書き換えなければならなくなってしまいます。一方、以下のようなスクリプトを使うと、データの列数によらず、ij行目のデータをA[i+(j-1)*M]に格納することができます(Mはデータの列数)。

# データサイズの取得
stats "array_data_2.dat" nooutput
N = STATS_records # データの行数
M = STATS_columns # データの列数(コラム数)

# 配列の宣言
array A_2d[N*M]

# データの配列への保存
stats "array_data_2.dat" using (sum[i=1:M] (A_2d[i + $0*M] = column(i), 0)) nooutput

# 配列の内容の表示
do for [i=1:N*M]{
     print A_2d[i]
     }

このスクリプトでは、まずstatsコマンドを使って、データの列数と行数を取得して、それぞれMNという変数に保存しています。その後、N*Mというサイズの配列A_2dを宣言します。その後、statsコマンドの中でループを使って配列にデータを保存したいのですが、gnuplotではusingの中で使えるループが存在しません。そこで、usingの中でも使えるsum演算子を、繰り返しループとして用いますsum演算子でi変数を1からMまでループさせ、i列目のデータ(column(i))をA_2d[i + $0*M]に代入しています($0は「データの行数−1」であることに注意)。

このスクリプトの実行結果は、以下のようになり、ちゃんと全部のデータが配列A_2dに格納されていることがわかります。

1.0
2.0
3.0
4.0
5.0
(中略)
96.0
97.0
98.0
99.0
100.0

バイナリデータを配列に格納

同じように、画像などのバイナリデータの内容も配列に格納できます。下図のような900×600ピクセルのjpg画像(photo.jpg)の内容を配列に格納することを考えます。

そのためには、以下のスクリプトを用います。以下の例では、statsコマンドの代わりにunknownターミナル(つまり、何もプロットしない)にplotしながら、配列に値を代入しています。plotするときにはbinary filetype=autowith rgbimageを使って、バイナリのjpgファイルを読み込んでいます。後で述べるように、配列はファイルと同じようにplotすることが可能で、その機能を使って配列の表示テストをすると、元の写真と同じ画像がgnuplot上に表示できていることがわかります。(usingの引数が (int(Nh*Nv-$1)%Nh)(int(Nh*Nv-$1)/Nh)となっているのは、1番目のピクセルが画像の左上のピクセルになっているためです。)

Nh = 900 # 画像の横ピクセルサイズ
Nv = 600 # 画像の縦ピクセルサイズ

# 配列の確保(r-g-b)
array Pr[Nh*Nv]
array Pg[Nh*Nv]
array Pb[Nh*Nv]

# 配列への読み込み
set term unknown
i=1
plot "photo.jpg" binary filetype=auto using (Pr[i]=$1, Pg[i]=$2, Pb[i]=$3, i=i+1, $1):2:3 with rgbimage
set term pop

# 配列の表示テスト
set size ratio -1
plot Pr using (int(Nh*Nv-$1)%Nh):(int(Nh*Nv-$1)/Nh):(Pr[$1]):(Pg[$1]):(Pb[$1])  with rgbimage

配列をプロットする

gnuplotの配列の特徴として、plotコマンドに配列名を渡すことで、配列のデータをグラフにプロットできるという点が挙げられます。

例えば、以下のように、配列Aplotコマンドに渡すと、配列の中身をグラフ化できます。この場合、横軸の値は配列の添え字・縦軸の値は配列に格納されている値になります。

array A[4] = [1, 5, 3, 8]
plot A with linespoints

配列のプロットの場合もusingを使えます。この場合、1コラム目が配列の添え字、2コラム目が配列の値になります。下のコマンドでは、上の図と同じプロットができます。

array A[4] = [1, 5, 3, 8]
plot A using 1:2 with linespoints

需要が多いと思うのが、Xデータを例えば配列X[]に、Yデータを例えば配列Y[]に入れておいて、(X[i], Y[i])をプロットするという方法です。これは上記のusingを使えば可能になります。

set size ratio -1
set angles degrees
array X[12] = [cos(0), cos(30), cos(60), cos(90), cos(120), cos(150), cos(180), cos(210), cos(240), cos(270), cos(300), cos(330)]
array Y[12] = [sin(0), sin(30), sin(60), sin(90), sin(120), sin(150), sin(180), sin(210), sin(240), sin(270), sin(300), sin(330)]

plot Y using (X[$1]):2 w lp

こうすると、配列Yの添え字(using$1)が配列Xに与えられるので、以下のように(X[i], Y[i])をプロットできます。

やや二度手間感はありますが、plotの部分を以下のようにした方がスクリプトの意味は読み取りやすいかもしれません。

plot Y using (X[$1]):(Y[$1]) w lp