先日にメルカリがKaggleで投稿商品の販売価格予測のコンペを開始したばかりですが、2017年11月29日にリクルートから第二弾目となるKaggleコンペティションが公開されました!
リクルートのKaggleコンペ第一回目は同社が運営するクーポンサイト「ポンパレ」の顧客情報からの購入するクーポンの予測(レコメンデーション)を行うものでした。
今回の第二回目のKaggleリクルートコンペでは、同じく同社が運営する「ホットペッパー」と「Airレジ(飲食店向けPOSレジアプリ)」のデータを利用して、レストランのお客さんの数を予測するコンペとなっています。
すでに簡単な予測モデルでのカーネル(Kaggleの公開コーディング)が立ち上がっておりますので、そちらを参考にしながら、Kaggle初心者向けのハンズオンチュートリアルを早速まとめました。
Kaggleへチームで参加してみませんか?codexaではチームメイトとのマッチングを行なっています。お気軽にお問い合わせからKaggleプロフィールURLと一緒にご連絡ください。
では、早速概要とシンプルな予測モデルをPythonを使って作ってみましょう!
この記事の目次
Kaggleリクルート レストラン客数予測チャレンジ概要
まずは概要から紐解いていきましょう。英語が苦手な方向けの内容なので、問題ない方はRecruit Restaurant Visitor Forecastingをご覧ください。
- 2017年11月29日にKaggleリクルートチャレンジ(ホットペッパー)が開始
- 2018年1月30日が参加申し込みおよびチーム申請の最終期限
- 2018年2月6日が最終提出の締め切り
- 賞金は1位12,000米ドル、2位8,000米ドル、3位5,000米ドル
- 評価はRMSLEのスコア
- 提出ファイルはidとvisitorsの2カラム
- idはair_store_idとvisit_dateをアンダースコアーで連結する
- air_store_id以外にhpg(Hot Pepper Gourmet)のidもあるので要注意
蛇足ですが、リクルートが以前に行なったクーポン予測のコンペでは、賞金が1位30,000ドルでしたので、今回のコンペは賞金が半分以下になってしまいましたね。とはいえ、日系の会社からKaggleへの参加は嬉しいものです!日本人カグラーで上位を目指していきましょう!
それでは、Kaggleリクルートレストラン客数予測チャレンジの初心者向けハンズオンチュートリアルをやってみましょう。
このチュートリアルでやる内容&対象の方
本記事ですがKaggleまたは機械学習初心者向けのチュートリアルとなっています。すでにKaggleで活躍されている方、機械学習を長年やっている方には物足りない内容となっていますので、ご注意ください。
このチュートリアルでやる内容
- Kaggleリクルートのデータセットの確認
- 色々な平均を出して予測をする(後ほど詳しくやります)
- Kaggleへファイルを提出(スコア確認)
使うもの
- Python 3.6
- Pandas
- Numpy
- Kaggleアカウント
- Jupyter Notebook(必須ではありません)
Jupyter Notebookですが、必須ではありません。Pythonのみを使って処理は可能ですが、あると便利です。多くのデーターサイエンティストが使っているツールですので、まだインストールされていない方は、この機会にいかがでしょうか?(ダウロードはこちらからどうぞ)
またPandasとNumpyのバージョンはさほど大きな影響はありませんが、Python2.7をお使いの方は、正しく処理できませんので3.6へのアップデートをご検討ください。
最後にデータを使うには、Kaggleリクルートコンペへの参加と利用規約の同意が必須となります。Kaggleの無料会員アカウントで可能なので、まだKaggleへ登録されていない方はこちらから登録をお願いします。
では、早速データをみていきましょう!
Kaggleリクルートのデータ確認
Kaggleリクルートチャレンジ2ではデータセットが全部で8つリクルートから提供されています。データは大きく分けると2種類あり、「ホットペッパー」と「Airレジ」の各サービスの2016年〜2017年4月までの予約情報(予約日時や予約人数)が提供されています。
- air_reserve.csv
- Airレジ経由の予約情報
- air_store_id – Airレジ固有のレストランID
- visit_datetime – 予約時のお店訪問予定時間
- reserve_datetime – 予約をした時の時間
- reserve_visitors – 予約人数
- hpg_reserve.csv
- ホットペッパー経由の予約情報
- hpg_store_id – ホットペッパー固有のレストランID
- visit_datetime – 予約時のお店訪問予定時間
- reserve_datetime – 予約をした時の時間
- reserve_visitors – 予約人数
- air_store_info.csv
- Airレジのレストラン情報
- air_store_id – Airレジ固有ID
- air_genre_name – レストランのジャンル
- air_area_name – レストランの所属エリア
- latitude – 緯度
- longitude – 経度
- hpg_store_info.csv
- ホットペッパーのレストラン情報
- hpg_store_id – ホットペッパー固有ID
- air_genre_name – レストランのジャンル
- air_area_name – レストランの所属エリア
- latitude – 緯度
- longitude – 経度
- store_id_relation.csv
- AirレジとホットペッパーのIDリレーション
- 両サービスを使っているお店のみ
- hpg_store_id – ホットペッパー固有ID
- air_store_id – Airレジ固有ID
- air_visit_data.csv
- Airレジの各レストランの日付ごとの実客数
- air_store_id – AirレジID
- visit_date – 日付
- visitors – 実客数
- sample_submission.csv
- 提出ファイルのサンプルフィーマット
- 予測しなくてはいけない日付も入っている
- idの項目など少し特徴がある
- id – air_store_idとvisit_dateを連結させたid
- visitors – 予想客数(店&日付のコンビネーション)
- data_info.csv
- カレンダー日付の基本的な情報
ファイル数は多いですが、実際に予測に使える項目は思ったよりも少なさそうです。次に本チュートリアルでやる予測の手法をみていきましょう
Kaggleリクルート レストラン予測の手法
本チュートリアルですが、すでにKaggleで公開されているカーネルを参考に、初心者向けに書き直しています。公開されているコードとなりますので、当然ですがこちらの予測では100%上位ランクインできません!ただ、この予測を手始めに、色々な発展系を作るのはありだと思います。
早速、予測の手法をみていきましょう。
予測するデータ
これはsample_submission.csvを紐解くとわかりますが、予測しなくてはいけないデータは、Airレジを使っている821店舗のレストランの2017年4月最終週〜2017年5月末日までの各日にちのお客さん来店数となります。821店舗×39日間で32019行がsample_submission.csvに格納されています。全ての行のvisit(つまり来客数)を予測しなくてはいけません。
ステップ① 過去データの曜日の中央値(median)を算出
予測するレストランの過去の来客数データから、曜日ごとの中央値(median)を算出します。過去の来客数データはair_visit_data.csvに入っています。ただし、日付、レストランID、実客数のみしかありませんので、このデータに曜日を追加して、レストランIDと曜日毎に実客数をまとめて中央値(median)を算出します。
中央値(median)と平均値(mean)は似ていますが、異なりますので注意しましょう。簡単な説明ではありますが、下記のとあるレストランの月曜日〜水曜日の3週間分の来客数を見てください。
月曜日の平均値は25名で、中央値は25名とたまたま一緒でしたが、他の曜日では平均値と中央値は異なります。中央値は、データを並べたときに「真ん中」にある値をさします。今回のステップ1での予測では、この中央値(メディアン)を使います。
ステップ② 重み付き平均(加重平均)を算出
二つ目のステップとして、予測ターゲットを「重み付き平均」を使って算出します。まず簡単にですが、重み付き平均について説明をします。下記の表をまずご覧ください。
とある学校でAとBのクラスがあり、その平均点とクラスの人数を表しています。AとBのクラスの合わせた全体の平均点は82点となります。ただし、クラスの人数に差がありますので、人数が多いクラウスの方が重要(全体の平均点へ与える影響が大きい)と考えるのが加重平均の考え方です。
求め方としては各クラスの平均点にクラス人数をかけたものを足して、全体の人数で割ります。
最初の表に出ていますが、このようにクラスの人数=重みをつけて改めて平均を計算すると、85.5点と算術平均と比べて高くなるのがわかります。
今回の予測ですが、複雑な計算は抜きにして、この重み平均も使ってみましょう。そもそもの議論として、どのような項目を使って「重み」を算出するのかは大きな議論となりますが、今回は単純にair_visit_data.csvの日付を重みとして利用します。前述しましたが、こちらのデータには2016年から2017年4月までの各店舗の実客数が日付ごとに入っています。
古い日付には重み(重要度)を低く、予測しなくはいけない2017年4月の直近のデータには重み(重要度)を高くします。
この重みを使って、各レストランの「曜日」「祝日フラグ」の項目にまとめて加重平均を算出します。意味合いとしては、「Aのレストランの祝日ではない火曜日は、過去1年間のデータで直近に重みをおいた加重平均で19名来店する可能性があります」という予測が可能です。
ステップ3 中央値と加重平均のさらに平均を算出
とうとうステップ3で、Kaggleへ提出する予測データを作ります。今まで2通りの方法で予測を算出してきましたが、最後の処理としてステップ1(中央値)とステップ2(加重平均)で算出した数値の、さらに平均をとって最終の予測データとしましょう。
Aをステップ1で求めた中央値(meadin)、Bをステップ2で求めた加重平均(wmean)とした時に、さらにその2つの数値の平均を下記の3つの異なる平均で求めてみましょう!
最後に求めた平均をCSVファイルへ書き出して、Kaggleへ投稿すればスコアもつきます!実際に3つの異なる平均で計算した予測データは、それが一番優秀なモデルなのか?も判明します。
では、実際にKaggleリクルートレストラン予測チャレンジからデータをダウロードして、Pythonを使って予測を計算してみましょう!
過去データの曜日の中央値を算出しよう
では、早速Pythonを使ってデータの処理と必要な値を算出していきましょう。またデータセットのCSVヲダウロードされていない方は、Kaggleへ無料会員登録を行いこちらからダウンロードしましょう。
まずは必要なライブラリをインポートしましょう。
1 2 3 4 5 6 7 8 |
# numpyなど必要なライブラリをインポート import numpy as np import pandas as pd import glob, re |
次にsample_submission.csvを読み込んで確認してみましょう。
1 2 3 4 5 6 7 8 9 10 |
# sample_submission.csvを読みこんでヘッダー情報&Shapeの確認 test_df = pd.read_csv('sample_submission.csv') test_df.head(20) test_df.shape (32019, 4) |
sample_submission.csvですが、Kaggleへの提出データの形式となっています。こちらに入ってるAirレジのレストランと日付を使って、予測(実客数)の予測をしなくてはいけません。前の項目でも軽く触れましたがIDの形式がすこしややこしいですね。「air_ストアID_日付」と3つの情報がアンダースコアで連結されています。前処理として、このIDを情報毎に分解しましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
# 元々のIDからstore_idとvisit_dateを切り出します test_df['store_id'] = test_df['id'].str[:20] test_df['visit_date'] = test_df['id'].str[21:] # 提出ファイルのサンプルでvisitorsは意味がないのでdropしましょう test_df.drop(['visitors'], axis=1, inplace=True) # 日付の型をobjectからdatetimeに変換しておきましょう test_df['visit_date'] = pd.to_datetime(test_df['visit_date']) # 念のためカラム情報の確認 test_df.info() # ヘッダー情報もみておきましょう test_df.head() |
左がtest_dfのカラム情報で右がヘッダー情報となります。元々のIDに入っていたstore_idとvisit_dateを処理して別のカラムに分けました。visit_dateはdatetimeとして型の変更もしています。
次はair_visit_data.csvの読み込みと前処理をしましょう。air_visit_data.csvですが、Airレジの各レストランの日付と実客数のデータとなります。つまり予測しなくてはいけないレストランの過去の実客数の実績データです。このデータを処理してレストラン/曜日毎の「中央値(Median)」を算出しましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# air_visit_data.csvを読み込む、parse_datesでdatetime型へ変換しておく air_data = pd.read_csv('air_visit_data.csv', parse_dates=['visit_date']) # サイズを確認しておきましょう air_data.shape (252108, 3) # ヘッダー情報 air_data.head() |
air_visit_data.csvは単純な構造ですね。visit_dateはdatetime型として扱えるようにparse_datesのアーギュメントを使いましょう。
予測で使うsample_submission.csvの中から一つだけレストランIDを取り出して、air_visit_data.csvのデータを参照してみましょう。今回はsample_submission.csvの最上部に載っていたID「air_00a91d42b08b08d9」を参照してみます。
1 2 3 4 5 6 7 8 9 10 11 |
# 予測するレストランIDを1つ使ってair_dataから実客数をみてみる check_store_sample = air_data[air_data['air_store_id'] == 'air_00a91d42b08b08d9'] # 基本統計量の確認 check_store_sample.describe() # visit_dateの確認 check_store_sample.visit_date.describe() |
左がvisitorsの基本統計量、右がvisit_dateの情報となります。レストランID「air_00a91d42b08b08d9」では、平均26.0名、標準偏差12.43、最小1名〜最大99名のお客さんが来ていたことがわかります。右のvisit_dateを確認する通り、「2016年7月1日〜2017年4月22日」の期間のデータとなります。(こちらはデータセットの前処理ではありません。単純にデータがどうなっているのか確認のために出しました)
さて、本題に戻ってair_dataを訓練データとして使うために前処理を行なっていきましょう。前処理でやる事としては、主に下記の3つです。
- 2017-01-28以降のデータの切り出し
- visit_dateを基に曜日のデータ作成
- 曜日とレストランIDを基にグルーピングして中央値を算出
日付から曜日への変換ですが、pandasのdayofweekを使用して、dow(Day of Week)としてカラムに追加しています。dayofweekですが、曜日を文字列としてではなく数値(0=月曜日、6=日曜日)として出力しますので注意しましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
# air_dataの日付(visit_date)を曜日(dow)へ変換する air_data['dow'] = air_data['visit_date'].dt.dayofweek # air_dataから2017-01-28以降のデータを切り出して訓練データ「train」へ格納 train = air_data[air_data['visit_date'] > '2017-01-28'].reset_index() # trainとtest_dfも日付を曜日へ変換して「dow」をカラム追加する train['dow'] = train['visit_date'].dt.dayofweek test_df['dow'] = test_df['visit_date'].dt.dayofweek #データを確認しておきましょう test_df.head() train.head() |
左がtrainで右がtest_dfのヘッダー情報です。両データ共に日付から変換された曜日(dow)が入っているのが確認できます。
さて、次はtrainからair_store_idとdowをグルーピングして、median(中央値)を取り出しましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# pandasのagg関数で使うリストを作成 aggregation = {'visitors' : {'total_visitors' : 'median'}} # trainからair_store_idとdowをグルーピングしてvisitorsの中央値(median)を算出 agg_data = train.groupby(['air_store_id', 'dow']).agg(aggregation).reset_index() # agg_dataのカラム名をつける agg_data.columns = ['air_store_id', 'dow', 'visitors'] agg_data['visitors']= agg_data['visitors'] # agg_dataを確認しよう agg_data.head(12) |
air_data(air_visit_data.csv)から各レストランIDごとに各曜日(dow)のお客さんの中央値のデータができました。こちらのデータですが、平均値(mean)ではなくて、中央値(median)ですので注意しましょう。
Kaggleへ提出するデータ(sample_submission.csv)から前処理したtest_dfと、各レストランの実客数データ(air_visit_data.csv)を処理して中央値を算出して作ったagg_dataをマージさせましょう。最後にfinalとして、Kaggleへの提出データに必要なidとvisitorsを切り取りましょう。
1 2 3 4 5 6 7 8 9 10 11 |
# test_dfとagg_dataのstoreid_id、dowをすり合わせmergeさせる merged = pd.merge(test_df, agg_data, how='left', left_on=['store_id', 'dow'], right_on=['air_store_id', 'dow']) # idとvisitorsだけをfinalへ格納 final = merged[['id', 'visitors']] # finalのヘッダー情報 final.head() |
元々のid(air_レストランID_日付)とagg_dataで処理したvisitors(レストランIDと曜日を基にair_visit_data.csvの実客数の中央値)だけ残しています。
上記のヘッドでも確認できますが、finalのデータの中にNaNが入っていますね。どれくらいNaNが入っているのか確認してみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# NaNを探してテーブルにする関数 def missing_values_table(df): mis_val = df.isnull().sum() mis_val_percent = 100 * df.isnull().sum()/len(df) mis_val_table = pd.concat([mis_val, mis_val_percent], axis=1) mis_val_table_ren_columns = mis_val_table.rename( columns = {0 : 'Missing Values', 1 : '% of Total Values'}) return mis_val_table_ren_columns # finalのNaNを確認してみよう missing_values_table(final) |
当然idは全て埋まっていますが、visitorsに1114個のNaNがあるのがわかります。全体の3.48%がNaNとなっています。これではまずいので・・こちらのNaNへ0を入れておきましょう。
1 2 3 4 5 6 7 8 |
# fillna関数を使ってvisitorsのNaNへ0を入れておく final.fillna(0, inplace=True) # 念のため確認 missing_values_table(final) |
visitorsのカラムも全て何かしらの値が入ったことが確認できます。さて、ひとまずここまでで、予測しなくてはいけないAirレジレストランの各日付に対して、過去の曜日と実客数のデータから算出をした中央値を予測値としてもつデータができました。
次は、ステップ2として、、重み付き平均を算出しましょう。
重み付き平均(加重平均)を算出
ステップ2へ移る前に、他のCSVファイルもデータフレームに読み込んでしまいましょう。下記ですが、Kaggleリクルートチャレンジからダルロードしたファイルを同一フォルダーに入れていないと動きませんのでご注意ください。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# 全てのCSVを一気に読み込む # glob.glob('')に適切なファイルのパスを指定してください dfs = { re.search('/([^/\.]*)\.csv', fn).group(1):pd.read_csv( fn) for fn in glob.glob('./*.csv')} for k, v in dfs.items(): locals()[k] = v # 読み込んだファイルを確認 print('data frames read:{}'.format(list(dfs.keys()))) data frames read:['air_store_info', 'date_info', 'store_id_relation', 'hpg_reserve', 'air_reserve', 'air_visit_data', 'sample_submission', 'hpg_store_info'] |
これで各CSVファイルのファイル名=データフレーム名として、読み込みが完了しました。前の項目で触れましたが、重み付き平均は「曜日」と「祝日フラグ」の項目別にまとめて出します。祝日ですが、リクルートから提供されているdata_info.csvを少し処理する必要があります。
まずはdate_infoを確認してみましょう。
1 2 3 4 5 |
# data_infoの祝日フラグが1(オン)のデータを確認 date_info[date_info['holiday_flg'] == 1].head(10) |
index1とindex2のレコードを見るとわかりますが、祝日フラグには週末でも祝日の場合は「1」とフラグが立っているのがわかります。前処理として、まずはこちらのフラグを週末の場合(土曜日or日曜日)は、祝日であってもフラグを0とする処理を行いましょう。
1 2 3 4 5 6 7 8 |
# date_infoから土日で祝日フラグが「1」のレコードを探してweekend_hdaysに格納 weekend_hdays = date_info.apply((lambda x:(x.day_of_week=='Sunday' or x.day_of_week=='Saturday') and x.holiday_flg==1), axis=1) # date_infoの該当の箇所のフラグを1から0へ更新をする date_info.loc[weekend_hdays, 'holiday_flg'] = 0 |
これで祝日=土日ではない平日のお休みというデータとなります。
次のステップとしては、日付を基に「重み」を作成しましょう。前述しましたが、日付が古いものには少ない重みを、予測する日付に近い(新しい日付)には多い重みを与えましょう。
1 2 3 4 5 6 7 8 9 10 |
# 該当の日付+1 ÷ 全部の日付の個数で重みを計算 # date_info.indexの値が小さい=より昔のデータ date_info['weight'] = (date_info.index + 1) / len(date_info) #ヘッダーとテイルの情報を出して確認してみよう date_info.head() date_info.tail() |
左がheadで右がtailとなります。こちらの表で確認できる通り日付2016-01-01の重みは「0.001934」に対して、2017-05-31の重みは「1.000000」となっています。つまり予測しなくてはいけない日程に近いほど重要度が高いということが言えます。
重みの処理もできましたので、次のステップは実際に重み付き平均を算出することです。やり方としては、air_visit_data(Airレジの各レストランの実客数データ)に上記で算出したWeight(重み)を加えて、visitors(実客数)をnp.log1pを使って対数にして、「air_store_id」「day_of_week」「holiday_flg」(各レストランID、曜日、祝日フラグ)でグルーピングをして 重み付き平均を算出します。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# air_visit_dataと重みを追加したdate_infoをマージさせてvisit_dataを作成 # visit_dataから不必要なcalendar_dateを落とす visit_data = air_visit_data.merge(date_info, left_on='visit_date', right_on='calendar_date', how='left') visit_data.drop('calendar_date', axis=1, inplace=True) # visit_dataの実客数にnp.log1pの対数関数を使って処理 visit_data['visitors'] = visit_data.visitors.map(pd.np.log1p) # visit_dataの確認 visit_data.head(10) |
上記の通り、事前に算出した「重み(weight)」、さらに実客数をnp.log1pで処理した数値が「visitors」へ処理が加わっているのが確認できます。
これで、やっと「レスストランID」「曜日」「祝日」に応じた重み付き平均の算出が可能になりました!本チュートリアルの前半で、重み付き平均の求め方をやっていますので、忘れてしまった方は改めて確認しておきましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# wmean(重み付き平均)の式を格納 wmean = lambda x:( (x.weight * x.visitors).sum() / x.weight.sum() ) # グルーピングして重み付き平均を算出 visitors = visit_data.groupby( ['air_store_id', 'day_of_week', 'holiday_flg']).apply(wmean).reset_index() visitors.rename(columns={0:'visitors'}, inplace=True) # データを確認 visitors.head(10) |
これで、レストランID、曜日、祝日フラグごとの客数の「重み平均」が算出されました。上のヘッダー情報をみてもわかりますが、index1とindex2は同じレストランID「air_00a91d42b08b08d9」で曜日も「Monday(月曜日)」と一緒ですが、祝日フラグが異なりますので、各レコードに重み平均が算出されています。
さて、次の処理として、この重み付き平均で算出した予測客数を、sample_submissionのデータのレストランIDや日付を基に客数を埋めていきましょう。(sample_submissionに予測しなくてはいけない日付やレストランIDが入っていましたね)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
# sample_submissionのIDをレストランIDや日付に分ける sample_submission['air_store_id'] = sample_submission.id.map( lambda x: '_'.join(x.split('_')[:-1])) sample_submission['calendar_date'] = sample_submission.id.map(lambda x: x.split('_')[2]) # 重み付き平均で予測したvisitorsとsample_submissionをマージする sample_submission.drop('visitors', axis=1, inplace=True) sample_submission = sample_submission.merge(date_info, on='calendar_date', how='left') sample_submission = sample_submission.merge( visitors, on=['air_store_id', 'day_of_week', 'holiday_flg'], how='left') # データセットを確認してみよう sample_submission.head() |
これで、事前に処理をした「重み付き平均」の客数が各レストラン毎に入りました。次に欠損データの確認と処理を行いましょう。まずは、欠損データをmissing_values_tableで確認してみましょう。
1 2 3 4 5 |
# sampe_submissionの欠損データを確認 missing_values_table(sample_submission) |
visitorsで重み付き平均が入っていないレコードが668個もありますね。一番最初に重み付き平均を入れた時は過去データの「レストランID」「曜日」「祝日フラグ」に基づいて入れましたが、それに該当していないレコードが欠損していますので、今度は「祝日フラグ」の条件を除いて、「レストランID」「曜日」に基づいて重み付き平均を入れていきましょう。
1 2 3 4 5 6 7 8 9 10 11 |
# 「air_store_id」と「 day_of_week」のみで欠損データに重み平均を入れる missings = sample_submission.visitors.isnull() sample_submission.loc[missings, 'visitors'] = sample_submission[missings].merge( visitors[visitors.holiday_flg==0], on=( 'air_store_id', 'day_of_week'), how='left')['visitors_y'].values # 改めて欠損データの確認 missing_values_table(sample_submission) |
上の条件で約200の欠損データが埋まりましたが、まだ448個あります。最後は「曜日」の条件も省いて、単純に「レストランID」にのみの重み付き平均を欠損データへ埋めておきましょう。
1 2 3 4 5 6 7 8 9 10 11 |
# 「air_store_id」のみの重み付き平均を計算して欠損データへ入れる missings = sample_submission.visitors.isnull() sample_submission.loc[missings, 'visitors'] = sample_submission[missings].merge( visitors[['air_store_id', 'visitors']].groupby('air_store_id').mean().reset_index(), on='air_store_id', how='left')['visitors_y'].values # 欠損データを確認 missing_values_table(sample_submission) |
これで全ての欠損データが埋まったのが確認できます。
最後にこのsample_submissionをKaggleリクルートレストランチャレンジの提出規定の形に整えましょう。visitorsですが、現段階ではnp.log1pで対数となっていますので、np.expm1で実際の予測した客数に戻してから、カラムを規定のフォーマットに戻します。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# visitorsをnp.expm1で処理して実客数へ戻す sample_submission['visitors'] = sample_submission.visitors.map(pd.np.expm1) # 提出フォーマットの規定に合うように処理してsub_fileへ格納 sample_submission = sample_submission[['id', 'visitors']] final['visitors'][final['visitors'] ==0] = sample_submission['visitors'][final['visitors'] ==0] sub_file = final.copy() # データの確認 sub_file.head() |
これで、重み付き平均で予測をしたデータが「sub_file」としてKaggleへ提出データのフォーマットへ戻ったのが確認できます。次はとうとう最後のステップ「中央値と加重平均のさらに平均を算出」をして最終の予測データを出して、Kaggleでスコアを確認してみましょう。
中央値と加重平均のさらに平均を算出
現段階でfinaleのvisitorsへは、ステップ1で算出した「曜日の中央値 」が客数予測値として入っています。またsample_submissionのvisitorsへは、ステップ2で算出をした「重み付き平均」の客数予測値が入っています。
最後に、このふた通りの客数予測をさらに3つの手法で平均を算出してKaggleへデータを提出してみましょう。Aがfinal[‘visitors’]でBがsample_submission[‘visitors’]として、簡単に求めることが可能です。
各平均を求めて、それぞれ別名でcsvファイルに書き出しましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# 算術平均をnp.meanで算出 sub_file['visitors'] = np.mean([final['visitors'], sample_submission['visitors']], axis = 0) sub_file.to_csv('sub_math_mean_1.csv', index=False) # 相乗平均を算出 sub_file['visitors'] = (final['visitors'] * sample_submission['visitors']) ** (1/2) sub_file.to_csv('sub_geo_mean_1.csv', index=False) # 調和平均を算出 sub_file['visitors'] = 2/(1/final['visitors'] + 1/sample_submission['visitors']) sub_file.to_csv('sub_hrm_mean_1.csv', index=False) |
ローカルに「sub_math_mean_1.csv」「sub_geo_mean_1.csv」「sub_hrm_mean_1.csv」の三つのファイルが出力されて入れば成功です。それでは、気になる予測の結果をKaggleへ投稿して確認してみましょう!
【結果】予測したファイルをKaggleへ投稿
Recruit Restaurant Visitor Forecasting Submitのページでファイルの提出が可能です。下記が、三つのファイルをKaggleへ提出した結果となります。Nameの部分が最後に出力したCSVファイル名となっています。
あまりスコアに大差はありませんが、どうやら最後の処理で「調和平均」で処理をした結果が一番良いスコアがでています。(つまり一番予測モデルとしての精度が優秀)参考までにですが、こちらのスコアは338名中の現在147位となっています!
Kaggleリクルートへ挑戦しよう
本チュートリアルでは、Kaggleリクルートチャレンジ2のデータを実際に触ってみて、Pythonを使って様々な平均を算出して予測をしました。
来年1月末までが最終提出の期限となっていますので、ぜひ、より精度の高い予測モデルの作成に挑んでみましょう!まだいまいちに不安な方は、他にもカーネルで多数の予測モデルが公開されています。カーネルを一つ一つ紐解くことで、データの前処理やモデリングなど参考になることは多くあります!
codexaでもKaggleのタイタニックや、メルカリチャレンジのハンズオンも同様に公開していますので、もう少し初心者向けハンズオンをやってみたいという方は、こちらも是非、やってみてください。
【Kaggle初心者入門編】タイタニック号で生き残るのは誰?
Kaggle メルカリ価格予想チャレンジの初心者チュートリアル
以上、Kaggleリクルートレストラン客数予測チャレンジの初心者向けチュートリアルでした!