5. データの傾向をつかもう

いよいよ、データを使って統計分析する手法を学んでいきます。

今回は、Pandas と グラフ描画を組み合わせて、データの可視化による分析を学びます。

モジュールの準備

[1]:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
try:
    import japanize_matplotlib #matplotlibの日本語化
except ModuleNotFoundError:
    !pip install japanize_matplotlib
    import japanize_matplotlib
sns.set(font="IPAexGothic") #日本語フォント設定

5.1. データ入手

今回は、ポルトガルの高校生の数学の成績データを使って、データ解析をします。

1df0234e2f204f37bfc7652584c15edb

データの取り寄せ

!wget https://kuramitsulab.github.io/data/student-mat.csv

wget

URLからファイルをダウンロードするUNIXコマンドです。 Colab上にファイルをアップロードするとき、重宝します。

5.1.1. データの内容確認

student-mat.csvをデータフレームとして読み込みます。

[2]:
data = pd.read_csv('./student-mat.csv')
data.head() #最初の5行を確認
[2]:
school sex age address famsize Pstatus Medu Fedu Mjob Fjob ... famrel freetime goout Dalc Walc health absences G1 G2 G3
0 GP F 18 U GT3 A 4 4 at_home teacher ... 4 3 4 1 1 3 6 5 6 6
1 GP F 17 U GT3 T 1 1 at_home other ... 5 3 3 1 1 3 4 5 5 6
2 GP F 15 U LE3 T 1 1 at_home other ... 4 3 2 2 3 3 10 7 8 10
3 GP F 15 U GT3 T 4 2 health services ... 3 2 2 1 1 5 2 15 14 15
4 GP F 16 U GT3 T 3 3 other other ... 4 3 2 1 2 5 4 6 10 10

5 rows × 33 columns

[3]:
data.tail() # 最後の5行を確認
[3]:
school sex age address famsize Pstatus Medu Fedu Mjob Fjob ... famrel freetime goout Dalc Walc health absences G1 G2 G3
390 MS M 20 U LE3 A 2 2 services services ... 5 5 4 4 5 4 11 9 9 9
391 MS M 17 U LE3 T 3 1 services services ... 2 4 5 3 4 2 3 14 16 16
392 MS M 21 R GT3 T 1 1 other other ... 5 5 3 3 3 3 3 10 8 7
393 MS M 18 R LE3 T 3 2 services other ... 4 4 1 3 4 5 0 11 12 10
394 MS M 19 U LE3 T 1 1 other at_home ... 3 2 3 3 3 5 5 8 9 9

5 rows × 33 columns

5.1.2. データの属性を確認する

新しいデータをロードしたとき、最初に行うことはデータの属性を確認することです。

属性名

説明

school

学校名

sex

性別 F: 女性, M: 男性

age

年齢

address

居住地 U: 都市部, R: 地方

famsize

家族の人数

Pstatus

同居 T: 同居, A: 寮

Medu

母親の学歴

Fedu

父親の学歴

Mjob

母親の職業

Fjob

父親の職業

reason

学校を選んだ理由

guardian

保護者は誰か?

traveltime

通学時間

studytime

週の勉強時間

failures

過去の落第数

schoolsup

補修

famsup

家族からのサポート

paid

activities

部活動

nursery

保育園

higher

高等教育

internet

インターネット接続

romantic

恋人

famrel

家族関係

freetime

自由時間

goout

友人と遊ぶ頻度

Dalc

平日のアルコール量

Walc

週末のアルコール量

health

健康状態

absense

欠席数

G1

一学期の成績 (0〜20)

G2

二学期の成績 (0〜20)

G3

最終成績 (0〜20)

ポルトガルの高校なので、日本人の感覚からすると???なところもありますが、 上記の情報が含まれています。

ここからのミッション

高校生の各属性が数学の成績(G1, G2, G3)、特に最終成績(G3)に与える影響を調べていきましょう。 (ちなみに、G3が0.0の学生は、ドロップアウトしてしまった学生です。エラーではありません。)

5.1.3. データの形式を確認する

