実践データサイエンス─サンプルコードと図表で学ぶ、前処理・モデル評価・パラメータチューニング
実践とともに、データサイエンスに入門しよう!敷居が高いと思われがちなデータサイエンスですが、データの前処理からの手順は意外とシンプルです。本記事では、データの前処理や特徴量の作成、モデルの評価・訓練、ハイパーパラメータの調整など、基本的な知識をサンプルコードと図表を見ながら学びます。
データサイエンティストとしてのスキルを向上させるには、データの前処理や特徴量の作成、モデルの評価・訓練、ハイパーパラメータの調整など、広域にわたる知識を身に付ける必要があります。
この記事は、そうした知識を「サンプルコードと図表を見ながら、分かりやすく学習できること」を目指して作成されました。記事内では、新米データサイエンティストのOさんが登場して、ある案件のデータ分析を担当します。読者のみなさんも、ぜひOさんと一緒にプログラムを書き、問題を解いてみてください。
それでは、データサイエンスの基礎知識を学んでいきましょう!
- ストーリー:新米データサイエンティストOさんの挑戦
- 分析を始める前の準備
- 1. データを理解する
- 探索的データ解析(Exploratory Data Analysis)
- 2. 特徴量の作成
- 3. モデルの評価方法を決める
- 4. モデルの訓練
- 5. ハイパーパラメータの調整
- 6. テストデータで確認する
- まとめ
ストーリー:新米データサイエンティストOさんの挑戦
新米データサイエンティストのOさんは、あるサービス(保険、不動産、ECサイトなど)を運営している会社のデータサイエンティストです。ある日、営業担当者から次のような相談を受けました。
「最近出した複数の新商品をお客さまにおすすめしているのだが、買ってくださる方とそうでない方がいる。データ分析をして、新商品を好みそうなお客さまを見つけることはできますか?」
よく話を聞いてみると、「新商品を購入する確率が高いお客さまに対して、優先的に売り込みたい」ことが分かってきました。「これはデータサイエンティストが得意な問題だ」と感じたOさんは、この問題に取り組んでみようと決意しました。ちなみに、今は営業担当者の経験と勘と度胸でおすすめしているようです。
Oさんは、この問題は機械学習でいうマルチクラス分類やマルチラベル分類、レコメンド問題など複数の方法で解決できるのではと考えました。しかし、このような案件を担当するのが初めてだったOさんは、まずはこれを簡単な問題に落とし込み、果たして解ける問題なのかどうかを確認しようと思いました。
Oさんは、以下の入力と出力の関係性を導くことができれば、新商品の購入有無を予測できると考えました。
- 入力:ユーザー情報、過去の商品購入情報
- 出力(予測対象):新商品の購入有無
この関係性を学習できれば、「どういったお客さまが新商品を買ってくれそうか?」「どの商品を買っていたお客さまが新商品を好んでくれそうか?」が分かりそうです。新商品を好みそうなお客さまに、優先的におすすめすることが可能になります。
検討の結果、下記のような二値分類の問題に落とし込みました。
- ユーザー情報と過去の商品(Item A~I)の購買履歴データを使って、現在の商品の購入有無を予測する
- まずは簡単に、現在購入可能な1つの商品(Item X)の予測ができるかどうかを確認する
これを専門的に言うと「Item Xの購入有無の予測(二値分類)」となります。
この問題を解くため、Oさんは以下の方針で進めようと考えました。
- データを理解し、分析・前処理をする
- 予測に有効そうな特徴量を作成する
- モデルの評価方法を決める
- モデルを訓練する
- チューニングをして、モデル精度を高める
- 新たなデータで予測結果を確かめる
さて、Oさんはそれぞれのタスクをどのように解決していったのでしょうか? 1つずつ見ていきながら、データサイエンスの世界に飛び込んでみましょう!
分析を始める前の準備
データを分析する前に、いくつか準備することがあります。
データセットの用意
まずは、データセットの用意です。Oさんはデータエンジニアと協力して、下記のようなデータセットを作成しました。
- user_table.csv
-
- 新商品(Item X)を売り込みにいったユーザーの情報が入ったテーブル
- 欠損値がある
- 重複するデータもある(データ収集ミスなどが原因)
- historical_transactions_AtoI.csv
-
- 過去購入可能だった商品 Item A~I に関する購買履歴のテーブル
- 過去に何も購入していないユーザーはデータが存在しない
- historical_transactions_X.csv
-
- 現在購入可能な商品 Item X に関する購買履歴のテーブル
- 過去に何も購入していないユーザーはデータが存在しない
※コードを実際に手元で確かめたい場合、データセットは下記にあります。
分析に使用するPythonライブラリの用意
今回、Oさんが分析に使用するPythonライブラリは以下の通りです。
ライブラリ | 用途 |
---|---|
numpy | 行列の取り扱い |
pandas | 表の取り扱い |
scikit-learn | 機械学習 |
matplotlib | グラフの描画 |
seaborn | グラフを綺麗にする |
category_encoders | カテゴリ変数の変換 |
読者のみなさんの環境で入っていないライブラリがあれば、次のコマンドを実行してインストールしてください。
$ pip install -U [ライブラリ名]
次のように、全てのライブラリを一括でインストールすることもできます (既に入っているライブラリのバージョンが上がるため、不都合がある場合は個別にインストールしてください)。
$ pip install -U numpy pandas scikit-learn matplotlib seaborn category_encoders
1. データを理解する
データサイエンスにおいては一般的に、データの特性をつかみ、仮説と検証を繰り返すことでモデルを改善していくというプロセスが取られます。本章ではその第一歩として、データの基本的な情報を確認することから始めましょう。
基本構成の確認
まず、各データの構成から確認していきましょう。データを読み込んだ後、各データのカラム構成を確認します。
# データ処理のためのライブラリ import numpy as np import pandas as pd # 可視化用のライブラリ import matplotlib.pyplot as plt %matplotlib inline import seaborn as sns sns.set() sns.set_style(style='dark') # 1セルの実行結果を複数表示するための便利設定 from IPython.core.interactiveshell import InteractiveShell InteractiveShell.ast_node_interactivity = "all"
# データの読み込み users = pd.read_csv('user_table.csv') historical_transaction = pd.read_csv('historical_transactions_AtoI.csv') X_transaction = pd.read_csv('historical_transactions_X.csv')
# 各データの中身確認(最初の3つ) users.head(3) historical_transaction.head(3) X_transaction.head(3)
それぞれ次のようになります。
user_id | name | nickname | age | country | num_family | married | job | income | profile | |
---|---|---|---|---|---|---|---|---|---|---|
0 | 14742 | Richard Chen | kathryn77 | 20.0 | NaN | 1.0 | 0.0 | Human resources officer | 394.0 | Last sit star physical accept. Actually relate... |
1 | 21530 | Kayla Garcia | brandtalexander | 59.0 | Saint Kitts and Nevis | 4.0 | 0.0 | Teacher, early years/pre | 370.0 | Door entire as. Whose suddenly mission hold.\n... |
2 | 34985 | Troy Blackwell | richardfarmer | 44.0 | Iraq | 3.0 | 2.0 | Forensic psychologist | 326.0 | Writer drug a tax. Team standard both write pr... |
user_id | price | num_purchase | item | |
---|---|---|---|---|
0 | 0 | 867 | 3 | C |
1 | 0 | 947 | 2 | F |
2 | 0 | 815 | 2 | D |
user_id | price | num_purchase | item | |
---|---|---|---|---|
0 | 1 | 137 | 1 | X |
1 | 7 | 137 | 1 | X |
2 | 9 | 137 | 1 | X |
カラムの意味を理解する
カラムとは、データの列である属性(例えば、年齢や価格)を表します。今回用意したカラムは、それぞれどのような意味を持っているのでしょうか。
user_table(コード上はusers
)
カラム | 意味 |
---|---|
user_id |
ユーザーを区別するための識別子 |
name |
名前 |
nickname |
ニックネーム |
age |
年齢 |
country |
国籍 |
num_family |
家族の人数 |
married |
0: 未婚/1: 既婚/2: 離婚 |
job |
職業 |
income |
収入 |
profile |
プロフィール |
historical_transactions_AtoI(コード上はhistorical_transaction
)
カラム | 意味 |
---|---|
user_id |
ユーザーを区別するための識別子 |
price |
購入単価 |
num_purchase |
購入個数 |
item |
購入した商品の種類(A~I) |
historical_transactions_X(コード上はX_transaction
)
カラム | 意味 |
---|---|
user_id |
ユーザーを区別するための識別子 |
price |
購入単価 |
num_purchase |
購入個数 |
item |
購入した商品の種類(このテーブルにはXしか入っていない) |
データ不備への対応
「与えられたデータが分析に使用しても問題ない品質かどうか?」を確認することは、地道ではありますが、非常に重要なプロセスです。
データ不備の代表的なケースとして、次のような場合が挙げられます。
- 重複が存在する
- 欠損が存在する
そもそも、これらが起きるとなぜ問題なのでしょうか。
1. 重複が存在する
データ送信システムの不具合や考慮漏れなどによって、同一の内容であるにもかかわらず、データが重複して保存されてしまうケースがあります。
データに重複が含まれていたとき、本来は同一の内容であるにもかかわらず、モデルがその部分を「新しい別のデータ」であるとして大きな比重で学習してしまい、結果的に過学習になるなど、意図しないことが生じるリスクがあります。そのため、事前に重複を削除しておく必要があります。
もちろん、もともとは別のデータにもかかわらず、偶然レコードの値が一致しているという場合には、重複を削除する必要はありません。
2. 欠損が存在する
モデルによっては、データの欠損部分をそのまま利用できない場合があります。学習に使用できる情報が少ないと、モデルの性能は低下してしまう可能性があります。
加えて、例えば最も基礎的な予測モデルの1つである「線形回帰モデル」では、欠損を含むデータはそもそも入力ベクトル(行列)として適切ではなく、そのまま使用できません。したがって、欠損に直面した際は、何らかの方法で欠損値補完を行うことがあります†。
欠損値の補完では、次のような方法が代表的です。
- 0、-1、-999で埋める(学習に含まれない値での補完)
- 各カラムの平均値で埋める
- 他のカラムを利用して推測した値で埋める
ですが、最も良い補完方法はデータごとに異なるので、試行錯誤して方法を定めるのが良いでしょう。
また、欠損に対する別の対処として、欠損を含むデータはそもそも学習に用いないという方法もあります。
†Note: データによっては、「データが欠損している」という情報自体が意味を持つことがあります。
例えば、最大1000人が参加するマラソン大会の成績データにおいて、“途中棄権”したユーザーについては「順位」カラムの値が欠損しているようなケースを考えてみましょう。この場合は「欠損している」ことを示すため、順位として取り得ない-1
や1000000
などの値で補完することがあります。
このように「欠損」自体を表現することが重要な場合には、下記のような補完も有効かもしれません。
- 量的データ
- 各カラムが取り得ないほど大きな、もしくは小さな値で埋める
- 質的データ
- 「欠損」カテゴリを作成する
重複データへの対処
早速、実際のデータを使って、重複の確認と、重複部分の削除を行ってみます。
users
、historical_transaction
、X_transaction
のそれぞれについて、重複除去前・後のデータフレームの形を比較してみましょう
# 重複するデータを削除 names = ['users', 'historical_transaction', 'X_transaction'] dfs = [users, historical_transaction, X_transaction] for name, df in zip(names, dfs): print(f"{name} - shape before: {df.shape}") df.drop_duplicates(inplace=True) df.reset_index(drop=True, inplace=True) print(f"{name} - shape after: {df.shape}")
この実行結果は次のようになります。
users - shape before: (50103, 10)
users - shape after: (50000, 10)
historical_transaction - shape before: (169379, 4)
historical_transaction - shape after: (152200, 4)
X_transaction - shape before: (6444, 4)
X_transaction - shape after: (5618, 4)
各データには重複がありましたが、上記の処理によって重複部分をきちんと削除できたようですね。
欠損データへの対処
欠損値が存在した場合、どのような値で埋めるべきなのかはデータの分布や特性、データの作成仕様によっても異なります。
今回は、ある程度分析を行った後で、どのような値で埋めるかを定めます。後半パートの「欠損の確認」にて、欠損値を埋めることにします。
探索的データ解析(Exploratory Data Analysis)
続きをお読みいただけます(無料)。

- すべての過去記事を読める
- 過去のウェビナー動画を
視聴できる - 企業やエージェントから
スカウトが届く