第12回:データ分析入門(講義)#

担当:古居彬

内容#

第12回では,Pythonを用いた基礎的なデータ分析を体験する. 本講義の初回で述べたように,Pythonはデータ分析や機械学習,Webサービスなど幅広い分野で利用されている. その大きな理由として,さまざまな処理を実現するための機能が豊富に準備されていることが挙げられる. このような機能を管理しているのが,モジュール と呼ばれる単位である.モジュールには,さまざまな関数が含まれている. データ解析や画像処理といった高度な処理も,それぞれの用途に特化したモジュールを組み合わせることで比較的簡単に実行することができる. また,モジュールを複数集めたものを ライブラリパッケージ と呼ぶ. 今回は実践編として,いくつかのモジュールを用いてPythonによる簡単なデータ分析を行っていこう.

  • モジュール,ライブラリ,パッケージとその使い方を理解する

  • Pandas によるデータ操作を学ぶ

  • Matplotlib によるデータの可視化を学ぶ

Pythonにおけるモジュール,パッケージ,ライブラリ#

前述の通り,Pythonの強みの一つは,さまざまな処理を実現するための機能が準備されており,幅広い分野に応用できることである. このような機能を管理する単位は モジュール と呼ばれ,その中にはいくつかの関数やクラスが含まれている.

また,複数のモジュールをまとめたものを パッケージ と呼ぶ.

さらに,いくつかのパッケージをまとめて,一括でインストールできるようにしたものを ライブラリ と呼ぶ. ライブラリには大きく2種類があり,Pythonにはじめから付属していてすぐに使える 標準ライブラリ と,追加でインストールすることで利用可能な 外部ライブラリ がある.

以上をまとめた概念図は以下の通りである.

ざっくり「モジュールやライブラリを利用することで,データ解析や画像処理など,高度な機能が利用できるようになる」と理解してもらえれば大丈夫だ.

Pythonの標準ライブラリ#

前述の通り,標準ライブラリとはあらかじめ用意されているライブラリである. 第8回 関数とスコープ で紹介した組み込み関数も,この標準ライブラリの中に含まれている.

組み込み関数は何もせずそのまま使うことができたが,特殊な機能を持つモジュールは後述する import 文で読み込む必要がある.

Pythonの標準ライブラリで提供されている関数・モジュールの一覧は以下の公式ページから参照することができる.

https://docs.python.org/ja/3/library/index.html

Pythonの外部ライブラリ#

標準ライブラリだけでもかなり幅広い処理が実現できるが,データ分析や画像処理,統計解析や科学技術計算など,特定の分野に特化した機能を利用するためには,外部ライブラリを利用する必要がある.

外部ライブラリは,その名の通り外部の団体や個人によって開発,提供されているライブラリである. 外部ライブラリを利用するためには,まず初めにインストールが必要である. インストールされた外部ライブラリは,後述する import 文で読み込むことができる.

なお,Google Colaboratoryでは既に主要な外部ライブラリがインストールされており,これらは最初から import 文で読み込みが可能である.

インストール済ライブラリの表示

Google Colaboratoryにインストールされているライブラリ一覧を表示したい場合は,コードセルにて以下のコマンドを実行すればよい.

!pip list

以下のように,大量のパッケージが表示されるはずだ.

Package                       Version
----------------------------- ----------------------
absl-py                       1.3.0
aeppl                         0.0.33
aesara                        2.7.9
aiohttp                       3.8.3
aiosignal                     1.2.0
alabaster                     0.7.12
albumentations                1.2.1
altair                        4.2.0
appdirs                       1.4.4
arviz                         0.12.1
… (以下省略) 

なお,本講義で利用する外部ライブラリについても,上記インストール済リストに含まれるため,新たにインストールの手順を踏む必要はない. 外部ライブラリを自分で追加インストールする方法を学びたい人は,以下の発展項目を参照されたい.

モジュール/ライブラリのインポート#

前述の通り,モジュールやライブラリに含まれる関数やクラスを使用するためには,対象となるモジュール/ライブラリを読み込む( インポート する)必要がある. これには, import という文を使う.以下のように記述することでモジュールをインポートできる.

import モジュール名

または

import ライブラリ名

モジュール/ライブラリのインポートは,最初に1回記述すればよい.

試しに,数学関係の便利な関数をまとめた math というモジュールをインポートしてみよう.

import math

これで,math モジュールが使えるようになった. math モジュールに含まれる関数を使用するためには, math.関数名 のように記述すればOKである.

例えば,平方根を計算したいときは math.sqrt() のように記述する.

math.sqrt(2)
1.4142135623730951

ほかにも math モジュールには様々な関数が用意されている. 以下に,いくつかの例を示す.

関数