データの属性を確認したら、次は属性がどのような形式で入っているか確認します。

データの.info()を呼ぶと、属性とデータ形式のリストを表示してくれます。

[4]:
data.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 395 entries, 0 to 394
Data columns (total 33 columns):
 #   Column      Non-Null Count  Dtype
---  ------      --------------  -----
 0   school      395 non-null    object
 1   sex         395 non-null    object
 2   age         395 non-null    int64
 3   address     395 non-null    object
 4   famsize     395 non-null    object
 5   Pstatus     395 non-null    object
 6   Medu        395 non-null    int64
 7   Fedu        395 non-null    int64
 8   Mjob        395 non-null    object
 9   Fjob        395 non-null    object
 10  reason      395 non-null    object
 11  guardian    395 non-null    object
 12  traveltime  395 non-null    int64
 13  studytime   395 non-null    int64
 14  failures    395 non-null    int64
 15  schoolsup   395 non-null    object
 16  famsup      395 non-null    object
 17  paid        395 non-null    object
 18  activities  395 non-null    object
 19  nursery     395 non-null    object
 20  higher      395 non-null    object
 21  internet    395 non-null    object
 22  romantic    395 non-null    object
 23  famrel      395 non-null    int64
 24  freetime    395 non-null    int64
 25  goout       395 non-null    int64
 26  Dalc        395 non-null    int64
 27  Walc        395 non-null    int64
 28  health      395 non-null    int64
 29  absences    395 non-null    int64
 30  G1          395 non-null    int64
 31  G2          395 non-null    int64
 32  G3          395 non-null    int64
dtypes: int64(16), object(17)
memory usage: 102.0+ KB

データの属性名に続いて、データの個数、さらに形式がint64objectか示されています。

  • int64: 数値データ

  • object: 文字列データ

重要なのは、数値データ(量的データ)カテゴリデータ(質的データ)を区別することです。

  • 数値データ: 量が数値によって連続的に表現されるデータであり、基本的に比較可能。(例. 人数や金額など)

  • カテゴリデータ: カテゴリやグループを表す不連続のデータ

少し注意したいのは、カテゴリデータであっても、カテゴリの種類が数値で表現されていると、int64形式となります。 同じく、カテゴリデータであっても、論理値などの比較可能な数値に変換できるものがあります。

MeduFeduは、カテゴリの種類が数値で入っていますが、数値データ(int64)と解釈されていますが、カテゴリデータとして処理したいなら、型をobjectに変更しておくとよいでしょう。

[5]:
data['Medu'] = data['Medu'].astype('object')
data['Fedu'] = data['Fedu'].astype('object')

カテゴリから数値データへの変換

次のように.map()を行えば、カテゴリーデータも数値データに変換できます。

data['school']=data['school'].map({'GP':0, 'MS':1})
data['sex']=data['sex'].map({'M':0 ,'F':1})
data['address']=data['address'].map({'R':0 ,'U':1})
data['famsize']=data['famsize'].map({'LE3':0 ,'GT3':1})
data['Pstatus']=data['Pstatus'].map({'A':0 ,'T':1})
data['Mjob']=data['Mjob'].map({'at_home':0 ,'services':1, 'teacher':2, 'health':3, 'other':4})
data['Fjob']=data['Fjob'].map({'at_home':0 ,'services':1, 'teacher':2, 'health':3, 'other':4})
data['famsup']=data['famsup'].map({'no':0, 'yes':1})
data['reason']=data['reason'].map({'course':0 ,'home':1, 'reputation':2, 'other':3})
data['guardian']=data['guardian'].map({'mother':0 ,'father':1, 'other':2})
data['schoolsup']=data['schoolsup'].map({'no':0, 'yes':1})
data['paid']=data['paid'].map({'no':0, 'yes':1})
data['activities']=data['activities'].map({'no':0, 'yes':1})
data['nursery']=data['nursery'].map({'no':0, 'yes':1})
data['higher']=data['higher'].map({'no':0, 'yes':1})
data['internet']=data['internet'].map({'no':0, 'yes':1})
data['romantic']=data['romantic'].map({'no':0, 'yes':1})

