はじめての gnuplot プログラミング

第2版  update: 2022年9月
葛西 真寿 弘前大学大学院理工学研究科

gnuplot はグラフ作成アプリケーションですが,条件分岐や繰り返し処理などを利用することで,プログラミング言語としても利用できます。この Notebook では,プログラミング言語としての共通部分に焦点を絞って解説します。

エンゲル係数を求める例

エンゲル係数とは,家計の消費支出に占める飲食費(食料費,食費)の割合(パーセント単位)です。

消費支出 110,000円,飲食費 37,000円の人のエンゲル係数を求める例。gnuplot では以下のように print コマンドを使うとすぐ答えが出ます。

本稿のように Jupyter Notebook 環境では,以下の1行を書いたあとに,Shift キーを押しながら Enter キー(または Return キー)を押して実行します。

(パーセントにするのに 100 ではなく,100.0 をかける理由は後で説明します。)

In [1]:
print 37000 * 100.0/110000
Out[1]:
33.6363636363636

ここからは,単に電卓的に使うだけではなく,応用・発展も見据えて以下のようなプログラム文を作成して実行することにします。

以下の例では,

  1. # で始まる部分はコメント。コメントはそれを読む人間のための注釈であり,プログラムの実行時には無視されます。
  2. 3行目で変数 spending に消費支出である 110000(円)という数値を代入し,
  3. 4行目で変数 food に飲食費である 37000(円)という数値を代入し,
  4. 5行目でエンゲル係数を計算して,変数 eng に代入し,表示させています。
In [2]:
# エンゲル係数を求める
# 消費支出 spending, 飲食費 food, エンゲル係数 eng 
spending = 110000
food= 37000
eng = food * 100.0 / spending
print eng
Out[2]:
33.6363636363636

エラーとデバグ

意に反して,プログラムがうまく動作しない場合があります。たとえば,上記の 3行目を以下のように書いた場合。

掛け算を示す演算子は *,割り算を示す演算子は / であるところを誤って,×÷ と書くと,これらの記号は gnuplot では演算子として定義されていないのでエラーとなります。このようなエラーは文法エラー と呼ばれ,Jupyter Notebook では,以下のように誤りやその箇所を指摘してくれます。

In [3]:
spending = 110000
food= 37000
eng = food × 100.0 ÷ spending
print eng
eng = food × 100.0 ÷ spending
           ^
unexpected or unrecognized token: ×


別の種類の誤りもあります。たとえば,「100.0」を「1000」と入力した場合...

In [4]:
spending = 110000
food= 37000
eng = food * 1000 / spending
print eng
Out[4]:
336

上記の例では文法エラーの警告が出ませんが,答えは明らかに変です。(エンゲル係数の最大値は100だから。)

このような場合はコンピュータにとって誤りではなく,指示通りに計算し,その結果を表示します。 このようなプログラム作成者の不注意や勘違いによって生じるエラーには注意が必要です。

プログラムが正しく動作しない場合は,誤りの箇所を探し,修正して再度動作を確認します。正しく動作するようになるまで,プログラムを修正し,実行(Shift + Enter)します。この作業をデバグといいます。

データの型と変数

上記の例の 1行目では,spending という名前の変数に 110000 という 数値を代入しています。変数名はアルファベットで始まる英数字列にします。大文字と小文字は区別されます。

変数 moji に文字列を代入する場合は,以下のように " で囲みます。

In [5]:
moji = "エンゲル係数"
print moji
Out[5]:
エンゲル係数

式と演算

主な演算子

足し算は +, 引き算は -,掛け算は *,割り算は /。です。

優先的に計算したい箇所は丸括弧 ( )で グループ化します。

In [6]:
print 2 +  50 - 5 * 6 /2
print 2 + (50 - 5 * 6)/2
Out[6]:
37
12

文字列の連結演算は . を使います。以下の例を参照。

In [7]:
mo = "エンゲル"
ji = "係数"
moji = mo . ji
print moji
Out[7]:
エンゲル係数

