データ分析入門(講義)#

担当:古居彬

内容#

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

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

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

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

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

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

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

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

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

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

Pythonの標準ライブラリ#

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

組み込み関数は何もせずそのまま使うことができたが,特殊な機能を持つモジュールは後述する 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
… (以下省略) 

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

💡 【発展】外部ライブラリをインストールする方法

Pythonで利用可能な外部ライブラリは,Python Package Index(PyPl) というソフトウェアリポジトリで管理されている. ざっくり表現すると,外部ライブラリのカタログのようなものである.

以下のサイトより,インストール可能な外部ライブラリを検索することができる.

https://pypi.org

pip などのパッケージ管理システムを利用することで,PyPlに登録されているライブラリやパッケージをダウンロード&インストールすることができる. Google Colaboratoryにおける外部ライブラリも,このpipというシステムによって管理されている.

Google Colaboratoryで新しい外部ライブラリをインストールしたい場合,コードセルにて次のコマンドを実行すれば良い.

!pip install ライブラリ名

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

前述の通り,モジュールやライブラリに含まれる関数やクラスを使用するためには,対象となるモジュール/ライブラリを読み込む( インポート する)必要がある. これには, 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

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

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

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

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

  • モジュールやライブラリは 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 には,リスト型や辞書型の変数を指定することができる(復習 → 変数とデータ型).

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

データの読み込み#

今回は,あるコンビニエンスストアにおけるコーヒー販売データを用いてデータ分析に取り組んでいく. このデータは教育用に作成した架空のデータである.

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

!mkdir -p data
!wget -P data https://raw.githubusercontent.com/python-hu/resource/refs/heads/main/zero_to_programming/coffee_sales.csv -O data/coffee_sales.csv
--2025-12-11 15:27:45--  https://raw.githubusercontent.com/python-hu/resource/refs/heads/main/zero_to_programming/coffee_sales.csv
raw.githubusercontent.com (raw.githubusercontent.com) をDNSに問いあわせています... 185.199.109.133, 185.199.108.133, 185.199.111.133, ...
raw.githubusercontent.com (raw.githubusercontent.com)|185.199.109.133|:443 に接続しています... 接続しました。
HTTP による接続要求を送信しました、応答を待っています... 
200 OK
長さ: 274 [text/plain]
`data/coffee_sales.csv' に保存中


coffee_sales.csv      0%[                    ]       0  --.-KB/s               
coffee_sales.csv    100%[===================>]     274  --.-KB/s 時間 0s       

2025-12-11 15:27:45 (7.06 MB/s) - `data/coffee_sales.csv' へ保存完了 [274/274]

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

さて, coffee_sales.csvには,あるコンビニエンスストアにおける8月のコーヒー販売データ(2週間 = 14日分)が格納されている. このファイルは CSVファイル と呼ばれ,以下のように各データの間がコンマ , で区切られている.

日付, 最高気温, アイスコーヒー, ホットコーヒー
1, 29.2, 312, 168
2, 30.1, 348, 152
3, 28.9, 284, 196
...

1行目は列のタイトル(ヘッダとも呼ばれる)となっており,2行目から各日付におけるデータが格納されている. 暑い日にはアイスコーヒー,涼しい日にはホットコーヒーの需要が変わるかもしれない. そこで,このファイルには各日付におけるコーヒー販売数以外に,その日の最高気温も含まれている.

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

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

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

# dfの中身を表示
df
日付 最高気温 アイスコーヒー ホットコーヒー
0 1 29.2 289 187
1 2 30.1 358 142
2 3 28.9 275 213
3 4 31.5 392 108
4 5 32.3 425 95
5 6 33.1 398 87
6 7 31.8 412 125
7 8 28.5 298 198
8 9 30.8 345 165
9 10 32.7 408 102
10 11 33.5 465 78
11 12 34.2 489 45
12 13 29.5 302 172
13 14 30.2 368 158

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

💡 列名(ヘッダ)の認識