5.1.4. カテゴリデータの確認

data.dtypesobjectあるものをカテゴリデータとみなして、 カテゴリデータの属性リストを取り出しています。

[6]:
# カテゴリデータのリストを取り出す
print(list(data.dtypes[data.dtypes == 'object'].index))

['school', 'sex', 'address', 'famsize', 'Pstatus', 'Medu', 'Fedu', 'Mjob', 'Fjob', 'reason', 'guardian', 'schoolsup', 'famsup', 'paid', 'activities', 'nursery', 'higher', 'internet', 'romantic']

各カテゴリデータがどのような値が含まれるか、調べます。

[7]:
# カテゴリデータの値の調べる
for cat_feat in data.dtypes[data.dtypes == 'object'].index:
    print(cat_feat)
    print(data[cat_feat].value_counts())
    print()
school
GP    349
MS     46
Name: school, dtype: int64

sex
F    208
M    187
Name: sex, dtype: int64

address
U    307
R     88
Name: address, dtype: int64

famsize
GT3    281
LE3    114
Name: famsize, dtype: int64

Pstatus
T    354
A     41
Name: Pstatus, dtype: int64

Medu
4    131
2    103
3     99
1     59
0      3
Name: Medu, dtype: int64

Fedu
2    115
3    100
4     96
1     82
0      2
Name: Fedu, dtype: int64

Mjob
other       141
services    103
at_home      59
teacher      58
health       34
Name: Mjob, dtype: int64

Fjob
other       217
services    111
teacher      29
at_home      20
health       18
Name: Fjob, dtype: int64

reason
course        145
home          109
reputation    105
other          36
Name: reason, dtype: int64

guardian
mother    273
father     90
other      32
Name: guardian, dtype: int64

schoolsup
no     344
yes     51
Name: schoolsup, dtype: int64

famsup
yes    242
no     153
Name: famsup, dtype: int64

paid
no     214
yes    181
Name: paid, dtype: int64

activities
yes    201
no     194
Name: activities, dtype: int64

nursery
yes    314
no      81
Name: nursery, dtype: int64

higher
yes    375
no      20
Name: higher, dtype: int64

internet
yes    329
no      66
Name: internet, dtype: int64

romantic
no     263
yes    132
Name: romantic, dtype: int64

Pandas 力をあげるためには?

Pandas は高機能で、Google で調べるといってもなかなか求めている機能を探すことが難しいです。 最初のうちは、よくわからないコードが出てきたら、地道にひとつずつ動きを確認するようにしましょう。

data.dtypes
data.dtypes == 'object'
data.dtypes[data.dtypes == 'object']
data.dtypes[data.dtypes == 'object'].index

こういう地道な努力がプログラミング向上に繋がります。

5.1.5. 数値データの要約

describe()は、数値データの基礎統計量を集計するメソッドです。

基礎統計量

  • count: データ数

  • mean: 平均値

  • std: 標準偏差

  • min: 最小値

  • 25%: 小さい方から25%目の値(第一四分位点)

  • 50%: 中央値

  • 75%: 小さい方から75%目の値(第三四分位点)

  • max: 最大値

ざっくりと傾向をみるとき、重宝します。

[8]:
data.describe()
[8]:
age traveltime studytime failures famrel freetime goout Dalc Walc health absences G1 G2 G3
count 395.000000 395.000000 395.000000 395.000000 395.000000 395.000000 395.000000 395.000000 395.000000 395.000000 395.000000 395.000000 395.000000 395.000000
mean 16.696203 1.448101 2.035443 0.334177 3.944304 3.235443 3.108861 1.481013 2.291139 3.554430 5.708861 10.908861 10.713924 10.415190
std 1.276043 0.697505 0.839240 0.743651 0.896659 0.998862 1.113278 0.890741 1.287897 1.390303 8.003096 3.319195 3.761505 4.581443
min 15.000000 1.000000 1.000000 0.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 0.000000 3.000000 0.000000 0.000000
25% 16.000000 1.000000 1.000000 0.000000 4.000000 3.000000 2.000000 1.000000 1.000000 3.000000 0.000000 8.000000 9.000000 8.000000
50% 17.000000 1.000000 2.000000 0.000000 4.000000 3.000000 3.000000 1.000000 2.000000 4.000000 4.000000 11.000000 11.000000 11.000000
75% 18.000000 2.000000 2.000000 0.000000 5.000000 4.000000 4.000000 2.000000 3.000000 5.000000 8.000000 13.000000 13.000000 14.000000
max 22.000000 4.000000 4.000000 3.000000 5.000000 5.000000 5.000000 5.000000 5.000000 5.000000 75.000000 19.000000 19.000000 20.000000