説明

sin(x)

x のサイン(正弦)を返す

cos(x)

x のコサイン(余弦)を返す

tan(x)

x のタンジェント(正接)を返す

exp(x)

自然対数の底(ネイピア数)の x 乗を返す

log(x)

x の自然対数を返す

sqrt(x)

x の平方根を返す

そのうちいくつかを実行してみよう.

print(math.log(13)) # 自然対数
print(math.sin(math.pi/4)) # 正弦(サイン)
2.5649493574615367
0.7071067811865475

また,math モジュールには関数の他にも,数学で多用される 定数 も含まれている. 以下に代表例を表示してみる.

print(math.pi) # 円周率
print(math.e) # 自然対数の底(ネイピア数)
3.141592653589793
2.718281828459045

モジュールに別名をつける#

モジュールをインポートする際,別の名前(例えば短縮名)を付けることが可能である.

import モジュール名 as 短縮名

例えば,以下のように書くことで,モジュール math に短縮名 m を付けてインポートすることができる.

import math as m

このようにインポートすることで,math モジュールに含まれる関数を使用する際, m.関数名 のように簡潔に書けるようになる.

m.sqrt(2)
1.4142135623730951

【発展】特定の関数だけを読み込む#

モジュールやライブラリを用いる際,特定の関数だけを読み込みたいことがある. そのような場合は,以下の表記を用いて指定した関数・クラスだけをインポートすることができる.

from モジュール名 import 関数名

from でモジュール名を指定し,続く import に読み込みたい関数・クラスを指定する(複数の関数・クラスを指定したい場合は,カンマ区切りで並べて書く).

このようにしてインポートした場合,モジュール名を記述しなくとも,関数・クラスをそのまま呼び出すことができる.

from math import sqrt

sqrt(3)
1.7320508075688772

ワイルドカードを用いたインポート

なお,次のようにアスタリスク(*)を用いると,対象のモジュールに含まれるすべての関数やクラスを一気にインポートできる.

from モジュール名 import *

なお,この際の *ワイルドカード と呼ばれ,任意の文字列を意味している. したがって,対象のモジュール内の任意の文字列を名前として持つ関数やクラス(つまり,すべての関数やクラス)をインポートする,という表記法である.

上記のようなインポートの方法は ワイルドカードインポート とも呼ばれ,一見楽な方法に思えるが,一般的なPythonプログラミングにおいては 推奨されない ことが多い.

理由はいくつかあるが,最も多いトラブルは関数や変数の名前衝突である.

通常,外部モジュールや外部ライブラリに含まれている関数やクラス,定数を全て把握していることは少ない. ワイルドカードインポートではそれら把握できていない物も含め全てがインポートされてしまうため,自分が別で定義した関数やクラスの名前がインポートした関数やクラスの名前と衝突する危険がある.

名前の衝突が起こると,変数や関数が上書きされてしまうリスクがあるため,基本的にはワイルドカードインポートは使わない方がよいだろう.

モジュール,パッケージ,ライブラリのまとめ

  • 複数の関数やクラスをまとめたものをモジュールと呼ぶ

  • モジュールをさらにまとめたものをパッケージやライブラリと呼ぶ

  • 外部ライブラリを追加インストールすることで,様々な追加機能が使えるようにある

  • モジュールやライブラリは import 文で読み込むことができる

なお,これ以降は代表的な外部ライブラリである pandasmatplotlib を用いてデータ分析に取り組んでいく. これら2つのライブラリはそれぞれ膨大な数のモジュールを含んでおり,それらの網羅的な紹介は本講義では行わず,各ライブラリの機能を体験する上で最小限の説明にとどめる.

将来的にPythonでプログラミングをする機会が生じたときに,各自で検索しながら様々な具体的な機能を学んで行って欲しい. プログラミングにおいては,網羅的な知識を有しておくよりも,必要に応じて適切に情報を検索し利用する能力の方が重要である.

Pandas によるデータ操作#

では,実際にデータ分析の本題に入っていこう. 今回は,Pandas というライブラリを用いる. Pandasは,データ解析を容易に行うための機能を提供しており,データの操作や加工,分析などさまざまな処理を行うことができる.

以下のコマンドで,まずはPandasをインポートしよう.

import pandas as pd

なお,簡単のため as を用いて pd という短縮名でインポートした.

Series と DataFrame#

Pandasでは,Series(シリーズ)DataFrame(データフレーム) といった特殊なデータ構造が使用される. Seriesは1次元(列形式)のデータ,DataFrameは2次元(行列形式)のデータを扱うための構造である.

実際のデータ分析の場面では2次元のデータを扱うことが多いため,本講義では2次元のデータ構造であるDataFrameを主に扱っていく.