文字列と数値を直接連結することはできません。

In [8]:
spending = 110000
food= 37000
eng = food * 100.0 / spending

print "エンゲル係数は " . eng
Out[8]:
internal error : STRING operator applied to undefined or non-STRING variable

そんなときは,sprintf() 関数で数値を文字列に変換して連結します。

In [9]:
spending = 110000
food= 37000
eng = food * 100.0 / spending

answer = "エンゲル係数は" . sprintf("%6.1f", eng)
print answer
Out[9]:
エンゲル係数は  33.6

print のところは,文字列にして連結しなくても,以下のようにも書くことができます。 print 文は,カンマ (,) で区切ることによって複数の項目(変数)を表示することができます。

In [10]:
spending = 110000
food= 37000
eng = food * 100.0 / spending

print "エンゲル係数は ", eng, " です。"
Out[10]:
エンゲル係数は 33.6363636363636 です。

除算 / は常に浮動小数点数を返します。 // 演算子は 整数除算を行い、(小数部を切り捨てた) 整数値を返します。剰余は、% で求めます。

除算 / の例を以下に示します。

  • 1行目では,整数を整数で割るので,小数分を切り捨てた整数値を返します。

  • 2行目では,17. と最後に小数点をつけたので,実数値を返します。

  • 3行目のように,剰余は、% で求めます。

In [11]:
print 17 / 3
print 17./ 3
print 17 % 3
Out[11]:
5
5.66666666666667
2

冪乗は ** です。

In [12]:
print 2**4
print 2**(1./2)
print sqrt(2)
Out[12]:
16
1.4142135623731
1.4142135623731

(練習)

1 天文単位(1 au)の距離を光速 c で進むのに要する時間はいくらか。ただし,

  • 1 天文単位は 12 桁の定義定数(誤差無し)で 149597870700 m
  • 光速 c は 9 桁の定義定数(誤差無し)で 299792458 m/s である。

参考:

対話型プログラムは想定されていない模様

上記の例では,プログラムの中で消費支出や飲食費を決めていましたが,今度はプログラムを実行する人が入力できるようにしてみたいところですが,gnuplot にはキーボードからの入力を受け付けるような適当な命令文がないようです。

関数の定義と呼び出し

(これまでの例では1回しか計算していませんが)よく使う計算を「関数」として定義する例です。

以下の例では,1行目でエンゲル係数を計算する関数 calc() を定義し,5行目でその関数を呼び出しています。

In [13]:
calc(spending, food) = food * 100.0 / spending

spending = 110000
food = 37000
print "エンゲル係数は ", calc(spending, food)
Out[13]:
エンゲル係数は 33.6363636363636

いったん定義された関数は,以下のように何回でも利用できます。

In [14]:
print calc(120000, 53000)
Out[14]:
44.1666666666667

(練習)

消費支出を入力すると,エンゲル係数が 20 ~ 40 となる飲食費を表示するプログラムをつくりなさい。

数学関数を使う

gnuplot では,$\sin x, \cos x, \tan x$などの数学関数が利用できます。また,数学定数として円周率 pi などが使えます。

In [15]:
print pi, "  ", sin(pi/2),"  ",  cos(pi/3),"  ",  tan(pi/4)
Out[15]:
3.14159265358979  1.0  0.5  1.0

以下では,関数の例として,

  • それ以下の整数を返す floor()
  • 整数部分を返す int()

を使用しています。

  • ちなみに,小数点以下を切り上げる関数は ceil() です。
In [16]:
print floor(12.3), int(12.3)
print floor(-12.3), int(-12.3)
Out[16]:
12 12
-13 -12
In [17]:
spending = 110000
food = 37000
eng = calc(spending, food)
print "エンゲル係数は ", floor(eng), " です。(小数点以下切り捨て)"
Out[17]:
エンゲル係数は 33 です。(小数点以下切り捨て)

また,現在のところ gnuplot には四捨五入する関数がありませんので,「書式指定」で説明する sprintf() でフォーマットを指定して表示します。