5.2. 可視化

表データは、数値や統計量だけみていても、データの傾向をつかめません。

Let’s try

ここから例として、G3列を示しますが、他の属性も自分で調べてみましょう。

さて、それではMatplotlib を用いてデータの可視化していきましょう。

5.2.1. 箱ひげ図

箱ひげ図は、最大値、最小値、中央値、四分位範囲を視覚化してくれるグラフです。 ヒゲの上端が最大値、ヒゲの下端が最小値を示します。データの範囲がみえてきます。

最終成績(G3)の統計量

[9]:
data['G3'].describe()
[9]:
count    395.000000
mean      10.415190
std        4.581443
min        0.000000
25%        8.000000
50%       11.000000
75%       14.000000
max       20.000000
Name: G3, dtype: float64

最終成績(G3)の箱ヒゲ図

[10]:
plt.boxplot(data['G3'])
plt.grid(True)

_images/ds05data_23_0.svg
[11]:
## G1, G2, G3 と表示する例
plt.boxplot([data['G1'], data['G2'], data['G3']])
plt.grid(True)
_images/ds05data_24_0.svg

seaborn のboxplot()を用いれば、もう少しこった凝ったヒストグラムを描画できます。

[12]:
sns.boxplot(x=data['Fjob'],y=data['G3'])
plt.title('父親の仕事が子供の成績に与える影響')
plt.xlabel('父親の仕事')
plt.ylabel('成績')
plt.show()
_images/ds05data_26_0.svg
[13]:
## subplot を使った凝ったグラフ

plt.figure(figsize= (15,5))
plt.subplot(1,2,1)
order_by = data.groupby('Fjob')['G3'].median().sort_values(ascending = False).index
sns.boxplot(x = data['Fjob'], y = data['G3'],order = order_by)
plt.xticks(rotation = 90)
plt.title('Fjob v/s G3')

plt.subplot(1,2,2)
order_by = data.groupby('Mjob')['G3'].median().sort_values(ascending = False).index
sns.boxplot(x = data['Mjob'], y = data['G3'],order = order_by)
plt.xticks(rotation = 90)
plt.title('Mjob v/s G3')

plt.show()
_images/ds05data_27_0.svg

可視化の達人になるためには

皆さんから、先生のようにPandasやMatplotlibを使いこなせるようになりたいと言われますが、 過去に書いたグラフを直しながら、必要に応じてGoogleで調べて、書き直しているだけです。 とにかく、色々なグラフを書いて試してみて、 後で探しやすいようにColabのノートブックでライブラリを作っておきましょう。

5.2.2. ヒストグラム

おなじみのヒストグラムも描画してみましょう。

最終成績(G3)のヒストグラム

[14]:
plt.hist(data['G3'], bins=20, range=[0,21], rwidth=0.8)
plt.title('Distribution of grades average of students')
plt.xlabel('G3')
plt.ylabel('Count')
plt.show()
_images/ds05data_30_0.svg
[15]:
sns.countplot(x=data['G3'], palette='gray_r')
sns.set_theme(style="whitegrid")
plt.xlabel('G3')
plt.ylabel('Count')
plt.show()
_images/ds05data_31_0.svg

グループ分けされたヒストグラム

hueを追加すると、カテゴリーごとに色分けされたヒストグラムが得られます。

[16]:
sns.countplot(x='G3',hue='sex', data=data)
plt.title('Students distribution according to G3 and sex')
plt.xlabel('G3')
plt.ylabel('Count')
plt.show()
_images/ds05data_33_0.svg