read_csv 関数では,デフォルトでCSVファイル中の1行目の情報を列名(ヘッダ)として解釈してくれる. 先ほど読み込んだ coffee_sales.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.2
1     30.1
2     28.9
3     31.5
4     32.3
5     33.1
6     31.8
7     28.5
8     30.8
9     32.7
10    33.5
11    34.2
12    29.5
13    30.2
Name: 最高気温, dtype: float64

同様にして,「アイスコーヒー」の列を取り出してみよう.

df['アイスコーヒー']
0     289
1     358
2     275
3     392
4     425
5     398
6     412
7     298
8     345
9     408
10    465
11    489
12    302
13    368
Name: アイスコーヒー, dtype: int64

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

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

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

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

df[['最高気温', 'アイスコーヒー']]
最高気温 アイスコーヒー
0 29.2 289
1 30.1 358
2 28.9 275
3 31.5 392
4 32.3 425
5 33.1 398
6 31.8 412
7 28.5 298
8 30.8 345
9 32.7 408
10 33.5 465
11 34.2 489
12 29.5 302
13 30.2 368

行の抽出#

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

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

df[2:5]
日付 最高気温 アイスコーヒー ホットコーヒー
2 3 28.9 275 213
3 4 31.5 392 108
4 5 32.3 425 95

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

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

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

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

df.iloc[1]
日付           2.0
最高気温        30.1
アイスコーヒー    358.0
ホットコーヒー    142.0
Name: 1, dtype: float64

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

df.iloc[3:6]
日付 最高気温 アイスコーヒー ホットコーヒー
3 4 31.5 392 108
4 5 32.3 425 95
5 6 33.1 398 87

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

df.iloc[:, 2]
0     289
1     358
2     275
3     392
4     425
5     398
6     412
7     298
8     345
9     408
10    465
11    489
12    302
13    368
Name: アイスコーヒー, dtype: int64

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

df.iloc[3:6, 2]
3    392
4    425
5    398
Name: アイスコーヒー, dtype: int64

条件による抽出#

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

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

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

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

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

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

df[df['最高気温'] > 30]
日付 最高気温 アイスコーヒー ホットコーヒー
1 2 30.1 358 142
3 4 31.5 392 108
4 5 32.3 425 95
5 6 33.1 398 87
6 7 31.8 412 125
8 9 30.8 345 165
9 10 32.7 408 102
10 11 33.5 465 78
11 12 34.2 489 45
13 14 30.2 368 158

同様に,「アイスコーヒーが400個より多く売れた」行だけを抜き出してみよう.

df[df['アイスコーヒー'] > 400]
日付 最高気温 アイスコーヒー ホットコーヒー
4 5 32.3 425 95
6 7 31.8 412 125
9 10 32.7 408 102
10 11 33.5 465 78
11 12 34.2 489 45

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

df[df['アイスコーヒー'] > 400]['最高気温']
4     32.3
6     31.8
9     32.7
10    33.5
11    34.2
Name: 最高気温, dtype: float64

複数条件による抽出#

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

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

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

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

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

例えば,「アイスコーヒーが400個より多く売れた」かつ「最高気温が32度以上」の行だけを抽出したい場合は,以下のように書く.

df[(df['アイスコーヒー'] > 400) & (df['最高気温'] >= 32)]
日付 最高気温 アイスコーヒー ホットコーヒー
4 5 32.3 425 95
9 10 32.7 408 102
10 11 33.5 465 78
11 12 34.2 489 45

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

df[(df['最高気温'] < 30) | (df['最高気温'] >=33)]
日付 最高気温 アイスコーヒー ホットコーヒー
0 1 29.2 289 187
2 3 28.9 275 213
5 6 33.1 398 87
7 8 28.5 298 198
10 11 33.5 465 78
11 12 34.2 489 45
12 13 29.5 302 172

要約統計量の算出#

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

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

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

DataFrame.統計量()

今回はデータ中の「アイスコーヒー」に対して,各種統計量を計算してみる.

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