gnuplot で使える組み込み関数

gnuplot で使える組み込み関数については,以下のサイトが参考になります。

(練習)

小数点以下を切り上げたエンゲル係数の値を表示させるように上記のプログラムを変更しなさい。

条件分岐

条件分岐とは,例えばエンゲル係数の値によって実行文を変えることです。例を示します。

In [18]:
spending = 110000
food = 37000
eng = calc(spending, food)

if (eng >= 40) {
    print "エンゲル係数は ", floor(eng), " です。高い値です。"}
  else {
    print "エンゲル係数は ", floor(eng), " です。"}

food = 47000
eng = calc(spending, food)

if (eng >= 40) {
    print "エンゲル係数は ", floor(eng), " です。高い値です。"}
  else {
    print "エンゲル係数は ", floor(eng), " です。"}
Out[18]:
エンゲル係数は 33 です。
エンゲル係数は 42 です。高い値です。

if 文の書式は以下の通りです。

if (条件式) { 
    条件式が満たされた場合に実行する文
    } else {
    それ以外の場合に実行する文
}

if 文は入れ子にもできます。

関係演算子

上の例では,eng >= 40 つまり eng40 以上のとき,という関係演算子を使いました。他の例は以下のとおりです。

a < b  ab より小さい
a <= b  ab 以下
a > b  ab より大きい
a >= b  ab 以上
a == b  ab が等しい

(練習)

上記の条件分岐を,エンゲル係数が

  1. 40 以上の場合には「高い値です。」
  2. 20 未満の場合には「低い値です。」

と表示させるように変更しなさい。(ヒント:if 文は入れ子にもできます。)

繰り返し処理

以下の例では,1行目で代入された消費支出に対してエンゲル係数を計算します。

1回だけ計算して表示するのではなく,2行目の飲食費の初期値(ここでは 40000 円)から,5行目の条件式(ここでは 80000 円以下)が成り立つ場合に,8行目にあるように 5000 円ずつ増やしながらエンゲル係数を表示します。

In [19]:
spending = 110000
food = 30000
print "消費支出 ", spending, "円に対するエンゲル係数の値" 

while (food <= 50000){
    eng = calc(spending, food)
    print "飲食費 ", food, "円の場合: ", eng
    food = food + 5000
}
Out[19]:
消費支出 110000円に対するエンゲル係数の値
飲食費 30000円の場合: 27.2727272727273
飲食費 35000円の場合: 31.8181818181818
飲食費 40000円の場合: 36.3636363636364
飲食費 45000円の場合: 40.9090909090909
飲食費 50000円の場合: 45.4545454545455

while 文の書式は以下のとおりです。

while (条件式){
    条件式が成り立つ場合に実行される文
}

同様の繰り返し処理を do for 文を使って行うこともできます。

do for 文の書式は以下のとおりです。

do for [ivar = istart: iend: istep] {
    ivar が istart から iend までの間,実行される文
    }
In [20]:
spending = 110000
print "消費支出 ", spending, "円に対するエンゲル係数の値" 

do for [food = 30000: 50000: 5000] {
    eng = calc(spending, food)
    print "飲食費 ", food, "円の場合: ", eng
}
Out[20]:
消費支出 110000円に対するエンゲル係数の値
飲食費 30000円の場合: 27.2727272727273
飲食費 35000円の場合: 31.8181818181818
飲食費 40000円の場合: 36.3636363636364
飲食費 45000円の場合: 40.9090909090909
飲食費 50000円の場合: 45.4545454545455

(練習)

以下の値を求めるプログラムを作成せよ。

$$\sum_{n = 1}^{10} n^2$$

配列とファイルの読み書き

配列 array

要素が5個の配列の各成分に数値を入れたり表示したりする例です。

In [21]:
array a[5] = [5, 4, 3, 2, 1]
print "全成分を一挙に表示"
print a

print "全成分を縦に表示"
do for [i=1:5]{
     print a[i]
     }