カーネル密度推定法によるヒストグラム

[18]:

sns.kdeplot(data.groupby('sex').get_group('M')['G3'], shade = True,label = 'male')
sns.kdeplot(data.groupby('sex').get_group('F')['G3'], shade = True, label = 'female')
plt.xlabel('G3')
plt.ylabel('% data distribution')
plt.show()
_images/ds05data_35_0.svg

5.2.3. 散布図と相関係数

ここまでは、基本的にひとつの属性に着目してきました。 データサイエンスや機械学習では、2つの属性間の関係性がより重要になります。

属性間の関係性により注目するため、散布図と相関係数を学びます。

一学期の成績(G1)と最終成績(G3)

[19]:
plt.scatter(data['G3'], data['G1'], marker='o')
plt.xlabel('G3')
plt.ylabel('G1')
plt.grid(True)
_images/ds05data_37_0.svg

第一学期から成績が良い学生が最終成績の成績がよくなる傾向がグラフから読み取ることができます。

この関係性を数値化する手法が、相関係数です。

  • 1に近いほど、正の相関がある

  • 0無相関

  • -1に近いほど、負の相関がある

Python では、SciPy に含まれているpearsonrを使って相関係数を算出できます。

G3とG1の相関係数

[20]:
import scipy as sp
sp.stats.pearsonr(data['G1'], data['G3'])
[20]:
(0.8014679320174141, 9.001430312277354e-90)

ひとつ目の値が相関係数です。(ふたつ目の値は、p値です。)

p値

偶然、実際に反した数値が統計量として計算される確率です。p値が低いほど、ありえないことが起きたことになります。

線形近似(予習)

成績G3とG1の関係を\(y = ax + b\)という線形近似をしてみます。

次回以降用いる機械学習モジュールsklearnを予習として用いて、 最小二乗法によって、\(a\)(回帰係数)と\(b\)(切片)を求めます。

線形近似

[21]:
from sklearn import linear_model
reg = linear_model.LinearRegression()
X = data.loc[:, ['G3']].values
Y = data['G1'].values
reg.fit(X, Y)
print('a=', reg.coef_, ', b=', reg.intercept_)
a= [0.58065293] , b= 4.861250211993213
[22]:
plt.scatter(X, Y)
plt.xlabel('G3')
plt.ylabel('G1')

plt.plot(X, reg.predict(X), color='red') # y=ax+bを描画
plt.grid(True)
_images/ds05data_43_0.svg

詳しくは

第7回目 線形回帰でもう一度勉強します。

5.3. 可視化(プロの技)

最後に、少しデータサイエンスの可視化テクニックを紹介します。

5.3.1. 相関行列とヒートマップ

相関行列は、すべての属性(数値データ間)で相関係数を計算した行列です。

相関行列

