問題
不動産の価格予測の問題に取り組んでみた。
https://competition.nishika.com/competitions/mansion_pra/summary
データ分析の流れや考え方を学ぶために取り組んだ。
流れを振り返るためにも備忘録としてアウトプットする。
取り組み方
下記の流れで取り組んだ。
1. データの確認(データ型、欠損値の量、データ量とカラム)
2. 使うデータを絞る。
絞り方は下記にあてはまるものを削除した。
・nullが半分以上あるもの
・カテゴリー変数かつ取る値が多すぎて、数値化しても無意味そうなもの
よって使えそうなカラムは下記と考えた。
3. カテゴリー変数を数値に変換する。
便利なLabelEncoderを使った。
from sklearn.preprocessing import LabelEncoder
realestate_data = df.copy()
def encode_categorical(df, columns):
for col in columns:
LE = LabelEncoder()
df[col] = LE.fit_transform(df[col])
return df
realestate_data = encode_categorical(realestate_data,['都道府県名','間取り', '建物の構造','用途','都市計画','改装'])
4.文字列として入力されている値を数値に変換する。
和暦で記載されている部分を西暦に変換する。
ロジックが非常に勉強になった。
applyで定義した関数をデータフレームに適用できることもわかった。
import unicodedata
import re
import datetime
# 各年号の元年を定義
eraDict = {
"明治": 1868,
"大正": 1912,
"昭和": 1926,
"平成": 1989,
"令和": 2019,
}
def japanese_calender_converter(text):
# テキストがNaNやNoneの場合の処理
# テキストがNaNやNone、または文字列でない場合の処理
if pd.isna(text) or text is None or not isinstance(text, str):
return None
# 正規化
normalized_text = unicodedata.normalize("NFKC", text)
# 年のみを抽出する正規表現
pattern = r"(?P<era>{eraList})(?P<year>[0-9]{{1,2}}|元)年".format(eraList="|".join(eraDict.keys()))
date = re.search(pattern, normalized_text)
# 抽出できなかったら終わり
if date is None:
# print(f"Cannot convert {text} to western year")
return None
# 年を変換
era = date.group("era")
if date.group("year") == "元":
year = eraDict[era]
else:
year = eraDict[era] + int(date.group("year")) - 1
return year
realestate_data['建築年'] = realestate_data['建築年'].apply(japanese_calender_converter)
四半期などの文字を数値に変換。
def convert_quarter_to_decimal(text):
# 年と四半期を抽出
match = re.search(r'(\d+)年第(\d)四半期', text)
if match:
year = int(match.group(1))
quarter = int(match.group(2))
# 四半期を小数点の形式に変換
decimal = (quarter - 1) * 0.25
return year + decimal
else:
return None
値の文字も数字に変換
ここは地道に変換していったが、値の数が多くなると取れない手段だったので
もっといい方法があるのだと思う。
realestate_data.loc[realestate_data['最寄駅:距離(分)'] == '30分?60分' , '最寄駅:距離(分)'] = 45
realestate_data.loc[realestate_data['最寄駅:距離(分)'] == '1H?1H30' , '最寄駅:距離(分)'] = 75
realestate_data.loc[realestate_data['最寄駅:距離(分)'] == '1H30?2H' , '最寄駅:距離(分)'] = 105
realestate_data.loc[realestate_data['最寄駅:距離(分)'] == '2H' , '最寄駅:距離(分)'] = 120
realestate_data['最寄駅:距離(分)'] = pd.to_numeric(realestate_data['最寄駅:距離(分)'],errors='coerce')
5. 欠損値の処理
勉強のついでにと2パターン試してみた。
①平均値で欠損値を埋めるパターン
中央値やランダムが良いパターンも有る。
mean_value = realestate_data['最寄駅:距離(分)'].mean()
realestate_data['最寄駅:距離(分)'].fillna(mean_value, inplace=True)
②欠損値を削除するパターン
あんまり使うことがよくなさそうな印象。
とはいえ有効なタイミングもあると思うので、使い分けれるようにデータ理解を深める必要がある。
realestate_data = realestate_data.dropna(subset=['建築年'])
6. テストデータを機械学習に入れてみる。
何やら使い勝手が良いらしい、GBDTを使う。
import xgboost as xgb
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error
# データの分割
X = realestate_data.drop(columns=['ID', '取引価格(総額)_log'])
y = realestate_data['取引価格(総額)_log']
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# XGBoostの設定
dtrain = xgb.DMatrix(X_train, label=y_train)
dtest = xgb.DMatrix(X_test, label=y_test)
params = {
"objective": "reg:squarederror",
"eval_metric": "rmse",
"booster": "gbtree"
}
# トレーニング
num_round = 100
bst = xgb.train(params, dtrain, num_round, evals=[(dtrain, 'train'), (dtest, 'eval')], early_stopping_rounds=10)
# 予測
y_pred = bst.predict(dtest)
mse = mean_squared_error(y_test, y_pred)
print(f'Mean Squared Error: {mse}')
7. 提出用データでモデルから予測し、csv化して提出
結果は下記。
どれだけ悪いのかよくわかんないけど、順位は下の方。
わかったこと
・機械学習は実装するだけではだめっぽい。
・「モデルの使い分けや学習をどのように進めるか」「同じデータからどんな特徴量を見出すか」にデータ分析の能力の違いが出るのかな。
今後は分析コンペの参加の仕方やGBDTの流れを体験できたので、
次は学習を特徴量に着目してモデルを作成してみる。