DataFrameは前述の通り,行と列の2次元で表現され,表のような構造となっている. それぞれの列に対して,文字列型や数値型など,一様な型のデータが格納されている.

列と行にはそれぞれ列名と行名(またはインデックス)が付けられており,これらを指定することで,データの集計や抽出,加工に利用することができる.

DataFrameからSeriesを抽出する

DataFrameから抽出した1行または1列の情報は,1次元のデータ構造であるSeriesとなる. 逆に,複数のSeriesを結合してDataFrameとして扱うこともできる.

このように,DataFrameは複数のSeriesの集合と考えることができる.

DataFrameを作成するには,以下のようなコードを書けば良い.

pd.DataFrame(data=格納するデータ, index=行名, columns=列名)

このとき,引数の data には,リスト型や辞書型の変数を指定することができる(復習 → 第2回).

本講義では,ファイルに格納された情報をDataFrameに取り込んで解析に利用していくこととする.

データの読み込み#

今回は,アイスクリーム屋さんで学ぶ楽しい統計学 というサイトで公開されている架空のデータを用いてデータ分析に取り組んでいく.

まず,当該データをダウンロードしよう. 以下のセルを実行すると,data フォルダの中に ice_cream.csv というファイルがダウンロードされる.

!mkdir data
!wget -P data https://raw.githubusercontent.com/ground-zero-programming/zero-pro2024-public/main/docs/12/data/ice_cream.csv
mkdir: data: File exists
zsh:1: command not found: wget

本講義の範囲内では上記のコードが何をやっているかを意識する必要はないが,簡単に説明しよう. !mkdir dataでGoogle Colab内にdataという名前がついたフォルダを作成し,以降の!wget -P data {URL}URLにあるファイルを先ほど作成したdataフォルダにダウンロードするという操作をしている.

さて, ice_cream.csvには,あるアイスクリームチェーン店における8月の客数データ(2週間 = 14日分)が格納されている. このファイルは CSVファイル と呼ばれ,以下のように各データの間がコンマ , で区切られている.

日付, 最高気温, 客数
1, 29, 312
2, 30, 348
3, 29, 284
...

1行目は列のタイトル(ヘッダとも呼ばれる)となっており,2行目から各日付におけるデータが格納されている. また,暑い日にはアイスクリームを食べたいと思う人が増えるかもしれない. そこで,このファイルには各日付における客数以外に,その日の最高気温も含まれている.

CSVファイルを読み込むには,Pandasに含まれる read_csv という関数が便利である.

read_csv 関数は,目的とするファイルの保存場所までの経路(これを パス といいます)を文字列として引数に渡すことで,そのファイルを開くことができる. 今回は data というフォルダに入っている ice_cream.csv を開きたいので,'data/ice_cream.csv' という文字列を渡せばOK. ここで,スラッシュ / はパスを記述する際の区切り文字で, A/B と書くとフォルダ A の中に B があることを意味する.

# ファイルを `df` という変数に読み込む
df = pd.read_csv('data/ice_cream.csv') 

# dfの中身を表示
df
日付 最高気温 客数
0 1 29 312
1 2 30 348
2 3 29 284
3 4 32 369
4 5 33 420
5 6 32 536
6 7 34 652
7 8 27 275
8 9 28 294
9 10 32 368
10 11 34 451
11 12 32 405
12 13 30 458
13 14 28 422

上の出力の結果から,「日付」,「最高気温」,「客数」の3つの列で構成されるデータが読み込まれていることがわかる. なお,一番左の列はPandasによって付けられた行のインデックスである.このインデックスは0からスタートしていることに気をつけよう.

列名(ヘッダ)の認識

read_csv 関数では,デフォルトでCSVファイル中の1行目の情報を列名(ヘッダ)として解釈してくれる. 先ほど読み込んだ ice_cream.csv はまさに1行目にヘッダが入っていたため上手くいったが,場合によってはヘッダのないCSVファイルを読み込むこともあるだろう.

そのような場合は,read_csv 関数の引数として header=None とすればよい.

df = pd.read_csv('data/sample_no_header.csv', header=None) 

また,列名を自分で設定したい場合は,引数 names で指定できる.

df = pd.read_csv('data/sample_no_header.csv', header=None, names=['A', 'B', 'C', 'D']) 

列の抽出#

