初投稿です
Kaggleで初めてコンペに挑戦しようとしたので挑戦するにあたり備忘録を置いておくことにしました
詳しいこと確認してないのですが、賞に関係ない?的なことも書いてあったきがしますがデータ分析自体結構楽しいので一通り挑戦が終わるまでは続けていこうと思います。
(あまり調べきれてないのですが、Points This competition does not award ranking pointsとかTiers This competition does not count towards tiersと書かれていたので多分賞とか関係ないですよね...)
Tabular Playground Series - Jun2021
コンペの主な内容
特徴量75個から目標変数(target、8種類)を推定する形式をとってます。この特徴量に関して予めどのような形態化はブラックボックスかされており、ただ目標変数と何かしら関係のあるといったかたちをとってます。
実際にデータを眺めてみる
データを可視化する際に気を付けること
データ可視化にあたって以下のことを気を付けます
- 1 データが正常かそうではないか(欠損値、外れ値など)
- 2 目標変数と説明変数の内容を確認する(記述統計量、個数、変数自体の内容、型)
- 3 各変数についてカテゴリカル変数、離散変数、連続変数のどの形態をとっているか
- 4 説明変数をどう目標変数について説明させるのか() ←修正ほしい
また何か気を付けることがあれば追記します
ライブラリのインポート
正直pandasとseabornとmatplotlibがあれば可視化においては困ることないと思っています
import numpy as np
import pandas as pd
import seaborn as sns
import plotly.express as px
import matplotlib.pyplot as plt
%matplotlib inline
データセットの読み込み
data = pd.read_csv("train.csv")
data.info()
きちんとfeature_74, targetまでしっかり表示されます。
ここで言えるのは説明変数feature_...がint型でtargetがobject型、欠損値はなく200000行あるということです。
あとからNon-NUll Countの部分に気づいたので欠損値確認とかしてたのですがする意味今回はなかったですね。基本的に欠損値が存在する不完全なデータの時は欠損値確認しなければなりません。一応行った欠損値確認の手法は置いておきます。
data.isnull().sum()
# data.isnull().any()
## データ全体の中に欠損値が一つでもあればそれを示す
flag = False
for i in range(len(data.columns)):
if data[data.columns[i]].isnull().any() == True:
print(data[i] + 'have a missing value' )
flag = True
if flag == False:
print('There are no missing value')
他にも最初の段階で見ておくといいことはあります。
生のデータセットを見るのもいいてですが、手元に簡潔なデータセットの一覧が表示されておくと、変数名とか値を確認する際に便利です。
data.head()
data.tail()
統計量を見ることができるプログラミングもあります。結局各変数はグラフで可視化するのが一番ですが最小値、最大値、平均値、4分位数は手元にあるといい気もします。
data.describe()
他には目標変数の中身を見てもいいかもしれません。実際生のデータを見てどのような値があるのかみるのは大変ですし...
data["target"].unique()
冒頭でも一応概要として軽く述べましたけど、今回はClass1~8までを説明変数から推定する問題です。そのため、目標変数ももちろん8つ存在しています。例えば、クラス1について確認してみたかったら以下のようにします。
data.query('target == "Class_1"')
このような感じになります。あまりこのデータから参考にできることは少ないですが、feature_6の列にある43という数字が少し気にかかるくらいですかね...
データを可視化してみる
実際にこれ以降は様々なツールやプログラミングを駆使してデータを可視化していきます。まず初めに、実際に使わないid列を消去します(※idにも何かしらの法則があればデータ分析の際に欠かせない要素となりますが、いったんそのことは念頭に置きます)。
data1 = data.drop('id', axis=1)
data1.head()
では実際に、ヒートマップを用いて特に説明変数と目標変数の相関関係を眺めていこうと思います。まず、targetについてこれはカテゴリカル変数なのでダミー変数に置き換える必要があります。そうすることで、各Classを数値0か1の数値として扱うことができ、説明変数との相関係数を求めることができます。
data_dummies = pd.get_dummies(data1)
そして、ヒートマップで可視化していきます。
data_corr = data_dummies.corr
plt.subplots(figsize=(20, 20))
sns.heatmap(data_corr(), vmax= 0.9, square=True)
Qiitaだと解像度の問題でみにくい...正直相関係数が0.4~0.6あたりの説明変数があると思っていたのでこの時点でかなりビビりました。今回は83×83行のヒートマップだったこともあり、可視化に成功したとはいいがたいので、各目標変数Classと説明変数において相関関係が大きいものから順にリストアップしようと思いました。
# 相関係数行列を作成(長方形でもそう呼んでいいのかな?)
data_dummies_corr = calc_corr(data_dummies)
## 相関係数が大きいものから順に並べている
df_new_corr = pd.DataFrame([])
df_new_corr = data_dummies_corr.sort_values(by = "corr", ascending = False, axis=0)
df_new_corr
feature同士、class同士になっているので調整します
for j in range(9):
a = str("df_new_corr_" + str(j+1))
for j in range(9):
# 各Class1~9に対応したdict(featureと相関係数が中に入っている)を作成
dict_corr_1 = {}
for i in range(df_new_corr.shape[0]):
if df_new_corr['var1'][i] == ('target_Class_'+ str(j+1)):
dict_corr_1[df_new_corr['var2'][i]] = df_new_corr['corr'][i]
# 相関係数が大きいものから順に辞書を降順ソート
dict_corr_1 = sorted(dict_corr_1.items(), key=lambda x:x[1], reverse=True)
print('now we see correlation between target_Class_' + str(j+1) + ' and features')
for k in range(20):
print(dict_corr_1[k])
print('-------------------------------------------------------------------------------------------------')
こんな感じで各Class(計8個)とfeature間で相関係数が高いものを順に表示させていきました。とはいうものの、あまりにも相関関係が低くてどうしようもできません...
次に考えたのは各Class(目標変数)におけるあるfeature(説明変数)の分布の可視化です。イメージとして、Class1に所属する商品はfeature1の0とか10といった値が多く出現する...みたいな感じの事つかめれればと考えていました。
k = input()
## feature_0~74とtargetを列に持つデータフレームを作成
df_feature_1 = pd.DataFrame([])
df_feature_1['feature_'+str(k)] = data['feature_'+str(k)]
df_feature_1['target'] = data['target']
fig = plt.figure(figsize=(20, 20))
## targetごとにグラフを描画する
for i in range(len(df_feature_1['target'].unique())):
df_feature_i = df_feature_1[df_feature_1['target'] == 'Class_'+str(i+1)]
feature_1_i = df_feature_i['feature_' + str(k)].unique().tolist()
feature_1_i = sorted(feature_1_i)
count1_i = [0]*len(feature_1_i)
for j in range(len(feature_1_i)):
count1_i[j] = df_feature_i[df_feature_i['feature_'+str(k)] == feature_1_i[j]].count()["feature_"+ str(k)]
left = np.array(feature_1_i)
height = np.array(count1_i)
ax = fig.add_subplot(3, 3, i+1, xlabel = "feature_" + str(k) + "count", ylabel = "total_count")
ax.bar(left, height, width=0.8)
左上の数字がfeature_(?)の?に入る部分です。0~74までの計85個から入力して、その説明変数(feature)とClassの関係を可視化した感じです。縦軸とか横軸しっかりかいとけばよかったなぁ...とか思ったのですがまぁいいや。値0が一番多い=説明変数の値が0のものがたくさんあるといったイメージです。0や1のせいで他の特徴量をじっくり確認できないなぁとか思ったのですが、結論から言ってしまえばわかったところで相関係数の関係上、使用方法があまり鮮明ではなかったのでここらへんで正直挫折してます。
実際に予測してみる
説明変数をどう加工するかわからなかったので、RandomForestと決定木にかけて分類を試みてみました。ちょうどいましてるところなのでハイパーパラメータの調節等はしっかりできてないところもあると思います。
標準化
説明変数(feature)の値を0~1にします。慣例上した方が精度はよくなるらしいです(メモリの問題?)。
def normalize(df):
result = (df-df.min()) / (df.max() - df.min())
return result
data = data.drop('id', axis=1)
for i in range(data.shape[1]-2): # -2で甘えてしまいました...
data['feature_' + str(i)] = normalize(data['feature_' + str(i)])
データセットを分割
testデータセットはあるのですが、練習かねてtrainデータを分割してそこからtestデータを作ります
from sklearn.model_selection import train_test_split
X_train, X_test = train_test_split(data, test_size=0.3)
X_train.shape , X_test.shape
x_train = X_train.drop('target', axis=1)
y_train = X_train['target']
x_test = X_test.drop('target', axis=1)
y_test = X_test['target']
決定木
from sklearn.tree import DecisionTreeClassifier
decision_tree = DecisionTreeClassifier()
decision_tree.fit(x_train, y_train)
Y_pred = decision_tree.predict(x_test)
acc_decision_tree = round(decision_tree.score(x_train, y_train) * 100, 2)
acc_decision_tree
精度99.4%です。かなりいい結果が出てはいます。しかし、テストデータでは
acc_decision_tree = round(decision_tree.score(x_test, y_test) * 100, 2)
acc_decision_tree
精度23.28%でした。過学習といいたいところですが、実態がつかめてない以上過学習の範疇を超えてる問題だと思います...
一応実際に決定木を可視化します。Graphvizは環境設定の問題でつかえなかったため、plot_treeにて可視化してます。
from sklearn.tree import plot_tree
features = data.columns[:75]
plt.figure(figsize=(50, 24))
plot_tree(decision_tree, feature_names=features, max_depth=4, fontsize=8, filled=True)
plt.savefig('tree_high_dpi', dpi=100) # ディレクトリに保存される
plt.show()
全体像みてもしょうがないので一応こういうのできましたよ...的な感じで抑えておきます。決定木の情報利得にはジニとエントロピーの二つあるのですが、ここら辺も詳しくなりたかったりしますね...テストデータでは精度がかなり、いやだいぶ落ちるものの、分類はしっかりできるんだなぁと感じました。
ランダムフォレスト
基本的に決定木より高精度です。
## Random Forest
from sklearn.ensemble import RandomForestClassifier
random_forest = RandomForestClassifier(n_estimators=100)
random_forest.fit(x_train, y_train)
Y_pred = random_forest.predict(x_test)
random_forest.score(x_train, y_train)
acc_random_forest = round(random_forest.score(x_train, y_train) * 100, 2)
acc_random_forest
精度は99.4%でした。テストデータでは
acc_random_forest = round(random_forest.score(x_test, y_test) * 100, 2)
acc_random_forest
精度は34.94%でした。アンサンブル学習のため、決定木に比べると精度はまだましです。
今後の目標
自分で考えられることはほとんどやったので、コンペに参加している人から先人の知恵を借りたいと思っています。実際に次の記事では先人の知恵+ PipelineやXGBoostとかも触っていきたいと思ってます。
その他
不備があれば連絡お願いします。