LoginSignup
6
1

More than 3 years have passed since last update.

[機械学習]ゴールデンウィークの旅行者数を予測してみた。

Posted at

はじめに

2019年のゴールデンウィークは10連休ということで、
一人旅好きな私はどこに行こうか胸を膨らませていました。
ただ、どう考えても混雑しそう。。。

というわけでデータ整理の勉強がてら、旅行者数について予測してみました。
なお投稿日はゴールデンウィーク直前

使用したデータ

使用データ:観光庁 宿泊旅行統計調査より、2008~2018年度の確定版(第四表)
※データ加工部分は別の投稿で書きます。(たぶん)

こんな感じに加工しました。(単位は人泊です。)

Hokkai_Foreign Hokkai_Japanese (略) Okinawa_Foreign Okinawa_Japanese date
0 199730 1683310 (略) 19270 865170 2008-01-01
... ... ... ... ... ... ...
131 1078470 1964580 (略) 399980 1392080 2018-12-01

予測結果

先に結果を出します。
※前年比実績と予測値の変化率を出しています。

旅行者の少ない県:茨城(-21.310428%)、鹿児島(-16.064494%)、大阪(-13.311542)
旅行者の多い県 :石川(19.886501%)、群馬(12.469104%)、山口(11.781561)

というわけで、GWは茨城にいこう!
私は四国に行ってきます。

使用したアルゴリズム

scikit-learnの MultiOutputRegressor + GradientBoostingRegressor です。
MultiOutputRegressorは複数の目的変数を同時に学習できるという優れものです。
※目的変数同士が学習に影響することはないようです。 ソース

目的変数別にモデルを作ってしまったほうが細かい調整ができるため、あまり使われることがないかと思います。

とはいえ、一つのモデルで複数の出力を得ることが出来るのは利点だと思います。
今回は目的変数が94個あるため、メリットを感じます。

データについて

データは各都道府県ごとの日本人旅行者と外国人旅行者の数、そしてそのデータが取得された年と月です。
目的変数は「日本人旅行者と外国人旅行者の数」となりますので、ここから説明変数を作っていきます。

グラフ化

以下の図は北海道の観光客の推移の図となります。
download.png
図1 北海道の日本人観光客推移

日本人観光客は2013年の夏をピークに停滞傾向が見えます。
また、どうやら夏季に北海道に行く人が多いようです。
(私は秋/冬の北海道が好きです。)

download.png
図2 北海道の外国人観光客推移

外国人観光客は増加傾向が見えます。
また、外国人は日本人とは逆で、冬の北海道に行く人が多いようです。
ウィンタースポーツを目的としているのでしょうか。

特徴量生成

今回のデータは時系列データなので、特徴量は基本的に過去のデータから生成します。
とりあえず、1年前のデータと2年前と1年前の差分データの二つを生成します。
(予測の際、当年のデータは使えないため。)

df = pd.read_csv("travel.csv",index_col=0)
df.set_index("date",inplace=True)

def make_diff(df,prefac):
    target_i = prefac + "_i"
    target_d = prefac + "_d"
    diff_F = prefac + "_diff_F"
    diff_J = prefac + "_diff_J"
    last_F = prefac + "_last_F"
    last_J = prefac + "_last_J"

    df[diff_F] = df[target_F].diff(12).shift(12)
    df[diff_J] = df[target_J].diff(12).shift(12)
    df[last_F] = df[target_F].shift(12)
    df[last_J] = df[target_J].shift(12)

    return df

# 変数 targetには 各目的変数の名前が入っています。
for item in target:
    df = make_diff(df,item)

# NaNが生成されますが、学習する直前で処理します。

季節/トレンド成分の取得

時系列データということで、トレンド成分と季節成分を抽出して特徴量にしてみることにしました。

def make_season(df,prefac):
    trend_F = prefac + "_trend_F"
    trend_J = prefac + "_trend_J"
    season_F = prefac + "_season_F"
    season_J = prefac + "_season_J"

    stmodel_F = sm.tsa.seasonal_decompose(df[target_F].values, freq=12)
    stmodel_J = sm.tsa.seasonal_decompose(df[target_J].values, freq=12)

    df[trend_F] = stmodel_F.trend
    df[trend_J] = stmodel_J.trend
    df[season_F] = stmodel_F.seasonal
    df[season_J] = stmodel_J.seasonal

    # NaNは月ごとの平均で埋めます。
    for i in range(1,13):
        df.loc[(df["month"] == i) & (df[trend_F].isnull()),trend_F] = df[(df["month"] == i)][trend_F].mean()
        df.loc[(df["month"] == i) & (df[trend_J].isnull()),trend_J] = df[(df["month"] == i)][trend_J].mean()
        df.loc[(df["month"] == i) & (df[season_F].isnull()),season_F] = df[(df["month"] == i)][season_F].mean()
        df.loc[(df["month"] == i) & (df[season_J].isnull()),season_J] = df[(df["month"] == i)][season_J].mean()

    return df

# 特徴量生成のため monthデータを生成()
df["month"] = df.index.month
for item in target:
    df = make_season(df,item)

df.drop(["month"],axis=1,inplace=True)

# NaN(2008,9年)のデータを削除します。
df = df.dropna()

*参考にさせていただいたページ:さくっとトレンド抽出: Pythonのstatsmodelsで時系列分析入門

学習

学習機

パラメータなしです。手抜き

def train_MultiGrad(X_train, y_train):
    model = MultiOutputRegressor(GradientBoostingRegressor())
    model.fit(X_train, y_train)

    return model

モデル生成

testデータがだいぶ少ないですが、2018年をテストデータ、
それ以外をトレインデータにします。

