京大生協の食堂メニューの最適解を探る

# はじめに
京都大学には「[ミールシステム](https://www.s-coop.net/service/food/meal/)」という悪魔のシステムがあります。

簡単に説明すると学食版の定期みたいなものです。
うかつにもこれに登録してしまうと雨の日も風の日も雪にも夏の暑さにも耐え、例え休日であろうと元を取るために学食へ向かう通称「ミール奴隷」となってしまいます。
（僕は少食である程度自炊もし食堂と下宿の距離がそんなに近いわけでもないのでミールシステムに対するヘイトが多少極端になってるかもしれませんが悪しからず）

一応「バランスの取れた食事を毎日とる」ことを大義名分としているみたいですが学食定期を配っただけでバランスの取れた食事が自動的にとれるようになるわけがありません。

そこで今回少しでも可哀そうなミール奴隷達を救うため食堂メニューの最適解を調べました。

# データ
去年はオンライン授業でほとんど学校に行くことが無かったので気付きませんでしたが生協がいつのまにかネットで食堂のメニューを確認出来るようにしていたようです。（chuboz？とかいうシステムを使ってるっぽい？）
http://west2-univ.jp/sp/kyoto-univ.php
ここからスクレイピングさせていただきました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/522724/ddd2c154-1861-0a70-5805-a1a4cb818569.png)
学食を買うとメニューによって上の画像のような赤、緑、黄で点数が計算されます。
今回最適解を探るうえで一食の目安を参考にメニューを最適化します。

結果の例としてカフェテリアルネのメニューの表
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/522724/341dbd0f-0880-a8c9-693f-7201dc87c56e.png)

# コード
## 方針
ソルバーとしてはPuLPとかいう非常に簡単に問題を設定して最適化してくれるのがあるっぽいのでこれを利用しようと思います。

最適化する問題としては
### 制約条件

 - 値段>550円
 - 赤の点数>2.7
 - 緑の点数>1.0
 - 黄色の点数>5.7

値段はミール一食分の値段550円を参考に、赤緑黄の点数はレシートにある目安の点数を参考に（僕が男なので男性の目安の点数を採用）条件を設定
### 問題
値段と、赤緑黄の目安の点数との差の合計を最小化する

## コード
上の表をdataframeとして読み込んでいるものとして

まず問題設定の前準備

```python
#最小化問題の設定
problem = pulp.LpProblem("Shokudo")

#注文する個数を表す変数、上限は適当に設定した
df['order_num'] = [pulp.LpVariable(f'{i}ko', 0, 100, "Integer") for i in df.index]
```

合計金額や点数の合計は要は内積で求めることが出来るので目的関数は以下の通り設定

```python
# 目的関数
    problem += pulp.lpDot(df['price'], df['order_num'])+ (
        pulp.lpDot(df['red_score'], df['order_num'])) + (
                   pulp.lpDot(df['green_score'], df['order_num'])) + (
                   pulp.lpDot(df['yellow_score'], df['order_num']))
```

上で説明したとおりに制約条件を記述していく

```python
# 制約条件
    problem += pulp.lpDot(df['red_score'], df['order_num']) >= 2.7
    problem += pulp.lpDot(df['green_score'], df['order_num']) >= 1.0
    problem += pulp.lpDot(df['yellow_score'], df['order_num']) >= 5.7
    problem += pulp.lpDot(df['price'], df['order_num']) >= 550
```

最適化の開始

```python
problem.solve()

df['result'] = df['order_num'].apply(lambda x: pulp.value(x))
print(df[['name', 'price',  "result"]])
```

# 結果
京都大学生協ルネカフェテリアのデータの結果

```
         name  price  result
12  オクラ巣ごもり玉子     88     2.0
18        味噌汁     33     2.0
23  ショコラモンブラン    220     1.0
25        大学芋     88     1.0
```


以下に各食堂でのメニュー最適解を紹介します
(画像の縮尺は適当)

## 京都大学生協ルネカフェテリア
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/522724/10b80730-27f2-041b-9e8d-d80a6a9a154f.png)
オクラ巣ごもり玉子：2杯
味噌汁：2杯
ショコラモンブラン：1杯
大学芋：1杯
合計金額
550円
赤：2.8
緑：1.0
黄：5.8

味噌汁2杯にショコラモンブランですか……
##京都大学生協中央食堂
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/522724/a372e253-8d3e-59ba-33d8-7cdd1e3f7190.png)
鶏きも煮：1杯
茄子のピリ辛胡麻風味：1杯
海老クリームコロッケ：1杯
ほうれん草塩ナムル：1杯
温泉玉子：1杯
納豆：1個
大学芋：1杯
合計金額
550円
赤：2.7
緑：1.1
黄：5.8

品数的には多いけど全部小鉢なので満足感が無いかも
##京都大学生協南部食堂
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/522724/7320a4c0-cead-c399-d09d-9405f95b42c8.png)
鶏のホワイトシチュー：1杯
海老クリームコロッケ：2杯
だし巻き：1杯
合計金額
550円
赤：3.7
緑：1.3
黄：6.6

金無いの？
##京都大学生協吉田食堂
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/522724/10beb716-5987-c517-871b-f17b6e924d11.png)

鶏から南蛮：1杯
ひじき煮：2杯
南瓜サラダ：1杯
大学芋：1杯
合計金額
550円
赤：2.7
緑：1.5
黄：5.7

久しぶりに「肉」が来た。肉があるだけで見た目の満足感が上がる
##京都大学生協中央食堂
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/522724/a19b2ef5-7154-5049-3d99-0b774f86860f.png)
ローストンカツごまソース：1杯
小松菜わさび和え：1杯
味噌汁：2杯
大学芋：1杯
合計金額
550円
赤：2.7
緑：1.0
黄：5.7

カツがあるだけでもう満足感が得られるので味噌汁が2杯あることについても気にならなくなる
##京都大学生協北部食堂
入れ忘れてました、北部食堂ファンの人ごめんなさい
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/522724/6cb3dbab-f43d-980e-f1ce-0ca0af13a167.png)
ササミチーズカツ：264円：1杯
ほうれん草：66円：1杯
きんぴらごぼう：66円：1杯
南瓜サラダ：66円：1杯
温泉玉子：44円：2杯
合計金額
550円
赤：2.9
緑：1.0
黄：5.7

さすが人気の北食、主食が無いことを除けばなんだか普通に良さそう
# 考察
大学芋がほとんどのメニューに含まれていた。これは赤緑黄のスコアがカロリーベースで計算されているためである。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/522724/1dd8d9db-5ea3-11f5-4ab8-468a923c9e1d.png)
こちらにある通り大学芋は野菜量0gにもかかわらずカロリーが高めであるため緑スコアが0.8も稼げてしまい、多くのメニューで採択されたものと考えられる。

追記：コメントによると緑の分類は決して野菜類を指しているのではなく「体の調子を良くする食材」を指しているため緑に芋も含まれるらしいです。
真にバランスのいい食事をとるには様々なパラメーターを総合的に判断する必要があるということっぽい。バランスのとれた食事って大変だ