【第1回】SIGNATE 第2回 国土交通省 地理空間情報データチャレンジ: 初見とベースライン
SIGNATE 第2回 国土交通省 地理空間情報データチャレンジ: 初見とベースライン
🗺️ はじめに
この記事は、私が参加している 「第2回 国土交通省 地理空間情報データチャレンジ」(SIGNATE主催)の取り組みを記録するシリーズの第1回です。
このシリーズの目的は以下のとおりです:
- 機械学習および地理空間分析の理解を より深める
- 他の参加者の参考になる 実践的な知見を共有する
本記事では、コンペ概要、データセットの特徴、評価指標のポイント、そして初期ベースラインモデルの構築について扱います。
🎯 コンペ概要
| 項目 | 内容 |
|---|---|
| コンペティション | 第2回 国土交通省 地理空間情報データチャレンジ |
| タスク | 国の地理空間データを用いた不動産売買価格の予測 |
| 評価指標 | MAPE(Mean Absolute Percentage Error) |
| コードリポジトリ | mmrbulbul/ml-competition-notes |
📋 タスク説明
国土数値情報(地理空間データ)および提供された物件データを用いて、不動産売買価格 を予測します。
重要ポイント:地理空間データの活用は 必須 です。
地理空間データにはまだ不慣れなので、まずは「表形式データだけでどこまで戦えるか?」を試したいと考えています。ただし、前回大会の上位者は地理空間特徴量を大きく活用しており、この領域は避けて通れないと感じています。
📊 データセット概要
- Train: 363,924 サンプル / 149 特徴量
- Test: 112,437 サンプル(ターゲットなし)
-
Target:
money_room(物件の売買価格・円)
特徴量には、築年数・構造などの物件属性、緯度経度や住所といった位置情報、日付関連情報、最寄り駅や周辺施設までの距離など、多岐にわたる要素が含まれます。
一見すると 149 特徴量ですが、data_definition.xlsx を確認すると、以下の列はスラッシュ区切りで複数の値が入っており、展開が必要です:
["building_tag_id", "unit_tag_id",
"reform_interior", "reform_exterior", "reform_wet_area",
"statuses"]
⚠️ 重要:時系列のズレ
README.pdf によると、学習データとテストデータは掲載時期が異なります。
学習用データ:2019〜2022年の 1月または 7月
テストデータ:2023年の 1月または 7月
この時間差から考えられるポイント:
- 不動産市場の変化による データ分布のズレ
- 時間関連特徴量の重要度が増す
- 訓練・テストの分布比較が必須
- バリデーションはランダム分割より 時系列分割の方が適切
これらは次回以降で詳しく扱います。
📏 評価指標:MAPE
評価指標は MAPE(平均絶対パーセンテージ誤差) です。
MAPE = (1/n) * Σ |y_true - y_pred| / |y_true| * 100
直感的で分かりやすい一方、学習の目的関数としては扱いにくい という課題があります。
主な理由:
- 0 に近いターゲットで不安定
- 対称性がなく、過小予測にバイアスがかかりやすい
解決策:ログ変換
相対誤差に強い表現にするため、ターゲットに log 変換 を行い、学習時には MAE / RMSE / Huber Loss を使用するのが一般的です。
- RMSE:大きな誤差に敏感(補正が必要な場合あり)
- MAE:MAPE の性質にやや近い
- Huber:MAE と RMSE の中間(α の調整が必要)
🧪 ベースラインモデルの構築
まずはシンプルなベースラインを作成します。
前処理
実施した主な処理:
-
スラッシュ区切りタグの one-hot 展開
→ 特徴量数:149 → 約 509 -
日付系特徴量の 統一フォーマット化
-
欠損値処理
- 完全に空の列(欠損率=1.0)を削除
- カテゴリカル列を文字列に統一
-
地理情報の変換
-
addr1_1、addr1_2(コード)→ 都道府県名、市区町村名
-
1. 欠損値の確認と処理
まず、欠損値の状況を確認します:
def get_missing_report(df):
missing_df = df.isnull().sum(axis=0).reset_index()
missing_df.columns = ['column_name', 'missing_count']
missing_df = missing_df.loc[missing_df['missing_count']>0]
missing_df["missing_ratio"] = missing_df["missing_count"]/len(df)
return missing_df
train_missing = get_missing_report(train_df)
完全に空の列(欠損率=1.0)を削除します:
# trainとtestから削除
drop_cols = train_missing[train_missing.missing_ratio==1].column_name
train_df.drop(drop_cols, axis=1, inplace=True)
test_df.drop(drop_cols, axis=1, inplace=True)
2. スラッシュ区切り特徴量の展開
data_definition.xlsx を確認すると、以下の列はスラッシュ区切りで複数の値が入っています:
slashed_columns = ["building_tag_id", "unit_tag_id",
"reform_interior", "reform_exterior", "reform_wet_area",
"statuses"]
例:"210301/210101/210201" → 3つの個別特徴量に展開
まず、タグIDから内容へのマッピング辞書を作成:
tag_master = pd.read_excel(f"{ROOT_DIR}/data_definition.xlsx",
sheet_name=data_definition.sheet_names[2])
tag_master = tag_master[['タグID', 'タグ内容']]
tag_master["タグID"] = tag_master["タグID"].astype("str")
tag_master.set_index('タグID', inplace=True)
tag_master = tag_master.to_dict()['タグ内容']
次に、スラッシュ区切りの列を展開する関数:
def get_slashed_tags(df):
"""スラッシュ区切りの値を持つ列を個別の列に変換する"""
temp_dfs = []
for col in slashed_columns:
temp_df = df[col].str.get_dummies(sep="/").astype("str")
temp_df.rename(columns=tag_master, inplace=True)
new_col_name = [f"{col} " + c for c in temp_df.columns]
temp_df.columns = new_col_name
temp_dfs.append(temp_df)
temp_dfs = pd.concat(temp_dfs, axis=1)
return temp_dfs
重要: pandas.get_dummies() を使用するため、trainとtestで列数が一致しない可能性があります。
そこで、結合 → 展開 → 再分割 という手順を取ります:
# trainとtestを結合
combined_df = pd.concat([train_df, test_df])
# スラッシュ区切り列を展開
slashed_df = get_slashed_tags(combined_df)
# 新しく生成された列名を保存
tag_columns = slashed_df.columns
# 抽出した特徴量を結合
combined_df = pd.concat([combined_df, slashed_df], axis=1)
# スラッシュ区切りの列を削除
combined_df.drop(slashed_columns, axis=1, inplace=True)
# trainとtestに分割
train_df = combined_df[:len(train_df)]
test_df = combined_df[len(train_df):]
これにより、374列の新しい特徴量が追加され、合計約509列になりました。
3. モデル構築
ターゲットを対数変換し、CatBoostで学習します。
損失関数には、ベースラインのシンプルさを優先して MAE を使用します。将来的にはRMSEやHuber損失との比較も検討していきます。
# ターゲットを対数変換
train_y = np.log1p(train_money_room)
# モデルパラメータ
params = {
"loss_function": "MAE",
"task_type": "GPU", # CPUで実行する場合は削除
"random_seed": 2025,
"verbose": 1000,
"iterations": 5000
}
# モデル学習
model = CatBoostRegressor(**params)
model.fit(train_df, train_y, cat_features=categorical_cols)
# 予測
train_pred = np.expm1(model.predict(train_df))
# 誤差計算
from sklearn.metrics import mean_absolute_percentage_error
mape_error = mean_absolute_percentage_error(train_money_room, train_pred)
print("MAPE Error ", mape_error)
MAPE Error 0.1312
ベースライン結果
このシンプルなベースラインモデルで提出した結果:
Public Leaderboard Score: 17.64 (MAPE)
表形式データのみを用いた初回提出としては、悪くないスタート地点です。
特徴量重要度の分析
CatBoostの特徴量重要度を確認すると、以下のトップ20が得られました:
| Rank | Feature Name | Importance |
|---|---|---|
| 1 | house_area | 13.977 |
| 2 | year_built | 12.145 |
| 3 | City/town/village name | 8.936 |
| 4 | full_address | 5.212 |
| 5 | madori_kind_all | 4.588 |
| 6 | building_create_date | 3.458 |
| 7 | snapshot_land_area | 2.821 |
| 8 | eki_name1 | 2.512 |
| 9 | post1 | 2.427 |
| 10 | walk_distance1 | 2.186 |
| 11 | Prefecture name | 2.016 |
| 12 | money_kyoueki | 1.802 |
| 13 | floor_count | 1.691 |
| 14 | lon | 1.399 |
| 15 | target_ym | 1.286 |
| 16 | rosen_name1 | 1.277 |
| 17 | el | 1.254 |
| 18 | bus_time1 | 1.043 |
| 19 | lat | 1.024 |
| 20 | statuses 浴室乾燥機 | 0.978 |
重要な気づき:
- 物件属性(面積、築年)が圧倒的に重要
- 地理情報(市区町村、住所、駅、経緯度)が上位にランクイン
- 時系列情報(target_ym)も重要度が高い
- 特徴量展開により生成された
statuses 浴室乾燥機がトップ20入り。この結果は、スラッシュ区切りタグの展開が有効であったことを示している
これらの特徴量を起点に、さらなる特徴量エンジニアリングを検討していきます。
🗂️ 今後の予定
ベースラインを構築したことで、改善すべきポイントが見えてきました。今後は以下の領域に取り組んでいく予定です(詳細と順序はTBD):
-
詳細なEDA(探索的データ分析)
- データ分布、時系列変化、地域特性の理解を深める
-
バリデーション戦略の改善
- 時系列分割など、テストデータの特性を考慮したCV戦略
-
欠損値処理の見直し
- より適切な補完方法や欠損パターンの活用
-
特徴量エンジニアリング
- 地理空間特徴量(コンペの必須要件)
- 時系列・集約・交互作用特徴量
-
モデルの高度化
- アンサンブル、時系列ドリフト対策など
-
誤差分析
- 予測精度の改善ポイントを特定
🧵 まとめ
今回のポイント:
-
シンプルなベースラインモデルを構築
- スラッシュ区切り特徴量の展開(149 → 509特徴量)
- Log変換 + MAE損失でCatBoostを学習
- Public Leaderboard: 17.64 MAPE
-
特徴量重要度の分析
- 物件属性(house_area、year_built)が最重要
- 地理情報(市区町村、住所、駅、経緯度)が上位
- 時系列情報(target_ym)の重要性を確認
-
今後の改善方向性を明確化
- バリデーション戦略(時系列分割)
- 欠損値処理の改善
- 地理空間特徴量の追加(必須要件)
- 特徴量エンジニアリングの強化
次回は 詳細なEDA を行い、データの特性をより深く理解していきます。お楽しみに!
本文の一部は、AI による翻訳・文章生成を参考にしています。
Discussion