データフレームから任意の列を抽出したい場合,DataFrame['列名'] のように書けばよい(文字列として指定するため,クォーテーション ' で囲っていることに注意). 例えば上記 df から「最高気温」の列を取り出したい場合は,以下の通りである.

df['最高気温']
0     29
1     30
2     29
3     32
4     33
5     32
6     34
7     27
8     28
9     32
10    34
11    32
12    30
13    28
Name: 最高気温, dtype: int64

同様にして,「客数」の列を取り出してみよう.

df['客数']
0     312
1     348
2     284
3     369
4     420
5     536
6     652
7     275
8     294
9     368
10    451
11    405
12    458
13    422
Name: 客数, dtype: int64

なお,複数の列を取り出したい場合は,取り出したい列名のリスト ['列1', '列2', '列3', ...]DataFrame[ ] の括弧 [ ] の中に記述すればよい.

DataFrame[['列1', '列2', '列3', ...] ]

括弧 [ ] が二重になっていることに注意すること.

例えば最高気温と客数の両方を取り出したい場合は,以下のような書き方になる.

df[['最高気温', '客数']]
最高気温 客数
0 29 312
1 30 348
2 29 284
3 32 369
4 33 420
5 32 536
6 34 652
7 27 275
8 28 294
9 32 368
10 34 451
11 32 405
12 30 458
13 28 422

行の抽出#

コロン(:)で行のインデックスを指定することで,特定の区間の行を抽出することができる.

例えば,2行目から5行目までを抽出したい場合は,スライシングを用いて以下のように書けば良い.

df[2:5]
日付 最高気温 客数
2 3 29 284
3 4 32 369
4 5 33 420

【発展】 iloc を用いて特定の行・列を抽出#

iloc を利用すると,DataFrameに対して行番号と列番号を指定してデータを抽出することが可能である.

DataFrame.iloc[行番号, 列番号]

例えば1行目を抽出する場合は,以下のように書けば良い.なお,行番号のみを指定する場合,列番号の指定は省略できる.

df.iloc[1]
日付        2
最高気温     30
客数      348
Name: 1, dtype: int64

また,3行目から6行目を抽出したい場合は,スライシング(復習 → 第2回)を利用して以下のように書くことができる.

df.iloc[3:6]
日付 最高気温 客数
3 4 32 369
4 5 33 420
5 6 32 536

今度は列を考える. 例えば2列目を抽出したい場合は,行の指定を : (すべての行という意味)とし,列の指定を 2 とする.

df.iloc[:, 2]
0     312
1     348
2     284
3     369
4     420
5     536
6     652
7     275
8     294
9     368
10    451
11    405
12    458
13    422
Name: 客数, dtype: int64

これらを組み合わせ,例えば2列目の3行目から6行目を抽出したい場合は,以下のように書けばよい,

df.iloc[3:6, 2]
3    369
4    420
5    536
Name: 客数, dtype: int64

条件による抽出#

True または False を返す条件式を指定することで,データフレームから条件を満たす任意の行を抽出することができる.

例えば,データフレーム df から,「最高気温が30度より大きい」の行だけ抜き出したいとしよう. これを表す条件式は, df['最高気温'] > 30 となる. 実際に実行してみよう.

df['最高気温'] > 30
0     False
1     False
2     False
3      True
4      True
5      True
6      True
7     False
8     False
9      True
10     True
11     True
12    False
13    False
Name: 最高気温, dtype: bool

縦に TrueFalse がズラッと並んでいるが,これはデータフレーム df のうち,どの行が条件式 df['最高気温'] > 30 を満たすかを表している. 上記の出力で条件式が満たされる(つまり True になっている)のは,行インデックスが「3, 4, 5, 6, 9, 10, 11」の場合であることがわかる.

実際に上の方で出力した df の中身と照らし合わせてみよう. True となっている行における最高気温が30度よりも大きくなっているはずだ.

最終的には, DataFrame[条件式] のように書くことで,条件式を満たす行のみを取り出すことができる.

df[df['最高気温'] > 30]
日付 最高気温 客数
3 4 32 369
4 5 33 420
5 6 32 536
6 7 34 652
9 10 32 368
10 11 34 451
11 12 32 405

同様に,「客数が400より少ない」行だけを抜き出してみよう.

df[df['客数'] < 400]
日付 最高気温 客数
0 1 29 312
1 2 30 348
2 3 29 284
3 4 32 369
7 8 27 275
8 9 28 294
9 10 32 368

このうち,さらに「客数が400より少ない」行について,「最高気温」だけを抜き出したい場合は,DataFrame[条件式]['列名'] のように,列名を指定する [ ] を後ろにくっ付ければよい.

df[df['客数'] < 400]['最高気温']
0    29
1    30
2    29
3    32
7    27
8    28
9    32
Name: 最高気温, dtype: int64

複数条件による抽出#

単一の条件ではなく,複数の条件を使って行を抽出することもできる.

そのためには,条件分岐の回で学んだ論理演算子の考え方で各条件を繋げればよい.

ただし,条件分岐の際とは以下の2点が異なる.

  • 条件分岐のときに用いた andor は使えない.DataFrameに対して複数の条件を設定する際は,and の代わりに &or の代わりに | を使う必要がある.

  • 各条件は括弧 () で囲む必要がある.

例えば,「客数が400より少ない」かつ「最高気温が30度以上」の行だけを抽出したい場合は,以下のように書く.

df[(df['客数'] < 400) & (df['最高気温'] >= 30)]
日付 最高気温 客数
1 2 30 348
3 4 32 369
9 10 32 368

同様に,「最高気温が30度未満」または「最高気温が33度以上」の行だけを抽出したい場合は,以下のように書く.

df[(df['最高気温'] < 30) | (df['最高気温'] >=33)]
日付 最高気温 客数
0 1 29 312
2 3 29 284
4 5 33 420
6 7 34 652
7 8 27 275
8 9 28 294
10 11 34 451
13 14 28 422

要約統計量の算出#

データが与えられた時,生の数字だけを見てもなかなか情報は得られない. 多くの場合,データの特徴を要約した数値を計算し,全体の傾向の掴んだり,さまざまな条件で比較したりする.

このとき計算される数値のことを 要約統計量記述統計量 と呼ぶ. これには様々な種類があるが,今回は最も基本的な5つの要約統計量を計算してみよう.

各統計量は,データフレームに対して以下のように書くことで計算できる.

DataFrame.統計量()

今回はデータ中の最高気温に対して,各種統計量を計算してみる.

最小値min):データに含まれる最も小さい数.

