散布図をつくる

ふたつの数量がどのように関連しているかを知るためには、 一方を横軸、他方を縦軸にとって点をうつことによって2次元の図にした 「散布図」 (英語では scatterplot) が、基本的方法である。

Excel などの表計算ソフトウェアでも散布図をつくることができるが、 一定の方式で多数のくみあわせの図をつくるには、 プログラムによったほうがよい。

ここでは、Python の matplotlib と pandas をつかった方法の初歩を説明する。

データ例

つぎの csv ファイルは、Excel でつくったワークシートを、 「名まえをつけて保存」で ファイル形式「csv」を指定してかきだしたものである。 (このファイルの日本語文字コードは Shift_JIS である。 Excelのバージョンによっては csv ファイルの文字コードが UTF-8 になるかもしれない。)

散布図のつくりかたの説明にとって重要ではないが、 データの内容はつぎのもののうち、1991年から2021年までをかきぬいたものである。

csv ファイルの表を pandas のデータフレームに読みこむ

つぎのようなプログラムで、上にあげた csv ファイルを、 pandas の データフレーム (変数名を df とした) に読みこむことができる。 「header=0」は、csv ファイルのいちばん上の行の内容を列の見出しとしてつかうことを 指定している。

import pandas as pd

df = pd.read_csv('nino3_ts.csv', header=0, encoding='shift_jis')

これだけでは何も表示しないが、 つづいて「print(df)」を実行すれば、よみこんだ表の内容が文字で端末画面に表示される (途中が省略されるかもしれない)。 「print(df['1月'])」 「print(df['台風発生数']) などとすれば、指定した見出しの列の内容が表示される。 (dfにつづいて角かっこ、その内側に single quotation marks があることに注意。)

1つのデータフレームの2つの列によって散布図をつくる

作図には matplotlib をつかうので、「import pandas ...」の行につづいてつぎの行を追加する。

import matplotlib.pyplot as plt

そして、「df = pd.read_csv ...」のあとで

plt.plot(df['1月'], df['台風発生数'])

として、最後に

plt.show()
とする。これで、横軸に 1月のNino3指数、縦軸に台風発生数をとった散布図が画面に表示される ... と思ったのだが、plot で表示形式を指定しないと折れ線になるので、 表にあらわれた順に線でつながれた図ができてしまう。 つぎのようにすれば、各点を〇印であらわした、散布図らしいものになる。

plt.plot(df['1月'], df['台風発生数'], 'o')

matplotlib には散布図のための scatter という関数がある。 これをつかうならば、つぎのように書ける。

plt.scatter(df['1月'], df['台風発生数'])

また、pandas のデータフレームに scatter という機能がある。 そのなかから matplotlib がよびだされるらしく、結果は同じになるが、 よびだしかたがちがい、このほうがわかりやすいかもしれない。

df.plot.scatter(x='1月', y='台風発生数')

図の表題や軸の見出しをつけ、 結果を画面に表示するかわりに PNG 形式の画像ファイルにしまうようにした プログラムはつぎのようになる。 (図に日本語文字を表示させるときはフォントの指定が必要になる。 ここでは MS Windows 上で実行したので MSゴシックのフォントを指定している。)

import pandas as pd
import matplotlib.pyplot as plt

df = pd.read_csv('nino3_ts.csv', header=0, encoding='shift_jis')

plt.scatter(df['1月'],df['台風発生数'])
plt.xlabel('Nino3 指数 (1月)', fontname='MS Gothic')
plt.ylabel('台風発生数', fontname='MS Gothic')
plt.title('年ごとの比較, 1991-2021年', fontname='MS Gothic')
#plt.show()
imagefilename = 'nino3_ts.png'
plt.savefig(imagefilename)
plt.clf()
plt.close()

ひとつひとつの点に文字列をつける (暫定)

散布図のそれぞれの点がどの年のものであるかを書いておきたい。 scatter 関数に、点に文字をそえる機能があるか、さがしたのだが、見あたらない。 ここでは、1点ごとに matplotlib の text 関数をよびだす形でやってみる。