余談ですが、DataFrameのindexをDatetimeにしておくと、
処理しやすくて便利ですね。

train = df["2008":"2017"]
test = df["2018"]

X_train = train.drop(col,axis=1)
y_train = train[col]

X_test = test.drop(col,axis=1)
y_test = test[col]

model = train_MultiGrad(X_train,y_train)

学習結果の確認

評価の設定が難しいですが、予測誤差20%以下の個数が占める割合を見ることにしました。
10%だと60%くらいになってしまうため

pred = model.predict(X_test)
pred = pd.DataFrame(data=pred,columns=y_test.columns)

# 予測誤差をパーセントで出します。
Eval = abs((y_test.reset_index(drop=True) - pred) / y_test.reset_index(drop=True) * 100)

# 予測誤差20%以下の個数が占める割合を出します。
print((Eval < 20).sum().sum() / (12 * 47 * 2) * 100)
結果
73.22695035460993

まあ、、、ね?

2019年の旅行者数予測

気を取り直して2019年の予測をしていきます。

2019年予測用のデータ作成

df_2019 = df["2016":"2018"].copy()
df_2019["month"] = df_2019.index.month
def make_2019_Feature(df,prefac):
    target_F = prefac + "_Foreign"
    target_J = prefac + "_Japanese"
    diff_F = prefac + "_diff_F"
    diff_J = prefac + "_diff_J"
    last_F = prefac + "_last_F"
    last_J = prefac + "_last_J"
    trend_F = prefac + "_trend_F"
    trend_J = prefac + "_trend_J"
    season_F = prefac + "_season_F"
    season_J = prefac + "_season_J"

    stmodel_F = sm.tsa.seasonal_decompose(df[target_F].values, freq=12)
    stmodel_J = sm.tsa.seasonal_decompose(df[target_J].values, freq=12)

    tr_F = stmodel_F.trend
    tr_J = stmodel_J.trend
    ss_F = stmodel_F.seasonal
    ss_J = stmodel_J.seasonal

    df[diff_F] = df[target_F].diff(12)
    df[diff_J] = df[target_J].diff(12)
    df[last_F] = df[target_F]
    df[last_J] = df[target_J]
    df[trend_F] = tr_F
    df[trend_J] = tr_J
    df[season_F] = ss_F
    df[season_J] = ss_J

    for i in range(1,13):
        df.loc[(df["month"] == i) & (df[trend_F].isnull()),trend_F] = df[(df["month"] == i)][trend_F].mean()
        df.loc[(df["month"] == i) & (df[trend_J].isnull()),trend_J] = df[(df["month"] == i)][trend_J].mean()
        df.loc[(df["month"] == i) & (df[season_F].isnull()),season_F] = df[(df["month"] == i)][season_F].mean()
        df.loc[(df["month"] == i) & (df[season_J].isnull()),season_J] = df[(df["month"] == i)][season_J].mean()

    return df
for item in target:
    df_2019 = make_2019_Feature(df_2019,item)
    gc.collect()

2019年の旅行者予測

df_2019= df_2019["2018"]
df_2019= df_2019.drop(col,axis=1)
df_2019= df_2019.drop("month",axis=1)

predict = model.predict(df_2019)
predict = pd.DataFrame(data=predict,columns=col)
predict.head(1)
Hokkai_Foreign Hokkai_Japanese (略) Okinawa_Foreign Okinawa_Japanese
0 909433.033886 1.979600e+06 (略) 336811.716793 1.206626e+06

出来ました!

2018年との比較

評価のため、外国人観光客と日本人観光客の合算値を出します。

def total_calc(df,prefac):
    target_F = prefac + "_Foreign"
    target_J = prefac + "_Japanese"

    df[prefac] = df[target_F] + df[target_J]

    return df

sum_2018 = df["2018"].copy()
sum_2018 = sum_2018.reset_index(drop=True)
sum_2019 = predict.copy()
for item in target:
    sum_2019 = total_calc(sum_2019,item)
    gc.collect()

for item in target:
    sum_2018 = total_calc(sum_2018,item)
    gc.collect()

sum_2019.drop(col,axis=1,inplace=True)
sum_2018.drop(col,axis=1,inplace=True)

5月の増加割合を出します。

eval_per = (sum_2019 - sum_2018) / sum_2018 * 100
eval_per.loc[4,:]

最終結果

Hokkai       11.281826
Aomori        4.626654
Iwate        12.114893
Miyagi       -3.160811
Akita        11.558399
Yamagata      3.331513
Fukushima    -1.152551
Ibaragi     -21.310428
Tochigi       3.898319
Gunma        12.469104
Saitama       3.997887
Chiba        -9.016136
Tokyo         0.923218
Kanagawa     -5.683196
Niigata       0.582975
Toyama       -3.851999
Ishikawa     19.886501
Fukui         3.087758
Yamanashi    -7.792293
Nagano        1.515756
Gifu          3.559687
Shizuoka      4.386247
Aichi        -2.187282
Mie           9.956324
Siga          8.482789
Kyoto         0.962704
Osaka       -13.311542
Hyogo         8.845633
Nara          8.216589
Wakayama     -2.589020
Tottori      -8.953793
Shimane       9.582237
Okayama       2.927056
Hiroshima    -4.158414
Yamaguchi    11.781561
Tokushima     1.507007
Kagawa        6.948959
Ehime         4.746469
Kouchi       -3.690178
Hukuoka       6.996519
Saga         10.794311
Nagasaki      7.564196
Kumamoto     -4.329024
Ohita         0.990946
Miyazagi      3.431375
Kagoshima   -16.064494
Okinawa      -0.859506

需要があれば全文ソース上げます。

6
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
1