1.はじめに
- 筆者はSPSS ModelerやSQLを用いて集計をする機会が多く、pythonでデータ集計をする際に関数を忘れてしまうのでよく使うコードを整理しました。
- ライブラリは、データ加工はpandas、グラフはseabornをメインで利用しています。
- 関数だけ記述してもわかりにくいため、今回はseabornにあるtitanicデータを拝借しています。
- 筆者はjupyter notebook上で処理しています。
- 各処理を理解することに重点を置いているため、集計内容に深い意味はありません。
- SPSS Modeler、SQLとの対応についても簡単に記述しています。
※ただし完全に同じではないので、あくまで処理イメージとしてください。
※SQLはRDBMSの種類によって書き方が異なる箇所がありますのでご留意ください。
2.事前準備
-
jupyter notebookの準備
https://techacademy.jp/magazine/17430 -
numpy、pandas、matplotlib、seabornのインストール
ANACONDA NAVIGATORを利用している場合、上記リンクでmatplotlibをインストールしている方法と同様に他のライブラリもインストールを行う。 -
ANACONDA NAVIGATORを利用していない場合は下記を参照
https://gammasoft.jp/python/python-library-install/ -
各ライブラリの説明は下記を参照
https://qiita.com/hik0107/items/19dd2f6a4ab61ec21905
3.ライブラリと利用データのインポート
今回利用するライブラリとデータをロードします。
# pandasを宣言
import pandas as pd
# numpyを宣言
import numpy as np
# matplotlibを宣言
from matplotlib import pyplot as plt
# jupyter notebook上で表示
%matplotlib inline
# seabornを宣言
import seaborn as sns
# seabornのtitanicデータをインポート
rawdata = sns.load_dataset('titanic')
4.データの読み込み・書き出し
今回はseabornから読み込んでいるデータを利用するが、csvファイルから読み込みを行うことが多いため記述しています。
# 読み込み
# (参考)headerがない場合は、引数にheader=Noneを入れる
train = pd.read_csv("train.csv" , encoding='shift_jis')
# 書き出し
rawdata.to_csv("rawdata.csv" , index = None , header = None)
5.データの確認
まれにカラムずれなど明らかにおかしなデータセットとなっている場合があるので、データの大きさや中身について、最低限の確認を行います。
SPSS modeler:サンプルノード
SQL:LIMIT句
# 先頭行の確認
rawdata.head()
# 後尾行の確認
# 関数の場合、()の中にオプションを入れられる。今回は10行を見る。デフォルトは5行
rawdata.tail(10)
# 行数、列数の確認
rawdata.shape
(891, 15)
# 重複を削除した配列作成
np.unique(rawdata["embark_town"])
6.データ型
データ型の確認と、意図と異なっていた場合は変更します。
特に、数値データは(定量的に)数字として扱うのか、コード値として扱うのか確認します。
SPSS modeler:データ型ノード
SQL:CAST関数など
# データ型の確認
rawdata.info()
# データ型変換
# (例)文字列型に変換
rawdata["pclass"] = rawdata["pclass"].astype(np.str)
rawdata.info()
# データ型変換
# (例)数値列型に変換
rawdata["pclass"] = rawdata["pclass"].astype(np.int)
rawdata.info()
7.統計量の確認
ざっくりとした各カラムの全体傾向の確認します。
# 要約統計量の確認
rawdata.describe()
# 平均値の確認
rawdata.mean()
# 一部カラムの平均値の確認
rawdata["age"].mean()
29.69911764705882
# 相関係数の確認
rawdata[["pclass","survived"]].corr()
8.欠損値処理
利用するデータによっては、欠損値が存在します。存在する場合は集計前に対応しておきます。
# 欠損値のあるカラムを確認(いずれかのカラムに欠損値があるとTrue)
rawdata.isnull().any()
# 各カラムの欠損値の数を確認
rawdata.isnull().sum()
# 欠損値を平均値で補完
rawdata["age"] = rawdata["age"].fillna(rawdata["age"].mean())
# 欠損値があるレコードは削除
rawdata = rawdata.dropna(subset=["embarked"])
9.基礎集計
1変数のデータ傾向や、変数間のデータ傾向を定量的に確認します。
# 1変数集計 レコードカウント
rawdata["sex"].value_counts()
# 2変数(クロス)集計 レコードカウント margins=Trueとして、総和も表示
pd.crosstab(rawdata["sex"] , rawdata["survived"] , margins=True)
# 2変数(クロス)集計 行和が100%
cross_tab = pd.crosstab(rawdata["sex"] , rawdata["survived"])
cross_tab.apply(lambda x:x/sum(x),axis=1)
10.レコード抽出
集計に用いるレコードのみを抽出します。
SPSS modeler:条件抽出ノード
SQL:WHERE句
# 抽出条件が数値データ
# (例)ageが30以上のレコードを抽出
rawdata[rawdata["age"] >= 30]
# 抽出条件が文字列(完全一致)
# (例)sexがfemaleのレコードを抽出
rawdata[rawdata["sex"] == "female"]
# 抽出条件が文字列(完全一致)
# (例)最初の2文字を抽出
rawdata["class"].str[:2]
# 抽出条件が文字列(完全一致)
# (例)3文字目以降を抽出
rawdata["class"].str[2:]
# 抽出条件が文字列の場合(部分一致)
# (例)sexに「fe」を含むレコードを抽出
rawdata[rawdata["sex"].str.contains('fe')]
# 抽出条件が文字列の場合(前方部分一致)
# (例)classの最後の文字に「Fir」を含むレコードを抽出
rawdata[rawdata["class"].str.startswith('Fir')]
# 抽出条件が文字列の場合(後方部分一致)
# (例)classの最後の文字に「st」を含むレコードを抽出
rawdata[rawdata["class"].str.endswith('st')]
# 抽出条件がorで複数ある場合
# (例)ageが30以上 もしくは sexがfemaleのレコードを抽出
rawdata[(rawdata["sex"] == "female" ) | (rawdata["age"] >= 30)]
# 抽出条件がandで複数ある場合
# (例)ageが30以上 かつ sexがfemaleのレコードを抽出
rawdata[(rawdata["sex"] == "female" ) & (rawdata["age"] >= 30)]
11.カラム抽出
集計に用いるカラムのみを抽出します。
SPSS modeler:フィルターノード、カラム順序入替ノード
SQL:SELECT句
# 必要なカラムのみ抽出(1カラムの場合)
# (例)survivedのみ抽出
rawdata["survived"]
# 必要カラム名のみ抽出(複数カラムの場合)
# (例)survivedとsexを抽出
rawdata[["survived","sex"]]
# カラム名を変更する
# (例)who を sex_child に変換
rawdata.rename(columns={'who': 'sex_child'}, inplace=True)
rawdata.head()
12.レコード&カラム抽出
集計に用いるレコードとカラムのみを抽出します。
- loc
行、列をラベルで指定
- iloc
行、列を番号で指定
# カラム名から抽出(全レコード)
# (例)カラム「survived」と「sex」を抽出
rawdata.loc[:,["survived","sex"]]
# カラム番号から抽出(全レコード)
# (例)0番目、1番目、2番目、3番目、6番目、12番目のカラムを抽出
rawdata.iloc[:,[0,1,2,3,6,12]]
# レコード番号、カラム番号から抽出
# (例)2行目、3行目のレコードかつ、0番目、1番目のカラムを抽出
rawdata.iloc[[2,3], [0,1]]
13.置換
データの中身を置換します。
SPSSmodeler:置換ノード
SQL:replace関数
# 文字列の置換
# (例)「embark_town」カラムにある「Southampton」を「England」に置換する
rawdata["embark_town"].replace("Southampton" , "England")
14.新カラム作成
既存のデータを用いて新たなデータを作成します。
SPSS modeler:フィールド作成ノード
# 1変数を用いて新カラム作成
# (例)ageが20未満の場合、childが1、それ以外(欠損値なども含む)を0とする
rawdata["child"] = rawdata["age"].apply(lambda x : 1 if x < 20 else 0)
rawdata.head(10)
# 1変数を用いて新カラム作成
# (例)embark_townがCherbourgの場合はFrance、Queenstownの場合はNew Zealand、Southamptonの場合はEnglandとして新カラムembark_town_renameを作成
rawdata["embark_town_rename"] = rawdata["embark_town"].map({"Cherbourg":"France", "Queenstown":"New Zealand" , "Southampton":"England" })
rawdata.head(6)
# 2変数以上を用いて新カラム作成(複雑な条件の場合)
# (例)今回は、テレビ広告で用いられることの多いターゲット区分を用いる。
# まずは、ageとsexから定義表を作成
def target_class(sex,age):
if age >= 4 and age <= 12:
return "C"
elif age >= 13 and age <= 19:
return "T"
elif sex == "male" and age >= 20 and age <= 34:
return "M1"
elif sex == "male" and age >= 35 and age <= 49:
return "M2"
elif sex == "male" and age >= 50:
return "M3"
elif sex == "female" and age >= 20 and age <= 34:
return "F1"
elif sex == "female" and age >= 35 and age <= 49:
return "F2"
elif sex == "female" and age >= 50:
return "F3"
else:
return "OTHER"
# 定義表にあてはまる形で新カラムtarget_classを作成
rawdata["target_class"] = rawdata.apply(lambda rawdata: target_class(rawdata["sex"], rawdata["age"]), axis=1)
rawdata.head()
# 数値データから閾値を設けて新カラム作成
# (例)ageを10刻みに分ける
rawdata["age_bining"] = pd.cut(rawdata["age"],[0,20,30,40,50,60,100])
rawdata.head()
# ダミー変数として作成
pd.get_dummies(rawdata[["sex","embark_town"]])
# 小数点0桁を表示
round(rawdata["fare"],0)
15.レコード集計
グループごとに集計を行います。
SPSS modeler:レコード集計ノード
SQL:GROUP BY句
# レコード集計後、レコード件数を表示
# (例)pclass×sexのレコード件数
rawdata.groupby(["pclass" , "sex"], as_index=False).size()
# 集計結果を再度テーブル化する
rawdata.groupby(["pclass" , "sex"]).size().reset_index()
# レコード集計後、平均値を表示(全ての数値カラム)
# (例)pclass×sexの平均値
rawdata.groupby(["pclass" , "sex"], as_index=False).mean()
# レコード集計後、平均値を表示(一部の数値カラム)
# (例)pclass×sexのage平均値
rawdata.groupby(["pclass" , "sex"], as_index=False)["age"].mean()
16.ピボットテーブル
行、列、集計値を指定して、自由自在に集計を行います。
SPSS modeler:再構成ノード、レコード集計ノード
pd.pivot_table(data, values=[値のカラム名], index=[行のカラム名], columns=[列のカラム名], aggfunc='[集計を行う関数]',margins=[True/False])
# 集計値を平均値としたピボットテーブル
# (例)行をsex、列をpclassとしたピボットテーブル
pd.pivot_table(rawdata, index="sex", columns="pclass", values="age",margins=True)
# 集計値を最小値、最大値としたピボットテーブル
# (例)行をsex、列をpclassとしたピボットテーブル
pd.pivot_table(rawdata, index="sex", columns="pclass", values="age", aggfunc=[min, max],margins=True)
# 先ほどの結果を行へピボット
pd.pivot_table(rawdata, index="sex", columns="pclass", values="age", aggfunc=[min, max],margins=True).stack()
- stack 列から行へのピボット
- unstack 行から列へのピボット
17.レコード結合
2つ以上のデータセットの結合を行います。(横に繋げる)
SPSS modeler:レコード結合ノード
SQL:JOIN句
# (例)事例で用いる結合マスタを作成
sex_class_master = pd.DataFrame([["male", "First","male_first"],
["male", "Second","male_second"],
["male", "Third","male_third"],
["female", "First","female_first"],
["female", "Second","female_second"],
["female", "Third","female_third"]],
columns=["sex", "class","sex_class"])
sex_class_master
# 内部結合
# (例)rawdataと先ほど作成したsex_class_masterのデータセットから、sexとclassをキーとして結合
pd.merge(rawdata, sex_class_master, on=["sex","class"])
# 左部分外部結合
# (例)rawdataと先ほど作成したsex_class_masterのデータセットから、sexとclassをキーとして結合
pd.merge(rawdata, sex_class_master, how="left", on=["sex","class"])
18.レコード追加
2つ以上のデータセットの結合を行います。(縦に繋げる)
SPSS modeler:レコード追加ノード
SQL:UNION句
# (例)事例用にデータ分割
rawdata_male = rawdata[rawdata["sex"] == "male"]
rawdata_female = rawdata[rawdata["sex"] == "female"]
# レコード追加
# 列名を同じにする必要がある。また、indexを振り直す場合、ignore_index=Trueとする
# (例)先ほど作成したrawdata_maleとrawdata_femaleを縦に積む
rawdata = pd.concat([rawdata_male,rawdata_female], ignore_index=True)
19.行列入れ替え
SPSS modeler:行列入れ替えノード
# (例)事例用に行をsex、列をpclassとしたピボットテーブルを作成
sc_temp = pd.pivot_table(rawdata, index="sex", columns="class", values="age",margins=True)
sc_temp
# 行列を入れ替え
# (例)先ほど作成した、sc_tempを用いて行列入れ替え
sc_temp.T
20.並べ替え
SPSS modeler:並べ替えノード
SQL:ORDER BY句
# 昇順
# (例)ageで昇順
rawdata.sort_values(by="age")
# 降順
# (例)ageで降順
rawdata.sort_values(by="age", ascending=False)
21.グラフ基本
データの傾向を視覚的に確認します。
SPSS modeler:各グラフノード
# ヒストグラム
# (例)ageの分布を10刻みとして確認
sns.distplot(rawdata["age"],kde=False, bins=10)
# 箱ひげ図
# (例)sexごとにageのばらつきを確認
sns.boxplot(x="sex", y="age", data=rawdata )
# 散布図
# (例)ageとfareの関係性を確認
sns.scatterplot(x="age", y="fare", data=rawdata)
# 散布図(ヒストグラムも同時に表示)
# (例)ageとfareの関係性を確認
sns.jointplot(x="age", y="fare", data=rawdata)
# 棒グラフ(縦軸:レコードカウント)
# (例)sex別pclassのレコードカウント
sns.countplot(x="sex", hue="pclass", data=rawdata)
# 棒グラフ(データの平均値と信頼区間を出力)
# (例)sex別pclassのsurvivedの平均値
sns.barplot(x="sex", y="survived", hue="pclass", data=rawdata)
# 積み上げ棒グラフ
# (例)事例用に、sexとsurvivedのクロス表を作成
cross_tab_temp = pd.crosstab(rawdata["sex"] , rawdata["survived"] )
cross_tab_temp
# (例)sex別のsurvivedごとのレコードカウント
cross_tab_temp.plot.bar(stacked=True)
# 折れ線グラフ
# (例)事例用にage×sexのfareの平均値のデータセットを作成
temp = pd.DataFrame(rawdata.groupby(["age" , "sex"], as_index=False)["fare"].mean())
temp
# (例)sex別age×fareの折れ線グラフ
sns.lineplot(x="age", y="fare", hue="sex", data=temp)
# 帯グラフ
# (例)事例用にclassとtarget_classのクロス表(行和100%)を作成
cross_tab = pd.crosstab(rawdata["class"] , rawdata["target_class"] )
cross_tab_rate = cross_tab.apply(lambda x:x/sum(x),axis=1)
cross_tab_rate
# (例)classごとのtarget_classの帯グラフ
# 1回で作成出来ないため、target_classの項目別の積み上げ棒グラフを繰り返しで作成するイメージ
temp = []
ncol = len(cross_tab_rate.columns)
for i in range(ncol):
if i == 0:
plt.bar(cross_tab_rate.index,cross_tab_rate.iloc[:,i],label=cross_tab_rate.columns[i])
temp = cross_tab_rate.iloc[:,i]
else:
plt.bar(cross_tab_rate.index,cross_tab_rate.iloc[:,i], bottom=temp,label=cross_tab_rate.columns[i])
temp = temp + cross_tab_rate.iloc[:,i]
plt.legend( bbox_to_anchor=(0.8, 0.3, 0.5, 0.5))
22.グラフオプション
グラフのカスタマイズを行います。以下にも詳しく記述されています。
Python: seaborn を使った可視化を試してみる
seabornでMatplotlibの見た目を良くする
# 背景を変更
# (例)whitegridにする
sns.set_style("whitegrid")
sns.distplot(rawdata["age"],kde=False, bins=10)
# カラーパレットを変更
# (例)Pastel1にする
sns.countplot(x="sex", hue="pclass", data=rawdata, palette="Pastel1")
# タイトル作成
# (例)sex × pclass countとする
sns.countplot(x="sex", hue="pclass", data=rawdata)
plt.title("sex × pclass count")
# グラフを保存
sns.countplot(x="sex", hue="pclass", data=rawdata)
plt.title("sex × pclass count")
plt.savefig("sex × pclass count.png")
# グラフを横にして作成
sns.boxplot(x="age", y="sex", orient="h" , data=rawdata )
# グラフのサイズ変更
plt.figure(figsize=(20, 5))
sns.distplot(rawdata["age"],kde=False)