上のプログラム例の plt.scatter のよびだしのつぎに、つぎのようなプログラムコードをさしこむ。 データフレームの行数をかぞえ、行についてのループをまわす。 年の情報は「年」の列 (ここでは iloc をつかって「列番号 0」のような形で参照している) に 「1991年」のような形ではいっていて、データフレームに文字型で読みこまれているが、 「年」という字は不要なので、それをとりのぞいた文字列を syear としてつくっておく。 「1月」の列の値を x 座標、「台風発生数」の列の値を y 座標として、 その位置の真上に文字列をかく。 (plt.text の va は vertical alignment、鉛直方向の位置合わせだが、 「文字列の下側を 指定された y 座標にあわせる」と考えるので「bottom」をつかう。 ha は horizontal alignment、水平方向の位置合わせで、 ここでは、文字列の中央とした。)

nk = len(df)
for k in range(nk):
    syear = df.iloc[k, 0].replace('年', '')
    x = df['1月'].iloc[k]
    y = df['台風発生数'].iloc[k]
    plt.text(x, y, syear, ha='center', va='bottom')

結果はつぎのようになる。(文字がかさなって読めないところがある。)

nino3_ts.png

文字がかさなることの対策 [2022-11-27 追加, 2022-12-02 改訂]

文字がかさなることの対策を自動化するのはむずかしい。 手作業をまじえた対策としては、 暫定的につくった図を人が見て判断し、 文字がかさなるところは、文字の位置をずらす、文字を小さくする、 あるいは文字を省略する、という手順が考えられる。

例として、文字を小さくするばあいにプログラムをどう修正するかをしめす。

nk = len(df)
for k in range(nk):
    syear = df.iloc[k, 0].replace('年', '')
    x = df['1月'].iloc[k]
    y = df['台風発生数'].iloc[k]
    fs = df['fontsz'].iloc[k]
    plt.text(x, y, syear, ha='center', va='bottom', fontsize=fs)

この例文を実行すると、「2」を入れたところは小さな文字をかくが、たぶん実質的に読めない。 fontsz の値が 10 でなければ文字をかかないようにしたいならば、 上のとおり fs の値をきめたあとの plt.text をつぎのように 条件つきにする。

    if fs == 10:
        plt.text(x, y, syear, ha='center', va='bottom')

文字と、データをあらわす点 (x, y) との位置関係をしめすのが horizontal alignment (ha) と vertical alignment (va) である。 これを、データ点ごと (材料となる表の行ごと) に変えることもできる。 そのやりかたの例をしめす。

nk = len(df)
for k in range(nk):
    syear = df.iloc[k, 0].replace('年', '')
    x = df['1月'].iloc[k]
    y = df['台風発生数'].iloc[k]
    hali = df['hali'].iloc[k]
    vali = df['vali'].iloc[k]
    plt.text(x, y, syear, ha=hali, va=vali)

場合わけして記号をかえる [2022-11-22 追加]

事例には散布図の両軸のほかにも属性があって、 それによって散布図の点のところにかく記号をかえたいことがある。

ここで例としてとりあげている「Nino 3 指数」は、気象庁でエルニーニョの診断に つかわれているものである。 気象庁が診断したエルニーニョとラニーニャの期間は、つぎのページにしめされている。 そこでつかわれた時間のくぎりは、年よりは短いが月よりは長く、 「冬、春、夏、秋」のような表現がつかわれている。

ここでは暫定的に、nino3_ts.csv のあいている列を「ENLN_1月」とし、 各年1月が、気象庁の判断によるエルニーニョ期間にふくまれるとき「E」、 ラニーニャ期間にふくまれるとき「L」を入れることにする。 どちらでもないときは何も入れない。

このファイルを、pandas のデータフレーム df に読みこんだあと、 「ENLN_1月」の列の内容によって df から抽出した部分データフレーム dfen, dfln, dfnn をつくる。 (セルに何も入れなかった行の抽出は、空文字列との比較ではうまくいかず、 データ欠損を判定する pandas の関数 isna() をつかう必要がある。)

部分データフレームそれぞれについて、記号をかえて、plt.scatter() をよびだす。

なお、図の x軸と y軸の範囲は、上の (部分にわけないばあいの) プログラム例では データ値の範囲から自動的にきめさせたのだが、 今度は、あらかじめ plt.xlim(), plt.ylim() で指定しておくことにする。

nino3_ts_enln.png


複数の csv ファイルの内容をあわせてつかう [2022-06-23]

