💭

LLMを使って自分の住みたい街を見つけてみた

2023/12/18に公開

はじめに

こんにちは、Ubieでアナリティクスエンジニアをやっている@matsu-ryuです。
普段は、Ubieが提供する症状検索エンジンを始めとするヘルスケア関連のデータを扱い、
「テクノロジーで人々を適切な医療に案内する」ミッション遂行のために、頑張っています。

昨今、話題であるLLM(Large language Models:大規模言語モデル)ですが、Ubieの分析業務においても、活用しています。同僚の@masa_kazamaが
https://note.com/masa_kazama/n/n0f340ab3a3d8
でUbieでのLLM活用についてまとめています。

今回は、皆さんにも身近な「住みたい街を見つける」というテーマを通じて、分析業務におけるLLMの具体的な活用方法を紹介しようと思います。

分析に利用するデータの説明

今回利用するデータは、
https://repository.kulib.kyoto-u.ac.jp/dspace/handle/2433/277225
で提供されている[LWC指標_1741市町村データ]。
(引用:http://hdl.handle.net/2433/277225
を利用します。
データには、以下の特徴量が含まれています。

'LWCI客観指数', '身体的健康平均値(客観)', '社会的健康平均値(客観)',
'精神的健康平均値(客観)', '医療・健康', '介護・福祉', '買物・飲食',
'住宅環境', '移動・交通', '空気・騒音・清潔さ','事故・犯罪', '自然災害', 
'環境共生', '自然景観', '都市景観', '遊び・娯楽', '公共空間', 
'雇用・所得','デジタル生活', '地域とのつながり', '子育て', 
'初等・中等教育', '教育環境の選択可能性', '事業創造', 
'文化・芸術','多様性'

LWC指標について、簡単に説明すると、
「Living With Comfort」の略で、私たちが住む街の快適さを数値化したもので、自分に合った街を見つける際の便利なガイドになります。
この指標は、経済活動、教育環境、医療サービス、レクリエーション、安全など、さまざまな生活領域を考慮に入れています。それぞれの領域は、さまざまなデータをもとに複合的に評価され、全体としての街の快適さを数値化します。したがって、この指標を使うことで、自分のライフスタイルや価値観に合った街を具体的に探すことが可能になります。

分析の流れ

実際に居住地を探す場合、自分の中でいくつかの優先度を検討すると思います。
今回は、

  • 生活の利便性
  • 程よい自然環境
  • 教育・子育てのしやすさ

の3つの軸で評価を進めていきたいと思います。
また、都市の特徴は上記3つだけでは表現しきれない部分もあるので、クラスター分析により都市をグループ分けします。

事前に決めた評価軸と、クラスターで分類したグループの相対的な位置関係により、住みたい街に近そうな都市グループを選択します。

最後にそのグループに属する都市から、自分の住みたい街を選択します。
各所でLLMを活用してきます!

さあ、やってみよう!

分析コードの生成には、Cursorを利用します。
https://cursor.sh/
Cursor上で、Jupyter Notebookファイルを作成し、コード生成を自然言語で依頼します。
データの読み込みを例にすると
データの読み込み

上記のように、自然言語での依頼から、コードが生成されます。
以降、必要に応じて、プロンプトと生成されたコードを明示していきます。

都市の特徴を要約する

早速1つ目のタスクにLLMを活用していきましょう
データには、30個弱の属性があるので、それを

  • 生活基盤とコミュニティ
  • 環境と健康
  • 子育てと教育
    に分類する必要があります。ここでLLMを活用していきます。
import os
from langchain.chat_models import ChatOpenAI
from langchain.schema import AIMessage, HumanMessage, SystemMessage

os.environ["OPENAI_API_KEY"]="openai apiのkey"

chat = ChatOpenAI(model_name="gpt-4-1106-preview", temperature=0) 

messages = [
    SystemMessage(content="あなたは、スマートシティの専門家です。"),
    HumanMessage(content=f"""私は移住を検討しており、地域データの分析をしようと思っています。
                 以下のデータ項目
                 - 生活基盤とコミュニティ
                 - 環境と健康
                 - 子育てと教育
                 の3つにグループ分けしてほしい。 
                 === データ {df.columns.to_list()[2:]}"""),
]

result = chat(messages)
print(result.content)

df.columns.to_list()[2:]でLLMにデータの列情報を伝えています。
LangChainを利用して、LLMに特徴量の分類を依頼してみました。
結果は、

それぞれのデータ項目を、指定された3つのグループに分類すると以下のようになります。

### 生活基盤とコミュニティ
- 買物・飲食
- 住宅環境
- 移動・交通
- 事故・犯罪
- 都市景観
- 遊び・娯楽
- 公共空間
- 雇用・所得
- デジタル生活
- 地域とのつながり
- 事業創造
- 文化・芸術
- 多様性

### 環境と健康
- 身体的健康平均値(客観)
- 社会的健康平均値(客観)
- 精神的健康平均値(客観)
- 医療・健康
- 介護・福祉
- 空気・騒音・清潔さ
- 自然災害
- 環境共生
- 自然景観

### 子育てと教育
- 子育て
- 初等・中等教育
- 教育環境の選択可能性

これらのグループ分けは、各データ項目が主に関連する分野に基づいています。
ただし、一部の項目は複数のカテゴリーにまたがる可能性があり、
その場合は最も密接に関連するグループに分類しています。
例えば、「デジタル生活」は生活基盤とコミュニティに関連するが、
環境と健康や子育てと教育にも影響を与える可能性があります。
また、「多様性」はコミュニティの健全性に寄与するが、文化・芸術とも関連が深いため、
生活基盤とコミュニティに分類しています。

上記結果から、3つの集約項目を作成するコードを生成します。

  • プロンプト
dfに以下のグルーピング項目を追加してほしい。各項目を加算してください。
### 生活基盤とコミュニティ
- 買物・飲食
- 住宅環境
・・・ (略)
### 環境と健康
- 身体的健康平均値(客観)
- 社会的健康平均値(客観)
・・・ (略)
  • 生成コード
df['生活基盤とコミュニティ'] = df[['買物・飲食', '住宅環境', '移動・交通', '事故・犯罪', '都市景観', '遊び・娯楽', '公共空間', '雇用・所得', 'デジタル生活', '地域とのつながり', '事業創造', '文化・芸術', '多様性']].sum(axis=1)
df['環境と健康'] = df[['身体的健康平均値(客観)', '社会的健康平均値(客観)', '精神的健康平均値(客観)', '医療・健康', '介護・福祉', '空気・騒音・清潔さ', '自然災害', '環境共生', '自然景観']].sum(axis=1)
df['子育てと教育'] = df[['子育て', '初等・中等教育', '教育環境の選択可能性']].sum(axis=1)

3つの項目について、他項目のスケールと合わせるため、1-100に標準化しておきます。

  • プロンプト
dfの生活基盤とコミュニティ、環境と健康、子育てと教育の値を0-100の間に標準化してほしい
  • 生成コード
from sklearn.preprocessing import MinMaxScaler

scaler = MinMaxScaler(feature_range=(0, 100))
df[['生活基盤とコミュニティ', '環境と健康', '子育てと教育']] = scaler.fit_transform(df[['生活基盤とコミュニティ', '環境と健康', '子育てと教育']])

ここまでで、都市と3つの軸のデータが作成されました。

都市が持つ26個の属性から、5つにグループ分けする

次に都市データのグループ分けを実施していきます。
ここでもCursorを利用して、コード生成を依頼してみましょう。

  • プロンプト
dfについて、クラスター分析してください。
- グループ数は5
- 利用する項目は、LWCI客観指数〜子育てと教育とコミュニティ
  • 生成コード
from sklearn.cluster import KMeans

kmeans = KMeans(n_clusters=5, random_state=0)
df['cluster'] = kmeans.fit_predict(df.iloc[:, 1:])
df.head()

これにより、都市が5つのグループに分けられました。
グループ毎の特徴の平均をとると

抜粋ですが、このようになります。それぞれのクラスター毎に都市の特徴が算出されていますが、これらのクラスターに適切な命名を行い、解釈性を上げたいと思います。
このタスクもLLMを活用してみます。
LLMにデータを渡せるようにJSONフォーマットに変換しておきます。

  • プロンプト
cluster_means_jsonについて、reset_indexした上で、JSONデータに変換してほしい
日本語項目は、変換しないでほしい。
  • 生成コード
cluster_means_json = cluster_means.reset_index().to_json(orient='records', force_ascii=False)

JSONデータのイメージ
[{"cluster":0,"LWCI客観指数":43.7324469846,"身体的健康平均値(客観)":34.6331111721,"社会的健康平均値(客観)":49.7576952586,

では、LLMに問い合わせてみましょう。

chat = ChatOpenAI(model_name="gpt-4-1106-preview", temperature=0) 
messages = [
    SystemMessage(content="あなたは、スマートシティの専門家です。"),
    HumanMessage(content=f"""
                 次のデータは、地域特性サマリーデータです。5つのクラスに分類されていいます。
                 各特徴の重みを反映して、居住地を選択しやすい、
		 特徴を要約したクラスの名前をつけてください。
                 === データ {cluster_means_json}"""),
]

result = chat(messages)
print(result.content)

LLMからの回答

このデータセットに基づいて、各クラスターに名前を付けるには、その特徴を要約し、
そのクラスターが何を代表しているかを理解する必要があります。
以下は、各クラスターの特徴を要約し、それに基づいて名前を提案する一例です。

クラスター0: 「バランスの取れたコミュニティ」
- LWCI客観指数が中程度で、全体的にバランスの取れた特徴を持つ。
- 「介護・福祉」「住宅環境」「文化・芸術」のスコアが高い。
- 「買物・飲食」「都市景観」「デジタル生活」のスコアが低い。

クラスター1: 「福祉重視のエリア」
- LWCI客観指数が低いが、「介護・福祉」のスコアが非常に高い。
- 「買物・飲食」のスコアが極端に低い。
- 「雇用・所得」「地域とのつながり」のスコアが高い。

クラスター2: 「文化的豊かさのエリア」
- LWCI客観指数が中程度。
- 「文化・芸術」のスコアが非常に高い。
- 「買物・飲食」のスコアが低い。

クラスター3: 「健康とレクリエーションのエリア」
- LWCI客観指数が中程度。
- 「遊び・娯楽」「文化・芸術」のスコアが高い。
- 「空気・騒音・清潔さ」のスコアが非常に低い。

クラスター4: 「エンターテインメントと多様性のエリア」
- LWCI客観指数が非常に高い。
- 「遊び・娯楽」「文化・芸術」のスコアが極端に高い。
- 「住宅環境」「空気・騒音・清潔さ」のスコアが低い。

これらの名前は、各クラスターの特徴を簡潔に表現するための提案です。
実際には、これらの名前を地域の実情や住民のニーズに合わせて調整することが重要です。
また、データの解釈には主観が入るため、他の専門家が異なる名前を提案する可能性もあります。

クラスター名を更新します。

  • プロンプト
dfについて、以下のようにクラスター名を更新してください。
クラスター0: 「バランスの取れたコミュニティ」
クラスター1: 「福祉重視のエリア」
クラスター2: 「文化的豊かさのエリア」
クラスター3: 「健康とレクリエーションのエリア」
クラスター4: 「エンターテインメントと多様性のエリア」
  • 生成コード
df['cluster'] = df['cluster'].map({
    0: 'バランスの取れたコミュニティ',
    1: '福祉重視のエリア',
    2: '文化的豊かさのエリア',
    3: '健康とレクリエーションのエリア',
    4: 'エンターテインメントと多様性のエリア'
})

予め決めた3つの評価軸と、5つのグループを可視化してみる

ここまでで、データは予め決めた3つの評価軸の指標と、クラスター分析から求めたグループラベルが市区町村毎にまとまっています。
データの一部を抜粋すると

となっています。
これを3Dプロットします。
※ ここでもCursorを利用してコード生成したのですが、何回か試行錯誤してまとめずらいので、可視化結果だけ載せていきます。

全体のイメージは

LLMの回答から「健康とレクリエーションのエリア」

  • LWCI客観指数が中程度。
  • 「遊び・娯楽」「文化・芸術」のスコアが高い。

がいいなと感じたので、「健康とレクリエーションのエリア」の分布を確認してみます。

環境と健康が高い位置で分布していて、子育てと教育も高い地域もありそうなので、
「健康とレクリエーションのエリア」から探してみようと思います。

「健康とレクリエーションのエリア」から住む都市をきめる

グループが確定したので、そのグループの特徴をヒートマップで確認してみましょう
まずは、都道府県レベルで居住するエリアを決めていこうと思います。

  • プロンプト
dfについて、「健康とレクリエーションのエリア」を抽出して
都道府県別に、
'LWCI客観指数', '身体的健康平均値(客観)', '社会的健康平均値(客観)', '精神的健康平均値(客観)','医療・健康', '介護・福祉', '買物・飲食', '住宅環境', '移動・交通', '空気・騒音・清潔さ', '事故・犯罪','自然災害', '環境共生', '自然景観', '都市景観', '遊び・娯楽', '公共空間', '雇用・所得', 'デジタル生活','地域とのつながり', '子育て', '初等・中等教育', '教育環境の選択可能性', '事業創造', '文化・芸術', '多様性'
の平均をとって、ヒートマップで表示してほしい

表示順
'北海道', '青森', '岩手', '宮城', '秋田', '山形', '福島', '茨城', '栃木', '群馬', '埼玉','千葉', '東京', '神奈川', '新潟', '富山', '石川', '福井', '山梨', '長野', '岐阜', '静岡','愛知', '三重', '滋賀', '京都', '大阪', '兵庫', '奈良', '和歌山', '鳥取', '島根', '岡山','広島', '山口', '徳島', '香川', '愛媛', '高知', '福岡', '佐賀', '長崎', '熊本', '大分','宮崎', '鹿児島', '沖縄'
としてください。
  • 生成コード
import seaborn as sns
import matplotlib.pyplot as plt

health_and_recreation = df.query("cluster == '健康とレクリエーションのエリア'")[['都道府県', 'LWCI客観指数', '身体的健康平均値(客観)', '社会的健康平均値(客観)', '精神的健康平均値(客観)','医療・健康', '介護・福祉', '買物・飲食', '住宅環境', '移動・交通', '空気・騒音・清潔さ', '事故・犯罪','自然災害', '環境共生', '自然景観', '都市景観', '遊び・娯楽', '公共空間', '雇用・所得', 'デジタル生活','地域とのつながり', '子育て', '初等・中等教育', '教育環境の選択可能性', '事業創造', '文化・芸術', '多様性']]

health_and_recreation_mean = health_and_recreation.groupby('都道府県').mean()

prefecture_order = ['北海道', '青森', '岩手', '宮城', '秋田', '山形', '福島', '茨城', '栃木', '群馬', '埼玉','千葉', '東京', '神奈川', '新潟', '富山', '石川', '福井', '山梨', '長野', '岐阜', '静岡','愛知', '三重', '滋賀', '京都', '大阪', '兵庫', '奈良', '和歌山', '鳥取', '島根', '岡山','広島', '山口', '徳島', '香川', '愛媛', '高知', '福岡', '佐賀', '長崎', '熊本', '大分','宮崎', '鹿児島', '沖縄']

health_and_recreation_mean = health_and_recreation_mean.loc[prefecture_order]

plt.figure(figsize=(10, 10))
sns.heatmap(health_and_recreation_mean, cmap='coolwarm')
plt.show()

可視化のイメージは

※「空気・騒音・清潔さ」については、Nullを0置換したので、その影響で全て0になっているようです。実務においては、欠損値の扱いは慎重に行う必要がありますが、今回は、無視して分析を進めます。

上記のヒートマップをみて人が判断するのは、難しそうです。
そこで、LLMに自分の住みたい都市の特徴をつたえて、お勧めの都市を聞いてみようと思います。

chat = ChatOpenAI(model_name="gpt-4-1106-preview", temperature=0)

messages = [
    SystemMessage(content="あなたは、不動産業の専門家です。"),
    HumanMessage(content=f"""次のデータは、地域特性サマリーデータです。
                 私は移住を検討しています。
                 - 子供が小さいので、初等・中等教育に力を入れている地域がいいです。
                 - 程よく都会で、自然もある地域がいいです。
                 - 若い人が多い地域がいいです。
                 - データ中の、空気・騒音・清潔さは、欠損値なので、無視してください。
                 おすすめの地域を教えてください。 
                  === データ {health_and_recreation_mean.reset_index().to_json(orient='records', force_ascii=False)}"""),
]

result = chat(messages)
print(result.content)

LLMからの回答

移住を検討されている条件を踏まえて、以下のポイントに基づいておすすめの地域を選定します。

1. 初等・中等教育に力を入れている地域
2. 自然景観が高く、都市景観もある程度整っている地域
3. 若い人が多い(社会的健康平均値が高い)地域

これらの条件を満たす地域をデータから探します。まず、初等・中等教育の評価が高い地域を見てみましょう。
次に、自然景観と都市景観のバランスが良い地域を探し、
最後に社会的健康平均値が高い地域を考慮に入れます。

データを見ると、初等・中等教育の評価が特に高いのは「島根」、「福井」、「長野」
などが挙げられます。
これらの中で自然景観と都市景観のバランスが良く、若い人が多い地域を探します。

「福井」は初等・中等教育の評価が55.45、自然景観が56.25、都市景観が40.09、
社会的健康平均値が51.49と全体的にバランスが良いです。
また、「島根」も初等・中等教育が61.04、自然景観が52.58、都市景観が37.35、
社会的健康平均値が50.08と高い評価を受けています。

これらの情報を基に、特に「福井」と「島根」がおすすめの地域として挙げられます。
どちらも教育に力を入れており、自然と都市のバランスが良く、
社会的健康平均値も高いため、若い人が多い活気のある地域と言えるでしょう。
最終的な選択は、ご家族のライフスタイルや仕事、その他の条件にも依存するため、
これらの地域をさらに詳しく調べてみることをおすすめします。

福井県が良さそう!とのこと。
では、福井県にデータを絞った上で、LLMにさらに絞ってもらいましょう
ここまでくると、コピペして人が書いたほうが早いので、コード生成は利用しません。

health_and_recreation = df.query("cluster == '健康とレクリエーションのエリア' & 都道府県=='福井'")[['都道府県市区町村', 'LWCI客観指数', '身体的健康平均値(客観)', '社会的健康平均値(客観)', '精神的健康平均値(客観)',
                            '医療・健康', '介護・福祉', '買物・飲食', '住宅環境', '移動・交通', '空気・騒音・清潔さ', '事故・犯罪',
                            '自然災害', '環境共生', '自然景観', '都市景観', '遊び・娯楽', '公共空間', '雇用・所得', 'デジタル生活',
                            '地域とのつながり', '子育て', '初等・中等教育', '教育環境の選択可能性', '事業創造', '文化・芸術', '多様性']]

health_and_recreation_mean = health_and_recreation.groupby('都道府県市区町村').mean()

messages = [
    SystemMessage(content="あなたは、不動産業の専門家です。"),
    HumanMessage(content=f"""次のデータは、地域特性サマリーデータです。
                 私は移住を検討しています。
                 - 子供が小さいので、初等・中等教育に力を入れている地域がいいです。
                 - 程よく都会で、自然もある地域がいいです。
                 - 若い人が多い地域がいいです。
                 - データ中の、空気・騒音・清潔さは、欠損値なので、無視してください。
                 おすすめの地域を教えてください。 
                  === データ {health_and_recreation_mean.reset_index().to_json(orient='records', force_ascii=False)}"""),
]

result = chat(messages)
print(result.content)

LLMからの回答

移住を検討されている条件を踏まえて、提供されたデータから最適な地域を選ぶために、
以下のポイントに注目します。

1. 初等・中等教育に力を入れている地域
2. 自然景観が高い地域(自然がある)
3. 都市景観が高い地域(程よく都会)
4. 若い人が多い地域(データから直接的な若者の割合はわかりませんが、遊び・娯楽や文化・芸術のスコアが高い地域は若者にとって魅力的な可能性があります)

データを見ると、初等・中等教育のスコアが最も高いのは「福井県勝山市」で58.4589514226です。
自然景観と都市景観のバランスも良く、遊び・娯楽のスコアも比較的高いです。
これらの点から、勝山市はご家族にとって魅力的な選択肢となる可能性があります。

ただし、若い人が多い地域については、提供されたデータからは直接的な情報が得られません。
そのため、遊び・娯楽や文化・芸術のスコアが高い地域を若者が多いと仮定しています。
勝山市は文化・芸術のスコアが64.025458237であり、
遊び・娯楽も56.3860985897となっており、
若者にとっても魅力的な地域である可能性があります。

総合的に考えると、「福井県勝山市」がおすすめの地域として挙げられます。
ただし、実際の移住を検討する際には、他の生活に関わる要素や個人の好み、
現地の雰囲気なども重要ですので、データだけでなく実際に訪れてみることをお勧めします。

ということで、福井県勝山市が選択されまし。
データだけ決めずに、「実際に見に行くこと」が大事とのアドバイスまでいただきました。
さすがLLM!

ちなみに福井県勝山市の特色を、goole検索してみると

霊峰白山を望む福井県勝山市は「恐竜のまち」として知られ、国内最大の恐竜化石発掘地を有します。 
自然豊かなこのまちには、1年を通して多くの人が訪れる福井県立恐竜博物館だけでなく、
スキーやスノーボードといった冬のアクティビティや、国史跡白山平泉寺や越前大仏、
グルメなど様々な魅力があります。

楽しそうです。
また、福井県の教育・学力について検索すると

全国学力テスト、福井県は全6科目で2位か3位 公立の小学6年と中学3年、
トップクラス維持 文部科学省は7月28日、小学6年と中学3年の全員を対象に
4月に行った2022年度全国学力・学習状況調査(全国学力テスト)の結果を公表した。

とのこと。(素晴らしい場所のようですね、福井県!)

まとめ

「自分の住みたい街を見つけみたい」という題材から、分析作業のLLM活用について具体的に説明してきました。
改めて今回のLLM活用を振り返ってみると

  • 30個弱の都市の特徴を3つのグループに分ける
  • 30個弱の特徴が似ている都市グループを5つのグループに分ける(クラスター分析結果のラベル付け)
  • グループ選択後、希望の都市の条件をLLMに伝えて、お勧めの都市を回答してもらう。
  • コード生成には、Cursorを利用

でした。
Open Interpreterのように、自然言語での指示から、分析環境の整備、コード実行、結果解釈を一気通貫で行うアプローチもありますが、現時点においては、LLMが得意なタスクを人が判断して、適切にLLMを活用するほうが、色々はかどるな、という感覚をもちました。

今後もこの領域は日進月歩で進歩していくので、キャッチアップを継続し、自身の業務生産性をバク上げできたらいいな!と思っています。

一緒に働く仲間を探しています!

Ubieでは、「テクノロジーで適切な医療に導く」ことをミッションとしています。
このミッションをデータドリブンで実現することを目指しています。
そのためには、データエンジニア、アナリティクスエンジニアの力が必要です。
そこで、私たちは一緒に働く仲間を募集しています!

興味のある方は下記JDから直接応募していただいても良いですし、一度カジュアルにお話したい方は私までX(Twitter) DMお待ちしております!
https://twitter.com/matsu_ryu0323
https://herp.careers/v1/ubiehr/Bilnrfg8U7vj
https://herp.careers/v1/ubiehr/MExdfj2UgARV

Ubie テックブログ

Discussion