minimum = df['最高気温'].min() # 最小値の計算
print(minimum)
27

最大値max):データに含まれる最も大きい数.

maximum = df['最高気温'].max() # 最大値の計算
print(maximum)
34

平均値mean):データの中間的な値を表す統計量.\(N\) 個のデータ \(x_1, x_2, \ldots, x_N\) に対して,以下のようにデータの合計を個数で割ることで計算される.

\[ \bar{x} = \frac{1}{N} \sum_{i=1}^N x_i \]
mean_value = df['最高気温'].mean() # 平均値の計算
print(mean_value)
30.714285714285715

分散var):データのばらつき具合を表す統計量.以下の式で計算される.

\[ s^2 = \frac{1}{N} \sum_{i=1}^N (x_i - \bar{x})^2 \]
variance = df['最高気温'].var() # 分散の計算
print(variance)
5.296703296703297

標準偏差std):分散 \(s^2\) の平方根で計算される.分散は各データを2乗した値から求めているため,平均と比較することができない.分散の平方根を計算することで,データのばらつきを平均を比較できるようになる.

\[ s = \sqrt{s^2} \]
stdev = df['最高気温'].std() # 標準偏差の計算
print(stdev)
2.3014567770660603

以上をまとめると,14日間における最高気温は27度から34度の範囲で変化し,平均値は約30.7度,標準偏差は約2.3度なので,値のばらつきはそこまで大きくないことがわかった.

なお,上記の例では一つ一つ記述統計量を算出したが,describe() メソッドを用いることで,DataFrameの各列に対して複数の要約統計量を一気に計算することもできる.

df.describe()
日付 最高気温 客数
count 14.0000 14.000000 14.000000
mean 7.5000 30.714286 399.571429
std 4.1833 2.301457 104.374116
min 1.0000 27.000000 275.000000
25% 4.2500 29.000000 321.000000
50% 7.5000 31.000000 387.000000
75% 10.7500 32.000000 443.750000
max 14.0000 34.000000 652.000000

Pandasによるデータ分析のまとめ

  • Pandasというライブラリを用いることで,データの収集や整理,加工が容易となる

  • PandasではDataFrameやSeriesといったデータ構造が利用される

  • DataFrameの列や行に対してに対して条件式などを用いた用いた抽出が可能である

Matplotlib によるデータの可視化#

データ分析において,集計した結果を図やグラフなどを用いて視覚的に表示してみることは非常に重要である. そのデータから読み取ることができる情報と,それに基づく意思決定にまで影響を及ぼす. このような操作を,データの 可視化(visualization) と呼ぶ.

ここでは,グラフ描画用ライブラリである Matplotlib のモジュールを使用して,データを可視化するためのグラフを作成していく.

Matplotlibライブラリを使用するためには,まず matplotlib のモジュールをインポートする. ここでは,基本的なグラフを描画するための matplotlib.pyplot というモジュールを使用する. 慣例として,同モジュールに plt という短縮名を付けて利用しよう.

import matplotlib.pyplot as plt
%matplotlib inline

なお,%matplotlib inline は,ノートブック内でグラフを表示するために必要なコードである.

なお,以下はMatplotlibで日本語フォントを扱うための設定用コードなので,実行だけして中身は無視して構わない.

