見出し画像

【python分析】ワンルームマンション引越回数とオーナーの利益の関係性をシミュレーションしてみた【不動産】

こんにちは!私です。

不動産投資/不動産運営において、購入者が物件を精査する時には体感的に入居者の居住性や入居率についてある程度の拘りを持つ事が多くあると思います。
何故かというと、入居率が悪く回転率が高い物件は、人の入れ替えに伴う広告料や設備の維持運用費が発生し、手間がかかり経費が高くなりやすいイメージがあるからです。

ただ、実際その認識は正しいのか?どれ位の相違が発生し易いのか?その相違はオーナーにとって許容できるゾーンか? pythonでテストデータを用いてシミュレーションしてみようと思います。

勿論、物件の収支を決める物には、立地や税金など入居率以外の要因も存在しますが、今回は一旦分解して入居率に関して調べてみようと思います。


検証結果(グラフ)

まず検証に関わるデータと、結果を記載していこうと思います。

インプットデータは家賃、年間維持費、退去に関わる費用、契約期間、次の募集までの募集期間、広告費に掛かる費用とし、それぞれランダムで1000データ作成し、30年間所有した場合の利益と引越回数の関係をシミュレーションしました。データの詳細は後述を参照ください。

出力結果は下記です。

横軸は引越回数、縦軸は30年運用した後の利益です。
赤線は分散しているデータの中心線(回帰線)を示しています。

テストデータはランダムデータですが、上限・下限・中央値を指定して生成しているので、「だいたいこの辺りの条件帯であれば上記の結果になる」という風に考えられるでしょう。

引越回数は5回~30回にかけ行われますが、得られる利益は1500万から1000万以下まで右肩下がりの減少傾向で回帰線が引かれ、想像通り、引越回数が多い程利益が減少する事がわかりました。

では具体的な検証方法から改めて図を見ていきましょう。

1. 検証方法

テストデータの作成

始めにテストデータを作成します。
1000個のデータを下記の上限~下限値の範囲で中央値を設定した正規分布でランダムデータを作成します。
この条件は東京都多摩地域のワンルームマンションの家賃帯/運用費をイメージしています。

  • 年間維持費:5万〜10万 中央値:5万

  • 退去費用:5万〜10万 中央値:5万 ※オーナーが支払う退去に関わる雑費

  • 広告費:1カ月〜4カ月 中央値:1.5カ月

  • 契約期間:6ヶ月〜48ヶ月 中央値:24ヶ月

  • 家賃:3万〜15万 中央値:6万

  • 募集期間:1ヶ月〜12ヶ月 中央値:6ヶ月

データ作成用のpythonプログラムコードは末尾に記述します。該当プログラムを実行すると、1000行のデータが書かれたCSVが作られます。

プログラムの作成

ここでは、上記で作成したデータを収支計算し、図示化していきます。コードは末尾に記載します。

シミュレーションで引越回数と利益を散布図にプロットしたいため、プログラムでは30年間の引越回数、収支計算と図示化を行っています。

①引越回数=30年/((契約期間月+募集期間月)/12カ月)
②収入=家賃*契約期間*引越回数
③支出=(年間維持費*30年)+(((広告費*家賃)+退去費用)*引越回数)
④利益=収入-支出

図示化にあたってはpythonのSeabornライブラリを使用して回帰線(線形回帰モデル)付きの散布図(regplot())を描画しています。線形回帰する事で散布図の赤の中心軸を直線で引き、予測値を求めます。

2. 検証結果

改めて散布図を見てみようと思います。

ワンルームマンションなので、契約期間の中央値を2年間で設定しています。このシミュレーションだと、平均引越回数は5回~20回の間に多くプロットされます。

また、テストデータでは家賃6万円を中央値としていますが、30年間運用した場合の収支計では、1500万~1000万ほどの価格差が発生し、回転率が低い物件は高い物件より0.66倍程の収益率になってしまう結果となりました。

おわりに

今回の検証プログラムは単純なシミュレーションでの検証でしたが、実体データを利用してみたり、パラメータを複雑化したり、パラメータの相関係数を指定してあげる事で、より細かいケースに応じた面白い検証ができるかと思います。

皆さんも、是非色々なパラメータで検証してみてください。

ではまた、次の記事でお会いしましょう!

コード

テストデータ作成用プログラムコード

input用のcsvデータを作成するプログラムコードです。
インプットデータは実測値が使える様に外出しにしています。CSVデータは同フォルダ内「oneroom_data.csv」として出力されます。

import pandas as pd
import numpy as np
import os

# 各項目の上限値、下限値、中央値を設定
config = {
    "年間維持費(万)": {"min": 5, "max": 10, "mean": 5},  # 年間維持費
    "退去費用(万)": {"min": 5, "max": 10, "mean": 5},    # 退去費用
    "広告費/月": {"min": 1, "max": 4, "mean": 1.5},        # 広告費/月
    "契約期間/月": {"min": 6, "max": 48, "mean": 24},      # 契約期間/月
    "家賃(万)": {"min": 3, "max": 15, "mean": 6},           # 家賃  
    "募集期間/月": {"min": 1, "max": 12, "mean": 6},         # 募集期間(月)を1以上に変更
}