[23]:
data.corr()
[23]:
age traveltime studytime failures famrel freetime goout Dalc Walc health absences G1 G2 G3
age 1.000000 0.070641 -0.004140 0.243665 0.053940 0.016434 0.126964 0.131125 0.117276 -0.062187 0.175230 -0.064081 -0.143474 -0.161579
traveltime 0.070641 1.000000 -0.100909 0.092239 -0.016808 -0.017025 0.028540 0.138325 0.134116 0.007501 -0.012944 -0.093040 -0.153198 -0.117142
studytime -0.004140 -0.100909 1.000000 -0.173563 0.039731 -0.143198 -0.063904 -0.196019 -0.253785 -0.075616 -0.062700 0.160612 0.135880 0.097820
failures 0.243665 0.092239 -0.173563 1.000000 -0.044337 0.091987 0.124561 0.136047 0.141962 0.065827 0.063726 -0.354718 -0.355896 -0.360415
famrel 0.053940 -0.016808 0.039731 -0.044337 1.000000 0.150701 0.064568 -0.077594 -0.113397 0.094056 -0.044354 0.022168 -0.018281 0.051363
freetime 0.016434 -0.017025 -0.143198 0.091987 0.150701 1.000000 0.285019 0.209001 0.147822 0.075733 -0.058078 0.012613 -0.013777 0.011307
goout 0.126964 0.028540 -0.063904 0.124561 0.064568 0.285019 1.000000 0.266994 0.420386 -0.009577 0.044302 -0.149104 -0.162250 -0.132791
Dalc 0.131125 0.138325 -0.196019 0.136047 -0.077594 0.209001 0.266994 1.000000 0.647544 0.077180 0.111908 -0.094159 -0.064120 -0.054660
Walc 0.117276 0.134116 -0.253785 0.141962 -0.113397 0.147822 0.420386 0.647544 1.000000 0.092476 0.136291 -0.126179 -0.084927 -0.051939
health -0.062187 0.007501 -0.075616 0.065827 0.094056 0.075733 -0.009577 0.077180 0.092476 1.000000 -0.029937 -0.073172 -0.097720 -0.061335
absences 0.175230 -0.012944 -0.062700 0.063726 -0.044354 -0.058078 0.044302 0.111908 0.136291 -0.029937 1.000000 -0.031003 -0.031777 0.034247
G1 -0.064081 -0.093040 0.160612 -0.354718 0.022168 0.012613 -0.149104 -0.094159 -0.126179 -0.073172 -0.031003 1.000000 0.852118 0.801468
G2 -0.143474 -0.153198 0.135880 -0.355896 -0.018281 -0.013777 -0.162250 -0.064120 -0.084927 -0.097720 -0.031777 0.852118 1.000000 0.904868
G3 -0.161579 -0.117142 0.097820 -0.360415 0.051363 0.011307 -0.132791 -0.054660 -0.051939 -0.061335 0.034247 0.801468 0.904868 1.000000

ご多分に漏れず、可視化しておくとデータ解析が行やすいです。

[24]:
# Correlation matrix
corr = data.corr()
plt.figure(figsize=(10,10))
sns.heatmap(corr, annot=True)
[24]:
<AxesSubplot:>
_images/ds05data_48_1.svg

5.3.2. ヒストグラムと散布図をまとめて書く

seabornモジュールのpairplotを用いると、さまざまな属性の関係性を一度に確認できます。

  • 欠席数absences

  • アルコール摂取量Dalc

  • 年齢age

  • 最終成績G3

[25]:
sns.pairplot(data[['absences', 'Dalc', 'age', 'G3']])
[25]:
<seaborn.axisgrid.PairGrid at 0x13f141e50>
_images/ds05data_50_1.svg
[26]:
sns.pairplot(data[['absences', 'Dalc', 'age', 'G3', 'sex']], hue='sex')
[26]:
<seaborn.axisgrid.PairGrid at 0x13f867190>
_images/ds05data_51_1.svg

5.3.3. カテゴリーデータを視覚化する

カテゴリデータの分類がどのように散らばっているか一覧を作ります。

[27]:
plt.figure(figsize = (15,15))
for i,item in enumerate(['school', 'sex', 'famsize', 'Pstatus', 'Mjob', 'Fjob',
       'reason', 'guardian', 'schoolsup', 'famsup', 'paid', 'activities',
       'nursery', 'higher', 'internet', 'romantic']):
    plt.subplot(4,4,i+1)
    sns.countplot(data=data, x=item)
    plt.title(item)

plt.show()
_images/ds05data_53_0.svg

5.4. コースワーク

演習(ランチボックス)

カフェフロアで販売されているお弁当の販売数のデータを可視化して、 販売数yを増やすため、特徴をつかんでみよう。

属性

説明

datetid

インデックスとして使用する日付

y

販売数

week

曜日(月~金)

soldout

完売フラグ(0:完売せず、1:完売)

name

メインメニュー

kcal

おかずのカロリー(kcal)

remarks

特記事項

event

社内イベント

payday

給料日フラグ(1:給料日)

weather

天気

precipitation

降水量

temperature

気温

  • 販売数yの影響を与える属性は?

  • 販売数y

データの入手方法

SIGNATE社のコンテスト用データを 利用させていただきます。データの入手法の詳細は授業中に紹介します。