from matplotlib import rcParams
rcParams['font.family'] = 'sans-serif'
rcParams['font.sans-serif'] = ['Hiragino Sans', 'Yu Gothic', 'Meiryo', 'Takao', 'IPAexGothic', 'IPAPGothic', 'VL PGothic', 'Noto Sans CJK JP']

線グラフ#

pyplotモジュールに含まれている plot 関数を用いて,線グラフを描画していこう.

plt.plot(X, Y)

と書くことで,x軸に変数 X,y軸に変数 Y を取った線グラフを表示することができる. このとき,XY にはリストやPandasのSeries,DataFrameなど様々指定できる.

まずは,「日付」によって「客数」がどう変わるかをグラフ化してみよう. x軸に日付,y軸に客数を指定してグラフを描画すればよい.

plt.plot(df['日付'], df['客数']);
../../_images/31e30cb10d9e8a6b31c894b7729595a3daeb9822c9122d61252fca4153abd029.png

なお,セルの最後に実行されたオブジェクトの出力表示を無くすため,これ以降グラフを描画する際は,セルの最後の行にセミコロン(;)をつけていることに注意されたい(セミコロンが無くてもグラフ自体は問題なく描画できる).

plot 関数ではグラフの線の種類や色,データポイントのマーカの種類を,様々な引数で指定できる.

例えば,先ほど作成した線グラフにデータポイントの丸型マーカを加えたい場合,plot 関数の引数に marker='o' を追加する.

plt.plot(df['日付'], df['客数'], marker='o');
../../_images/f108e7889e0361ff0f4f9e4b0b46d1e4d00fcf90c048324731ed5b67041169ae.png

引数 marker に違う値を指定するとマーカの形状が変わる.

plt.plot(df['日付'], df['客数'], marker='x');
../../_images/8967164ed40b5fc32e6594a9ed8fff3c3e27a24f86ff39ca72fc935573abdc85.png

他にも,linestyle で線の種類を変えたり,color で線の色を変更したりできる.

plt.plot(df['日付'], df['客数'], marker='*', linestyle='--', color='green');
../../_images/82e918b880ea6fe515ad35cba6290e01a27f86632391078563e340fd358fa192.png

それぞれの引数で指定可能な値は以下を参照されたい.

pyplot モジュールでは,グラフのタイトルと各軸のラベルを指定して表示することができる. タイトルは title 関数,x軸のラベルは xlabel 関数,y軸のラベルは ylabel 関数に文字列を渡すことで指定可能である.

plt.plot(df['日付'], df['客数'], marker='o') # グラフを作成
plt.title('日付と客数の関係') # タイトルを追加
plt.xlabel('日付') # x軸のラベルを追加
plt.ylabel('客数 (人)'); # y軸のラベルを追加
../../_images/6c9505e12261728d5325e4d4c8760bec81154e8d30b4b7e0d928a3c15a01d141.png

同様に,「日付」によって「最高気温」がどう変わるかを線グラフで描画してみよう.

plt.plot(df['日付'], df['最高気温'], marker='o') # グラフを作成
plt.title('日付と最高気温の関係') # タイトルを追加
plt.xlabel('日付') # x軸のラベルを追加
plt.ylabel('最高気温(度)'); # y軸のラベルを追加
../../_images/5b98f6009d7bd5fe09212b432a9c814104200e0f808c9624d48e2d873e9dda9c.png

さて,では「日付と客数」のグラフと,「日付と最高気温」のグラフを交互に見てみましょう. 何か気がつくことがないだろうか?

最高気温が高い日には客数も多く,最高気温が低い日には客数が少ない傾向があるように見える.

2つの変数の関係を可視化するために,次に 散布図 というグラフで最高気温と客数の関係を見ていこう.

散布図#

散布図とは,横軸と縦軸に異なる要素を設定し,それらの関係性を調べる際に用いられる.

Matplotlibでは,pyplot モジュールの scatter 関数を用いて描画することができる. 次のように,

plt.scatter(X, Y)

と書くことで,x軸に変数 X,y軸に変数 Y を取った散布図を作成できる. このとき,線グラフの場合と同様に,さまざまな引数を指定することでグラフの見た目を変更できる. ここでは例として,引数 salpha を用いて,各マーカの大きさと透明度を指定している.

plt.scatter(df['最高気温'], df['客数'], s=100, alpha=0.5)
plt.title('最高気温と客数の関係')
plt.xlabel('最高気温(度)')
plt.ylabel('客数(人)');
../../_images/9249d59f6731ee02d954f59eb9c92b16b8686d26a85294ecb0fa293f2a95c634.png

