システムとモデリング

modelica, Julia, Design Structure Matrix, SysML, 他モデリング全般について。

Juliaによる座席選択シミュレーション

Juliaで座席選択の人間心理をシミュレーションしてみた

はじめに

通勤電車に乗っていると、空席がいくつかあっても「なぜそこに座らないの?」と思うことがあります。誰かの隣が避けられていたり、端の席から埋まっていったり。実はこれ、無意識のうちに人の心理が働いているのです。

この記事では、そんな「座席選択の心理」をシミュレーションするための数理モデルを、Juliaで構築してみました。


モデルの考え方

電車の座席選びには、いくつかの心理的な傾向が見られます。以下のような要素をモデルに取り入れました:

  • 他人と距離を取りたい(端の席が人気)
  • 女子高生の隣は座りにくい(気まずさ)
  • 大柄な人の隣は狭そうで避けたい(快適さ)
  • 誰も座っていない席はちょっと怖い(観察学習)

これらを表現するために、乗客には以下のような性格パラメータを持たせます。

乗客の性格パラメータ

パラメータ 説明
sociability 社交性:他人の隣でも気にしない度合い
sensitivity 配慮性:気まずさや狭さへの敏感さ
observational 観察学習:他人の行動を参考にする傾向

座席の設定

  • 横1列に10席(Seat 1 〜 Seat 10)
  • Seat 3 に女子高生、Seat 7 に大柄な男性が座っている(固定)
  • 他の席は空席。1人ずつ順番に乗車して、どこに座るかを選びます。

実装(Julia)

ランダムに生成した乗客が、性格パラメータに応じて各席の「スコア」を計算し、softmax関数を使って着席先を選びます。

softmax関数(Julia)

function softmax(x::Vector{Float64}; beta::Float64=1.0)
    exps = exp.((x .- maximum(x)) .* beta)
    return exps ./ sum(exps)
end

スコアの差が大きいと高い確率で良い席を選び、小さいとランダム性が強くなります。


結果:座席の選ばれ方をヒートマップで可視化

100人の乗客が1人ずつ座席を選んだ結果、どの席が何回選ばれたかを集計し、ヒートマップで表示します。

heatmap(
    counts',
    c=:blues,
    xlabel="座席番号 (1–10)",
    title="座席選択頻度ヒートマップ(100人)",
    colorbar_title="回数"
)

見えてきた傾向

  • 端の席(Seat 1 や Seat 10)は人気が高め
  • 女子高生(Seat 3)や大柄な男性(Seat 7)の「隣」は避けられる傾向
  • 観察学習によって、誰も座らない席はより避けられるようになる

拡張性

このモデルは非常にシンプルですが、以下のような拡張が考えられます:

  • 2列×5の対面座席で空間配置を複雑にする
  • 通勤ラッシュやガラガラの時間帯など状況設定を変える
  • 女子高生や大柄な男性の位置をランダムにする
  • 観察学習の強さを調整して社会的影響力を分析

おわりに

「座るだけ」の行動にも、無意識の心理や社会的な気遣いが詰まっています。

こうした身近な行動をモデル化してシミュレーションすることで、行動の背景にある「見えないルール」を読み解くことができます。

技術メモ

  • 使用言語:Julia
  • 使用ライブラリ:Plots.jl, Distributions.jl
  • モデリング手法:エージェントベースモデル + softmax選択 + 観察学習

ちょっとした遊び心から始めたプロジェクトでしたが、意外と深い考察にたどり着けました。次は、座席予約システムや対面座席編もやってみたいですね。

コード全文

using Random
using Distributions   # ← Categorical分布用
using Plots
default(fontfamily="MS Gothic")

#########################
# softmax を自作
#########################
function softmax(x::Vector{Float64}; beta::Float64=1.0)
    exps = exp.((x .- maximum(x)) .* beta)  # 安定化のため最大値引く
    return exps ./ sum(exps)
end

#########################
# 型定義
#########################
struct Passenger
    name::String
    sociability::Float64
    sensitivity::Float64
    observational::Float64
end

mutable struct Seated
    label::String
    category::Symbol
end

#########################
# スコア計算
#########################
function seat_score(seats, skipped, idx, person::Passenger)
    seats[idx] !== nothing && return -Inf
    s = 0.0
    n = length(seats)

    if idx == 1 || idx == n
        s += 0.5
    end

    if (idx == 1 || seats[idx - 1] === nothing) && (idx == n || seats[idx + 1] === nothing)
        s += person.sociability
    end

    for j in (-1, 1)
        k = idx + j
        if 1 <= k <= n && seats[k] isa Seated
            nb = seats[k]
            if nb.category == :jk
                s -= 1.5 * person.sensitivity
            elseif nb.category == :big
                s -= 1.0 * person.sensitivity
            end
        end
    end

    s -= 0.05 * person.observational * skipped[idx]
    return s
end

#########################
# 着席処理(ソフトマックスで確率選択)
#########################
function seat_one!(seats, skipped, counts, person::Passenger; beta=2.0)
    scores = [seat_score(seats, skipped, i, person) for i in 1:length(seats)]
    probs = softmax(scores; beta=beta)
    sel = rand(Categorical(probs))
    seats[sel] = Seated(person.name, :normal)
    counts[sel] += 1

    for i in 1:length(seats)
        seats[i] === nothing && (skipped[i] += 1)
    end

    seats[sel] = nothing  # 降車(JK/BIG以外)
end

#########################
# 初期化
#########################
n_seats = 10
n_passengers = 100

seats = Vector{Union{Nothing, Seated}}(fill(nothing, n_seats))
skipped = zeros(Int, n_seats)
counts = zeros(Int, n_seats)

seats[3] = Seated("JK", :jk)
seats[7] = Seated("BIG", :big)

passengers = [Passenger("P$i", rand(), rand(), rand()) for i in 1:n_passengers]

#########################
# シミュレーション
#########################
for p in passengers
    seat_one!(seats, skipped, counts, p)
end

println("各席の選ばれた回数:", counts)

#########################
# ヒートマップ
#########################
heatmap(
    counts',
    c=:blues,
    xlabel="座席番号 (1–10)",
    title="座席選択頻度ヒートマップ(100人)",
    colorbar_title="回数"
)