{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# ๐ŸŽฏ [MOV01] ๋ฐ์ดํ„ฐ ๊ธฐ๋ฐ˜ ์ˆ˜์†ก ์†Œ์š”์˜ˆ์ธก Tool\n", "### Google Colab ์‹ค์Šต ํ™˜๊ฒฝ (์ˆ˜์†กยท๋ฌผ๋ฅ˜ยท์ฐฝ๊ณ  / Level 3)\n", "\n", "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/edunlab/milicontent2/blob/main/uploads/MOV01/MOV01_solution.ipynb)\n", "\n", "---\n", "\n", "## ๐Ÿ“Œ 1. ํ”„๋กœ์ ํŠธ ๊ฐœ์š” ๋ฐ ๋ฐฐ๊ฒฝ\n", "\n", "| ํ•ญ๋ชฉ | ๋‚ด์šฉ |\n", "| :--- | :--- |\n", "| **๊ณผ์ • ๋ถ„๋ฅ˜** | ์ˆ˜์†กยท๋ฌผ๋ฅ˜ยท์ฐฝ๊ณ  (Level 3) |\n", "| **ํ•™์Šต ์†Œ์š” ์‹œ๊ฐ„** | 10๊ฐœ ๋ฏธ์…˜ (5์ผ / ์ด 28.5์‹œ๊ฐ„) |\n", "| **ํ™˜๊ฒฝ ์‚ฌ์–‘** | Python 3.10+, numpy, pandas, matplotlib, scipy, scikit-learn |\n", "\n", "### ๐Ÿ“– ํ”„๋กœ์ ํŠธ ๋ชฉํ‘œ\n", "์ˆ˜์†ก ์†Œ์š”๋ฅผ ๋ฐ์ดํ„ฐ๋กœ ์˜ˆ์ธกํ•ด ๊ตฐ์ˆ˜ ์ˆ˜์†ก๊ณ„ํš์„ ์„ธ์šฐ๋Š” ์ผ์€ ๊ตฌ๊ฐ„๋ณ„ ํ†ต๊ณผ ์šฉ๋Ÿ‰๊ณผ ๋ณ‘๋ชฉ์„ ํ•จ๊ป˜ ๋ณด์•„์•ผ ์ •ํ™•ํ•ด์ง„๋‹ค. ๋ณธ ํ”„๋กœ์ ํŠธ๋Š” ์ˆ˜์†ก ์†Œ์š”์˜ˆ์ธก์—์„œ ์˜ˆ์ธก ๋Œ€์ƒ ์ •์˜, ์‹œ๊ณ„์—ดยทํ”ผ์ฒ˜ ๊ตฌ์„ฑ, ๊ธฐ์ค€๋ชจ๋ธ ๋น„๊ต, ์„ค๋ช…๊ฐ€๋Šฅ์„ฑ, ์—…๋ฌด ์ž„๊ณ„๊ฐ’, ์‹ค์  ๋Œ€์กฐ๋ฅผ ์ตํžˆ๋„๋ก ๊ต์œก์šฉ ๊ฐ€์ƒ ๋…ธ์„ ๋ง ์ž๋ฃŒ๋ฅผ ์ฝ”๋“œ๋กœ ๋งŒ๋“ค์–ด ๊ตฌ๊ฐ„๋ณ„ ์ˆ˜์†ก ์†Œ์š”์™€ ์ง€์ฒด๋ฅผ ์˜ˆ์ธกํ•œ๋‹ค. ํ•™์Šต์ž๋Š” ๋ณ‘๋ชฉ ๋ฐ์ดํ„ฐ ์ •์˜, ์ง€์ฒด ์ „์ฒ˜๋ฆฌ, BPR ๊ธฐ์ค€ ๋ชจ๋ธ, ์ตœ๋Œ€์œ ๋Ÿ‰ ๊ธฐ๋ฐ˜ ๋ณ‘๋ชฉ ์‹๋ณ„๊ณผ ํ”ผ์ฒ˜ ๋ชจ๋ธ, ์šฐํšŒ ์†Œ์š”ยท์œ ๋ฅ˜ ์‚ฐ์ถœ๊ณผ ์•™์ƒ๋ธ” ๋ชจ๋ธ, ํ˜ผ์žก๋„ ๋Œ€์‹œ๋ณด๋“œ์™€ ์š”์ธ ์„ค๋ช…, ์ž‘์ „๊ตฐ์ˆ˜๊ด€ ์Šน์ธ(HITL)๊ณผ ์„ ํ˜•๊ณ„ํš ์ตœ์ ํ™”, 3๊ฐœ ๊ตฌ๊ฐ„ ๋™์‹œ ์ฐจ๋‹จ ์ŠคํŠธ๋ ˆ์Šค ์‹œํ—˜, AAR ์žฌ๋ณด์ •๊ณผ ROI, ์žฌํ•™์Šต ํŒŒ์ดํ”„๋ผ์ธ ํ†ตํ•ฉ์„ ์ฐจ๋ก€๋กœ ์ˆ˜ํ–‰ํ•œ๋‹ค. ์ตœ์ข… ๋ชฉํ‘œ๋Š” ๊ตฌ๊ฐ„๋ณ„ ์†Œ์š”ยท์ง€์ฒด๋ฅผ ์˜ˆ์ธกํ•ด ๋ณ‘๋ชฉ์„ ์‚ฌ์ „์— ์ฐพ๊ณ  ์šฐํšŒ ๋ฌผ๋Ÿ‰ ๋ฐฐ๋ถ„์•ˆ์„ ๋งŒ๋“ค๋˜, ์šฐํšŒ ๋ช…๋ น์˜ ํ•˜๋‹ฌ ์—ฌ๋ถ€๋Š” ๋ฐ˜๋“œ์‹œ ์‚ฌ๋žŒ์ด ๊ฒฐ์ •ํ•˜๋Š” ์ˆ˜์†ก ์†Œ์š”์˜ˆ์ธก ๋„๊ตฌ๋ฅผ ์™„์„ฑํ•˜๋Š” ๊ฒƒ์ด๋‹ค.\n", "\n", "> **โš ๏ธ [๊ต์œก์šฉ ๊ฐ€์ƒ ๋ฐ์ดํ„ฐ์…‹ ์›์น™]**\n", "> - ๋ณธ ๋…ธํŠธ๋ถ์˜ ๋ชจ๋“  ๋ฐ์ดํ„ฐ(์ œ์›, ๋กœ๊ทธ, ์‹œ๋‚˜๋ฆฌ์˜ค ๋“ฑ)๋Š” ๋ณด์•ˆ ๊ทœ์ •์„ ์ค€์ˆ˜ํ•˜์—ฌ **์ฝ”๋“œ ๋‚ด ๋‚œ์ˆ˜ ๊ธฐ๋ฐ˜ ๊ฐ€์ƒ ๋ฐ์ดํ„ฐ๋กœ ์ƒ์„ฑ**๋ฉ๋‹ˆ๋‹ค.\n", "> - ์‹ค์ œ ๋ถ€๋Œ€ ์œ„์น˜, ์ž‘์ „ ์ธ๊ฐ€, ์‹ค์ œ ์ „์žฅ ์ •๋ณด, ๊ตฐ์‚ฌ ๊ธฐ๋ฐ€ ๋ฐ์ดํ„ฐ๋Š” ์ผ์ฒด ํฌํ•จ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.\n", "\n", "---\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# ============================================================================\n", "# [Google Colab ํ™˜๊ฒฝ ์„ค์ •] ํ•„์ˆ˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์„ค์น˜ ๋ฐ ์ž„ํฌํŠธ\n", "# ============================================================================\n", "!pip install -q numpy pandas matplotlib scipy scikit-learn\n", "\n", "import os\n", "import sys\n", "import time\n", "import math\n", "import json\n", "import re\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "import warnings\n", "warnings.filterwarnings('ignore')\n", "\n", "# ๊ทธ๋ž˜ํ”„ ์˜๋ฌธ ํฐํŠธ ๊ธฐ๋ณธ ์„ค์ • (ํ™˜๊ฒฝ ๋…๋ฆฝ์„ฑ ๋ณด์žฅ)\n", "plt.rcParams['font.sans-serif'] = 'DejaVu Sans'\n", "plt.rcParams['axes.unicode_minus'] = False\n", "\n", "print(f\"์‹คํ–‰ ํ™˜๊ฒฝ ์ค€๋น„ ์™„๋ฃŒ (Python {sys.version.split()[0]})\")\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 01. ์ „์‹œ ์ฃผ์š” ๋ณด๊ธ‰๋กœ(MSR) ๊ต์ฐจ๋กœ, ๋ณ‘๋ชฉ ๊ตฌ๊ฐ„(๋ณ‘๋ชฉ ๋‹ค๋ฆฌ/ํ„ฐ๋„) ๋ฐ ํ†ต๊ณผ ๋Šฅ๋ ฅ ๋ฐ์ดํ„ฐ ์ •์˜\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 1.5์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "์ฃผ์š” ๋ณด๊ธ‰๋กœ(MSR) ๊ต์ฐจ๋กœ ๋ฐ ๋ณ‘๋ชฉ ๊ตฌ๊ฐ„ ํ†ต๊ณผ ๋Šฅ๋ ฅ ๋ฐ์ดํ„ฐ๋ฅผ ์ •์˜ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **๊ฐ€์ƒ ๋…ธ์„ ๋ง์˜ ๊ต์ฐจ๋กœยท๋ณ‘๋ชฉ(๊ต๋Ÿ‰ยทํ„ฐ๋„) ๊ตฌ๊ฐ„๊ณผ ํ†ต๊ณผ ๋Šฅ๋ ฅ ๋ฐ์ดํ„ฐ๋ฅผ ์ •์˜ํ•˜๊ณ , ์ˆ˜์†ก ์†Œ์š”์˜ˆ์ธก ์—…๋ฌด ํ”„๋กœ์„ธ์Šค ๋งต๊ณผ ํ”ผ์ฒ˜ ๋ฐ์ดํ„ฐ ์‚ฌ์ „์„ ๋งŒ๋“ ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **๊ตฌ๊ฐ„ ์ฝ”๋“œ, ์œ ํ˜•, ์—ฐ์žฅ, ํ”ผํฌ์‹œ๊ฐ„ ํ†ต๊ณผ ์šฉ๋Ÿ‰, ๊ณ„ํš ๋ฌผ๋Ÿ‰, ์‹ค์ œ ํ†ต๊ณผ๋Ÿ‰, ๋น„๊ณ„ํš ์ •๋น„ยท๊ธด๊ธ‰ ๋ถˆ์ถœยท์šฐ์ฒœ ์—ฌ๋ถ€, ์ง€์ฒด ์‹œ๊ฐ„์˜ ํ•„๋“œ์™€ ํ—ˆ์šฉ๊ฐ’์„ ์ • ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `docs/mov01_logistics_domain_spec.md ๋ฐ ๋ฐ์ดํ„ฐ ๋”•์…”๋„ˆ๋ฆฌ` ํ˜•ํƒœ์˜ ๊ฒฐ๊ณผ๋ฌผ์„ ์™„์„ฑํ•œ๋‹ค.\n", "\n", "### ๐Ÿ” ํ‰๊ฐ€ ๋ฐ ๊ฒ€์ฆ ๊ธฐ์ค€ (PASS ์กฐ๊ฑด)\n", "#### ๋ฏธ์…˜ ํŒ์ •\n", "\n", "**ํ‰๊ฐ€ ๊ตฌ๋ถ„:** ํ•„์ˆ˜\n", "\n", "**ํ•ต์‹ฌ ํ‰๊ฐ€์˜์—ญ:** ๊ตฐ์ˆ˜ ์‹ค๋ฌด ์—…๋ฌด ํ”„๋กœ์„ธ์Šค์™€์˜ ์ผ์น˜์„ฑ ๋ฐ ๋ถ„์„ ๋ชฉํ‘œ์˜ ๋ช…ํ™•์„ฑ\n", "\n", "#### ๋ฏธ์…˜ PASS ์กฐ๊ฑด\n", "\n", "- ์ •๋Ÿ‰ ๊ธฐ์ค€: ์—…๋ฌด ํ”„๋กœ์„ธ์Šค ๋งต ์ž‘์„ฑ ์™„๋ฃŒ, ํ•„์š” ํ”ผ์ฒ˜(Feature) ๋ชฉ๋ก 20๊ฐœ ์ด์ƒ ์ •์˜\n", "\n", "\n", "- ์ง„ํ–‰ ๊ธฐ์ค€: ๋„๋ฉ”์ธ ๋ถ„์„์„œ ๋ฐ ๋ฐ์ดํ„ฐ ์š”๊ตฌ๋ช…์„ธ์„œ ์Šน์ธ\n", "\n", "\n", "- ๊ฒ€์ฆ ๋ฐฉ๋ฒ•: ๊ตฐ์ˆ˜ ๋‹ด๋‹น ์‹ค๋ฌด์ž ์ธํ„ฐ๋ทฐ ๋ฐ ๊ธฐ์กด ์ „์‚ฐ์ฒด๊ณ„ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ ๋Œ€์กฐ\n", "\n", "\n", "#### ์ฑ„์ ์ž ํŒ์ • ๋ฉ”๋ชจ\n", "\n", "> ๋‹จ์ˆœ ์ฝ”๋“œ ์‹คํ–‰ ์—ฌ๋ถ€๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ถœ๋ ฅ๋œ ์ˆ˜์น˜์™€ ๊ทผ๊ฑฐ ์ž๋ฃŒ๊ฐ€ ๊ตฐ ์‹ค๋ฌด ์š”๊ตฌ์‚ฌํ•ญ์— ๋ถ€ํ•ฉํ•˜๋Š”์ง€ ๋ฉด๋ฐ€ํžˆ ํ™•์ธํ•œ๋‹ค.\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\"\"\"๊ฐ€์ƒ ๋…ธ์„ ๋ง์˜ ๊ต์ฐจ๋กœยท๋ณ‘๋ชฉ(๊ต๋Ÿ‰ยทํ„ฐ๋„) ๊ตฌ๊ฐ„๊ณผ ํ†ต๊ณผ ๋Šฅ๋ ฅ ๋ฐ์ดํ„ฐ๋ฅผ ์ •์˜ํ•˜๊ณ , ์ˆ˜์†ก ์†Œ์š”์˜ˆ์ธก ์—…๋ฌด ํ”„๋กœ์„ธ์Šค ๋งต๊ณผ 20๊ฐœ ์ด์ƒ์˜ ํ”ผ์ฒ˜ ๋ฐ์ดํ„ฐ ์‚ฌ์ „์„ ๋งŒ๋“ค์–ด ์‹ค์ œ ๋ฐ์ดํ„ฐ ์ปฌ๋Ÿผ๊ณผ ๋Œ€์กฐํ•œ๋‹ค.\"\"\"\n", "import re\n", "import numpy as np\n", "import pandas as pd\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ƒ ๋…ธ์„ ๋งยท๊ตฌ๊ฐ„ ํ†ต๊ณผ๋Ÿ‰ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ๋„๋กœยท์ง€๋ช…ยท๋ถ€๋Œ€ ์—†์Œ)\n", "# ๊ฐ€์ƒ ์ถœ๋ฐœ์ง€(SRC)์—์„œ ๊ฐ€์ƒ ๋ชฉ์ ์ง€(DST)๊นŒ์ง€ 3๊ฐœ ์ถ•์„ (AยทBยทC)๊ณผ ์—ฐ๊ฒฐ๋กœ 2๊ฐœ, ๊ตฌ๊ฐ„ 10๊ฐœ. 150์ผ์น˜ ํ”ผํฌ์‹œ๊ฐ„ ํ†ต๊ณผ๋Ÿ‰ยท์ง€์ฒด ์‹œ๊ฐ„์„ ๋งŒ๋“ ๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "SEGS = pd.DataFrame([\n", " (\"A1\", \"SRC\", \"J1\", 12, 130, \"์ผ๋ฐ˜\"), (\"A2\", \"J1\", \"J2\", 8, 70, \"๊ต๋Ÿ‰\"), (\"A3\", \"J2\", \"DST\", 10, 140, \"๊ต์ฐจ๋กœ\"),\n", " (\"B1\", \"SRC\", \"J3\", 15, 100, \"์ผ๋ฐ˜\"), (\"B2\", \"J3\", \"J2\", 9, 80, \"ํ„ฐ๋„\"), (\"B3\", \"J3\", \"DST\", 14, 95, \"์ผ๋ฐ˜\"),\n", " (\"C1\", \"SRC\", \"J4\", 18, 70, \"์ผ๋ฐ˜\"), (\"C2\", \"J4\", \"DST\", 20, 75, \"๊ต๋Ÿ‰\"),\n", " (\"X1\", \"J1\", \"J3\", 7, 50, \"๊ต์ฐจ๋กœ\"), (\"X2\", \"J4\", \"J3\", 10, 60, \"์ผ๋ฐ˜\")],\n", " columns=[\"seg\", \"u\", \"v\", \"km\", \"cap\", \"kind\"]) # cap = ํ”ผํฌ์‹œ๊ฐ„ ํ†ต๊ณผ ์šฉ๋Ÿ‰(๋Œ€/์‹œ, ๊ต์œก์šฉ ๊ฐ€์ •๊ฐ’)\n", "KSPD = {\"์ผ๋ฐ˜\": 55, \"๊ต๋Ÿ‰\": 40, \"ํ„ฐ๋„\": 45, \"๊ต์ฐจ๋กœ\": 45} # ๊ตฌ๊ฐ„ ์œ ํ˜•๋ณ„ ์ž์œ  ์ฃผํ–‰ ์†๋„(km/h, ๊ฐ€์ •๊ฐ’)\n", "SEGS[\"t0\"] = SEGS[\"km\"] / SEGS[\"kind\"].map(KSPD) * 60 # ์ž์œ  ์ฃผํ–‰ ์‹œ๊ฐ„(๋ถ„)\n", "UTIL = dict(A1=.78, A2=.85, A3=.66, B1=.70, B2=.72, B3=.64, C1=.55, C2=.58, X1=.45, X2=.40) # ํ‰์‹œ ์ด์šฉ๋ฅ \n", "\n", "\n", "def bpr(t0, v, cap, a=0.15, b=4.0):\n", " \"\"\"BPR ์ง€์ฒด ํ•จ์ˆ˜(๊ตํ†ต๊ณตํ•™์˜ ํ‘œ์ค€ํ˜•): ํ†ต๊ณผ๋Ÿ‰/์šฉ๋Ÿ‰ ๋น„๊ฐ€ ์ปค์งˆ์ˆ˜๋ก ์ง€์ฒด๊ฐ€ ๊ธ‰์ฆํ•œ๋‹ค. (๋ถ„)\"\"\"\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 1] ์ „์‹œ ์ฃผ์š” ๋ณด๊ธ‰๋กœ(MSR) ๊ต์ฐจ๋กœ, ๋ณ‘๋ชฉ ๊ตฌ๊ฐ„(๋ณ‘๋ชฉ ๋‹ค๋ฆฌ/ํ„ฐ๋„) ๋ฐ ํ†ต๊ณผ ๋Šฅ๋ ฅ ๋ฐ์ดํ„ฐ ์ •์˜ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def make_data(seed=11, days=150):\n", " \"\"\"๊ตฌ๊ฐ„ x ์ผ์ž ํŒจ๋„. v=์‹ค์ œ ํ”ผํฌ ํ†ต๊ณผ๋Ÿ‰, plan_v=์ˆ˜์†ก๊ณ„ํš ์ˆ˜๋Ÿ‰(๊ธด๊ธ‰ ๋ถˆ์ถœ ๋ฏธ๋ฐ˜์˜), delay=์‹ค์ œ ์ง€์ฒด(๋ถ„).\"\"\"\n", " rng = np.random.default_rng(seed)\n", " d_idx = np.arange(days)\n", " lvl = np.array([1.0, 1.08, 1.04, 1.10, 1.02, 0.82, 0.78])[d_idx % 7] * (1 + 0.0012 * d_idx)\n", " lvl = lvl * np.where((d_idx >= 100) & (d_idx < 110), 1.30, 1.0) # ์ˆ˜์†ก๋Ÿ‰ ํญ์ฆ ๊ธฐ๊ฐ„(๊ฐ€์ƒ)\n", " prev, rows = {s: 0.0 for s in SEGS[\"seg\"]}, []\n", " for d in d_idx:\n", " rain = bool(rng.random() < 0.18)\n", " for r in SEGS.itertuples():\n", " maint, urgent = bool(rng.random() < 0.06), bool(rng.random() < 0.05) # ๋น„๊ณ„ํš ์ •๋น„ / ๊ธด๊ธ‰ ๋ถˆ์ถœ\n", " v = r.cap * UTIL[r.seg] * lvl[d] * (1.25 if urgent else 1.0) * rng.normal(1, 0.05)\n", " cap_eff = r.cap * (0.65 if maint else 1.0) * (0.92 if rain else 1.0)\n", " delay = max(r.t0 * 0.2 * (v / cap_eff) ** 3.5 + 0.25 * prev[r.seg] + rng.normal(0, 0.4), 0.0)\n", " prev[r.seg] = delay\n", " rows.append(dict(day=int(d), dow=int(d % 7), seg=r.seg, kind=r.kind, km=r.km, cap=r.cap, t0=r.t0,\n", " plan_v=v / (1.25 if urgent else 1.0) * rng.normal(1, 0.04), v=v, maint=int(maint), urgent=int(urgent),\n", " rain=int(rain), cap_eff=cap_eff, delay=delay))\n", " df = pd.DataFrame(rows)\n", " df[\"transit\"] = df[\"t0\"] * (1 + 0.08 * df[\"rain\"]) + df[\"delay\"]\n", " return df\n", "\n", "\n", "def mape(y, p):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 1] ์ „์‹œ ์ฃผ์š” ๋ณด๊ธ‰๋กœ(MSR) ๊ต์ฐจ๋กœ, ๋ณ‘๋ชฉ ๊ตฌ๊ฐ„(๋ณ‘๋ชฉ ๋‹ค๋ฆฌ/ํ„ฐ๋„) ๋ฐ ํ†ต๊ณผ ๋Šฅ๋ ฅ ๋ฐ์ดํ„ฐ ์ •์˜ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def mae(y, p):\n", " return float(np.mean(np.abs(np.asarray(y, float) - np.asarray(p, float))))\n", "# ---------------------------------------------------------------------------\n", "# ---------------------------------------------------------------------------\n", "# ์ง€์ฒด ์˜ˆ์ธก์šฉ ํŒŒ์ƒ ํ”ผ์ฒ˜ ์ƒ์„ฑ(๋‹น์ผ ์˜ค์ „ ์‹œ์ ์— ์•Œ ์ˆ˜ ์žˆ๋Š” ์ •๋ณด๋งŒ ์‚ฌ์šฉ: ๊ณ„ํš ๋ฌผ๋Ÿ‰, ๊ณต์ง€ ์ •๋น„, ๊ธฐ์ƒ ์˜ˆ๋ณด, ์ „์ผยท์ „์ฃผ ์‹ค์ )\n", "# ---------------------------------------------------------------------------\n", "FEATS = [\"plan_v\", \"cap\", \"cap_eff\", \"t0\", \"km\", \"maint\", \"rain\", \"util\", \"util_eff\", \"util_eff4\", \"bpr_base\", \"lag1_delay\", \"lag7_delay\",\n", " \"roll7_v\", \"surge_ratio\", \"is_weekend\", \"dow_sin\", \"dow_cos\", \"k_๊ต๋Ÿ‰\", \"k_ํ„ฐ๋„\", \"k_๊ต์ฐจ๋กœ\"]\n", "\n", "\n", "def add_features(df):\n", " d = df.sort_values([\"seg\", \"day\"]).copy()\n", " g = d.groupby(\"seg\")\n", " d[\"lag1_delay\"] = g[\"delay\"].shift(1)\n", " d[\"lag7_delay\"] = g[\"delay\"].shift(7)\n", " d[\"roll7_v\"] = g[\"v\"].transform(lambda s: s.shift(1).rolling(7).mean())\n", " d[\"util\"] = d[\"plan_v\"] / d[\"cap\"]\n", " d[\"util_eff\"] = d[\"plan_v\"] / d[\"cap_eff\"]\n", " d[\"util_eff4\"] = d[\"util_eff\"] ** 4\n", " d[\"bpr_base\"] = bpr(d[\"t0\"], d[\"plan_v\"], d[\"cap\"])\n", " d[\"surge_ratio\"] = d[\"plan_v\"] / d[\"roll7_v\"]\n", " d[\"is_weekend\"] = (d[\"dow\"] >= 5).astype(int)\n", " d[\"dow_sin\"], d[\"dow_cos\"] = np.sin(2 * np.pi * d[\"dow\"] / 7), np.cos(2 * np.pi * d[\"dow\"] / 7)\n", " for k in (\"๊ต๋Ÿ‰\", \"ํ„ฐ๋„\", \"๊ต์ฐจ๋กœ\"):\n", " d[\"k_\" + k] = (d[\"kind\"] == k).astype(int)\n", " return d.dropna().sort_values([\"day\", \"seg\"]).reset_index(drop=True)\n", "# ---------------------------------------------------------------------------\n", "\n", "# 1) ๋ฐ์ดํ„ฐ ์‚ฌ์ „: ์›์ฒœ ์ปฌ๋Ÿผ + ํŒŒ์ƒ ํ”ผ์ฒ˜ (์ด๋ฆ„, ๊ตฌ๋ถ„, ์ž๋ฃŒํ˜•, ํ—ˆ์šฉ ๋ฒ”์œ„/๊ฐ’, ์„ค๋ช…)\n", "DICT = [\n", " (\"seg\", \"์›์ฒœ\", \"str\", r\"^[ABCX]\\d$\", \"๊ตฌ๊ฐ„ ์ฝ”๋“œ(๊ฐ€์ƒ ๋…ธ์„ ๋ง)\"), (\"kind\", \"์›์ฒœ\", \"str\", \"์ผ๋ฐ˜|๊ต๋Ÿ‰|ํ„ฐ๋„|๊ต์ฐจ๋กœ\", \"๊ตฌ๊ฐ„ ์œ ํ˜•(๋ณ‘๋ชฉ ํ›„๋ณด)\"),\n", " (\"km\", \"์›์ฒœ\", \"float\", \"1~50\", \"๊ตฌ๊ฐ„ ์—ฐ์žฅ(km)\"), (\"cap\", \"์›์ฒœ\", \"float\", \"10~300\", \"ํ”ผํฌ์‹œ๊ฐ„ ํ†ต๊ณผ ์šฉ๋Ÿ‰(๋Œ€/์‹œ)\"),\n", " (\"t0\", \"์›์ฒœ\", \"float\", \"1~120\", \"์ž์œ  ์ฃผํ–‰ ์‹œ๊ฐ„(๋ถ„)\"), (\"day\", \"์›์ฒœ\", \"int\", \"0~\", \"๊ด€์ธก ์ผ์ž ๋ฒˆํ˜ธ\"),\n", " (\"dow\", \"์›์ฒœ\", \"int\", \"0~6\", \"์š”์ผ ๋ฒˆํ˜ธ\"), (\"plan_v\", \"์›์ฒœ\", \"float\", \"0~\", \"์ˆ˜์†ก๊ณ„ํš ํ”ผํฌ ๋ฌผ๋Ÿ‰(๋Œ€/์‹œ)\"),\n", " (\"v\", \"์›์ฒœ\", \"float\", \"0~\", \"์‹ค์ œ ํ”ผํฌ ํ†ต๊ณผ๋Ÿ‰(๋Œ€/์‹œ)\"), (\"maint\", \"์›์ฒœ\", \"int\", \"0|1\", \"๋น„๊ณ„ํš ์ •๋น„ ์—ฌ๋ถ€(ํ†ต๊ณผ ์šฉ๋Ÿ‰ ๊ฐ์†Œ)\"),\n", " (\"urgent\", \"์›์ฒœ\", \"int\", \"0|1\", \"๊ธด๊ธ‰ ๋ถˆ์ถœ ์—ฌ๋ถ€(๋ฌผ๋Ÿ‰ ์ฆ๊ฐ€)\"), (\"rain\", \"์›์ฒœ\", \"int\", \"0|1\", \"์šฐ์ฒœ ์—ฌ๋ถ€\"),\n", " (\"cap_eff\", \"์›์ฒœ\", \"float\", \"0~cap\", \"์ •๋น„ยท๊ธฐ์ƒ ๋ฐ˜์˜ ์œ ํšจ ์šฉ๋Ÿ‰\"), (\"delay\", \"๋ชฉํ‘œ\", \"float\", \"0~\", \"๊ตฌ๊ฐ„ ์ง€์ฒด ์‹œ๊ฐ„(๋ถ„)\"),\n", " (\"transit\", \"๋ชฉํ‘œ\", \"float\", \"t0~\", \"๊ตฌ๊ฐ„ ํ†ต๊ณผ์‹œ๊ฐ„(๋ถ„)\"),\n", " (\"util\", \"ํŒŒ์ƒ\", \"float\", \"0~2\", \"๊ณ„ํš๋ฌผ๋Ÿ‰/๊ณต์นญ์šฉ๋Ÿ‰\"), (\"util_eff\", \"ํŒŒ์ƒ\", \"float\", \"0~3\", \"๊ณ„ํš๋ฌผ๋Ÿ‰/์œ ํšจ์šฉ๋Ÿ‰\"),\n", " (\"util_eff4\", \"ํŒŒ์ƒ\", \"float\", \"0~\", \"์œ ํšจ ์ด์šฉ๋ฅ  4์ œ๊ณฑ(ํ˜ผ์žก ๋น„์„ ํ˜•)\"), (\"bpr_base\", \"ํŒŒ์ƒ\", \"float\", \"0~\", \"BPR ๊ธฐ์ค€ ์ง€์ฒด(๋ถ„)\"),\n", " (\"lag1_delay\", \"ํŒŒ์ƒ\", \"float\", \"0~\", \"์ „์ผ ์ง€์ฒด\"), (\"lag7_delay\", \"ํŒŒ์ƒ\", \"float\", \"0~\", \"์ „์ฃผ ๋™์š”์ผ ์ง€์ฒด\"),\n", " (\"roll7_v\", \"ํŒŒ์ƒ\", \"float\", \"0~\", \"์ตœ๊ทผ 7์ผ ํ‰๊ท  ํ†ต๊ณผ๋Ÿ‰\"), (\"surge_ratio\", \"ํŒŒ์ƒ\", \"float\", \"0~\", \"๊ณ„ํš๋ฌผ๋Ÿ‰/์ตœ๊ทผ ํ‰๊ท (ํญ์ฆ ์ง€ํ‘œ)\"),\n", " (\"is_weekend\", \"ํŒŒ์ƒ\", \"int\", \"0|1\", \"์ฃผ๋ง ์—ฌ๋ถ€\"), (\"dow_sin\", \"ํŒŒ์ƒ\", \"float\", \"-1~1\", \"์š”์ผ ์ˆœํ™˜(์‚ฌ์ธ)\"),\n", " (\"dow_cos\", \"ํŒŒ์ƒ\", \"float\", \"-1~1\", \"์š”์ผ ์ˆœํ™˜(์ฝ”์‚ฌ์ธ)\"), (\"k_๊ต๋Ÿ‰\", \"ํŒŒ์ƒ\", \"int\", \"0|1\", \"๊ต๋Ÿ‰ ๊ตฌ๊ฐ„ ๋”๋ฏธ\"),\n", " (\"k_ํ„ฐ๋„\", \"ํŒŒ์ƒ\", \"int\", \"0|1\", \"ํ„ฐ๋„ ๊ตฌ๊ฐ„ ๋”๋ฏธ\"), (\"k_๊ต์ฐจ๋กœ\", \"ํŒŒ์ƒ\", \"int\", \"0|1\", \"๊ต์ฐจ๋กœ ๊ตฌ๊ฐ„ ๋”๋ฏธ\")]\n", "dd = pd.DataFrame(DICT, columns=[\"ํ•„๋“œ\", \"๊ตฌ๋ถ„\", \"ํ˜•\", \"ํ—ˆ์šฉ๊ฐ’\", \"์„ค๋ช…\"])\n", "print(f\"[1] ๋ฐ์ดํ„ฐ ์‚ฌ์ „ {len(dd)}๊ฐœ ํ•„๋“œ (์›์ฒœ {int((dd.๊ตฌ๋ถ„ == '์›์ฒœ').sum())}, ๋ชฉํ‘œ {int((dd.๊ตฌ๋ถ„ == '๋ชฉํ‘œ').sum())}, ํŒŒ์ƒ {int((dd.๊ตฌ๋ถ„ == 'ํŒŒ์ƒ').sum())})\")\n", "\n", "# 2) ์‹ค์ œ ์ƒ์„ฑ ๋ฐ์ดํ„ฐ์™€ ๋Œ€์กฐ: ์‚ฌ์ „์— ์—†๋Š” ์ปฌ๋Ÿผ / ์‚ฌ์ „์—๋งŒ ์žˆ๋Š” ํ•„๋“œ\n", "df = make_data()\n", "feat = add_features(df)\n", "miss = sorted(set(feat.columns) - set(dd[\"ํ•„๋“œ\"]))\n", "extra = sorted(set(dd[\"ํ•„๋“œ\"]) - set(feat.columns))\n", "print(f\"[2] ๋ฐ์ดํ„ฐ ์‚ฌ์ „ ๋Œ€์กฐ: ์‚ฌ์ „ ๋ˆ„๋ฝ ์ปฌ๋Ÿผ {miss or '์—†์Œ'}, ๋ฐ์ดํ„ฐ์— ์—†๋Š” ํ•„๋“œ {extra or '์—†์Œ'}\")\n", "fe = dd[dd[\"๊ตฌ๋ถ„\"] != \"๋ชฉํ‘œ\"]\n", "print(f\" ์˜ˆ์ธก ํ”ผ์ฒ˜ ํ›„๋ณด(์›์ฒœ+ํŒŒ์ƒ ์ค‘ ๋ชฉํ‘œ ์ œ์™ธ) {len(fe)}๊ฐœ -> ๋ชฉํ‘œ(20๊ฐœ ์ด์ƒ) {'๋‹ฌ์„ฑ' if len(fe) >= 20 else '๋ฏธ๋‹ฌ'}\")\n", "bad_rng = {\"v\": int((feat.v < 0).sum()), \"cap_eff\": int((feat.cap_eff > feat.cap).sum()), \"util_eff\": int((feat.util_eff > 3).sum())}\n", "print(\" ํ—ˆ์šฉ ๋ฒ”์œ„ ์œ„๋ฐ˜ ๊ฑด์ˆ˜:\", bad_rng)\n", "\n", "# 3) ๊ตฌ๊ฐ„ ๋งˆ์Šคํ„ฐ: ๋ณ‘๋ชฉ ํ›„๋ณด(๊ต๋Ÿ‰ยทํ„ฐ๋„ยท๊ต์ฐจ๋กœ)์™€ ํ†ต๊ณผ ๋Šฅ๋ ฅ\n", "seg = SEGS.copy()\n", "seg[\"๋ณ‘๋ชฉํ›„๋ณด\"] = np.where(seg[\"kind\"].isin([\"๊ต๋Ÿ‰\", \"ํ„ฐ๋„\", \"๊ต์ฐจ๋กœ\"]), \"Y\", \"\")\n", "seg[\"ํ‰์‹œ ์ด์šฉ๋ฅ \"] = seg[\"seg\"].map(UTIL)\n", "print(\"\\n[3] ๊ตฌ๊ฐ„ ๋งˆ์Šคํ„ฐ(๊ฐ€์ƒ ๋…ธ์„ ๋ง, ๊ต์œก์šฉ ๊ฐ€์ •๊ฐ’)\")\n", "print(seg[[\"seg\", \"u\", \"v\", \"km\", \"kind\", \"cap\", \"t0\", \"ํ‰์‹œ ์ด์šฉ๋ฅ \", \"๋ณ‘๋ชฉํ›„๋ณด\"]].round(2).to_string(index=False))\n", "\n", "# 4) ์ˆ˜์†ก ์†Œ์š”์˜ˆ์ธก ์—…๋ฌด ํ”„๋กœ์„ธ์Šค ๋งต(๋‹จ๊ณ„, ๋‹ด๋‹น, ์ž…๋ ฅ ๋ฐ์ดํ„ฐ, ์‚ฐ์ถœ, ํ˜„ํ–‰ ์†Œ์š” ๋ถ„)\n", "proc = [(\"์ˆ˜์†ก ์†Œ์š” ์ ‘์ˆ˜\", \"์ˆ˜์†ก๊ณ„ํš์žฅ๊ต\", \"๋ถ€๋Œ€๋ณ„ ์š”์ฒญ ์„œ์‹\", \"์ผ์ผ ์ˆ˜์†ก ์†Œ์š”ํ‘œ\", 30), (\"๊ตฌ๊ฐ„ ํ†ต๊ณผ๋Ÿ‰ ์ง‘๊ณ„\", \"์ˆ˜์†กํ†ต์ œ์†Œ\", \"๊ตฌ๊ฐ„ ํ†ต๊ณผ ๋ณด๊ณ \", \"plan_v, v\", 40),\n", " (\"์ •๋น„ยท๊ธฐ์ƒ ์ •๋ณด ์ทจํ•ฉ\", \"ํ†ต์ œ์†Œ/๊ธฐ์ƒ๋ฐ˜\", \"์ •๋น„ ๊ณต์ง€, ์˜ˆ๋ณด\", \"maint, rain\", 25), (\"ํ†ต๊ณผ ์šฉ๋Ÿ‰ ์‚ฐ์ •\", \"์ˆ˜์†กํ†ต์ œ์†Œ\", \"๊ตฌ๊ฐ„ ๋งˆ์Šคํ„ฐ\", \"cap, cap_eff\", 20),\n", " (\"์ง€์ฒด ์˜ˆ์ธก\", \"๋ถ„์„ ๋‹ด๋‹น\", \"ํ”ผ์ฒ˜ ํ…Œ์ด๋ธ”\", \"delay ์˜ˆ์ธก\", 45), (\"๋ณ‘๋ชฉ ํŒ์ •\", \"๋ถ„์„ ๋‹ด๋‹น\", \"์˜ˆ์ธก ์ง€์ฒด, ์ด์šฉ๋ฅ \", \"๋ณ‘๋ชฉ ๋ชฉ๋ก\", 20),\n", " (\"์šฐํšŒ ๊ฒ€ํ† ยท์Šน์ธ\", \"์ž‘์ „๊ตฐ์ˆ˜๊ด€\", \"์šฐํšŒ ํ›„๋ณด, ์ถ”๊ฐ€ ์†Œ์š”\", \"์šฐํšŒ ๋ช…๋ น(์Šน์ธ)\", 30), (\"๋ช…๋ น ํ•˜๋‹ฌยท์šดํ–‰\", \"์ˆ˜์†กํ†ต์ œ์†Œ\", \"์Šน์ธ๋œ ๋ช…๋ น\", \"์šดํ–‰ ์ด๋ฒคํŠธ\", 15),\n", " (\"์‹ค์  ๋Œ€์กฐยท๋ณด์ •\", \"๋ถ„์„ ๋‹ด๋‹น\", \"์˜ˆ์ธก ๋Œ€ ์‹ค์ \", \"์˜ค์ฐจ ๋ณด๊ณ \", 35)]\n", "pm = pd.DataFrame(proc, columns=[\"๋‹จ๊ณ„\", \"๋‹ด๋‹น\", \"์ž…๋ ฅ\", \"์‚ฐ์ถœ\", \"ํ˜„ํ–‰(๋ถ„)\"])\n", "pm.index = range(1, len(pm) + 1)\n", "print(f\"\\n[4] ์—…๋ฌด ํ”„๋กœ์„ธ์Šค ๋งต {len(pm)}๋‹จ๊ณ„, ํ˜„ํ–‰ ํ•ฉ๊ณ„ {pm['ํ˜„ํ–‰(๋ถ„)'].sum()}๋ถ„ (๊ฐ€์žฅ ๊ธด ๋‹จ๊ณ„: {pm.loc[pm['ํ˜„ํ–‰(๋ถ„)'].idxmax(), '๋‹จ๊ณ„']})\")\n", "print(pm.to_string())\n", "print(\"\\nํ”„๋กœ์„ธ์Šค ๋งต ์ž‘์„ฑ ์™„๋ฃŒ(9๋‹จ๊ณ„), ํ”ผ์ฒ˜ ๋ชฉ๋ก 20๊ฐœ ์ด์ƒ -> ๋ชฉํ‘œ\", \"๋‹ฌ์„ฑ\" if len(pm) >= 5 and len(fe) >= 20 else \"๋ฏธ๋‹ฌ\")\n", "print(\"๋ชจ๋“  ๋…ธ์„ ยท๊ตฌ๊ฐ„ยท์ˆ˜์น˜๋Š” ๊ต์œก์šฉ ๊ฐ€์ƒ ์ž๋ฃŒ์ด๋ฉฐ ์‹ค์ œ ๋ณด๊ธ‰๋กœยท์ง€๋ช…ยท๋ถ€๋Œ€์™€ ๋ฌด๊ด€ํ•˜๋‹ค.\")\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 01 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[1] ๋ฐ์ดํ„ฐ ์‚ฌ์ „ 29๊ฐœ ํ•„๋“œ (์›์ฒœ 13, ๋ชฉํ‘œ 2, ํŒŒ์ƒ 14)\n", "[2] ๋ฐ์ดํ„ฐ ์‚ฌ์ „ ๋Œ€์กฐ: ์‚ฌ์ „ ๋ˆ„๋ฝ ์ปฌ๋Ÿผ ์—†์Œ, ๋ฐ์ดํ„ฐ์— ์—†๋Š” ํ•„๋“œ ์—†์Œ\n", " ์˜ˆ์ธก ํ”ผ์ฒ˜ ํ›„๋ณด(์›์ฒœ+ํŒŒ์ƒ ์ค‘ ๋ชฉํ‘œ ์ œ์™ธ) 27๊ฐœ -> ๋ชฉํ‘œ(20๊ฐœ ์ด์ƒ) ๋‹ฌ์„ฑ\n", " ํ—ˆ์šฉ ๋ฒ”์œ„ ์œ„๋ฐ˜ ๊ฑด์ˆ˜: {'v': 0, 'cap_eff': 0, 'util_eff': 0}\n", "\n", "[3] ๊ตฌ๊ฐ„ ๋งˆ์Šคํ„ฐ(๊ฐ€์ƒ ๋…ธ์„ ๋ง, ๊ต์œก์šฉ ๊ฐ€์ •๊ฐ’)\n", "seg u v km kind cap t0 ํ‰์‹œ ์ด์šฉ๋ฅ  ๋ณ‘๋ชฉํ›„๋ณด\n", " A1 SRC J1 12 ์ผ๋ฐ˜ 130 13.09 0.78 \n", " A2 J1 J2 8 ๊ต๋Ÿ‰ 70 12.00 0.85 Y\n", " A3 J2 DST 10 ๊ต์ฐจ๋กœ 140 13.33 0.66 Y\n", " B1 SRC J3 15 ์ผ๋ฐ˜ 100 16.36 0.70 \n", " B2 J3 J2 9 ํ„ฐ๋„ 80 12.00 0.72 Y\n", " B3 J3 DST 14 ์ผ๋ฐ˜ 95 15.27 0.64 \n", " C1 SRC J4 18 ์ผ๋ฐ˜ 70 19.64 0.55 \n", " C2 J4 DST 20 ๊ต๋Ÿ‰ 75 30.00 0.58 Y\n", " X1 J1 J3 7 ๊ต์ฐจ๋กœ 50 9.33 0.45 Y\n", " X2 J4 J3 10 ์ผ๋ฐ˜ 60 10.91 0.40 \n", "\n", "[4] ์—…๋ฌด ํ”„๋กœ์„ธ์Šค ๋งต 9๋‹จ๊ณ„, ํ˜„ํ–‰ ํ•ฉ๊ณ„ 260๋ถ„ (๊ฐ€์žฅ ๊ธด ๋‹จ๊ณ„: ์ง€์ฒด ์˜ˆ์ธก)\n", " ๋‹จ๊ณ„ ๋‹ด๋‹น ์ž…๋ ฅ ์‚ฐ์ถœ ํ˜„ํ–‰(๋ถ„)\n", "1 ์ˆ˜์†ก ์†Œ์š” ์ ‘์ˆ˜ ์ˆ˜์†ก๊ณ„ํš์žฅ๊ต ๋ถ€๋Œ€๋ณ„ ์š”์ฒญ ์„œ์‹ ์ผ์ผ ์ˆ˜์†ก ์†Œ์š”ํ‘œ 30\n", "2 ๊ตฌ๊ฐ„ ํ†ต๊ณผ๋Ÿ‰ ์ง‘๊ณ„ ์ˆ˜์†กํ†ต์ œ์†Œ ๊ตฌ๊ฐ„ ํ†ต๊ณผ ๋ณด๊ณ  plan_v, v 40\n", "3 ์ •๋น„ยท๊ธฐ์ƒ ์ •๋ณด ์ทจํ•ฉ ํ†ต์ œ์†Œ/๊ธฐ์ƒ๋ฐ˜ ์ •๋น„ ๊ณต์ง€, ์˜ˆ๋ณด maint, rain 25\n", "4 ํ†ต๊ณผ ์šฉ๋Ÿ‰ ์‚ฐ์ • ์ˆ˜์†กํ†ต์ œ์†Œ ๊ตฌ๊ฐ„ ๋งˆ์Šคํ„ฐ cap, cap_eff 20\n", "5 ์ง€์ฒด ์˜ˆ์ธก ๋ถ„์„ ๋‹ด๋‹น ํ”ผ์ฒ˜ ํ…Œ์ด๋ธ” delay ์˜ˆ์ธก 45\n", "6 ๋ณ‘๋ชฉ ํŒ์ • ๋ถ„์„ ๋‹ด๋‹น ์˜ˆ์ธก ์ง€์ฒด, ์ด์šฉ๋ฅ  ๋ณ‘๋ชฉ ๋ชฉ๋ก 20\n", "7 ์šฐํšŒ ๊ฒ€ํ† ยท์Šน์ธ ์ž‘์ „๊ตฐ์ˆ˜๊ด€ ์šฐํšŒ ํ›„๋ณด, ์ถ”๊ฐ€ ์†Œ์š” ์šฐํšŒ ๋ช…๋ น(์Šน์ธ) 30\n", "8 ๋ช…๋ น ํ•˜๋‹ฌยท์šดํ–‰ ์ˆ˜์†กํ†ต์ œ์†Œ ์Šน์ธ๋œ ๋ช…๋ น ์šดํ–‰ ์ด๋ฒคํŠธ 15\n", "9 ์‹ค์  ๋Œ€์กฐยท๋ณด์ • ๋ถ„์„ ๋‹ด๋‹น ์˜ˆ์ธก ๋Œ€ ์‹ค์  ์˜ค์ฐจ ๋ณด๊ณ  35\n", "\n", "ํ”„๋กœ์„ธ์Šค ๋งต ์ž‘์„ฑ ์™„๋ฃŒ(9๋‹จ๊ณ„), ํ”ผ์ฒ˜ ๋ชฉ๋ก 20๊ฐœ ์ด์ƒ -> ๋ชฉํ‘œ ๋‹ฌ์„ฑ\n", "๋ชจ๋“  ๋…ธ์„ ยท๊ตฌ๊ฐ„ยท์ˆ˜์น˜๋Š” ๊ต์œก์šฉ ๊ฐ€์ƒ ์ž๋ฃŒ์ด๋ฉฐ ์‹ค์ œ ๋ณด๊ธ‰๋กœยท์ง€๋ช…ยท๋ถ€๋Œ€์™€ ๋ฌด๊ด€ํ•˜๋‹ค.\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 02. ์ˆ˜์†ก ์ˆ˜๋Ÿ‰ ํญ์ฆ์— ๋”ฐ๋ฅธ ๊ตฌ๊ฐ„๋ณ„ ๋ณ‘๋ชฉ ์ง€์ฒด ์‹œ๊ฐ„(Delay) ๊ณ„์‚ฐ ์ „์ฒ˜๋ฆฌ ๋ชจ๋“ˆ ๊ตฌํ˜„\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 2.0์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "์ˆ˜์†ก๋Ÿ‰ ํญ์ฆ์— ๋”ฐ๋ฅธ ๊ตฌ๊ฐ„๋ณ„ ์ง€์ฒด ์‹œ๊ฐ„ ๊ณ„์‚ฐ ์ „์ฒ˜๋ฆฌ ๋ชจ๋“ˆ์„ ๊ตฌํ˜„ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **์ˆ˜์†ก๋Ÿ‰ ํญ์ฆ์— ๋”ฐ๋ฅธ ๊ตฌ๊ฐ„๋ณ„ ์ง€์ฒด ์‹œ๊ฐ„ ๊ณ„์‚ฐ์„ ์œ„ํ•ด ์˜ค์—ผ๋œ ๊ตฌ๊ฐ„ ํ†ต๊ณผ ๊ธฐ๋ก์„ ์ •์ œํ•˜๋Š” ์ „์ฒ˜๋ฆฌ ๋ชจ๋“ˆ์„ ๊ตฌํ˜„ํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **๊ฒฐ์ธก, ์Œ์ˆ˜, ๋‹จ์œ„ ์˜ค๋ฅ˜, ํ‘œ๊ธฐ ์˜ค๋ฅ˜, ๋ฏธ๋“ฑ๋ก ๊ตฌ๊ฐ„, ์šฉ๋Ÿ‰ 0, ์ค‘๋ณต์„ ์ฃผ์ž…ํ•œ ๊ฐ€์ƒ ๊ธฐ๋ก์— ์ •์ œ ๊ทœ์น™ 10์ข…์„ ์ˆœ์„œ๋Œ€๋กœ ์ ์šฉ ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `src/etl/mov01_data_cleansing.py ๋ฐ ์ •์ œ ํ’ˆ์งˆ ๋ณด๊ณ ์„œ` ํ˜•ํƒœ์˜ ๊ฒฐ๊ณผ๋ฌผ์„ ์™„์„ฑํ•œ๋‹ค.\n", "\n", "### ๐Ÿ” ํ‰๊ฐ€ ๋ฐ ๊ฒ€์ฆ ๊ธฐ์ค€ (PASS ์กฐ๊ฑด)\n", "#### ๋ฏธ์…˜ ํŒ์ •\n", "\n", "**ํ‰๊ฐ€ ๊ตฌ๋ถ„:** ํ•„์ˆ˜\n", "\n", "**ํ•ต์‹ฌ ํ‰๊ฐ€์˜์—ญ:** ๊ตฐ์ˆ˜ ํŠน์ˆ˜ ์˜ˆ์™ธ(๋น„๊ณ„ํš ์ •๋น„, ๊ธด๊ธ‰ ๋ถˆ์ถœ ๋“ฑ) ๋ฐ์ดํ„ฐ ๋ณด์กด์˜ ํƒ€๋‹น์„ฑ\n", "\n", "#### ๋ฏธ์…˜ PASS ์กฐ๊ฑด\n", "\n", "- ์ •๋Ÿ‰ ๊ธฐ์ค€: ๋ฐ์ดํ„ฐ ์ •์ œ ๊ทœ์น™ 10์ข… ์ ์šฉ, ๋ฐ์ดํ„ฐ ์ •ํ•ฉ์„ฑ ๊ฒ€์ฆ ํ†ต๊ณผ์œจ 100%\n", "\n", "\n", "- ์ง„ํ–‰ ๊ธฐ์ค€: ETL ํŒŒ์ดํ”„๋ผ์ธ ๊ตฌํ˜„ ๋ฐ ์ •์ œ๋œ ๋งˆ์Šคํ„ฐ ๋ฐ์ดํ„ฐ์…‹ ๊ตฌ์ถ•\n", "\n", "\n", "- ๊ฒ€์ฆ ๋ฐฉ๋ฒ•: ์ „ํ›„ ๋ฐ์ดํ„ฐ ํ†ต๊ณ„๋Ÿ‰(๊ธฐ์ˆ ํ†ต๊ณ„) ๋น„๊ต ๋ฐ ๋ฌด๊ฒฐ์„ฑ ๊ฒ€์ฆ\n", "\n", "\n", "#### ์ฑ„์ ์ž ํŒ์ • ๋ฉ”๋ชจ\n", "\n", "> ๋‹จ์ˆœ ์ฝ”๋“œ ์‹คํ–‰ ์—ฌ๋ถ€๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ถœ๋ ฅ๋œ ์ˆ˜์น˜์™€ ๊ทผ๊ฑฐ ์ž๋ฃŒ๊ฐ€ ๊ตฐ ์‹ค๋ฌด ์š”๊ตฌ์‚ฌํ•ญ์— ๋ถ€ํ•ฉํ•˜๋Š”์ง€ ๋ฉด๋ฐ€ํžˆ ํ™•์ธํ•œ๋‹ค.\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\"\"\"์ˆ˜์†ก๋Ÿ‰ ํญ์ฆ์— ๋”ฐ๋ฅธ ๊ตฌ๊ฐ„๋ณ„ ์ง€์ฒด ์‹œ๊ฐ„ ๊ณ„์‚ฐ ์ „์ฒ˜๋ฆฌ ๋ชจ๋“ˆ: ์˜ค์—ผ๋œ ๊ตฌ๊ฐ„ ํ†ต๊ณผ ๊ธฐ๋ก์— ์ •์ œ ๊ทœ์น™ 10์ข…์„ ์ ์šฉํ•˜๊ณ  ๋น„๊ณ„ํš ์ •๋น„ยท๊ธด๊ธ‰ ๋ถˆ์ถœ ๊ฐ™์€ ๊ตฐ์ˆ˜ ํŠน์ˆ˜ ์˜ˆ์™ธ๋Š” ์‚ญ์ œํ•˜์ง€ ์•Š๊ณ  ๋ณด์กดํ•˜๋ฉฐ, ์ •ํ•ฉ์„ฑ ๊ฒ€์ฆ๊ณผ ์ „ํ›„ ํ†ต๊ณ„๋ฅผ ๋น„๊ตํ•œ๋‹ค.\"\"\"\n", "import numpy as np\n", "import pandas as pd\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ƒ ๋…ธ์„ ๋งยท๊ตฌ๊ฐ„ ํ†ต๊ณผ๋Ÿ‰ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ๋„๋กœยท์ง€๋ช…ยท๋ถ€๋Œ€ ์—†์Œ)\n", "# ๊ฐ€์ƒ ์ถœ๋ฐœ์ง€(SRC)์—์„œ ๊ฐ€์ƒ ๋ชฉ์ ์ง€(DST)๊นŒ์ง€ 3๊ฐœ ์ถ•์„ (AยทBยทC)๊ณผ ์—ฐ๊ฒฐ๋กœ 2๊ฐœ, ๊ตฌ๊ฐ„ 10๊ฐœ. 150์ผ์น˜ ํ”ผํฌ์‹œ๊ฐ„ ํ†ต๊ณผ๋Ÿ‰ยท์ง€์ฒด ์‹œ๊ฐ„์„ ๋งŒ๋“ ๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "SEGS = pd.DataFrame([\n", " (\"A1\", \"SRC\", \"J1\", 12, 130, \"์ผ๋ฐ˜\"), (\"A2\", \"J1\", \"J2\", 8, 70, \"๊ต๋Ÿ‰\"), (\"A3\", \"J2\", \"DST\", 10, 140, \"๊ต์ฐจ๋กœ\"),\n", " (\"B1\", \"SRC\", \"J3\", 15, 100, \"์ผ๋ฐ˜\"), (\"B2\", \"J3\", \"J2\", 9, 80, \"ํ„ฐ๋„\"), (\"B3\", \"J3\", \"DST\", 14, 95, \"์ผ๋ฐ˜\"),\n", " (\"C1\", \"SRC\", \"J4\", 18, 70, \"์ผ๋ฐ˜\"), (\"C2\", \"J4\", \"DST\", 20, 75, \"๊ต๋Ÿ‰\"),\n", " (\"X1\", \"J1\", \"J3\", 7, 50, \"๊ต์ฐจ๋กœ\"), (\"X2\", \"J4\", \"J3\", 10, 60, \"์ผ๋ฐ˜\")],\n", " columns=[\"seg\", \"u\", \"v\", \"km\", \"cap\", \"kind\"]) # cap = ํ”ผํฌ์‹œ๊ฐ„ ํ†ต๊ณผ ์šฉ๋Ÿ‰(๋Œ€/์‹œ, ๊ต์œก์šฉ ๊ฐ€์ •๊ฐ’)\n", "KSPD = {\"์ผ๋ฐ˜\": 55, \"๊ต๋Ÿ‰\": 40, \"ํ„ฐ๋„\": 45, \"๊ต์ฐจ๋กœ\": 45} # ๊ตฌ๊ฐ„ ์œ ํ˜•๋ณ„ ์ž์œ  ์ฃผํ–‰ ์†๋„(km/h, ๊ฐ€์ •๊ฐ’)\n", "SEGS[\"t0\"] = SEGS[\"km\"] / SEGS[\"kind\"].map(KSPD) * 60 # ์ž์œ  ์ฃผํ–‰ ์‹œ๊ฐ„(๋ถ„)\n", "UTIL = dict(A1=.78, A2=.85, A3=.66, B1=.70, B2=.72, B3=.64, C1=.55, C2=.58, X1=.45, X2=.40) # ํ‰์‹œ ์ด์šฉ๋ฅ \n", "\n", "\n", "def bpr(t0, v, cap, a=0.15, b=4.0):\n", " \"\"\"BPR ์ง€์ฒด ํ•จ์ˆ˜(๊ตํ†ต๊ณตํ•™์˜ ํ‘œ์ค€ํ˜•): ํ†ต๊ณผ๋Ÿ‰/์šฉ๋Ÿ‰ ๋น„๊ฐ€ ์ปค์งˆ์ˆ˜๋ก ์ง€์ฒด๊ฐ€ ๊ธ‰์ฆํ•œ๋‹ค. (๋ถ„)\"\"\"\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 2] ์ˆ˜์†ก ์ˆ˜๋Ÿ‰ ํญ์ฆ์— ๋”ฐ๋ฅธ ๊ตฌ๊ฐ„๋ณ„ ๋ณ‘๋ชฉ ์ง€์ฒด ์‹œ๊ฐ„(Delay) ๊ณ„์‚ฐ ์ „์ฒ˜๋ฆฌ ๋ชจ๋“ˆ ๊ตฌํ˜„ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def make_data(seed=11, days=150):\n", " \"\"\"๊ตฌ๊ฐ„ x ์ผ์ž ํŒจ๋„. v=์‹ค์ œ ํ”ผํฌ ํ†ต๊ณผ๋Ÿ‰, plan_v=์ˆ˜์†ก๊ณ„ํš ์ˆ˜๋Ÿ‰(๊ธด๊ธ‰ ๋ถˆ์ถœ ๋ฏธ๋ฐ˜์˜), delay=์‹ค์ œ ์ง€์ฒด(๋ถ„).\"\"\"\n", " rng = np.random.default_rng(seed)\n", " d_idx = np.arange(days)\n", " lvl = np.array([1.0, 1.08, 1.04, 1.10, 1.02, 0.82, 0.78])[d_idx % 7] * (1 + 0.0012 * d_idx)\n", " lvl = lvl * np.where((d_idx >= 100) & (d_idx < 110), 1.30, 1.0) # ์ˆ˜์†ก๋Ÿ‰ ํญ์ฆ ๊ธฐ๊ฐ„(๊ฐ€์ƒ)\n", " prev, rows = {s: 0.0 for s in SEGS[\"seg\"]}, []\n", " for d in d_idx:\n", " rain = bool(rng.random() < 0.18)\n", " for r in SEGS.itertuples():\n", " maint, urgent = bool(rng.random() < 0.06), bool(rng.random() < 0.05) # ๋น„๊ณ„ํš ์ •๋น„ / ๊ธด๊ธ‰ ๋ถˆ์ถœ\n", " v = r.cap * UTIL[r.seg] * lvl[d] * (1.25 if urgent else 1.0) * rng.normal(1, 0.05)\n", " cap_eff = r.cap * (0.65 if maint else 1.0) * (0.92 if rain else 1.0)\n", " delay = max(r.t0 * 0.2 * (v / cap_eff) ** 3.5 + 0.25 * prev[r.seg] + rng.normal(0, 0.4), 0.0)\n", " prev[r.seg] = delay\n", " rows.append(dict(day=int(d), dow=int(d % 7), seg=r.seg, kind=r.kind, km=r.km, cap=r.cap, t0=r.t0,\n", " plan_v=v / (1.25 if urgent else 1.0) * rng.normal(1, 0.04), v=v, maint=int(maint), urgent=int(urgent),\n", " rain=int(rain), cap_eff=cap_eff, delay=delay))\n", " df = pd.DataFrame(rows)\n", " df[\"transit\"] = df[\"t0\"] * (1 + 0.08 * df[\"rain\"]) + df[\"delay\"]\n", " return df\n", "\n", "\n", "def mape(y, p):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 2] ์ˆ˜์†ก ์ˆ˜๋Ÿ‰ ํญ์ฆ์— ๋”ฐ๋ฅธ ๊ตฌ๊ฐ„๋ณ„ ๋ณ‘๋ชฉ ์ง€์ฒด ์‹œ๊ฐ„(Delay) ๊ณ„์‚ฐ ์ „์ฒ˜๋ฆฌ ๋ชจ๋“ˆ ๊ตฌํ˜„ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def mae(y, p):\n", " return float(np.mean(np.abs(np.asarray(y, float) - np.asarray(p, float))))\n", "# ---------------------------------------------------------------------------\n", "\n", "clean = make_data()\n", "rng = np.random.default_rng(2)\n", "dirty = clean.copy()\n", "# ---- ์˜ค์—ผ ์ฃผ์ž…(๊ฐ€์ƒ): ๊ฒฐ์ธกยท์Œ์ˆ˜ยท๋‹จ์œ„ ์˜ค๋ฅ˜ยทํ‘œ๊ธฐ ์˜ค๋ฅ˜ยท๋ฏธ๋“ฑ๋ก ๊ตฌ๊ฐ„ยท0 ์šฉ๋Ÿ‰ยท์ค‘๋ณต ----\n", "ix = rng.choice(len(dirty), 40, replace=False)\n", "dirty.loc[ix[:12], \"v\"] = np.nan\n", "dirty.loc[ix[12:18], \"v\"] = -dirty.loc[ix[12:18], \"v\"]\n", "dirty.loc[ix[18:23], \"v\"] = dirty.loc[ix[18:23], \"v\"] * 10\n", "dirty.loc[ix[23:29], \"transit\"] = np.nan\n", "dirty.loc[ix[29:32], \"cap\"] = 0\n", "dirty.loc[ix[32:36], \"seg\"] = dirty.loc[ix[32:36], \"seg\"].str.lower() + \" \"\n", "ghost = dirty.iloc[ix[36:40]].copy(); ghost[\"seg\"] = \"Z9\" # ๋งˆ์Šคํ„ฐ์— ์—†๋Š” ๊ตฌ๊ฐ„ ์ฝ”๋“œ๊ฐ€ ์„ž์—ฌ ๋“ค์–ด์˜จ ํ–‰\n", "dirty = pd.concat([dirty, ghost, dirty.iloc[rng.choice(len(dirty), 15, replace=False)]], ignore_index=True)\n", "planted = {\"๊ฒฐ์ธก ํ†ต๊ณผ๋Ÿ‰\": 12, \"์Œ์ˆ˜ ํ†ต๊ณผ๋Ÿ‰\": 6, \"๋‹จ์œ„ ์˜ค๋ฅ˜(10๋ฐฐ)\": 5, \"๊ฒฐ์ธก ํ†ต๊ณผ์‹œ๊ฐ„\": 6, \"์šฉ๋Ÿ‰ 0\": 3, \"๊ตฌ๊ฐ„ ํ‘œ๊ธฐ ์˜ค๋ฅ˜\": 4, \"๋ฏธ๋“ฑ๋ก ๊ตฌ๊ฐ„\": 4, \"์ค‘๋ณต ํ–‰\": 15}\n", "print(f\"[0] ์˜ค์—ผ ๋ฐ์ดํ„ฐ {len(dirty)}ํ–‰ (์›๋ณธ {len(clean)}ํ–‰), ์ฃผ์ž… ์˜ค๋ฅ˜ {sum(planted.values())}๊ฑด: {planted}\")\n", "\n", "log, w = [], dirty.copy()\n", "master = SEGS.set_index(\"seg\")\n", "\n", "\n", "def rule(name, n):\n", " log.append((len(log) + 1, name, n))\n", "\n", "\n", "# R1 ๊ตฌ๊ฐ„ ์ฝ”๋“œ ํ‘œ๊ธฐ ์ •๊ทœํ™”\n", "before = w[\"seg\"].copy(); w[\"seg\"] = w[\"seg\"].str.strip().str.upper(); rule(\"๊ตฌ๊ฐ„ ์ฝ”๋“œ ๊ณต๋ฐฑยท๋Œ€์†Œ๋ฌธ์ž ์ •๊ทœํ™”\", int((before != w[\"seg\"]).sum()))\n", "# R2 ๋ฏธ๋“ฑ๋ก ๊ตฌ๊ฐ„ ๊ฒฉ๋ฆฌ(์‚ญ์ œํ•˜์ง€ ์•Š๊ณ  ๋ณ„๋„ ๋ณด๊ด€)\n", "bad = ~w[\"seg\"].isin(master.index); quarantine = w[bad].copy(); w = w[~bad]; rule(\"๋ฏธ๋“ฑ๋ก ๊ตฌ๊ฐ„ ๊ฒฉ๋ฆฌ(๋ณ„๋„ ๋ณด๊ด€)\", int(bad.sum()))\n", "# R3 ์ค‘๋ณต ์ œ๊ฑฐ(๊ตฌ๊ฐ„ยท์ผ์ž ํ‚ค)\n", "n0 = len(w); w = w.drop_duplicates([\"seg\", \"day\"], keep=\"first\"); rule(\"(๊ตฌ๊ฐ„, ์ผ์ž) ์ค‘๋ณต ์ œ๊ฑฐ\", n0 - len(w))\n", "# R4 ์šฉ๋Ÿ‰ 0/๊ฒฐ์ธก์€ ๊ตฌ๊ฐ„ ๋งˆ์Šคํ„ฐ์—์„œ ๋ณต์›\n", "bad = w[\"cap\"] <= 0; w.loc[bad, \"cap\"] = w.loc[bad, \"seg\"].map(master[\"cap\"]); rule(\"์šฉ๋Ÿ‰ 0 -> ๊ตฌ๊ฐ„ ๋งˆ์Šคํ„ฐ ๋ณต์›\", int(bad.sum()))\n", "# R5 ์Œ์ˆ˜ ํ†ต๊ณผ๋Ÿ‰์€ ๊ฒฐ์ธก์œผ๋กœ ์ „ํ™˜(๋ถ€ํ˜ธ ์˜ค๋ฅ˜ ๊ฐ€๋Šฅ์„ฑ -> ๋ณด๊ฐ„ ๋Œ€์ƒ)\n", "bad = w[\"v\"] < 0; w.loc[bad, \"v\"] = np.nan; rule(\"์Œ์ˆ˜ ํ†ต๊ณผ๋Ÿ‰ -> ๊ฒฐ์ธก ์ฒ˜๋ฆฌ\", int(bad.sum()))\n", "# R6 ๋‹จ์œ„ ์˜ค๋ฅ˜(์šฉ๋Ÿ‰์˜ 3๋ฐฐ ์ดˆ๊ณผ)๋Š” 1/10 ๋ณด์ •\n", "bad = w[\"v\"] > 3 * w[\"cap\"]; w.loc[bad, \"v\"] = w.loc[bad, \"v\"] / 10; rule(\"๋‹จ์œ„ ์˜ค๋ฅ˜(์šฉ๋Ÿ‰ 3๋ฐฐ ์ดˆ๊ณผ) 1/10 ๋ณด์ •\", int(bad.sum()))\n", "# R7 ๊ฒฐ์ธก ํ†ต๊ณผ๋Ÿ‰: ๊ฐ™์€ ๊ตฌ๊ฐ„ยท๊ฐ™์€ ์š”์ผ์˜ ์ค‘์•™๊ฐ’์œผ๋กœ ๋ณด๊ฐ„(๊ณ„์ ˆ์„ฑ ๋ณด๊ฐ„)\n", "bad = w[\"v\"].isna(); med = w.groupby([\"seg\", \"dow\"])[\"v\"].transform(\"median\"); w.loc[bad, \"v\"] = med[bad]; rule(\"๊ฒฐ์ธก ํ†ต๊ณผ๋Ÿ‰ -> ๊ตฌ๊ฐ„ยท์š”์ผ ์ค‘์•™๊ฐ’ ๋ณด๊ฐ„\", int(bad.sum()))\n", "# R8 ๊ฒฐ์ธก ํ†ต๊ณผ์‹œ๊ฐ„: ์ž์œ ์ฃผํ–‰ + ์ง€์ฒด๋กœ ์žฌ๊ณ„์‚ฐ\n", "bad = w[\"transit\"].isna(); w.loc[bad, \"transit\"] = w.loc[bad, \"t0\"] * (1 + 0.08 * w.loc[bad, \"rain\"]) + w.loc[bad, \"delay\"]; rule(\"๊ฒฐ์ธก ํ†ต๊ณผ์‹œ๊ฐ„ ์žฌ๊ณ„์‚ฐ\", int(bad.sum()))\n", "# R9 ์ง€์ฒด ์‹œ๊ฐ„ ์ •ํ•ฉ: delay = transit - ์ž์œ ์ฃผํ–‰(์šฐ์ฒœ ๋ณด์ •), ์Œ์ˆ˜๋Š” 0์œผ๋กœ\n", "d2 = (w[\"transit\"] - w[\"t0\"] * (1 + 0.08 * w[\"rain\"])).clip(lower=0); mism = (d2 - w[\"delay\"]).abs() > 1e-6\n", "w[\"delay\"] = d2; rule(\"์ง€์ฒด = ํ†ต๊ณผ์‹œ๊ฐ„ - ์ž์œ ์ฃผํ–‰ ์ •ํ•ฉ ์žฌ๊ณ„์‚ฐ\", int(mism.sum()))\n", "# R10 ์ด์ƒ์น˜ ํ‘œ์‹œ: IQR ๋ฐ– ์ง€์ฒด๋Š” 'ํ‘œ์‹œ'๋งŒ ํ•˜๊ณ , ์ •๋น„ยท๊ธด๊ธ‰ ๋ถˆ์ถœ ํ–‰์€ ์˜ˆ์™ธ๋กœ ๋ณด์กด(์‚ญ์ œ ๊ธˆ์ง€)\n", "q1, q3 = w[\"delay\"].quantile([.25, .75]); out = w[\"delay\"] > q3 + 3 * (q3 - q1)\n", "w[\"outlier\"] = out.astype(int); w[\"exception_keep\"] = ((w[\"maint\"] == 1) | (w[\"urgent\"] == 1)).astype(int)\n", "rule(\"์ง€์ฒด ์ด์ƒ์น˜ ํ”Œ๋ž˜๊ทธ(์‚ญ์ œ ์—†์Œ)\", int(out.sum()))\n", "w = w.sort_values([\"day\", \"seg\"]).reset_index(drop=True)\n", "\n", "print(\"\\n[1] ์ •์ œ ๊ทœ์น™ ์ ์šฉ ๋กœ๊ทธ\")\n", "print(pd.DataFrame(log, columns=[\"๊ทœ์น™\", \"๋‚ด์šฉ\", \"์ฒ˜๋ฆฌ ๊ฑด์ˆ˜\"]).to_string(index=False))\n", "print(f\" ์ •์ œ ๊ทœ์น™ {len(log)}์ข… ์ ์šฉ(๋ชฉํ‘œ 10์ข…: {'๋‹ฌ์„ฑ' if len(log) >= 10 else '๋ฏธ๋‹ฌ'})\")\n", "\n", "# ---- ์ •ํ•ฉ์„ฑ ๊ฒ€์ฆ ----\n", "checks = {\"(๊ตฌ๊ฐ„, ์ผ์ž) ์œ ์ผ\": not w.duplicated([\"seg\", \"day\"]).any(), \"ํ•„์ˆ˜ ์ปฌ๋Ÿผ ๊ฒฐ์ธก 0\": int(w[[\"seg\", \"v\", \"cap\", \"delay\", \"transit\"]].isna().sum().sum()) == 0,\n", " \"๊ตฌ๊ฐ„ ์ฝ”๋“œ ๋งˆ์Šคํ„ฐ ํฌํ•จ\": bool(w[\"seg\"].isin(master.index).all()), \"ํ†ต๊ณผ๋Ÿ‰ 0 ์ด์ƒยท์šฉ๋Ÿ‰ 3๋ฐฐ ์ดํ•˜\": bool(((w[\"v\"] >= 0) & (w[\"v\"] <= 3 * w[\"cap\"])).all()),\n", " \"์šฉ๋Ÿ‰ = ๋งˆ์Šคํ„ฐ ๊ฐ’\": bool((w[\"cap\"] == w[\"seg\"].map(master[\"cap\"])).all()), \"ํ†ต๊ณผ์‹œ๊ฐ„ >= ์ž์œ ์ฃผํ–‰\": bool((w[\"transit\"] >= w[\"t0\"] - 1e-9).all()),\n", " \"์ง€์ฒด >= 0\": bool((w[\"delay\"] >= 0).all())}\n", "passed = sum(checks.values())\n", "print(\"\\n[2] ์ •ํ•ฉ์„ฑ ๊ฒ€์ฆ\")\n", "for k, v in checks.items():\n", " print(f\" [{'PASS' if v else 'FAIL'}] {k}\")\n", "print(f\" ํ†ต๊ณผ์œจ {passed / len(checks):.0%} (๋ชฉํ‘œ 100%)\")\n", "\n", "# ---- ๊ตฐ์ˆ˜ ํŠน์ˆ˜ ์˜ˆ์™ธ ๋ณด์กด / ์›๋ณธ ๋Œ€๋น„ ๋ณต์› ์ •ํ™•๋„ ----\n", "ex = w[w[\"exception_keep\"] == 1]\n", "print(f\"\\n[3] ๊ตฐ์ˆ˜ ํŠน์ˆ˜ ์˜ˆ์™ธ ๋ณด์กด: ๋น„๊ณ„ํš ์ •๋น„ {int(w['maint'].sum())}๊ฑด, ๊ธด๊ธ‰ ๋ถˆ์ถœ {int(w['urgent'].sum())}๊ฑด ๋ชจ๋‘ ์œ ์ง€(์›๋ณธ {int(clean['maint'].sum())}, {int(clean['urgent'].sum())}๊ฑด)\")\n", "print(f\" ์˜ˆ์™ธ ํ–‰ ํ‰๊ท  ์ง€์ฒด {ex['delay'].mean():.2f}๋ถ„ vs ์ผ๋ฐ˜ ํ–‰ {w.loc[w['exception_keep'] == 0, 'delay'].mean():.2f}๋ถ„ -> ์ด์ƒ์น˜๋กœ ์ง€์› ๋‹ค๋ฉด ํญ์ฆ ๊ตฌ๊ฐ„ ์ •๋ณด๊ฐ€ ์‚ฌ๋ผ์ง„๋‹ค\")\n", "m = w.merge(clean[[\"seg\", \"day\", \"v\"]], on=[\"seg\", \"day\"], suffixes=(\"\", \"_true\"))\n", "print(f\" ํ†ต๊ณผ๋Ÿ‰ ๋ณต์› ์˜ค์ฐจ(๋ณด์ •ยท๋ณด๊ฐ„๋œ ํ–‰ {int((m['v'] != m['v_true']).sum())}๊ฑด ํฌํ•จ ์ „์ฒด MAE) {np.abs(m['v'] - m['v_true']).mean():.3f}๋Œ€/์‹œ\")\n", "print(\"\\n[4] ์ „ํ›„ ๊ธฐ์ˆ ํ†ต๊ณ„ ๋น„๊ต(ํ†ต๊ณผ๋Ÿ‰ v / ์ง€์ฒด delay)\")\n", "st = pd.DataFrame({\"์˜ค์—ผ ์›๋ณธ\": dirty[[\"v\", \"delay\"]].describe().loc[[\"count\", \"mean\", \"min\", \"max\"]].stack(), \"์ •์ œ ํ›„\": w[[\"v\", \"delay\"]].describe().loc[[\"count\", \"mean\", \"min\", \"max\"]].stack(),\n", " \"์ •๋‹ต ์›๋ณธ\": clean[[\"v\", \"delay\"]].describe().loc[[\"count\", \"mean\", \"min\", \"max\"]].stack()}).round(2)\n", "print(st.to_string())\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 02 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[0] ์˜ค์—ผ ๋ฐ์ดํ„ฐ 1519ํ–‰ (์›๋ณธ 1500ํ–‰), ์ฃผ์ž… ์˜ค๋ฅ˜ 55๊ฑด: {'๊ฒฐ์ธก ํ†ต๊ณผ๋Ÿ‰': 12, '์Œ์ˆ˜ ํ†ต๊ณผ๋Ÿ‰': 6, '๋‹จ์œ„ ์˜ค๋ฅ˜(10๋ฐฐ)': 5, '๊ฒฐ์ธก ํ†ต๊ณผ์‹œ๊ฐ„': 6, '์šฉ๋Ÿ‰ 0': 3, '๊ตฌ๊ฐ„ ํ‘œ๊ธฐ ์˜ค๋ฅ˜': 4, '๋ฏธ๋“ฑ๋ก ๊ตฌ๊ฐ„': 4, '์ค‘๋ณต ํ–‰': 15}\n", "\n", "[1] ์ •์ œ ๊ทœ์น™ ์ ์šฉ ๋กœ๊ทธ\n", " ๊ทœ์น™ ๋‚ด์šฉ ์ฒ˜๋ฆฌ ๊ฑด์ˆ˜\n", " 1 ๊ตฌ๊ฐ„ ์ฝ”๋“œ ๊ณต๋ฐฑยท๋Œ€์†Œ๋ฌธ์ž ์ •๊ทœํ™” 4\n", " 2 ๋ฏธ๋“ฑ๋ก ๊ตฌ๊ฐ„ ๊ฒฉ๋ฆฌ(๋ณ„๋„ ๋ณด๊ด€) 4\n", " 3 (๊ตฌ๊ฐ„, ์ผ์ž) ์ค‘๋ณต ์ œ๊ฑฐ 15\n", " 4 ์šฉ๋Ÿ‰ 0 -> ๊ตฌ๊ฐ„ ๋งˆ์Šคํ„ฐ ๋ณต์› 3\n", " 5 ์Œ์ˆ˜ ํ†ต๊ณผ๋Ÿ‰ -> ๊ฒฐ์ธก ์ฒ˜๋ฆฌ 6\n", " 6 ๋‹จ์œ„ ์˜ค๋ฅ˜(์šฉ๋Ÿ‰ 3๋ฐฐ ์ดˆ๊ณผ) 1/10 ๋ณด์ • 5\n", " 7 ๊ฒฐ์ธก ํ†ต๊ณผ๋Ÿ‰ -> ๊ตฌ๊ฐ„ยท์š”์ผ ์ค‘์•™๊ฐ’ ๋ณด๊ฐ„ 18\n", " 8 ๊ฒฐ์ธก ํ†ต๊ณผ์‹œ๊ฐ„ ์žฌ๊ณ„์‚ฐ 6\n", " 9 ์ง€์ฒด = ํ†ต๊ณผ์‹œ๊ฐ„ - ์ž์œ ์ฃผํ–‰ ์ •ํ•ฉ ์žฌ๊ณ„์‚ฐ 0\n", " 10 ์ง€์ฒด ์ด์ƒ์น˜ ํ”Œ๋ž˜๊ทธ(์‚ญ์ œ ์—†์Œ) 43\n", " ์ •์ œ ๊ทœ์น™ 10์ข… ์ ์šฉ(๋ชฉํ‘œ 10์ข…: ๋‹ฌ์„ฑ)\n", "\n", "[2] ์ •ํ•ฉ์„ฑ ๊ฒ€์ฆ\n", " [PASS] (๊ตฌ๊ฐ„, ์ผ์ž) ์œ ์ผ\n", " [PASS] ํ•„์ˆ˜ ์ปฌ๋Ÿผ ๊ฒฐ์ธก 0\n", " [PASS] ๊ตฌ๊ฐ„ ์ฝ”๋“œ ๋งˆ์Šคํ„ฐ ํฌํ•จ\n", " [PASS] ํ†ต๊ณผ๋Ÿ‰ 0 ์ด์ƒยท์šฉ๋Ÿ‰ 3๋ฐฐ ์ดํ•˜\n", " [PASS] ์šฉ๋Ÿ‰ = ๋งˆ์Šคํ„ฐ ๊ฐ’\n", " [PASS] ํ†ต๊ณผ์‹œ๊ฐ„ >= ์ž์œ ์ฃผํ–‰\n", " [PASS] ์ง€์ฒด >= 0\n", " ํ†ต๊ณผ์œจ 100% (๋ชฉํ‘œ 100%)\n", "\n", "[3] ๊ตฐ์ˆ˜ ํŠน์ˆ˜ ์˜ˆ์™ธ ๋ณด์กด: ๋น„๊ณ„ํš ์ •๋น„ 95๊ฑด, ๊ธด๊ธ‰ ๋ถˆ์ถœ 87๊ฑด ๋ชจ๋‘ ์œ ์ง€(์›๋ณธ 95, 87๊ฑด)\n", " ์˜ˆ์™ธ ํ–‰ ํ‰๊ท  ์ง€์ฒด 4.37๋ถ„ vs ์ผ๋ฐ˜ ํ–‰ 1.60๋ถ„ -> ์ด์ƒ์น˜๋กœ ์ง€์› ๋‹ค๋ฉด ํญ์ฆ ๊ตฌ๊ฐ„ ์ •๋ณด๊ฐ€ ์‚ฌ๋ผ์ง„๋‹ค\n", " ํ†ต๊ณผ๋Ÿ‰ ๋ณต์› ์˜ค์ฐจ(๋ณด์ •ยท๋ณด๊ฐ„๋œ ํ–‰ 18๊ฑด ํฌํ•จ ์ „์ฒด MAE) 0.104๋Œ€/์‹œ\n", "\n", "[4] ์ „ํ›„ ๊ธฐ์ˆ ํ†ต๊ณ„ ๋น„๊ต(ํ†ต๊ณผ๋Ÿ‰ v / ์ง€์ฒด delay)\n", " ์˜ค์—ผ ์›๋ณธ ์ •์ œ ํ›„ ์ •๋‹ต ์›๋ณธ\n", "count v 1507.00 1500.00 1500.00\n", " delay 1519.00 1500.00 1500.00\n", "mean v 63.64 62.95 62.95\n", " delay 1.94 1.93 1.93\n", "min v -146.18 16.78 16.78\n", " delay 0.00 0.00 0.00\n", "max v 635.70 174.46 174.46\n", " delay 21.79 21.79 21.79\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 03. ๊ตฌ๊ฐ„๋ณ„ ํ†ต๊ณผ ์šฉ๋Ÿ‰ ๊ธฐ๋ฐ˜ 1์ฐจ ์ˆ˜์†ก ์ง€์ฒด ์˜ˆ์ธก Baseline ์•Œ๊ณ ๋ฆฌ์ฆ˜ ๊ตฌํ˜„\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 2.5์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "ํ†ต๊ณผ ์šฉ๋Ÿ‰ ๊ธฐ๋ฐ˜ 1์ฐจ ์ˆ˜์†ก ์ง€์ฒด ์˜ˆ์ธก Baseline ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ๊ตฌํ˜„ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **๊ตฌ๊ฐ„๋ณ„ ํ†ต๊ณผ ์šฉ๋Ÿ‰ ๊ธฐ๋ฐ˜ BPR ์ง€์ฒด ํ•จ์ˆ˜๋กœ 1์ฐจ ์ˆ˜์†ก ์ง€์ฒด ์˜ˆ์ธก Baseline์„ ๋งŒ๋“ค๊ณ , EDA๋กœ Baseline์˜ ํ•œ๊ณ„์™€ ํ•ต์‹ฌ ์š”์ธ์„ ์ฐพ๋Š”๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **๊ณ„ํš ๋ฌผ๋Ÿ‰์„ ๊ณต์นญ ์šฉ๋Ÿ‰์œผ๋กœ ๋‚˜๋ˆˆ ์ด์šฉ๋ฅ ๋กœ ๊ตฌ๊ฐ„ ์ง€์ฒด๋ฅผ ๊ณ„์‚ฐํ•˜๊ณ  ํ†ต๊ณผ์‹œ๊ฐ„์˜ ์˜ค์ฐจ๋ฅผ ์ธก์ • ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `notebooks/mov01_eda_analysis.ipynb ๋ฐ EDA ์š”์•ฝ ๋ฆฌํฌํŠธ` ํ˜•ํƒœ์˜ ๊ฒฐ๊ณผ๋ฌผ์„ ์™„์„ฑํ•œ๋‹ค.\n", "\n", "### ๐Ÿ” ํ‰๊ฐ€ ๋ฐ ๊ฒ€์ฆ ๊ธฐ์ค€ (PASS ์กฐ๊ฑด)\n", "#### ๋ฏธ์…˜ ํŒ์ •\n", "\n", "**ํ‰๊ฐ€ ๊ตฌ๋ถ„:** ํ•„์ˆ˜\n", "\n", "**ํ•ต์‹ฌ ํ‰๊ฐ€์˜์—ญ:** ๊ตฐ์ˆ˜ ์˜์‚ฌ๊ฒฐ์ •์— ์ง๊ด€์ ์ธ ์ธ์‚ฌ์ดํŠธ ์ œ๊ณต ์ˆ˜์ค€\n", "\n", "#### ๋ฏธ์…˜ PASS ์กฐ๊ฑด\n", "\n", "- ์ •๋Ÿ‰ ๊ธฐ์ค€: EDA ์‹œ๊ฐํ™” ์ฐจํŠธ 8์ข… ์ด์ƒ ์ƒ์„ฑ, ์œ ์˜๋ฏธํ•œ ์ƒ๊ด€๊ด€๊ณ„ ํ”ผ์ฒ˜ 5๊ฐœ ์ด์ƒ ๋„์ถœ\n", "\n", "\n", "- ์ง„ํ–‰ ๊ธฐ์ค€: EDA ๋…ธํŠธ๋ถ ์‹คํ–‰ ์™„๋ฃŒ ๋ฐ ํ•ต์‹ฌ ๋ฐœ๊ฒฌ์ (Key Findings) ๋ฌธ์„œํ™”\n", "\n", "\n", "- ๊ฒ€์ฆ ๋ฐฉ๋ฒ•: ํ†ต๊ณ„์  ๊ฐ€์„ค ๊ฒ€์ •(p-value, ์ƒ๊ด€๊ณ„์ˆ˜)์„ ํ†ตํ•œ ์œ ์˜์„ฑ ๊ฒ€์ฆ\n", "\n", "\n", "#### ์ฑ„์ ์ž ํŒ์ • ๋ฉ”๋ชจ\n", "\n", "> ๋‹จ์ˆœ ์ฝ”๋“œ ์‹คํ–‰ ์—ฌ๋ถ€๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ถœ๋ ฅ๋œ ์ˆ˜์น˜์™€ ๊ทผ๊ฑฐ ์ž๋ฃŒ๊ฐ€ ๊ตฐ ์‹ค๋ฌด ์š”๊ตฌ์‚ฌํ•ญ์— ๋ถ€ํ•ฉํ•˜๋Š”์ง€ ๋ฉด๋ฐ€ํžˆ ํ™•์ธํ•œ๋‹ค.\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\"\"\"๊ตฌ๊ฐ„๋ณ„ ํ†ต๊ณผ ์šฉ๋Ÿ‰(BPR ํ•จ์ˆ˜) ๊ธฐ๋ฐ˜ 1์ฐจ ์ˆ˜์†ก ์ง€์ฒด ์˜ˆ์ธก Baseline์„ ๋งŒ๋“ค์–ด ์˜ค์ฐจ๋ฅผ ์ธก์ •ํ•˜๊ณ , EDA ์ฐจํŠธ 8์ข…๊ณผ ์ƒ๊ด€ ํ”ผ์ฒ˜ยทํŒŒ๋ ˆํ† (์ƒ์œ„ 20% ๊ตฌ๊ฐ„) ๋ถ„์„์œผ๋กœ Baseline์˜ ํ•œ๊ณ„๋ฅผ ์ฐพ๋Š”๋‹ค.\"\"\"\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from scipy import stats\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ƒ ๋…ธ์„ ๋งยท๊ตฌ๊ฐ„ ํ†ต๊ณผ๋Ÿ‰ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ๋„๋กœยท์ง€๋ช…ยท๋ถ€๋Œ€ ์—†์Œ)\n", "# ๊ฐ€์ƒ ์ถœ๋ฐœ์ง€(SRC)์—์„œ ๊ฐ€์ƒ ๋ชฉ์ ์ง€(DST)๊นŒ์ง€ 3๊ฐœ ์ถ•์„ (AยทBยทC)๊ณผ ์—ฐ๊ฒฐ๋กœ 2๊ฐœ, ๊ตฌ๊ฐ„ 10๊ฐœ. 150์ผ์น˜ ํ”ผํฌ์‹œ๊ฐ„ ํ†ต๊ณผ๋Ÿ‰ยท์ง€์ฒด ์‹œ๊ฐ„์„ ๋งŒ๋“ ๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "SEGS = pd.DataFrame([\n", " (\"A1\", \"SRC\", \"J1\", 12, 130, \"์ผ๋ฐ˜\"), (\"A2\", \"J1\", \"J2\", 8, 70, \"๊ต๋Ÿ‰\"), (\"A3\", \"J2\", \"DST\", 10, 140, \"๊ต์ฐจ๋กœ\"),\n", " (\"B1\", \"SRC\", \"J3\", 15, 100, \"์ผ๋ฐ˜\"), (\"B2\", \"J3\", \"J2\", 9, 80, \"ํ„ฐ๋„\"), (\"B3\", \"J3\", \"DST\", 14, 95, \"์ผ๋ฐ˜\"),\n", " (\"C1\", \"SRC\", \"J4\", 18, 70, \"์ผ๋ฐ˜\"), (\"C2\", \"J4\", \"DST\", 20, 75, \"๊ต๋Ÿ‰\"),\n", " (\"X1\", \"J1\", \"J3\", 7, 50, \"๊ต์ฐจ๋กœ\"), (\"X2\", \"J4\", \"J3\", 10, 60, \"์ผ๋ฐ˜\")],\n", " columns=[\"seg\", \"u\", \"v\", \"km\", \"cap\", \"kind\"]) # cap = ํ”ผํฌ์‹œ๊ฐ„ ํ†ต๊ณผ ์šฉ๋Ÿ‰(๋Œ€/์‹œ, ๊ต์œก์šฉ ๊ฐ€์ •๊ฐ’)\n", "KSPD = {\"์ผ๋ฐ˜\": 55, \"๊ต๋Ÿ‰\": 40, \"ํ„ฐ๋„\": 45, \"๊ต์ฐจ๋กœ\": 45} # ๊ตฌ๊ฐ„ ์œ ํ˜•๋ณ„ ์ž์œ  ์ฃผํ–‰ ์†๋„(km/h, ๊ฐ€์ •๊ฐ’)\n", "SEGS[\"t0\"] = SEGS[\"km\"] / SEGS[\"kind\"].map(KSPD) * 60 # ์ž์œ  ์ฃผํ–‰ ์‹œ๊ฐ„(๋ถ„)\n", "UTIL = dict(A1=.78, A2=.85, A3=.66, B1=.70, B2=.72, B3=.64, C1=.55, C2=.58, X1=.45, X2=.40) # ํ‰์‹œ ์ด์šฉ๋ฅ \n", "\n", "\n", "def bpr(t0, v, cap, a=0.15, b=4.0):\n", " \"\"\"BPR ์ง€์ฒด ํ•จ์ˆ˜(๊ตํ†ต๊ณตํ•™์˜ ํ‘œ์ค€ํ˜•): ํ†ต๊ณผ๋Ÿ‰/์šฉ๋Ÿ‰ ๋น„๊ฐ€ ์ปค์งˆ์ˆ˜๋ก ์ง€์ฒด๊ฐ€ ๊ธ‰์ฆํ•œ๋‹ค. (๋ถ„)\"\"\"\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 3] ๊ตฌ๊ฐ„๋ณ„ ํ†ต๊ณผ ์šฉ๋Ÿ‰ ๊ธฐ๋ฐ˜ 1์ฐจ ์ˆ˜์†ก ์ง€์ฒด ์˜ˆ์ธก Baseline ์•Œ๊ณ ๋ฆฌ์ฆ˜ ๊ตฌํ˜„ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def make_data(seed=11, days=150):\n", " \"\"\"๊ตฌ๊ฐ„ x ์ผ์ž ํŒจ๋„. v=์‹ค์ œ ํ”ผํฌ ํ†ต๊ณผ๋Ÿ‰, plan_v=์ˆ˜์†ก๊ณ„ํš ์ˆ˜๋Ÿ‰(๊ธด๊ธ‰ ๋ถˆ์ถœ ๋ฏธ๋ฐ˜์˜), delay=์‹ค์ œ ์ง€์ฒด(๋ถ„).\"\"\"\n", " rng = np.random.default_rng(seed)\n", " d_idx = np.arange(days)\n", " lvl = np.array([1.0, 1.08, 1.04, 1.10, 1.02, 0.82, 0.78])[d_idx % 7] * (1 + 0.0012 * d_idx)\n", " lvl = lvl * np.where((d_idx >= 100) & (d_idx < 110), 1.30, 1.0) # ์ˆ˜์†ก๋Ÿ‰ ํญ์ฆ ๊ธฐ๊ฐ„(๊ฐ€์ƒ)\n", " prev, rows = {s: 0.0 for s in SEGS[\"seg\"]}, []\n", " for d in d_idx:\n", " rain = bool(rng.random() < 0.18)\n", " for r in SEGS.itertuples():\n", " maint, urgent = bool(rng.random() < 0.06), bool(rng.random() < 0.05) # ๋น„๊ณ„ํš ์ •๋น„ / ๊ธด๊ธ‰ ๋ถˆ์ถœ\n", " v = r.cap * UTIL[r.seg] * lvl[d] * (1.25 if urgent else 1.0) * rng.normal(1, 0.05)\n", " cap_eff = r.cap * (0.65 if maint else 1.0) * (0.92 if rain else 1.0)\n", " delay = max(r.t0 * 0.2 * (v / cap_eff) ** 3.5 + 0.25 * prev[r.seg] + rng.normal(0, 0.4), 0.0)\n", " prev[r.seg] = delay\n", " rows.append(dict(day=int(d), dow=int(d % 7), seg=r.seg, kind=r.kind, km=r.km, cap=r.cap, t0=r.t0,\n", " plan_v=v / (1.25 if urgent else 1.0) * rng.normal(1, 0.04), v=v, maint=int(maint), urgent=int(urgent),\n", " rain=int(rain), cap_eff=cap_eff, delay=delay))\n", " df = pd.DataFrame(rows)\n", " df[\"transit\"] = df[\"t0\"] * (1 + 0.08 * df[\"rain\"]) + df[\"delay\"]\n", " return df\n", "\n", "\n", "def mape(y, p):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 3] ๊ตฌ๊ฐ„๋ณ„ ํ†ต๊ณผ ์šฉ๋Ÿ‰ ๊ธฐ๋ฐ˜ 1์ฐจ ์ˆ˜์†ก ์ง€์ฒด ์˜ˆ์ธก Baseline ์•Œ๊ณ ๋ฆฌ์ฆ˜ ๊ตฌํ˜„ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def mae(y, p):\n", " return float(np.mean(np.abs(np.asarray(y, float) - np.asarray(p, float))))\n", "# ---------------------------------------------------------------------------\n", "# ---------------------------------------------------------------------------\n", "# ์ง€์ฒด ์˜ˆ์ธก์šฉ ํŒŒ์ƒ ํ”ผ์ฒ˜ ์ƒ์„ฑ(๋‹น์ผ ์˜ค์ „ ์‹œ์ ์— ์•Œ ์ˆ˜ ์žˆ๋Š” ์ •๋ณด๋งŒ ์‚ฌ์šฉ: ๊ณ„ํš ๋ฌผ๋Ÿ‰, ๊ณต์ง€ ์ •๋น„, ๊ธฐ์ƒ ์˜ˆ๋ณด, ์ „์ผยท์ „์ฃผ ์‹ค์ )\n", "# ---------------------------------------------------------------------------\n", "FEATS = [\"plan_v\", \"cap\", \"cap_eff\", \"t0\", \"km\", \"maint\", \"rain\", \"util\", \"util_eff\", \"util_eff4\", \"bpr_base\", \"lag1_delay\", \"lag7_delay\",\n", " \"roll7_v\", \"surge_ratio\", \"is_weekend\", \"dow_sin\", \"dow_cos\", \"k_๊ต๋Ÿ‰\", \"k_ํ„ฐ๋„\", \"k_๊ต์ฐจ๋กœ\"]\n", "\n", "\n", "def add_features(df):\n", " d = df.sort_values([\"seg\", \"day\"]).copy()\n", " g = d.groupby(\"seg\")\n", " d[\"lag1_delay\"] = g[\"delay\"].shift(1)\n", " d[\"lag7_delay\"] = g[\"delay\"].shift(7)\n", " d[\"roll7_v\"] = g[\"v\"].transform(lambda s: s.shift(1).rolling(7).mean())\n", " d[\"util\"] = d[\"plan_v\"] / d[\"cap\"]\n", " d[\"util_eff\"] = d[\"plan_v\"] / d[\"cap_eff\"]\n", " d[\"util_eff4\"] = d[\"util_eff\"] ** 4\n", " d[\"bpr_base\"] = bpr(d[\"t0\"], d[\"plan_v\"], d[\"cap\"])\n", " d[\"surge_ratio\"] = d[\"plan_v\"] / d[\"roll7_v\"]\n", " d[\"is_weekend\"] = (d[\"dow\"] >= 5).astype(int)\n", " d[\"dow_sin\"], d[\"dow_cos\"] = np.sin(2 * np.pi * d[\"dow\"] / 7), np.cos(2 * np.pi * d[\"dow\"] / 7)\n", " for k in (\"๊ต๋Ÿ‰\", \"ํ„ฐ๋„\", \"๊ต์ฐจ๋กœ\"):\n", " d[\"k_\" + k] = (d[\"kind\"] == k).astype(int)\n", " return d.dropna().sort_values([\"day\", \"seg\"]).reset_index(drop=True)\n", "# ---------------------------------------------------------------------------\n", "\n", "df = make_data()\n", "d = add_features(df)\n", "\n", "# 1) Baseline: ๊ณ„ํš ๋ฌผ๋Ÿ‰ / ๊ณต์นญ ์šฉ๋Ÿ‰์œผ๋กœ BPR ์ง€์ฒด ๊ณ„์‚ฐ(์ •๋น„ยท๊ธฐ์ƒยท์ „์ผ ์ง€์ฒด ๋ฏธ๋ฐ˜์˜)\n", "d[\"pred_base\"] = d[\"bpr_base\"]\n", "d[\"pred_transit\"] = d[\"t0\"] + d[\"pred_base\"]\n", "print(f\"[1] Baseline(BPR) ์ง€์ฒด ์˜ˆ์ธก: ํ†ต๊ณผ์‹œ๊ฐ„ MAPE {mape(d.transit, d.pred_transit):.1f}%, ํ†ต๊ณผ์‹œ๊ฐ„ MAE {mae(d.transit, d.pred_transit):.2f}๋ถ„, ์ง€์ฒด MAE {mae(d.delay, d.pred_base):.2f}๋ถ„\")\n", "bias = (d[\"pred_base\"] - d[\"delay\"])\n", "grp = pd.DataFrame({\"ํ‰๊ท  ์˜ค์ฐจ(์˜ˆ์ธก-์‹ค์ œ, ๋ถ„)\": [bias[d.maint == 1].mean(), bias[d.maint == 0].mean(), bias[d.rain == 1].mean(), bias[d.rain == 0].mean(), bias[d.urgent == 1].mean(), bias[(d.day >= 100) & (d.day < 110)].mean()]},\n", " index=[\"๋น„๊ณ„ํš ์ •๋น„์ผ\", \"์ •๋น„ ์—†์Œ\", \"์šฐ์ฒœ์ผ\", \"๋ง‘์Œ\", \"๊ธด๊ธ‰ ๋ถˆ์ถœ์ผ\", \"์ˆ˜์†ก๋Ÿ‰ ํญ์ฆ ๊ธฐ๊ฐ„\"]).round(2)\n", "print(\"[2] ์กฐ๊ฑด๋ณ„ Baseline ์˜ค์ฐจ(์Œ์ˆ˜ = ๊ณผ์†Œ์˜ˆ์ธก)\")\n", "print(grp.to_string())\n", "print(\" -> Baseline์€ ์ •๋น„ยท๊ธด๊ธ‰ ๋ถˆ์ถœยทํญ์ฆ ์กฐ๊ฑด์—์„œ ์ง€์ฒด๋ฅผ ๊ณผ์†Œ์˜ˆ์ธกํ•œ๋‹ค(ํ•œ๊ณ„).\")\n", "\n", "# 2) ์ƒ๊ด€ ๋ถ„์„: ์ง€์ฒด์™€ ๊ฐ ํ”ผ์ฒ˜์˜ ์Šคํ”ผ์–ด๋งŒ ์ƒ๊ด€ยทp-value\n", "rows = []\n", "for f in FEATS:\n", " r, p = stats.spearmanr(d[f], d[\"delay\"])\n", " rows.append((f, r, p))\n", "cor = pd.DataFrame(rows, columns=[\"ํ”ผ์ฒ˜\", \"์ƒ๊ด€\", \"p\"]).assign(abs_r=lambda x: x[\"์ƒ๊ด€\"].abs()).sort_values(\"abs_r\", ascending=False)\n", "sig = cor[(cor[\"abs_r\"] >= 0.3) & (cor[\"p\"] < 0.05)]\n", "print(f\"\\n[3] ์ง€์ฒด์™€ ์œ ์˜ํ•œ ์ƒ๊ด€(|r|>=0.3, p<0.05) ํ”ผ์ฒ˜ {len(sig)}๊ฐœ (๋ชฉํ‘œ 5๊ฐœ ์ด์ƒ: {'๋‹ฌ์„ฑ' if len(sig) >= 5 else '๋ฏธ๋‹ฌ'})\")\n", "print(sig.head(7)[[\"ํ”ผ์ฒ˜\", \"์ƒ๊ด€\", \"p\"]].round(3).to_string(index=False))\n", "t, p = stats.ttest_ind(d.loc[d.maint == 1, \"delay\"], d.loc[d.maint == 0, \"delay\"], equal_var=False)\n", "print(f\" ๊ฐ€์„ค๊ฒ€์ •: ๋น„๊ณ„ํš ์ •๋น„์ผ vs ํ‰์‹œ ์ง€์ฒด ํ‰๊ท ์ฐจ t={t:.1f}, p={p:.2g}\")\n", "\n", "# 3) ํŒŒ๋ ˆํ† (ABC): ๊ตฌ๊ฐ„๋ณ„ ์ด ์ง€์ฒด ๊ธฐ์—ฌ\n", "tot = d.groupby(\"seg\")[\"delay\"].sum().sort_values(ascending=False)\n", "cum = tot.cumsum() / tot.sum()\n", "top = tot.index[:max(1, int(np.ceil(len(tot) * 0.2)))]\n", "print(f\"\\n[4] ํŒŒ๋ ˆํ† : ์ƒ์œ„ 20% ๊ตฌ๊ฐ„({', '.join(top)})์ด ์ด ์ง€์ฒด์˜ {tot[top].sum() / tot.sum():.0%}๋ฅผ ์ฐจ์ง€, ์ƒ์œ„ 50% ๊ตฌ๊ฐ„ {cum.iloc[4]:.0%}\")\n", "\n", "# 4) EDA ์ฐจํŠธ 8์ข…\n", "fig, ax = plt.subplots(2, 4, figsize=(18, 8))\n", "daily = d.groupby(\"day\")[\"v\"].sum(); ax[0, 0].plot(daily.index, daily.values); ax[0, 0].set_title(\"1 Daily total peak volume\")\n", "ax[0, 1].hist(d[\"delay\"], bins=40); ax[0, 1].set_title(\"2 Delay distribution\")\n", "ax[0, 2].scatter(d[\"util\"], d[\"delay\"], s=6, alpha=.4); ax[0, 2].set_title(\"3 Utilization vs delay\")\n", "mean_seg = d.groupby(\"seg\")[\"delay\"].mean().sort_values(); ax[0, 3].barh(mean_seg.index, mean_seg.values); ax[0, 3].set_title(\"4 Mean delay by segment\")\n", "ax[1, 0].boxplot([d.loc[d.dow == k, \"delay\"] for k in range(7)]); ax[1, 0].set_title(\"5 Delay by weekday\")\n", "ax[1, 1].boxplot([d.loc[d.rain == 0, \"delay\"], d.loc[d.rain == 1, \"delay\"]])\n", "ax[1, 1].set_xticklabels([\"dry\", \"rain\"]); ax[1, 1].set_title(\"6 Rain effect\")\n", "ax[1, 2].boxplot([d.loc[d.maint == 0, \"delay\"], d.loc[d.maint == 1, \"delay\"]])\n", "ax[1, 2].set_xticklabels([\"normal\", \"maintenance\"]); ax[1, 2].set_title(\"7 Maintenance effect\")\n", "ax[1, 3].bar(range(len(tot)), (tot / tot.sum()).values); ax[1, 3].plot(range(len(tot)), cum.values, \"r-o\"); ax[1, 3].set_title(\"8 Pareto of total delay\")\n", "plt.suptitle(\"MOV01 EDA (synthetic data)\"); plt.tight_layout(); plt.show()\n", "print(\"\\n[5] EDA ์ฐจํŠธ 8์ข… ์ƒ์„ฑ ์™„๋ฃŒ(๋ชฉํ‘œ 8์ข… ์ด์ƒ: ๋‹ฌ์„ฑ)\")\n", "print(\"ํ•ต์‹ฌ ๋ฐœ๊ฒฌ: ์ง€์ฒด๋Š” ์ด์šฉ๋ฅ ์˜ ๋น„์„ ํ˜• ํ•จ์ˆ˜์ด๋ฉฐ ์ •๋น„ยทํญ์ฆยท๊ธด๊ธ‰ ๋ถˆ์ถœ์ด ๊ฒน์น˜๋ฉด ๊ธ‰์ฆํ•œ๋‹ค. ์ด๋Š” M4 ํŒŒ์ƒ ํ”ผ์ฒ˜ ์„ค๊ณ„์˜ ๊ทผ๊ฑฐ๊ฐ€ ๋œ๋‹ค.\")\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 03 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[1] Baseline(BPR) ์ง€์ฒด ์˜ˆ์ธก: ํ†ต๊ณผ์‹œ๊ฐ„ MAPE 7.4%, ํ†ต๊ณผ์‹œ๊ฐ„ MAE 1.39๋ถ„, ์ง€์ฒด MAE 1.23๋ถ„\n", "[2] ์กฐ๊ฑด๋ณ„ Baseline ์˜ค์ฐจ(์Œ์ˆ˜ = ๊ณผ์†Œ์˜ˆ์ธก)\n", " ํ‰๊ท  ์˜ค์ฐจ(์˜ˆ์ธก-์‹ค์ œ, ๋ถ„)\n", "๋น„๊ณ„ํš ์ •๋น„์ผ -4.87\n", "์ •๋น„ ์—†์Œ -0.96\n", "์šฐ์ฒœ์ผ -1.67\n", "๋ง‘์Œ -1.14\n", "๊ธด๊ธ‰ ๋ถˆ์ถœ์ผ -2.62\n", "์ˆ˜์†ก๋Ÿ‰ ํญ์ฆ ๊ธฐ๊ฐ„ -2.66\n", " -> Baseline์€ ์ •๋น„ยท๊ธด๊ธ‰ ๋ถˆ์ถœยทํญ์ฆ ์กฐ๊ฑด์—์„œ ์ง€์ฒด๋ฅผ ๊ณผ์†Œ์˜ˆ์ธกํ•œ๋‹ค(ํ•œ๊ณ„).\n", "\n", "[3] ์ง€์ฒด์™€ ์œ ์˜ํ•œ ์ƒ๊ด€(|r|>=0.3, p<0.05) ํ”ผ์ฒ˜ 14๊ฐœ (๋ชฉํ‘œ 5๊ฐœ ์ด์ƒ: ๋‹ฌ์„ฑ)\n", " ํ”ผ์ฒ˜ ์ƒ๊ด€ p\n", " util_eff 0.836 0.0\n", " util_eff4 0.836 0.0\n", " bpr_base 0.830 0.0\n", " util 0.793 0.0\n", "lag1_delay 0.730 0.0\n", " plan_v 0.653 0.0\n", "lag7_delay 0.650 0.0\n", " ๊ฐ€์„ค๊ฒ€์ •: ๋น„๊ณ„ํš ์ •๋น„์ผ vs ํ‰์‹œ ์ง€์ฒด ํ‰๊ท ์ฐจ t=8.2, p=1.7e-12\n", "\n", "[4] ํŒŒ๋ ˆํ† : ์ƒ์œ„ 20% ๊ตฌ๊ฐ„(A2, A1)์ด ์ด ์ง€์ฒด์˜ 33%๋ฅผ ์ฐจ์ง€, ์ƒ์œ„ 50% ๊ตฌ๊ฐ„ 70%\n", "\n", "[5] EDA ์ฐจํŠธ 8์ข… ์ƒ์„ฑ ์™„๋ฃŒ(๋ชฉํ‘œ 8์ข… ์ด์ƒ: ๋‹ฌ์„ฑ)\n", "ํ•ต์‹ฌ ๋ฐœ๊ฒฌ: ์ง€์ฒด๋Š” ์ด์šฉ๋ฅ ์˜ ๋น„์„ ํ˜• ํ•จ์ˆ˜์ด๋ฉฐ ์ •๋น„ยทํญ์ฆยท๊ธด๊ธ‰ ๋ถˆ์ถœ์ด ๊ฒน์น˜๋ฉด ๊ธ‰์ฆํ•œ๋‹ค. ์ด๋Š” M4 ํŒŒ์ƒ ํ”ผ์ฒ˜ ์„ค๊ณ„์˜ ๊ทผ๊ฑฐ๊ฐ€ ๋œ๋‹ค.\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 04. ๋„คํŠธ์›Œํฌ ํ๋ฆ„(Max-Flow) ์•Œ๊ณ ๋ฆฌ์ฆ˜ ๊ธฐ๋ฐ˜ ๋ณ‘๋ชฉ ๋ฐœ์ƒ ์ง€์  ์‚ฌ์ „ ์‹๋ณ„ ๋ฐ ์šฐํšŒ๋กœ ํƒ์ƒ‰ ๋ชจ๋ธ ๊ณ ๋„ํ™”\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 3.0์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "๋„คํŠธ์›Œํฌ ํ๋ฆ„ ์•Œ๊ณ ๋ฆฌ์ฆ˜ ๊ธฐ๋ฐ˜ ๋ณ‘๋ชฉ ์‚ฌ์ „ ์‹๋ณ„ ๋ฐ ์šฐํšŒ๋กœ ํƒ์ƒ‰ ๋ชจ๋ธ์„ ๊ณ ๋„ํ™”ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **์ตœ๋Œ€์œ ๋Ÿ‰(Max-Flow) ์•Œ๊ณ ๋ฆฌ์ฆ˜์œผ๋กœ ๋ณ‘๋ชฉ ๊ตฌ๊ฐ„์„ ์‚ฌ์ „์— ์‹๋ณ„ํ•˜๊ณ  ๊ตฌ๊ฐ„ ํ์‡„ ์‹œ ์šฐํšŒ๋กœ๋ฅผ ์ฐพ์œผ๋ฉฐ, ํŒŒ์ƒ ํ”ผ์ฒ˜ ๊ธฐ๋ฐ˜ ๊ธฐ์ค€ ๋ชจ๋ธ์„ ๊ตฌ์ถ•ํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **๋…ธ์„ ๋ง์—์„œ ์ถœ๋ฐœ์ง€์—์„œ ๋ชฉ์ ์ง€๊นŒ์ง€์˜ ์ตœ๋Œ€ ํ†ต๊ณผ๋Šฅ๋ ฅ๊ณผ ์ตœ์†Œ ์ ˆ๋‹จ ๊ตฌ๊ฐ„์„ ๊ตฌํ•˜๊ณ  ๊ตฌ๊ฐ„๋ณ„ ์ฆ์„ค ๋ฏผ๊ฐ๋„๋ฅผ ๋น„๊ต ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `src/features/mov01_feature_engineering.py ๋ฐ ๊ธฐ์ค€ ๋ชจ๋ธ` ํ˜•ํƒœ์˜ ๊ฒฐ๊ณผ๋ฌผ์„ ์™„์„ฑํ•œ๋‹ค.\n", "\n", "### ๐Ÿ” ํ‰๊ฐ€ ๋ฐ ๊ฒ€์ฆ ๊ธฐ์ค€ (PASS ์กฐ๊ฑด)\n", "#### ๋ฏธ์…˜ ํŒ์ •\n", "\n", "**ํ‰๊ฐ€ ๊ตฌ๋ถ„:** ํ•„์ˆ˜\n", "\n", "**ํ•ต์‹ฌ ํ‰๊ฐ€์˜์—ญ:** ๊ตฐ์ˆ˜ ์‹ค๋ฌด์—์„œ ๋‚ฉ๋“ ๊ฐ€๋Šฅํ•œ ํ”ผ์ฒ˜ ์„ค๊ณ„์˜ ๋…ผ๋ฆฌ์„ฑ\n", "\n", "#### ๋ฏธ์…˜ PASS ์กฐ๊ฑด\n", "\n", "- ์ •๋Ÿ‰ ๊ธฐ์ค€: ํŒŒ์ƒ ํ”ผ์ฒ˜ 10๊ฐœ ์ด์ƒ ์ƒ์„ฑ, ๊ธฐ์ค€ ๋ชจ๋ธ MAPE 25% ์ดํ•˜ ๋‹ฌ์„ฑ\n", "\n", "\n", "- ์ง„ํ–‰ ๊ธฐ์ค€: ํ”ผ์ฒ˜ ํŒŒ์ดํ”„๋ผ์ธ ๋ฐ ๋ฒ ์ด์Šค๋ผ์ธ ๋ชจ๋ธ ์„ฑ๋Šฅ ์ง€ํ‘œ ๋„์ถœ\n", "\n", "\n", "- ๊ฒ€์ฆ ๋ฐฉ๋ฒ•: ๊ต์ฐจ ๊ฒ€์ฆ(Time-series Cross Validation)์„ ํ†ตํ•œ ๊ณผ์ ํ•ฉ ๋ฐฉ์ง€\n", "\n", "\n", "#### ์ฑ„์ ์ž ํŒ์ • ๋ฉ”๋ชจ\n", "\n", "> ๋‹จ์ˆœ ์ฝ”๋“œ ์‹คํ–‰ ์—ฌ๋ถ€๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ถœ๋ ฅ๋œ ์ˆ˜์น˜์™€ ๊ทผ๊ฑฐ ์ž๋ฃŒ๊ฐ€ ๊ตฐ ์‹ค๋ฌด ์š”๊ตฌ์‚ฌํ•ญ์— ๋ถ€ํ•ฉํ•˜๋Š”์ง€ ๋ฉด๋ฐ€ํžˆ ํ™•์ธํ•œ๋‹ค.\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\"\"\"์ตœ๋Œ€์œ ๋Ÿ‰(Max-Flow) ์•Œ๊ณ ๋ฆฌ์ฆ˜์œผ๋กœ ๊ฐ€์ƒ ๋…ธ์„ ๋ง์˜ ์ตœ์†Œ ์ ˆ๋‹จ(๋ณ‘๋ชฉ) ๊ตฌ๊ฐ„์„ ์‚ฌ์ „ ์‹๋ณ„ํ•˜๊ณ  ๊ตฌ๊ฐ„ ํ์‡„ ์‹œ ์šฐํšŒ ๊ฒฝ๋กœ๋ฅผ ์ฐพ์œผ๋ฉฐ, ํŒŒ์ƒ ํ”ผ์ฒ˜ 14์ข…๊ณผ ์‹œ๊ณ„์—ด ๊ต์ฐจ๊ฒ€์ฆ์œผ๋กœ ์ง€์ฒด ์˜ˆ์ธก ๊ธฐ์ค€ ๋ชจ๋ธ(MAPE)์„ ๋งŒ๋“ ๋‹ค.\"\"\"\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from scipy.optimize import linprog\n", "from sklearn.ensemble import GradientBoostingRegressor\n", "from sklearn.model_selection import TimeSeriesSplit\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ƒ ๋…ธ์„ ๋งยท๊ตฌ๊ฐ„ ํ†ต๊ณผ๋Ÿ‰ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ๋„๋กœยท์ง€๋ช…ยท๋ถ€๋Œ€ ์—†์Œ)\n", "# ๊ฐ€์ƒ ์ถœ๋ฐœ์ง€(SRC)์—์„œ ๊ฐ€์ƒ ๋ชฉ์ ์ง€(DST)๊นŒ์ง€ 3๊ฐœ ์ถ•์„ (AยทBยทC)๊ณผ ์—ฐ๊ฒฐ๋กœ 2๊ฐœ, ๊ตฌ๊ฐ„ 10๊ฐœ. 150์ผ์น˜ ํ”ผํฌ์‹œ๊ฐ„ ํ†ต๊ณผ๋Ÿ‰ยท์ง€์ฒด ์‹œ๊ฐ„์„ ๋งŒ๋“ ๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "SEGS = pd.DataFrame([\n", " (\"A1\", \"SRC\", \"J1\", 12, 130, \"์ผ๋ฐ˜\"), (\"A2\", \"J1\", \"J2\", 8, 70, \"๊ต๋Ÿ‰\"), (\"A3\", \"J2\", \"DST\", 10, 140, \"๊ต์ฐจ๋กœ\"),\n", " (\"B1\", \"SRC\", \"J3\", 15, 100, \"์ผ๋ฐ˜\"), (\"B2\", \"J3\", \"J2\", 9, 80, \"ํ„ฐ๋„\"), (\"B3\", \"J3\", \"DST\", 14, 95, \"์ผ๋ฐ˜\"),\n", " (\"C1\", \"SRC\", \"J4\", 18, 70, \"์ผ๋ฐ˜\"), (\"C2\", \"J4\", \"DST\", 20, 75, \"๊ต๋Ÿ‰\"),\n", " (\"X1\", \"J1\", \"J3\", 7, 50, \"๊ต์ฐจ๋กœ\"), (\"X2\", \"J4\", \"J3\", 10, 60, \"์ผ๋ฐ˜\")],\n", " columns=[\"seg\", \"u\", \"v\", \"km\", \"cap\", \"kind\"]) # cap = ํ”ผํฌ์‹œ๊ฐ„ ํ†ต๊ณผ ์šฉ๋Ÿ‰(๋Œ€/์‹œ, ๊ต์œก์šฉ ๊ฐ€์ •๊ฐ’)\n", "KSPD = {\"์ผ๋ฐ˜\": 55, \"๊ต๋Ÿ‰\": 40, \"ํ„ฐ๋„\": 45, \"๊ต์ฐจ๋กœ\": 45} # ๊ตฌ๊ฐ„ ์œ ํ˜•๋ณ„ ์ž์œ  ์ฃผํ–‰ ์†๋„(km/h, ๊ฐ€์ •๊ฐ’)\n", "SEGS[\"t0\"] = SEGS[\"km\"] / SEGS[\"kind\"].map(KSPD) * 60 # ์ž์œ  ์ฃผํ–‰ ์‹œ๊ฐ„(๋ถ„)\n", "UTIL = dict(A1=.78, A2=.85, A3=.66, B1=.70, B2=.72, B3=.64, C1=.55, C2=.58, X1=.45, X2=.40) # ํ‰์‹œ ์ด์šฉ๋ฅ \n", "\n", "\n", "def bpr(t0, v, cap, a=0.15, b=4.0):\n", " \"\"\"BPR ์ง€์ฒด ํ•จ์ˆ˜(๊ตํ†ต๊ณตํ•™์˜ ํ‘œ์ค€ํ˜•): ํ†ต๊ณผ๋Ÿ‰/์šฉ๋Ÿ‰ ๋น„๊ฐ€ ์ปค์งˆ์ˆ˜๋ก ์ง€์ฒด๊ฐ€ ๊ธ‰์ฆํ•œ๋‹ค. (๋ถ„)\"\"\"\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 4] ๋„คํŠธ์›Œํฌ ํ๋ฆ„(Max-Flow) ์•Œ๊ณ ๋ฆฌ์ฆ˜ ๊ธฐ๋ฐ˜ ๋ณ‘๋ชฉ ๋ฐœ์ƒ ์ง€์  ์‚ฌ์ „ ์‹๋ณ„ ๋ฐ ์šฐํšŒ๋กœ ํƒ์ƒ‰ ๋ชจ๋ธ ๊ณ ๋„ํ™” ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def make_data(seed=11, days=150):\n", " \"\"\"๊ตฌ๊ฐ„ x ์ผ์ž ํŒจ๋„. v=์‹ค์ œ ํ”ผํฌ ํ†ต๊ณผ๋Ÿ‰, plan_v=์ˆ˜์†ก๊ณ„ํš ์ˆ˜๋Ÿ‰(๊ธด๊ธ‰ ๋ถˆ์ถœ ๋ฏธ๋ฐ˜์˜), delay=์‹ค์ œ ์ง€์ฒด(๋ถ„).\"\"\"\n", " rng = np.random.default_rng(seed)\n", " d_idx = np.arange(days)\n", " lvl = np.array([1.0, 1.08, 1.04, 1.10, 1.02, 0.82, 0.78])[d_idx % 7] * (1 + 0.0012 * d_idx)\n", " lvl = lvl * np.where((d_idx >= 100) & (d_idx < 110), 1.30, 1.0) # ์ˆ˜์†ก๋Ÿ‰ ํญ์ฆ ๊ธฐ๊ฐ„(๊ฐ€์ƒ)\n", " prev, rows = {s: 0.0 for s in SEGS[\"seg\"]}, []\n", " for d in d_idx:\n", " rain = bool(rng.random() < 0.18)\n", " for r in SEGS.itertuples():\n", " maint, urgent = bool(rng.random() < 0.06), bool(rng.random() < 0.05) # ๋น„๊ณ„ํš ์ •๋น„ / ๊ธด๊ธ‰ ๋ถˆ์ถœ\n", " v = r.cap * UTIL[r.seg] * lvl[d] * (1.25 if urgent else 1.0) * rng.normal(1, 0.05)\n", " cap_eff = r.cap * (0.65 if maint else 1.0) * (0.92 if rain else 1.0)\n", " delay = max(r.t0 * 0.2 * (v / cap_eff) ** 3.5 + 0.25 * prev[r.seg] + rng.normal(0, 0.4), 0.0)\n", " prev[r.seg] = delay\n", " rows.append(dict(day=int(d), dow=int(d % 7), seg=r.seg, kind=r.kind, km=r.km, cap=r.cap, t0=r.t0,\n", " plan_v=v / (1.25 if urgent else 1.0) * rng.normal(1, 0.04), v=v, maint=int(maint), urgent=int(urgent),\n", " rain=int(rain), cap_eff=cap_eff, delay=delay))\n", " df = pd.DataFrame(rows)\n", " df[\"transit\"] = df[\"t0\"] * (1 + 0.08 * df[\"rain\"]) + df[\"delay\"]\n", " return df\n", "\n", "\n", "def mape(y, p):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 4] ๋„คํŠธ์›Œํฌ ํ๋ฆ„(Max-Flow) ์•Œ๊ณ ๋ฆฌ์ฆ˜ ๊ธฐ๋ฐ˜ ๋ณ‘๋ชฉ ๋ฐœ์ƒ ์ง€์  ์‚ฌ์ „ ์‹๋ณ„ ๋ฐ ์šฐํšŒ๋กœ ํƒ์ƒ‰ ๋ชจ๋ธ ๊ณ ๋„ํ™” ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def mae(y, p):\n", " return float(np.mean(np.abs(np.asarray(y, float) - np.asarray(p, float))))\n", "# ---------------------------------------------------------------------------\n", "# ---------------------------------------------------------------------------\n", "# ์ง€์ฒด ์˜ˆ์ธก์šฉ ํŒŒ์ƒ ํ”ผ์ฒ˜ ์ƒ์„ฑ(๋‹น์ผ ์˜ค์ „ ์‹œ์ ์— ์•Œ ์ˆ˜ ์žˆ๋Š” ์ •๋ณด๋งŒ ์‚ฌ์šฉ: ๊ณ„ํš ๋ฌผ๋Ÿ‰, ๊ณต์ง€ ์ •๋น„, ๊ธฐ์ƒ ์˜ˆ๋ณด, ์ „์ผยท์ „์ฃผ ์‹ค์ )\n", "# ---------------------------------------------------------------------------\n", "FEATS = [\"plan_v\", \"cap\", \"cap_eff\", \"t0\", \"km\", \"maint\", \"rain\", \"util\", \"util_eff\", \"util_eff4\", \"bpr_base\", \"lag1_delay\", \"lag7_delay\",\n", " \"roll7_v\", \"surge_ratio\", \"is_weekend\", \"dow_sin\", \"dow_cos\", \"k_๊ต๋Ÿ‰\", \"k_ํ„ฐ๋„\", \"k_๊ต์ฐจ๋กœ\"]\n", "\n", "\n", "def add_features(df):\n", " d = df.sort_values([\"seg\", \"day\"]).copy()\n", " g = d.groupby(\"seg\")\n", " d[\"lag1_delay\"] = g[\"delay\"].shift(1)\n", " d[\"lag7_delay\"] = g[\"delay\"].shift(7)\n", " d[\"roll7_v\"] = g[\"v\"].transform(lambda s: s.shift(1).rolling(7).mean())\n", " d[\"util\"] = d[\"plan_v\"] / d[\"cap\"]\n", " d[\"util_eff\"] = d[\"plan_v\"] / d[\"cap_eff\"]\n", " d[\"util_eff4\"] = d[\"util_eff\"] ** 4\n", " d[\"bpr_base\"] = bpr(d[\"t0\"], d[\"plan_v\"], d[\"cap\"])\n", " d[\"surge_ratio\"] = d[\"plan_v\"] / d[\"roll7_v\"]\n", " d[\"is_weekend\"] = (d[\"dow\"] >= 5).astype(int)\n", " d[\"dow_sin\"], d[\"dow_cos\"] = np.sin(2 * np.pi * d[\"dow\"] / 7), np.cos(2 * np.pi * d[\"dow\"] / 7)\n", " for k in (\"๊ต๋Ÿ‰\", \"ํ„ฐ๋„\", \"๊ต์ฐจ๋กœ\"):\n", " d[\"k_\" + k] = (d[\"kind\"] == k).astype(int)\n", " return d.dropna().sort_values([\"day\", \"seg\"]).reset_index(drop=True)\n", "# ---------------------------------------------------------------------------\n", "# ---------------------------------------------------------------------------\n", "# ๋…ธ์„ ๋ง ํ๋ฆ„ ๋„๊ตฌ: ์ตœ๋Œ€์œ ๋Ÿ‰(Edmonds-Karp), ์ฆ๋ถ„ ๋ฐฐ์ •(ํ˜ผ์žก ๋ฐ˜์˜ ์ตœ๋‹จ์‹œ๊ฐ„ ๊ฒฝ๋กœ), ๊ฒฝ๋กœ ์—ด๊ฑฐ\n", "# ---------------------------------------------------------------------------\n", "CAP = dict(zip(SEGS[\"seg\"], SEGS[\"cap\"]))\n", "T0 = dict(zip(SEGS[\"seg\"], SEGS[\"t0\"]))\n", "KM = dict(zip(SEGS[\"seg\"], SEGS[\"km\"]))\n", "ARCS = {r.seg: (r.u, r.v) for r in SEGS.itertuples()}\n", "SRC, DST = \"SRC\", \"DST\"\n", "\n", "\n", "def seg_time(s, flow, cap=None, a=0.2, b=3.5):\n", " \"\"\"๊ตฌ๊ฐ„ ํ†ต๊ณผ์‹œ๊ฐ„(๋ถ„) = ์ž์œ ์ฃผํ–‰ + ํ˜ผ์žก ์ง€์ฒด. cap์„ ์ฃผ๋ฉด ์šฉ๋Ÿ‰์„ ๋Œ€์ฒด(์ •๋น„ยท๊ธฐ์ƒ ๋ฐ˜์˜).\"\"\"\n", " r = flow / (cap or CAP[s])\n", " return T0[s] * (1 + a * r ** b + 6.0 * max(0.0, r - 1.0)) # ์šฉ๋Ÿ‰ ์ดˆ๊ณผ๋ถ„์€ ๋Œ€๊ธฐ์—ด๋กœ ๊ธ‰์ฆ\n", "\n", "\n", "def max_flow(closed=(), capmul=None):\n", " \"\"\"์ตœ๋Œ€์œ ๋Ÿ‰(๋Œ€/์‹œ)๊ณผ ์ตœ์†Œ ์ ˆ๋‹จ ๊ตฌ๊ฐ„(๋ณ‘๋ชฉ). capmul = {๊ตฌ๊ฐ„: ์šฉ๋Ÿ‰ ๋ฐฐ์œจ}.\"\"\"\n", " cap = {s: (0 if s in closed else CAP[s] * (capmul or {}).get(s, 1.0)) for s in CAP}\n", " res = {}\n", " for s, (u, v) in ARCS.items():\n", " res[(u, v)] = res.get((u, v), 0) + cap[s]\n", " res.setdefault((v, u), 0)\n", " flow = 0.0\n", " while True:\n", " prev, q = {SRC: None}, [SRC]\n", " for u in q:\n", " for (a, b), c in res.items():\n", " if a == u and c > 1e-9 and b not in prev:\n", " prev[b] = u; q.append(b)\n", " if DST not in prev:\n", " break\n", " path, x = [], DST\n", " while prev[x]:\n", " path.append((prev[x], x)); x = prev[x]\n", " f = min(res[e] for e in path)\n", " for a, b in path:\n", " res[(a, b)] -= f; res[(b, a)] += f\n", " flow += f\n", " reach = set(prev)\n", " cut = [s for s, (u, v) in ARCS.items() if u in reach and v not in reach and cap[s] > 0]\n", " return flow, cut\n", "\n", "\n", "def all_paths():\n", " \"\"\"SRC -> DST ๋‹จ์ˆœ ๊ฒฝ๋กœ(๊ตฌ๊ฐ„ ์ด๋ฆ„ ํŠœํ”Œ) ์ „์ฒด.\"\"\"\n", " out = []\n", "\n", " def dfs(node, segs, seen):\n", " if node == DST:\n", " out.append(tuple(segs)); return\n", " for s, (u, v) in ARCS.items():\n", " if u == node and v not in seen:\n", " dfs(v, segs + [s], seen | {v})\n", " dfs(SRC, [], {SRC})\n", " return out\n", "\n", "\n", "PATHS = all_paths()\n", "\n", "\n", "def assign(demand, closed=(), capmul=None, steps=40):\n", " \"\"\"์ฆ๋ถ„ ๋ฐฐ์ •: ์ˆ˜์š”๋ฅผ steps์กฐ๊ฐ์œผ๋กœ ๋‚˜๋ˆ , ์ „์ฒด ํ†ต๊ณผ์‹œ๊ฐ„(๋Œ€์ˆ˜ x ์‹œ๊ฐ„) ์ฆ๊ฐ€๊ฐ€ ๊ฐ€์žฅ ์ž‘์€ ๊ฒฝ๋กœ์— ์ฐจ๋ก€๋กœ ์–น๋Š”๋‹ค(์‹œ์Šคํ…œ ์ตœ์  ๊ทผ์‚ฌ).\"\"\"\n", " cap = {s: CAP[s] * (capmul or {}).get(s, 1.0) for s in CAP}\n", " flow, pf = {s: 0.0 for s in CAP}, {}\n", " ok = [p for p in PATHS if not any(s in closed for s in p)]\n", " if not ok:\n", " return None\n", " for _ in range(steps):\n", " chunk = demand / steps\n", " best = min(ok, key=lambda p: sum((flow[s] + chunk) * seg_time(s, flow[s] + chunk, cap[s]) - flow[s] * seg_time(s, flow[s], cap[s]) for s in p))\n", " for s in best:\n", " flow[s] += chunk\n", " pf[best] = pf.get(best, 0) + chunk\n", " tt = {p: sum(seg_time(s, flow[s], cap[s]) for s in p) for p in pf}\n", " avg = sum(pf[p] * tt[p] for p in pf) / demand\n", " return {\"flow\": flow, \"paths\": pf, \"path_min\": tt, \"avg_min\": avg, \"util\": {s: flow[s] / cap[s] for s in cap if cap[s] > 0}}\n", "\n", "\n", "FIXED_SHARE = {(\"A1\", \"A2\", \"A3\"): 0.32, (\"B1\", \"B3\"): 0.40, (\"C1\", \"C2\"): 0.28} # ๊ณผ๊ฑฐ ๊ด€ํ–‰: ์ถ•์„ ๋ณ„ ๊ณ ์ • ๋ฐฐ๋ถ„(๊ฐ€์ƒ)\n", "\n", "\n", "def fixed_assign(demand, closed=(), capmul=None):\n", " \"\"\"๊ณผ๊ฑฐ ๊ด€ํ–‰ ๋ฐฐ๋ถ„: ์ถ•์„ ๋ณ„ ๊ณ ์ • ๋น„์œจ. ๋ง‰ํžŒ ์ถ•์„  ๋ฌผ๋Ÿ‰์€ ์—ด๋ ค ์žˆ๋Š” ์ถ•์„ ์— ๋น„์œจ๋Œ€๋กœ ์žฌ๋ถ„๋ฐฐ.\"\"\"\n", " cap = {s: CAP[s] * (capmul or {}).get(s, 1.0) for s in CAP}\n", " live = {p: w for p, w in FIXED_SHARE.items() if not any(s in closed for s in p)}\n", " if not live:\n", " return None\n", " tot = sum(live.values())\n", " flow = {s: 0.0 for s in CAP}\n", " pf = {p: demand * w / tot for p, w in live.items()}\n", " for p, f in pf.items():\n", " for s in p:\n", " flow[s] += f\n", " tt = {p: sum(seg_time(s, flow[s], cap[s]) for s in p) for p in pf}\n", " return {\"flow\": flow, \"paths\": pf, \"path_min\": tt, \"avg_min\": sum(pf[p] * tt[p] for p in pf) / demand,\n", " \"util\": {s: flow[s] / cap[s] for s in cap if cap[s] > 0}}\n", "\n", "\n", "def lp_assign(demand, closed=(), capmul=None):\n", " \"\"\"์„ ํ˜•๊ณ„ํš(LP): ๊ตฌ๊ฐ„ ์šฉ๋Ÿ‰ ์ƒํ•œ(์ด์šฉ๋ฅ  rho) ์•„๋ž˜์—์„œ ์ž์œ ์ฃผํ–‰์‹œ๊ฐ„ ํ•ฉ์„ ์ตœ์†Œํ™”ํ•˜๋Š” ๊ฒฝ๋กœ๋ณ„ ๋ฌผ๋Ÿ‰. ๋ถˆ๊ฐ€๋Šฅํ•˜๋ฉด rho๋ฅผ 1.0๊นŒ์ง€ ์™„ํ™”.\"\"\"\n", " cap = {s: CAP[s] * (capmul or {}).get(s, 1.0) for s in CAP}\n", " ok = [p for p in PATHS if not any(s in closed for s in p)]\n", " if not ok:\n", " return None\n", " for rho in (0.85, 0.95, 1.0):\n", " r = linprog([sum(T0[s] for s in p) for p in ok], A_ub=[[1.0 if s in p else 0.0 for p in ok] for s in CAP],\n", " b_ub=[rho * cap[s] for s in CAP], A_eq=[[1.0] * len(ok)], b_eq=[demand], bounds=(0, None), method=\"highs\")\n", " if r.status == 0:\n", " break\n", " else:\n", " return assign(demand, closed, capmul) # ์šฉ๋Ÿ‰ ์ดˆ๊ณผ ์ˆ˜์š”: ์ฆ๋ถ„ ๋ฐฐ์ •์œผ๋กœ ๋Œ€์ฒด(๊ณผ๋ถ€ํ•˜ ํ‘œ์‹œ)\n", " flow = {s: sum(r.x[i] for i, p in enumerate(ok) if s in p) for s in CAP}\n", " pf = {p: float(r.x[i]) for i, p in enumerate(ok) if r.x[i] > 1e-6}\n", " tt = {p: sum(seg_time(s, flow[s], cap[s]) for s in p) for p in pf}\n", " return {\"flow\": flow, \"paths\": pf, \"path_min\": tt, \"avg_min\": sum(pf[p] * tt[p] for p in pf) / demand,\n", " \"util\": {s: flow[s] / cap[s] for s in cap if cap[s] > 0}, \"rho\": rho}\n", "\n", "\n", "def cost_of(res, tv=1000, fuel_km=450):\n", " \"\"\"์‹œ๊ฐ„๋‹น ๋น„์šฉ(์›, ๊ต์œก์šฉ ๊ฐ€์ •): ๋Œ€๋‹น ์‹œ๊ฐ„๋น„์šฉ tv์›/๋ถ„ + ์œ ๋ฅ˜๋น„ fuel_km์›/km.\"\"\"\n", " return sum(f * (res[\"path_min\"][p] * tv + sum(KM[s] for s in p) * fuel_km) for p, f in res[\"paths\"].items())\n", "# ---------------------------------------------------------------------------\n", "\n", "# 1) ์ตœ๋Œ€์œ ๋Ÿ‰๊ณผ ๋ณ‘๋ชฉ(์ตœ์†Œ ์ ˆ๋‹จ)\n", "f0, cut0 = max_flow()\n", "print(f\"[1] ๋…ธ์„ ๋ง ์ตœ๋Œ€ ํ†ต๊ณผ๋Šฅ๋ ฅ {f0:.0f}๋Œ€/์‹œ, ์ตœ์†Œ ์ ˆ๋‹จ(๋ณ‘๋ชฉ) ๊ตฌ๊ฐ„: {', '.join(cut0)}\")\n", "gain = []\n", "for s in CAP:\n", " g, _ = max_flow(capmul={s: 1.2})\n", " gain.append((s, SEGS.set_index(\"seg\").loc[s, \"kind\"], CAP[s], g - f0))\n", "gain = pd.DataFrame(gain, columns=[\"๊ตฌ๊ฐ„\", \"์œ ํ˜•\", \"์šฉ๋Ÿ‰\", \"์šฉ๋Ÿ‰ 20% ์ฆ์„ค ์‹œ ์ฆ๊ฐ€(๋Œ€/์‹œ)\"]).sort_values(\"์šฉ๋Ÿ‰ 20% ์ฆ์„ค ์‹œ ์ฆ๊ฐ€(๋Œ€/์‹œ)\", ascending=False)\n", "print(\"[2] ๊ตฌ๊ฐ„๋ณ„ ์ฆ์„ค ๋ฏผ๊ฐ๋„(๋ณ‘๋ชฉ์ผ์ˆ˜๋ก ์ฆ์„ค ํšจ๊ณผ๊ฐ€ ํผ)\")\n", "print(gain.head(5).to_string(index=False))\n", "\n", "# 2) ์‚ฌ์ „ ๋ณ‘๋ชฉ ์‹๋ณ„: ๊ณ„ํš ๋ฌผ๋Ÿ‰ ๊ธฐ์ค€ ์ด์šฉ๋ฅ  >= 0.9์ธ ๊ตฌ๊ฐ„ + ์ตœ์†Œ ์ ˆ๋‹จ ๊ตฌ๊ฐ„\n", "df = make_data(); d = add_features(df)\n", "today = d[d.day == d.day.max()].set_index(\"seg\")\n", "risk = today[(today.util_eff >= 0.9)].index.tolist()\n", "print(f\"\\n[3] ์ตœ์ข…์ผ(day {int(d.day.max())}) ์‚ฌ์ „ ๋ณ‘๋ชฉ ๊ฒฝ๋ณด: ์œ ํšจ ์ด์šฉ๋ฅ  0.9 ์ด์ƒ ๊ตฌ๊ฐ„ {risk or '์—†์Œ'}\")\n", "\n", "# 3) ๊ตฌ๊ฐ„ ํ์‡„ ์‹œ ์šฐํšŒ: ์ตœ๋Œ€์œ ๋Ÿ‰ ๋ณ€ํ™”์™€ ์šฐํšŒ ๊ฒฝ๋กœ\n", "D = 200\n", "print(f\"\\n[4] ๊ตฌ๊ฐ„ ํ์‡„ ์‹œ๋‚˜๋ฆฌ์˜ค(์ˆ˜์š” {D}๋Œ€/์‹œ): ์šฐํšŒ ๊ฒฝ๋กœ์™€ ํ‰๊ท  ํ†ต๊ณผ์‹œ๊ฐ„\")\n", "base = assign(D)\n", "rows = [(\"ํ‰์‹œ\", \"-\", f0, base[\"avg_min\"], \"AยทBยทC ๋ถ„์‚ฐ\", \"์ˆ˜์šฉ\")]\n", "for c in ((\"A2\",), (\"B2\",), (\"A3\",)):\n", " mf, cut = max_flow(c)\n", " r = assign(D, closed=c)\n", " top = max(r[\"paths\"], key=r[\"paths\"].get)\n", " rows.append((f\"{c[0]} ํ์‡„\", \",\".join(cut), mf, r[\"avg_min\"], \"->\".join(top), \"์ˆ˜์šฉ\" if D <= mf else \"๊ณผ๋ถ€ํ•˜\"))\n", "sc = pd.DataFrame(rows, columns=[\"์‹œ๋‚˜๋ฆฌ์˜ค\", \"์ƒˆ ๋ณ‘๋ชฉ\", \"์ตœ๋Œ€์œ ๋Ÿ‰\", \"ํ‰๊ท  ํ†ต๊ณผ(๋ถ„)\", \"์ฃผ ๊ฒฝ๋กœ\", \"์ˆ˜์š” ๋Œ€๋น„\"]).round(1)\n", "print(sc.to_string(index=False))\n", "print(f\" A2 ํ์‡„ ์‹œ ํ‰๊ท  ํ†ต๊ณผ์‹œ๊ฐ„ +{rows[1][3] - rows[0][3]:.1f}๋ถ„, ์ˆ˜์š” {D}๋Œ€/์‹œ ๋Œ€๋น„ ์ตœ๋Œ€์œ ๋Ÿ‰ {rows[1][2]:.0f}๋Œ€/์‹œ -> {rows[1][5]}(๊ณผ๋ถ€ํ•˜๋ฉด ์šฐํšŒ๋งŒ์œผ๋กœ ๋ถ€์กฑํ•˜๋ฏ€๋กœ ๋ฌผ๋Ÿ‰ ์กฐ์ •ยทํƒ€ ์ˆ˜๋‹จ ๊ฒ€ํ† ๊ฐ€ ํ•„์š”)\")\n", "\n", "# 4) ํŒŒ์ƒ ํ”ผ์ฒ˜๋กœ ์ง€์ฒด ์˜ˆ์ธก ๊ธฐ์ค€ ๋ชจ๋ธ: ์‹œ๊ณ„์—ด ๊ต์ฐจ๊ฒ€์ฆ(TimeSeriesSplit)\n", "print(f\"\\n[5] ํŒŒ์ƒ ํ”ผ์ฒ˜ {len([f for f in FEATS if f not in ['plan_v','cap','cap_eff','t0','km','maint','rain']])}๊ฐœ ์ƒ์„ฑ(lagยท์ด์šฉ๋ฅ ยทBPRยทํญ์ฆ๋น„ยท์š”์ผ ์ˆœํ™˜ยท๊ตฌ๊ฐ„ ๋”๋ฏธ ๋“ฑ)\")\n", "days = np.sort(d.day.unique())\n", "tscv = TimeSeriesSplit(n_splits=4)\n", "res = []\n", "for k, (a, b) in enumerate(tscv.split(days)):\n", " tr, te = d[d.day.isin(days[a])], d[d.day.isin(days[b])]\n", " m = GradientBoostingRegressor(n_estimators=150, max_depth=3, learning_rate=0.08, random_state=0).fit(tr[FEATS], tr[\"delay\"])\n", " p = te[\"t0\"] * (1 + 0.08 * te[\"rain\"]) + m.predict(te[FEATS])\n", " pb = te[\"t0\"] + te[\"bpr_base\"]\n", " res.append((k + 1, len(tr), len(te), mape(te[\"transit\"], pb), mape(te[\"transit\"], p)))\n", "cv = pd.DataFrame(res, columns=[\"fold\", \"ํ•™์Šต\", \"๊ฒ€์ฆ\", \"BPR MAPE(%)\", \"๊ธฐ์ค€๋ชจ๋ธ MAPE(%)\"]).round(2)\n", "print(cv.to_string(index=False))\n", "mm = cv[\"๊ธฐ์ค€๋ชจ๋ธ MAPE(%)\"].mean()\n", "print(f\" ๊ต์ฐจ๊ฒ€์ฆ ํ‰๊ท  MAPE {mm:.2f}% (๋ชฉํ‘œ 25% ์ดํ•˜: {'๋‹ฌ์„ฑ' if mm <= 25 else '๋ฏธ๋‹ฌ'}), ํด๋“œ๊ฐ€ ๋’ค๋กœ ๊ฐˆ์ˆ˜๋ก ํ•™์Šต ๊ตฌ๊ฐ„์ด ๊ธธ์–ด์ ธ ๋ฏธ๋ž˜ ์ •๋ณด ๋ˆ„์ˆ˜๊ฐ€ ์—†๋‹ค\")\n", "m = GradientBoostingRegressor(n_estimators=150, max_depth=3, learning_rate=0.08, random_state=0).fit(d[FEATS], d[\"delay\"])\n", "imp = pd.Series(m.feature_importances_, index=FEATS).sort_values(ascending=False)\n", "print(\" ํ”ผ์ฒ˜ ์ค‘์š”๋„ ์ƒ์œ„ 5:\", \", \".join(f\"{k} {v:.2f}\" for k, v in imp.head(5).items()), f\"/ ์ค‘์š”๋„ 1% ๋ฏธ๋งŒ {int((imp < 0.01).sum())}๊ฐœ(์ฐจ์› ์ถ•์†Œ ํ›„๋ณด)\")\n", "\n", "fig, ax = plt.subplots(1, 2, figsize=(12, 4.5))\n", "for s, (u, v) in ARCS.items():\n", " pos = {\"SRC\": (0, 1.5), \"J1\": (1, 3), \"J2\": (2, 3), \"J3\": (1.3, 1.5), \"J4\": (1, 0), \"DST\": (3, 1.5)}\n", " ax[0].plot([pos[u][0], pos[v][0]], [pos[u][1], pos[v][1]], \"r-\" if s in cut0 else \"gray\", lw=2 + CAP[s] / 40)\n", " ax[0].text((pos[u][0] + pos[v][0]) / 2, (pos[u][1] + pos[v][1]) / 2, s, fontsize=9)\n", "ax[0].set_title(\"Network (red = min-cut bottleneck)\"); ax[0].axis(\"off\")\n", "imp.head(10)[::-1].plot.barh(ax=ax[1]); ax[1].set_title(\"Feature importance (top 10)\")\n", "plt.tight_layout(); plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 04 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[1] ๋…ธ์„ ๋ง ์ตœ๋Œ€ ํ†ต๊ณผ๋Šฅ๋ ฅ 290๋Œ€/์‹œ, ์ตœ์†Œ ์ ˆ๋‹จ(๋ณ‘๋ชฉ) ๊ตฌ๊ฐ„: A2, B1, C1, X1\n", "[2] ๊ตฌ๊ฐ„๋ณ„ ์ฆ์„ค ๋ฏผ๊ฐ๋„(๋ณ‘๋ชฉ์ผ์ˆ˜๋ก ์ฆ์„ค ํšจ๊ณผ๊ฐ€ ํผ)\n", "๊ตฌ๊ฐ„ ์œ ํ˜• ์šฉ๋Ÿ‰ ์šฉ๋Ÿ‰ 20% ์ฆ์„ค ์‹œ ์ฆ๊ฐ€(๋Œ€/์‹œ)\n", "B1 ์ผ๋ฐ˜ 100 15.0\n", "C1 ์ผ๋ฐ˜ 70 14.0\n", "X1 ๊ต์ฐจ๋กœ 50 10.0\n", "A2 ๊ต๋Ÿ‰ 70 10.0\n", "A1 ์ผ๋ฐ˜ 130 0.0\n", "\n", "[3] ์ตœ์ข…์ผ(day 149) ์‚ฌ์ „ ๋ณ‘๋ชฉ ๊ฒฝ๋ณด: ์œ ํšจ ์ด์šฉ๋ฅ  0.9 ์ด์ƒ ๊ตฌ๊ฐ„ ['A1', 'A2']\n", "\n", "[4] ๊ตฌ๊ฐ„ ํ์‡„ ์‹œ๋‚˜๋ฆฌ์˜ค(์ˆ˜์š” 200๋Œ€/์‹œ): ์šฐํšŒ ๊ฒฝ๋กœ์™€ ํ‰๊ท  ํ†ต๊ณผ์‹œ๊ฐ„\n", " ์‹œ๋‚˜๋ฆฌ์˜ค ์ƒˆ ๋ณ‘๋ชฉ ์ตœ๋Œ€์œ ๋Ÿ‰ ํ‰๊ท  ํ†ต๊ณผ(๋ถ„) ์ฃผ ๊ฒฝ๋กœ ์ˆ˜์š” ๋Œ€๋น„\n", " ํ‰์‹œ - 290.0 40.9 AยทBยทC ๋ถ„์‚ฐ ์ˆ˜์šฉ\n", "A2 ํ์‡„ B1,C1,X1 220.0 44.9 B1->B3 ์ˆ˜์šฉ\n", "B2 ํ์‡„ A2,B3,C1 235.0 41.0 B1->B3 ์ˆ˜์šฉ\n", "A3 ํ์‡„ B3,C1 165.0 70.9 B1->B3 ๊ณผ๋ถ€ํ•˜\n", " A2 ํ์‡„ ์‹œ ํ‰๊ท  ํ†ต๊ณผ์‹œ๊ฐ„ +4.0๋ถ„, ์ˆ˜์š” 200๋Œ€/์‹œ ๋Œ€๋น„ ์ตœ๋Œ€์œ ๋Ÿ‰ 220๋Œ€/์‹œ -> ์ˆ˜์šฉ(๊ณผ๋ถ€ํ•˜๋ฉด ์šฐํšŒ๋งŒ์œผ๋กœ ๋ถ€์กฑํ•˜๋ฏ€๋กœ ๋ฌผ๋Ÿ‰ ์กฐ์ •ยทํƒ€ ์ˆ˜๋‹จ ๊ฒ€ํ† ๊ฐ€ ํ•„์š”)\n", "\n", "[5] ํŒŒ์ƒ ํ”ผ์ฒ˜ 14๊ฐœ ์ƒ์„ฑ(lagยท์ด์šฉ๋ฅ ยทBPRยทํญ์ฆ๋น„ยท์š”์ผ ์ˆœํ™˜ยท๊ตฌ๊ฐ„ ๋”๋ฏธ ๋“ฑ)\n", " fold ํ•™์Šต ๊ฒ€์ฆ BPR MAPE(%) ๊ธฐ์ค€๋ชจ๋ธ MAPE(%)\n", " 1 310 280 6.72 3.00\n", " 2 590 280 6.02 2.54\n", " 3 870 280 9.16 3.93\n", " 4 1150 280 9.24 2.78\n", " ๊ต์ฐจ๊ฒ€์ฆ ํ‰๊ท  MAPE 3.06% (๋ชฉํ‘œ 25% ์ดํ•˜: ๋‹ฌ์„ฑ), ํด๋“œ๊ฐ€ ๋’ค๋กœ ๊ฐˆ์ˆ˜๋ก ํ•™์Šต ๊ตฌ๊ฐ„์ด ๊ธธ์–ด์ ธ ๋ฏธ๋ž˜ ์ •๋ณด ๋ˆ„์ˆ˜๊ฐ€ ์—†๋‹ค\n", " ํ”ผ์ฒ˜ ์ค‘์š”๋„ ์ƒ์œ„ 5: util_eff 0.35, util_eff4 0.34, bpr_base 0.13, lag1_delay 0.09, km 0.02 / ์ค‘์š”๋„ 1% ๋ฏธ๋งŒ 14๊ฐœ(์ฐจ์› ์ถ•์†Œ ํ›„๋ณด)\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 05. ์šฐํšŒ ์ˆ˜์†ก ์‹œ ์ถ”๊ฐ€ ์†Œ์š” ์‹œ๊ฐ„ ๋ฐ ์œ ๋ฅ˜ ์†Œ๋ชจ๋Ÿ‰ ์‚ฐ์ถœ ์—”์ง„ ๊ฐœ๋ฐœ\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 3.5์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "์šฐํšŒ ์ˆ˜์†ก ์‹œ ์ถ”๊ฐ€ ์†Œ์š” ์‹œ๊ฐ„ ๋ฐ ์œ ๋ฅ˜ ์†Œ๋ชจ๋Ÿ‰ ์‚ฐ์ถœ ์—”์ง„์„ ๊ฐœ๋ฐœํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **์šฐํšŒ ์ˆ˜์†ก ์‹œ ์ถ”๊ฐ€ ์†Œ์š” ์‹œ๊ฐ„๊ณผ ์œ ๋ฅ˜ ์†Œ๋ชจ๋Ÿ‰์„ ์‚ฐ์ถœํ•˜๋Š” ์—”์ง„์„ ๊ฐœ๋ฐœํ•˜๊ณ , ๊ธฐ์ค€ ๋ชจ๋ธ ๋Œ€๋น„ ์•™์ƒ๋ธ” ์˜ˆ์ธก ๋ชจ๋ธ์˜ ๊ฐœ์„ ์œจ์„ ๊ฒ€์ฆํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **์ฆ๋ถ„ ๋ฐฐ์ •์œผ๋กœ ๊ตฌ๊ฐ„ ํ์‡„ยท์ •๋น„ ์‹œ๋‚˜๋ฆฌ์˜ค๋ณ„ ์šฐํšŒ ๊ฒฝ๋กœ๋ฅผ ๊ตฌํ•˜๊ณ  ๋Œ€๋‹น ์ถ”๊ฐ€ ์†Œ์š” ์‹œ๊ฐ„, ์ถ”๊ฐ€ ๊ฑฐ๋ฆฌ, ์ถ”๊ฐ€ ์œ ๋ฅ˜์™€ ์ผ ์ถ”๊ฐ€ ์œ ๋ฅ˜๋Ÿ‰ ์‚ฐ์ถœํ•˜๊ณ  ๋ชจ๋ธ์— ์ ์šฉํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `src/models/mov01_advanced_model.py` ํ˜•ํƒœ์˜ ๊ฒฐ๊ณผ๋ฌผ์„ ์™„์„ฑํ•œ๋‹ค.\n", "\n", "### ๐Ÿ” ํ‰๊ฐ€ ๋ฐ ๊ฒ€์ฆ ๊ธฐ์ค€ (PASS ์กฐ๊ฑด)\n", "#### ๋ฏธ์…˜ ํŒ์ •\n", "\n", "**ํ‰๊ฐ€ ๊ตฌ๋ถ„:** ํ•„์ˆ˜\n", "\n", "**ํ•ต์‹ฌ ํ‰๊ฐ€์˜์—ญ:** ๋น„์ •ํ˜•/๋ถˆ๊ทœ์น™ ์ˆ˜์š”์— ๋Œ€ํ•œ ๋ชจ๋ธ์˜ ์ ์‘๋ ฅ ๋ฐ ์˜ˆ์ธก ์•ˆ์ •์„ฑ\n", "\n", "#### ๋ฏธ์…˜ PASS ์กฐ๊ฑด\n", "\n", "- ์ •๋Ÿ‰ ๊ธฐ์ค€: ๊ธฐ์ค€ ๋ชจ๋ธ ๋Œ€๋น„ ์˜ค์ฐจ์œจ(MAE/RMSE) 20% ์ด์ƒ ๊ฐœ์„ , R2 Score 0.85 ์ด์ƒ\n", "\n", "\n", "- ์ง„ํ–‰ ๊ธฐ์ค€: ๋ชจ๋ธ ํ•˜์ดํผํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ์™„๋ฃŒ ๋ฐ ์ตœ์  ๋ชจ๋ธ ๊ฐ€์ค‘์น˜ ์ €์žฅ\n", "\n", "\n", "- ๊ฒ€์ฆ ๋ฐฉ๋ฒ•: Hold-out ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ์…‹ ๋Œ€์ƒ ์ตœ์ข… ์„ฑ๋Šฅ ํ‰๊ฐ€\n", "\n", "\n", "#### ์ฑ„์ ์ž ํŒ์ • ๋ฉ”๋ชจ\n", "\n", "> ๋‹จ์ˆœ ์ฝ”๋“œ ์‹คํ–‰ ์—ฌ๋ถ€๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ถœ๋ ฅ๋œ ์ˆ˜์น˜์™€ ๊ทผ๊ฑฐ ์ž๋ฃŒ๊ฐ€ ๊ตฐ ์‹ค๋ฌด ์š”๊ตฌ์‚ฌํ•ญ์— ๋ถ€ํ•ฉํ•˜๋Š”์ง€ ๋ฉด๋ฐ€ํžˆ ํ™•์ธํ•œ๋‹ค.\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\"\"\"์šฐํšŒ ์ˆ˜์†ก ์‹œ ์ถ”๊ฐ€ ์†Œ์š” ์‹œ๊ฐ„๊ณผ ์œ ๋ฅ˜ ์†Œ๋ชจ๋Ÿ‰์„ ์‚ฐ์ถœํ•˜๋Š” ์—”์ง„์„ ๊ตฌํ˜„ํ•˜๊ณ , ๊ธฐ์ค€ ๋ชจ๋ธ(BPR) ๋Œ€๋น„ ์•™์ƒ๋ธ” ์ง€์ฒด ์˜ˆ์ธก ๋ชจ๋ธ์˜ ์˜ค์ฐจ ๊ฐœ์„ ์œจ๊ณผ R2๋ฅผ Hold-out ๋ฐ์ดํ„ฐ๋กœ ๊ฒ€์ฆํ•œ๋‹ค.\"\"\"\n", "import numpy as np\n", "import pandas as pd\n", "from scipy.optimize import linprog\n", "from sklearn.ensemble import GradientBoostingRegressor, RandomForestRegressor\n", "from sklearn.metrics import r2_score\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ƒ ๋…ธ์„ ๋งยท๊ตฌ๊ฐ„ ํ†ต๊ณผ๋Ÿ‰ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ๋„๋กœยท์ง€๋ช…ยท๋ถ€๋Œ€ ์—†์Œ)\n", "# ๊ฐ€์ƒ ์ถœ๋ฐœ์ง€(SRC)์—์„œ ๊ฐ€์ƒ ๋ชฉ์ ์ง€(DST)๊นŒ์ง€ 3๊ฐœ ์ถ•์„ (AยทBยทC)๊ณผ ์—ฐ๊ฒฐ๋กœ 2๊ฐœ, ๊ตฌ๊ฐ„ 10๊ฐœ. 150์ผ์น˜ ํ”ผํฌ์‹œ๊ฐ„ ํ†ต๊ณผ๋Ÿ‰ยท์ง€์ฒด ์‹œ๊ฐ„์„ ๋งŒ๋“ ๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "SEGS = pd.DataFrame([\n", " (\"A1\", \"SRC\", \"J1\", 12, 130, \"์ผ๋ฐ˜\"), (\"A2\", \"J1\", \"J2\", 8, 70, \"๊ต๋Ÿ‰\"), (\"A3\", \"J2\", \"DST\", 10, 140, \"๊ต์ฐจ๋กœ\"),\n", " (\"B1\", \"SRC\", \"J3\", 15, 100, \"์ผ๋ฐ˜\"), (\"B2\", \"J3\", \"J2\", 9, 80, \"ํ„ฐ๋„\"), (\"B3\", \"J3\", \"DST\", 14, 95, \"์ผ๋ฐ˜\"),\n", " (\"C1\", \"SRC\", \"J4\", 18, 70, \"์ผ๋ฐ˜\"), (\"C2\", \"J4\", \"DST\", 20, 75, \"๊ต๋Ÿ‰\"),\n", " (\"X1\", \"J1\", \"J3\", 7, 50, \"๊ต์ฐจ๋กœ\"), (\"X2\", \"J4\", \"J3\", 10, 60, \"์ผ๋ฐ˜\")],\n", " columns=[\"seg\", \"u\", \"v\", \"km\", \"cap\", \"kind\"]) # cap = ํ”ผํฌ์‹œ๊ฐ„ ํ†ต๊ณผ ์šฉ๋Ÿ‰(๋Œ€/์‹œ, ๊ต์œก์šฉ ๊ฐ€์ •๊ฐ’)\n", "KSPD = {\"์ผ๋ฐ˜\": 55, \"๊ต๋Ÿ‰\": 40, \"ํ„ฐ๋„\": 45, \"๊ต์ฐจ๋กœ\": 45} # ๊ตฌ๊ฐ„ ์œ ํ˜•๋ณ„ ์ž์œ  ์ฃผํ–‰ ์†๋„(km/h, ๊ฐ€์ •๊ฐ’)\n", "SEGS[\"t0\"] = SEGS[\"km\"] / SEGS[\"kind\"].map(KSPD) * 60 # ์ž์œ  ์ฃผํ–‰ ์‹œ๊ฐ„(๋ถ„)\n", "UTIL = dict(A1=.78, A2=.85, A3=.66, B1=.70, B2=.72, B3=.64, C1=.55, C2=.58, X1=.45, X2=.40) # ํ‰์‹œ ์ด์šฉ๋ฅ \n", "\n", "\n", "def bpr(t0, v, cap, a=0.15, b=4.0):\n", " \"\"\"BPR ์ง€์ฒด ํ•จ์ˆ˜(๊ตํ†ต๊ณตํ•™์˜ ํ‘œ์ค€ํ˜•): ํ†ต๊ณผ๋Ÿ‰/์šฉ๋Ÿ‰ ๋น„๊ฐ€ ์ปค์งˆ์ˆ˜๋ก ์ง€์ฒด๊ฐ€ ๊ธ‰์ฆํ•œ๋‹ค. (๋ถ„)\"\"\"\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 5] ์šฐํšŒ ์ˆ˜์†ก ์‹œ ์ถ”๊ฐ€ ์†Œ์š” ์‹œ๊ฐ„ ๋ฐ ์œ ๋ฅ˜ ์†Œ๋ชจ๋Ÿ‰ ์‚ฐ์ถœ ์—”์ง„ ๊ฐœ๋ฐœ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def make_data(seed=11, days=150):\n", " \"\"\"๊ตฌ๊ฐ„ x ์ผ์ž ํŒจ๋„. v=์‹ค์ œ ํ”ผํฌ ํ†ต๊ณผ๋Ÿ‰, plan_v=์ˆ˜์†ก๊ณ„ํš ์ˆ˜๋Ÿ‰(๊ธด๊ธ‰ ๋ถˆ์ถœ ๋ฏธ๋ฐ˜์˜), delay=์‹ค์ œ ์ง€์ฒด(๋ถ„).\"\"\"\n", " rng = np.random.default_rng(seed)\n", " d_idx = np.arange(days)\n", " lvl = np.array([1.0, 1.08, 1.04, 1.10, 1.02, 0.82, 0.78])[d_idx % 7] * (1 + 0.0012 * d_idx)\n", " lvl = lvl * np.where((d_idx >= 100) & (d_idx < 110), 1.30, 1.0) # ์ˆ˜์†ก๋Ÿ‰ ํญ์ฆ ๊ธฐ๊ฐ„(๊ฐ€์ƒ)\n", " prev, rows = {s: 0.0 for s in SEGS[\"seg\"]}, []\n", " for d in d_idx:\n", " rain = bool(rng.random() < 0.18)\n", " for r in SEGS.itertuples():\n", " maint, urgent = bool(rng.random() < 0.06), bool(rng.random() < 0.05) # ๋น„๊ณ„ํš ์ •๋น„ / ๊ธด๊ธ‰ ๋ถˆ์ถœ\n", " v = r.cap * UTIL[r.seg] * lvl[d] * (1.25 if urgent else 1.0) * rng.normal(1, 0.05)\n", " cap_eff = r.cap * (0.65 if maint else 1.0) * (0.92 if rain else 1.0)\n", " delay = max(r.t0 * 0.2 * (v / cap_eff) ** 3.5 + 0.25 * prev[r.seg] + rng.normal(0, 0.4), 0.0)\n", " prev[r.seg] = delay\n", " rows.append(dict(day=int(d), dow=int(d % 7), seg=r.seg, kind=r.kind, km=r.km, cap=r.cap, t0=r.t0,\n", " plan_v=v / (1.25 if urgent else 1.0) * rng.normal(1, 0.04), v=v, maint=int(maint), urgent=int(urgent),\n", " rain=int(rain), cap_eff=cap_eff, delay=delay))\n", " df = pd.DataFrame(rows)\n", " df[\"transit\"] = df[\"t0\"] * (1 + 0.08 * df[\"rain\"]) + df[\"delay\"]\n", " return df\n", "\n", "\n", "def mape(y, p):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 5] ์šฐํšŒ ์ˆ˜์†ก ์‹œ ์ถ”๊ฐ€ ์†Œ์š” ์‹œ๊ฐ„ ๋ฐ ์œ ๋ฅ˜ ์†Œ๋ชจ๋Ÿ‰ ์‚ฐ์ถœ ์—”์ง„ ๊ฐœ๋ฐœ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def mae(y, p):\n", " return float(np.mean(np.abs(np.asarray(y, float) - np.asarray(p, float))))\n", "# ---------------------------------------------------------------------------\n", "# ---------------------------------------------------------------------------\n", "# ์ง€์ฒด ์˜ˆ์ธก์šฉ ํŒŒ์ƒ ํ”ผ์ฒ˜ ์ƒ์„ฑ(๋‹น์ผ ์˜ค์ „ ์‹œ์ ์— ์•Œ ์ˆ˜ ์žˆ๋Š” ์ •๋ณด๋งŒ ์‚ฌ์šฉ: ๊ณ„ํš ๋ฌผ๋Ÿ‰, ๊ณต์ง€ ์ •๋น„, ๊ธฐ์ƒ ์˜ˆ๋ณด, ์ „์ผยท์ „์ฃผ ์‹ค์ )\n", "# ---------------------------------------------------------------------------\n", "FEATS = [\"plan_v\", \"cap\", \"cap_eff\", \"t0\", \"km\", \"maint\", \"rain\", \"util\", \"util_eff\", \"util_eff4\", \"bpr_base\", \"lag1_delay\", \"lag7_delay\",\n", " \"roll7_v\", \"surge_ratio\", \"is_weekend\", \"dow_sin\", \"dow_cos\", \"k_๊ต๋Ÿ‰\", \"k_ํ„ฐ๋„\", \"k_๊ต์ฐจ๋กœ\"]\n", "\n", "\n", "def add_features(df):\n", " d = df.sort_values([\"seg\", \"day\"]).copy()\n", " g = d.groupby(\"seg\")\n", " d[\"lag1_delay\"] = g[\"delay\"].shift(1)\n", " d[\"lag7_delay\"] = g[\"delay\"].shift(7)\n", " d[\"roll7_v\"] = g[\"v\"].transform(lambda s: s.shift(1).rolling(7).mean())\n", " d[\"util\"] = d[\"plan_v\"] / d[\"cap\"]\n", " d[\"util_eff\"] = d[\"plan_v\"] / d[\"cap_eff\"]\n", " d[\"util_eff4\"] = d[\"util_eff\"] ** 4\n", " d[\"bpr_base\"] = bpr(d[\"t0\"], d[\"plan_v\"], d[\"cap\"])\n", " d[\"surge_ratio\"] = d[\"plan_v\"] / d[\"roll7_v\"]\n", " d[\"is_weekend\"] = (d[\"dow\"] >= 5).astype(int)\n", " d[\"dow_sin\"], d[\"dow_cos\"] = np.sin(2 * np.pi * d[\"dow\"] / 7), np.cos(2 * np.pi * d[\"dow\"] / 7)\n", " for k in (\"๊ต๋Ÿ‰\", \"ํ„ฐ๋„\", \"๊ต์ฐจ๋กœ\"):\n", " d[\"k_\" + k] = (d[\"kind\"] == k).astype(int)\n", " return d.dropna().sort_values([\"day\", \"seg\"]).reset_index(drop=True)\n", "# ---------------------------------------------------------------------------\n", "# ---------------------------------------------------------------------------\n", "# ๋…ธ์„ ๋ง ํ๋ฆ„ ๋„๊ตฌ: ์ตœ๋Œ€์œ ๋Ÿ‰(Edmonds-Karp), ์ฆ๋ถ„ ๋ฐฐ์ •(ํ˜ผ์žก ๋ฐ˜์˜ ์ตœ๋‹จ์‹œ๊ฐ„ ๊ฒฝ๋กœ), ๊ฒฝ๋กœ ์—ด๊ฑฐ\n", "# ---------------------------------------------------------------------------\n", "CAP = dict(zip(SEGS[\"seg\"], SEGS[\"cap\"]))\n", "T0 = dict(zip(SEGS[\"seg\"], SEGS[\"t0\"]))\n", "KM = dict(zip(SEGS[\"seg\"], SEGS[\"km\"]))\n", "ARCS = {r.seg: (r.u, r.v) for r in SEGS.itertuples()}\n", "SRC, DST = \"SRC\", \"DST\"\n", "\n", "\n", "def seg_time(s, flow, cap=None, a=0.2, b=3.5):\n", " \"\"\"๊ตฌ๊ฐ„ ํ†ต๊ณผ์‹œ๊ฐ„(๋ถ„) = ์ž์œ ์ฃผํ–‰ + ํ˜ผ์žก ์ง€์ฒด. cap์„ ์ฃผ๋ฉด ์šฉ๋Ÿ‰์„ ๋Œ€์ฒด(์ •๋น„ยท๊ธฐ์ƒ ๋ฐ˜์˜).\"\"\"\n", " r = flow / (cap or CAP[s])\n", " return T0[s] * (1 + a * r ** b + 6.0 * max(0.0, r - 1.0)) # ์šฉ๋Ÿ‰ ์ดˆ๊ณผ๋ถ„์€ ๋Œ€๊ธฐ์—ด๋กœ ๊ธ‰์ฆ\n", "\n", "\n", "def max_flow(closed=(), capmul=None):\n", " \"\"\"์ตœ๋Œ€์œ ๋Ÿ‰(๋Œ€/์‹œ)๊ณผ ์ตœ์†Œ ์ ˆ๋‹จ ๊ตฌ๊ฐ„(๋ณ‘๋ชฉ). capmul = {๊ตฌ๊ฐ„: ์šฉ๋Ÿ‰ ๋ฐฐ์œจ}.\"\"\"\n", " cap = {s: (0 if s in closed else CAP[s] * (capmul or {}).get(s, 1.0)) for s in CAP}\n", " res = {}\n", " for s, (u, v) in ARCS.items():\n", " res[(u, v)] = res.get((u, v), 0) + cap[s]\n", " res.setdefault((v, u), 0)\n", " flow = 0.0\n", " while True:\n", " prev, q = {SRC: None}, [SRC]\n", " for u in q:\n", " for (a, b), c in res.items():\n", " if a == u and c > 1e-9 and b not in prev:\n", " prev[b] = u; q.append(b)\n", " if DST not in prev:\n", " break\n", " path, x = [], DST\n", " while prev[x]:\n", " path.append((prev[x], x)); x = prev[x]\n", " f = min(res[e] for e in path)\n", " for a, b in path:\n", " res[(a, b)] -= f; res[(b, a)] += f\n", " flow += f\n", " reach = set(prev)\n", " cut = [s for s, (u, v) in ARCS.items() if u in reach and v not in reach and cap[s] > 0]\n", " return flow, cut\n", "\n", "\n", "def all_paths():\n", " \"\"\"SRC -> DST ๋‹จ์ˆœ ๊ฒฝ๋กœ(๊ตฌ๊ฐ„ ์ด๋ฆ„ ํŠœํ”Œ) ์ „์ฒด.\"\"\"\n", " out = []\n", "\n", " def dfs(node, segs, seen):\n", " if node == DST:\n", " out.append(tuple(segs)); return\n", " for s, (u, v) in ARCS.items():\n", " if u == node and v not in seen:\n", " dfs(v, segs + [s], seen | {v})\n", " dfs(SRC, [], {SRC})\n", " return out\n", "\n", "\n", "PATHS = all_paths()\n", "\n", "\n", "def assign(demand, closed=(), capmul=None, steps=40):\n", " \"\"\"์ฆ๋ถ„ ๋ฐฐ์ •: ์ˆ˜์š”๋ฅผ steps์กฐ๊ฐ์œผ๋กœ ๋‚˜๋ˆ , ์ „์ฒด ํ†ต๊ณผ์‹œ๊ฐ„(๋Œ€์ˆ˜ x ์‹œ๊ฐ„) ์ฆ๊ฐ€๊ฐ€ ๊ฐ€์žฅ ์ž‘์€ ๊ฒฝ๋กœ์— ์ฐจ๋ก€๋กœ ์–น๋Š”๋‹ค(์‹œ์Šคํ…œ ์ตœ์  ๊ทผ์‚ฌ).\"\"\"\n", " cap = {s: CAP[s] * (capmul or {}).get(s, 1.0) for s in CAP}\n", " flow, pf = {s: 0.0 for s in CAP}, {}\n", " ok = [p for p in PATHS if not any(s in closed for s in p)]\n", " if not ok:\n", " return None\n", " for _ in range(steps):\n", " chunk = demand / steps\n", " best = min(ok, key=lambda p: sum((flow[s] + chunk) * seg_time(s, flow[s] + chunk, cap[s]) - flow[s] * seg_time(s, flow[s], cap[s]) for s in p))\n", " for s in best:\n", " flow[s] += chunk\n", " pf[best] = pf.get(best, 0) + chunk\n", " tt = {p: sum(seg_time(s, flow[s], cap[s]) for s in p) for p in pf}\n", " avg = sum(pf[p] * tt[p] for p in pf) / demand\n", " return {\"flow\": flow, \"paths\": pf, \"path_min\": tt, \"avg_min\": avg, \"util\": {s: flow[s] / cap[s] for s in cap if cap[s] > 0}}\n", "\n", "\n", "FIXED_SHARE = {(\"A1\", \"A2\", \"A3\"): 0.32, (\"B1\", \"B3\"): 0.40, (\"C1\", \"C2\"): 0.28} # ๊ณผ๊ฑฐ ๊ด€ํ–‰: ์ถ•์„ ๋ณ„ ๊ณ ์ • ๋ฐฐ๋ถ„(๊ฐ€์ƒ)\n", "\n", "\n", "def fixed_assign(demand, closed=(), capmul=None):\n", " \"\"\"๊ณผ๊ฑฐ ๊ด€ํ–‰ ๋ฐฐ๋ถ„: ์ถ•์„ ๋ณ„ ๊ณ ์ • ๋น„์œจ. ๋ง‰ํžŒ ์ถ•์„  ๋ฌผ๋Ÿ‰์€ ์—ด๋ ค ์žˆ๋Š” ์ถ•์„ ์— ๋น„์œจ๋Œ€๋กœ ์žฌ๋ถ„๋ฐฐ.\"\"\"\n", " cap = {s: CAP[s] * (capmul or {}).get(s, 1.0) for s in CAP}\n", " live = {p: w for p, w in FIXED_SHARE.items() if not any(s in closed for s in p)}\n", " if not live:\n", " return None\n", " tot = sum(live.values())\n", " flow = {s: 0.0 for s in CAP}\n", " pf = {p: demand * w / tot for p, w in live.items()}\n", " for p, f in pf.items():\n", " for s in p:\n", " flow[s] += f\n", " tt = {p: sum(seg_time(s, flow[s], cap[s]) for s in p) for p in pf}\n", " return {\"flow\": flow, \"paths\": pf, \"path_min\": tt, \"avg_min\": sum(pf[p] * tt[p] for p in pf) / demand,\n", " \"util\": {s: flow[s] / cap[s] for s in cap if cap[s] > 0}}\n", "\n", "\n", "def lp_assign(demand, closed=(), capmul=None):\n", " \"\"\"์„ ํ˜•๊ณ„ํš(LP): ๊ตฌ๊ฐ„ ์šฉ๋Ÿ‰ ์ƒํ•œ(์ด์šฉ๋ฅ  rho) ์•„๋ž˜์—์„œ ์ž์œ ์ฃผํ–‰์‹œ๊ฐ„ ํ•ฉ์„ ์ตœ์†Œํ™”ํ•˜๋Š” ๊ฒฝ๋กœ๋ณ„ ๋ฌผ๋Ÿ‰. ๋ถˆ๊ฐ€๋Šฅํ•˜๋ฉด rho๋ฅผ 1.0๊นŒ์ง€ ์™„ํ™”.\"\"\"\n", " cap = {s: CAP[s] * (capmul or {}).get(s, 1.0) for s in CAP}\n", " ok = [p for p in PATHS if not any(s in closed for s in p)]\n", " if not ok:\n", " return None\n", " for rho in (0.85, 0.95, 1.0):\n", " r = linprog([sum(T0[s] for s in p) for p in ok], A_ub=[[1.0 if s in p else 0.0 for p in ok] for s in CAP],\n", " b_ub=[rho * cap[s] for s in CAP], A_eq=[[1.0] * len(ok)], b_eq=[demand], bounds=(0, None), method=\"highs\")\n", " if r.status == 0:\n", " break\n", " else:\n", " return assign(demand, closed, capmul) # ์šฉ๋Ÿ‰ ์ดˆ๊ณผ ์ˆ˜์š”: ์ฆ๋ถ„ ๋ฐฐ์ •์œผ๋กœ ๋Œ€์ฒด(๊ณผ๋ถ€ํ•˜ ํ‘œ์‹œ)\n", " flow = {s: sum(r.x[i] for i, p in enumerate(ok) if s in p) for s in CAP}\n", " pf = {p: float(r.x[i]) for i, p in enumerate(ok) if r.x[i] > 1e-6}\n", " tt = {p: sum(seg_time(s, flow[s], cap[s]) for s in p) for p in pf}\n", " return {\"flow\": flow, \"paths\": pf, \"path_min\": tt, \"avg_min\": sum(pf[p] * tt[p] for p in pf) / demand,\n", " \"util\": {s: flow[s] / cap[s] for s in cap if cap[s] > 0}, \"rho\": rho}\n", "\n", "\n", "def cost_of(res, tv=1000, fuel_km=450):\n", " \"\"\"์‹œ๊ฐ„๋‹น ๋น„์šฉ(์›, ๊ต์œก์šฉ ๊ฐ€์ •): ๋Œ€๋‹น ์‹œ๊ฐ„๋น„์šฉ tv์›/๋ถ„ + ์œ ๋ฅ˜๋น„ fuel_km์›/km.\"\"\"\n", " return sum(f * (res[\"path_min\"][p] * tv + sum(KM[s] for s in p) * fuel_km) for p, f in res[\"paths\"].items())\n", "# ---------------------------------------------------------------------------\n", "\n", "# ---------------- 1) ์šฐํšŒ ์—”์ง„: ์ถ”๊ฐ€ ์†Œ์š” ์‹œ๊ฐ„ยท์œ ๋ฅ˜ (๊ฐ€์ •๊ฐ’: ๋Œ€๋‹น ์ฃผํ–‰ L/km, ์ •์ฒด ๊ณตํšŒ์ „ L/๋ถ„) ----------------\n", "L_KM, L_IDLE = 0.30, 0.08 # ๊ต์œก์šฉ ๊ฐ€์ • ์œ ๋ฅ˜ ๊ณ„์ˆ˜(์‹ค์ œ ์žฅ๋น„ ์ œ์› ์•„๋‹˜)\n", "\n", "\n", "def trip(res, fleet_vehicles):\n", " \"\"\"๊ฒฝ๋กœ๋ณ„ ๋ฌผ๋Ÿ‰ ๋น„์œจ๋กœ ํ‰๊ท  ํ†ต๊ณผ์‹œ๊ฐ„ยท๊ฑฐ๋ฆฌยท์œ ๋ฅ˜(๋Œ€๋‹น L)๋ฅผ ๊ตฌํ•˜๊ณ  ์ด๋Ÿ‰์œผ๋กœ ํ™˜์‚ฐํ•œ๋‹ค.\"\"\"\n", " tot = sum(res[\"paths\"].values())\n", " km = sum(f * sum(KM[s] for s in p) for p, f in res[\"paths\"].items()) / tot\n", " mins = res[\"avg_min\"]\n", " free = sum(f * sum(T0[s] for s in p) for p, f in res[\"paths\"].items()) / tot\n", " fuel = km * L_KM + max(mins - free, 0) * L_IDLE\n", " return {\"min\": mins, \"km\": km, \"fuel_l\": fuel, \"fleet_fuel_l\": fuel * fleet_vehicles}\n", "\n", "\n", "DEMAND, FLEET = 200, 600 # ํ”ผํฌ 200๋Œ€/์‹œ, ์ผ ์ˆ˜์†ก 600๋Œ€(๊ฐ€์ •)\n", "norm = trip(assign(DEMAND), FLEET)\n", "rows = []\n", "for name, closed, cm in [(\"A2 ๊ต๋Ÿ‰ ํ์‡„\", (\"A2\",), None), (\"B2 ํ„ฐ๋„ ํ์‡„\", (\"B2\",), None), (\"A1 ์šฉ๋Ÿ‰ 50% ๊ฐ์†Œ(์ •๋น„)\", (), {\"A1\": 0.5}), (\"A2 ํ์‡„ + B1 ์ •๋น„(65%)\", (\"A2\",), {\"B1\": 0.65})]:\n", " r = assign(DEMAND, closed, cm)\n", " t = trip(r, FLEET)\n", " rows.append((name, t[\"min\"] - norm[\"min\"], t[\"km\"] - norm[\"km\"], t[\"fuel_l\"] - norm[\"fuel_l\"], t[\"fleet_fuel_l\"] - norm[\"fleet_fuel_l\"], max(r[\"util\"].values())))\n", "det = pd.DataFrame(rows, columns=[\"์‹œ๋‚˜๋ฆฌ์˜ค\", \"์ถ”๊ฐ€ ์†Œ์š”(๋ถ„/๋Œ€)\", \"์ถ”๊ฐ€ ๊ฑฐ๋ฆฌ(km/๋Œ€)\", \"์ถ”๊ฐ€ ์œ ๋ฅ˜(L/๋Œ€)\", \"์ผ ์ถ”๊ฐ€ ์œ ๋ฅ˜(L)\", \"์ตœ๋Œ€ ์ด์šฉ๋ฅ \"]).round(2)\n", "print(f\"[1] ์šฐํšŒ ์—”์ง„ ๊ฒฐ๊ณผ(ํ‰์‹œ ๊ธฐ์ค€ {norm['min']:.1f}๋ถ„, {norm['km']:.1f}km, {norm['fuel_l']:.2f}L/๋Œ€)\")\n", "print(det.to_string(index=False))\n", "print(\" ์œ ๋ฅ˜ ๊ณ„์ˆ˜๋Š” ๊ต์œก์šฉ ๊ฐ€์ •๊ฐ’์ด๋‹ค. ์ด์šฉ๋ฅ  1.0 ์ดˆ๊ณผ ์‹œ๋‚˜๋ฆฌ์˜ค๋Š” ์šฐํšŒ๋งŒ์œผ๋กœ ์ˆ˜์šฉ์ด ์–ด๋ ค์›Œ ์‚ฌ๋žŒ์˜ ๋ฌผ๋Ÿ‰ ์กฐ์ • ํŒ๋‹จ์ด ํ•„์š”ํ•˜๋‹ค.\")\n", "\n", "# ---------------- 2) ๊ณ ๊ธ‰ ๋ชจ๋ธ: ๊ธฐ์ค€ ๋ชจ๋ธ(BPR) vs ์•™์ƒ๋ธ”(GBR+RF), Hold-out ----------------\n", "df = make_data(); d = add_features(df)\n", "tr, te = d[d.day < 120], d[d.day >= 120]\n", "gbr = GradientBoostingRegressor(n_estimators=250, max_depth=3, learning_rate=0.06, subsample=0.8, random_state=0).fit(tr[FEATS], tr[\"delay\"])\n", "rf = RandomForestRegressor(n_estimators=150, min_samples_leaf=3, random_state=0, n_jobs=1).fit(tr[FEATS], tr[\"delay\"])\n", "fac = te[\"t0\"] * (1 + 0.08 * te[\"rain\"])\n", "P = {\"๊ธฐ์ค€(BPR)\": te[\"t0\"] + te[\"bpr_base\"], \"GBR\": fac + gbr.predict(te[FEATS]), \"RandomForest\": fac + rf.predict(te[FEATS])}\n", "P[\"์•™์ƒ๋ธ”(ํ‰๊ท )\"] = (P[\"GBR\"] + P[\"RandomForest\"]) / 2\n", "tab = pd.DataFrame({k: {\"MAE(๋ถ„)\": mae(te.transit, v), \"RMSE(๋ถ„)\": float(np.sqrt(np.mean((te.transit - v) ** 2))), \"MAPE(%)\": mape(te.transit, v), \"R2\": r2_score(te.transit, v)} for k, v in P.items()}).T.round(3)\n", "print(f\"\\n[2] Hold-out(day 120~149, {len(te)}ํ–‰) ํ†ต๊ณผ์‹œ๊ฐ„ ์˜ˆ์ธก ์„ฑ๋Šฅ\")\n", "print(tab.to_string())\n", "b, e = tab.loc[\"๊ธฐ์ค€(BPR)\"], tab.loc[\"์•™์ƒ๋ธ”(ํ‰๊ท )\"]\n", "print(f\" MAE ๊ฐœ์„  {1 - e['MAE(๋ถ„)'] / b['MAE(๋ถ„)']:.1%}, RMSE ๊ฐœ์„  {1 - e['RMSE(๋ถ„)'] / b['RMSE(๋ถ„)']:.1%} (๋ชฉํ‘œ 20% ์ด์ƒ: {'๋‹ฌ์„ฑ' if 1 - e['MAE(๋ถ„)'] / b['MAE(๋ถ„)'] >= .2 and 1 - e['RMSE(๋ถ„)'] / b['RMSE(๋ถ„)'] >= .2 else '๋ฏธ๋‹ฌ'}), R2 {e['R2']:.3f} (๋ชฉํ‘œ 0.85 ์ด์ƒ: {'๋‹ฌ์„ฑ' if e['R2'] >= .85 else '๋ฏธ๋‹ฌ'})\")\n", "dl = te[\"delay\"]; pdl = P[\"์•™์ƒ๋ธ”(ํ‰๊ท )\"] - fac\n", "print(f\" ์ฐธ๊ณ : ์ง€์ฒด ์‹œ๊ฐ„๋งŒ ๋†“๊ณ  ๋ณด๋ฉด R2 {r2_score(dl, pdl):.3f}, MAE {mae(dl, pdl):.2f}๋ถ„ (๊ธฐ์ค€ BPR MAE {mae(dl, te['bpr_base']):.2f}๋ถ„). ํ†ต๊ณผ์‹œ๊ฐ„ R2๋Š” ๊ตฌ๊ฐ„ ๊ฐ„ ๊ธธ์ด ์ฐจ์ด๋ฅผ ํฌํ•จํ•œ๋‹ค.\")\n", "\n", "# ---------------- 3) ๋ถˆ๊ทœ์น™ ์ƒํ™ฉ(๋น„์ •ํ˜• ์ˆ˜์š”) ์ ์‘๋ ฅ: ์ •๋น„์ผยทํญ์ฆ ๊ธฐ๊ฐ„ ์˜ค์ฐจ ----------------\n", "stress = {\"๋น„๊ณ„ํš ์ •๋น„์ผ\": te[\"maint\"] == 1, \"๊ธด๊ธ‰ ๋ถˆ์ถœ์ผ\": te[\"urgent\"] == 1, \"์šฐ์ฒœ์ผ\": te[\"rain\"] == 1, \"๊ทธ ์™ธ\": (te[\"maint\"] == 0) & (te[\"urgent\"] == 0) & (te[\"rain\"] == 0)}\n", "rr = [(k, int(m.sum()), mae(te.transit[m], P[\"๊ธฐ์ค€(BPR)\"][m]), mae(te.transit[m], P[\"์•™์ƒ๋ธ”(ํ‰๊ท )\"][m])) for k, m in stress.items() if m.sum() > 0]\n", "print(\"\\n[3] ์กฐ๊ฑด๋ณ„ MAE(๋ถ„): ๊ธฐ์ค€ vs ์•™์ƒ๋ธ”\")\n", "print(pd.DataFrame(rr, columns=[\"์กฐ๊ฑด\", \"๊ฑด์ˆ˜\", \"๊ธฐ์ค€\", \"์•™์ƒ๋ธ”\"]).round(2).to_string(index=False))\n", "print(\"ํ•˜์ดํผํŒŒ๋ผ๋ฏธํ„ฐ(ํŠธ๋ฆฌ 250๊ฐœ, ๊นŠ์ด 3, ํ•™์Šต๋ฅ  0.06)๋Š” ์‹œ๊ณ„์—ด ๊ต์ฐจ๊ฒ€์ฆ(M4) ๊ฒฐ๊ณผ๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ์ •ํ–ˆ๊ณ , ํ•™์Šต๋œ ๋ชจ๋ธ์€ ์ด ์‹คํ–‰๊ธฐ์—์„œ๋Š” ์ €์žฅํ•˜์ง€ ์•Š๊ณ  ์„ค์ •๋งŒ ์ถœ๋ ฅํ•œ๋‹ค.\")\n", "print(\"์„ค์ •:\", {\"gbr\": gbr.get_params()[\"n_estimators\"], \"rf\": rf.get_params()[\"n_estimators\"], \"features\": len(FEATS)})\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 05 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[1] ์šฐํšŒ ์—”์ง„ ๊ฒฐ๊ณผ(ํ‰์‹œ ๊ธฐ์ค€ 40.9๋ถ„, 31.2km, 9.63L/๋Œ€)\n", " ์‹œ๋‚˜๋ฆฌ์˜ค ์ถ”๊ฐ€ ์†Œ์š”(๋ถ„/๋Œ€) ์ถ”๊ฐ€ ๊ฑฐ๋ฆฌ(km/๋Œ€) ์ถ”๊ฐ€ ์œ ๋ฅ˜(L/๋Œ€) ์ผ ์ถ”๊ฐ€ ์œ ๋ฅ˜(L) ์ตœ๋Œ€ ์ด์šฉ๋ฅ \n", " A2 ๊ต๋Ÿ‰ ํ์‡„ 3.96 2.38 0.81 485.68 1.0\n", " B2 ํ„ฐ๋„ ํ์‡„ 0.05 -0.12 -0.02 -10.48 1.0\n", " A1 ์šฉ๋Ÿ‰ 50% ๊ฐ์†Œ(์ •๋น„) 0.98 0.10 0.10 60.12 1.0\n", "A2 ํ์‡„ + B1 ์ •๋น„(65%) 12.90 3.98 1.85 1113.00 1.3\n", " ์œ ๋ฅ˜ ๊ณ„์ˆ˜๋Š” ๊ต์œก์šฉ ๊ฐ€์ •๊ฐ’์ด๋‹ค. ์ด์šฉ๋ฅ  1.0 ์ดˆ๊ณผ ์‹œ๋‚˜๋ฆฌ์˜ค๋Š” ์šฐํšŒ๋งŒ์œผ๋กœ ์ˆ˜์šฉ์ด ์–ด๋ ค์›Œ ์‚ฌ๋žŒ์˜ ๋ฌผ๋Ÿ‰ ์กฐ์ • ํŒ๋‹จ์ด ํ•„์š”ํ•˜๋‹ค.\n", "\n", "[2] Hold-out(day 120~149, 300ํ–‰) ํ†ต๊ณผ์‹œ๊ฐ„ ์˜ˆ์ธก ์„ฑ๋Šฅ\n", " MAE(๋ถ„) RMSE(๋ถ„) MAPE(%) R2\n", "๊ธฐ์ค€(BPR) 1.763 2.662 9.245 0.829\n", "GBR 0.494 0.877 2.784 0.981\n", "RandomForest 0.495 0.877 2.813 0.981\n", "์•™์ƒ๋ธ”(ํ‰๊ท ) 0.475 0.847 2.725 0.983\n", " MAE ๊ฐœ์„  73.1%, RMSE ๊ฐœ์„  68.2% (๋ชฉํ‘œ 20% ์ด์ƒ: ๋‹ฌ์„ฑ), R2 0.983 (๋ชฉํ‘œ 0.85 ์ด์ƒ: ๋‹ฌ์„ฑ)\n", " ์ฐธ๊ณ : ์ง€์ฒด ์‹œ๊ฐ„๋งŒ ๋†“๊ณ  ๋ณด๋ฉด R2 0.846, MAE 0.47๋ถ„ (๊ธฐ์ค€ BPR MAE 1.52๋ถ„). ํ†ต๊ณผ์‹œ๊ฐ„ R2๋Š” ๊ตฌ๊ฐ„ ๊ฐ„ ๊ธธ์ด ์ฐจ์ด๋ฅผ ํฌํ•จํ•œ๋‹ค.\n", "\n", "[3] ์กฐ๊ฑด๋ณ„ MAE(๋ถ„): ๊ธฐ์ค€ vs ์•™์ƒ๋ธ”\n", " ์กฐ๊ฑด ๊ฑด์ˆ˜ ๊ธฐ์ค€ ์•™์ƒ๋ธ”\n", "๋น„๊ณ„ํš ์ •๋น„์ผ 23 5.72 0.81\n", " ๊ธด๊ธ‰ ๋ถˆ์ถœ์ผ 16 3.98 2.17\n", " ์šฐ์ฒœ์ผ 60 3.32 0.61\n", " ๊ทธ ์™ธ 211 1.06 0.38\n", "ํ•˜์ดํผํŒŒ๋ผ๋ฏธํ„ฐ(ํŠธ๋ฆฌ 250๊ฐœ, ๊นŠ์ด 3, ํ•™์Šต๋ฅ  0.06)๋Š” ์‹œ๊ณ„์—ด ๊ต์ฐจ๊ฒ€์ฆ(M4) ๊ฒฐ๊ณผ๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ์ •ํ–ˆ๊ณ , ํ•™์Šต๋œ ๋ชจ๋ธ์€ ์ด ์‹คํ–‰๊ธฐ์—์„œ๋Š” ์ €์žฅํ•˜์ง€ ์•Š๊ณ  ์„ค์ •๋งŒ ์ถœ๋ ฅํ•œ๋‹ค.\n", "์„ค์ •: {'gbr': 250, 'rf': 150, 'features': 21}\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 06. ์ „์‹œ ๋ณด๊ธ‰๋กœ ๋ณ‘๋ชฉ ํ˜ผ์žก๋„ ๋ฐ ์šฐํšŒ ๊ฒฝ๋กœ ๋น„๊ต ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ๋Œ€์‹œ๋ณด๋“œ ๊ตฌํ˜„\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 3.0์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "๋ณด๊ธ‰๋กœ ๋ณ‘๋ชฉ ํ˜ผ์žก๋„ ๋ฐ ์šฐํšŒ ๊ฒฝ๋กœ ๋น„๊ต ๋Œ€์‹œ๋ณด๋“œ๋ฅผ ๊ตฌํ˜„ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **๋ณด๊ธ‰๋กœ ๋ณ‘๋ชฉ ํ˜ผ์žก๋„์™€ ์šฐํšŒ ๊ฒฝ๋กœ ๋น„๊ต ๋Œ€์‹œ๋ณด๋“œ๋ฅผ ๊ตฌํ˜„ํ•˜๊ณ , ์˜ˆ์ธก ๊ฑด๋งˆ๋‹ค ๊ธฐ์—ฌ๋„ ์ƒ์œ„ ์š”์ธ์„ ์„ค๋ช…ํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **๊ตฌ๊ฐ„๋ณ„ ์ด์šฉ๋ฅ  ํžˆํŠธ๋งต, ์˜ค๋Š˜์˜ ์˜ˆ์ธก ์ง€์ฒด์™€ ๊ฒฝ๋ณด ๊ธฐ์ค€, ๊ณ ์ • ๋ฐฐ๋ถ„ ๋Œ€๋น„ ์šฐํšŒ ๋ฐฐ์ • ๋น„๊ต, ์ตœ์•… ์‚ฌ๋ก€ ์š”์ธ ๊ธฐ์—ฌ๋„๋ฅผ 2x2 ํ™”๋ฉด์œผ๋กœ ๊ทธ๋ฆฐ๋‹ค ๊ณผ์ •์„ ์ง„ํ–‰ํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `src/explainability/mov01_xai_explainer.py ๋ฐ XAI ๋ฆฌํฌํŠธ` ํ˜•ํƒœ์˜ ๊ฒฐ๊ณผ๋ฌผ์„ ์™„์„ฑํ•œ๋‹ค.\n", "\n", "### ๐Ÿ” ํ‰๊ฐ€ ๋ฐ ๊ฒ€์ฆ ๊ธฐ์ค€ (PASS ์กฐ๊ฑด)\n", "#### ๋ฏธ์…˜ ํŒ์ •\n", "\n", "**ํ‰๊ฐ€ ๊ตฌ๋ถ„:** ํ•„์ˆ˜\n", "\n", "**ํ•ต์‹ฌ ํ‰๊ฐ€์˜์—ญ:** ๊ตฐ์ˆ˜ ์žฅ๊ต/๊ตฐ๋ฌด์›์ด ์‹ ๋ขฐํ•˜๊ณ  ์กฐ์น˜ํ•  ์ˆ˜ ์žˆ๋Š” ์„ค๋ช…์˜ ๋ช…ํ™•์„ฑ\n", "\n", "#### ๋ฏธ์…˜ PASS ์กฐ๊ฑด\n", "\n", "- ์ •๋Ÿ‰ ๊ธฐ์ค€: ์ฃผ์š” ์˜ˆ์ธก ๊ฑด๋‹น ๊ธฐ์—ฌ๋„ ์ƒ์œ„ 3๊ฐœ ์ธ์ž ์ž๋™ ์‚ฐ์ถœ๋ฅ  100%\n", "\n", "\n", "- ์ง„ํ–‰ ๊ธฐ์ค€: SHAP summary plot ๋ฐ waterfall plot ์ƒ์„ฑ ํŒŒ์ดํ”„๋ผ์ธ ์™„์„ฑ\n", "\n", "\n", "- ๊ฒ€์ฆ ๋ฐฉ๋ฒ•: ์‹ค๋ฌด์ž ๋Œ€์ƒ ์„ค๋ช… ๊ทผ๊ฑฐ ์œ ์šฉ์„ฑ ํ‰๊ฐ€ ๊ฒ€์ฆ\n", "\n", "\n", "#### ์ฑ„์ ์ž ํŒ์ • ๋ฉ”๋ชจ\n", "\n", "> ๋‹จ์ˆœ ์ฝ”๋“œ ์‹คํ–‰ ์—ฌ๋ถ€๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ถœ๋ ฅ๋œ ์ˆ˜์น˜์™€ ๊ทผ๊ฑฐ ์ž๋ฃŒ๊ฐ€ ๊ตฐ ์‹ค๋ฌด ์š”๊ตฌ์‚ฌํ•ญ์— ๋ถ€ํ•ฉํ•˜๋Š”์ง€ ๋ฉด๋ฐ€ํžˆ ํ™•์ธํ•œ๋‹ค.\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\"\"\"๋ณด๊ธ‰๋กœ ๋ณ‘๋ชฉ ํ˜ผ์žก๋„์™€ ์šฐํšŒ ๊ฒฝ๋กœ ๋น„๊ต ๋Œ€์‹œ๋ณด๋“œ๋ฅผ ๊ตฌํ˜„ํ•˜๊ณ , ์˜ˆ์ธก ๊ฑด๋งˆ๋‹ค ๊ธฐ์—ฌ๋„ ์ƒ์œ„ 3๊ฐœ ์š”์ธ์„ ์ž๋™ ์‚ฐ์ถœ(์š”์ธ๊ตฐ ์ œ๊ฑฐ ๋ฐฉ์‹ ์„ค๋ช…)ํ•ด ์ž์—ฐ์–ด ์š”์•ฝ์œผ๋กœ ๋ณด์—ฌ ์ค€๋‹ค.\"\"\"\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from scipy.optimize import linprog\n", "from sklearn.ensemble import GradientBoostingRegressor\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ƒ ๋…ธ์„ ๋งยท๊ตฌ๊ฐ„ ํ†ต๊ณผ๋Ÿ‰ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ๋„๋กœยท์ง€๋ช…ยท๋ถ€๋Œ€ ์—†์Œ)\n", "# ๊ฐ€์ƒ ์ถœ๋ฐœ์ง€(SRC)์—์„œ ๊ฐ€์ƒ ๋ชฉ์ ์ง€(DST)๊นŒ์ง€ 3๊ฐœ ์ถ•์„ (AยทBยทC)๊ณผ ์—ฐ๊ฒฐ๋กœ 2๊ฐœ, ๊ตฌ๊ฐ„ 10๊ฐœ. 150์ผ์น˜ ํ”ผํฌ์‹œ๊ฐ„ ํ†ต๊ณผ๋Ÿ‰ยท์ง€์ฒด ์‹œ๊ฐ„์„ ๋งŒ๋“ ๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "SEGS = pd.DataFrame([\n", " (\"A1\", \"SRC\", \"J1\", 12, 130, \"์ผ๋ฐ˜\"), (\"A2\", \"J1\", \"J2\", 8, 70, \"๊ต๋Ÿ‰\"), (\"A3\", \"J2\", \"DST\", 10, 140, \"๊ต์ฐจ๋กœ\"),\n", " (\"B1\", \"SRC\", \"J3\", 15, 100, \"์ผ๋ฐ˜\"), (\"B2\", \"J3\", \"J2\", 9, 80, \"ํ„ฐ๋„\"), (\"B3\", \"J3\", \"DST\", 14, 95, \"์ผ๋ฐ˜\"),\n", " (\"C1\", \"SRC\", \"J4\", 18, 70, \"์ผ๋ฐ˜\"), (\"C2\", \"J4\", \"DST\", 20, 75, \"๊ต๋Ÿ‰\"),\n", " (\"X1\", \"J1\", \"J3\", 7, 50, \"๊ต์ฐจ๋กœ\"), (\"X2\", \"J4\", \"J3\", 10, 60, \"์ผ๋ฐ˜\")],\n", " columns=[\"seg\", \"u\", \"v\", \"km\", \"cap\", \"kind\"]) # cap = ํ”ผํฌ์‹œ๊ฐ„ ํ†ต๊ณผ ์šฉ๋Ÿ‰(๋Œ€/์‹œ, ๊ต์œก์šฉ ๊ฐ€์ •๊ฐ’)\n", "KSPD = {\"์ผ๋ฐ˜\": 55, \"๊ต๋Ÿ‰\": 40, \"ํ„ฐ๋„\": 45, \"๊ต์ฐจ๋กœ\": 45} # ๊ตฌ๊ฐ„ ์œ ํ˜•๋ณ„ ์ž์œ  ์ฃผํ–‰ ์†๋„(km/h, ๊ฐ€์ •๊ฐ’)\n", "SEGS[\"t0\"] = SEGS[\"km\"] / SEGS[\"kind\"].map(KSPD) * 60 # ์ž์œ  ์ฃผํ–‰ ์‹œ๊ฐ„(๋ถ„)\n", "UTIL = dict(A1=.78, A2=.85, A3=.66, B1=.70, B2=.72, B3=.64, C1=.55, C2=.58, X1=.45, X2=.40) # ํ‰์‹œ ์ด์šฉ๋ฅ \n", "\n", "\n", "def bpr(t0, v, cap, a=0.15, b=4.0):\n", " \"\"\"BPR ์ง€์ฒด ํ•จ์ˆ˜(๊ตํ†ต๊ณตํ•™์˜ ํ‘œ์ค€ํ˜•): ํ†ต๊ณผ๋Ÿ‰/์šฉ๋Ÿ‰ ๋น„๊ฐ€ ์ปค์งˆ์ˆ˜๋ก ์ง€์ฒด๊ฐ€ ๊ธ‰์ฆํ•œ๋‹ค. (๋ถ„)\"\"\"\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 6] ์ „์‹œ ๋ณด๊ธ‰๋กœ ๋ณ‘๋ชฉ ํ˜ผ์žก๋„ ๋ฐ ์šฐํšŒ ๊ฒฝ๋กœ ๋น„๊ต ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ๋Œ€์‹œ๋ณด๋“œ ๊ตฌํ˜„ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def make_data(seed=11, days=150):\n", " \"\"\"๊ตฌ๊ฐ„ x ์ผ์ž ํŒจ๋„. v=์‹ค์ œ ํ”ผํฌ ํ†ต๊ณผ๋Ÿ‰, plan_v=์ˆ˜์†ก๊ณ„ํš ์ˆ˜๋Ÿ‰(๊ธด๊ธ‰ ๋ถˆ์ถœ ๋ฏธ๋ฐ˜์˜), delay=์‹ค์ œ ์ง€์ฒด(๋ถ„).\"\"\"\n", " rng = np.random.default_rng(seed)\n", " d_idx = np.arange(days)\n", " lvl = np.array([1.0, 1.08, 1.04, 1.10, 1.02, 0.82, 0.78])[d_idx % 7] * (1 + 0.0012 * d_idx)\n", " lvl = lvl * np.where((d_idx >= 100) & (d_idx < 110), 1.30, 1.0) # ์ˆ˜์†ก๋Ÿ‰ ํญ์ฆ ๊ธฐ๊ฐ„(๊ฐ€์ƒ)\n", " prev, rows = {s: 0.0 for s in SEGS[\"seg\"]}, []\n", " for d in d_idx:\n", " rain = bool(rng.random() < 0.18)\n", " for r in SEGS.itertuples():\n", " maint, urgent = bool(rng.random() < 0.06), bool(rng.random() < 0.05) # ๋น„๊ณ„ํš ์ •๋น„ / ๊ธด๊ธ‰ ๋ถˆ์ถœ\n", " v = r.cap * UTIL[r.seg] * lvl[d] * (1.25 if urgent else 1.0) * rng.normal(1, 0.05)\n", " cap_eff = r.cap * (0.65 if maint else 1.0) * (0.92 if rain else 1.0)\n", " delay = max(r.t0 * 0.2 * (v / cap_eff) ** 3.5 + 0.25 * prev[r.seg] + rng.normal(0, 0.4), 0.0)\n", " prev[r.seg] = delay\n", " rows.append(dict(day=int(d), dow=int(d % 7), seg=r.seg, kind=r.kind, km=r.km, cap=r.cap, t0=r.t0,\n", " plan_v=v / (1.25 if urgent else 1.0) * rng.normal(1, 0.04), v=v, maint=int(maint), urgent=int(urgent),\n", " rain=int(rain), cap_eff=cap_eff, delay=delay))\n", " df = pd.DataFrame(rows)\n", " df[\"transit\"] = df[\"t0\"] * (1 + 0.08 * df[\"rain\"]) + df[\"delay\"]\n", " return df\n", "\n", "\n", "def mape(y, p):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 6] ์ „์‹œ ๋ณด๊ธ‰๋กœ ๋ณ‘๋ชฉ ํ˜ผ์žก๋„ ๋ฐ ์šฐํšŒ ๊ฒฝ๋กœ ๋น„๊ต ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ๋Œ€์‹œ๋ณด๋“œ ๊ตฌํ˜„ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def mae(y, p):\n", " return float(np.mean(np.abs(np.asarray(y, float) - np.asarray(p, float))))\n", "# ---------------------------------------------------------------------------\n", "# ---------------------------------------------------------------------------\n", "# ์ง€์ฒด ์˜ˆ์ธก์šฉ ํŒŒ์ƒ ํ”ผ์ฒ˜ ์ƒ์„ฑ(๋‹น์ผ ์˜ค์ „ ์‹œ์ ์— ์•Œ ์ˆ˜ ์žˆ๋Š” ์ •๋ณด๋งŒ ์‚ฌ์šฉ: ๊ณ„ํš ๋ฌผ๋Ÿ‰, ๊ณต์ง€ ์ •๋น„, ๊ธฐ์ƒ ์˜ˆ๋ณด, ์ „์ผยท์ „์ฃผ ์‹ค์ )\n", "# ---------------------------------------------------------------------------\n", "FEATS = [\"plan_v\", \"cap\", \"cap_eff\", \"t0\", \"km\", \"maint\", \"rain\", \"util\", \"util_eff\", \"util_eff4\", \"bpr_base\", \"lag1_delay\", \"lag7_delay\",\n", " \"roll7_v\", \"surge_ratio\", \"is_weekend\", \"dow_sin\", \"dow_cos\", \"k_๊ต๋Ÿ‰\", \"k_ํ„ฐ๋„\", \"k_๊ต์ฐจ๋กœ\"]\n", "\n", "\n", "def add_features(df):\n", " d = df.sort_values([\"seg\", \"day\"]).copy()\n", " g = d.groupby(\"seg\")\n", " d[\"lag1_delay\"] = g[\"delay\"].shift(1)\n", " d[\"lag7_delay\"] = g[\"delay\"].shift(7)\n", " d[\"roll7_v\"] = g[\"v\"].transform(lambda s: s.shift(1).rolling(7).mean())\n", " d[\"util\"] = d[\"plan_v\"] / d[\"cap\"]\n", " d[\"util_eff\"] = d[\"plan_v\"] / d[\"cap_eff\"]\n", " d[\"util_eff4\"] = d[\"util_eff\"] ** 4\n", " d[\"bpr_base\"] = bpr(d[\"t0\"], d[\"plan_v\"], d[\"cap\"])\n", " d[\"surge_ratio\"] = d[\"plan_v\"] / d[\"roll7_v\"]\n", " d[\"is_weekend\"] = (d[\"dow\"] >= 5).astype(int)\n", " d[\"dow_sin\"], d[\"dow_cos\"] = np.sin(2 * np.pi * d[\"dow\"] / 7), np.cos(2 * np.pi * d[\"dow\"] / 7)\n", " for k in (\"๊ต๋Ÿ‰\", \"ํ„ฐ๋„\", \"๊ต์ฐจ๋กœ\"):\n", " d[\"k_\" + k] = (d[\"kind\"] == k).astype(int)\n", " return d.dropna().sort_values([\"day\", \"seg\"]).reset_index(drop=True)\n", "# ---------------------------------------------------------------------------\n", "# ---------------------------------------------------------------------------\n", "# ๋…ธ์„ ๋ง ํ๋ฆ„ ๋„๊ตฌ: ์ตœ๋Œ€์œ ๋Ÿ‰(Edmonds-Karp), ์ฆ๋ถ„ ๋ฐฐ์ •(ํ˜ผ์žก ๋ฐ˜์˜ ์ตœ๋‹จ์‹œ๊ฐ„ ๊ฒฝ๋กœ), ๊ฒฝ๋กœ ์—ด๊ฑฐ\n", "# ---------------------------------------------------------------------------\n", "CAP = dict(zip(SEGS[\"seg\"], SEGS[\"cap\"]))\n", "T0 = dict(zip(SEGS[\"seg\"], SEGS[\"t0\"]))\n", "KM = dict(zip(SEGS[\"seg\"], SEGS[\"km\"]))\n", "ARCS = {r.seg: (r.u, r.v) for r in SEGS.itertuples()}\n", "SRC, DST = \"SRC\", \"DST\"\n", "\n", "\n", "def seg_time(s, flow, cap=None, a=0.2, b=3.5):\n", " \"\"\"๊ตฌ๊ฐ„ ํ†ต๊ณผ์‹œ๊ฐ„(๋ถ„) = ์ž์œ ์ฃผํ–‰ + ํ˜ผ์žก ์ง€์ฒด. cap์„ ์ฃผ๋ฉด ์šฉ๋Ÿ‰์„ ๋Œ€์ฒด(์ •๋น„ยท๊ธฐ์ƒ ๋ฐ˜์˜).\"\"\"\n", " r = flow / (cap or CAP[s])\n", " return T0[s] * (1 + a * r ** b + 6.0 * max(0.0, r - 1.0)) # ์šฉ๋Ÿ‰ ์ดˆ๊ณผ๋ถ„์€ ๋Œ€๊ธฐ์—ด๋กœ ๊ธ‰์ฆ\n", "\n", "\n", "def max_flow(closed=(), capmul=None):\n", " \"\"\"์ตœ๋Œ€์œ ๋Ÿ‰(๋Œ€/์‹œ)๊ณผ ์ตœ์†Œ ์ ˆ๋‹จ ๊ตฌ๊ฐ„(๋ณ‘๋ชฉ). capmul = {๊ตฌ๊ฐ„: ์šฉ๋Ÿ‰ ๋ฐฐ์œจ}.\"\"\"\n", " cap = {s: (0 if s in closed else CAP[s] * (capmul or {}).get(s, 1.0)) for s in CAP}\n", " res = {}\n", " for s, (u, v) in ARCS.items():\n", " res[(u, v)] = res.get((u, v), 0) + cap[s]\n", " res.setdefault((v, u), 0)\n", " flow = 0.0\n", " while True:\n", " prev, q = {SRC: None}, [SRC]\n", " for u in q:\n", " for (a, b), c in res.items():\n", " if a == u and c > 1e-9 and b not in prev:\n", " prev[b] = u; q.append(b)\n", " if DST not in prev:\n", " break\n", " path, x = [], DST\n", " while prev[x]:\n", " path.append((prev[x], x)); x = prev[x]\n", " f = min(res[e] for e in path)\n", " for a, b in path:\n", " res[(a, b)] -= f; res[(b, a)] += f\n", " flow += f\n", " reach = set(prev)\n", " cut = [s for s, (u, v) in ARCS.items() if u in reach and v not in reach and cap[s] > 0]\n", " return flow, cut\n", "\n", "\n", "def all_paths():\n", " \"\"\"SRC -> DST ๋‹จ์ˆœ ๊ฒฝ๋กœ(๊ตฌ๊ฐ„ ์ด๋ฆ„ ํŠœํ”Œ) ์ „์ฒด.\"\"\"\n", " out = []\n", "\n", " def dfs(node, segs, seen):\n", " if node == DST:\n", " out.append(tuple(segs)); return\n", " for s, (u, v) in ARCS.items():\n", " if u == node and v not in seen:\n", " dfs(v, segs + [s], seen | {v})\n", " dfs(SRC, [], {SRC})\n", " return out\n", "\n", "\n", "PATHS = all_paths()\n", "\n", "\n", "def assign(demand, closed=(), capmul=None, steps=40):\n", " \"\"\"์ฆ๋ถ„ ๋ฐฐ์ •: ์ˆ˜์š”๋ฅผ steps์กฐ๊ฐ์œผ๋กœ ๋‚˜๋ˆ , ์ „์ฒด ํ†ต๊ณผ์‹œ๊ฐ„(๋Œ€์ˆ˜ x ์‹œ๊ฐ„) ์ฆ๊ฐ€๊ฐ€ ๊ฐ€์žฅ ์ž‘์€ ๊ฒฝ๋กœ์— ์ฐจ๋ก€๋กœ ์–น๋Š”๋‹ค(์‹œ์Šคํ…œ ์ตœ์  ๊ทผ์‚ฌ).\"\"\"\n", " cap = {s: CAP[s] * (capmul or {}).get(s, 1.0) for s in CAP}\n", " flow, pf = {s: 0.0 for s in CAP}, {}\n", " ok = [p for p in PATHS if not any(s in closed for s in p)]\n", " if not ok:\n", " return None\n", " for _ in range(steps):\n", " chunk = demand / steps\n", " best = min(ok, key=lambda p: sum((flow[s] + chunk) * seg_time(s, flow[s] + chunk, cap[s]) - flow[s] * seg_time(s, flow[s], cap[s]) for s in p))\n", " for s in best:\n", " flow[s] += chunk\n", " pf[best] = pf.get(best, 0) + chunk\n", " tt = {p: sum(seg_time(s, flow[s], cap[s]) for s in p) for p in pf}\n", " avg = sum(pf[p] * tt[p] for p in pf) / demand\n", " return {\"flow\": flow, \"paths\": pf, \"path_min\": tt, \"avg_min\": avg, \"util\": {s: flow[s] / cap[s] for s in cap if cap[s] > 0}}\n", "\n", "\n", "FIXED_SHARE = {(\"A1\", \"A2\", \"A3\"): 0.32, (\"B1\", \"B3\"): 0.40, (\"C1\", \"C2\"): 0.28} # ๊ณผ๊ฑฐ ๊ด€ํ–‰: ์ถ•์„ ๋ณ„ ๊ณ ์ • ๋ฐฐ๋ถ„(๊ฐ€์ƒ)\n", "\n", "\n", "def fixed_assign(demand, closed=(), capmul=None):\n", " \"\"\"๊ณผ๊ฑฐ ๊ด€ํ–‰ ๋ฐฐ๋ถ„: ์ถ•์„ ๋ณ„ ๊ณ ์ • ๋น„์œจ. ๋ง‰ํžŒ ์ถ•์„  ๋ฌผ๋Ÿ‰์€ ์—ด๋ ค ์žˆ๋Š” ์ถ•์„ ์— ๋น„์œจ๋Œ€๋กœ ์žฌ๋ถ„๋ฐฐ.\"\"\"\n", " cap = {s: CAP[s] * (capmul or {}).get(s, 1.0) for s in CAP}\n", " live = {p: w for p, w in FIXED_SHARE.items() if not any(s in closed for s in p)}\n", " if not live:\n", " return None\n", " tot = sum(live.values())\n", " flow = {s: 0.0 for s in CAP}\n", " pf = {p: demand * w / tot for p, w in live.items()}\n", " for p, f in pf.items():\n", " for s in p:\n", " flow[s] += f\n", " tt = {p: sum(seg_time(s, flow[s], cap[s]) for s in p) for p in pf}\n", " return {\"flow\": flow, \"paths\": pf, \"path_min\": tt, \"avg_min\": sum(pf[p] * tt[p] for p in pf) / demand,\n", " \"util\": {s: flow[s] / cap[s] for s in cap if cap[s] > 0}}\n", "\n", "\n", "def lp_assign(demand, closed=(), capmul=None):\n", " \"\"\"์„ ํ˜•๊ณ„ํš(LP): ๊ตฌ๊ฐ„ ์šฉ๋Ÿ‰ ์ƒํ•œ(์ด์šฉ๋ฅ  rho) ์•„๋ž˜์—์„œ ์ž์œ ์ฃผํ–‰์‹œ๊ฐ„ ํ•ฉ์„ ์ตœ์†Œํ™”ํ•˜๋Š” ๊ฒฝ๋กœ๋ณ„ ๋ฌผ๋Ÿ‰. ๋ถˆ๊ฐ€๋Šฅํ•˜๋ฉด rho๋ฅผ 1.0๊นŒ์ง€ ์™„ํ™”.\"\"\"\n", " cap = {s: CAP[s] * (capmul or {}).get(s, 1.0) for s in CAP}\n", " ok = [p for p in PATHS if not any(s in closed for s in p)]\n", " if not ok:\n", " return None\n", " for rho in (0.85, 0.95, 1.0):\n", " r = linprog([sum(T0[s] for s in p) for p in ok], A_ub=[[1.0 if s in p else 0.0 for p in ok] for s in CAP],\n", " b_ub=[rho * cap[s] for s in CAP], A_eq=[[1.0] * len(ok)], b_eq=[demand], bounds=(0, None), method=\"highs\")\n", " if r.status == 0:\n", " break\n", " else:\n", " return assign(demand, closed, capmul) # ์šฉ๋Ÿ‰ ์ดˆ๊ณผ ์ˆ˜์š”: ์ฆ๋ถ„ ๋ฐฐ์ •์œผ๋กœ ๋Œ€์ฒด(๊ณผ๋ถ€ํ•˜ ํ‘œ์‹œ)\n", " flow = {s: sum(r.x[i] for i, p in enumerate(ok) if s in p) for s in CAP}\n", " pf = {p: float(r.x[i]) for i, p in enumerate(ok) if r.x[i] > 1e-6}\n", " tt = {p: sum(seg_time(s, flow[s], cap[s]) for s in p) for p in pf}\n", " return {\"flow\": flow, \"paths\": pf, \"path_min\": tt, \"avg_min\": sum(pf[p] * tt[p] for p in pf) / demand,\n", " \"util\": {s: flow[s] / cap[s] for s in cap if cap[s] > 0}, \"rho\": rho}\n", "\n", "\n", "def cost_of(res, tv=1000, fuel_km=450):\n", " \"\"\"์‹œ๊ฐ„๋‹น ๋น„์šฉ(์›, ๊ต์œก์šฉ ๊ฐ€์ •): ๋Œ€๋‹น ์‹œ๊ฐ„๋น„์šฉ tv์›/๋ถ„ + ์œ ๋ฅ˜๋น„ fuel_km์›/km.\"\"\"\n", " return sum(f * (res[\"path_min\"][p] * tv + sum(KM[s] for s in p) * fuel_km) for p, f in res[\"paths\"].items())\n", "# ---------------------------------------------------------------------------\n", "\n", "df = make_data(); d = add_features(df)\n", "tr, te = d[d.day < 120], d[d.day >= 120].copy()\n", "gbr = GradientBoostingRegressor(n_estimators=200, max_depth=3, learning_rate=0.08, random_state=0).fit(tr[FEATS], tr[\"delay\"])\n", "te[\"pred\"] = gbr.predict(te[FEATS])\n", "\n", "# ---------------- 1) ์„ค๋ช… ๊ฐ€๋Šฅ์„ฑ: ์š”์ธ๊ตฐ์„ ํ‰๊ท ๊ฐ’์œผ๋กœ ๋ฐ”๊ฟจ์„ ๋•Œ ์˜ˆ์ธก์ด ์–ผ๋งˆ๋‚˜ ๋ณ€ํ•˜๋Š”๊ฐ€ ----------------\n", "GROUPS = {\"๊ณ„ํš ๋ฌผ๋Ÿ‰ยท์ด์šฉ๋ฅ \": [\"plan_v\", \"util\", \"util_eff\", \"util_eff4\", \"bpr_base\", \"surge_ratio\", \"roll7_v\"], \"์ •๋น„ยท์œ ํšจ ์šฉ๋Ÿ‰\": [\"maint\", \"cap_eff\"],\n", " \"์šฐ์ฒœ\": [\"rain\"], \"์ „์ผยท์ „์ฃผ ์ง€์ฒด\": [\"lag1_delay\", \"lag7_delay\"], \"์š”์ผ ํŒจํ„ด\": [\"is_weekend\", \"dow_sin\", \"dow_cos\"],\n", " \"๊ตฌ๊ฐ„ ํŠน์„ฑ\": [\"cap\", \"t0\", \"km\", \"k_๊ต๋Ÿ‰\", \"k_ํ„ฐ๋„\", \"k_๊ต์ฐจ๋กœ\"]}\n", "mean_row = tr[FEATS].mean()\n", "contrib = pd.DataFrame(index=te.index)\n", "for g, cols in GROUPS.items():\n", " x = te[FEATS].copy()\n", " x[cols] = mean_row[cols].values\n", " contrib[g] = te[\"pred\"] - gbr.predict(x) # ์–‘์ˆ˜ = ์ด ์š”์ธ์ด ์ง€์ฒด๋ฅผ ๋Š˜๋ฆผ\n", "top3 = contrib.abs().apply(lambda r: r.nlargest(3).index.tolist(), axis=1)\n", "ok = int(sum(len(t) == 3 and len(set(t)) == 3 and np.isfinite(contrib.loc[i, t].values).all() for i, t in top3.items()))\n", "weak = int((contrib.abs().apply(lambda r: r.nlargest(3).iloc[2], axis=1) < 0.01).sum())\n", "print(f\"[1] ์˜ˆ์ธก {len(te)}๊ฑด ์ค‘ ๊ธฐ์—ฌ๋„ ์ƒ์œ„ 3๊ฐœ ์š”์ธ ์ž๋™ ์‚ฐ์ถœ {ok}๊ฑด ({ok / len(te):.0%}, ๋ชฉํ‘œ 100%)\")\n", "imp = contrib.abs().mean().sort_values(ascending=False)\n", "print(f\" (3๋ฒˆ์งธ ์š”์ธ ๊ธฐ์—ฌ๊ฐ€ 0.01๋ถ„ ๋ฏธ๋งŒ์ธ ๋ฏธ๋ฏธํ•œ ๊ฑด {weak}๊ฑด: ์ˆœ์œ„๋Š” ์‚ฐ์ถœ๋˜๋‚˜ ์„ค๋ช…๋ ฅ์€ ๋‚ฎ์Œ)\")\n", "print(\" ์š”์ธ๊ตฐ๋ณ„ ํ‰๊ท  ๊ธฐ์—ฌ ํฌ๊ธฐ(๋ถ„):\", \", \".join(f\"{k} {v:.2f}\" for k, v in imp.items()))\n", "\n", "\n", "def explain(i):\n", " r, c = te.loc[i], contrib.loc[i]\n", " parts = [f\"{g}({c[g]:+.1f}๋ถ„)\" for g in top3[i]]\n", " sev = \"์ •์ฒด\" if r[\"util_eff\"] >= 0.9 else \"ํ˜ผ์žก\" if r[\"util_eff\"] >= 0.7 else \"์›ํ™œ\"\n", " cause = {\"์ •๋น„ยท์œ ํšจ ์šฉ๋Ÿ‰\": \"๋น„๊ณ„ํš ์ •๋น„๋กœ ํ†ต๊ณผ ์šฉ๋Ÿ‰์ด ์ค„์–ด\" if r[\"maint\"] else \"์œ ํšจ ์šฉ๋Ÿ‰ ๋ณ€ํ™”๋กœ\", \"์šฐ์ฒœ\": \"์šฐ์ฒœ ์˜ํ–ฅ์œผ๋กœ\", \"๊ณ„ํš ๋ฌผ๋Ÿ‰ยท์ด์šฉ๋ฅ \": \"๊ณ„ํš ๋ฌผ๋Ÿ‰์ด ์šฉ๋Ÿ‰์— ๊ทผ์ ‘ํ•ด\",\n", " \"์ „์ผยท์ „์ฃผ ์ง€์ฒด\": \"์ „์ผ ์ง€์ฒด๊ฐ€ ์ด์–ด์ ธ\", \"์š”์ผ ํŒจํ„ด\": \"์š”์ผ ํŒจํ„ด ๋•Œ๋ฌธ์—\", \"๊ตฌ๊ฐ„ ํŠน์„ฑ\": \"๊ตฌ๊ฐ„ ํŠน์„ฑ์ƒ\"}[top3[i][0]]\n", " return f\"{r['seg']} ๊ตฌ๊ฐ„ day {int(r['day'])}: {cause} ์ง€์ฒด {r['pred']:.1f}๋ถ„ ์˜ˆ์ƒ({sev}). ์ฃผ์š” ์š”์ธ {', '.join(parts)}\"\n", "\n", "\n", "print(\"\\n[2] ์ž์—ฐ์–ด ์„ค๋ช…(์˜ˆ์ธก ์ง€์ฒด ์ƒ์œ„ 4๊ฑด)\")\n", "for i in te[\"pred\"].nlargest(4).index:\n", " print(\" -\", explain(i))\n", "\n", "# ---------------- 2) ๋Œ€์‹œ๋ณด๋“œ ๋ฐ์ดํ„ฐ: ์ตœ์ข…์ผ ๊ตฌ๊ฐ„๋ณ„ ํ˜ผ์žก๋„ยท๊ฒฝ๋ณด ----------------\n", "last = te[te.day == te.day.max()].set_index(\"seg\")\n", "last[\"์ƒํƒœ\"] = np.where(last.util_eff >= 0.9, \"์ •์ฒด\", np.where(last.util_eff >= 0.7, \"ํ˜ผ์žก\", \"์›ํ™œ\"))\n", "print(f\"\\n[3] ์ตœ์ข…์ผ ๊ตฌ๊ฐ„ ์ƒํƒœํŒ: \" + \", \".join(f\"{s} {r.์ƒํƒœ}({r.util_eff:.2f})\" for s, r in last.iterrows()))\n", "alert = last[last[\"pred\"] >= 5].index.tolist()\n", "print(f\" ์ง€์ฒด 5๋ถ„ ์ด์ƒ ์˜ˆ์ธก ๊ฒฝ๋ณด ๊ตฌ๊ฐ„: {alert or '์—†์Œ'} -> ์šฐํšŒ ๊ฒ€ํ†  ๋Œ€์ƒ(์‹ค์ œ ํ•˜๋‹ฌ์€ ์ž‘์ „๊ตฐ์ˆ˜๊ด€ ์Šน์ธ ํ›„)\")\n", "\n", "# ---------------- 3) ์šฐํšŒ ๊ฒฝ๋กœ ๋น„๊ต: ๊ณผ๊ฑฐ ๊ด€ํ–‰(์ถ•์„  ๊ณ ์ • ๋ฐฐ๋ถ„) vs ์ฆ๋ถ„ ๋ฐฐ์ • ----------------\n", "D = 200\n", "cmp = []\n", "for name, closed, cm in [(\"ํ‰์‹œ\", (), None), (\"A2 ํ์‡„\", (\"A2\",), None), (\"A1 ์ •๋น„(50%)\", (), {\"A1\": 0.5}), (\"B1 ์ •๋น„(65%)\", (), {\"B1\": 0.65})]:\n", " a, f = assign(D, closed, cm), fixed_assign(D, closed, cm)\n", " cmp.append((name, f[\"avg_min\"], a[\"avg_min\"], max(f[\"util\"].values()), max(a[\"util\"].values())))\n", "cm_df = pd.DataFrame(cmp, columns=[\"์‹œ๋‚˜๋ฆฌ์˜ค\", \"๊ณ ์ • ๋ฐฐ๋ถ„(๋ถ„)\", \"์šฐํšŒ ๋ฐฐ์ •(๋ถ„)\", \"๊ณ ์ • ์ตœ๋Œ€ ์ด์šฉ๋ฅ \", \"๋ฐฐ์ • ์ตœ๋Œ€ ์ด์šฉ๋ฅ \"]).round(2)\n", "cm_df[\"๋‹จ์ถ•๋ฅ \"] = (1 - cm_df[\"์šฐํšŒ ๋ฐฐ์ •(๋ถ„)\"] / cm_df[\"๊ณ ์ • ๋ฐฐ๋ถ„(๋ถ„)\"]).map(\"{:.0%}\".format)\n", "print(f\"\\n[4] ์šฐํšŒ ๊ฒฝ๋กœ ๋น„๊ต(์ˆ˜์š” {D}๋Œ€/์‹œ)\")\n", "print(cm_df.to_string(index=False))\n", "\n", "fig, ax = plt.subplots(2, 2, figsize=(13, 8))\n", "piv = te.pivot(index=\"seg\", columns=\"day\", values=\"util_eff\").iloc[:, -14:]\n", "im = ax[0, 0].imshow(piv.values, aspect=\"auto\", cmap=\"YlOrRd\", vmin=0.3, vmax=1.2); ax[0, 0].set_yticks(range(len(piv))); ax[0, 0].set_yticklabels(piv.index)\n", "ax[0, 0].set_title(\"1 Congestion heatmap (effective utilization, last 14 days)\"); plt.colorbar(im, ax=ax[0, 0])\n", "colors = [\"tab:red\" if s == \"์ •์ฒด\" else \"tab:orange\" if s == \"ํ˜ผ์žก\" else \"tab:green\" for s in last[\"์ƒํƒœ\"]]\n", "ax[0, 1].bar(last.index, last[\"pred\"], color=colors); ax[0, 1].axhline(5, ls=\"--\", c=\"k\"); ax[0, 1].set_title(\"2 Predicted delay today (min), dashed = alert 5\")\n", "x = np.arange(len(cm_df)); ax[1, 0].bar(x - .2, cm_df[\"๊ณ ์ • ๋ฐฐ๋ถ„(๋ถ„)\"], .4, label=\"fixed share\"); ax[1, 0].bar(x + .2, cm_df[\"์šฐํšŒ ๋ฐฐ์ •(๋ถ„)\"], .4, label=\"detour assignment\")\n", "ax[1, 0].set_xticks(x); ax[1, 0].set_xticklabels([\"normal\", \"A2 closed\", \"A1 maint\", \"B1 maint\"]); ax[1, 0].legend(); ax[1, 0].set_title(\"3 Detour comparison (avg transit min)\")\n", "worst = te[\"pred\"].idxmax(); c = contrib.loc[worst]\n", "ax[1, 1].barh([\"plan/util\", \"maint/cap\", \"rain\", \"lag delay\", \"weekday\", \"segment\"], c[list(GROUPS)].values); ax[1, 1].set_title(\"4 Contribution for worst case (min)\")\n", "plt.suptitle(\"MOV01 bottleneck dashboard (synthetic)\"); plt.tight_layout(); plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 06 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[1] ์˜ˆ์ธก 300๊ฑด ์ค‘ ๊ธฐ์—ฌ๋„ ์ƒ์œ„ 3๊ฐœ ์š”์ธ ์ž๋™ ์‚ฐ์ถœ 300๊ฑด (100%, ๋ชฉํ‘œ 100%)\n", " (3๋ฒˆ์งธ ์š”์ธ ๊ธฐ์—ฌ๊ฐ€ 0.01๋ถ„ ๋ฏธ๋งŒ์ธ ๋ฏธ๋ฏธํ•œ ๊ฑด 7๊ฑด: ์ˆœ์œ„๋Š” ์‚ฐ์ถœ๋˜๋‚˜ ์„ค๋ช…๋ ฅ์€ ๋‚ฎ์Œ)\n", " ์š”์ธ๊ตฐ๋ณ„ ํ‰๊ท  ๊ธฐ์—ฌ ํฌ๊ธฐ(๋ถ„): ๊ณ„ํš ๋ฌผ๋Ÿ‰ยท์ด์šฉ๋ฅ  0.95, ์ „์ผยท์ „์ฃผ ์ง€์ฒด 0.41, ๊ตฌ๊ฐ„ ํŠน์„ฑ 0.15, ์š”์ผ ํŒจํ„ด 0.06, ์ •๋น„ยท์œ ํšจ ์šฉ๋Ÿ‰ 0.04, ์šฐ์ฒœ 0.02\n", "\n", "[2] ์ž์—ฐ์–ด ์„ค๋ช…(์˜ˆ์ธก ์ง€์ฒด ์ƒ์œ„ 4๊ฑด)\n", " - C2 ๊ตฌ๊ฐ„ day 134: ๊ณ„ํš ๋ฌผ๋Ÿ‰์ด ์šฉ๋Ÿ‰์— ๊ทผ์ ‘ํ•ด ์ง€์ฒด 12.4๋ถ„ ์˜ˆ์ƒ(์ •์ฒด). ์ฃผ์š” ์š”์ธ ๊ณ„ํš ๋ฌผ๋Ÿ‰ยท์ด์šฉ๋ฅ (+9.1๋ถ„), ๊ตฌ๊ฐ„ ํŠน์„ฑ(+4.0๋ถ„), ์ •๋น„ยท์œ ํšจ ์šฉ๋Ÿ‰(+2.0๋ถ„)\n", " - B1 ๊ตฌ๊ฐ„ day 120: ๊ณ„ํš ๋ฌผ๋Ÿ‰์ด ์šฉ๋Ÿ‰์— ๊ทผ์ ‘ํ•ด ์ง€์ฒด 10.9๋ถ„ ์˜ˆ์ƒ(์ •์ฒด). ์ฃผ์š” ์š”์ธ ๊ณ„ํš ๋ฌผ๋Ÿ‰ยท์ด์šฉ๋ฅ (+8.8๋ถ„), ์ •๋น„ยท์œ ํšจ ์šฉ๋Ÿ‰(+0.9๋ถ„), ๊ตฌ๊ฐ„ ํŠน์„ฑ(+0.4๋ถ„)\n", " - A1 ๊ตฌ๊ฐ„ day 142: ๊ณ„ํš ๋ฌผ๋Ÿ‰์ด ์šฉ๋Ÿ‰์— ๊ทผ์ ‘ํ•ด ์ง€์ฒด 10.5๋ถ„ ์˜ˆ์ƒ(์ •์ฒด). ์ฃผ์š” ์š”์ธ ๊ณ„ํš ๋ฌผ๋Ÿ‰ยท์ด์šฉ๋ฅ (+7.5๋ถ„), ์ •๋น„ยท์œ ํšจ ์šฉ๋Ÿ‰(+0.7๋ถ„), ์ „์ผยท์ „์ฃผ ์ง€์ฒด(-0.1๋ถ„)\n", " - A1 ๊ตฌ๊ฐ„ day 137: ๊ณ„ํš ๋ฌผ๋Ÿ‰์ด ์šฉ๋Ÿ‰์— ๊ทผ์ ‘ํ•ด ์ง€์ฒด 10.3๋ถ„ ์˜ˆ์ƒ(์ •์ฒด). ์ฃผ์š” ์š”์ธ ๊ณ„ํš ๋ฌผ๋Ÿ‰ยท์ด์šฉ๋ฅ (+7.7๋ถ„), ์ •๋น„ยท์œ ํšจ ์šฉ๋Ÿ‰(+0.7๋ถ„), ์šฐ์ฒœ(+0.2๋ถ„)\n", "\n", "[3] ์ตœ์ข…์ผ ๊ตฌ๊ฐ„ ์ƒํƒœํŒ: A1 ์ •์ฒด(0.97), A2 ์ •์ฒด(0.97), A3 ํ˜ผ์žก(0.82), B1 ํ˜ผ์žก(0.89), B2 ํ˜ผ์žก(0.83), B3 ํ˜ผ์žก(0.81), C1 ์›ํ™œ(0.60), C2 ์›ํ™œ(0.69), X1 ์›ํ™œ(0.61), X2 ์›ํ™œ(0.46)\n", " ์ง€์ฒด 5๋ถ„ ์ด์ƒ ์˜ˆ์ธก ๊ฒฝ๋ณด ๊ตฌ๊ฐ„: ์—†์Œ -> ์šฐํšŒ ๊ฒ€ํ†  ๋Œ€์ƒ(์‹ค์ œ ํ•˜๋‹ฌ์€ ์ž‘์ „๊ตฐ์ˆ˜๊ด€ ์Šน์ธ ํ›„)\n", "\n", "[4] ์šฐํšŒ ๊ฒฝ๋กœ ๋น„๊ต(์ˆ˜์š” 200๋Œ€/์‹œ)\n", " ์‹œ๋‚˜๋ฆฌ์˜ค ๊ณ ์ • ๋ฐฐ๋ถ„(๋ถ„) ์šฐํšŒ ๋ฐฐ์ •(๋ถ„) ๊ณ ์ • ์ตœ๋Œ€ ์ด์šฉ๋ฅ  ๋ฐฐ์ • ์ตœ๋Œ€ ์ด์šฉ๋ฅ  ๋‹จ์ถ•๋ฅ \n", " ํ‰์‹œ 41.91 40.91 0.91 1.0 2%\n", " A2 ํ์‡„ 91.40 44.87 1.24 1.0 51%\n", "A1 ์ •๋น„(50%) 42.64 41.89 0.98 1.0 2%\n", "B1 ์ •๋น„(65%) 53.08 42.14 1.23 1.0 21%\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 07. ์šฐํšŒ๋กœ ์ˆ˜์†ก ๋ช…๋ น ํ•˜๋‹ฌ์„ ์œ„ํ•œ ์ž‘์ „๊ตฐ์ˆ˜๊ด€ HITL ํ†ต์ œ ์›Œํฌํ”Œ๋กœ์šฐ ์„ค๊ณ„\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 3.5์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "์šฐํšŒ๋กœ ์ˆ˜์†ก ๋ช…๋ น ํ•˜๋‹ฌ์„ ์œ„ํ•œ ์ž‘์ „๊ตฐ์ˆ˜๊ด€ HITL ์›Œํฌํ”Œ๋กœ์šฐ๋ฅผ ์„ค๊ณ„ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **์šฐํšŒ ์ˆ˜์†ก ๋ช…๋ น์˜ ํ•˜๋‹ฌ ์—ฌ๋ถ€๋ฅผ ์ž‘์ „๊ตฐ์ˆ˜๊ด€์ด ์ง์ ‘ ์Šน์ธยท๋ฐ˜๋ คํ•˜๋Š” ์ธ๊ฐ„ ๊ฐœ์ž…(HITL) ๊ฒฐ์žฌ ์›Œํฌํ”Œ๋กœ์šฐ๋ฅผ ์„ค๊ณ„ํ•˜๊ณ , ์„ ํ˜•๊ณ„ํš์œผ๋กœ ์šฐํšŒ ๋ฌผ๋Ÿ‰ ๋ฐฐ๋ถ„์„ ์ตœ์ ํ™”ํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **์„ ํ˜•๊ณ„ํš(LP)์œผ๋กœ ๊ตฌ๊ฐ„ ์šฉ๋Ÿ‰ ์ƒํ•œ ์•„๋ž˜์—์„œ ๊ฒฝ๋กœ๋ณ„ ๋ฌผ๋Ÿ‰์„ ๋ฐฐ๋ถ„ํ•˜๊ณ  ์‹œ๋‚˜๋ฆฌ์˜ค 6์ข…๊ณผ ๊ณผ๊ฑฐ 30์ผ ์กฐ๊ฑด์—์„œ ๊ณ ์ • ๋ฐฐ๋ถ„ ๋Œ€๋น„ ๋น„์šฉ ์ ˆ๊ฐ๋ฅ ์„ ๋ฐฑํ…Œ์ŠคํŠธํ•˜๋ฉฐ ๋Œ๋ฐœ ์ˆ˜์š” ๋ฏผ๊ฐ๋„ ํ™•์ธํ•˜๊ณ  ์œ ํšจ์„ฑ์„ ์ ๊ฒ€ํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `src/optimization/mov01_simulator.py` ํ˜•ํƒœ์˜ ๊ฒฐ๊ณผ๋ฌผ์„ ์™„์„ฑํ•œ๋‹ค.\n", "\n", "### ๐Ÿ” ํ‰๊ฐ€ ๋ฐ ๊ฒ€์ฆ ๊ธฐ์ค€ (PASS ์กฐ๊ฑด)\n", "#### ๋ฏธ์…˜ ํŒ์ •\n", "\n", "**ํ‰๊ฐ€ ๊ตฌ๋ถ„:** ํ•„์ˆ˜\n", "\n", "**ํ•ต์‹ฌ ํ‰๊ฐ€์˜์—ญ:** ํ˜„์‹ค์  ๊ตฐ์ˆ˜ ์ œ์•ฝ์กฐ๊ฑด ๋ฐ˜์˜๋„ ๋ฐ ์‹œ๋‚˜๋ฆฌ์˜ค๋ณ„ ๋Œ€์•ˆ ์ œ์‹œ ์ ์ ˆ์„ฑ\n", "\n", "#### ๋ฏธ์…˜ PASS ์กฐ๊ฑด\n", "\n", "- ์ •๋Ÿ‰ ๊ธฐ์ค€: ์ตœ์ ํ™” ์ ์šฉ ์‹œ ๋น„์šฉ 15% ์ ˆ๊ฐ ๋˜๋Š” ์žฌ๊ณ  ํšŒ์ „์œจ 20% ํ–ฅ์ƒ\n", "\n", "\n", "- ์ง„ํ–‰ ๊ธฐ์ค€: ์„ ํ˜•/์ •์ˆ˜ ๊ณ„ํš๋ฒ•(LP/MIP) ์ตœ์ ํ™” ํ•ด ๋„์ถœ ๋ฐ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ์™„๋ฃŒ\n", "\n", "\n", "- ๊ฒ€์ฆ ๋ฐฉ๋ฒ•: ๊ณผ๊ฑฐ ์‹ค์  ๋ฐ์ดํ„ฐ ๋Œ€๋น„ ์ตœ์ ํ™” ๊ฒฐ๊ณผ ๋น„๊ต ๋ฐฑํ…Œ์ŠคํŒ…\n", "\n", "\n", "#### ์ฑ„์ ์ž ํŒ์ • ๋ฉ”๋ชจ\n", "\n", "> ๋‹จ์ˆœ ์ฝ”๋“œ ์‹คํ–‰ ์—ฌ๋ถ€๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ถœ๋ ฅ๋œ ์ˆ˜์น˜์™€ ๊ทผ๊ฑฐ ์ž๋ฃŒ๊ฐ€ ๊ตฐ ์‹ค๋ฌด ์š”๊ตฌ์‚ฌํ•ญ์— ๋ถ€ํ•ฉํ•˜๋Š”์ง€ ๋ฉด๋ฐ€ํžˆ ํ™•์ธํ•œ๋‹ค.\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\"\"\"์„ ํ˜•๊ณ„ํš(LP)์œผ๋กœ ์šฐํšŒ ์ˆ˜์†ก ๋ฌผ๋Ÿ‰ ๋ฐฐ๋ถ„์„ ์ตœ์ ํ™”ํ•ด ๊ณผ๊ฑฐ ๊ด€ํ–‰ ๋Œ€๋น„ ๋น„์šฉ ์ ˆ๊ฐ์„ ๋ฐฑํ…Œ์ŠคํŠธํ•˜๊ณ , ์šฐํšŒ ์ˆ˜์†ก ๋ช…๋ น์„ ์ž‘์ „๊ตฐ์ˆ˜๊ด€์ด ์ง์ ‘ ์Šน์ธยท๋ฐ˜๋ คํ•˜๋Š” HITL ๊ฒฐ์žฌ ์›Œํฌํ”Œ๋กœ์šฐ(๊ถŒํ•œยท2๋‹จ๊ณ„ ๊ฒฐ์žฌยทํ•ด์‹œ ์ฒด์ธ ๊ฐ์‚ฌ ๋กœ๊ทธ)๋ฅผ ๊ตฌํ˜„ํ•œ๋‹ค.\"\"\"\n", "import hashlib\n", "import json\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from scipy.optimize import linprog\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ƒ ๋…ธ์„ ๋งยท๊ตฌ๊ฐ„ ํ†ต๊ณผ๋Ÿ‰ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ๋„๋กœยท์ง€๋ช…ยท๋ถ€๋Œ€ ์—†์Œ)\n", "# ๊ฐ€์ƒ ์ถœ๋ฐœ์ง€(SRC)์—์„œ ๊ฐ€์ƒ ๋ชฉ์ ์ง€(DST)๊นŒ์ง€ 3๊ฐœ ์ถ•์„ (AยทBยทC)๊ณผ ์—ฐ๊ฒฐ๋กœ 2๊ฐœ, ๊ตฌ๊ฐ„ 10๊ฐœ. 150์ผ์น˜ ํ”ผํฌ์‹œ๊ฐ„ ํ†ต๊ณผ๋Ÿ‰ยท์ง€์ฒด ์‹œ๊ฐ„์„ ๋งŒ๋“ ๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "SEGS = pd.DataFrame([\n", " (\"A1\", \"SRC\", \"J1\", 12, 130, \"์ผ๋ฐ˜\"), (\"A2\", \"J1\", \"J2\", 8, 70, \"๊ต๋Ÿ‰\"), (\"A3\", \"J2\", \"DST\", 10, 140, \"๊ต์ฐจ๋กœ\"),\n", " (\"B1\", \"SRC\", \"J3\", 15, 100, \"์ผ๋ฐ˜\"), (\"B2\", \"J3\", \"J2\", 9, 80, \"ํ„ฐ๋„\"), (\"B3\", \"J3\", \"DST\", 14, 95, \"์ผ๋ฐ˜\"),\n", " (\"C1\", \"SRC\", \"J4\", 18, 70, \"์ผ๋ฐ˜\"), (\"C2\", \"J4\", \"DST\", 20, 75, \"๊ต๋Ÿ‰\"),\n", " (\"X1\", \"J1\", \"J3\", 7, 50, \"๊ต์ฐจ๋กœ\"), (\"X2\", \"J4\", \"J3\", 10, 60, \"์ผ๋ฐ˜\")],\n", " columns=[\"seg\", \"u\", \"v\", \"km\", \"cap\", \"kind\"]) # cap = ํ”ผํฌ์‹œ๊ฐ„ ํ†ต๊ณผ ์šฉ๋Ÿ‰(๋Œ€/์‹œ, ๊ต์œก์šฉ ๊ฐ€์ •๊ฐ’)\n", "KSPD = {\"์ผ๋ฐ˜\": 55, \"๊ต๋Ÿ‰\": 40, \"ํ„ฐ๋„\": 45, \"๊ต์ฐจ๋กœ\": 45} # ๊ตฌ๊ฐ„ ์œ ํ˜•๋ณ„ ์ž์œ  ์ฃผํ–‰ ์†๋„(km/h, ๊ฐ€์ •๊ฐ’)\n", "SEGS[\"t0\"] = SEGS[\"km\"] / SEGS[\"kind\"].map(KSPD) * 60 # ์ž์œ  ์ฃผํ–‰ ์‹œ๊ฐ„(๋ถ„)\n", "UTIL = dict(A1=.78, A2=.85, A3=.66, B1=.70, B2=.72, B3=.64, C1=.55, C2=.58, X1=.45, X2=.40) # ํ‰์‹œ ์ด์šฉ๋ฅ \n", "\n", "\n", "def bpr(t0, v, cap, a=0.15, b=4.0):\n", " \"\"\"BPR ์ง€์ฒด ํ•จ์ˆ˜(๊ตํ†ต๊ณตํ•™์˜ ํ‘œ์ค€ํ˜•): ํ†ต๊ณผ๋Ÿ‰/์šฉ๋Ÿ‰ ๋น„๊ฐ€ ์ปค์งˆ์ˆ˜๋ก ์ง€์ฒด๊ฐ€ ๊ธ‰์ฆํ•œ๋‹ค. (๋ถ„)\"\"\"\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 7] ์šฐํšŒ๋กœ ์ˆ˜์†ก ๋ช…๋ น ํ•˜๋‹ฌ์„ ์œ„ํ•œ ์ž‘์ „๊ตฐ์ˆ˜๊ด€ HITL ํ†ต์ œ ์›Œํฌํ”Œ๋กœ์šฐ ์„ค๊ณ„ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def make_data(seed=11, days=150):\n", " \"\"\"๊ตฌ๊ฐ„ x ์ผ์ž ํŒจ๋„. v=์‹ค์ œ ํ”ผํฌ ํ†ต๊ณผ๋Ÿ‰, plan_v=์ˆ˜์†ก๊ณ„ํš ์ˆ˜๋Ÿ‰(๊ธด๊ธ‰ ๋ถˆ์ถœ ๋ฏธ๋ฐ˜์˜), delay=์‹ค์ œ ์ง€์ฒด(๋ถ„).\"\"\"\n", " rng = np.random.default_rng(seed)\n", " d_idx = np.arange(days)\n", " lvl = np.array([1.0, 1.08, 1.04, 1.10, 1.02, 0.82, 0.78])[d_idx % 7] * (1 + 0.0012 * d_idx)\n", " lvl = lvl * np.where((d_idx >= 100) & (d_idx < 110), 1.30, 1.0) # ์ˆ˜์†ก๋Ÿ‰ ํญ์ฆ ๊ธฐ๊ฐ„(๊ฐ€์ƒ)\n", " prev, rows = {s: 0.0 for s in SEGS[\"seg\"]}, []\n", " for d in d_idx:\n", " rain = bool(rng.random() < 0.18)\n", " for r in SEGS.itertuples():\n", " maint, urgent = bool(rng.random() < 0.06), bool(rng.random() < 0.05) # ๋น„๊ณ„ํš ์ •๋น„ / ๊ธด๊ธ‰ ๋ถˆ์ถœ\n", " v = r.cap * UTIL[r.seg] * lvl[d] * (1.25 if urgent else 1.0) * rng.normal(1, 0.05)\n", " cap_eff = r.cap * (0.65 if maint else 1.0) * (0.92 if rain else 1.0)\n", " delay = max(r.t0 * 0.2 * (v / cap_eff) ** 3.5 + 0.25 * prev[r.seg] + rng.normal(0, 0.4), 0.0)\n", " prev[r.seg] = delay\n", " rows.append(dict(day=int(d), dow=int(d % 7), seg=r.seg, kind=r.kind, km=r.km, cap=r.cap, t0=r.t0,\n", " plan_v=v / (1.25 if urgent else 1.0) * rng.normal(1, 0.04), v=v, maint=int(maint), urgent=int(urgent),\n", " rain=int(rain), cap_eff=cap_eff, delay=delay))\n", " df = pd.DataFrame(rows)\n", " df[\"transit\"] = df[\"t0\"] * (1 + 0.08 * df[\"rain\"]) + df[\"delay\"]\n", " return df\n", "\n", "\n", "def mape(y, p):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 7] ์šฐํšŒ๋กœ ์ˆ˜์†ก ๋ช…๋ น ํ•˜๋‹ฌ์„ ์œ„ํ•œ ์ž‘์ „๊ตฐ์ˆ˜๊ด€ HITL ํ†ต์ œ ์›Œํฌํ”Œ๋กœ์šฐ ์„ค๊ณ„ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def mae(y, p):\n", " return float(np.mean(np.abs(np.asarray(y, float) - np.asarray(p, float))))\n", "# ---------------------------------------------------------------------------\n", "# ---------------------------------------------------------------------------\n", "# ๋…ธ์„ ๋ง ํ๋ฆ„ ๋„๊ตฌ: ์ตœ๋Œ€์œ ๋Ÿ‰(Edmonds-Karp), ์ฆ๋ถ„ ๋ฐฐ์ •(ํ˜ผ์žก ๋ฐ˜์˜ ์ตœ๋‹จ์‹œ๊ฐ„ ๊ฒฝ๋กœ), ๊ฒฝ๋กœ ์—ด๊ฑฐ\n", "# ---------------------------------------------------------------------------\n", "CAP = dict(zip(SEGS[\"seg\"], SEGS[\"cap\"]))\n", "T0 = dict(zip(SEGS[\"seg\"], SEGS[\"t0\"]))\n", "KM = dict(zip(SEGS[\"seg\"], SEGS[\"km\"]))\n", "ARCS = {r.seg: (r.u, r.v) for r in SEGS.itertuples()}\n", "SRC, DST = \"SRC\", \"DST\"\n", "\n", "\n", "def seg_time(s, flow, cap=None, a=0.2, b=3.5):\n", " \"\"\"๊ตฌ๊ฐ„ ํ†ต๊ณผ์‹œ๊ฐ„(๋ถ„) = ์ž์œ ์ฃผํ–‰ + ํ˜ผ์žก ์ง€์ฒด. cap์„ ์ฃผ๋ฉด ์šฉ๋Ÿ‰์„ ๋Œ€์ฒด(์ •๋น„ยท๊ธฐ์ƒ ๋ฐ˜์˜).\"\"\"\n", " r = flow / (cap or CAP[s])\n", " return T0[s] * (1 + a * r ** b + 6.0 * max(0.0, r - 1.0)) # ์šฉ๋Ÿ‰ ์ดˆ๊ณผ๋ถ„์€ ๋Œ€๊ธฐ์—ด๋กœ ๊ธ‰์ฆ\n", "\n", "\n", "def max_flow(closed=(), capmul=None):\n", " \"\"\"์ตœ๋Œ€์œ ๋Ÿ‰(๋Œ€/์‹œ)๊ณผ ์ตœ์†Œ ์ ˆ๋‹จ ๊ตฌ๊ฐ„(๋ณ‘๋ชฉ). capmul = {๊ตฌ๊ฐ„: ์šฉ๋Ÿ‰ ๋ฐฐ์œจ}.\"\"\"\n", " cap = {s: (0 if s in closed else CAP[s] * (capmul or {}).get(s, 1.0)) for s in CAP}\n", " res = {}\n", " for s, (u, v) in ARCS.items():\n", " res[(u, v)] = res.get((u, v), 0) + cap[s]\n", " res.setdefault((v, u), 0)\n", " flow = 0.0\n", " while True:\n", " prev, q = {SRC: None}, [SRC]\n", " for u in q:\n", " for (a, b), c in res.items():\n", " if a == u and c > 1e-9 and b not in prev:\n", " prev[b] = u; q.append(b)\n", " if DST not in prev:\n", " break\n", " path, x = [], DST\n", " while prev[x]:\n", " path.append((prev[x], x)); x = prev[x]\n", " f = min(res[e] for e in path)\n", " for a, b in path:\n", " res[(a, b)] -= f; res[(b, a)] += f\n", " flow += f\n", " reach = set(prev)\n", " cut = [s for s, (u, v) in ARCS.items() if u in reach and v not in reach and cap[s] > 0]\n", " return flow, cut\n", "\n", "\n", "def all_paths():\n", " \"\"\"SRC -> DST ๋‹จ์ˆœ ๊ฒฝ๋กœ(๊ตฌ๊ฐ„ ์ด๋ฆ„ ํŠœํ”Œ) ์ „์ฒด.\"\"\"\n", " out = []\n", "\n", " def dfs(node, segs, seen):\n", " if node == DST:\n", " out.append(tuple(segs)); return\n", " for s, (u, v) in ARCS.items():\n", " if u == node and v not in seen:\n", " dfs(v, segs + [s], seen | {v})\n", " dfs(SRC, [], {SRC})\n", " return out\n", "\n", "\n", "PATHS = all_paths()\n", "\n", "\n", "def assign(demand, closed=(), capmul=None, steps=40):\n", " \"\"\"์ฆ๋ถ„ ๋ฐฐ์ •: ์ˆ˜์š”๋ฅผ steps์กฐ๊ฐ์œผ๋กœ ๋‚˜๋ˆ , ์ „์ฒด ํ†ต๊ณผ์‹œ๊ฐ„(๋Œ€์ˆ˜ x ์‹œ๊ฐ„) ์ฆ๊ฐ€๊ฐ€ ๊ฐ€์žฅ ์ž‘์€ ๊ฒฝ๋กœ์— ์ฐจ๋ก€๋กœ ์–น๋Š”๋‹ค(์‹œ์Šคํ…œ ์ตœ์  ๊ทผ์‚ฌ).\"\"\"\n", " cap = {s: CAP[s] * (capmul or {}).get(s, 1.0) for s in CAP}\n", " flow, pf = {s: 0.0 for s in CAP}, {}\n", " ok = [p for p in PATHS if not any(s in closed for s in p)]\n", " if not ok:\n", " return None\n", " for _ in range(steps):\n", " chunk = demand / steps\n", " best = min(ok, key=lambda p: sum((flow[s] + chunk) * seg_time(s, flow[s] + chunk, cap[s]) - flow[s] * seg_time(s, flow[s], cap[s]) for s in p))\n", " for s in best:\n", " flow[s] += chunk\n", " pf[best] = pf.get(best, 0) + chunk\n", " tt = {p: sum(seg_time(s, flow[s], cap[s]) for s in p) for p in pf}\n", " avg = sum(pf[p] * tt[p] for p in pf) / demand\n", " return {\"flow\": flow, \"paths\": pf, \"path_min\": tt, \"avg_min\": avg, \"util\": {s: flow[s] / cap[s] for s in cap if cap[s] > 0}}\n", "\n", "\n", "FIXED_SHARE = {(\"A1\", \"A2\", \"A3\"): 0.32, (\"B1\", \"B3\"): 0.40, (\"C1\", \"C2\"): 0.28} # ๊ณผ๊ฑฐ ๊ด€ํ–‰: ์ถ•์„ ๋ณ„ ๊ณ ์ • ๋ฐฐ๋ถ„(๊ฐ€์ƒ)\n", "\n", "\n", "def fixed_assign(demand, closed=(), capmul=None):\n", " \"\"\"๊ณผ๊ฑฐ ๊ด€ํ–‰ ๋ฐฐ๋ถ„: ์ถ•์„ ๋ณ„ ๊ณ ์ • ๋น„์œจ. ๋ง‰ํžŒ ์ถ•์„  ๋ฌผ๋Ÿ‰์€ ์—ด๋ ค ์žˆ๋Š” ์ถ•์„ ์— ๋น„์œจ๋Œ€๋กœ ์žฌ๋ถ„๋ฐฐ.\"\"\"\n", " cap = {s: CAP[s] * (capmul or {}).get(s, 1.0) for s in CAP}\n", " live = {p: w for p, w in FIXED_SHARE.items() if not any(s in closed for s in p)}\n", " if not live:\n", " return None\n", " tot = sum(live.values())\n", " flow = {s: 0.0 for s in CAP}\n", " pf = {p: demand * w / tot for p, w in live.items()}\n", " for p, f in pf.items():\n", " for s in p:\n", " flow[s] += f\n", " tt = {p: sum(seg_time(s, flow[s], cap[s]) for s in p) for p in pf}\n", " return {\"flow\": flow, \"paths\": pf, \"path_min\": tt, \"avg_min\": sum(pf[p] * tt[p] for p in pf) / demand,\n", " \"util\": {s: flow[s] / cap[s] for s in cap if cap[s] > 0}}\n", "\n", "\n", "def lp_assign(demand, closed=(), capmul=None):\n", " \"\"\"์„ ํ˜•๊ณ„ํš(LP): ๊ตฌ๊ฐ„ ์šฉ๋Ÿ‰ ์ƒํ•œ(์ด์šฉ๋ฅ  rho) ์•„๋ž˜์—์„œ ์ž์œ ์ฃผํ–‰์‹œ๊ฐ„ ํ•ฉ์„ ์ตœ์†Œํ™”ํ•˜๋Š” ๊ฒฝ๋กœ๋ณ„ ๋ฌผ๋Ÿ‰. ๋ถˆ๊ฐ€๋Šฅํ•˜๋ฉด rho๋ฅผ 1.0๊นŒ์ง€ ์™„ํ™”.\"\"\"\n", " cap = {s: CAP[s] * (capmul or {}).get(s, 1.0) for s in CAP}\n", " ok = [p for p in PATHS if not any(s in closed for s in p)]\n", " if not ok:\n", " return None\n", " for rho in (0.85, 0.95, 1.0):\n", " r = linprog([sum(T0[s] for s in p) for p in ok], A_ub=[[1.0 if s in p else 0.0 for p in ok] for s in CAP],\n", " b_ub=[rho * cap[s] for s in CAP], A_eq=[[1.0] * len(ok)], b_eq=[demand], bounds=(0, None), method=\"highs\")\n", " if r.status == 0:\n", " break\n", " else:\n", " return assign(demand, closed, capmul) # ์šฉ๋Ÿ‰ ์ดˆ๊ณผ ์ˆ˜์š”: ์ฆ๋ถ„ ๋ฐฐ์ •์œผ๋กœ ๋Œ€์ฒด(๊ณผ๋ถ€ํ•˜ ํ‘œ์‹œ)\n", " flow = {s: sum(r.x[i] for i, p in enumerate(ok) if s in p) for s in CAP}\n", " pf = {p: float(r.x[i]) for i, p in enumerate(ok) if r.x[i] > 1e-6}\n", " tt = {p: sum(seg_time(s, flow[s], cap[s]) for s in p) for p in pf}\n", " return {\"flow\": flow, \"paths\": pf, \"path_min\": tt, \"avg_min\": sum(pf[p] * tt[p] for p in pf) / demand,\n", " \"util\": {s: flow[s] / cap[s] for s in cap if cap[s] > 0}, \"rho\": rho}\n", "\n", "\n", "def cost_of(res, tv=1000, fuel_km=450):\n", " \"\"\"์‹œ๊ฐ„๋‹น ๋น„์šฉ(์›, ๊ต์œก์šฉ ๊ฐ€์ •): ๋Œ€๋‹น ์‹œ๊ฐ„๋น„์šฉ tv์›/๋ถ„ + ์œ ๋ฅ˜๋น„ fuel_km์›/km.\"\"\"\n", " return sum(f * (res[\"path_min\"][p] * tv + sum(KM[s] for s in p) * fuel_km) for p, f in res[\"paths\"].items())\n", "# ---------------------------------------------------------------------------\n", "\n", "# ---------------- 1) ์ตœ์ ํ™” ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ: ๊ณผ๊ฑฐ ๊ด€ํ–‰(์ถ•์„  ๊ณ ์ • ๋ฐฐ๋ถ„) vs LP ----------------\n", "SC = [(\"ํ‰์‹œ\", 200, (), None), (\"์ˆ˜์†ก๋Ÿ‰ ํญ์ฆ(+30%)\", 260, (), None), (\"A2 ๊ต๋Ÿ‰ ์ •๋น„(์šฉ๋Ÿ‰ 65%)\", 200, (), {\"A2\": .65}), (\"A2 ๊ต๋Ÿ‰ ํ์‡„\", 200, (\"A2\",), None),\n", " (\"A2 ํ์‡„ + ์šฐ์ฒœ\", 200, (\"A2\",), {k: .92 for k in CAP}), (\"B1 ์ •๋น„ + ํญ์ฆ\", 250, (), {\"B1\": .65})]\n", "rows, plans = [], {}\n", "for name, D, cl, cm in SC:\n", " f, l = fixed_assign(D, cl, cm), lp_assign(D, cl, cm)\n", " cf, cl_ = cost_of(f), cost_of(l)\n", " rows.append((name, D, f[\"avg_min\"], l[\"avg_min\"], cf / 1e4, cl_ / 1e4, 1 - cl_ / cf))\n", " plans[name] = (D, cl, cm, l, cf, cl_)\n", "res = pd.DataFrame(rows, columns=[\"์‹œ๋‚˜๋ฆฌ์˜ค\", \"์ˆ˜์š”\", \"๊ณ ์ • ๋ฐฐ๋ถ„(๋ถ„)\", \"LP(๋ถ„)\", \"๊ณ ์ • ๋น„์šฉ(๋งŒ์›/์‹œ)\", \"LP ๋น„์šฉ(๋งŒ์›/์‹œ)\", \"์ ˆ๊ฐ๋ฅ \"])\n", "print(\"[1] ์‹œ๋‚˜๋ฆฌ์˜ค๋ณ„ ๋น„์šฉ ๋น„๊ต(์‹œ๊ฐ„ยท์œ ๋ฅ˜ ๋‹จ๊ฐ€๋Š” ๊ต์œก์šฉ ๊ฐ€์ •๊ฐ’)\")\n", "print(res.assign(์ ˆ๊ฐ๋ฅ =res[\"์ ˆ๊ฐ๋ฅ \"].map(\"{:.1%}\".format)).round(1).to_string(index=False))\n", "avg = res[\"์ ˆ๊ฐ๋ฅ \"].mean()\n", "print(f\" ์‹œ๋‚˜๋ฆฌ์˜ค 6์ข… ํ‰๊ท  ์ ˆ๊ฐ {avg:.1%}, ์šฐํšŒ๊ฐ€ ํ•„์š”ํ•œ 4๊ฐœ ์‹œ๋‚˜๋ฆฌ์˜ค ํ‰๊ท  {res['์ ˆ๊ฐ๋ฅ '].iloc[2:].mean():.1%} (๋ชฉํ‘œ 15% ์ด์ƒ: {'๋‹ฌ์„ฑ' if avg >= .15 else '๋ฏธ๋‹ฌ'}), ํ‰์‹œ๋Š” {res['์ ˆ๊ฐ๋ฅ '].iloc[0]:.1%}์— ๊ทธ์นœ๋‹ค\")\n", "\n", "# ---------------- 2) ๋ฐฑํ…Œ์ŠคํŒ…: ๊ณผ๊ฑฐ 30์ผ(day 120~149) ์‹ค์  ์กฐ๊ฑด์œผ๋กœ ์žฌํ˜„ ----------------\n", "df = make_data()\n", "rec = []\n", "for day, g in df[df.day >= 120].groupby(\"day\"):\n", " D = float(g[g.seg.isin([\"A1\", \"B1\", \"C1\"])][\"plan_v\"].sum())\n", " cm = {r.seg: r.cap_eff / r.cap for r in g.itertuples() if r.cap_eff < r.cap}\n", " f, l = fixed_assign(D, (), cm), lp_assign(D, (), cm)\n", " rec.append((day, D, cost_of(f), cost_of(l)))\n", "bt = pd.DataFrame(rec, columns=[\"day\", \"D\", \"fix\", \"lp\"])\n", "bt[\"save\"] = 1 - bt[\"lp\"] / bt[\"fix\"]\n", "print(f\"\\n[2] ๋ฐฑํ…Œ์ŠคํŒ…(30์ผ): ํ‰๊ท  ์ ˆ๊ฐ {bt['save'].mean():.1%}(๋ชฉํ‘œ 15% ์ด์ƒ: {'๋‹ฌ์„ฑ' if bt['save'].mean() >= .15 else '๋ฏธ๋‹ฌ'}), ์ตœ๋Œ€ {bt['save'].max():.1%}(day {int(bt.loc[bt['save'].idxmax(), 'day'])}), ์ ˆ๊ฐ 0 ์ดํ•˜์ธ ๋‚  {int((bt['save'] <= 0).sum())}์ผ\")\n", "print(f\" ์ ˆ๊ฐ 10% ๋ฏธ๋งŒ์ธ ๋‚  {int((bt['save'] < .10).sum())}์ผ: ํšจ๊ณผ๋Š” ์ •๋น„ยทํญ์ฆ์ด ๊ฒน์นœ ๋‚ ์— ์ง‘์ค‘๋œ๋‹ค. ๊ณผ๊ฑฐ ๊ด€ํ–‰ ๋ฐฐ๋ถ„์€ ๊ฐ€์ •์ด๋ฏ€๋กœ ์‹ค์ œ ๋ถ€๋Œ€ ์‹ค์ ์œผ๋กœ๋Š” ๋ณ„๋„ ๊ฒ€์ฆ์ด ํ•„์š”ํ•˜๋‹ค.\")\n", "\n", "# ---------------- 3) ๋ฏผ๊ฐ๋„: ๋Œ๋ฐœ ์ˆ˜์š”(์ „์‹œ ์ „ํ™˜ ๊ฐ€์ •) ----------------\n", "sens = []\n", "for k in (0.8, 1.0, 1.2, 1.4, 1.6):\n", " f, l = fixed_assign(200 * k, (\"A2\",)), lp_assign(200 * k, (\"A2\",))\n", " sens.append((f\"x{k}\", 200 * k, max(l[\"util\"].values()), 1 - cost_of(l) / cost_of(f)))\n", "sdf = pd.DataFrame(sens, columns=[\"์ˆ˜์š” ๋ฐฐ์œจ\", \"์ˆ˜์š”\", \"LP ์ตœ๋Œ€ ์ด์šฉ๋ฅ \", \"์ ˆ๊ฐ๋ฅ \"])\n", "print(\"[3] ๋Œ๋ฐœ ์ˆ˜์š” ๋ฏผ๊ฐ๋„(A2 ํ์‡„ ์ƒํ™ฉ):\", \"; \".join(f\"{r['์ˆ˜์š” ๋ฐฐ์œจ']} -> ์ด์šฉ๋ฅ  {r['LP ์ตœ๋Œ€ ์ด์šฉ๋ฅ ']:.2f}, ์ ˆ๊ฐ {r['์ ˆ๊ฐ๋ฅ ']:.0%}\" for _, r in sdf.iterrows()))\n", "print(\" ์ˆ˜์š” x1.2 ์ด์ƒ์—์„œ๋Š” ์ตœ๋Œ€์œ ๋Ÿ‰(220๋Œ€/์‹œ)์„ ๋„˜์–ด ์šฐํšŒ๋งŒ์œผ๋กœ ์ˆ˜์šฉํ•  ์ˆ˜ ์—†์œผ๋ฏ€๋กœ ๋ฌผ๋Ÿ‰ ์กฐ์ • ๊ฒฐ์ •์ด ํ•„์š”ํ•˜๋‹ค.\")\n", "\n", "# ---------------- 4) HITL ๊ฒฐ์žฌ ์›Œํฌํ”Œ๋กœ์šฐ ----------------\n", "USERS = {\"plan_a\": \"PLN\", \"plan_b\": \"PLN\", \"ops_kim\": \"OPS\", \"ops_lee\": \"OPS\", \"sup_park\": \"SUP\", \"aud_han\": \"AUD\"}\n", "PERM = {\"PLN\": {\"submit\", \"redraft\"}, \"OPS\": {\"approve\", \"reject\"}, \"SUP\": {\"approve\", \"reject\"}, \"AUD\": {\"read\"}}\n", "\n", "\n", "class Flow:\n", " def __init__(self):\n", " self.docs, self.log, self.blocked = {}, [], []\n", "\n", " def _audit(self, user, action, doc, detail):\n", " prev = self.log[-1][\"hash\"] if self.log else \"GENESIS\"\n", " rec = {\"seq\": len(self.log) + 1, \"user\": user, \"action\": action, \"doc\": doc, \"detail\": detail, \"prev\": prev}\n", " rec[\"hash\"] = hashlib.sha256(json.dumps(rec, ensure_ascii=False, sort_keys=True).encode()).hexdigest()[:12]\n", " self.log.append(rec)\n", "\n", " def verify(self):\n", " prev = \"GENESIS\"\n", " for r in self.log:\n", " body = {k: v for k, v in r.items() if k != \"hash\"}\n", " if r[\"prev\"] != prev or hashlib.sha256(json.dumps(body, ensure_ascii=False, sort_keys=True).encode()).hexdigest()[:12] != r[\"hash\"]:\n", " return False, r[\"seq\"]\n", " prev = r[\"hash\"]\n", " return True, None\n", "\n", " def deny(self, user, act, doc, why):\n", " self.blocked.append((user, act, doc, why)); self._audit(user, \"DENIED:\" + act, doc, why); return False\n", "\n", " def submit(self, user, scen, parent=None):\n", " need = \"redraft\" if parent else \"submit\"\n", " if need not in PERM.get(USERS.get(user), set()):\n", " return self.deny(user, need, \"-\", \"์ƒ์‹  ๊ถŒํ•œ ์—†์Œ\")\n", " D, cl, cm, l, cf, cl_ = plans[scen]\n", " extra = l[\"avg_min\"] - plans[\"ํ‰์‹œ\"][3][\"avg_min\"]\n", " big = extra >= 3 or max(l[\"util\"].values()) > 0.98 # ์ถ”๊ฐ€ ์†Œ์š” 3๋ถ„ ์ด์ƒ ๋˜๋Š” ์šฉ๋Ÿ‰ ํฌํ™” -> 2๋‹จ๊ณ„ ๊ฒฐ์žฌ\n", " did = f\"ORD{len(self.docs) + 1:02d}\"\n", " self.docs[did] = {\"scen\": scen, \"line\": [\"OPS\", \"SUP\"] if big else [\"OPS\"], \"step\": 0, \"status\": \"๊ฒ€ํ† ๋Œ€๊ธฐ\", \"drafter\": user, \"done\": [], \"parent\": parent}\n", " self._audit(user, \"submit\", did, f\"{scen} ์ถ”๊ฐ€์†Œ์š” {extra:+.1f}๋ถ„\")\n", " return did\n", "\n", " def decide(self, user, did, act, reason=\"\"):\n", " role, d = USERS.get(user), self.docs.get(did)\n", " if role is None: return self.deny(user, act, did, \"๋ฏธ๋“ฑ๋ก ์‚ฌ์šฉ์ž\")\n", " if d is None or d[\"status\"] != \"๊ฒ€ํ† ๋Œ€๊ธฐ\": return self.deny(user, act, did, \"๊ฒฐ์žฌ ๋Œ€์ƒ ์•„๋‹˜(์ฒ˜๋ฆฌ ์™„๋ฃŒ)\")\n", " if act not in PERM[role]: return self.deny(user, act, did, f\"{role} ์—ญํ• ์€ {act} ๋ถˆ๊ฐ€\")\n", " if d[\"line\"][d[\"step\"]] != role: return self.deny(user, act, did, \"๊ฒฐ์žฌ ์ˆœ์„œ ์œ„๋ฐ˜\")\n", " if user == d[\"drafter\"] or user in d[\"done\"]: return self.deny(user, act, did, \"๊ธฐ์•ˆ์žยท๊ธฐ๊ฒฐ์žฌ์ž ์žฌ๊ฒฐ์žฌ ๊ธˆ์ง€\")\n", " if len(reason.strip()) < 10: return self.deny(user, act, did, \"์‚ฌ์œ  10์ž ์ด์ƒ ํ•„์ˆ˜\")\n", " if act == \"reject\":\n", " d[\"status\"] = \"๋ฐ˜๋ ค\"\n", " else:\n", " d[\"done\"].append(user); d[\"step\"] += 1\n", " d[\"status\"] = \"์Šน์ธ\" if d[\"step\"] == len(d[\"line\"]) else \"๊ฒ€ํ† ๋Œ€๊ธฐ\"\n", " self._audit(user, act, did, reason); return True\n", "\n", " def edit(self, user, did):\n", " if self.docs[did][\"status\"] == \"์Šน์ธ\": return self.deny(user, \"edit\", did, \"์Šน์ธ ํ›„ ๋ช…๋ น ๋ณ€๊ฒฝ ๊ธˆ์ง€(์žฌ๊ธฐ์•ˆ)\")\n", " return True\n", "\n", "\n", "wf = Flow()\n", "names = [\"A2 ๊ต๋Ÿ‰ ํ์‡„\", \"A2 ๊ต๋Ÿ‰ ์ •๋น„(์šฉ๋Ÿ‰ 65%)\", \"์ˆ˜์†ก๋Ÿ‰ ํญ์ฆ(+30%)\", \"B1 ์ •๋น„ + ํญ์ฆ\", \"A2 ํ์‡„ + ์šฐ์ฒœ\", \"ํ‰์‹œ\"]\n", "ids = [wf.submit(\"plan_a\" if i % 2 == 0 else \"plan_b\", n) for i, n in enumerate(names)]\n", "print(f\"\\n[4] AI ์šฐํšŒ์•ˆ {len(ids)}๊ฑด ์ „๋ถ€ '๊ฒ€ํ† ๋Œ€๊ธฐ' ๋“ฑ๋ก, ์ž๋™ ์Šน์ธ {sum(wf.docs[i]['status'] == '์Šน์ธ' for i in ids)}๊ฑด (๋ชฉํ‘œ 0๊ฑด). 2๋‹จ๊ณ„ ๊ฒฐ์žฌ ๋Œ€์ƒ {[i for i in ids if len(wf.docs[i]['line']) == 2]}\")\n", "wf.decide(\"ops_kim\", ids[0], \"reject\", \"์ถ”๊ฐ€ ์†Œ์š”์™€ ์šฉ๋Ÿ‰ ํฌํ™” ์šฐ๋ ค, ๋ฌผ๋Ÿ‰ ์กฐ์ •์•ˆ ํฌํ•จํ•ด ์žฌ๊ธฐ์•ˆ\")\n", "new = wf.submit(\"plan_a\", names[0], parent=ids[0])\n", "wf.decide(\"ops_kim\", new, \"approve\", \"์šฐํšŒ ๊ฒฝ๋กœ ์šฉ๋Ÿ‰ ํ™•์ธ, ์ƒ๊ธ‰ ์Šน์ธ ์ƒ์‹ ํ•จ\")\n", "wf.decide(\"sup_park\", new, \"approve\", \"์šฐํšŒ ์‹œํ–‰, ์šดํ–‰ ์ค‘ ์ง€์ฒด ๋ณด๊ณ  ์กฐ๊ฑด\")\n", "wf.decide(\"ops_lee\", ids[1], \"approve\", \"์ •๋น„ ๊ตฌ๊ฐ„ ์šฉ๋Ÿ‰ ๊ฐ์†Œ๋ถ„ ์šฐํšŒ ๋ฐ˜์˜ ํƒ€๋‹น\")\n", "wf.decide(\"ops_kim\", ids[5], \"approve\", \"ํ‰์‹œ ๋ฌผ๋Ÿ‰์œผ๋กœ ๋ณ€๊ฒฝ ์‚ฌ์œ  ์—†์–ด ๊ธฐ์กด ์œ ์ง€\")\n", "attacks = [(\"plan_a\", ids[2], \"approve\", \"๋‚ด๊ฐ€ ์˜ฌ๋ฆฐ ์•ˆ์„ ์ง์ ‘ ์Šน์ธํ•œ๋‹ค\", \"์š”์ฒญ์ž ์ž๊ธฐ ์Šน์ธ\"), (\"aud_han\", ids[2], \"approve\", \"๊ฐ์‚ฌ ๊ณ„์ •์œผ๋กœ ์Šน์ธ ์‹œ๋„\", \"๊ฐ์‚ฌ ์—ญํ•  ์Šน์ธ\"),\n", " (\"ghost\", ids[2], \"approve\", \"์™ธ๋ถ€ ๊ณ„์ •์œผ๋กœ ์Šน์ธ ์‹œ๋„\", \"๋ฏธ๋“ฑ๋ก ์‚ฌ์šฉ์ž\"), (\"ops_kim\", ids[2], \"approve\", \"\", \"์‚ฌ์œ  ์—†๋Š” ์Šน์ธ\"),\n", " (\"ops_kim\", ids[2], \"reject\", \"์งง์Œ\", \"์‚ฌ์œ  ์งง์€ ๋ฐ˜๋ ค\"), (\"sup_park\", ids[2], \"approve\", \"์ƒ๊ธ‰์ž๊ฐ€ ์ˆœ์„œ๋ฅผ ๊ฑด๋„ˆ๋›ฐ์–ด ์Šน์ธ\", \"๊ฒฐ์žฌ ์ˆœ์„œ ๊ฑด๋„ˆ๋›ฐ๊ธฐ\"),\n", " (\"ops_kim\", ids[5], \"approve\", \"์ด๋ฏธ ์Šน์ธ๋œ ๋ฌธ์„œ๋ฅผ ๋‹ค์‹œ ์Šน์ธ\", \"์ฒ˜๋ฆฌ ์™„๋ฃŒ ์žฌ์Šน์ธ\"), (\"sup_park\", new, \"reject\", \"์Šน์ธ๋œ ๋ฌธ์„œ๋ฅผ ๋’ค์ง‘์–ด ๋ฐ˜๋ ค\", \"์Šน์ธ ํ›„ ๋ฒˆ๋ณต\"),\n", " (\"plan_b\", ids[3], \"approve\", \"์š”์ฒญ์ž ๊ณ„์ •์œผ๋กœ ์Šน์ธ ์‹œ๋„ํ•จ\", \"์š”์ฒญ์ž ๊ถŒํ•œ ์œ„๋ฐ˜\")]\n", "for u, doc, act, rs, _ in attacks:\n", " wf.decide(u, doc, act, rs)\n", "wf.edit(\"plan_a\", new) # 10) ์Šน์ธ ํ›„ ๋ช…๋ น ๋ณ€๊ฒฝ\n", "print(f\" ๊ถŒํ•œ ์šฐํšŒ ์‹œ๋„ 10๊ฑด ์ค‘ ์ฐจ๋‹จ {len(wf.blocked)}๊ฑด (์ฐจ๋‹จ์œจ {len(wf.blocked) / 10:.0%}, ๋ชฉํ‘œ 100%)\")\n", "for u, a, doc, why in wf.blocked[:4]:\n", " print(f\" ์ฐจ๋‹จ {u:<9} {a:<8} {doc}: {why}\")\n", "ok, _ = wf.verify()\n", "snap = json.loads(json.dumps(wf.log))\n", "wf.log[3][\"detail\"] = \"์‚ฌํ›„์— ๊ณ ์ณ ์“ด ์‚ฌ์œ \"\n", "ok2, at = wf.verify()\n", "wf.log = snap\n", "print(f\" ๊ฐ์‚ฌ ๋กœ๊ทธ {len(wf.log)}๊ฑด ํ•ด์‹œ ์ฒด์ธ ๋ฌด๊ฒฐ์„ฑ {'100%' if ok else 'ํ›ผ์†'}; 4๋ฒˆ ๊ธฐ๋ก ์‚ฌํ›„ ์ˆ˜์ • ์‹œ {'ํ†ต๊ณผ' if ok2 else f'์œ„๋ณ€์กฐ ๊ฒ€์ถœ({at}๋ฒˆ)'}\")\n", "print(f\" {ids[0]}(๋ฐ˜๋ ค) -> {new}(์žฌ๊ธฐ์•ˆ, parent ์—ฐ๊ฒฐ) ์ด๋ ฅ:\", [(r['action'], r['user']) for r in wf.log if r['doc'] in (ids[0], new)])\n", "print(\" ๋ฌธ์„œ ์ƒํƒœ:\", pd.Series({k: v[\"status\"] for k, v in wf.docs.items()}).value_counts().to_dict(), \"| ์‹ค์ œ ์šฐํšŒ ๋ช…๋ น ํ•˜๋‹ฌ์€ ์ง€ํœ˜๊ณ„ํ†ต(์ž‘์ „๊ตฐ์ˆ˜๊ด€ ์Šน์ธ) ๊ฒฐ์‹ฌ์— ๋”ฐ๋ฅธ๋‹ค.\")\n", "\n", "fig, ax = plt.subplots(1, 2, figsize=(12, 4))\n", "x = np.arange(len(res)); ax[0].bar(x - .2, res[\"๊ณ ์ • ๋น„์šฉ(๋งŒ์›/์‹œ)\"], .4, label=\"fixed share\"); ax[0].bar(x + .2, res[\"LP ๋น„์šฉ(๋งŒ์›/์‹œ)\"], .4, label=\"LP\")\n", "ax[0].set_xticks(x); ax[0].set_xticklabels([f\"S{i + 1}\" for i in x]); ax[0].legend(); ax[0].set_title(\"Cost per hour by scenario\")\n", "ax[1].plot(sdf[\"์ˆ˜์š”\"], sdf[\"์ ˆ๊ฐ๋ฅ \"] * 100, \"o-\"); ax[1].set_title(\"Sensitivity: demand vs saving (%), A2 closed\"); ax[1].set_xlabel(\"demand (veh/h)\")\n", "plt.tight_layout(); plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 07 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[1] ์‹œ๋‚˜๋ฆฌ์˜ค๋ณ„ ๋น„์šฉ ๋น„๊ต(์‹œ๊ฐ„ยท์œ ๋ฅ˜ ๋‹จ๊ฐ€๋Š” ๊ต์œก์šฉ ๊ฐ€์ •๊ฐ’)\n", " ์‹œ๋‚˜๋ฆฌ์˜ค ์ˆ˜์š” ๊ณ ์ • ๋ฐฐ๋ถ„(๋ถ„) LP(๋ถ„) ๊ณ ์ • ๋น„์šฉ(๋งŒ์›/์‹œ) LP ๋น„์šฉ(๋งŒ์›/์‹œ) ์ ˆ๊ฐ๋ฅ \n", " ํ‰์‹œ 200 41.9 41.8 1124.8 1122.7 0.2%\n", " ์ˆ˜์†ก๋Ÿ‰ ํญ์ฆ(+30%) 260 57.2 44.8 1860.5 1547.9 16.8%\n", "A2 ๊ต๋Ÿ‰ ์ •๋น„(์šฉ๋Ÿ‰ 65%) 200 53.3 42.2 1351.6 1138.9 15.7%\n", " A2 ๊ต๋Ÿ‰ ํ์‡„ 200 91.4 45.1 2122.3 1207.2 43.1%\n", " A2 ํ์‡„ + ์šฐ์ฒœ 200 119.7 47.0 2688.8 1245.4 53.7%\n", " B1 ์ •๋น„ + ํญ์ฆ 250 76.5 46.2 2270.9 1523.7 32.9%\n", " ์‹œ๋‚˜๋ฆฌ์˜ค 6์ข… ํ‰๊ท  ์ ˆ๊ฐ 27.1%, ์šฐํšŒ๊ฐ€ ํ•„์š”ํ•œ 4๊ฐœ ์‹œ๋‚˜๋ฆฌ์˜ค ํ‰๊ท  36.4% (๋ชฉํ‘œ 15% ์ด์ƒ: ๋‹ฌ์„ฑ), ํ‰์‹œ๋Š” 0.2%์— ๊ทธ์นœ๋‹ค\n", "\n", "[2] ๋ฐฑํ…Œ์ŠคํŒ…(30์ผ): ํ‰๊ท  ์ ˆ๊ฐ 16.0%(๋ชฉํ‘œ 15% ์ด์ƒ: ๋‹ฌ์„ฑ), ์ตœ๋Œ€ 48.5%(day 134), ์ ˆ๊ฐ 0 ์ดํ•˜์ธ ๋‚  0์ผ\n", " ์ ˆ๊ฐ 10% ๋ฏธ๋งŒ์ธ ๋‚  12์ผ: ํšจ๊ณผ๋Š” ์ •๋น„ยทํญ์ฆ์ด ๊ฒน์นœ ๋‚ ์— ์ง‘์ค‘๋œ๋‹ค. ๊ณผ๊ฑฐ ๊ด€ํ–‰ ๋ฐฐ๋ถ„์€ ๊ฐ€์ •์ด๋ฏ€๋กœ ์‹ค์ œ ๋ถ€๋Œ€ ์‹ค์ ์œผ๋กœ๋Š” ๋ณ„๋„ ๊ฒ€์ฆ์ด ํ•„์š”ํ•˜๋‹ค.\n", "[3] ๋Œ๋ฐœ ์ˆ˜์š” ๋ฏผ๊ฐ๋„(A2 ํ์‡„ ์ƒํ™ฉ): x0.8 -> ์ด์šฉ๋ฅ  0.85, ์ ˆ๊ฐ 4%; x1.0 -> ์ด์šฉ๋ฅ  0.95, ์ ˆ๊ฐ 43%; x1.2 -> ์ด์šฉ๋ฅ  1.32, ์ ˆ๊ฐ 58%; x1.4 -> ์ด์šฉ๋ฅ  1.54, ์ ˆ๊ฐ 58%; x1.6 -> ์ด์šฉ๋ฅ  1.76, ์ ˆ๊ฐ 56%\n", " ์ˆ˜์š” x1.2 ์ด์ƒ์—์„œ๋Š” ์ตœ๋Œ€์œ ๋Ÿ‰(220๋Œ€/์‹œ)์„ ๋„˜์–ด ์šฐํšŒ๋งŒ์œผ๋กœ ์ˆ˜์šฉํ•  ์ˆ˜ ์—†์œผ๋ฏ€๋กœ ๋ฌผ๋Ÿ‰ ์กฐ์ • ๊ฒฐ์ •์ด ํ•„์š”ํ•˜๋‹ค.\n", "\n", "[4] AI ์šฐํšŒ์•ˆ 6๊ฑด ์ „๋ถ€ '๊ฒ€ํ† ๋Œ€๊ธฐ' ๋“ฑ๋ก, ์ž๋™ ์Šน์ธ 0๊ฑด (๋ชฉํ‘œ 0๊ฑด). 2๋‹จ๊ณ„ ๊ฒฐ์žฌ ๋Œ€์ƒ ['ORD01', 'ORD03', 'ORD04', 'ORD05']\n", " ๊ถŒํ•œ ์šฐํšŒ ์‹œ๋„ 10๊ฑด ์ค‘ ์ฐจ๋‹จ 10๊ฑด (์ฐจ๋‹จ์œจ 100%, ๋ชฉํ‘œ 100%)\n", " ์ฐจ๋‹จ plan_a approve ORD03: PLN ์—ญํ• ์€ approve ๋ถˆ๊ฐ€\n", " ์ฐจ๋‹จ aud_han approve ORD03: AUD ์—ญํ• ์€ approve ๋ถˆ๊ฐ€\n", " ์ฐจ๋‹จ ghost approve ORD03: ๋ฏธ๋“ฑ๋ก ์‚ฌ์šฉ์ž\n", " ์ฐจ๋‹จ ops_kim approve ORD03: ์‚ฌ์œ  10์ž ์ด์ƒ ํ•„์ˆ˜\n", " ๊ฐ์‚ฌ ๋กœ๊ทธ 22๊ฑด ํ•ด์‹œ ์ฒด์ธ ๋ฌด๊ฒฐ์„ฑ 100%; 4๋ฒˆ ๊ธฐ๋ก ์‚ฌํ›„ ์ˆ˜์ • ์‹œ ์œ„๋ณ€์กฐ ๊ฒ€์ถœ(4๋ฒˆ)\n", " ORD01(๋ฐ˜๋ ค) -> ORD07(์žฌ๊ธฐ์•ˆ, parent ์—ฐ๊ฒฐ) ์ด๋ ฅ: [('submit', 'plan_a'), ('reject', 'ops_kim'), ('submit', 'plan_a'), ('approve', 'ops_kim'), ('approve', 'sup_park'), ('DENIED:reject', 'sup_park'), ('DENIED:edit', 'plan_a')]\n", " ๋ฌธ์„œ ์ƒํƒœ: {'์Šน์ธ': 3, '๊ฒ€ํ† ๋Œ€๊ธฐ': 3, '๋ฐ˜๋ ค': 1} | ์‹ค์ œ ์šฐํšŒ ๋ช…๋ น ํ•˜๋‹ฌ์€ ์ง€ํœ˜๊ณ„ํ†ต(์ž‘์ „๊ตฐ์ˆ˜๊ด€ ์Šน์ธ) ๊ฒฐ์‹ฌ์— ๋”ฐ๋ฅธ๋‹ค.\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 08. ์ฃผ์š” ์ถ•์„  3๊ฐœ ๋™์‹œ ๋ด‰์‡„ ์ƒํ™ฉ ๋ชจ์˜ ์šฐํšŒ ์ˆ˜์†ก ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ์ŠคํŠธ๋ ˆ์Šค ํ…Œ์ŠคํŠธ\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 3.0์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "์ฃผ์š” ์ถ•์„  ๋ด‰์‡„ ๋ชจ์˜ ์šฐํšŒ ์ˆ˜์†ก ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ์ŠคํŠธ๋ ˆ์Šค ํ…Œ์ŠคํŠธ๋ฅผ ์ˆ˜ํ–‰ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **๊ตฌ๊ฐ„ 3๊ฐœ๊ฐ€ ๋™์‹œ์— ๋ง‰ํžˆ๋Š” ์ƒํ™ฉ์„ ๋ชจ์˜ํ•˜๋Š” ์ŠคํŠธ๋ ˆ์Šค ์‹œํ—˜์œผ๋กœ ๋…ธ์„ ๋ง์˜ ์ทจ์•ฝ ์กฐํ•ฉ๊ณผ ๋Œ€์‹œ๋ณด๋“œ ๊ฐฑ์‹  ์„ฑ๋Šฅยท๊ฒฝ๋ณด ์ •ํ™•๋„๋ฅผ ํ™•์ธํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **๊ตฌ๊ฐ„ 10๊ฐœ ์ค‘ 3๊ฐœ๊ฐ€ ๋™์‹œ์— ๋ง‰ํžˆ๋Š” 120๊ฐœ ์กฐํ•ฉ๊ณผ 3๊ฐœ ์ถ•์„ ์˜ ์ ‘๊ทผ ๊ตฌ๊ฐ„ ๋™์‹œ ๋ด‰์‡„๋ฅผ ๋ชจ์˜ํ•ด ๋‹จ์ ˆ, ์ˆ˜์š” ์ดˆ๊ณผ, ์ˆ˜์šฉ ๊ฐ€๋Šฅ ์กฐํ•ฉ์„ ๋ถ„๋ฅ˜ํ•˜๊ณ  ๋‹จ์ ˆ ์กฐํ•ฉ์— ์ž์ฃผ ๋“ฑ์žฅํ•˜๋Š” ๊ตฌ๊ฐ„์„ ์ด์ค‘ํ™” ํ›„๋ณด๋กœ ์ œ์‹œ ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `src/dashboard/mov01_app.py (Streamlit/Dash ๋“ฑ)` ํ˜•ํƒœ์˜ ๊ฒฐ๊ณผ๋ฌผ์„ ์™„์„ฑํ•œ๋‹ค.\n", "\n", "### ๐Ÿ” ํ‰๊ฐ€ ๋ฐ ๊ฒ€์ฆ ๊ธฐ์ค€ (PASS ์กฐ๊ฑด)\n", "#### ๋ฏธ์…˜ ํŒ์ •\n", "\n", "**ํ‰๊ฐ€ ๊ตฌ๋ถ„:** ํ•„์ˆ˜\n", "\n", "**ํ•ต์‹ฌ ํ‰๊ฐ€์˜์—ญ:** ์˜์‚ฌ๊ฒฐ์ • ์ง€์›์— ์ตœ์ ํ™”๋œ ์‹œ๊ฐ์  ์œ„๊ณ„ ๋ฐ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜(UX)\n", "\n", "#### ๋ฏธ์…˜ PASS ์กฐ๊ฑด\n", "\n", "- ์ •๋Ÿ‰ ๊ธฐ์ค€: ๋Œ€์‹œ๋ณด๋“œ ๋กœ๋”ฉ ์‹œ๊ฐ„ 2์ดˆ ์ด๋‚ด, ๊ฒฝ๋ณด ๋ฐœ๋ น ์ •ํ™•๋„ 95% ์ด์ƒ\n", "\n", "\n", "- ์ง„ํ–‰ ๊ธฐ์ค€: ์›น ๊ธฐ๋ฐ˜ ๋ถ„์„ ๋Œ€์‹œ๋ณด๋“œ ๊ตฌ๋™ ๋ฐ ์‹ค์‹œ๊ฐ„ ํ•„ํ„ฐ๋ง ๊ธฐ๋Šฅ ๊ฒ€์ฆ\n", "\n", "\n", "- ๊ฒ€์ฆ ๋ฐฉ๋ฒ•: ์‹ค์ œ ๊ตฐ์ˆ˜ ๋ฐ์ดํ„ฐ ์—ฐ๋™ ํ™”๋ฉด ํ…Œ์ŠคํŠธ ๋ฐ UI ๋™์ž‘ ๊ฒ€์ฆ\n", "\n", "\n", "#### ์ฑ„์ ์ž ํŒ์ • ๋ฉ”๋ชจ\n", "\n", "> ๋‹จ์ˆœ ์ฝ”๋“œ ์‹คํ–‰ ์—ฌ๋ถ€๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ถœ๋ ฅ๋œ ์ˆ˜์น˜์™€ ๊ทผ๊ฑฐ ์ž๋ฃŒ๊ฐ€ ๊ตฐ ์‹ค๋ฌด ์š”๊ตฌ์‚ฌํ•ญ์— ๋ถ€ํ•ฉํ•˜๋Š”์ง€ ๋ฉด๋ฐ€ํžˆ ํ™•์ธํ•œ๋‹ค.\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\"\"\"๊ฐ€์ƒ ๋…ธ์„ ๋ง์—์„œ ๊ตฌ๊ฐ„ 3๊ฐœ๊ฐ€ ๋™์‹œ์— ๋ง‰ํžˆ๋Š” ๋ชจ๋“  ์กฐํ•ฉ(120๊ฐ€์ง€)๊ณผ ์ถ•์„  3๊ฐœ ์ „๋ฉด ๋ด‰์‡„๋ฅผ ๋ชจ์˜ํ•ด ๋‹จ์ ˆยท๊ณผ๋ถ€ํ•˜ยท์ถ”๊ฐ€ ์ง€์ฒด๋ฅผ ํ‰๊ฐ€ํ•˜๊ณ , ๋Œ€์‹œ๋ณด๋“œ ๊ฐฑ์‹  ์‹œ๊ฐ„(2์ดˆ ์ด๋‚ด)๊ณผ ์ง€์ฒด ๊ฒฝ๋ณด ์ •ํ™•๋„(95% ์ด์ƒ)๋ฅผ ์ŠคํŠธ๋ ˆ์Šค ์กฐ๊ฑด์—์„œ ์ธก์ •ํ•œ๋‹ค.\"\"\"\n", "import itertools\n", "import time\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from scipy.optimize import linprog\n", "from sklearn.ensemble import GradientBoostingRegressor\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ƒ ๋…ธ์„ ๋งยท๊ตฌ๊ฐ„ ํ†ต๊ณผ๋Ÿ‰ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ๋„๋กœยท์ง€๋ช…ยท๋ถ€๋Œ€ ์—†์Œ)\n", "# ๊ฐ€์ƒ ์ถœ๋ฐœ์ง€(SRC)์—์„œ ๊ฐ€์ƒ ๋ชฉ์ ์ง€(DST)๊นŒ์ง€ 3๊ฐœ ์ถ•์„ (AยทBยทC)๊ณผ ์—ฐ๊ฒฐ๋กœ 2๊ฐœ, ๊ตฌ๊ฐ„ 10๊ฐœ. 150์ผ์น˜ ํ”ผํฌ์‹œ๊ฐ„ ํ†ต๊ณผ๋Ÿ‰ยท์ง€์ฒด ์‹œ๊ฐ„์„ ๋งŒ๋“ ๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "SEGS = pd.DataFrame([\n", " (\"A1\", \"SRC\", \"J1\", 12, 130, \"์ผ๋ฐ˜\"), (\"A2\", \"J1\", \"J2\", 8, 70, \"๊ต๋Ÿ‰\"), (\"A3\", \"J2\", \"DST\", 10, 140, \"๊ต์ฐจ๋กœ\"),\n", " (\"B1\", \"SRC\", \"J3\", 15, 100, \"์ผ๋ฐ˜\"), (\"B2\", \"J3\", \"J2\", 9, 80, \"ํ„ฐ๋„\"), (\"B3\", \"J3\", \"DST\", 14, 95, \"์ผ๋ฐ˜\"),\n", " (\"C1\", \"SRC\", \"J4\", 18, 70, \"์ผ๋ฐ˜\"), (\"C2\", \"J4\", \"DST\", 20, 75, \"๊ต๋Ÿ‰\"),\n", " (\"X1\", \"J1\", \"J3\", 7, 50, \"๊ต์ฐจ๋กœ\"), (\"X2\", \"J4\", \"J3\", 10, 60, \"์ผ๋ฐ˜\")],\n", " columns=[\"seg\", \"u\", \"v\", \"km\", \"cap\", \"kind\"]) # cap = ํ”ผํฌ์‹œ๊ฐ„ ํ†ต๊ณผ ์šฉ๋Ÿ‰(๋Œ€/์‹œ, ๊ต์œก์šฉ ๊ฐ€์ •๊ฐ’)\n", "KSPD = {\"์ผ๋ฐ˜\": 55, \"๊ต๋Ÿ‰\": 40, \"ํ„ฐ๋„\": 45, \"๊ต์ฐจ๋กœ\": 45} # ๊ตฌ๊ฐ„ ์œ ํ˜•๋ณ„ ์ž์œ  ์ฃผํ–‰ ์†๋„(km/h, ๊ฐ€์ •๊ฐ’)\n", "SEGS[\"t0\"] = SEGS[\"km\"] / SEGS[\"kind\"].map(KSPD) * 60 # ์ž์œ  ์ฃผํ–‰ ์‹œ๊ฐ„(๋ถ„)\n", "UTIL = dict(A1=.78, A2=.85, A3=.66, B1=.70, B2=.72, B3=.64, C1=.55, C2=.58, X1=.45, X2=.40) # ํ‰์‹œ ์ด์šฉ๋ฅ \n", "\n", "\n", "def bpr(t0, v, cap, a=0.15, b=4.0):\n", " \"\"\"BPR ์ง€์ฒด ํ•จ์ˆ˜(๊ตํ†ต๊ณตํ•™์˜ ํ‘œ์ค€ํ˜•): ํ†ต๊ณผ๋Ÿ‰/์šฉ๋Ÿ‰ ๋น„๊ฐ€ ์ปค์งˆ์ˆ˜๋ก ์ง€์ฒด๊ฐ€ ๊ธ‰์ฆํ•œ๋‹ค. (๋ถ„)\"\"\"\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 8] ์ฃผ์š” ์ถ•์„  3๊ฐœ ๋™์‹œ ๋ด‰์‡„ ์ƒํ™ฉ ๋ชจ์˜ ์šฐํšŒ ์ˆ˜์†ก ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ์ŠคํŠธ๋ ˆ์Šค ํ…Œ์ŠคํŠธ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def make_data(seed=11, days=150):\n", " \"\"\"๊ตฌ๊ฐ„ x ์ผ์ž ํŒจ๋„. v=์‹ค์ œ ํ”ผํฌ ํ†ต๊ณผ๋Ÿ‰, plan_v=์ˆ˜์†ก๊ณ„ํš ์ˆ˜๋Ÿ‰(๊ธด๊ธ‰ ๋ถˆ์ถœ ๋ฏธ๋ฐ˜์˜), delay=์‹ค์ œ ์ง€์ฒด(๋ถ„).\"\"\"\n", " rng = np.random.default_rng(seed)\n", " d_idx = np.arange(days)\n", " lvl = np.array([1.0, 1.08, 1.04, 1.10, 1.02, 0.82, 0.78])[d_idx % 7] * (1 + 0.0012 * d_idx)\n", " lvl = lvl * np.where((d_idx >= 100) & (d_idx < 110), 1.30, 1.0) # ์ˆ˜์†ก๋Ÿ‰ ํญ์ฆ ๊ธฐ๊ฐ„(๊ฐ€์ƒ)\n", " prev, rows = {s: 0.0 for s in SEGS[\"seg\"]}, []\n", " for d in d_idx:\n", " rain = bool(rng.random() < 0.18)\n", " for r in SEGS.itertuples():\n", " maint, urgent = bool(rng.random() < 0.06), bool(rng.random() < 0.05) # ๋น„๊ณ„ํš ์ •๋น„ / ๊ธด๊ธ‰ ๋ถˆ์ถœ\n", " v = r.cap * UTIL[r.seg] * lvl[d] * (1.25 if urgent else 1.0) * rng.normal(1, 0.05)\n", " cap_eff = r.cap * (0.65 if maint else 1.0) * (0.92 if rain else 1.0)\n", " delay = max(r.t0 * 0.2 * (v / cap_eff) ** 3.5 + 0.25 * prev[r.seg] + rng.normal(0, 0.4), 0.0)\n", " prev[r.seg] = delay\n", " rows.append(dict(day=int(d), dow=int(d % 7), seg=r.seg, kind=r.kind, km=r.km, cap=r.cap, t0=r.t0,\n", " plan_v=v / (1.25 if urgent else 1.0) * rng.normal(1, 0.04), v=v, maint=int(maint), urgent=int(urgent),\n", " rain=int(rain), cap_eff=cap_eff, delay=delay))\n", " df = pd.DataFrame(rows)\n", " df[\"transit\"] = df[\"t0\"] * (1 + 0.08 * df[\"rain\"]) + df[\"delay\"]\n", " return df\n", "\n", "\n", "def mape(y, p):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 8] ์ฃผ์š” ์ถ•์„  3๊ฐœ ๋™์‹œ ๋ด‰์‡„ ์ƒํ™ฉ ๋ชจ์˜ ์šฐํšŒ ์ˆ˜์†ก ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ์ŠคํŠธ๋ ˆ์Šค ํ…Œ์ŠคํŠธ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def mae(y, p):\n", " return float(np.mean(np.abs(np.asarray(y, float) - np.asarray(p, float))))\n", "# ---------------------------------------------------------------------------\n", "# ---------------------------------------------------------------------------\n", "# ์ง€์ฒด ์˜ˆ์ธก์šฉ ํŒŒ์ƒ ํ”ผ์ฒ˜ ์ƒ์„ฑ(๋‹น์ผ ์˜ค์ „ ์‹œ์ ์— ์•Œ ์ˆ˜ ์žˆ๋Š” ์ •๋ณด๋งŒ ์‚ฌ์šฉ: ๊ณ„ํš ๋ฌผ๋Ÿ‰, ๊ณต์ง€ ์ •๋น„, ๊ธฐ์ƒ ์˜ˆ๋ณด, ์ „์ผยท์ „์ฃผ ์‹ค์ )\n", "# ---------------------------------------------------------------------------\n", "FEATS = [\"plan_v\", \"cap\", \"cap_eff\", \"t0\", \"km\", \"maint\", \"rain\", \"util\", \"util_eff\", \"util_eff4\", \"bpr_base\", \"lag1_delay\", \"lag7_delay\",\n", " \"roll7_v\", \"surge_ratio\", \"is_weekend\", \"dow_sin\", \"dow_cos\", \"k_๊ต๋Ÿ‰\", \"k_ํ„ฐ๋„\", \"k_๊ต์ฐจ๋กœ\"]\n", "\n", "\n", "def add_features(df):\n", " d = df.sort_values([\"seg\", \"day\"]).copy()\n", " g = d.groupby(\"seg\")\n", " d[\"lag1_delay\"] = g[\"delay\"].shift(1)\n", " d[\"lag7_delay\"] = g[\"delay\"].shift(7)\n", " d[\"roll7_v\"] = g[\"v\"].transform(lambda s: s.shift(1).rolling(7).mean())\n", " d[\"util\"] = d[\"plan_v\"] / d[\"cap\"]\n", " d[\"util_eff\"] = d[\"plan_v\"] / d[\"cap_eff\"]\n", " d[\"util_eff4\"] = d[\"util_eff\"] ** 4\n", " d[\"bpr_base\"] = bpr(d[\"t0\"], d[\"plan_v\"], d[\"cap\"])\n", " d[\"surge_ratio\"] = d[\"plan_v\"] / d[\"roll7_v\"]\n", " d[\"is_weekend\"] = (d[\"dow\"] >= 5).astype(int)\n", " d[\"dow_sin\"], d[\"dow_cos\"] = np.sin(2 * np.pi * d[\"dow\"] / 7), np.cos(2 * np.pi * d[\"dow\"] / 7)\n", " for k in (\"๊ต๋Ÿ‰\", \"ํ„ฐ๋„\", \"๊ต์ฐจ๋กœ\"):\n", " d[\"k_\" + k] = (d[\"kind\"] == k).astype(int)\n", " return d.dropna().sort_values([\"day\", \"seg\"]).reset_index(drop=True)\n", "# ---------------------------------------------------------------------------\n", "# ---------------------------------------------------------------------------\n", "# ๋…ธ์„ ๋ง ํ๋ฆ„ ๋„๊ตฌ: ์ตœ๋Œ€์œ ๋Ÿ‰(Edmonds-Karp), ์ฆ๋ถ„ ๋ฐฐ์ •(ํ˜ผ์žก ๋ฐ˜์˜ ์ตœ๋‹จ์‹œ๊ฐ„ ๊ฒฝ๋กœ), ๊ฒฝ๋กœ ์—ด๊ฑฐ\n", "# ---------------------------------------------------------------------------\n", "CAP = dict(zip(SEGS[\"seg\"], SEGS[\"cap\"]))\n", "T0 = dict(zip(SEGS[\"seg\"], SEGS[\"t0\"]))\n", "KM = dict(zip(SEGS[\"seg\"], SEGS[\"km\"]))\n", "ARCS = {r.seg: (r.u, r.v) for r in SEGS.itertuples()}\n", "SRC, DST = \"SRC\", \"DST\"\n", "\n", "\n", "def seg_time(s, flow, cap=None, a=0.2, b=3.5):\n", " \"\"\"๊ตฌ๊ฐ„ ํ†ต๊ณผ์‹œ๊ฐ„(๋ถ„) = ์ž์œ ์ฃผํ–‰ + ํ˜ผ์žก ์ง€์ฒด. cap์„ ์ฃผ๋ฉด ์šฉ๋Ÿ‰์„ ๋Œ€์ฒด(์ •๋น„ยท๊ธฐ์ƒ ๋ฐ˜์˜).\"\"\"\n", " r = flow / (cap or CAP[s])\n", " return T0[s] * (1 + a * r ** b + 6.0 * max(0.0, r - 1.0)) # ์šฉ๋Ÿ‰ ์ดˆ๊ณผ๋ถ„์€ ๋Œ€๊ธฐ์—ด๋กœ ๊ธ‰์ฆ\n", "\n", "\n", "def max_flow(closed=(), capmul=None):\n", " \"\"\"์ตœ๋Œ€์œ ๋Ÿ‰(๋Œ€/์‹œ)๊ณผ ์ตœ์†Œ ์ ˆ๋‹จ ๊ตฌ๊ฐ„(๋ณ‘๋ชฉ). capmul = {๊ตฌ๊ฐ„: ์šฉ๋Ÿ‰ ๋ฐฐ์œจ}.\"\"\"\n", " cap = {s: (0 if s in closed else CAP[s] * (capmul or {}).get(s, 1.0)) for s in CAP}\n", " res = {}\n", " for s, (u, v) in ARCS.items():\n", " res[(u, v)] = res.get((u, v), 0) + cap[s]\n", " res.setdefault((v, u), 0)\n", " flow = 0.0\n", " while True:\n", " prev, q = {SRC: None}, [SRC]\n", " for u in q:\n", " for (a, b), c in res.items():\n", " if a == u and c > 1e-9 and b not in prev:\n", " prev[b] = u; q.append(b)\n", " if DST not in prev:\n", " break\n", " path, x = [], DST\n", " while prev[x]:\n", " path.append((prev[x], x)); x = prev[x]\n", " f = min(res[e] for e in path)\n", " for a, b in path:\n", " res[(a, b)] -= f; res[(b, a)] += f\n", " flow += f\n", " reach = set(prev)\n", " cut = [s for s, (u, v) in ARCS.items() if u in reach and v not in reach and cap[s] > 0]\n", " return flow, cut\n", "\n", "\n", "def all_paths():\n", " \"\"\"SRC -> DST ๋‹จ์ˆœ ๊ฒฝ๋กœ(๊ตฌ๊ฐ„ ์ด๋ฆ„ ํŠœํ”Œ) ์ „์ฒด.\"\"\"\n", " out = []\n", "\n", " def dfs(node, segs, seen):\n", " if node == DST:\n", " out.append(tuple(segs)); return\n", " for s, (u, v) in ARCS.items():\n", " if u == node and v not in seen:\n", " dfs(v, segs + [s], seen | {v})\n", " dfs(SRC, [], {SRC})\n", " return out\n", "\n", "\n", "PATHS = all_paths()\n", "\n", "\n", "def assign(demand, closed=(), capmul=None, steps=40):\n", " \"\"\"์ฆ๋ถ„ ๋ฐฐ์ •: ์ˆ˜์š”๋ฅผ steps์กฐ๊ฐ์œผ๋กœ ๋‚˜๋ˆ , ์ „์ฒด ํ†ต๊ณผ์‹œ๊ฐ„(๋Œ€์ˆ˜ x ์‹œ๊ฐ„) ์ฆ๊ฐ€๊ฐ€ ๊ฐ€์žฅ ์ž‘์€ ๊ฒฝ๋กœ์— ์ฐจ๋ก€๋กœ ์–น๋Š”๋‹ค(์‹œ์Šคํ…œ ์ตœ์  ๊ทผ์‚ฌ).\"\"\"\n", " cap = {s: CAP[s] * (capmul or {}).get(s, 1.0) for s in CAP}\n", " flow, pf = {s: 0.0 for s in CAP}, {}\n", " ok = [p for p in PATHS if not any(s in closed for s in p)]\n", " if not ok:\n", " return None\n", " for _ in range(steps):\n", " chunk = demand / steps\n", " best = min(ok, key=lambda p: sum((flow[s] + chunk) * seg_time(s, flow[s] + chunk, cap[s]) - flow[s] * seg_time(s, flow[s], cap[s]) for s in p))\n", " for s in best:\n", " flow[s] += chunk\n", " pf[best] = pf.get(best, 0) + chunk\n", " tt = {p: sum(seg_time(s, flow[s], cap[s]) for s in p) for p in pf}\n", " avg = sum(pf[p] * tt[p] for p in pf) / demand\n", " return {\"flow\": flow, \"paths\": pf, \"path_min\": tt, \"avg_min\": avg, \"util\": {s: flow[s] / cap[s] for s in cap if cap[s] > 0}}\n", "\n", "\n", "FIXED_SHARE = {(\"A1\", \"A2\", \"A3\"): 0.32, (\"B1\", \"B3\"): 0.40, (\"C1\", \"C2\"): 0.28} # ๊ณผ๊ฑฐ ๊ด€ํ–‰: ์ถ•์„ ๋ณ„ ๊ณ ์ • ๋ฐฐ๋ถ„(๊ฐ€์ƒ)\n", "\n", "\n", "def fixed_assign(demand, closed=(), capmul=None):\n", " \"\"\"๊ณผ๊ฑฐ ๊ด€ํ–‰ ๋ฐฐ๋ถ„: ์ถ•์„ ๋ณ„ ๊ณ ์ • ๋น„์œจ. ๋ง‰ํžŒ ์ถ•์„  ๋ฌผ๋Ÿ‰์€ ์—ด๋ ค ์žˆ๋Š” ์ถ•์„ ์— ๋น„์œจ๋Œ€๋กœ ์žฌ๋ถ„๋ฐฐ.\"\"\"\n", " cap = {s: CAP[s] * (capmul or {}).get(s, 1.0) for s in CAP}\n", " live = {p: w for p, w in FIXED_SHARE.items() if not any(s in closed for s in p)}\n", " if not live:\n", " return None\n", " tot = sum(live.values())\n", " flow = {s: 0.0 for s in CAP}\n", " pf = {p: demand * w / tot for p, w in live.items()}\n", " for p, f in pf.items():\n", " for s in p:\n", " flow[s] += f\n", " tt = {p: sum(seg_time(s, flow[s], cap[s]) for s in p) for p in pf}\n", " return {\"flow\": flow, \"paths\": pf, \"path_min\": tt, \"avg_min\": sum(pf[p] * tt[p] for p in pf) / demand,\n", " \"util\": {s: flow[s] / cap[s] for s in cap if cap[s] > 0}}\n", "\n", "\n", "def lp_assign(demand, closed=(), capmul=None):\n", " \"\"\"์„ ํ˜•๊ณ„ํš(LP): ๊ตฌ๊ฐ„ ์šฉ๋Ÿ‰ ์ƒํ•œ(์ด์šฉ๋ฅ  rho) ์•„๋ž˜์—์„œ ์ž์œ ์ฃผํ–‰์‹œ๊ฐ„ ํ•ฉ์„ ์ตœ์†Œํ™”ํ•˜๋Š” ๊ฒฝ๋กœ๋ณ„ ๋ฌผ๋Ÿ‰. ๋ถˆ๊ฐ€๋Šฅํ•˜๋ฉด rho๋ฅผ 1.0๊นŒ์ง€ ์™„ํ™”.\"\"\"\n", " cap = {s: CAP[s] * (capmul or {}).get(s, 1.0) for s in CAP}\n", " ok = [p for p in PATHS if not any(s in closed for s in p)]\n", " if not ok:\n", " return None\n", " for rho in (0.85, 0.95, 1.0):\n", " r = linprog([sum(T0[s] for s in p) for p in ok], A_ub=[[1.0 if s in p else 0.0 for p in ok] for s in CAP],\n", " b_ub=[rho * cap[s] for s in CAP], A_eq=[[1.0] * len(ok)], b_eq=[demand], bounds=(0, None), method=\"highs\")\n", " if r.status == 0:\n", " break\n", " else:\n", " return assign(demand, closed, capmul) # ์šฉ๋Ÿ‰ ์ดˆ๊ณผ ์ˆ˜์š”: ์ฆ๋ถ„ ๋ฐฐ์ •์œผ๋กœ ๋Œ€์ฒด(๊ณผ๋ถ€ํ•˜ ํ‘œ์‹œ)\n", " flow = {s: sum(r.x[i] for i, p in enumerate(ok) if s in p) for s in CAP}\n", " pf = {p: float(r.x[i]) for i, p in enumerate(ok) if r.x[i] > 1e-6}\n", " tt = {p: sum(seg_time(s, flow[s], cap[s]) for s in p) for p in pf}\n", " return {\"flow\": flow, \"paths\": pf, \"path_min\": tt, \"avg_min\": sum(pf[p] * tt[p] for p in pf) / demand,\n", " \"util\": {s: flow[s] / cap[s] for s in cap if cap[s] > 0}, \"rho\": rho}\n", "\n", "\n", "def cost_of(res, tv=1000, fuel_km=450):\n", " \"\"\"์‹œ๊ฐ„๋‹น ๋น„์šฉ(์›, ๊ต์œก์šฉ ๊ฐ€์ •): ๋Œ€๋‹น ์‹œ๊ฐ„๋น„์šฉ tv์›/๋ถ„ + ์œ ๋ฅ˜๋น„ fuel_km์›/km.\"\"\"\n", " return sum(f * (res[\"path_min\"][p] * tv + sum(KM[s] for s in p) * fuel_km) for p, f in res[\"paths\"].items())\n", "# ---------------------------------------------------------------------------\n", "\n", "D = 150\n", "base = assign(D)\n", "print(f\"[0] ํ‰์‹œ: ์ˆ˜์š” {D}๋Œ€/์‹œ, ํ‰๊ท  ํ†ต๊ณผ {base['avg_min']:.1f}๋ถ„, ์ตœ๋Œ€์œ ๋Ÿ‰ {max_flow()[0]:.0f}๋Œ€/์‹œ\")\n", "\n", "# ---------------- 1) ์ถ•์„  3๊ฐœ(AยทBยทC) ์ „๋ฉด ๋ด‰์‡„ ----------------\n", "axes_cut = (\"A3\", \"B3\", \"C2\") # 3๊ฐœ ์ถ•์„ ์˜ ๋ชฉ์ ์ง€ ์ ‘๊ทผ ๊ตฌ๊ฐ„\n", "mf, _ = max_flow(axes_cut)\n", "print(f\"[1] 3๊ฐœ ์ถ•์„  ์ ‘๊ทผ ๊ตฌ๊ฐ„(A3ยทB3ยทC2) ๋™์‹œ ๋ด‰์‡„: ์ตœ๋Œ€์œ ๋Ÿ‰ {mf:.0f}๋Œ€/์‹œ, ๊ฒฝ๋กœ {len(PATHS) - len([p for p in PATHS if not any(s in axes_cut for s in p)])}/{len(PATHS)}๊ฐœ ์ฐจ๋‹จ -> \"\n", " + (\"๋„๋กœ ์ˆ˜์†ก ๋ถˆ๊ฐ€. ๊ณต์ค‘ยทํ•ด์ƒ ๋“ฑ ํƒ€ ์ˆ˜๋‹จ ๋˜๋Š” ๋ฌผ๋Ÿ‰ ๋ณด๋ฅ˜ ๊ฒฐ์ •์ด ํ•„์š”ํ•˜๋ฉฐ ์‚ฌ๋žŒ์ด ํŒ๋‹จํ•œ๋‹ค\" if assign(D, axes_cut) is None else \"์šฐํšŒ ๊ฐ€๋Šฅ\"))\n", "\n", "# ---------------- 2) ๊ตฌ๊ฐ„ 3๊ฐœ ๋™์‹œ ๋ด‰์‡„ 120์กฐํ•ฉ ----------------\n", "rows = []\n", "t0 = time.perf_counter()\n", "for c in itertools.combinations(CAP, 3):\n", " flow, cut = max_flow(c)\n", " r = assign(D, c) if flow > 0 else None\n", " rows.append({\"closed\": \"+\".join(c), \"maxflow\": flow, \"avg_min\": r[\"avg_min\"] if r else np.nan, \"max_util\": max(r[\"util\"].values()) if r else np.nan})\n", "el = time.perf_counter() - t0\n", "st = pd.DataFrame(rows)\n", "st[\"์ƒํƒœ\"] = np.where(st[\"maxflow\"] <= 0, \"๋‹จ์ ˆ\", np.where(st[\"maxflow\"] < D, \"์ˆ˜์š” ์ดˆ๊ณผ\", np.where(st[\"avg_min\"] > base[\"avg_min\"] * 1.5, \"์‹ฌ๊ฐ ์ง€์ฒด\", \"์ˆ˜์šฉ\")))\n", "vc = st[\"์ƒํƒœ\"].value_counts()\n", "print(f\"\\n[2] 120๊ฐœ ์กฐํ•ฉ ํ‰๊ฐ€({el:.2f}์ดˆ): \" + \", \".join(f\"{k} {v}\" for k, v in vc.items()))\n", "ok_rows = st[st[\"์ƒํƒœ\"].isin([\"์ˆ˜์šฉ\", \"์‹ฌ๊ฐ ์ง€์ฒด\"])]\n", "print(f\" ์ˆ˜์šฉ ๊ฐ€๋Šฅ ์กฐํ•ฉ์˜ ํ‰๊ท  ํ†ต๊ณผ์‹œ๊ฐ„ {ok_rows['avg_min'].mean():.1f}๋ถ„(ํ‰์‹œ {base['avg_min']:.1f}๋ถ„), ์ตœ์•… ์ˆ˜์šฉ ์กฐํ•ฉ {ok_rows.loc[ok_rows['avg_min'].idxmax(), 'closed']} {ok_rows['avg_min'].max():.1f}๋ถ„\")\n", "cnt = pd.Series([s for c in st[st[\"์ƒํƒœ\"] == \"๋‹จ์ ˆ\"][\"closed\"] for s in c.split(\"+\")]).value_counts()\n", "print(f\" ๋‹จ์ ˆ ์กฐํ•ฉ์— ์ž์ฃผ ํฌํ•จ๋œ ๊ตฌ๊ฐ„: {', '.join(f'{k}({v})' for k, v in cnt.head(4).items())} -> ์ด์ค‘ํ™”(์šฐํšŒ๋กœ ํ™•๋ณด) ์šฐ์„  ํ›„๋ณด\")\n", "\n", "# ---------------- 3) ๋Œ€์‹œ๋ณด๋“œ ๊ฐฑ์‹  ๋ถ€ํ•˜: ๊ตฌ๊ฐ„ ์˜ˆ์ธก + ์ตœ๋Œ€์œ ๋Ÿ‰ + ๋ฐฐ์ •์„ ๋ฐ˜๋ณต ----------------\n", "df = make_data(); d = add_features(df)\n", "tr, te = d[d.day < 120], d[d.day >= 120].copy()\n", "gbr = GradientBoostingRegressor(n_estimators=200, max_depth=3, learning_rate=0.08, random_state=0).fit(tr[FEATS], tr[\"delay\"])\n", "\n", "\n", "def refresh(day, closed=()):\n", " x = te[te.day == day]\n", " pred = gbr.predict(x[FEATS])\n", " flow, cut = max_flow(closed)\n", " r = assign(float(x[x.seg.isin([\"A1\", \"B1\", \"C1\"])][\"plan_v\"].sum()), closed)\n", " return pred, flow, cut, r\n", "\n", "\n", "lat = []\n", "rng = np.random.default_rng(8)\n", "combos = list(itertools.combinations(CAP, 2))\n", "for k in range(60):\n", " c = combos[int(rng.integers(len(combos)))]\n", " t1 = time.perf_counter(); refresh(120 + k % 30, c); lat.append(time.perf_counter() - t1)\n", "lat = np.array(lat)\n", "print(f\"\\n[3] ๋Œ€์‹œ๋ณด๋“œ ๋ฐ์ดํ„ฐ ๊ฐฑ์‹  60ํšŒ(๊ตฌ๊ฐ„ 2๊ฐœ ํ์‡„ ๋ฌด์ž‘์œ„): ํ‰๊ท  {lat.mean() * 1000:.0f}ms, ์ตœ๋Œ€ {lat.max() * 1000:.0f}ms (๋ชฉํ‘œ 2์ดˆ ์ด๋‚ด: {'๋‹ฌ์„ฑ' if lat.max() < 2 else '๋ฏธ๋‹ฌ'}; ํ™”๋ฉด ๋ Œ๋”๋ง ์‹œ๊ฐ„์€ ์ œ์™ธ)\")\n", "\n", "# ---------------- 4) ๊ฒฝ๋ณด ์ •ํ™•๋„: ์˜ˆ์ธก ์ง€์ฒด 5๋ถ„ ์ด์ƒ ๊ฒฝ๋ณด vs ์‹ค์ œ 5๋ถ„ ์ด์ƒ ----------------\n", "te[\"pred\"] = gbr.predict(te[FEATS])\n", "a_pred, a_true = te[\"pred\"] >= 5, te[\"delay\"] >= 5\n", "acc = float((a_pred == a_true).mean())\n", "tp, fp, fn = int((a_pred & a_true).sum()), int((a_pred & ~a_true).sum()), int((~a_pred & a_true).sum())\n", "print(f\"[4] ๊ฒฝ๋ณด ์ •ํ™•๋„: {acc:.1%} (๋ชฉํ‘œ 95% ์ด์ƒ: {'๋‹ฌ์„ฑ' if acc >= .95 else '๋ฏธ๋‹ฌ'}), ์‹ค์ œ ์ง€์ฒด 5๋ถ„ ์ด์ƒ {int(a_true.sum())}๊ฑด ์ค‘ ํƒ์ง€ {tp}๊ฑด(์žฌํ˜„์œจ {tp / max(tp + fn, 1):.0%}), ์˜ค๊ฒฝ๋ณด {fp}๊ฑด\")\n", "print(f\" ๊ฒฝ๋ณด ๊ธฐ์ค€์„ 3๋ถ„์œผ๋กœ ๋‚ฎ์ถ”๋ฉด: \", end=\"\")\n", "a3 = te[\"pred\"] >= 3; t3 = te[\"delay\"] >= 5\n", "print(f\"์žฌํ˜„์œจ {float((a3 & t3).sum() / max(t3.sum(), 1)):.0%}, ์˜ค๊ฒฝ๋ณด {int((a3 & ~t3).sum())}๊ฑด -> ๋†“์น˜์ง€ ์•Š๋Š” ์ชฝ์ด ์ค‘์š”ํ•˜๋ฉด ๊ธฐ์ค€์„ ๋‚ฎ์ถ”๋˜ ์˜ค๊ฒฝ๋ณด ํ”ผ๋กœ๋ฅผ ํ•จ๊ป˜ ๋ณธ๋‹ค\")\n", "print(\"๋ชจ๋“  ๋ชจ์˜๋Š” ๊ต์œก์šฉ ๊ฐ€์ƒ ๋…ธ์„ ์ด๋ฉฐ, ์‹ค์ œ ๋ด‰์‡„ยท์šฐํšŒ ๊ฒฐ์‹ฌ์€ ์ง€ํœ˜๊ณ„ํ†ต(์ž‘์ „๊ตฐ์ˆ˜๊ด€ ์Šน์ธ)์— ๋”ฐ๋ฅธ๋‹ค.\")\n", "\n", "fig, ax = plt.subplots(1, 3, figsize=(15, 4))\n", "vc.rename(index={\"๋‹จ์ ˆ\": \"cut off\", \"์ˆ˜์š” ์ดˆ๊ณผ\": \"over demand\", \"์ˆ˜์šฉ\": \"ok\", \"์‹ฌ๊ฐ ์ง€์ฒด\": \"severe delay\"}).plot.bar(ax=ax[0], color=\"tab:blue\")\n", "ax[0].set_title(\"120 triple-closure outcomes\"); ax[0].set_xlabel(\"\"); ax[0].tick_params(axis=\"x\", rotation=15)\n", "ax[1].hist(ok_rows[\"avg_min\"], bins=15); ax[1].axvline(base[\"avg_min\"], c=\"r\"); ax[1].set_title(\"Avg transit (min) of feasible combos\")\n", "ax[2].hist(lat * 1000, bins=15); ax[2].set_title(\"Refresh latency (ms)\")\n", "plt.tight_layout(); plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 08 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[0] ํ‰์‹œ: ์ˆ˜์š” 150๋Œ€/์‹œ, ํ‰๊ท  ํ†ต๊ณผ 37.8๋ถ„, ์ตœ๋Œ€์œ ๋Ÿ‰ 290๋Œ€/์‹œ\n", "[1] 3๊ฐœ ์ถ•์„  ์ ‘๊ทผ ๊ตฌ๊ฐ„(A3ยทB3ยทC2) ๋™์‹œ ๋ด‰์‡„: ์ตœ๋Œ€์œ ๋Ÿ‰ 0๋Œ€/์‹œ, ๊ฒฝ๋กœ 8/8๊ฐœ ์ฐจ๋‹จ -> ๋„๋กœ ์ˆ˜์†ก ๋ถˆ๊ฐ€. ๊ณต์ค‘ยทํ•ด์ƒ ๋“ฑ ํƒ€ ์ˆ˜๋‹จ ๋˜๋Š” ๋ฌผ๋Ÿ‰ ๋ณด๋ฅ˜ ๊ฒฐ์ •์ด ํ•„์š”ํ•˜๋ฉฐ ์‚ฌ๋žŒ์ด ํŒ๋‹จํ•œ๋‹ค\n", "\n", "[2] 120๊ฐœ ์กฐํ•ฉ ํ‰๊ฐ€(0.12์ดˆ): ์ˆ˜์š” ์ดˆ๊ณผ 73, ์ˆ˜์šฉ 44, ๋‹จ์ ˆ 3\n", " ์ˆ˜์šฉ ๊ฐ€๋Šฅ ์กฐํ•ฉ์˜ ํ‰๊ท  ํ†ต๊ณผ์‹œ๊ฐ„ 43.3๋ถ„(ํ‰์‹œ 37.8๋ถ„), ์ตœ์•… ์ˆ˜์šฉ ์กฐํ•ฉ A1+A2+B3 52.5๋ถ„\n", " ๋‹จ์ ˆ ์กฐํ•ฉ์— ์ž์ฃผ ํฌํ•จ๋œ ๊ตฌ๊ฐ„: C1(2), A3(2), B3(2), A1(1) -> ์ด์ค‘ํ™”(์šฐํšŒ๋กœ ํ™•๋ณด) ์šฐ์„  ํ›„๋ณด\n", "\n", "[3] ๋Œ€์‹œ๋ณด๋“œ ๋ฐ์ดํ„ฐ ๊ฐฑ์‹  60ํšŒ(๊ตฌ๊ฐ„ 2๊ฐœ ํ์‡„ ๋ฌด์ž‘์œ„): ํ‰๊ท  7ms, ์ตœ๋Œ€ 10ms (๋ชฉํ‘œ 2์ดˆ ์ด๋‚ด: ๋‹ฌ์„ฑ; ํ™”๋ฉด ๋ Œ๋”๋ง ์‹œ๊ฐ„์€ ์ œ์™ธ)\n", "[4] ๊ฒฝ๋ณด ์ •ํ™•๋„: 97.0% (๋ชฉํ‘œ 95% ์ด์ƒ: ๋‹ฌ์„ฑ), ์‹ค์ œ ์ง€์ฒด 5๋ถ„ ์ด์ƒ 22๊ฑด ์ค‘ ํƒ์ง€ 15๊ฑด(์žฌํ˜„์œจ 68%), ์˜ค๊ฒฝ๋ณด 2๊ฑด\n", " ๊ฒฝ๋ณด ๊ธฐ์ค€์„ 3๋ถ„์œผ๋กœ ๋‚ฎ์ถ”๋ฉด: ์žฌํ˜„์œจ 91%, ์˜ค๊ฒฝ๋ณด 46๊ฑด -> ๋†“์น˜์ง€ ์•Š๋Š” ์ชฝ์ด ์ค‘์š”ํ•˜๋ฉด ๊ธฐ์ค€์„ ๋‚ฎ์ถ”๋˜ ์˜ค๊ฒฝ๋ณด ํ”ผ๋กœ๋ฅผ ํ•จ๊ป˜ ๋ณธ๋‹ค\n", "๋ชจ๋“  ๋ชจ์˜๋Š” ๊ต์œก์šฉ ๊ฐ€์ƒ ๋…ธ์„ ์ด๋ฉฐ, ์‹ค์ œ ๋ด‰์‡„ยท์šฐํšŒ ๊ฒฐ์‹ฌ์€ ์ง€ํœ˜๊ณ„ํ†ต(์ž‘์ „๊ตฐ์ˆ˜๊ด€ ์Šน์ธ)์— ๋”ฐ๋ฅธ๋‹ค.\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 09. AAR ๊ธฐ๋ฐ˜ ๋ณ‘๋ชฉ ์˜ˆ์ธก ์˜ค์ฐจ ๋ถ„์„ ๋ฐ ๋„๋กœ ํ†ต๊ณผ ์šฉ๋Ÿ‰ ํŒŒ๋ผ๋ฏธํ„ฐ ์žฌ๋ณด์ •\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 3.0์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "AAR ๊ธฐ๋ฐ˜ ๋ณ‘๋ชฉ ์˜ˆ์ธก ์˜ค์ฐจ๋ฅผ ๋ถ„์„ํ•˜๊ณ  ํ†ต๊ณผ ์šฉ๋Ÿ‰์„ ์žฌ๋ณด์ •ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **AAR ๊ด€์ ์—์„œ ๋ณ‘๋ชฉ ์˜ˆ์ธก ์˜ค์ฐจ๋ฅผ ์›์ธ๋ณ„๋กœ ๋ถ„์„ํ•˜๊ณ  ๋„๋กœ ํ†ต๊ณผ ์šฉ๋Ÿ‰ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์žฌ๋ณด์ •ํ•˜๋ฉฐ, ํšจ๊ณผ๋ฅผ ROI๋กœ ํ™˜์‚ฐํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **๊ธฐ์ค€ BPR์˜ ์˜ค์ฐจ๋ฅผ ์ •๋น„์ผ, ๊ธด๊ธ‰ ๋ถˆ์ถœ์ผ, ์šฐ์ฒœ์ผ, ์ •์ƒ์ผ๋กœ ๋‚˜๋ˆ„์–ด ํ‰๊ท  ์˜ค์ฐจ๋ฅผ ๋น„๊ต ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `reports/mov01_roi_analysis.md` ํ˜•ํƒœ์˜ ๊ฒฐ๊ณผ๋ฌผ์„ ์™„์„ฑํ•œ๋‹ค.\n", "\n", "### ๐Ÿ” ํ‰๊ฐ€ ๋ฐ ๊ฒ€์ฆ ๊ธฐ์ค€ (PASS ์กฐ๊ฑด)\n", "#### ๋ฏธ์…˜ ํŒ์ •\n", "\n", "**ํ‰๊ฐ€ ๊ตฌ๋ถ„:** ํ•„์ˆ˜\n", "\n", "**ํ•ต์‹ฌ ํ‰๊ฐ€์˜์—ญ:** ์ถ”์ • ์‚ฐ์‹์˜ ๊ฐ๊ด€์„ฑ ๋ฐ ๊ตฐ ์žฌ์ •/๊ตฐ์ˆ˜ ๊ธฐํš ๋ถ€์„œ ์„ค๋“๋ ฅ\n", "\n", "#### ๋ฏธ์…˜ PASS ์กฐ๊ฑด\n", "\n", "- ์ •๋Ÿ‰ ๊ธฐ์ค€: ๋น„์šฉ ์ ˆ๊ฐ์•ก, ์‹œ๊ฐ„ ๋‹จ์ถ•๋ฅ  ๋“ฑ 3๋Œ€ ์ •๋Ÿ‰ ํšจ๊ณผ ์ง€ํ‘œ ์‚ฐ์ถœ\n", "\n", "\n", "- ์ง„ํ–‰ ๊ธฐ์ค€: ROI ๋ถ„์„ ๋ณด๊ณ ์„œ ์™„์„ฑ ๋ฐ ์ฃผ์š” ์ง€ํ‘œ ๋ฏผ๊ฐ๋„ ๋ถ„์„ ์ˆ˜๋ก\n", "\n", "\n", "- ๊ฒ€์ฆ ๋ฐฉ๋ฒ•: ํšŒ๊ณ„/๊ธฐํš ๊ธฐ์ค€ ๋ชจ๋ธ๊ณผ์˜ ๋น„๊ต ๊ฒ€์ฆ\n", "\n", "\n", "#### ์ฑ„์ ์ž ํŒ์ • ๋ฉ”๋ชจ\n", "\n", "> ๋‹จ์ˆœ ์ฝ”๋“œ ์‹คํ–‰ ์—ฌ๋ถ€๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ถœ๋ ฅ๋œ ์ˆ˜์น˜์™€ ๊ทผ๊ฑฐ ์ž๋ฃŒ๊ฐ€ ๊ตฐ ์‹ค๋ฌด ์š”๊ตฌ์‚ฌํ•ญ์— ๋ถ€ํ•ฉํ•˜๋Š”์ง€ ๋ฉด๋ฐ€ํžˆ ํ™•์ธํ•œ๋‹ค.\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\"\"\"AAR ๊ด€์ ์—์„œ BPR ๊ธฐ์ค€ ๋ชจ๋ธ์˜ ๋ณ‘๋ชฉ ์˜ˆ์ธก ์˜ค์ฐจ๋ฅผ ์›์ธ๋ณ„๋กœ ๋ถ„์„ํ•˜๊ณ , ์ตœ์†Œ์ œ๊ณฑ์œผ๋กœ ๊ตฌ๊ฐ„๋ณ„ ์œ ํšจ ํ†ต๊ณผ ์šฉ๋Ÿ‰ ๋ฐฐ์œจ๊ณผ ์ง€์ฒด ํ•จ์ˆ˜ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์žฌ๋ณด์ •ํ•ด ๊ฒ€์ฆ ๊ตฌ๊ฐ„ ์˜ค์ฐจ๋ฅผ ์ค„์ธ ๋’ค, ํšจ๊ณผ๋ฅผ ๋น„์šฉ ์ ˆ๊ฐ์•กยท์‹œ๊ฐ„ ๋‹จ์ถ•๋ฅ ยท๊ฒฝ๋ณด ํฌ์ฐฉ๋ฅ  3๋Œ€ ์ง€ํ‘œ๋กœ ํ™˜์‚ฐํ•˜๊ณ  ๋ฏผ๊ฐ๋„๋ฅผ ๋ถ„์„ํ•œ๋‹ค.\"\"\"\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from scipy.optimize import least_squares, linprog\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ƒ ๋…ธ์„ ๋งยท๊ตฌ๊ฐ„ ํ†ต๊ณผ๋Ÿ‰ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ๋„๋กœยท์ง€๋ช…ยท๋ถ€๋Œ€ ์—†์Œ)\n", "# ๊ฐ€์ƒ ์ถœ๋ฐœ์ง€(SRC)์—์„œ ๊ฐ€์ƒ ๋ชฉ์ ์ง€(DST)๊นŒ์ง€ 3๊ฐœ ์ถ•์„ (AยทBยทC)๊ณผ ์—ฐ๊ฒฐ๋กœ 2๊ฐœ, ๊ตฌ๊ฐ„ 10๊ฐœ. 150์ผ์น˜ ํ”ผํฌ์‹œ๊ฐ„ ํ†ต๊ณผ๋Ÿ‰ยท์ง€์ฒด ์‹œ๊ฐ„์„ ๋งŒ๋“ ๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "SEGS = pd.DataFrame([\n", " (\"A1\", \"SRC\", \"J1\", 12, 130, \"์ผ๋ฐ˜\"), (\"A2\", \"J1\", \"J2\", 8, 70, \"๊ต๋Ÿ‰\"), (\"A3\", \"J2\", \"DST\", 10, 140, \"๊ต์ฐจ๋กœ\"),\n", " (\"B1\", \"SRC\", \"J3\", 15, 100, \"์ผ๋ฐ˜\"), (\"B2\", \"J3\", \"J2\", 9, 80, \"ํ„ฐ๋„\"), (\"B3\", \"J3\", \"DST\", 14, 95, \"์ผ๋ฐ˜\"),\n", " (\"C1\", \"SRC\", \"J4\", 18, 70, \"์ผ๋ฐ˜\"), (\"C2\", \"J4\", \"DST\", 20, 75, \"๊ต๋Ÿ‰\"),\n", " (\"X1\", \"J1\", \"J3\", 7, 50, \"๊ต์ฐจ๋กœ\"), (\"X2\", \"J4\", \"J3\", 10, 60, \"์ผ๋ฐ˜\")],\n", " columns=[\"seg\", \"u\", \"v\", \"km\", \"cap\", \"kind\"]) # cap = ํ”ผํฌ์‹œ๊ฐ„ ํ†ต๊ณผ ์šฉ๋Ÿ‰(๋Œ€/์‹œ, ๊ต์œก์šฉ ๊ฐ€์ •๊ฐ’)\n", "KSPD = {\"์ผ๋ฐ˜\": 55, \"๊ต๋Ÿ‰\": 40, \"ํ„ฐ๋„\": 45, \"๊ต์ฐจ๋กœ\": 45} # ๊ตฌ๊ฐ„ ์œ ํ˜•๋ณ„ ์ž์œ  ์ฃผํ–‰ ์†๋„(km/h, ๊ฐ€์ •๊ฐ’)\n", "SEGS[\"t0\"] = SEGS[\"km\"] / SEGS[\"kind\"].map(KSPD) * 60 # ์ž์œ  ์ฃผํ–‰ ์‹œ๊ฐ„(๋ถ„)\n", "UTIL = dict(A1=.78, A2=.85, A3=.66, B1=.70, B2=.72, B3=.64, C1=.55, C2=.58, X1=.45, X2=.40) # ํ‰์‹œ ์ด์šฉ๋ฅ \n", "\n", "\n", "def bpr(t0, v, cap, a=0.15, b=4.0):\n", " \"\"\"BPR ์ง€์ฒด ํ•จ์ˆ˜(๊ตํ†ต๊ณตํ•™์˜ ํ‘œ์ค€ํ˜•): ํ†ต๊ณผ๋Ÿ‰/์šฉ๋Ÿ‰ ๋น„๊ฐ€ ์ปค์งˆ์ˆ˜๋ก ์ง€์ฒด๊ฐ€ ๊ธ‰์ฆํ•œ๋‹ค. (๋ถ„)\"\"\"\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 9] AAR ๊ธฐ๋ฐ˜ ๋ณ‘๋ชฉ ์˜ˆ์ธก ์˜ค์ฐจ ๋ถ„์„ ๋ฐ ๋„๋กœ ํ†ต๊ณผ ์šฉ๋Ÿ‰ ํŒŒ๋ผ๋ฏธํ„ฐ ์žฌ๋ณด์ • ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def make_data(seed=11, days=150):\n", " \"\"\"๊ตฌ๊ฐ„ x ์ผ์ž ํŒจ๋„. v=์‹ค์ œ ํ”ผํฌ ํ†ต๊ณผ๋Ÿ‰, plan_v=์ˆ˜์†ก๊ณ„ํš ์ˆ˜๋Ÿ‰(๊ธด๊ธ‰ ๋ถˆ์ถœ ๋ฏธ๋ฐ˜์˜), delay=์‹ค์ œ ์ง€์ฒด(๋ถ„).\"\"\"\n", " rng = np.random.default_rng(seed)\n", " d_idx = np.arange(days)\n", " lvl = np.array([1.0, 1.08, 1.04, 1.10, 1.02, 0.82, 0.78])[d_idx % 7] * (1 + 0.0012 * d_idx)\n", " lvl = lvl * np.where((d_idx >= 100) & (d_idx < 110), 1.30, 1.0) # ์ˆ˜์†ก๋Ÿ‰ ํญ์ฆ ๊ธฐ๊ฐ„(๊ฐ€์ƒ)\n", " prev, rows = {s: 0.0 for s in SEGS[\"seg\"]}, []\n", " for d in d_idx:\n", " rain = bool(rng.random() < 0.18)\n", " for r in SEGS.itertuples():\n", " maint, urgent = bool(rng.random() < 0.06), bool(rng.random() < 0.05) # ๋น„๊ณ„ํš ์ •๋น„ / ๊ธด๊ธ‰ ๋ถˆ์ถœ\n", " v = r.cap * UTIL[r.seg] * lvl[d] * (1.25 if urgent else 1.0) * rng.normal(1, 0.05)\n", " cap_eff = r.cap * (0.65 if maint else 1.0) * (0.92 if rain else 1.0)\n", " delay = max(r.t0 * 0.2 * (v / cap_eff) ** 3.5 + 0.25 * prev[r.seg] + rng.normal(0, 0.4), 0.0)\n", " prev[r.seg] = delay\n", " rows.append(dict(day=int(d), dow=int(d % 7), seg=r.seg, kind=r.kind, km=r.km, cap=r.cap, t0=r.t0,\n", " plan_v=v / (1.25 if urgent else 1.0) * rng.normal(1, 0.04), v=v, maint=int(maint), urgent=int(urgent),\n", " rain=int(rain), cap_eff=cap_eff, delay=delay))\n", " df = pd.DataFrame(rows)\n", " df[\"transit\"] = df[\"t0\"] * (1 + 0.08 * df[\"rain\"]) + df[\"delay\"]\n", " return df\n", "\n", "\n", "def mape(y, p):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 9] AAR ๊ธฐ๋ฐ˜ ๋ณ‘๋ชฉ ์˜ˆ์ธก ์˜ค์ฐจ ๋ถ„์„ ๋ฐ ๋„๋กœ ํ†ต๊ณผ ์šฉ๋Ÿ‰ ํŒŒ๋ผ๋ฏธํ„ฐ ์žฌ๋ณด์ • ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def mae(y, p):\n", " return float(np.mean(np.abs(np.asarray(y, float) - np.asarray(p, float))))\n", "# ---------------------------------------------------------------------------\n", "# ---------------------------------------------------------------------------\n", "# ์ง€์ฒด ์˜ˆ์ธก์šฉ ํŒŒ์ƒ ํ”ผ์ฒ˜ ์ƒ์„ฑ(๋‹น์ผ ์˜ค์ „ ์‹œ์ ์— ์•Œ ์ˆ˜ ์žˆ๋Š” ์ •๋ณด๋งŒ ์‚ฌ์šฉ: ๊ณ„ํš ๋ฌผ๋Ÿ‰, ๊ณต์ง€ ์ •๋น„, ๊ธฐ์ƒ ์˜ˆ๋ณด, ์ „์ผยท์ „์ฃผ ์‹ค์ )\n", "# ---------------------------------------------------------------------------\n", "FEATS = [\"plan_v\", \"cap\", \"cap_eff\", \"t0\", \"km\", \"maint\", \"rain\", \"util\", \"util_eff\", \"util_eff4\", \"bpr_base\", \"lag1_delay\", \"lag7_delay\",\n", " \"roll7_v\", \"surge_ratio\", \"is_weekend\", \"dow_sin\", \"dow_cos\", \"k_๊ต๋Ÿ‰\", \"k_ํ„ฐ๋„\", \"k_๊ต์ฐจ๋กœ\"]\n", "\n", "\n", "def add_features(df):\n", " d = df.sort_values([\"seg\", \"day\"]).copy()\n", " g = d.groupby(\"seg\")\n", " d[\"lag1_delay\"] = g[\"delay\"].shift(1)\n", " d[\"lag7_delay\"] = g[\"delay\"].shift(7)\n", " d[\"roll7_v\"] = g[\"v\"].transform(lambda s: s.shift(1).rolling(7).mean())\n", " d[\"util\"] = d[\"plan_v\"] / d[\"cap\"]\n", " d[\"util_eff\"] = d[\"plan_v\"] / d[\"cap_eff\"]\n", " d[\"util_eff4\"] = d[\"util_eff\"] ** 4\n", " d[\"bpr_base\"] = bpr(d[\"t0\"], d[\"plan_v\"], d[\"cap\"])\n", " d[\"surge_ratio\"] = d[\"plan_v\"] / d[\"roll7_v\"]\n", " d[\"is_weekend\"] = (d[\"dow\"] >= 5).astype(int)\n", " d[\"dow_sin\"], d[\"dow_cos\"] = np.sin(2 * np.pi * d[\"dow\"] / 7), np.cos(2 * np.pi * d[\"dow\"] / 7)\n", " for k in (\"๊ต๋Ÿ‰\", \"ํ„ฐ๋„\", \"๊ต์ฐจ๋กœ\"):\n", " d[\"k_\" + k] = (d[\"kind\"] == k).astype(int)\n", " return d.dropna().sort_values([\"day\", \"seg\"]).reset_index(drop=True)\n", "# ---------------------------------------------------------------------------\n", "# ---------------------------------------------------------------------------\n", "# ๋…ธ์„ ๋ง ํ๋ฆ„ ๋„๊ตฌ: ์ตœ๋Œ€์œ ๋Ÿ‰(Edmonds-Karp), ์ฆ๋ถ„ ๋ฐฐ์ •(ํ˜ผ์žก ๋ฐ˜์˜ ์ตœ๋‹จ์‹œ๊ฐ„ ๊ฒฝ๋กœ), ๊ฒฝ๋กœ ์—ด๊ฑฐ\n", "# ---------------------------------------------------------------------------\n", "CAP = dict(zip(SEGS[\"seg\"], SEGS[\"cap\"]))\n", "T0 = dict(zip(SEGS[\"seg\"], SEGS[\"t0\"]))\n", "KM = dict(zip(SEGS[\"seg\"], SEGS[\"km\"]))\n", "ARCS = {r.seg: (r.u, r.v) for r in SEGS.itertuples()}\n", "SRC, DST = \"SRC\", \"DST\"\n", "\n", "\n", "def seg_time(s, flow, cap=None, a=0.2, b=3.5):\n", " \"\"\"๊ตฌ๊ฐ„ ํ†ต๊ณผ์‹œ๊ฐ„(๋ถ„) = ์ž์œ ์ฃผํ–‰ + ํ˜ผ์žก ์ง€์ฒด. cap์„ ์ฃผ๋ฉด ์šฉ๋Ÿ‰์„ ๋Œ€์ฒด(์ •๋น„ยท๊ธฐ์ƒ ๋ฐ˜์˜).\"\"\"\n", " r = flow / (cap or CAP[s])\n", " return T0[s] * (1 + a * r ** b + 6.0 * max(0.0, r - 1.0)) # ์šฉ๋Ÿ‰ ์ดˆ๊ณผ๋ถ„์€ ๋Œ€๊ธฐ์—ด๋กœ ๊ธ‰์ฆ\n", "\n", "\n", "def max_flow(closed=(), capmul=None):\n", " \"\"\"์ตœ๋Œ€์œ ๋Ÿ‰(๋Œ€/์‹œ)๊ณผ ์ตœ์†Œ ์ ˆ๋‹จ ๊ตฌ๊ฐ„(๋ณ‘๋ชฉ). capmul = {๊ตฌ๊ฐ„: ์šฉ๋Ÿ‰ ๋ฐฐ์œจ}.\"\"\"\n", " cap = {s: (0 if s in closed else CAP[s] * (capmul or {}).get(s, 1.0)) for s in CAP}\n", " res = {}\n", " for s, (u, v) in ARCS.items():\n", " res[(u, v)] = res.get((u, v), 0) + cap[s]\n", " res.setdefault((v, u), 0)\n", " flow = 0.0\n", " while True:\n", " prev, q = {SRC: None}, [SRC]\n", " for u in q:\n", " for (a, b), c in res.items():\n", " if a == u and c > 1e-9 and b not in prev:\n", " prev[b] = u; q.append(b)\n", " if DST not in prev:\n", " break\n", " path, x = [], DST\n", " while prev[x]:\n", " path.append((prev[x], x)); x = prev[x]\n", " f = min(res[e] for e in path)\n", " for a, b in path:\n", " res[(a, b)] -= f; res[(b, a)] += f\n", " flow += f\n", " reach = set(prev)\n", " cut = [s for s, (u, v) in ARCS.items() if u in reach and v not in reach and cap[s] > 0]\n", " return flow, cut\n", "\n", "\n", "def all_paths():\n", " \"\"\"SRC -> DST ๋‹จ์ˆœ ๊ฒฝ๋กœ(๊ตฌ๊ฐ„ ์ด๋ฆ„ ํŠœํ”Œ) ์ „์ฒด.\"\"\"\n", " out = []\n", "\n", " def dfs(node, segs, seen):\n", " if node == DST:\n", " out.append(tuple(segs)); return\n", " for s, (u, v) in ARCS.items():\n", " if u == node and v not in seen:\n", " dfs(v, segs + [s], seen | {v})\n", " dfs(SRC, [], {SRC})\n", " return out\n", "\n", "\n", "PATHS = all_paths()\n", "\n", "\n", "def assign(demand, closed=(), capmul=None, steps=40):\n", " \"\"\"์ฆ๋ถ„ ๋ฐฐ์ •: ์ˆ˜์š”๋ฅผ steps์กฐ๊ฐ์œผ๋กœ ๋‚˜๋ˆ , ์ „์ฒด ํ†ต๊ณผ์‹œ๊ฐ„(๋Œ€์ˆ˜ x ์‹œ๊ฐ„) ์ฆ๊ฐ€๊ฐ€ ๊ฐ€์žฅ ์ž‘์€ ๊ฒฝ๋กœ์— ์ฐจ๋ก€๋กœ ์–น๋Š”๋‹ค(์‹œ์Šคํ…œ ์ตœ์  ๊ทผ์‚ฌ).\"\"\"\n", " cap = {s: CAP[s] * (capmul or {}).get(s, 1.0) for s in CAP}\n", " flow, pf = {s: 0.0 for s in CAP}, {}\n", " ok = [p for p in PATHS if not any(s in closed for s in p)]\n", " if not ok:\n", " return None\n", " for _ in range(steps):\n", " chunk = demand / steps\n", " best = min(ok, key=lambda p: sum((flow[s] + chunk) * seg_time(s, flow[s] + chunk, cap[s]) - flow[s] * seg_time(s, flow[s], cap[s]) for s in p))\n", " for s in best:\n", " flow[s] += chunk\n", " pf[best] = pf.get(best, 0) + chunk\n", " tt = {p: sum(seg_time(s, flow[s], cap[s]) for s in p) for p in pf}\n", " avg = sum(pf[p] * tt[p] for p in pf) / demand\n", " return {\"flow\": flow, \"paths\": pf, \"path_min\": tt, \"avg_min\": avg, \"util\": {s: flow[s] / cap[s] for s in cap if cap[s] > 0}}\n", "\n", "\n", "FIXED_SHARE = {(\"A1\", \"A2\", \"A3\"): 0.32, (\"B1\", \"B3\"): 0.40, (\"C1\", \"C2\"): 0.28} # ๊ณผ๊ฑฐ ๊ด€ํ–‰: ์ถ•์„ ๋ณ„ ๊ณ ์ • ๋ฐฐ๋ถ„(๊ฐ€์ƒ)\n", "\n", "\n", "def fixed_assign(demand, closed=(), capmul=None):\n", " \"\"\"๊ณผ๊ฑฐ ๊ด€ํ–‰ ๋ฐฐ๋ถ„: ์ถ•์„ ๋ณ„ ๊ณ ์ • ๋น„์œจ. ๋ง‰ํžŒ ์ถ•์„  ๋ฌผ๋Ÿ‰์€ ์—ด๋ ค ์žˆ๋Š” ์ถ•์„ ์— ๋น„์œจ๋Œ€๋กœ ์žฌ๋ถ„๋ฐฐ.\"\"\"\n", " cap = {s: CAP[s] * (capmul or {}).get(s, 1.0) for s in CAP}\n", " live = {p: w for p, w in FIXED_SHARE.items() if not any(s in closed for s in p)}\n", " if not live:\n", " return None\n", " tot = sum(live.values())\n", " flow = {s: 0.0 for s in CAP}\n", " pf = {p: demand * w / tot for p, w in live.items()}\n", " for p, f in pf.items():\n", " for s in p:\n", " flow[s] += f\n", " tt = {p: sum(seg_time(s, flow[s], cap[s]) for s in p) for p in pf}\n", " return {\"flow\": flow, \"paths\": pf, \"path_min\": tt, \"avg_min\": sum(pf[p] * tt[p] for p in pf) / demand,\n", " \"util\": {s: flow[s] / cap[s] for s in cap if cap[s] > 0}}\n", "\n", "\n", "def lp_assign(demand, closed=(), capmul=None):\n", " \"\"\"์„ ํ˜•๊ณ„ํš(LP): ๊ตฌ๊ฐ„ ์šฉ๋Ÿ‰ ์ƒํ•œ(์ด์šฉ๋ฅ  rho) ์•„๋ž˜์—์„œ ์ž์œ ์ฃผํ–‰์‹œ๊ฐ„ ํ•ฉ์„ ์ตœ์†Œํ™”ํ•˜๋Š” ๊ฒฝ๋กœ๋ณ„ ๋ฌผ๋Ÿ‰. ๋ถˆ๊ฐ€๋Šฅํ•˜๋ฉด rho๋ฅผ 1.0๊นŒ์ง€ ์™„ํ™”.\"\"\"\n", " cap = {s: CAP[s] * (capmul or {}).get(s, 1.0) for s in CAP}\n", " ok = [p for p in PATHS if not any(s in closed for s in p)]\n", " if not ok:\n", " return None\n", " for rho in (0.85, 0.95, 1.0):\n", " r = linprog([sum(T0[s] for s in p) for p in ok], A_ub=[[1.0 if s in p else 0.0 for p in ok] for s in CAP],\n", " b_ub=[rho * cap[s] for s in CAP], A_eq=[[1.0] * len(ok)], b_eq=[demand], bounds=(0, None), method=\"highs\")\n", " if r.status == 0:\n", " break\n", " else:\n", " return assign(demand, closed, capmul) # ์šฉ๋Ÿ‰ ์ดˆ๊ณผ ์ˆ˜์š”: ์ฆ๋ถ„ ๋ฐฐ์ •์œผ๋กœ ๋Œ€์ฒด(๊ณผ๋ถ€ํ•˜ ํ‘œ์‹œ)\n", " flow = {s: sum(r.x[i] for i, p in enumerate(ok) if s in p) for s in CAP}\n", " pf = {p: float(r.x[i]) for i, p in enumerate(ok) if r.x[i] > 1e-6}\n", " tt = {p: sum(seg_time(s, flow[s], cap[s]) for s in p) for p in pf}\n", " return {\"flow\": flow, \"paths\": pf, \"path_min\": tt, \"avg_min\": sum(pf[p] * tt[p] for p in pf) / demand,\n", " \"util\": {s: flow[s] / cap[s] for s in cap if cap[s] > 0}, \"rho\": rho}\n", "\n", "\n", "def cost_of(res, tv=1000, fuel_km=450):\n", " \"\"\"์‹œ๊ฐ„๋‹น ๋น„์šฉ(์›, ๊ต์œก์šฉ ๊ฐ€์ •): ๋Œ€๋‹น ์‹œ๊ฐ„๋น„์šฉ tv์›/๋ถ„ + ์œ ๋ฅ˜๋น„ fuel_km์›/km.\"\"\"\n", " return sum(f * (res[\"path_min\"][p] * tv + sum(KM[s] for s in p) * fuel_km) for p, f in res[\"paths\"].items())\n", "# ---------------------------------------------------------------------------\n", "\n", "df = make_data(); d = add_features(df)\n", "SEGL = list(SEGS[\"seg\"])\n", "cal, val = d[d.day < 135], d[d.day >= 135] # AAR ๋Œ€์ƒ(๋ณด์ •) ๊ตฌ๊ฐ„ / ๊ฒ€์ฆ ๊ตฌ๊ฐ„\n", "\n", "\n", "def model(p, x):\n", " \"\"\"๋ณด์ • ๋ชจ๋ธ: a, b, c(์ „์ผ ์ง€์ฒด ์ด์›”), ๊ตฌ๊ฐ„๋ณ„ ์šฉ๋Ÿ‰ ๋ฐฐ์œจ m_s, ์ •๋น„ ์‹œ ์šฉ๋Ÿ‰ ๋ฐฐ์œจ, ์šฐ์ฒœ ์‹œ ์šฉ๋Ÿ‰ ๋ฐฐ์œจ.\"\"\"\n", " a, b, c, mm, rr = p[0], p[1], p[2], p[3], p[4]\n", " m = pd.Series(p[5:], index=SEGL)[x[\"seg\"]].values\n", " eff = x[\"cap\"].values * m * np.where(x[\"maint\"] == 1, mm, 1.0) * np.where(x[\"rain\"] == 1, rr, 1.0)\n", " return x[\"t0\"].values * a * (x[\"plan_v\"].values / eff) ** b + c * x[\"lag1_delay\"].values\n", "\n", "\n", "p0 = np.r_[0.15, 4.0, 0.0, 1.0, 1.0, np.ones(len(SEGL))]\n", "lo = np.r_[0.01, 1.0, 0.0, 0.3, 0.5, np.full(len(SEGL), 0.5)]\n", "hi = np.r_[1.0, 6.0, 0.8, 1.2, 1.2, np.full(len(SEGL), 1.5)]\n", "fit = least_squares(lambda p: model(p, cal) - cal[\"delay\"].values, p0, bounds=(lo, hi))\n", "P = fit.x\n", "\n", "# ---------------- 1) ์˜ค์ฐจ ๋ถ„์„(AAR) ----------------\n", "base_cal, base_val = cal[\"bpr_base\"].values, val[\"bpr_base\"].values\n", "new_val = model(P, val)\n", "print(f\"[1] ๊ฒ€์ฆ ๊ตฌ๊ฐ„(day 135~149, {len(val)}ํ–‰) ์ง€์ฒด MAE: ๊ธฐ์ค€ {mae(val.delay, base_val):.2f}๋ถ„ -> ์žฌ๋ณด์ • {mae(val.delay, new_val):.2f}๋ถ„ ({1 - mae(val.delay, new_val) / mae(val.delay, base_val):.0%} ๊ฐœ์„ )\")\n", "print(f\" ํ†ต๊ณผ์‹œ๊ฐ„ MAPE: ๊ธฐ์ค€ {mape(val.transit, val.t0 + base_val):.2f}% -> ์žฌ๋ณด์ • {mape(val.transit, val.t0 * (1 + .08 * val.rain) + new_val):.2f}%\")\n", "err = pd.DataFrame({\"์›์ธ\": [\"๋น„๊ณ„ํš ์ •๋น„์ผ\", \"๊ธด๊ธ‰ ๋ถˆ์ถœ์ผ\", \"์šฐ์ฒœ์ผ\", \"์ •์ƒ์ผ\"],\n", " \"๊ฑด์ˆ˜\": [int((cal.maint == 1).sum()), int((cal.urgent == 1).sum()), int((cal.rain == 1).sum()), int(((cal.maint + cal.urgent + cal.rain) == 0).sum())],\n", " \"๊ธฐ์ค€ ํ‰๊ท ์˜ค์ฐจ\": [(base_cal - cal.delay)[m].mean() for m in (cal.maint == 1, cal.urgent == 1, cal.rain == 1, (cal.maint + cal.urgent + cal.rain) == 0)],\n", " \"์žฌ๋ณด์ • ํ‰๊ท ์˜ค์ฐจ\": [(model(P, cal) - cal.delay)[m].mean() for m in (cal.maint == 1, cal.urgent == 1, cal.rain == 1, (cal.maint + cal.urgent + cal.rain) == 0)]}).round(2)\n", "print(\"[2] AAR ์›์ธ๋ณ„ ํ‰๊ท  ์˜ค์ฐจ(์˜ˆ์ธก-์‹ค์ œ, ๋ถ„; ์Œ์ˆ˜ = ๊ณผ์†Œ์˜ˆ์ธก)\")\n", "print(err.to_string(index=False))\n", "pm = pd.DataFrame({\"ํŒŒ๋ผ๋ฏธํ„ฐ\": [\"a(์ง€์ฒด ๊ณ„์ˆ˜)\", \"b(๋น„์„ ํ˜• ์ง€์ˆ˜)\", \"c(์ „์ผ ์ด์›”)\", \"์ •๋น„ ์‹œ ์šฉ๋Ÿ‰ ๋ฐฐ์œจ\", \"์šฐ์ฒœ ์‹œ ์šฉ๋Ÿ‰ ๋ฐฐ์œจ\"], \"๊ณต์นญ/๊ธฐ์ค€\": [0.15, 4.0, 0.0, 1.0, 1.0], \"์žฌ๋ณด์ •\": P[:5]}).round(3)\n", "print(\"[3] ์žฌ๋ณด์ • ํŒŒ๋ผ๋ฏธํ„ฐ:\", \"; \".join(f\"{r['ํŒŒ๋ผ๋ฏธํ„ฐ']} {r['๊ณต์นญ/๊ธฐ์ค€']} -> {r['์žฌ๋ณด์ •']}\" for _, r in pm.iterrows()))\n", "ms = pd.Series(P[5:], index=SEGL).round(2)\n", "ms_txt = {k: float(v) for k, v in ms.items()}\n", "print(\" ๊ตฌ๊ฐ„๋ณ„ ์œ ํšจ ์šฉ๋Ÿ‰ ๋ฐฐ์œจ:\", ms_txt, \"(1.0 = ๊ณต์นญ ์šฉ๋Ÿ‰๊ณผ ์ผ์น˜)\")\n", "\n", "# ---------------- 2) ํšจ๊ณผ ํ™˜์‚ฐ: ๊ฒ€์ฆ 15์ผ ๋ฐฑํ…Œ์ŠคํŠธ ----------------\n", "TV = 300 # ๋Œ€๋‹น ๋ถ„๋‹น ์‹œ๊ฐ„๊ฐ€์น˜(์›, ๊ฐ€์ •)\n", "hours_per_day, APPLY = 2, 0.4 # ์ผ ํ”ผํฌ 2์‹œ๊ฐ„, ์Šน์ธยทํ˜„์žฅ ์ œ์•ฝ์œผ๋กœ ์‹ค์ œ ์ ์šฉ๋˜๋Š” ๋น„์œจ 40%(๊ฐ€์ •)\n", "rec = []\n", "for day, g in df[df.day >= 135].groupby(\"day\"):\n", " D = float(g[g.seg.isin([\"A1\", \"B1\", \"C1\"])][\"plan_v\"].sum())\n", " cm = {r.seg: r.cap_eff / r.cap for r in g.itertuples() if r.cap_eff < r.cap}\n", " f, l = fixed_assign(D, (), cm), lp_assign(D, (), cm)\n", " pr_b, pr_n = val[val.day == day], val[val.day == day]\n", " rec.append((day, cost_of(f, tv=TV), cost_of(l, tv=TV), f[\"avg_min\"], l[\"avg_min\"], D, float(g.delay.max()),\n", " float((pr_b.bpr_base).max()), float(model(P, pr_n).max())))\n", "bt = pd.DataFrame(rec, columns=[\"day\", \"c_fix\", \"c_lp\", \"t_fix\", \"t_lp\", \"D\", \"max_delay\", \"pred_base\", \"pred_new\"])\n", "saving_day = (bt[\"c_fix\"] - bt[\"c_lp\"]) * hours_per_day * APPLY\n", "ann = saving_day.mean() * 365\n", "time_cut = 1 - bt[\"t_lp\"].sum() / bt[\"t_fix\"].sum()\n", "veh_min = float(((bt[\"t_fix\"] - bt[\"t_lp\"]) * bt[\"D\"] * hours_per_day * APPLY).sum())\n", "alert_true = bt[\"max_delay\"] >= 5\n", "cap_b = float(((bt[\"pred_base\"] >= 5) & alert_true).sum() / max(alert_true.sum(), 1)); cap_n = float(((bt[\"pred_new\"] >= 5) & alert_true).sum() / max(alert_true.sum(), 1))\n", "fa_b, fa_n = int(((bt[\"pred_base\"] >= 5) & ~alert_true).sum()), int(((bt[\"pred_new\"] >= 5) & ~alert_true).sum())\n", "print(f\"\\n[4] 3๋Œ€ ์ •๋Ÿ‰ ํšจ๊ณผ ์ง€ํ‘œ(๊ฒ€์ฆ 15์ผ, ๊ณ ์ • ๋ฐฐ๋ถ„ ๋Œ€๋น„ LP ์šฐํšŒ ์ตœ์ ํ™”, ๊ฐ€์ • ๋‹จ๊ฐ€ ์‚ฌ์šฉ)\")\n", "print(f\" (1) ๋น„์šฉ ์ ˆ๊ฐ์•ก: ์ผ ํ‰๊ท  {saving_day.mean() / 1e4:,.0f}๋งŒ์›, ์—ฐ ํ™˜์‚ฐ {ann / 1e8:,.1f}์–ต์›(๊ฐ€์ •: ์ผ ํ”ผํฌ 2์‹œ๊ฐ„, ์ ์šฉ๋ฅ  40%, 365์ผ)\")\n", "print(f\" (2) ์‹œ๊ฐ„ ๋‹จ์ถ•๋ฅ : ํ‰๊ท  ํ†ต๊ณผ์‹œ๊ฐ„ {bt['t_fix'].mean():.1f}๋ถ„ -> {bt['t_lp'].mean():.1f}๋ถ„ ({time_cut:.1%}), ์ ˆ๊ฐ ์ฐจ๋Ÿ‰ยท์‹œ๊ฐ„ {veh_min / 60:,.0f}๋Œ€ยท์‹œ๊ฐ„\")\n", "print(f\" (3) ์ง€์ฒด ๊ฒฝ๋ณด ํฌ์ฐฉ๋ฅ (์‹ค์ œ ์ง€์ฒด 5๋ถ„ ์ด์ƒ {int(alert_true.sum())}์ผ ๋Œ€๋น„): ๊ธฐ์ค€ {cap_b:.0%} -> ์žฌ๋ณด์ • {cap_n:.0%} (์˜ค๊ฒฝ๋ณด {fa_b} -> {fa_n}์ผ)\")\n", "\n", "# ---------------- 3) ROI์™€ ๋ฏผ๊ฐ๋„(๊ฐ€์ • ๋„์ž…ยท์šด์˜๋น„) ----------------\n", "COST = 2.0e8 # ๋„์ž…ยท์šด์˜ ์—ฐ ๋น„์šฉ 2์–ต์›(๊ต์œก์šฉ ๊ฐ€์ •)\n", "roi = (ann - COST) / COST\n", "print(f\"\\n[5] ROI = (์—ฐ ์ ˆ๊ฐ - ๋น„์šฉ) / ๋น„์šฉ = {roi:.1%} (์—ฐ ๋น„์šฉ {COST / 1e8:.1f}์–ต์› ๊ฐ€์ •), ํšŒ์ˆ˜ ๊ธฐ๊ฐ„ {COST / ann * 12:.1f}๊ฐœ์›”\")\n", "sens = pd.DataFrame({\"์‹œ๊ฐ„๊ฐ€์น˜ -30%\": [ann * 0.7], \"๊ธฐ์ค€\": [ann], \"์‹œ๊ฐ„๊ฐ€์น˜ +30%\": [ann * 1.3]}).T[0]\n", "low_eff = ann * 0.5\n", "print(\" ๋ฏผ๊ฐ๋„(์—ฐ ์ ˆ๊ฐ, ์–ต์›):\", {k: round(float(v) / 1e8, 1) for k, v in sens.items()}, f\"| ํšจ๊ณผ๊ฐ€ ์ ˆ๋ฐ˜์ด๋ฉด ROI {(low_eff - COST) / COST:.1%}\")\n", "print(\" ๋‹จ๊ณ„๋ณ„ ๋กœ๋“œ๋งต: ๋‹จ๊ธฐ(1~3๊ฐœ์›”) ๊ฒฝ๋ณด ๊ธฐ์ค€ ์ •์ฐฉ -> ์ค‘๊ธฐ(6๊ฐœ์›”) ๊ตฌ๊ฐ„๋ณ„ ์šฉ๋Ÿ‰ ๋ฐฐ์œจ ์›” ๋‹จ์œ„ ์žฌ๋ณด์ • -> ์žฅ๊ธฐ(1๋…„) ์ƒ์œ„ ์ •๋ณด์ฒด๊ณ„ ์—ฐ๋™ ๊ฒ€ํ† \")\n", "print(\" ์ถ”์ • ์‚ฐ์‹์€ ๊ฐ€์ • ๋‹จ๊ฐ€์— ํฌ๊ฒŒ ์˜์กดํ•˜๋ฏ€๋กœ ์‹ค์ œ ์ ์šฉ ๋•Œ๋Š” ๊ธฐํšยท์žฌ์ • ๋ถ€์„œ๊ฐ€ ๋‹จ๊ฐ€๋ฅผ ์ œ์‹œํ•ด์•ผ ํ•˜๋ฉฐ ๋ณธ ๊ฐ’์€ ํ•™์Šต์šฉ์ด๋‹ค.\")\n", "\n", "fig, ax = plt.subplots(1, 3, figsize=(15, 4))\n", "ax[0].scatter(val[\"delay\"], base_val, s=8, label=\"baseline\"); ax[0].scatter(val[\"delay\"], new_val, s=8, label=\"recalibrated\"); ax[0].plot([0, 15], [0, 15], \"k--\"); ax[0].legend(); ax[0].set_title(\"Actual vs predicted delay (min)\")\n", "ax[1].bar(ms.index, ms.values); ax[1].axhline(1, c=\"k\", ls=\"--\"); ax[1].set_title(\"Effective capacity multiplier by segment\")\n", "ax[2].bar([\"-30%\", \"base\", \"+30%\"], [v / 1e8 for v in sens.values]); ax[2].set_title(\"Annual saving sensitivity (100M won)\")\n", "plt.tight_layout(); plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 09 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[1] ๊ฒ€์ฆ ๊ตฌ๊ฐ„(day 135~149, 150ํ–‰) ์ง€์ฒด MAE: ๊ธฐ์ค€ 1.69๋ถ„ -> ์žฌ๋ณด์ • 0.54๋ถ„ (68% ๊ฐœ์„ )\n", " ํ†ต๊ณผ์‹œ๊ฐ„ MAPE: ๊ธฐ์ค€ 10.02% -> ์žฌ๋ณด์ • 3.05%\n", "[2] AAR ์›์ธ๋ณ„ ํ‰๊ท  ์˜ค์ฐจ(์˜ˆ์ธก-์‹ค์ œ, ๋ถ„; ์Œ์ˆ˜ = ๊ณผ์†Œ์˜ˆ์ธก)\n", " ์›์ธ ๊ฑด์ˆ˜ ๊ธฐ์ค€ ํ‰๊ท ์˜ค์ฐจ ์žฌ๋ณด์ • ํ‰๊ท ์˜ค์ฐจ\n", "๋น„๊ณ„ํš ์ •๋น„์ผ 78 -4.73 -0.03\n", " ๊ธด๊ธ‰ ๋ถˆ์ถœ์ผ 69 -2.40 -1.33\n", " ์šฐ์ฒœ์ผ 160 -1.55 0.01\n", " ์ •์ƒ์ผ 994 -0.81 0.09\n", "[3] ์žฌ๋ณด์ • ํŒŒ๋ผ๋ฏธํ„ฐ: a(์ง€์ฒด ๊ณ„์ˆ˜) 0.15 -> 0.197; b(๋น„์„ ํ˜• ์ง€์ˆ˜) 4.0 -> 3.091; c(์ „์ผ ์ด์›”) 0.0 -> 0.258; ์ •๋น„ ์‹œ ์šฉ๋Ÿ‰ ๋ฐฐ์œจ 1.0 -> 0.623; ์šฐ์ฒœ ์‹œ ์šฉ๋Ÿ‰ ๋ฐฐ์œจ 1.0 -> 0.922\n", " ๊ตฌ๊ฐ„๋ณ„ ์œ ํšจ ์šฉ๋Ÿ‰ ๋ฐฐ์œจ: {'A1': 0.98, 'A2': 0.98, 'A3': 1.04, 'B1': 1.01, 'B2': 0.98, 'B3': 1.04, 'C1': 1.0, 'C2': 1.0, 'X1': 0.97, 'X2': 1.03} (1.0 = ๊ณต์นญ ์šฉ๋Ÿ‰๊ณผ ์ผ์น˜)\n", "\n", "[4] 3๋Œ€ ์ •๋Ÿ‰ ํšจ๊ณผ ์ง€ํ‘œ(๊ฒ€์ฆ 15์ผ, ๊ณ ์ • ๋ฐฐ๋ถ„ ๋Œ€๋น„ LP ์šฐํšŒ ์ตœ์ ํ™”, ๊ฐ€์ • ๋‹จ๊ฐ€ ์‚ฌ์šฉ)\n", " (1) ๋น„์šฉ ์ ˆ๊ฐ์•ก: ์ผ ํ‰๊ท  90๋งŒ์›, ์—ฐ ํ™˜์‚ฐ 3.3์–ต์›(๊ฐ€์ •: ์ผ ํ”ผํฌ 2์‹œ๊ฐ„, ์ ์šฉ๋ฅ  40%, 365์ผ)\n", " (2) ์‹œ๊ฐ„ ๋‹จ์ถ•๋ฅ : ํ‰๊ท  ํ†ต๊ณผ์‹œ๊ฐ„ 63.3๋ถ„ -> 47.7๋ถ„ (24.6%), ์ ˆ๊ฐ ์ฐจ๋Ÿ‰ยท์‹œ๊ฐ„ 816๋Œ€ยท์‹œ๊ฐ„\n", " (3) ์ง€์ฒด ๊ฒฝ๋ณด ํฌ์ฐฉ๋ฅ (์‹ค์ œ ์ง€์ฒด 5๋ถ„ ์ด์ƒ 7์ผ ๋Œ€๋น„): ๊ธฐ์ค€ 0% -> ์žฌ๋ณด์ • 86% (์˜ค๊ฒฝ๋ณด 0 -> 0์ผ)\n", "\n", "[5] ROI = (์—ฐ ์ ˆ๊ฐ - ๋น„์šฉ) / ๋น„์šฉ = 64.9% (์—ฐ ๋น„์šฉ 2.0์–ต์› ๊ฐ€์ •), ํšŒ์ˆ˜ ๊ธฐ๊ฐ„ 7.3๊ฐœ์›”\n", " ๋ฏผ๊ฐ๋„(์—ฐ ์ ˆ๊ฐ, ์–ต์›): {'์‹œ๊ฐ„๊ฐ€์น˜ -30%': 2.3, '๊ธฐ์ค€': 3.3, '์‹œ๊ฐ„๊ฐ€์น˜ +30%': 4.3} | ํšจ๊ณผ๊ฐ€ ์ ˆ๋ฐ˜์ด๋ฉด ROI -17.5%\n", " ๋‹จ๊ณ„๋ณ„ ๋กœ๋“œ๋งต: ๋‹จ๊ธฐ(1~3๊ฐœ์›”) ๊ฒฝ๋ณด ๊ธฐ์ค€ ์ •์ฐฉ -> ์ค‘๊ธฐ(6๊ฐœ์›”) ๊ตฌ๊ฐ„๋ณ„ ์šฉ๋Ÿ‰ ๋ฐฐ์œจ ์›” ๋‹จ์œ„ ์žฌ๋ณด์ • -> ์žฅ๊ธฐ(1๋…„) ์ƒ์œ„ ์ •๋ณด์ฒด๊ณ„ ์—ฐ๋™ ๊ฒ€ํ† \n", " ์ถ”์ • ์‚ฐ์‹์€ ๊ฐ€์ • ๋‹จ๊ฐ€์— ํฌ๊ฒŒ ์˜์กดํ•˜๋ฏ€๋กœ ์‹ค์ œ ์ ์šฉ ๋•Œ๋Š” ๊ธฐํšยท์žฌ์ • ๋ถ€์„œ๊ฐ€ ๋‹จ๊ฐ€๋ฅผ ์ œ์‹œํ•ด์•ผ ํ•˜๋ฉฐ ๋ณธ ๊ฐ’์€ ํ•™์Šต์šฉ์ด๋‹ค.\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 10. ์ „์‹œ ์ˆ˜์†ก ๋ณ‘๋ชฉ ๊ตฌ๊ฐ„ ์‹๋ณ„ ๋ฐ ์šฐํšŒ ์ˆ˜์†ก ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ๋„๊ตฌ ํ†ตํ•ฉ ํŒจํ‚ค์ง€ ์™„์„ฑ\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 3.5์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "์ „์‹œ ์ˆ˜์†ก ๋ณ‘๋ชฉ ๊ตฌ๊ฐ„ ์‹๋ณ„ ๋ฐ ์šฐํšŒ ์ˆ˜์†ก ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ๋„๊ตฌ๋ฅผ ์™„์„ฑํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **๋ฐ์ดํ„ฐ ๊ฐฑ์‹ ๋ถ€ํ„ฐ ์ •ํ•ฉ์„ฑ ๊ฒ€์ฆ, ๋“œ๋ฆฌํ”„ํŠธ ์ ๊ฒ€, ์ž๋™ ์žฌํ•™์Šต, 7์ผ ์†Œ์š” ์˜ˆ์ธก, ์šฐํšŒ ๋ฐฐ๋ถ„, ์Šน์ธ๋Œ€๊ธฐ ๋ถ„๋ฆฌ๊นŒ์ง€ ํ•œ ๋ฒˆ์— ์‹คํ–‰๋˜๋Š” ํ†ตํ•ฉ ํŒŒ์ดํ”„๋ผ์ธ์„ ์™„์„ฑํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **์‹ ๊ทœ 30์ผ ๋ฐ์ดํ„ฐ๋ฅผ ์ถ”๊ฐ€ํ•ด ์ •ํ•ฉ์„ฑ์„ ๊ฒ€์ฆํ•˜๊ณ , ๊ธฐ์กด ๋ชจ๋ธ์˜ ์˜ค์ฐจ๋กœ ๋“œ๋ฆฌํ”„ํŠธ๋ฅผ ํŒ๋‹จํ•œ ๋’ค ์ฑŒ๋ฆฐ์ € ๋ชจ๋ธ์„ ํ•™์Šตํ•ด ๊ฐœ์„ ์ด 3% ์ด์ƒ์ผ ๋•Œ๋งŒ ์Šน๊ฒฉ ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `docs/mov01_operations_guide.md ๋ฐ pipeline/mov01_retrain.py` ํ˜•ํƒœ์˜ ๊ฒฐ๊ณผ๋ฌผ์„ ์™„์„ฑํ•œ๋‹ค.\n", "\n", "### ๐Ÿ” ํ‰๊ฐ€ ๋ฐ ๊ฒ€์ฆ ๊ธฐ์ค€ (PASS ์กฐ๊ฑด)\n", "#### ๋ฏธ์…˜ ํŒ์ •\n", "\n", "**ํ‰๊ฐ€ ๊ตฌ๋ถ„:** ํ•„์ˆ˜\n", "\n", "**ํ•ต์‹ฌ ํ‰๊ฐ€์˜์—ญ:** ์ง€์† ๊ฐ€๋Šฅํ•œ ์šด์˜ ๊ฐ€๋Šฅ์„ฑ ๋ฐ ๊ตญ๋ฐฉ ๊ตฐ์ˆ˜ ์ •๋ณด์ฒด๊ณ„์™€์˜ ์—ฐ๋™ ์šฉ์ด์„ฑ\n", "\n", "#### ๋ฏธ์…˜ PASS ์กฐ๊ฑด\n", "\n", "- ์ •๋Ÿ‰ ๊ธฐ์ค€: ์ž๋™ ์žฌํ•™์Šต ํŒŒ์ดํ”„๋ผ์ธ ์ •์ƒ ์‹คํ–‰, ์ตœ์ข… ๋ฌธ์„œ 100% ์™„๋น„\n", "\n", "\n", "- ์ง„ํ–‰ ๊ธฐ์ค€: ์ตœ์ข… ์ธ๋„๋ฌผ ๊ฒ€์ˆ˜ ๋ฐ ํ”„๋กœ์ ํŠธ ์™„๋ฃŒ ๋ฐœํ‘œ์ž๋ฃŒ ์™„์„ฑ\n", "\n", "\n", "- ๊ฒ€์ฆ ๋ฐฉ๋ฒ•: End-to-End ์ „์ฒด ํŒŒ์ดํ”„๋ผ์ธ ์ž๋™ํ™” ์‹คํ–‰ ๋ฐ ์ตœ์ข… ๊ฒ€์ˆ˜\n", "\n", "\n", "#### ์ฑ„์ ์ž ํŒ์ • ๋ฉ”๋ชจ\n", "\n", "> ๋‹จ์ˆœ ์ฝ”๋“œ ์‹คํ–‰ ์—ฌ๋ถ€๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ถœ๋ ฅ๋œ ์ˆ˜์น˜์™€ ๊ทผ๊ฑฐ ์ž๋ฃŒ๊ฐ€ ๊ตฐ ์‹ค๋ฌด ์š”๊ตฌ์‚ฌํ•ญ์— ๋ถ€ํ•ฉํ•˜๋Š”์ง€ ๋ฉด๋ฐ€ํžˆ ํ™•์ธํ•œ๋‹ค.\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\"\"\"๋ฐ์ดํ„ฐ ๊ฐฑ์‹ , ์ •ํ•ฉ์„ฑ ๊ฒ€์ฆ, ๋“œ๋ฆฌํ”„ํŠธ ์ ๊ฒ€, ์ž๋™ ์žฌํ•™์Šต(์ฑ”ํ”ผ์–ธ/์ฑŒ๋ฆฐ์ €), 7์ผ ์ˆ˜์†ก ์†Œ์š” ์˜ˆ์ธก, ๋ณ‘๋ชฉยท์šฐํšŒ ๋ฐฐ๋ถ„, ์Šน์ธ๋Œ€๊ธฐ ๋ถ„๋ฆฌ๊นŒ์ง€ ํ•œ ๋ฒˆ์— ์‹คํ–‰ํ•˜๋Š” ํ†ตํ•ฉ ํŒŒ์ดํ”„๋ผ์ธ์„ ์™„์„ฑํ•˜๊ณ  ์šด์˜ ๋ฌธ์„œ ์™„๋น„์œจยท์ž์ฒด ์ ๊ฒ€ยท์ธํ„ฐํŽ˜์ด์Šค ๋ช…์„ธยท๋ธŒ๋ฆฌํ•‘ ์š”์•ฝ์„ ์ถœ๋ ฅํ•œ๋‹ค.\"\"\"\n", "import importlib\n", "import json\n", "import sys\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from scipy.optimize import linprog\n", "from sklearn.ensemble import GradientBoostingRegressor\n", "from sklearn.linear_model import Ridge\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ƒ ๋…ธ์„ ๋งยท๊ตฌ๊ฐ„ ํ†ต๊ณผ๋Ÿ‰ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ๋„๋กœยท์ง€๋ช…ยท๋ถ€๋Œ€ ์—†์Œ)\n", "# ๊ฐ€์ƒ ์ถœ๋ฐœ์ง€(SRC)์—์„œ ๊ฐ€์ƒ ๋ชฉ์ ์ง€(DST)๊นŒ์ง€ 3๊ฐœ ์ถ•์„ (AยทBยทC)๊ณผ ์—ฐ๊ฒฐ๋กœ 2๊ฐœ, ๊ตฌ๊ฐ„ 10๊ฐœ. 150์ผ์น˜ ํ”ผํฌ์‹œ๊ฐ„ ํ†ต๊ณผ๋Ÿ‰ยท์ง€์ฒด ์‹œ๊ฐ„์„ ๋งŒ๋“ ๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "SEGS = pd.DataFrame([\n", " (\"A1\", \"SRC\", \"J1\", 12, 130, \"์ผ๋ฐ˜\"), (\"A2\", \"J1\", \"J2\", 8, 70, \"๊ต๋Ÿ‰\"), (\"A3\", \"J2\", \"DST\", 10, 140, \"๊ต์ฐจ๋กœ\"),\n", " (\"B1\", \"SRC\", \"J3\", 15, 100, \"์ผ๋ฐ˜\"), (\"B2\", \"J3\", \"J2\", 9, 80, \"ํ„ฐ๋„\"), (\"B3\", \"J3\", \"DST\", 14, 95, \"์ผ๋ฐ˜\"),\n", " (\"C1\", \"SRC\", \"J4\", 18, 70, \"์ผ๋ฐ˜\"), (\"C2\", \"J4\", \"DST\", 20, 75, \"๊ต๋Ÿ‰\"),\n", " (\"X1\", \"J1\", \"J3\", 7, 50, \"๊ต์ฐจ๋กœ\"), (\"X2\", \"J4\", \"J3\", 10, 60, \"์ผ๋ฐ˜\")],\n", " columns=[\"seg\", \"u\", \"v\", \"km\", \"cap\", \"kind\"]) # cap = ํ”ผํฌ์‹œ๊ฐ„ ํ†ต๊ณผ ์šฉ๋Ÿ‰(๋Œ€/์‹œ, ๊ต์œก์šฉ ๊ฐ€์ •๊ฐ’)\n", "KSPD = {\"์ผ๋ฐ˜\": 55, \"๊ต๋Ÿ‰\": 40, \"ํ„ฐ๋„\": 45, \"๊ต์ฐจ๋กœ\": 45} # ๊ตฌ๊ฐ„ ์œ ํ˜•๋ณ„ ์ž์œ  ์ฃผํ–‰ ์†๋„(km/h, ๊ฐ€์ •๊ฐ’)\n", "SEGS[\"t0\"] = SEGS[\"km\"] / SEGS[\"kind\"].map(KSPD) * 60 # ์ž์œ  ์ฃผํ–‰ ์‹œ๊ฐ„(๋ถ„)\n", "UTIL = dict(A1=.78, A2=.85, A3=.66, B1=.70, B2=.72, B3=.64, C1=.55, C2=.58, X1=.45, X2=.40) # ํ‰์‹œ ์ด์šฉ๋ฅ \n", "\n", "\n", "def bpr(t0, v, cap, a=0.15, b=4.0):\n", " \"\"\"BPR ์ง€์ฒด ํ•จ์ˆ˜(๊ตํ†ต๊ณตํ•™์˜ ํ‘œ์ค€ํ˜•): ํ†ต๊ณผ๋Ÿ‰/์šฉ๋Ÿ‰ ๋น„๊ฐ€ ์ปค์งˆ์ˆ˜๋ก ์ง€์ฒด๊ฐ€ ๊ธ‰์ฆํ•œ๋‹ค. (๋ถ„)\"\"\"\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 10] ์ „์‹œ ์ˆ˜์†ก ๋ณ‘๋ชฉ ๊ตฌ๊ฐ„ ์‹๋ณ„ ๋ฐ ์šฐํšŒ ์ˆ˜์†ก ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ๋„๊ตฌ ํ†ตํ•ฉ ํŒจํ‚ค์ง€ ์™„์„ฑ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def make_data(seed=11, days=150):\n", " \"\"\"๊ตฌ๊ฐ„ x ์ผ์ž ํŒจ๋„. v=์‹ค์ œ ํ”ผํฌ ํ†ต๊ณผ๋Ÿ‰, plan_v=์ˆ˜์†ก๊ณ„ํš ์ˆ˜๋Ÿ‰(๊ธด๊ธ‰ ๋ถˆ์ถœ ๋ฏธ๋ฐ˜์˜), delay=์‹ค์ œ ์ง€์ฒด(๋ถ„).\"\"\"\n", " rng = np.random.default_rng(seed)\n", " d_idx = np.arange(days)\n", " lvl = np.array([1.0, 1.08, 1.04, 1.10, 1.02, 0.82, 0.78])[d_idx % 7] * (1 + 0.0012 * d_idx)\n", " lvl = lvl * np.where((d_idx >= 100) & (d_idx < 110), 1.30, 1.0) # ์ˆ˜์†ก๋Ÿ‰ ํญ์ฆ ๊ธฐ๊ฐ„(๊ฐ€์ƒ)\n", " prev, rows = {s: 0.0 for s in SEGS[\"seg\"]}, []\n", " for d in d_idx:\n", " rain = bool(rng.random() < 0.18)\n", " for r in SEGS.itertuples():\n", " maint, urgent = bool(rng.random() < 0.06), bool(rng.random() < 0.05) # ๋น„๊ณ„ํš ์ •๋น„ / ๊ธด๊ธ‰ ๋ถˆ์ถœ\n", " v = r.cap * UTIL[r.seg] * lvl[d] * (1.25 if urgent else 1.0) * rng.normal(1, 0.05)\n", " cap_eff = r.cap * (0.65 if maint else 1.0) * (0.92 if rain else 1.0)\n", " delay = max(r.t0 * 0.2 * (v / cap_eff) ** 3.5 + 0.25 * prev[r.seg] + rng.normal(0, 0.4), 0.0)\n", " prev[r.seg] = delay\n", " rows.append(dict(day=int(d), dow=int(d % 7), seg=r.seg, kind=r.kind, km=r.km, cap=r.cap, t0=r.t0,\n", " plan_v=v / (1.25 if urgent else 1.0) * rng.normal(1, 0.04), v=v, maint=int(maint), urgent=int(urgent),\n", " rain=int(rain), cap_eff=cap_eff, delay=delay))\n", " df = pd.DataFrame(rows)\n", " df[\"transit\"] = df[\"t0\"] * (1 + 0.08 * df[\"rain\"]) + df[\"delay\"]\n", " return df\n", "\n", "\n", "def mape(y, p):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 10] ์ „์‹œ ์ˆ˜์†ก ๋ณ‘๋ชฉ ๊ตฌ๊ฐ„ ์‹๋ณ„ ๋ฐ ์šฐํšŒ ์ˆ˜์†ก ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ๋„๊ตฌ ํ†ตํ•ฉ ํŒจํ‚ค์ง€ ์™„์„ฑ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def mae(y, p):\n", " return float(np.mean(np.abs(np.asarray(y, float) - np.asarray(p, float))))\n", "# ---------------------------------------------------------------------------\n", "# ---------------------------------------------------------------------------\n", "# ์ง€์ฒด ์˜ˆ์ธก์šฉ ํŒŒ์ƒ ํ”ผ์ฒ˜ ์ƒ์„ฑ(๋‹น์ผ ์˜ค์ „ ์‹œ์ ์— ์•Œ ์ˆ˜ ์žˆ๋Š” ์ •๋ณด๋งŒ ์‚ฌ์šฉ: ๊ณ„ํš ๋ฌผ๋Ÿ‰, ๊ณต์ง€ ์ •๋น„, ๊ธฐ์ƒ ์˜ˆ๋ณด, ์ „์ผยท์ „์ฃผ ์‹ค์ )\n", "# ---------------------------------------------------------------------------\n", "FEATS = [\"plan_v\", \"cap\", \"cap_eff\", \"t0\", \"km\", \"maint\", \"rain\", \"util\", \"util_eff\", \"util_eff4\", \"bpr_base\", \"lag1_delay\", \"lag7_delay\",\n", " \"roll7_v\", \"surge_ratio\", \"is_weekend\", \"dow_sin\", \"dow_cos\", \"k_๊ต๋Ÿ‰\", \"k_ํ„ฐ๋„\", \"k_๊ต์ฐจ๋กœ\"]\n", "\n", "\n", "def add_features(df):\n", " d = df.sort_values([\"seg\", \"day\"]).copy()\n", " g = d.groupby(\"seg\")\n", " d[\"lag1_delay\"] = g[\"delay\"].shift(1)\n", " d[\"lag7_delay\"] = g[\"delay\"].shift(7)\n", " d[\"roll7_v\"] = g[\"v\"].transform(lambda s: s.shift(1).rolling(7).mean())\n", " d[\"util\"] = d[\"plan_v\"] / d[\"cap\"]\n", " d[\"util_eff\"] = d[\"plan_v\"] / d[\"cap_eff\"]\n", " d[\"util_eff4\"] = d[\"util_eff\"] ** 4\n", " d[\"bpr_base\"] = bpr(d[\"t0\"], d[\"plan_v\"], d[\"cap\"])\n", " d[\"surge_ratio\"] = d[\"plan_v\"] / d[\"roll7_v\"]\n", " d[\"is_weekend\"] = (d[\"dow\"] >= 5).astype(int)\n", " d[\"dow_sin\"], d[\"dow_cos\"] = np.sin(2 * np.pi * d[\"dow\"] / 7), np.cos(2 * np.pi * d[\"dow\"] / 7)\n", " for k in (\"๊ต๋Ÿ‰\", \"ํ„ฐ๋„\", \"๊ต์ฐจ๋กœ\"):\n", " d[\"k_\" + k] = (d[\"kind\"] == k).astype(int)\n", " return d.dropna().sort_values([\"day\", \"seg\"]).reset_index(drop=True)\n", "# ---------------------------------------------------------------------------\n", "# ---------------------------------------------------------------------------\n", "# ๋…ธ์„ ๋ง ํ๋ฆ„ ๋„๊ตฌ: ์ตœ๋Œ€์œ ๋Ÿ‰(Edmonds-Karp), ์ฆ๋ถ„ ๋ฐฐ์ •(ํ˜ผ์žก ๋ฐ˜์˜ ์ตœ๋‹จ์‹œ๊ฐ„ ๊ฒฝ๋กœ), ๊ฒฝ๋กœ ์—ด๊ฑฐ\n", "# ---------------------------------------------------------------------------\n", "CAP = dict(zip(SEGS[\"seg\"], SEGS[\"cap\"]))\n", "T0 = dict(zip(SEGS[\"seg\"], SEGS[\"t0\"]))\n", "KM = dict(zip(SEGS[\"seg\"], SEGS[\"km\"]))\n", "ARCS = {r.seg: (r.u, r.v) for r in SEGS.itertuples()}\n", "SRC, DST = \"SRC\", \"DST\"\n", "\n", "\n", "def seg_time(s, flow, cap=None, a=0.2, b=3.5):\n", " \"\"\"๊ตฌ๊ฐ„ ํ†ต๊ณผ์‹œ๊ฐ„(๋ถ„) = ์ž์œ ์ฃผํ–‰ + ํ˜ผ์žก ์ง€์ฒด. cap์„ ์ฃผ๋ฉด ์šฉ๋Ÿ‰์„ ๋Œ€์ฒด(์ •๋น„ยท๊ธฐ์ƒ ๋ฐ˜์˜).\"\"\"\n", " r = flow / (cap or CAP[s])\n", " return T0[s] * (1 + a * r ** b + 6.0 * max(0.0, r - 1.0)) # ์šฉ๋Ÿ‰ ์ดˆ๊ณผ๋ถ„์€ ๋Œ€๊ธฐ์—ด๋กœ ๊ธ‰์ฆ\n", "\n", "\n", "def max_flow(closed=(), capmul=None):\n", " \"\"\"์ตœ๋Œ€์œ ๋Ÿ‰(๋Œ€/์‹œ)๊ณผ ์ตœ์†Œ ์ ˆ๋‹จ ๊ตฌ๊ฐ„(๋ณ‘๋ชฉ). capmul = {๊ตฌ๊ฐ„: ์šฉ๋Ÿ‰ ๋ฐฐ์œจ}.\"\"\"\n", " cap = {s: (0 if s in closed else CAP[s] * (capmul or {}).get(s, 1.0)) for s in CAP}\n", " res = {}\n", " for s, (u, v) in ARCS.items():\n", " res[(u, v)] = res.get((u, v), 0) + cap[s]\n", " res.setdefault((v, u), 0)\n", " flow = 0.0\n", " while True:\n", " prev, q = {SRC: None}, [SRC]\n", " for u in q:\n", " for (a, b), c in res.items():\n", " if a == u and c > 1e-9 and b not in prev:\n", " prev[b] = u; q.append(b)\n", " if DST not in prev:\n", " break\n", " path, x = [], DST\n", " while prev[x]:\n", " path.append((prev[x], x)); x = prev[x]\n", " f = min(res[e] for e in path)\n", " for a, b in path:\n", " res[(a, b)] -= f; res[(b, a)] += f\n", " flow += f\n", " reach = set(prev)\n", " cut = [s for s, (u, v) in ARCS.items() if u in reach and v not in reach and cap[s] > 0]\n", " return flow, cut\n", "\n", "\n", "def all_paths():\n", " \"\"\"SRC -> DST ๋‹จ์ˆœ ๊ฒฝ๋กœ(๊ตฌ๊ฐ„ ์ด๋ฆ„ ํŠœํ”Œ) ์ „์ฒด.\"\"\"\n", " out = []\n", "\n", " def dfs(node, segs, seen):\n", " if node == DST:\n", " out.append(tuple(segs)); return\n", " for s, (u, v) in ARCS.items():\n", " if u == node and v not in seen:\n", " dfs(v, segs + [s], seen | {v})\n", " dfs(SRC, [], {SRC})\n", " return out\n", "\n", "\n", "PATHS = all_paths()\n", "\n", "\n", "def assign(demand, closed=(), capmul=None, steps=40):\n", " \"\"\"์ฆ๋ถ„ ๋ฐฐ์ •: ์ˆ˜์š”๋ฅผ steps์กฐ๊ฐ์œผ๋กœ ๋‚˜๋ˆ , ์ „์ฒด ํ†ต๊ณผ์‹œ๊ฐ„(๋Œ€์ˆ˜ x ์‹œ๊ฐ„) ์ฆ๊ฐ€๊ฐ€ ๊ฐ€์žฅ ์ž‘์€ ๊ฒฝ๋กœ์— ์ฐจ๋ก€๋กœ ์–น๋Š”๋‹ค(์‹œ์Šคํ…œ ์ตœ์  ๊ทผ์‚ฌ).\"\"\"\n", " cap = {s: CAP[s] * (capmul or {}).get(s, 1.0) for s in CAP}\n", " flow, pf = {s: 0.0 for s in CAP}, {}\n", " ok = [p for p in PATHS if not any(s in closed for s in p)]\n", " if not ok:\n", " return None\n", " for _ in range(steps):\n", " chunk = demand / steps\n", " best = min(ok, key=lambda p: sum((flow[s] + chunk) * seg_time(s, flow[s] + chunk, cap[s]) - flow[s] * seg_time(s, flow[s], cap[s]) for s in p))\n", " for s in best:\n", " flow[s] += chunk\n", " pf[best] = pf.get(best, 0) + chunk\n", " tt = {p: sum(seg_time(s, flow[s], cap[s]) for s in p) for p in pf}\n", " avg = sum(pf[p] * tt[p] for p in pf) / demand\n", " return {\"flow\": flow, \"paths\": pf, \"path_min\": tt, \"avg_min\": avg, \"util\": {s: flow[s] / cap[s] for s in cap if cap[s] > 0}}\n", "\n", "\n", "FIXED_SHARE = {(\"A1\", \"A2\", \"A3\"): 0.32, (\"B1\", \"B3\"): 0.40, (\"C1\", \"C2\"): 0.28} # ๊ณผ๊ฑฐ ๊ด€ํ–‰: ์ถ•์„ ๋ณ„ ๊ณ ์ • ๋ฐฐ๋ถ„(๊ฐ€์ƒ)\n", "\n", "\n", "def fixed_assign(demand, closed=(), capmul=None):\n", " \"\"\"๊ณผ๊ฑฐ ๊ด€ํ–‰ ๋ฐฐ๋ถ„: ์ถ•์„ ๋ณ„ ๊ณ ์ • ๋น„์œจ. ๋ง‰ํžŒ ์ถ•์„  ๋ฌผ๋Ÿ‰์€ ์—ด๋ ค ์žˆ๋Š” ์ถ•์„ ์— ๋น„์œจ๋Œ€๋กœ ์žฌ๋ถ„๋ฐฐ.\"\"\"\n", " cap = {s: CAP[s] * (capmul or {}).get(s, 1.0) for s in CAP}\n", " live = {p: w for p, w in FIXED_SHARE.items() if not any(s in closed for s in p)}\n", " if not live:\n", " return None\n", " tot = sum(live.values())\n", " flow = {s: 0.0 for s in CAP}\n", " pf = {p: demand * w / tot for p, w in live.items()}\n", " for p, f in pf.items():\n", " for s in p:\n", " flow[s] += f\n", " tt = {p: sum(seg_time(s, flow[s], cap[s]) for s in p) for p in pf}\n", " return {\"flow\": flow, \"paths\": pf, \"path_min\": tt, \"avg_min\": sum(pf[p] * tt[p] for p in pf) / demand,\n", " \"util\": {s: flow[s] / cap[s] for s in cap if cap[s] > 0}}\n", "\n", "\n", "def lp_assign(demand, closed=(), capmul=None):\n", " \"\"\"์„ ํ˜•๊ณ„ํš(LP): ๊ตฌ๊ฐ„ ์šฉ๋Ÿ‰ ์ƒํ•œ(์ด์šฉ๋ฅ  rho) ์•„๋ž˜์—์„œ ์ž์œ ์ฃผํ–‰์‹œ๊ฐ„ ํ•ฉ์„ ์ตœ์†Œํ™”ํ•˜๋Š” ๊ฒฝ๋กœ๋ณ„ ๋ฌผ๋Ÿ‰. ๋ถˆ๊ฐ€๋Šฅํ•˜๋ฉด rho๋ฅผ 1.0๊นŒ์ง€ ์™„ํ™”.\"\"\"\n", " cap = {s: CAP[s] * (capmul or {}).get(s, 1.0) for s in CAP}\n", " ok = [p for p in PATHS if not any(s in closed for s in p)]\n", " if not ok:\n", " return None\n", " for rho in (0.85, 0.95, 1.0):\n", " r = linprog([sum(T0[s] for s in p) for p in ok], A_ub=[[1.0 if s in p else 0.0 for p in ok] for s in CAP],\n", " b_ub=[rho * cap[s] for s in CAP], A_eq=[[1.0] * len(ok)], b_eq=[demand], bounds=(0, None), method=\"highs\")\n", " if r.status == 0:\n", " break\n", " else:\n", " return assign(demand, closed, capmul) # ์šฉ๋Ÿ‰ ์ดˆ๊ณผ ์ˆ˜์š”: ์ฆ๋ถ„ ๋ฐฐ์ •์œผ๋กœ ๋Œ€์ฒด(๊ณผ๋ถ€ํ•˜ ํ‘œ์‹œ)\n", " flow = {s: sum(r.x[i] for i, p in enumerate(ok) if s in p) for s in CAP}\n", " pf = {p: float(r.x[i]) for i, p in enumerate(ok) if r.x[i] > 1e-6}\n", " tt = {p: sum(seg_time(s, flow[s], cap[s]) for s in p) for p in pf}\n", " return {\"flow\": flow, \"paths\": pf, \"path_min\": tt, \"avg_min\": sum(pf[p] * tt[p] for p in pf) / demand,\n", " \"util\": {s: flow[s] / cap[s] for s in cap if cap[s] > 0}, \"rho\": rho}\n", "\n", "\n", "def cost_of(res, tv=1000, fuel_km=450):\n", " \"\"\"์‹œ๊ฐ„๋‹น ๋น„์šฉ(์›, ๊ต์œก์šฉ ๊ฐ€์ •): ๋Œ€๋‹น ์‹œ๊ฐ„๋น„์šฉ tv์›/๋ถ„ + ์œ ๋ฅ˜๋น„ fuel_km์›/km.\"\"\"\n", " return sum(f * (res[\"path_min\"][p] * tv + sum(KM[s] for s in p) * fuel_km) for p, f in res[\"paths\"].items())\n", "# ---------------------------------------------------------------------------\n", "\n", "libs = [\"numpy\", \"pandas\", \"scipy\", \"sklearn\", \"matplotlib\"]\n", "env_ok = sum(importlib.util.find_spec(m) is not None for m in libs)\n", "print(f\"[0] ํ™˜๊ฒฝ ์ ๊ฒ€: Python {sys.version_info.major}.{sys.version_info.minor}, ํ•„์ˆ˜ ๋ชจ๋“ˆ {env_ok}/{len(libs)}\")\n", "\n", "# ---------------- 1) ๋ฐ์ดํ„ฐ ๊ฐฑ์‹ ยท์ •ํ•ฉ์„ฑ ๊ฒ€์ฆ(๋ฏธ์…˜ 1~2 ์š”์•ฝ) ----------------\n", "raw = make_data(days=180) # day 0~149 ๊ธฐ์กด + day 150~179 ์‹ ๊ทœ ์ž…๋ ฅ๋ถ„\n", "d = add_features(raw)\n", "chk = {\"(๊ตฌ๊ฐ„,์ผ์ž) ์œ ์ผ\": not raw.duplicated([\"seg\", \"day\"]).any(), \"๊ฒฐ์ธก 0\": int(raw.isna().sum().sum()) == 0, \"ํ†ต๊ณผ๋Ÿ‰ ๋ฒ”์œ„\": bool(((raw.v >= 0) & (raw.v <= 3 * raw.cap)).all()),\n", " \"ํ†ต๊ณผ์‹œ๊ฐ„ >= ์ž์œ ์ฃผํ–‰\": bool((raw.transit >= raw.t0 - 1e-9).all())}\n", "print(f\"[1] ์‹ ๊ทœ 30์ผ ํฌํ•จ {len(raw)}ํ–‰ ์ •ํ•ฉ์„ฑ ๊ฒ€์ฆ {sum(chk.values())}/{len(chk)} ํ†ต๊ณผ\")\n", "\n", "# ---------------- 2) ์ž๋™ ์žฌํ•™์Šต: ๋“œ๋ฆฌํ”„ํŠธ ์ ๊ฒ€ -> ์ฑŒ๋ฆฐ์ € ํ•™์Šต -> ์Šน๊ฒฉ ํŒ๋‹จ ----------------\n", "def fit(tr):\n", " return GradientBoostingRegressor(n_estimators=200, max_depth=3, learning_rate=0.08, random_state=0).fit(tr[FEATS], tr[\"delay\"])\n", "\n", "\n", "def evaluate(m, te):\n", " p = te[\"t0\"] * (1 + 0.08 * te[\"rain\"]) + m.predict(te[FEATS])\n", " return mae(te[\"transit\"], p), mape(te[\"transit\"], p)\n", "\n", "\n", "def retrain_pipeline(old_end, new_end, drift_mape=3.0, min_gain=0.03):\n", " old, new = d[d.day < old_end], d[(d.day >= old_end) & (d.day < new_end)]\n", " champ = fit(old)\n", " ch_mae, ch_mape = evaluate(champ, new)\n", " drift = ch_mape > drift_mape\n", " chall = fit(d[d.day < new_end - 10]) # ์ตœ๊ทผ 10์ผ์€ ๊ฒ€์ฆ์šฉ์œผ๋กœ ๋‚จ๊น€\n", " hold = d[(d.day >= new_end - 10) & (d.day < new_end)]\n", " a, b = evaluate(champ, hold)[0], evaluate(chall, hold)[0]\n", " promote = (a - b) / a >= min_gain\n", " return {\"champ_mape\": ch_mape, \"drift\": drift, \"champ_mae\": a, \"chall_mae\": b, \"promote\": promote, \"model\": chall if promote else champ}\n", "\n", "\n", "rt = retrain_pipeline(150, 180)\n", "print(f\"[2] ์žฌํ•™์Šต ํŒŒ์ดํ”„๋ผ์ธ: ๊ธฐ์กด ๋ชจ๋ธ์˜ ์‹ ๊ทœ 30์ผ MAPE {rt['champ_mape']:.2f}% (๋“œ๋ฆฌํ”„ํŠธ ๊ธฐ์ค€ 3.0%: {'์ดˆ๊ณผ' if rt['drift'] else '์ด๋‚ด'})\")\n", "print(f\" ๊ฒ€์ฆ 10์ผ MAE ์ฑ”ํ”ผ์–ธ {rt['champ_mae']:.3f}๋ถ„ vs ์ฑŒ๋ฆฐ์ € {rt['chall_mae']:.3f}๋ถ„ -> {'์ฑŒ๋ฆฐ์ € ์Šน๊ฒฉ' if rt['promote'] else '๊ธฐ์กด ๋ชจ๋ธ ์œ ์ง€'}(๊ฐœ์„  3% ์ด์ƒ์ผ ๋•Œ๋งŒ ์Šน๊ฒฉ)\")\n", "model = rt[\"model\"]\n", "\n", "# ---------------- 3) 7์ผ ์ˆ˜์†ก ์†Œ์š” ์˜ˆ์ธก(Ridge ์š”์ผ+์ถ”์„ธ vs ์ „์ฃผ ๋™์š”์ผ Baseline) ----------------\n", "daily = raw[raw.seg.isin([\"A1\", \"B1\", \"C1\"])].groupby(\"day\")[\"plan_v\"].sum()\n", "\n", "\n", "def design(days):\n", " days = np.asarray(days)\n", " X = np.zeros((len(days), 9))\n", " X[np.arange(len(days)), days % 7] = 1\n", " X[:, 7] = days / 100\n", " X[:, 8] = ((days >= 100) & (days < 110)).astype(float) # ํญ์ฆ ๊ธฐ๊ฐ„(์•Œ๋ ค์ง„ ์‚ฌ๊ฑด) ๋”๋ฏธ\n", " return X\n", "\n", "\n", "tr_days = daily.index[daily.index < 173]\n", "rg = Ridge(alpha=0.1, fit_intercept=False).fit(design(tr_days), daily[tr_days])\n", "test_days = np.arange(173, 180)\n", "f_ridge = rg.predict(design(test_days)); f_naive = daily[test_days - 7].values\n", "print(f\"\\n[3] ์ˆ˜์†ก ์†Œ์š” 7์ผ ์˜ˆ์ธก ๊ฒ€์ฆ(day 173~179): Ridge MAPE {mape(daily[test_days], f_ridge):.1f}% vs ์ „์ฃผ ๋™์š”์ผ {mape(daily[test_days], f_naive):.1f}%\")\n", "rg = Ridge(alpha=0.1, fit_intercept=False).fit(design(daily.index), daily)\n", "nxt = np.arange(180, 187)\n", "dem = rg.predict(design(nxt))\n", "\n", "# ---------------- 4) ์†Œ์š” -> ๋ณ‘๋ชฉยท์šฐํšŒ ๋ฐฐ๋ถ„ -> ์Šน์ธ๋Œ€๊ธฐ ๋ถ„๋ฆฌ ----------------\n", "NOTICE = {183: {\"A2\": 0.65}} # ๊ณต์ง€๋œ ์ •๋น„(๊ฐ€์ •): day 183 A2 ๊ต๋Ÿ‰ ์šฉ๋Ÿ‰ 65%\n", "out = []\n", "for day, D in zip(nxt, dem):\n", " cm = NOTICE.get(int(day))\n", " f, l = fixed_assign(D, (), cm), lp_assign(D, (), cm)\n", " extra = l[\"avg_min\"] - lp_assign(D)[\"avg_min\"]\n", " risky = max(l[\"util\"].values()) > 0.98 or extra >= 3 or day in NOTICE\n", " out.append({\"day\": int(day), \"์˜ˆ์ธก ์†Œ์š”(๋Œ€/์‹œ)\": D, \"๊ณ ์ •(๋ถ„)\": f[\"avg_min\"], \"์šฐํšŒ ๋ฐฐ์ •(๋ถ„)\": l[\"avg_min\"], \"์ ˆ๊ฐ๋ฅ \": 1 - cost_of(l) / cost_of(f),\n", " \"์ตœ๋Œ€ ์ด์šฉ๋ฅ \": max(l[\"util\"].values()), \"์ฒ˜๋ฆฌ\": \"์ž‘์ „๊ตฐ์ˆ˜๊ด€ ์Šน์ธ๋Œ€๊ธฐ(2๋‹จ๊ณ„)\" if risky else \"์ถ”์ฒœ(๊ฒ€ํ†  ํ›„ ์ง‘ํ–‰)\"})\n", "pl = pd.DataFrame(out)\n", "print(\"\\n[4] ํ–ฅํ›„ 7์ผ ์ˆ˜์†ก ์†Œ์š”ยท์šฐํšŒ ๋ฐฐ๋ถ„ ๊ณ„ํš [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ]\")\n", "print(pl.round(2).to_string(index=False))\n", "pend = pl[pl[\"์ฒ˜๋ฆฌ\"].str.contains(\"์Šน์ธ๋Œ€๊ธฐ\")]\n", "print(f\" ์Šน์ธ๋Œ€๊ธฐ {len(pend)}๊ฑด(day {', '.join(map(str, pend['day']))}) - ์ž๋™ ์ง‘ํ–‰ํ•˜์ง€ ์•Š๊ณ  ์ž‘์ „๊ตฐ์ˆ˜๊ด€ ๊ฒฐ์žฌ(๋ฏธ์…˜ 7)๋กœ ๋„˜๊ธด๋‹ค.\")\n", "\n", "# ---------------- 5) ์šด์˜ ๋ฌธ์„œ ์™„๋น„์œจยท์ธํ„ฐํŽ˜์ด์Šค ๋ช…์„ธ ----------------\n", "docs = {\"๋ฐ์ดํ„ฐ ์‚ฌ์ „ยท์Šคํ‚ค๋งˆ(M1)\": 29, \"์ •์ œ ๊ทœ์น™ 10์ข…(M2)\": 10, \"BaselineยทEDA(M3)\": 8, \"๋ณ‘๋ชฉ ์‹๋ณ„ยท์šฐํšŒ(M4)\": 4, \"์šฐํšŒ ์†Œ์š”ยท์œ ๋ฅ˜ ์—”์ง„(M5)\": 4, \"๋Œ€์‹œ๋ณด๋“œยท์„ค๋ช…(M6)\": 6,\n", " \"์Šน์ธ ์ ˆ์ฐจยท๊ถŒํ•œ(M7)\": 4, \"์ŠคํŠธ๋ ˆ์Šค ์‹œํ—˜(M8)\": 4, \"AARยทROI(M9)\": 5, \"์šด์˜ยท์žฌํ•™์Šต ์ ˆ์ฐจ(M10)\": 6, \"์žฅ์•  ๋Œ€์‘\": 5, \"์ธํ„ฐํŽ˜์ด์Šค ๊ฐ€์ด๋“œ\": 4}\n", "done = sum(v > 0 for v in docs.values())\n", "iface = {\"input\": {\"daily_plan\": [\"day:int\", \"seg:str\", \"plan_v:float\", \"maint:int\", \"rain:int\"], \"format\": \"json-lines\"},\n", " \"output\": {\"forecast\": [\"day\", \"demand_vph\", \"paths\", \"avg_min\", \"status\"], \"approval\": \"PENDING_ONLY\"}, \"auth\": \"role-based(๊ฐ€์ƒ)\", \"note\": \"์ƒ์œ„ ๊ตฐ์ˆ˜์ •๋ณด์ฒด๊ณ„๋Š” ๊ฐ€์ƒ ์ธํ„ฐํŽ˜์ด์Šค๋กœ๋งŒ ์ •์˜\"}\n", "print(f\"\\n[5] ์šด์˜ ๋ฌธ์„œ {len(docs)}๊ฐœ ํ•ญ๋ชฉ ์ค‘ ์ž‘์„ฑ ์™„๋ฃŒ {done}๊ฐœ ({done / len(docs):.0%}) / ์ธํ„ฐํŽ˜์ด์Šค ๋ช…์„ธ(JSON):\")\n", "print(\" \", json.dumps(iface, ensure_ascii=False))\n", "\n", "# ---------------- 6) ์ž์ฒด ์ ๊ฒ€ยท๋ธŒ๋ฆฌํ•‘ ----------------\n", "te = d[d.day >= 150]\n", "fin_mae, fin_mape = evaluate(model, te)\n", "base_mape = mape(te.transit, te.t0 + te.bpr_base)\n", "checks = [(\"ํ™˜๊ฒฝ ์ ๊ฒ€ ํ†ต๊ณผ\", env_ok == len(libs)), (\"๋ฐ์ดํ„ฐ ์ •ํ•ฉ์„ฑ ์ „ ํ•ญ๋ชฉ ํ†ต๊ณผ\", all(chk.values())), (\"์žฌํ•™์Šต ํŒŒ์ดํ”„๋ผ์ธ ์ •์ƒ ์‹คํ–‰(์ฑ”ํ”ผ์–ธ/์ฑŒ๋ฆฐ์ € ํŒ์ • ์ถœ๋ ฅ)\", rt[\"champ_mae\"] > 0),\n", " (\"ํ†ต๊ณผ์‹œ๊ฐ„ ์˜ˆ์ธก MAPE 25% ์ดํ•˜\", fin_mape <= 25), (\"๊ธฐ์ค€ BPR ๋Œ€๋น„ MAE 20% ์ด์ƒ ๊ฐœ์„ \", fin_mae <= 0.8 * mae(te.transit, te.t0 + te.bpr_base)),\n", " (\"7์ผ ์†Œ์š” ์˜ˆ์ธก์ด ์ „์ฃผ ๋™์š”์ผ Baseline๋ณด๋‹ค ์ •ํ™•\", mape(daily[test_days], f_ridge) < mape(daily[test_days], f_naive)),\n", " (\"์œ„ํ—˜ยท์ •๋น„ ์šฐํšŒ์•ˆ ์ž๋™ ์ง‘ํ–‰ 0๊ฑด(์Šน์ธ๋Œ€๊ธฐ ๋ถ„๋ฆฌ)\", len(pend) >= 1 and (pl.loc[pl.day == 183, \"์ฒ˜๋ฆฌ\"].str.contains(\"์Šน์ธ๋Œ€๊ธฐ\")).all()), (\"์šด์˜ ๋ฌธ์„œ 100% ์™„๋น„\", done == len(docs))]\n", "print(\"\\n[6] ์ž์ฒด ์ ๊ฒ€\")\n", "for n, ok in checks:\n", " print(f\" [{'PASS' if ok else 'FAIL'}] {n}\")\n", "print(\"\\n[7] ๋ธŒ๋ฆฌํ•‘ ์š”์•ฝ\")\n", "print(f\" - ์‹ ๊ทœ 30์ผ ๋ฐ์ดํ„ฐ๋กœ ์žฌํ•™์Šต ํŒ๋‹จ์„ ์ž๋™ ์ˆ˜ํ–‰ํ–ˆ๋‹ค(๋“œ๋ฆฌํ”„ํŠธ {'๊ฐ์ง€' if rt['drift'] else '๋ฏธ๊ฐ์ง€'}, {'์ฑŒ๋ฆฐ์ € ์Šน๊ฒฉ' if rt['promote'] else '๊ธฐ์กด ๋ชจ๋ธ ์œ ์ง€'}).\")\n", "print(f\" - ํ†ต๊ณผ์‹œ๊ฐ„ MAPE {base_mape:.1f}%(BPR) -> {fin_mape:.1f}%, ํ–ฅํ›„ 7์ผ ์†Œ์š” ์˜ˆ์ธก MAPE {mape(daily[test_days], f_ridge):.1f}%.\")\n", "print(f\" - ํ–ฅํ›„ 7์ผ ์ค‘ ์šฐํšŒ ์ตœ์ ํ™” ํ‰๊ท  ์ ˆ๊ฐ {pl['์ ˆ๊ฐ๋ฅ '].mean():.1%}, ์ •๋น„ ๊ณต์ง€์ผ์€ ์Šน์ธ๋Œ€๊ธฐ๋กœ ๋ถ„๋ฆฌํ–ˆ๋‹ค. ์‹ค์ œ ์šฐํšŒ ํ•˜๋‹ฌ์€ ์ž‘์ „๊ตฐ์ˆ˜๊ด€ ๊ฒฐ์žฌ์™€ ์ง€ํœ˜๊ณ„ํ†ต ๊ฒฐ์‹ฌ์— ๋”ฐ๋ฅธ๋‹ค.\")\n", "\n", "fig, ax = plt.subplots(1, 2, figsize=(12, 4))\n", "ax[0].plot(daily.index, daily.values, label=\"actual\"); ax[0].plot(nxt, dem, \"r--o\", label=\"forecast\"); ax[0].legend(); ax[0].set_title(\"Daily transport demand and 7-day forecast\")\n", "ax[1].bar(pl[\"day\"] - .2, pl[\"๊ณ ์ •(๋ถ„)\"], .4, label=\"fixed\"); ax[1].bar(pl[\"day\"] + .2, pl[\"์šฐํšŒ ๋ฐฐ์ •(๋ถ„)\"], .4, label=\"detour assignment\"); ax[1].legend(); ax[1].set_title(\"Avg transit (min), next 7 days\")\n", "plt.suptitle(\"MOV01 integrated pipeline (synthetic)\"); plt.tight_layout(); plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 10 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[0] ํ™˜๊ฒฝ ์ ๊ฒ€: Python 3.11, ํ•„์ˆ˜ ๋ชจ๋“ˆ 5/5\n", "[1] ์‹ ๊ทœ 30์ผ ํฌํ•จ 1800ํ–‰ ์ •ํ•ฉ์„ฑ ๊ฒ€์ฆ 4/4 ํ†ต๊ณผ\n", "[2] ์žฌํ•™์Šต ํŒŒ์ดํ”„๋ผ์ธ: ๊ธฐ์กด ๋ชจ๋ธ์˜ ์‹ ๊ทœ 30์ผ MAPE 2.93% (๋“œ๋ฆฌํ”„ํŠธ ๊ธฐ์ค€ 3.0%: ์ด๋‚ด)\n", " ๊ฒ€์ฆ 10์ผ MAE ์ฑ”ํ”ผ์–ธ 0.534๋ถ„ vs ์ฑŒ๋ฆฐ์ € 0.535๋ถ„ -> ๊ธฐ์กด ๋ชจ๋ธ ์œ ์ง€(๊ฐœ์„  3% ์ด์ƒ์ผ ๋•Œ๋งŒ ์Šน๊ฒฉ)\n", "\n", "[3] ์ˆ˜์†ก ์†Œ์š” 7์ผ ์˜ˆ์ธก ๊ฒ€์ฆ(day 173~179): Ridge MAPE 3.5% vs ์ „์ฃผ ๋™์š”์ผ 4.7%\n", "\n", "[4] ํ–ฅํ›„ 7์ผ ์ˆ˜์†ก ์†Œ์š”ยท์šฐํšŒ ๋ฐฐ๋ถ„ ๊ณ„ํš [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ]\n", " day ์˜ˆ์ธก ์†Œ์š”(๋Œ€/์‹œ) ๊ณ ์ •(๋ถ„) ์šฐํšŒ ๋ฐฐ์ •(๋ถ„) ์ ˆ๊ฐ๋ฅ  ์ตœ๋Œ€ ์ด์šฉ๋ฅ  ์ฒ˜๋ฆฌ\n", " 180 216.46 42.89 42.43 0.00 0.85 ์ถ”์ฒœ(๊ฒ€ํ†  ํ›„ ์ง‘ํ–‰)\n", " 181 204.40 42.15 41.95 0.00 0.85 ์ถ”์ฒœ(๊ฒ€ํ†  ํ›„ ์ง‘ํ–‰)\n", " 182 258.65 56.35 44.70 0.16 0.95 ์ถ”์ฒœ(๊ฒ€ํ†  ํ›„ ์ง‘ํ–‰)\n", " 183 276.73 91.80 51.32 0.37 1.11 ์ž‘์ „๊ตฐ์ˆ˜๊ด€ ์Šน์ธ๋Œ€๊ธฐ(2๋‹จ๊ณ„)\n", " 184 266.37 61.40 45.29 0.21 0.95 ์ถ”์ฒœ(๊ฒ€ํ†  ํ›„ ์ง‘ํ–‰)\n", " 185 275.57 68.96 45.87 0.27 1.00 ์ž‘์ „๊ตฐ์ˆ˜๊ด€ ์Šน์ธ๋Œ€๊ธฐ(2๋‹จ๊ณ„)\n", " 186 259.89 57.16 44.79 0.17 0.95 ์ถ”์ฒœ(๊ฒ€ํ†  ํ›„ ์ง‘ํ–‰)\n", " ์Šน์ธ๋Œ€๊ธฐ 2๊ฑด(day 183, 185) - ์ž๋™ ์ง‘ํ–‰ํ•˜์ง€ ์•Š๊ณ  ์ž‘์ „๊ตฐ์ˆ˜๊ด€ ๊ฒฐ์žฌ(๋ฏธ์…˜ 7)๋กœ ๋„˜๊ธด๋‹ค.\n", "\n", "[5] ์šด์˜ ๋ฌธ์„œ 12๊ฐœ ํ•ญ๋ชฉ ์ค‘ ์ž‘์„ฑ ์™„๋ฃŒ 12๊ฐœ (100%) / ์ธํ„ฐํŽ˜์ด์Šค ๋ช…์„ธ(JSON):\n", " {\"input\": {\"daily_plan\": [\"day:int\", \"seg:str\", \"plan_v:float\", \"maint:int\", \"rain:int\"], \"format\": \"json-lines\"}, \"output\": {\"forecast\": [\"day\", \"demand_vph\", \"paths\", \"avg_min\", \"status\"], \"approval\": \"PENDING_ONLY\"}, \"auth\": \"role-based(๊ฐ€์ƒ)\", \"note\": \"์ƒ์œ„ ๊ตฐ์ˆ˜์ •๋ณด์ฒด๊ณ„๋Š” ๊ฐ€์ƒ ์ธํ„ฐํŽ˜์ด์Šค๋กœ๋งŒ ์ •์˜\"}\n", "\n", "[6] ์ž์ฒด ์ ๊ฒ€\n", " [PASS] ํ™˜๊ฒฝ ์ ๊ฒ€ ํ†ต๊ณผ\n", " [PASS] ๋ฐ์ดํ„ฐ ์ •ํ•ฉ์„ฑ ์ „ ํ•ญ๋ชฉ ํ†ต๊ณผ\n", " [PASS] ์žฌํ•™์Šต ํŒŒ์ดํ”„๋ผ์ธ ์ •์ƒ ์‹คํ–‰(์ฑ”ํ”ผ์–ธ/์ฑŒ๋ฆฐ์ € ํŒ์ • ์ถœ๋ ฅ)\n", " [PASS] ํ†ต๊ณผ์‹œ๊ฐ„ ์˜ˆ์ธก MAPE 25% ์ดํ•˜\n", " [PASS] ๊ธฐ์ค€ BPR ๋Œ€๋น„ MAE 20% ์ด์ƒ ๊ฐœ์„ \n", " [PASS] 7์ผ ์†Œ์š” ์˜ˆ์ธก์ด ์ „์ฃผ ๋™์š”์ผ Baseline๋ณด๋‹ค ์ •ํ™•\n", " [PASS] ์œ„ํ—˜ยท์ •๋น„ ์šฐํšŒ์•ˆ ์ž๋™ ์ง‘ํ–‰ 0๊ฑด(์Šน์ธ๋Œ€๊ธฐ ๋ถ„๋ฆฌ)\n", " [PASS] ์šด์˜ ๋ฌธ์„œ 100% ์™„๋น„\n", "\n", "[7] ๋ธŒ๋ฆฌํ•‘ ์š”์•ฝ\n", " - ์‹ ๊ทœ 30์ผ ๋ฐ์ดํ„ฐ๋กœ ์žฌํ•™์Šต ํŒ๋‹จ์„ ์ž๋™ ์ˆ˜ํ–‰ํ–ˆ๋‹ค(๋“œ๋ฆฌํ”„ํŠธ ๋ฏธ๊ฐ์ง€, ๊ธฐ์กด ๋ชจ๋ธ ์œ ์ง€).\n", " - ํ†ต๊ณผ์‹œ๊ฐ„ MAPE 9.6%(BPR) -> 2.9%, ํ–ฅํ›„ 7์ผ ์†Œ์š” ์˜ˆ์ธก MAPE 3.5%.\n", " - ํ–ฅํ›„ 7์ผ ์ค‘ ์šฐํšŒ ์ตœ์ ํ™” ํ‰๊ท  ์ ˆ๊ฐ 16.9%, ์ •๋น„ ๊ณต์ง€์ผ์€ ์Šน์ธ๋Œ€๊ธฐ๋กœ ๋ถ„๋ฆฌํ–ˆ๋‹ค. ์‹ค์ œ ์šฐํšŒ ํ•˜๋‹ฌ์€ ์ž‘์ „๊ตฐ์ˆ˜๊ด€ ๊ฒฐ์žฌ์™€ ์ง€ํœ˜๊ณ„ํ†ต ๊ฒฐ์‹ฌ์— ๋”ฐ๋ฅธ๋‹ค.\n", "```\n", "
\n" ] } ], "metadata": { "colab": { "provenance": [] }, "kernelspec": { "display_name": "Python 3", "name": "python3" }, "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 0 }