概要
Pythonの数理最適化ライブラリであるPuLPを使用する練習として、多くの人がやっている完全栄養マクドナルド食問題(一日に必要な栄養素を全て取れる、マクドナルドの商品の組み合わせを探す問題)にチャレンジしてみました。
参考
参考にしたのは、以下の記事です。
問題設定
1日に必要な栄養素を取りつつ、塩分や脂肪は上限を超えないようにして、できるだけ低カロリーな食事になることを目指します。
1日に必要な栄養素の基準としては、以下のグリコのサイトを参考にしました。
年齢や性別で推奨量・目安量が違うので、今回は女性・30~49(歳)の値を基準値とします。また、脂質については総エネルギーに占める割合で目標量が決まりますが、今回は上限として45gを設定しました。以下が、今回の分析における栄養素の条件になります。
栄養名 | 下限 | 上限 |
---|---|---|
タンパク質(g) | 50.0 | |
脂質(g) | 45.0 | |
カリウム(mg) | 2000.0 | |
カルシウム(mg) | 650.0 | |
リン(mg) | 800.0 | 3000.0 |
鉄(mg) | 10.5 | 40.0 |
ビタミンA(μg) | 700.0 | 2700.0 |
ビタミンB1(mg) | 1.1 | |
ビタミンB2(mg) | 1.2 | |
ナイアシン(mg) | 12.0 | 250.0 |
ビタミンC(mg) | 100.0 | |
食物繊維(g) | 18.0 | |
食塩相当量(g) | 6.5 |
また、1回の食事で同じものを2つ食べることになるのは辛いので、同じ商品は最大3つまでとします。一応ですが、たくさん食べることもできないので、1日に食べる食事の合計の重さは2kg以下に設定します。まー、これは特に影響がないでしょう。
マクドナルドの商品の栄養価は、マクドナルドのwebページで公開されています。
以上の条件を反映した整数計画問題を作成して、PuLPを使いカロリーの低い組み合わせを探します。
結果
分析に使用したコードは最後に紹介するとして、分析の結果は以下のようになりました。
個数_えだまめコーン = 3.0
個数_りんご&クリーム = 1.0
個数_ケチャップ = 1.0
個数_サイドサラダ = 3.0
個数_シャカチキ_チェダーチーズ味シーズニング = 1.0
個数_シャカチキ_レッドペッパー味シーズニング = 2.0
個数_プチパンケーキ = 1.0
個数_プレミアムローストコーヒー(S) = 3.0
個数_マックグリドル_ベーコンエッグ = 1.0
個数_マックシェイク_チョコレート(M) = 1.0
個数_マックシェイク_チョコレート(S) = 1.0
個数_野菜生活100(S) = 1.0
総カロリー:1604.0kcal
シャカチキ用のパウダーはあるが、シャカチキはないという悲しい結果。。。
総カロリーが約1600キロカロリーなので、ダイエット食としては悪くないですね。
とりあえず朝食、昼食、夕食に分けてみると、
朝食
- プチパンケーキ
- りんご&クリーム
- えだまめコーン
- シャカチキ_レッドペッパー味シーズニング
- サイドサラダ
- プレミアムローストコーヒー(S)
- マックシェイク_チョコレート(S)
昼食
- マックグリドル_ベーコンエッグ
- ケチャップ
- えだまめコーン
- シャカチキ_チェダーチーズ味シーズニング
- サイドサラダ
- プレミアムローストコーヒー(S)
- マックシェイク_チョコレート(M)
夕食
- えだまめコーン
- シャカチキ_レッドペッパー味シーズニング
- サイドサラダ
- プレミアムローストコーヒー(S)
- 野菜生活100(S)
ダイエット中なので、夜は炭水化物を控えるということですね。
シャカチキ用のパウダーは、えだまめコーンあたりにかけて食べましょう。
摂取する栄養は以下の通りです。
タンパク質:52.1
脂質:43.3
炭水化物:251.6
ナトリウム:2610.0
カリウム:2915.0
カルシウム:816.0
リン:1345.0
鉄:10.5
ビタミンA:770.0
ビタミンB1:1.3
ビタミンB2:1.4
ナイアシン:12.0
ビタミンC:102.0
コレステロール:247.0
食物繊維:18.4
食塩相当量:6.5
コード
計算に使ったコードは以下になります。
import pandas as pd
import numpy as np
import pulp
df = pd.read_csv("マクドナルド栄養価.csv",index_col=0)
# 欠損値を0埋め
df = df.fillna(0)
# カロリーが0の商品を除く
df = df[df['エネルギー']>0]
# 栄養値の上限・下限の入力
df_max_min = pd.read_csv("1日分の栄養.csv",index_col=0)
# 変数の設定
df["個数"] = pulp.LpVariable.dicts('個数',df.index,lowBound=0,upBound=3,cat='Integer').values()
# 最小化問題
problem = pulp.LpProblem('完全栄養マクドナルド問題',sense=pulp.LpMinimize)
# 目的変数の設定
problem.setObjective(pulp.lpDot(df["エネルギー"],df["個数"]))
# 拘束条件
for nutrition in df_max_min.index:
if not np.isnan(df_max_min.loc[nutrition,"max"]):
problem.addConstraint(pulp.lpDot(df[nutrition],df["個数"])<=df_max_min.loc[nutrition,"max"])
if not np.isnan(df_max_min.loc[nutrition,"min"]):
problem.addConstraint(pulp.lpDot(df[nutrition],df["個数"])>=df_max_min.loc[nutrition,"min"])
# 重量に条件
problem.addConstraint(pulp.lpDot(df["製品重量"],df["個数"])<=2000)
# 問題を解く
status = problem.solve()
print(pulp.LpStatus[problem.status])
# 答えの表示
for v in problem.variables():
if v.varValue != 0:
print(f'{v.name} = {v.varValue}')
print(f"総カロリー:{pulp.value(problem.objective)}kcal")
for nutrition in df_max_min.index:
print(nutrition + ":" + str(round(pulp.lpDot(df[nutrition], df["個数"]).value(),ndigits=1)))