このグラフをみると,最高気温と客数の間には,右肩上がりの関係があるように見える. つまり,最高気温が高くなればなるほど,客数は多くなるような関係がありそうだ.

この関係については,後ほど詳しく考えてみるとして,その前に他のグラフをいくつか紹介する.

棒グラフ#

棒グラフは日常でもよく目にするグラフの一つであり,横軸に項目,縦軸にデータ値をとり,棒の高さでデータの大きさを表している.

Matplotlibでは,pyplot モジュールの bar 関数を用いて棒グラフを描画できる.

plt.bar(X, Y)

先ほどは線グラフで「日付」ごとの「客数」を可視化したが,同じものを棒グラフで表現してみよう.

plt.bar(df['日付'], df['客数'])
plt.title('日付と客数の関係') # タイトルを追加
plt.xlabel('日付') # x軸のラベルを追加
plt.ylabel('客数 (人)'); # y軸のラベルを追加
../../_images/d1a10994150690f50c731d623020f51ec8259d7a33dbc69a000aa7d124539965.png

線グラフと棒グラフの使い分け

上記の通り,今回は「日付」と「客数」の関係を,線グラフと棒グラフという2種類の方法で可視化した. グラフより,どちらも同じような情報を表しているが,どんな時に使い分ければよいだろうか?

線グラフの場合は,データ点同士が線で結ばれているため,より日付に応じた変化が強調されているような印象である. 一方,棒グラフの場合は,ある日付と別の日付との間の値の差がより分かりやすい印象である. つまり,線グラフは「変化」が強調されており,棒グラフは「大小の違い」が強調されているわけである.

このことから,横軸の値が連続したデータ(例:年度ごとの売り上げの推移)に関しては,線グラフの方が分かりやすい場面が多い. 一方,横軸の値が独立しているデータ(例:広島,岡山,島根の3地域の売り上げ)については,棒グラフの方が適切だろう.

今回の「日付」と「客数」の場合,横軸は連続した「日付」なので,線グラフの方が適切と思われる.

ただし,「絶対にどちらかを使うべし」という厳密なルールは存在しない. むしろ,そのグラフから自分がどのような主張を行いたいか,に応じて適切なグラフを使い分けることが大切である.

【発展】ヒストグラム#

ヒストグラムは,データを階級で分割し,横軸に階級,縦軸に各階級の度数をとった統計グラフである. 簡単にいうと,データをいくつかの均等な区間に分割するとともに,各区間に含まれるデータの個数を計算し,可視化したものである. 主に,データの分布を調べるために用いられる.

Matplotlibでは,pyplot モジュールの hist 関数を用いてヒストグラムを描画できる.

plt.hist(data)

本来,ヒストグラムはデータ量がある程度多くないと描画が難しいが,今回はひとまず「客数」のデータのヒストグラムを表示してみよう.

plt.hist(df['客数'])
plt.title('客数のヒストグラム')
plt.xlabel('客数')
plt.ylabel('度数');
../../_images/3ef27a1f67a3bc681d80f76922dc092bf8d6790f68267195c0e222cf99b4d8e4.png

このグラフより,客数は300人から450人くらいの範囲に主に分布しており,客数が500人を超えることは稀であることがわかる.

なお,hist 関数ではデフォルトの分割数として 10 が設定されているが,ここを手動で設定したい場合は引数 bins に分割数を指定すれば良い. 例えば分割数により大きな 15 を設定すると,ヒストグラムの各棒が細くなり,分布の表現がより細かくなる.

plt.hist(df['客数'], bins=15)
plt.title('客数のヒストグラム')
plt.xlabel('客数')
plt.ylabel('度数');
../../_images/267abc8658cd8ddc0b91e131cf9acb818e35b8d31c42de88179ecf917662234c.png

複数のグラフの重ね合わせ#

Matplotlibでは,グラフ作成用関数を繰り返し実行することで,各グラフを重ねて表示することが可能である.

例として,df のデータを第1週目(日付1〜7)と第2週目(日付8〜14)に分け,それぞれ対応する曜日と「客数」の線グラフを重ねて描画してみる.

ここで,日付は月曜始まりとして扱う.つまり,日付1と8は月曜,日付2と9は火曜, …, 日付7と14は日曜となる.

df_1st_week = df[df['日付'] <= 7] # 第1週目のDataFrameを抽出
df_2nd_week = df[df['日付'] > 7] # 第2週目のDataFrameを抽出
days = ['月', '火', '水', '木', '金', '土', '日'] # 横軸となる曜日のリストを作っておく

plt.plot(days, df_1st_week['客数']) # 第1週目の線グラフ作成
plt.plot(days, df_2nd_week['客数']) # 第2週目の線グラフ作成
plt.title('曜日ごとの客数')
plt.xlabel('曜日')
plt.ylabel('客数(人)');
../../_images/7f17a40d617f5ec6342787a6110d84909e3f7b55aebb44659713ee9e98f9e6da.png