# ランダムデータを生成する関数
def generate_random_data(num_samples):
    data = {
        "年間維持費(万)": [],
        "退去費用(万)": [],
        "広告費/月": [],
        "契約期間/月": [],
        "家賃(万)": [],
        "募集期間/月": []
    }

    for _ in range(num_samples):
        for key, limits in config.items():
            # 中央値を中心に正規分布でランダムに値を生成
            value = np.random.normal(loc=limits["mean"], scale=(limits["max"] - limits["min"]) / 6)
            
            # 下限値と上限値でクリッピングして範囲内に収める
            value = np.clip(value, limits["min"], limits["max"])
            
            # 小数を切り上げして正数にし、最小値を保証
            value = np.ceil(value).astype(int)
            data[key].append(value)
    
    return pd.DataFrame(data)


# 初回実行時にCSVファイルの内容を消去する
csv_file_path = "oneroom_data.csv"
if os.path.exists(csv_file_path):
    # ファイルが存在する場合、内容を空にする
    open(csv_file_path, 'w').close()

# データを生成し、CSVに保存する
num_samples = 1000  # 1000データを生成
df = generate_random_data(num_samples)
df.to_csv(csv_file_path, index=False)

print(f"{num_samples}件のデータが {csv_file_path} に生成されました。")

計算&散布図作成用コード

上記の収支計算と図示化を行っています。
併せて、検証用にログを出力しています。

import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import logging

# ログファイルの設定
logging.basicConfig(filename='simulation_log.log', level=logging.INFO, format='%(asctime)s,%(message)s')

# 収入の計算
def calculate_income(rent, contract_period, num_cycles):
    return round((rent * contract_period * num_cycles), 2)

# 支出の計算
def calculate_expenses(annual_maintenance_cost, contract_period, ad_cost_per_month, rent, move_in_out_cost, num_cycles):
    return (annual_maintenance_cost * 30) + \
           (ad_cost_per_month * rent * num_cycles) + (move_in_out_cost * num_cycles)

# 30年間のサイクル数を計算する
def calculate_cycles(contract_period, recruitment_period):
    cycle_duration_years = (contract_period + recruitment_period) / 12  # 1サイクルにかかる年数
    return int(30 / cycle_duration_years), (contract_period + recruitment_period)  # サイクル数と期間の合計(月)

# データ処理の関数
def process_csv(file_path):
    data = pd.read_csv(file_path)
    
    move_durations = []
    profits = []
    rents = []

    for index, row in data.iterrows():
        num_cycles, move_duration = calculate_cycles(row['契約期間/月'], row['募集期間/月'])
        
        total_income = calculate_income(row['家賃(万)'], row['契約期間/月'], num_cycles)
        total_expense = calculate_expenses(
            row['年間維持費(万)'],
            row['契約期間/月'],
            row['広告費/月'],
            row['家賃(万)'],
            row['退去費用(万)'],
            num_cycles
        )
        total_profit = total_income - total_expense
        
        # ログ出力処理
        logging.info(f"{row['年間維持費(万)']},{row['契約期間/月']},{row['広告費/月']},{row['家賃(万)']},{row['退去費用(万)']},{total_income},{total_expense},{total_profit},{num_cycles}")

        move_durations.append(move_duration)
        profits.append(total_profit)
        rents.append(row['家賃(万)'])
    
    return move_durations, profits, rents

# 散布図と回帰線をプロットする関数
def plot_regression_scatter(num_cycles_list, profits):
    data = pd.DataFrame({'num_cycles': num_cycles_list, 'profit': profits})
    
    # 回帰線付きの散布図を作成
    plt.figure(figsize=(10, 6))
    sns.regplot(x='num_cycles', y='profit', data=data, scatter_kws={'s': 50}, line_kws={'color': 'red'}, marker='o')
    
    plt.xlabel('引越回数', fontname='MS Mincho')
    plt.ylabel('利益(万)', fontname='MS Mincho')
    plt.title('引越回数に応じた利益(回帰線付き)', fontname='MS Mincho')
    plt.grid(True)
    plt.show()

# CSVファイルのパス
csv_file_path = "oneroom_data.csv"

# 計算実行(引越回数リストも戻り値に追加)
move_durations, profits, rents = process_csv(csv_file_path)

# 結果をプロット(回帰線付き)
num_cycles_list = [calculate_cycles(row['契約期間/月'], row['募集期間/月'])[0] for index, row in pd.read_csv(csv_file_path).iterrows()]
plot_regression_scatter(num_cycles_list, profits)

この記事が気に入ったらサポートをしてみませんか?