df['アイスコーヒー'].min() # 最小値の計算
np.int64(275)

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

df['アイスコーヒー'].max() # 最大値の計算
np.int64(489)

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

\[ \bar{x} = \frac{1}{N} \sum_{i=1}^N x_i \]
df['アイスコーヒー'].mean() # 平均値の計算
np.float64(373.14285714285717)

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

\[ s^2 = \frac{1}{N} \sum_{i=1}^N (x_i - \bar{x})^2 \]
df['アイスコーヒー'].var() # 分散の計算
np.float64(4362.747252747252)

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

\[ s = \sqrt{s^2} \]
df['アイスコーヒー'].std() # 標準偏差の計算
np.float64(66.05109577249458)

以上をまとめると,14日間におけるアイスコーヒーの販売数は265個から468個の範囲で変化し,平均値は約366個,標準偏差は約60個なので,日によってかなりばらつきがあることがわかった.

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

df.describe()
日付 最高気温 アイスコーヒー ホットコーヒー
count 14.0000 14.000000 14.000000 14.000000
mean 7.5000 31.164286 373.142857 133.928571
std 4.1833 1.832026 66.051096 50.198781
min 1.0000 28.500000 275.000000 45.000000
25% 4.2500 29.650000 312.750000 96.750000
50% 7.5000 31.150000 380.000000 133.500000
75% 10.7500 32.600000 411.000000 170.250000
max 14.0000 34.200000 489.000000 213.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']

💡 【補足】上記コードが気になる人向けの解説

上記のコードについて簡単に解説する(気にならない人はスルーしてよい).

まず一行目では,Matplotlibに含まれる rcParams という辞書形式のオブジェクトをインポートしている. ここにはさまざまなデフォルト設定値が格納されており,この設定値を変更することで,プログラム全体のグラフに対して見た目などの変更が可能である.

rcParams['font.family'] を変更することで,グラフ中のフォントを任意に設定できる. ただし,使用しているPC環境(OSやブラウザ)によっては特定のフォントが使えたり使えなかったりするため,フォントの大きなカテゴリ(総称フォントと呼ばれる)である sans-serif を設定している.

rcParams['font.family'] = 'sans-serif'

sans-serif とは日本語フォントでいうところのゴシック体である.

また,rcParams['font.sans-serif'] を変更することで,sans-serif として利用可能なフォントの選択肢を具体的に指定している.

rcParams['font.sans-serif'] = ['Hiragino Sans', 'Yu Gothic', 'Meirio', 'Takao', 'IPAexGothic', 'IPAPGothic', 'VL PGothic', 'Noto Sans CJK JP']

このうち,使用しているPC環境が利用可能なものがグラフ中のフォントとして使用される. 例えば,Macであれば必ず Hiragino Sans が利用可能であり,Windowsであれば Yu GothicMeirio が,Ubuntuであれば Noto Sans CJK JP が使えるはずである. そのため,一般的なPC環境であれば,OSやブラウザを問わず正しく日本語フォントでグラフを出力できるようになっている.

線グラフ#

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

plt.plot(X, Y)

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

まずは,「日付」によって「アイスコーヒー」の販売数がどう変わるかをグラフ化してみよう. x軸に日付,y軸にアイスコーヒーの販売数を指定してグラフを描画すればよい.

plt.plot(df['日付'], df['アイスコーヒー']);
../../_images/2bc102bbb24a40b5338f3773c69d00f0e8c54ac55fef4a479c497697c438efea.png

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

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

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

plt.plot(df['日付'], df['アイスコーヒー'], marker='o');
../../_images/c1b65a1b07540465b1a403c2893c330958376f4b8c0d312ff7ac497eb2961684.png

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

plt.plot(df['日付'], df['アイスコーヒー'], marker='x');
../../_images/61fa568a8220901476b95678a8fae04d066e61685348941a5e1168d7b22f5e79.png

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