上記からわかるように,線グラフを重ねたい場合は同じセル内で単純に plt.plot を複数実行すればよいことがわかる. また,各グラフは自動で色分けされている(もちろん,引数で任意の色を指定することもできる).

ただし,上記のグラフだけだと青色と橙色のどちらが1週目と2週目なのか判断がつかない.そのような場合は凡例を表示する必要がある.

plot 関数の label 引数にグラフの各線の凡例として表示したい文字列を指定した上で,legend 関数を呼び出すことで,グラフ中に凡例を表示することができる.

plt.plot(days, df_1st_week['客数'], label='第1週') # 第1週目の線グラフ作成
plt.plot(days, df_2nd_week['客数'], label='第2週') # 第2週目の線グラフ作成
plt.title('曜日ごとの客数')
plt.xlabel('曜日')
plt.ylabel('客数(人)')
plt.legend();
../../_images/a9d520cc188d3efd64467e6e62dd71108fdbf81d23b303b84c88e65386acd24a.png

グラフから読み取れる相関関係#

さて,話を少し戻そう. 散布図の説明をした際に,「最高気温」と「客数」の間に,右肩上がりの関係が確認された.

このように,「x軸の値が大きくなるほど,y軸の値も大きくなる」ような関係性を,正の相関 という. 相関とは,「互いに関係がある」という意味である.

逆に,「x軸の値が大きくなるほど,y軸の値は小さくなる」ことを 負の相関 という. この場合,散布図は右肩下がりになるはずである.

「x軸の値とy軸の値の間に明確な関係がない」場合,無相関 と呼ばれる. この場合,散布図に傾きはみられない.

このような2つの値の相関関係の強さを表す指標として, 相関係数 というものがある. 相関係数は-1から1の値をとり,絶対値が大きいほど関係性が強いことを意味する. 相関係数にはいくつかの定義がありますが,最も代表的なピアソンの積率相関係数 \(r\) は以下の式で計算される.

\[ r = \frac{\sum_{i=1}^N (x_i - \bar{x}) (y_i - \bar{y})}{\sqrt{\sum_{i=1}^N(x_i - \bar{x})^2 \sum_{i=1}^N(y_i - \bar{y})^2}} \]

相関係数の目安

相関係数の値と相関の強さに関して,よく使われる目安は以下の通りである.

相関係数の絶対値

目安

0.7〜1.0

強い相関

0.4〜0.7

中程度の相関

0.2〜0.4

弱い相関

0.0〜0.2

ほとんど相関がない

ただしこれは絶対的な基準ではないことに注意されたい.

相関係数も要約統計量と同様に,Pandasの機能を用いて計算することができる.

相関係数を計算したい列を含むデータフレームに対し,

DataFrame.corr()

とすれば計算結果が返ってくる. 「最高気温」と「客数」の間の相関係数を計算してみよう.

df[['最高気温', '客数']].corr()
最高気温 客数
最高気温 1.000000 0.697871
客数 0.697871 1.000000

ここで返されるのは相関行列というものである.この行列の対角成分は同じ変数間の相関係数なので,「1」となる. 「最高気温」と「客数」の間の相関係数を確認するためには,非対角成分の部分に着目すれば良い.

これより,「最高気温」と「客数」の間の相関係数は約0.698であることがわかった. さきほどの目安と照らし合わせると,中程度〜強い正の相関関係があるといえる.

注意

上記の相関行列において,非対角成分は2つあるが,どちらを見ればよいだろうか. 正解は,「どちらでも良い」である.

「最高気温」と「客数」の間の相関も,その逆である「客数」と「最高気温」の間の相関も,同じ意味を持つ. これは,上述の相関係数において \(x\)\(y\) を入れ替えても本質的な計算は変わらないことからも明らかである. つまり,相関行列は相関行列は対称行列の一種である.

このように,データから平均値など要約統計量を計算したり,散布図などを描画して可視化してみたり,相関係数を計算したりすることで,データの中の性質や関係性を理解したり分析したりすることができる. 今回紹介したPandasやMatplotlibのように,Pythonにはこのようなデータ分析を実現するための様々なモジュール・ライブラリが揃っているため,それらを駆使することで幅広い応用に展開可能である.

Matplotlibによるデータの可視化のまとめ

  • データを様々な方法で視覚的わかりやすく提示することを可視化と呼ぶ

  • Matplotlibは,Pythonでよく用いられるグラフ描画ライブラリである

  • 線グラフや棒グラフ,ヒストグラムなど,さまざまなグラフを描画できる