ベストなお昼ご飯の組み合わせを求める（セブンイレブン編）

# モチベーション
消費税が上がるこのタイミングで、ワンコインで満足いくお昼ご飯の組み合わせを考えてみたいと思います。

※ワンコイン：税込500円
※満足いく：満腹感重視（本当は体積や重さで求めたいが、今回はカロリーで）

今回は、セブンイレブンで持ち帰り（消費税8%）を前提とします。
また、1種類の商品は1つだけしか買えないこととします。
（塩むすび×5個とかは避けたい ^^;）

# データ収集
セブンイレブンの全商品は[こちら](https://www.sej.co.jp/products/)で調べることができます。がしかし、ここにはカロリー表示が無い．．．
[ここ](https://www.sej.co.jp/products/info_allergy.html)に栄養成分情報があるが、お弁当やおにぎりの情報が無い．．．
かろうじて[ここ](https://www.sej.co.jp/i/products/anshin/)にいくつか載っているが全部ではない．．．

[FAQ](https://secure.okbiz.okwave.jp/sej/faq/show/5699?category_id=15&site_domain=default)を見ると、「使用する原材料は規格変更や調達の都合により断りなく変わります。アレルギー情報はお客様の健康に関わる重要な情報ですので、正確な情報を確認していただくため、店頭にて商品の表示をご確認のうえ、ご利用いただきたくお願いいたします。」とのことですので、カロリー等の情報も公開していない模様。

ということで、カロリーの値はググって出てきた情報（[ここ](https://www.eatsmart.jp/)が中心）も利用することにします。

残念ながらこれらすべてCSV等電子データで提供されておらず、またHTMLをスクレイピングしたくなかったので、地道に手打ちします。

※2019/09/28時点の情報で作成しています
※以下は含めません

- セブンプレミアム
- セブンプレミアムゴールド
- スイーツ
- セブンカフェ
- チルドカップドリンク
- こだわり特性弁当

※販売エリアが限られている商品がありますが、今回は全部手に入るものとしてデータを作成しました
※同じ商品が地域別に複数登録されています
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/6000fd79-40fb-8b7d-e024-2eb9e9068663.png)

で、こんな感じに出来上がりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/c0cf9fb4-78b2-b0fc-f1e8-233fffa4119d.png)

欠損値は、後で行ごと消します。

# 問題設定
今回は以下のような条件になります。

- 税込500円以下（消費税8%で計算）
- よりカロリーが高くなる組み合わせ
- 1品目1つだけ

# コーディング
さっそくPythonでコーディングしてみます。
数理最適化のモジュールは「PuLP」を使用してみます。

```python
# データの読み込み
import pandas as pd
df = pd.read_csv("se.csv", index_col=0, encoding="SHIFT-JIS")
```

作成したデータ（CSVファイル）をPandasで読み込み、DataFrameに保存します。
日本語バリバリなので、Shift-JISにエンコードしています。

```python
# 必要な情報だけ抜き出し
df_ = df[['商品名', '金額（円、税抜）', 'カロリー（kcal）']]

# 欠損値のある行は削除
df_ = df_.dropna()
```

必要な情報は金額とカロリーだけ（最後の表示で商品名も使用）なので、それだけ抜き出します。
また欠損値のある行は消します。

```python
# モデル作成
from pulp import *

# 金額
w = df_['金額（円、税抜）']

# カロリー
v = df_['カロリー（kcal）']

# 制約：税込500円
W = 500 / 1.08

r = range(len(w))

# 数理モデル（最大値取得）
m = LpProblem(sense=LpMaximize)

# 変数（あり／なし）
x = [LpVariable('x%d'%i, cat=LpBinary) for i in r]

# 目的関数（カロリーの加算）
m += lpDot(v, x)

# 制約条件（金額の加算≦税込500円）
m += lpDot(w, x) <= W
```

まず金額とカロリーだけ抜き出しています。
その後、モデルを作成します。

今回はカロリーが最大となるように数理モデルは最大値を求めるように設定します。
変数は、その品目を含めるか含めないか（0 or 1）になります。
目的関数として、カロリーを加算する式をしてします。
制約条件は、金額を加算した結果が税抜500円以下となるように指定します。

```python
# 計算
m.solve()
```

作成した条件で計算を行います。
問題が無ければ「1」が返ってきます。

```python
print('総カロリー:', value(m.objective), 'kcal')

print('組み合わせ:')
total = 0
for i in r:
   if value(x[i]) > 0.5:
      print(' ', df_['商品名'][i], '(', df_['金額（円、税抜）'][i], '円)')
      total +=  df_['金額（円、税抜）'][i]
      
print('総額:', total, '円（税抜）')
```

結果を出力します。

# 結果
```text
総カロリー: 1836.0 kcal
組み合わせ:
  北海道産じゃがいものコロッケパン ( 130 円)
  もちもちチョコブレッド ( 110 円)
  しっとりチョコチップスティックケーキ ( 120 円)
  口どけチョコのオールドファッション ( 90 円)
総額: 450 円（税抜）
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/88885b89-a661-8b80-fc7a-c125e478d67b.png)![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/e33d03d2-1a9e-dea9-c2bc-49b746e5bd1e.png)![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/6eb583e2-085c-60d0-399e-ab73c23b61a7.png)![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/fc49c52f-af14-c5b9-9b23-d2b8629a9de2.png)


とっても口の中が甘くなりそうな結果となりました。
ちょっとこれでは昼食としてはつらいので、もうちょっと絞った品目で試してみたほうがよさそうです。

***<font color="red">【チョコ、最強！ｗ】</font>***

# 感想
「PuLP」などモジュールを利用することで、簡単に組み合わせ最適化ができるって、ちょっと感動してしまいました。

# 後日談
ドーナツを削除して、再チャレンジしてみました。

```text
総カロリー: 1811.0 kcal
組み合わせ:
  もちもちチョコブレッド ( 110 円)
  しっとりチョコチップスティックケーキ ( 120 円)
  ふわふわちぎりパン（チョコクリーム） ( 120 円)
  北海道産小豆のつぶあん＆マーガリン ( 110 円)
総額: 460 円（税抜）
```

(out)
北海道産じゃがいものコロッケパン
口どけチョコのオールドファッション
(in)
ふわふわちぎりパン（チョコクリーム）
北海道産小豆のつぶあん＆マーガリン

ということで、さらに甘くなりました（苦笑）