Out[21]:
全成分を一挙に表示
[5,4,3,2,1]

全成分を縦に表示
5
4
3
2
1
In [22]:
# 配列 b の5つの要素を全て 0 に
array b[5] = [0, 0, 0, 0, 0]
print b
# 3番目の要素の値を設定
b[3] = 3
print b
Out[22]:
[0,0,0,0,0]

[0,0,3,0,0]

ファイルから配列への読み込み

以下のようなテキストファイル mydata.txt を用意します。

$ cat mydata.txt
1
2
3
4
5
6

ファイル mydata.txt の数値を配列 a に読み込みます。

In [23]:
# データの行数
stats "mydata.txt" nooutput
N = STATS_records

# 配列の宣言
array a[N]

# データファイルから配列へ代入
stats "mydata.txt" using (a[$0+1] = $1, 0) nooutput

# 配列の内容の表示
print a

# sprintf() で書式を設定できる
do for [i=1:N]{
    print sprintf("%d",a[i])
    }
Out[23]:
[1.0,2.0,3.0,4.0,5.0,6.0]

1
2
3
4
5
6
In [24]:
# 要素の総和,平均,最大値,最小値を出力します。
stats "mydata.txt" using 1 nooutput
print "要素の総和は ", STATS_sum
print "要素の平均は ", STATS_mean
print "最大値は     ", STATS_max
print "最小値は     ", STATS_min
Out[24]:
要素の総和は 21.0
要素の平均は 3.5
最大値は     6.0
最小値は     1.0

次は,以下のような「6行2列」のデータが入ったテキストファイル mydata2.txt を読み込む例です。

$ cat mydata2.txt
1  1
2  4
3  9
4 16
5 25
6 36
In [25]:
# データの行数
stats "mydata2.txt" using 1 nooutput
N =  STATS_records

# 配列の宣言。1次元配列のみ。
array bx[N]
array by[N]

# データファイルから配列へ代入
stats "mydata2.txt" using (bx[$0+1] = $1, 0) nooutput
stats "mydata2.txt" using (by[$0+1] = $2, 0) nooutput

# 配列の内容の表示
print bx
print by

# sprintf() で書式を設定できるが,
# なぜか Jupyter Notebook では行頭のスペースが無視される。
# 苦肉の策として全角スペースを入れてみた。
print "1234567890 桁数"
do for [i=1:N]{
    print " ", sprintf("%3d",bx[i]), sprintf("%5d",by[i])
    }
Out[25]:
[1.0,2.0,3.0,4.0,5.0,6.0]

[1.0,4.0,9.0,16.0,25.0,36.0]

1234567890 桁数
   1    1
   2    4
   3    9
   4   16
   5   25
   6   36
In [26]:
# 要素の総和,平均,最大値,最小値を出力します。
stats "mydata2.txt" using 1:2 nooutput
print "x 要素の総和は ", STATS_sum_x
print "x 要素の平均は ", STATS_mean_x
print "x 最大値は     ", STATS_max_x
print "x 最小値は     ", STATS_min_x
print ""
print "y 要素の総和は ", STATS_sum_y
print "y 要素の平均は ", STATS_mean_y
print "y 最大値は     ", STATS_max_y
print "y 最小値は     ", STATS_min_y
Out[26]:
x 要素の総和は 21.0
x 要素の平均は 3.5
x 最大値は     6.0
x 最小値は     1.0

y 要素の総和は 91.0
y 要素の平均は 15.1666666666667
y 最大値は     36.0
y 最小値は     1.0

配列の要素の総和は以下のように sum を使っても計算できます。

In [27]:
print sum [i = 1:N] by[i]
print sprintf("%d", sum [i = 1:N] by[i])
Out[27]:
91.0
91

ファイルへの書き込み

ファイルへの書き込み例です。この例では,$𝑥$ と $\sin 𝑥$ の値を $𝑥=0.1$ から $𝑥=1.0$ まで書き込んでいます。