plt.plot(df['日付'], df['アイスコーヒー'], marker='*', linestyle='--', color='green');
../../_images/5ad4704df062779c0f9be65eed2a7f0e5a40caaf2bfd0fb477b62cca6075f673.png

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

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

plt.plot(df['日付'], df['アイスコーヒー'], marker='o') # グラフを作成
plt.title('日付とアイスコーヒー販売数の関係') # タイトルを追加
plt.xlabel('日付') # x軸のラベルを追加
plt.ylabel('販売数 (個)'); # y軸のラベルを追加
../../_images/fd718858c52ff6e0d5bbd51a7153c060fe852936640f84fb67b9d6fed2117930.png

❗ タイトルなどが文字化けする場合

PC環境によっては,日本語フォント表示用コードを実行してもグラフ中に日本語がうまく表示されず,文字が のように表示されることがある.

その場合は,以下のコードをコードセルに貼り付け,実行してからグラフを描画せよ.

!pip install japanize-matplotlib
import japanize_matplotlib
japanize_matplotlib.japanize()

同様に,「日付」によって「ホットコーヒー」の販売数がどう変わるかを線グラフで描画してみよう.

plt.plot(df['日付'], df['ホットコーヒー'], marker='o', color='red') # グラフを作成
plt.title('日付とホットコーヒー販売数の関係') # タイトルを追加
plt.xlabel('日付') # x軸のラベルを追加
plt.ylabel('販売数(個)'); # y軸のラベルを追加
../../_images/fa344fc2f00fc3774ed4821541a65b207018a98c7642b35c06c77598b555e0fe.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/52bcc00e630d1dddbad14ba7a5fe148aca309778e7092d08132f925adf812d6d.png

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

では,ホットコーヒーについても同様に散布図を描いてみよう.

plt.scatter(df['最高気温'], df['ホットコーヒー'], s=100, alpha=0.5, color='red')
plt.title('最高気温とホットコーヒー販売数の関係')
plt.xlabel('最高気温(度)')
plt.ylabel('販売数(個)');
../../_images/b9d7534d374d7d1d854aa665978a8235256e58908e6fbf3284ffbe20acf93c0e.png

こちらは逆に,最高気温が高くなるほどホットコーヒーの販売数が少なくなる,右肩下がりの関係が見られる.

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

棒グラフ#

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

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

plt.bar(X, Y)

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

plt.bar(df['日付'], df['アイスコーヒー'])
plt.title('日付とアイスコーヒー販売数の関係') # タイトルを追加
plt.xlabel('日付') # x軸のラベルを追加
plt.ylabel('販売数 (個)'); # y軸のラベルを追加
../../_images/84683bdf50f75de81de2ca19188c7b9198f6362e42f74c109dfe55bc2b20de6b.png

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

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

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

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

今回の「日付」と「アイスコーヒー」販売数の場合,横軸は連続した「日付」なので,線グラフの方が適切と思われる.

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

【発展】ヒストグラム#

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

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

plt.hist(data)

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

plt.hist(df['アイスコーヒー'])
plt.title('アイスコーヒー販売数のヒストグラム')
plt.xlabel('販売数')
plt.ylabel('度数');
../../_images/4b51bd939d1ab03d7d4a1bd74c10724f522fa85d4040b704726c62e8cb1ee5c7.png

このグラフより,アイスコーヒーの販売数は250個から470個くらいの範囲に主に分布していることがわかる.

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

plt.hist(df['アイスコーヒー'], bins=20)
plt.title('アイスコーヒー販売数のヒストグラム')
plt.xlabel('販売数')
plt.ylabel('度数');
../../_images/9f4c007dac61c1fdc59eee0f16f5a22c751343d1e95ce6bbd229d02a14794e8d.png

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

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

例として,アイスコーヒーとホットコーヒーの販売数を同じグラフに重ねて描画してみる.

