TL;DR
- 国交省の実取引価格・SUUMO の賃料相場・e-Stat の人口需要データを Python で統合し、堺市の全駅を定量スコアリングした
- 表面利回り 18.8% という大阪府内最高水準のエリアを発見したが、それを主力推奨にしなかった
- 需要データと掛け合わせて初めて見えた「本当に買うべき駅」と「見た目は良いが避けるべき駅」の差を、コードと数字で解説する
- 「大学の町=ワンルーム需要あり」という通説がデータで否定された話も書いた
- 業者・コンサル向けの実務活用セクションあり(記事末尾)
なぜ作ったか
不動産投資を検討していると、よく「利回り○%のエリアが熱い」という話を聞く。だが、その数字がどこから来たのか、どんな物件の平均なのか、将来の需要はどうなのか――ほぼ説明がない。
業者の資料は「弊社物件の想定利回り」だし、ポータルサイトの情報は物件単位で散らばっている。
じゃあ自分でオープンデータから作ればいい。
3つのデータソースはすべて無料で公開されている。
| データ | ソース | 内容 |
|---|---|---|
| 実取引価格 | 国交省 reinfolib | 2010〜2025Q1 の実際の売買価格 |
| 賃料相場 | SUUMO スクレイピング | 現在の募集賃料(1R/1K/1DK) |
| 人口・世帯 | e-Stat 社会・人口統計体系 | 単独世帯数・未婚率・転入超過 |
システム構成
scripts/
├── scrape_suumo_sakai.py # ① SUUMO 賃料スクレイピング
├── fetch_estat_sakai_demand.py # ② e-Stat 需要データ取得
└── analyze_sakai_final_score.py # ③ 統合スコアリング(メイン)
① SUUMO スクレイピング
堺市の7区を順番にクロールして、1R/1K/1DK(または 40㎡以下)の賃料を全件収集する。
SAKAI_AREAS = [
{"name": "堺区", "code": "sakaishisakai"},
{"name": "北区", "code": "sakaishikita"},
{"name": "中区", "code": "sakaishinaka"},
{"name": "西区", "code": "sakaishinishi"},
{"name": "南区", "code": "sakaishiminami"},
# ...
]
def scrape_area_listings(area_name: str, area_code: str, max_pages: int = 8):
for page in range(1, max_pages + 1):
url = f"https://suumo.jp/chintai/osaka/sc_{area_code}/mansion/?page={page}"
resp = requests.get(url, headers=HEADERS, timeout=30)
soup = BeautifulSoup(resp.text, "lxml")
items = soup.select("div.cassetteitem")
# ...賃料・間取り・面積を正規表現で抽出
time.sleep(2) # 礼儀として必ず入れる
駅別に集計した賃料の中央値を使う(平均だと外れ値の影響を受けるため)。
② 国交省データの読み込み
reinfolib からダウンロードした zip を pandas で読み込み、間取り・面積・年代でフィルタリングする。
def load_price() -> pd.DataFrame:
files = [f for f in BASE.iterdir() if f.name.startswith("Osaka") and f.suffix == ".zip"]
dfs = []
for fpath in files:
with zipfile.ZipFile(fpath) as z:
with z.open(z.namelist()[0]) as f:
df = pd.read_csv(f, encoding="cp932")
dfs.append(df)
df = pd.concat(dfs, ignore_index=True)
# 1R/1K/1DK または 40㎡以下、2020〜2024年の取引に絞る
mask_madori = df["間取り"].isin(["1R", "1K", "1DK"])
mask_area = df["面積_数値"] <= 40
df_or = df[(mask_madori | mask_area) & df["年"].between(2020, 2024)].copy()
# 駅別に中央値価格を集計(取引3件未満は除外)
price_by_sta = df_or.groupby("最寄駅:名称").agg(
取引件数=("価格_数値", "count"),
中央値価格_万円=("価格_数値", lambda x: round(x.median() / 10000, 0)),
).reset_index()
return price_by_sta[price_by_sta["取引件数"] >= 3]
ポイントは中央値を使うこと。取引件数が少ない駅では外れ値が価格を歪める。後述するが、これが「深井問題」を早期発見するカギになった。
③ 表面利回りの計算
# 利回り = 賃料中央値 × 12カ月 ÷ 取引価格中央値
df["表面利回り_%"] = (df["賃料中央値_万円"] * 12 / df["中央値価格_万円"] * 100).round(1)
④ スコアリング
4指標を min-max 正規化して重み付き合計する。
def minmax(s: pd.Series) -> pd.Series:
mn, mx = s.min(), s.max()
if mx == mn:
return pd.Series([50.0] * len(s), index=s.index)
return ((s - mn) / (mx - mn) * 100).round(1)
df["S_利回り"] = minmax(df["表面利回り_%"]) # 重み 40%
df["S_単独増加率"] = minmax(df["単独世帯増加率_%"]) # 重み 25%
df["S_単独世帯率"] = minmax(df["単独世帯率_%"]) # 重み 20%
df["S_未婚率"] = minmax(df["未婚率_2020"]) # 重み 15%
df["総合スコア"] = (
df["S_利回り"] * 0.40 +
df["S_単独増加率"] * 0.25 +
df["S_単独世帯率"] * 0.20 +
df["S_未婚率"] * 0.15
).round(1)
なぜこの配点か? ワンルームの需要者は単身世帯なので、利回り(供給側)だけでなく、単身世帯の絶対数・成長率・将来予測(未婚率)を加味した。
結果:表面利回り18%を主力推奨にしなかった理由
全駅スコアランキング
| 駅名 | 区 | スコア | 利回り | 単独世帯増加率 | 単独世帯率 | 中央値価格 | 賃料中央値 | 平均築年数 |
|---|---|---|---|---|---|---|---|---|
| 堺 | 堺区 | 81.6 | 18.8% | +19.2% | 24.1% | 395万円 | 6.2万円 | 36年 |
| 湊 | 堺区 | 78.7 | 17.7% | +19.2% | 24.1% | 400万円 | 5.9万円 | 51年 |
| 鳳 | 西区 | 64.5 | 14.9% | +28.2% | 14.9% | 580万円 | 7.2万円 | 35年 |
| 堺東 | 堺区 | 63.5 | 11.9% | +19.2% | 24.1% | 625万円 | 6.2万円 | 24年 |
| 泉ヶ丘 | 南区 | 29.0 | 14.6% | +16.0% | 13.2% | 340万円 | 4.2万円 | 42年 |
| 深井 | 中区 | 19.1 | 3.5% | +22.4% | 13.4% | 1,800万円 | 5.3万円 | 18年 |
問題①:深井の中央値1,800万円は外れ値だった
深井の利回りは 3.5% と最低水準に見えるが、これは価格の問題だ。
取引件数:3件(最低ライン)。中央値1,800万円は他駅の3〜5倍。明らかに外れ値が含まれており、投資判断に使えるデータではない。
# 取引件数3件未満は除外しているが、3件ギリギリでも危ない
# → 実運用では5件以上を閾値にすることを推奨
price_by_sta = price_by_sta[price_by_sta["取引件数"] >= 3]
教訓:中央値を使っても取引件数が少ないと信頼性が崩れる。
問題②:堺・湊はスコア1〜2位でも「主力推奨」ではない
堺駅の利回り18.8%は大阪府内でも突出した水準だ。スコアも81.6と最高。
だが、これを主力推奨にしなかった理由がある。
平均築年数:堺36年、湊51年。
表面利回りは「賃料 ÷ 取引価格」で計算する。築古物件は取引価格が安いため利回りが高く見える。しかし実態は:
- 大規模修繕リスクが高い(給水管・外壁・エレベーター)
- 金融機関の融資期間が短くなる(築35年超は融資NGの銀行も)
- 空室時の入居付けが難しくなる
表面利回りから修繕積立・空室損・融資コストを差し引いた実質利回りは、大幅に低下する可能性がある。
これは分析ツールの限界でもある。国交省データには修繕状況や管理組合の財政情報は含まれない。スコアが高いからといって、そのまま買える物件ではない。
発見:「鳳」という穴場
スコア3位の鳳(西区)が面白い。
| 指標 | 鳳 | 堺 | 比較 |
|---|---|---|---|
| 利回り | 14.9% | 18.8% | 鳳が3.9pt低い |
| 単独世帯増加率 | +28.2% | +19.2% | 鳳が9pt高い |
| 単独世帯率 | 14.9% | 24.1% | 堺が高い |
| 平均築年数 | 35年 | 36年 | ほぼ同じ |
| 中央値価格 | 580万円 | 395万円 | 鳳がやや高い |
単独世帯増加率 +28.2% は堺市全7区中2位。需要が伸びているのに、知名度が低いため価格が割安に抑えられている。
堺市内で「利回り・需要成長・持続性」が同時に成立するエリアは、データ上では実質的に鳳のみだった。
これが「隠れ優良」と呼べる理由だ。
問題③:泉ヶ丘は「見た目は良い」が危ない
利回り14.6%、価格340万円と一見お得に見える。しかし需要側のデータを見ると:
# e-Stat 区別需要データ
区名 単独世帯増加率 人口増減率 転入超過
南区 +16.0% -6.2% -844人 ← 泉ヶ丘が属する区
南区は:
- 人口が5年で 6.2%減少
- 転出が転入を844人上回る慢性的な人口流出
価格が安いのは、市場がそのリスクを織り込んでいるからだ。利回りだけ見ると良さそうに見えるが、構造的に単身需要が弱く、長期保有するほど空室リスクが積み上がる。
未婚率を「需要の持続性」として使う
このプロジェクトで一番実感した工夫は、未婚率を代理変数として使うことだ。
単独世帯の現在数は「今の需要」を示す。しかし未婚率が高いエリアは「将来も単身者が一定数残り続ける」と推測できる。
# e-Stat から未婚率(15歳以上人口に占める未婚者割合)を取得
targets = {
"A161001_未婚者割合(15歳以上人口)": "未婚率",
# ...
}
| 区 | 未婚率 |
|---|---|
| 堺区 | 30.7% |
| 北区 | 27.7% |
| 西区 | 27.1% |
| 中区 | 26.8% |
| 東区 | 26.0% |
| 美原区 | 26.1% |
| 南区 | 24.4% |
堺区の30.7%は全区最高。単身世帯が今後も維持されやすい構造になっている。
余談:「大学の町=ワンルーム需要」は成立しなかった
分析中に予想外の発見があった。
堺市北区には中百舌鳥・なかもずエリアがある。大阪府立大学(現:大阪公立大学)のキャンパスがあり、「大学周辺=学生向けワンルーム需要が厚い」というのが一般的な通説だ。
しかし、このエリアはスコアリングの対象外になった。理由は単純で、ワンルームの実取引件数が分析に必要な最低ライン(3件)に届かなかったからだ。
# 北区(中百舌鳥・なかもず)→ 取引件数不足でスコア対象外
price_by_sta = price_by_sta[price_by_sta["取引件数"] >= 3]
北区の単独世帯増加率は+20.4%と悪くない。だが実取引が存在しないということは、「持ちたい人が少ない」か「市場として成立していない」 ことを意味する。大学があるから需要があるはずという先入観が、データによって否定された形だ。
「大学の町だから買い」という業者トークを鵜呑みにする前に、実取引データを確認する価値がある。
このアプローチの限界と今後
正直に書く。このスコアには以下の限界がある。
データの制約
- 需要データは区単位のため、同一区内の駅はすべて同じ需要スコアになる
- 国交省データに物件の管理状態・修繕履歴は含まれない
- 取引件数が少ない駅(3〜5件)はノイズが大きい
モデルの制約
- 重み(利回り40%・需要60%)は経験則による設定であり、最適化はしていない
- min-max正規化は外れ値に引っ張られやすい
今後やりたいこと
- 取引件数の閾値を5件以上に引き上げ
- 築年別のリスクプレミアム加算(築35年超はペナルティ)
- 近隣市(大阪市・堺市周辺)への横展開
- reinfolib API が承認後、データ取得を自動化
まとめ
分析結果の結論(投資判断ではなく、あくまでデータの話として):
| エリア | 評価 | 理由 |
|---|---|---|
| 鳳(西区) | 再現性の高い選択肢 | 利回り・需要成長・単身構造の3点が揃う |
| 堺東(堺区) | 初回投資向き | 築浅で融資が通りやすく、需要密度が高い |
| 堺・湊(堺区) | 上級者向け | 利回りは最高だが築古リスクを十分理解した上で |
| 泉ヶ丘(南区) | 要注意 | 人口流出が構造的で長期需要に課題 |
| 深井(中区) | データ不足 | 取引件数3件では判断不可 |
「利回りが高いから買う」ではなく、「需要が持続するかどうか」を軸にすると、見え方がガラリと変わる。
オープンデータは揃っている。使うかどうかだけの問題だ。
コード・データ
分析に使ったスクリプト・データの構成は以下。
docs/不動産/
├── scripts/
│ ├── scrape_suumo_sakai.py # SUUMO スクレイピング
│ ├── fetch_estat_sakai_demand.py # e-Stat API 取得
│ └── analyze_sakai_final_score.py # 統合スコアリング
├── data/
│ ├── suumo_sakai/ # 賃料データ
│ └── estat_sakai_demand/ # 人口・世帯データ
└── output/
└── sakai_final_score.csv # 最終スコア
国交省の取引価格データは reinfolib から手動DL。e-Stat は API 経由で取得(無料、要登録)。
業者・コンサル向け:この分析の実務活用について
本分析で使ったデータ・手法は、そのまま顧客提案資料として利用可能な水準を想定して設計している。
具体的には以下の用途に対応できる:
- エリア提案資料:顧客へのエリア比較レポートとして使用可
- 投資判断の裏付け:「なぜそのエリアか」を数字で説明するための根拠資料
- 営業トークの補強:スコアリング根拠を言語化した説明フォーマット付き
また、本記事は堺市を対象としているが、同じ手法は他の政令市・市区町村にも横展開できる。特定エリア・沿線での個別分析に興味がある方は、コメントまたはプロフィールのリンクからご連絡ください。
本記事はデータ分析の記録であり、特定の投資を推奨するものではありません。投資判断はご自身の責任で行ってください。