複数の表にある情報をあわせてつかうとき、 ファイル上で表をつなげなくても、 それぞれの csv ファイルをデータフレームに読みこんでおき、 データフレームの merge 演算でつなぎあわせてつかえばよい。

キーとなる列の内容が同じばあい

たとえば、上の nino3_ts.csv と同様な形の ninowest_ts.csv というファイルがある。 (内容はエルニーニョ監視指数のうちの Nino West である。) 「年」の列は、列の見出しも、年の情報のかきかたも、csv ファイル間で同じである。 (なん行めがどの年のデータであるかは同じでなくてもよい。) このようなばあいは、すなおに「年」の列をキーとして merge することができる。

import pandas as pd

dfnino3 = pd.read_csv('nino3_ts.csv',  header=0, encoding='shift_jis')
dfwest = pd.read_csv('ninowest_ts.csv', header=0, encoding='shift_jis')
df = pd.merge(dfnino3, dfwest, on='年')

結果の df の '年' の列はひとつになっている。 そのほかの、列見出しが同じである列は、たとえば '1月' ならば、 '1月_x' と '1月_y' になっている。 この '_x' などは merge がきめたもので、いつもこの形になるかどうかはよくわからない。 ともかくこの結果をつかうならば、 つぎのようにすれば、横軸に 1月の Nino 3、縦軸に 1月の Nino West をとった 散布図をつくることができる。

df.plot.scatter(x='1月_x', y='1月_y')

キーとなる列に編集が必要となるばあいの一例 [2022-06-24 追加]

mergeのキーとなる列 (いまの例では「年」の列) の記録形式がちがうと、 merge のまえにデータフレームの加工が必要になる。 たとえば、一方には「年」の字がはいっているが、他方にははいっていないとすると、 どちらかにそろえてからそれをつかって merge する。

まず、(すでにしめした) 文字列から「年」の字をとりのぞく方法を応用して、 dfnino3 のデータフレームに、「年」の字をとりのぞいて整数型にした年の値からなる あたらしい列 (列名は 'yearxxxx' とした) を追加する。 それには、「dfnino3 = pd.read_csv ...」につづいて、つぎのプログラムコードをさしこむ。 【これは k についてのループにしなくても書けるかもしれないが、 まだその方法を見つけていない。】

nk = len(dfnino3)
yearlist = []
for k in range(nk):
    syear = dfnino3.iloc[k, 0].replace('年', '')
    year  = int(syear)
    yearlist.append(year)
dfnino3['yearxxxx'] = yearlist

このようにしてつくった dfnino3 の 'yearxxxx' の列と、 もともと整数型で記録されていた dfts の '年' の列とを同一視して merge する。

df = pd.merge(dfnino3, dfts, left_on='yearxxxx', right_on='年')

複数の列からの演算によって列を追加する [2022-06-27 夜 追加]

csv ファイル 「ts.csv」に、月ごとの台風発生数がはいっていて、 列の見出しは「'1月'] のようなかたちでつけられているとする。 ただし、発生のない月は、空欄 (null string) になっている。

すなおに read_csv をつかったつぎのプログラムで読んでみる。

dfts = pd.read_csv('ts.csv', header=0, encoding='shift_jis')

すると、空欄のところが「NaN」 (not a number) になっている。 データ欠損あつかいの一種である。 欠損がふくまれる数値群を合計しようとすると、合計値も欠損になってしまう。

いまのばあいは、空欄を 0 とみなしたほうがよいので、 読みこんですぐに データ欠損を 0 でおきかえることにする。 それには、つぎのように「fillna(0)」をつかう。

dfts = pd.read_csv('ts.csv', header=0, encoding='shift_jis').fillna(0)

それから、 (一例として) 4月、5月、6月の発生数を合計して「AMJ」という名まえの列にいれることにする。

dfts['AMJ'] = dfts['4月']+dfts['5月']+dfts['6月']

これで、dfts['4月'] などと同じあつかいで dfts['AMJ'] をつかえるようになる。


2022-06-23 .. 2022-06-27, 2022-11-22, 2022-11-27, 2022-12-03
増田 耕一 (MASUDA Kooiti)
[計算機を利用した気象分野の実習の教材]
立正大学用 [セミナーのページ]