plt.plot(df['日付'], df['アイスコーヒー'], color='blue')
plt.plot(df['日付'], df['ホットコーヒー'], color='red')
plt.title('コーヒー販売数の推移')
plt.xlabel('日付')
plt.ylabel('販売数(個)')
plt.legend();
/var/folders/5c/dms_mf5s7fx_3cvf44lczp7w0000gn/T/ipykernel_98328/2774301907.py:6: UserWarning: No artists with labels found to put in legend.  Note that artists whose label start with an underscore are ignored when legend() is called with no argument.
  plt.legend();
../../_images/5ac2807f1b1fae4d8c2e87ba9ee3e7e5a2d0d8542c8c63b48c6a53682dc971ec.png

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

ただし,上記のグラフだけだと青色と橙色のどちらがアイスコーヒーとホットコーヒーなのか判断がつかない.そのような場合は凡例を表示する必要がある.

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

plt.plot(df['日付'], df['アイスコーヒー'],  color='blue', label='アイスコーヒー')
plt.plot(df['日付'], df['ホットコーヒー'], color='red', label='ホットコーヒー')
plt.title('コーヒー販売数の推移')
plt.xlabel('日付')
plt.ylabel('販売数(個)')
plt.legend();
../../_images/ed59adfb9e808d0e98fa2f52a4054a7ef0c55f2228aa9dee59a7aac36ecb829e.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.950528
アイスコーヒー 0.950528 1.000000

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

これより,「最高気温」と「アイスコーヒー」の間の相関係数は約0.98であることがわかった. これは非常に強い正の相関関係を示している.

❗ 注意

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

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

次に,「最高気温」と「ホットコーヒー」の間の相関係数を計算してみよう.

df[['最高気温', 'ホットコーヒー']].corr()
最高気温 ホットコーヒー
最高気温 1.000000 -0.969876
ホットコーヒー -0.969876 1.000000

最高気温」と「ホットコーヒー」の間の相関係数は約-0.98となった. 負の値であることから負の相関を示しており,その絶対値が1に近いことから非常に強い負の相関関係があることがわかる.

最後に,全ての変数間の相関関係を一度に確認してみよう.

df[['最高気温', 'アイスコーヒー', 'ホットコーヒー']].corr()
最高気温 アイスコーヒー ホットコーヒー
最高気温 1.000000 0.950528 -0.969876
アイスコーヒー 0.950528 1.000000 -0.961487
ホットコーヒー -0.969876 -0.961487 1.000000

この相関行列から,以下のことがわかる:

  • 最高気温とアイスコーヒー:強い正の相関(0.98)

  • 最高気温とホットコーヒー:強い負の相関(-0.98)

  • アイスコーヒーとホットコーヒー:強い負の相関(-0.97)

今回の分析から,コンビニでのコーヒー販売において,気温が消費者の商品選択に大きな影響を与えていることが明らかになった.このような知見は,在庫管理や販売戦略の立案に活用できるだろう.

このように,データから平均値など要約統計量を計算したり,散布図などを描画して可視化してみたり,相関係数を計算したりすることで,データの中の性質や関係性を理解したり分析したりすることができる.

今回紹介したPandasやMatplotlibのように,Pythonにはこのようなデータ分析を実現するための様々なモジュール・ライブラリが揃っているため,それらを駆使することで幅広い応用に展開可能である.

💡 【補足】相関関係と因果関係

相関係数は,あくまで2つの変数の間の関係性の強さを示す指標に過ぎない. 今回の分析で「最高気温」と「アイスコーヒー」の間に強い正の相関が,「最高気温」と「ホットコーヒー」の間に強い負の相関があることがわかった. これは,「最高気温が高いとアイスコーヒーが多く売れ,ホットコーヒーの売り上げは少なくなる」という傾向を示している(ある意味当然であるが).

しかし,この結果から「最高気温が高いことが 原因 で,結果 として「アイスコーヒーが多く売れ,ホットコーヒーは少なくなる」と明確に結論づけることはできない.実際には,店舗側の販売戦略や消費者の嗜好など,様々な要因が影響している可能性がある.

因果関係があるかどうかを示すためには,より複雑な別の分析を行う必要がある.

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

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

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

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