In [28]:
set print "myoutput.txt"
  do for [i=1:10]{
    x = 0.1 * i
    print x,"   ", sin(x)
    }
set print
In [29]:
! cat myoutput.txt
0.1   0.0998334166468282
0.2   0.198669330795061
0.3   0.29552020666134
0.4   0.389418342308651
0.5   0.479425538604203
0.6   0.564642473395035
0.7   0.644217687237691
0.8   0.717356090899523
0.9   0.783326909627483
1.0   0.841470984807897

書式指定

浮動小数点数の書式指定

実数を表示する際,全体の文字数や小数点以下の桁数を指定して表示させる例です。

例えば "%8.5f" は全体で 8 桁の表示範囲をとり,小数点以下は 5 桁表示します。(するはずですが,Jupyter Notebook では行頭のスペースが無視されるようです。)

In [30]:
sqrtwo = sqrt(2)
print "12345678901234567890    桁数"
print sqrtwo
print sprintf("%f", sqrtwo)
print sprintf("%8.5f", sqrtwo)
print sprintf("%18.15f", sqrtwo)

# 小数点以下の桁数のみを"%.2f" のように指定することもできます。
print sprintf("%.2f", sqrtwo)
Out[30]:
12345678901234567890    桁数
1.4142135623731
1.414214
1.41421
1.414213562373095
1.41

(練習)

以下の出力を,エンゲル係数の値を(四捨五入して)小数点以下1桁まで表示するように変更しなさい。

In [31]:
spending = 110000
food= 37000
eng = food * 100.0 / spending

print "エンゲル係数は ", eng, " です。"
Out[31]:
エンゲル係数は 33.6363636363636 です。

指数表式の指定

非常に大きい数や小さい数を表示するときには, $1.9891 \times 10^{30}$ や $6.67430 \times 10^{-11}$ などのような指数表示をします。

例えば "%9.2e" では,全体で 9 桁の表示分を確保し,小数点以下は 2 桁表示します。(Jupyter Notebook の表示では行頭のスペースが無視されるかもしれません。)

In [32]:
c = 299792458
print sprintf("%9.2e", c)
print sprintf("%15.8e", c)
print sprintf("光速は%15.8e m/s です。", c)

# 小数点以下の桁数のみを"%.2e" のように指定することもできます。
print sprintf("%.2e", c)
Out[32]:
3.00e+08
2.99792458e+08
光速は 2.99792458e+08 m/s です。
3.00e+08

文字型の書式指定

文字列の書式指定は "%s" です。(Jupyter Notebook では行頭のスペースが無視されるので,苦肉の策として全角スペースを入れてます。)

In [33]:
ten = "1234567890"
print ten

# 行頭に全角スペース
print sprintf(" %8s", ten[5:5])
print sprintf(" %8s", ten[4:5])
print sprintf(" %8s", ten[1:5])
Out[33]:
1234567890
        5
       45
    12345

整数の書式指定

整数の書式指定は "%d" です。"%5d" のように桁数を指定することもできます。

In [34]:
array v[4] = [1, 23, 456, 7890]
do for [i=1:4]{
    print v[i]
    }
Out[34]:
1
23
456
7890

せっかく桁数を揃えて右寄せで表示させようとしても,Jupyter Notebook では最初の行頭のスペースが無視されてしまいます。

In [35]:
do for [i=1:4]{
    print sprintf("%5d", v[i])
    }
Out[35]:
1
   23
  456
 7890

苦肉の策として,表示のためだけに全角スペースを入れてみます。

In [36]:
do for [i=1:4]{
    print sprintf(" %5d", v[i])
    }
Out[36]:
     1
    23
   456
  7890

さらに gnuplot を活用するために

この Notebook では,プログラミング言語として共通の部分に焦点を絞った gnuplot の使い方について解説しました。

グラフ作成アプリケーション gnuplot 固有の機能の活用例については,以下のページを参考にしてください。

日本語のマニュアルについては,以下のサイトを参照してください。