{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# ๐ŸŽฏ [MNT03] ๊ณ ์žฅ๋ฅ  ๋ถ„์„์„ ํ†ตํ•œ ๋‹ค๋นˆ๋„ ๊ณ ์žฅํ’ˆ๋ชฉ ์‹๋ณ„\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/MNT03/MNT03_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", "์•ผ์ „ ์ •๋น„ ํ˜„์žฅ์—์„œ๋Š” ์–ด๋–ค ๋ถ€ํ’ˆ์ด ์ž์ฃผ ๊ณ ์žฅ ๋‚˜๋Š”์ง€ ์ •ํ™•ํžˆ ์•Œ์•„์•ผ ์ˆ˜๋ฆฌ๋ถ€์†์„ ๋จผ์ € ํ™•๋ณดํ•˜๊ณ  ์˜ˆ๋ฐฉ์ •๋น„ ์ฃผ๊ธฐ๋ฅผ ์กฐ์ •ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ์ฐจ๋Ÿ‰ ์ „์ž์ œ์–ด์žฅ์น˜(ECU)๊ฐ€ ๋‚จ๊ธฐ๋Š” ๊ณ ์žฅ ์ง„๋‹จ ์ฝ”๋“œ(DTC)๋Š” ํ•œ ๋ฒˆ์— ์—ฌ๋Ÿฌ ๊ฐœ๊ฐ€ ๊ฒน์ณ ๊ธฐ๋ก๋˜๊ณ  ๋ถ€ํ’ˆ ๊ฐ„ ๊ณต์œ  ์ฝ”๋“œ๋„ ์žˆ์–ด ์ฒซ ๋ฒˆ์งธ ์ฝ”๋“œ๋งŒ ๋ณด๋ฉด ์›์ธ ๋ถ€ํ’ˆ์„ ์ž์ฃผ ๋†“์นœ๋‹ค. ๋ณธ ํ”„๋กœ์ ํŠธ๋Š” ๊ณ ์žฅยท์ •๋น„ ์ด๋ ฅ์„ ์—ฐ๊ฒฐํ•˜๊ณ  ๊ฐ€๋™์‹œ๊ฐ„ ๋Œ€๋น„ ๊ณ ์žฅ๋ฅ ์„ ๊ณ„์‚ฐํ•ด ๋‹ค๋นˆ๋„ ๊ณ ์žฅํ’ˆ๋ชฉ์„ ์‹๋ณ„ํ•˜๋Š” ๋ฐ์ดํ„ฐ๋ถ„์„ ๊ธฐ๋ณธ๊ธฐ๋ฅผ ์ตํžˆ๋Š” ๊ฒƒ์„ ๋ชฉํ‘œ๋กœ, ๊ต์œก์šฉ ๊ฐ€์ƒ ์ฐจ๋Ÿ‰ 100๋Œ€์˜ 18๊ฐœ์›” DTCยท์ •๋น„ ์ด๋ ฅ์„ ์ฝ”๋“œ๋กœ ๋งŒ๋“ค์–ด ๋ถ„์„ํ•œ๋‹ค. ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ ์ •์˜, ๋กœ๊ทธ ์ •์ œ, ๋‹จ์ผ ์ฝ”๋“œ Baseline๊ณผ ํŒŒ๋ ˆํ† ยท์ƒ๊ด€ ๋ถ„์„, ์—ฐ๊ด€ ๊ทœ์น™ ๊ธฐ๋ฐ˜ ๊ทผ๋ณธ ์›์ธ ์˜ˆ์ธก, ์ •๋น„ ์กฐ์น˜ ์ถ”์ฒœ๊ณผ ์‹ ๋ขฐ๋„ ์ ์ˆ˜, ์„ค๋ช…๊ฐ€๋Šฅ์„ฑ๊ณผ ์ •๋น„๋ณ‘์šฉ ์ง„๋‹จ ํ™”๋ฉด, ์ˆ™๋ จ ์ •๋น„๊ด€ 2์ฐจ ์ง„๋‹จ(HITL) ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ, 1,000๊ฑด ๋™์‹œ ์ฃผ์ž… ๋ถ€ํ•˜ ์‹œํ—˜, AAR ๋ฃฐ ์žฌํŠœ๋‹๊ณผ ํšจ๊ณผ ์‚ฐ์ถœ, ์žฌํ•™์Šต์„ ํฌํ•จํ•œ ํ†ตํ•ฉ ๋„๊ตฌ๊นŒ์ง€ ๋‹จ๊ณ„๋ณ„๋กœ ๊ตฌํ˜„ํ•œ๋‹ค. ์ตœ์ข… ๋ชฉํ‘œ๋Š” ๋‹ค๋นˆ๋„ ๊ณ ์žฅํ’ˆ๋ชฉ์„ ๊ทผ๊ฑฐ์™€ ํ•จ๊ป˜ ์‹๋ณ„ํ•ด ์ˆ˜๋ฆฌ๋ถ€์† ํ™•๋ณด์™€ ์˜ˆ๋ฐฉ์ •๋น„ ์šฐ์„ ์ˆœ์œ„๋ฅผ ์ •ํ•˜๋˜, ๋ณตํ•ฉ ๊ณ ์žฅ์˜ ์ตœ์ข… ํŒ๋‹จ์€ ์ˆ™๋ จ ์ •๋น„๊ด€์ด ๋‚ด๋ฆฌ๋„๋ก ์ง€์›ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.\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. ์ „์ฐจ/์žฅ๊ฐ‘์ฐจ ์ „์ž์ œ์–ด์žฅ์น˜(ECU) ๊ณ ์žฅ ์ง„๋‹จ ์ฝ”๋“œ(DTC) ๋ฐ ์ •๋น„ ๋งค๋‰ด์–ผ ๋ฐ์ดํ„ฐ์…‹ ์ •์˜\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 1.5์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "์ „์ฐจ/์žฅ๊ฐ‘์ฐจ ์ „์ž์ œ์–ด์žฅ์น˜(ECU) ๊ณ ์žฅ ์ง„๋‹จ ์ฝ”๋“œ(DTC) ๋ฐ์ดํ„ฐ์…‹์„ ์ •์˜ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **์ „์ฐจ/์žฅ๊ฐ‘์ฐจ ๊ณ„์—ด ๊ฐ€์ƒ ์ฐจ๋Ÿ‰์˜ ECU ๊ณ ์žฅ ์ง„๋‹จ ์ฝ”๋“œ(DTC) ๋ฐœ์ƒ ๋กœ๊ทธ, ์ •๋น„ ์ด๋ ฅ, ์ •๋น„ ๋งค๋‰ด์–ผ ๋ฐ์ดํ„ฐ์…‹์˜ ์Šคํ‚ค๋งˆ์™€ ๋ฐ์ดํ„ฐ ๋”•์…”๋„ˆ๋ฆฌ, ๊ณ ์žฅ๋ฅ  ๋ถ„์„ ์—…๋ฌด ํ”„๋กœ์„ธ์Šค ๋งต์„ ์ •์˜ํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **DTC ๋ฐœ์ƒ๋ถ€ํ„ฐ ์ •๋น„ ์ด๋ ฅ ์—ฐ๊ฒฐ, ๊ณ ์žฅ๋ฅ  ์ง‘๊ณ„, ๋‹ค๋นˆ๋„ ํ’ˆ๋ชฉ ์‹๋ณ„, ์‚ฌํ›„๊ฒ€ํ† ๊นŒ์ง€ ์—…๋ฌด ํ๋ฆ„์„ ๋‹จ๊ณ„๋ณ„๋กœ ์ •๋ฆฌํ•˜๊ณ , DTC ๋กœ๊ทธยท์ •๋น„ ๊ฑดยท์ฐจ๋Ÿ‰ยท์˜ˆ์ธก ๋Œ€์ƒ ๋ฌถ์Œ์œผ๋กœ ํ”ผ์ฒ˜ 20๊ฐœ ์ด์ƒ ์ •์˜ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `docs/mnt03_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": [ "\"\"\"์ „์ฐจ/์žฅ๊ฐ‘์ฐจ ๊ณ„์—ด ๊ฐ€์ƒ ์ฐจ๋Ÿ‰์˜ ์ „์ž์ œ์–ด์žฅ์น˜(ECU) ๊ณ ์žฅ ์ง„๋‹จ ์ฝ”๋“œ(DTC) ๋ฐœ์ƒ ๋กœ๊ทธ, ์ •๋น„ ์ด๋ ฅ, ์ •๋น„ ๋งค๋‰ด์–ผ ๋ฐ์ดํ„ฐ์…‹์˜ ์Šคํ‚ค๋งˆยท๋ฐ์ดํ„ฐ ๋”•์…”๋„ˆ๋ฆฌ(ํ”ผ์ฒ˜ 20๊ฐœ ์ด์ƒ)์™€ ๊ณ ์žฅ๋ฅ  ๋ถ„์„ ์—…๋ฌด ํ”„๋กœ์„ธ์Šค ๋งต์„ ์ •์˜ํ•˜๊ณ , ๊ฐ€์ƒ ๋ฐ์ดํ„ฐ๋กœ ๊ฒ€์ฆํ•œ๋‹ค.\"\"\"\n", "import json\n", "import re\n", "import numpy as np\n", "import pandas as pd\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ƒ ์ฐจ๋Ÿ‰ 100๋Œ€์˜ 18๊ฐœ์›” ๊ณ ์žฅ ์ง„๋‹จ ์ฝ”๋“œ(DTC)ยท์ •๋น„ ์ด๋ ฅ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ๋ถ€๋Œ€ยท์žฅ๋น„ยท์ •๋น„ ์‹ค์  ์—†์Œ)\n", "# ๋ถ€ํ’ˆ 16์ข…(1000๊ฐ€๋™์‹œ๊ฐ„๋‹น ๊ณ ์žฅ๋ฅ  ์ฐจ๋“ฑ), DTC 28์ข…(๋ถ€ํ’ˆ ๊ณ ์œ  ์ฝ”๋“œ, ์ง ๋ถ€ํ’ˆ ๊ณต์œ  ์ฝ”๋“œ, ๊ณ„ํ†ต ์ฆ์ƒ ์ฝ”๋“œ)\n", "# ๊ณ ์žฅ 1๊ฑด(์ •๋น„ ์ด๋ ฅ)๋งˆ๋‹ค DTC๊ฐ€ 1~5๊ฐœ ๋™์‹œ ๋ฐœ์ƒํ•˜๊ณ , ์—ฐ์‡„ ๊ณ ์žฅ ์ฝ”๋“œ์™€ ์ผ์‹œ ์žก์Œ ์ฝ”๋“œ๊ฐ€ ์„ž์ธ๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "PARTS = [ # (ID, ์ด๋ฆ„, ๊ณ„ํ†ต, 1000๊ฐ€๋™์‹œ๊ฐ„๋‹น ๊ณ ์žฅ๋ฅ , ๊ฐ€์ƒ ๋‹จ๊ฐ€์ง€์ˆ˜, ์ˆ˜๋ฆฌ์‹œ๊ฐ„ h)\n", " (\"P01\", \"์—ฐ๋ฃŒ๋ถ„์‚ฌ๊ธฐ\", \"engine\", 12.0, 3.0, 2.0), (\"P02\", \"ํ„ฐ๋ณด์ฐจ์ €\", \"engine\", 9.0, 8.0, 5.0),\n", " (\"P03\", \"์ถ•์ „์ง€\", \"electric\", 6.5, 4.0, 1.5), (\"P04\", \"๋ฐœ์ „๊ธฐ\", \"electric\", 4.0, 9.0, 3.0),\n", " (\"P05\", \"๋ณ€์†๊ธฐ ์ œ์–ด๋ชจ๋“ˆ\", \"drive\", 3.0, 12.0, 6.0), (\"P06\", \"๋ƒ‰๊ฐ์ˆ˜ ํŽŒํ”„\", \"engine\", 2.2, 3.5, 3.0),\n", " (\"P07\", \"์œ ์•• ํŽŒํ”„\", \"hydraulic\", 1.6, 10.0, 4.0), (\"P08\", \"์†๋„ ์„ผ์„œ\", \"drive\", 1.2, 2.0, 1.5),\n", " (\"P09\", \"ECU ์ปค๋„ฅํ„ฐ\", \"electric\", 0.9, 2.5, 1.5), (\"P10\", \"์—ฐ๋ฃŒ ํ•„ํ„ฐ\", \"engine\", 0.7, 0.8, 0.7),\n", " (\"P11\", \"์ œ๋™ ๋ฐธ๋ธŒ\", \"brake\", 0.5, 6.0, 3.0), (\"P12\", \"์กฐํ–ฅ ์•ก์ถ”์—์ดํ„ฐ\", \"drive\", 0.4, 9.0, 5.0),\n", " (\"P13\", \"๊ณต๊ธฐ ํ•„ํ„ฐ\", \"engine\", 0.3, 0.5, 0.5), (\"P14\", \"ํ†ต์‹  ์ธํ„ฐํŽ˜์ด์Šค ๋ชจ๋“ˆ\", \"electric\", 0.25, 3.0, 1.5),\n", " (\"P15\", \"์œคํ™œ์œ  ์••๋ ฅ ์„ผ์„œ\", \"engine\", 0.2, 1.0, 1.0), (\"P16\", \"ํŒฌ ๊ตฌ๋™ ๋ชจํ„ฐ\", \"cooling\", 0.15, 2.0, 2.0)]\n", "PID = [p[0] for p in PARTS]\n", "PNAME = {p[0]: p[1] for p in PARTS}; PSYS = {p[0]: p[2] for p in PARTS}; RATE = {p[0]: p[3] for p in PARTS}\n", "COST = {p[0]: p[4] for p in PARTS}; REPH = {p[0]: p[5] for p in PARTS}\n", "PAIRS = [(\"P01\", \"P02\"), (\"P03\", \"P04\"), (\"P05\", \"P08\"), (\"P06\", \"P10\"), (\"P09\", \"P14\"), (\"P13\", \"P15\")]\n", "SHARED = {p: f\"S{k + 1:02d}\" for k, pr in enumerate(PAIRS) for p in pr} # ์ง ๋ถ€ํ’ˆ์ด ํ•จ๊ป˜ ์“ฐ๋Š” ๋ชจํ˜ธํ•œ ์ฝ”๋“œ\n", "SPEC = {p: f\"C{p[1:]}1\" for p in PID} # ๋ถ€ํ’ˆ ๊ณ ์œ  ์ฝ”๋“œ\n", "SYS = [\"engine\", \"electric\", \"drive\", \"hydraulic\", \"brake\", \"cooling\"]\n", "SYMP = {s: f\"G{i + 1:02d}\" for i, s in enumerate(SYS)} # ๊ณ„ํ†ต ์ฆ์ƒ ์ฝ”๋“œ\n", "CODES = sorted(set(SPEC.values()) | set(SHARED.values()) | set(SYMP.values()))\n", "CASCADE = {\"P01\": [(\"P10\", 0.20)], \"P02\": [(\"P06\", 0.20)], \"P03\": [(\"P04\", 0.25)], \"P04\": [(\"P09\", 0.25)],\n", " \"P05\": [(\"P08\", 0.30)], \"P06\": [(\"P16\", 0.30)], \"P09\": [(\"P14\", 0.20)], \"P12\": [(\"P07\", 0.20)]}\n", "CRIT_SYS = {\"engine\", \"drive\", \"brake\"} # ๊ธฐ๋™ยท์ œ๋™ ๋ถˆ๋Šฅ ์œ„ํ—˜ ๊ณ„ํ†ต(๊ฒฝ๋ณด ๋Œ€์ƒ)\n", "MONTHS = 18\n", "\n", "\n", "def make_fleet(n=100, seed=0):\n", " rng = np.random.default_rng(seed)\n", " f = pd.DataFrame({\"veh\": [f\"V{i:03d}\" for i in range(n)], \"age\": rng.integers(1, 26, n).astype(float),\n", " \"terrain\": rng.uniform(0, 1, n), \"load\": rng.uniform(0, 1, n), \"pm_gap\": rng.uniform(20, 200, n),\n", " \"humid\": rng.uniform(0, 1, n), \"base_h\": rng.uniform(55, 125, n)})\n", " f[\"mult\"] = np.exp(0.045 * (f[\"age\"] - 13) + 0.9 * (f[\"terrain\"] - .5) + 0.8 * (f[\"load\"] - .5)\n", " + 0.006 * (f[\"pm_gap\"] - 110) + 0.7 * (f[\"humid\"] - .5))\n", " return f\n", "\n", "\n", "def make_data(seed=1, months=MONTHS):\n", " \"\"\"๋ฐ˜ํ™˜: fleet(์ฐจ๋Ÿ‰ ์†์„ฑ), ev(์ •๋น„ ์ด๋ ฅ 1๊ฑด=1ํ–‰: ๋ฐœ์ƒ DTC ๋ชฉ๋ก, ์‹ค์ œ ๊ณ ์žฅ ๋ถ€ํ’ˆ), log(DTC ๋ฐœ์ƒ ๋กœ๊ทธ, ํ–‰=์ฝ”๋“œ 1๊ฑด)\"\"\"\n", " rng = np.random.default_rng(seed)\n", " fleet = make_fleet()\n", " V = len(fleet)\n", " temp = 12 + 14 * np.sin(2 * np.pi * (np.arange(months) - 3) / 12) # ์›”ํ‰๊ท  ๊ธฐ์˜จ(๊ฐ€์ƒ)\n", " hours = fleet[\"base_h\"].values[:, None] * (1 + 0.25 * np.sin(2 * np.pi * np.arange(months) / 12 + 1)) * rng.lognormal(0, .12, (V, months))\n", " fleet[\"hours\"] = hours.sum(axis=1) # ์ฐจ๋Ÿ‰๋ณ„ 18๊ฐœ์›” ์ด ๊ฐ€๋™์‹œ๊ฐ„\n", " fleet.attrs[\"month_h\"] = hours.sum(axis=0) # ์›”๋ณ„ ์ „์ฒด ๊ฐ€๋™์‹œ๊ฐ„(๊ณ ์žฅ๋ฅ  ๋ถ„๋ชจ)\n", " evs = []\n", " for p in PID:\n", " s = np.ones(months)\n", " if p in (\"P03\", \"P04\"): s = np.exp(-0.06 * (temp - 12)) # ํ•œ๋žญ๊ธฐ ์ „๊ธฐ๊ณ„ํ†ต ์ฆ๊ฐ€\n", " if p in (\"P06\", \"P16\"): s = np.exp(0.05 * (temp - 12)) # ํ˜น์„œ๊ธฐ ๋ƒ‰๊ฐ๊ณ„ํ†ต ์ฆ๊ฐ€\n", " cnt = rng.poisson(RATE[p] * hours / 1000 * fleet[\"mult\"].values[:, None] * s[None, :])\n", " for v, m in zip(*np.nonzero(cnt)):\n", " for _ in range(cnt[v, m]):\n", " evs.append((fleet[\"veh\"][v], int(m), m * 720 + rng.uniform(0, 720), p, float(temp[m] + rng.normal(0, 3))))\n", " ev = pd.DataFrame(evs, columns=[\"veh\", \"month\", \"t\", \"part\", \"temp\"]).sort_values(\"t\").reset_index(drop=True)\n", " ev[\"ev_id\"] = [f\"W{i:05d}\" for i in range(len(ev))]\n", " rows, codes = [], []\n", " for e in ev.itertuples():\n", " c, r = [], rng.random(3)\n", " if r[0] < 0.6: c.append(SPEC[e.part])\n", " if e.part in SHARED and r[1] < 0.6: c.append(SHARED[e.part])\n", " if r[2] < 0.5: c.append(SYMP[PSYS[e.part]])\n", " if not c: c.append(SHARED.get(e.part, SPEC[e.part]))\n", " for q, pr in CASCADE.get(e.part, []):\n", " if rng.random() < pr: c.append(SPEC[q])\n", " if rng.random() < 0.12: c.append(SPEC[PID[rng.integers(0, 16)]])\n", " c = list(dict.fromkeys(c))\n", " rng.shuffle(c) # ๋™์‹œ ๋ฐœ์ƒ ์ฝ”๋“œ์˜ ๊ธฐ๋ก ์ˆœ์„œ๋Š” ์ž„์˜\n", " codes.append(c)\n", " for k, cd in enumerate(c):\n", " rows.append((e.ev_id, e.veh, e.t + k * 0.1, cd))\n", " ev[\"codes\"] = codes\n", " log = pd.DataFrame(rows, columns=[\"ev_id\", \"veh\", \"ts\", \"code\"])\n", " return fleet, ev, log\n", "\n", "# 1) ์—…๋ฌด ํ”„๋กœ์„ธ์Šค ๋งต: DTC ๋ฐœ์ƒ๋ถ€ํ„ฐ ๊ณ ์žฅ๋ฅ  ์ง‘๊ณ„ยทํ™˜๋ฅ˜๊นŒ์ง€ (๊ฐ ๋‹จ๊ณ„์—์„œ ์ƒ๊ธฐ๋Š” ๋ฐ์ดํ„ฐ)\n", "PROCESS = [(\"DTC ๋ฐœ์ƒ\", \"์ฐจ๋Ÿ‰ ID, ๋ฐœ์ƒ ์‹œ๊ฐ, DTC ์ฝ”๋“œ, ๊ฐ€๋™์‹œ๊ฐ„ ๊ณ„๊ธฐ๊ฐ’\"), (\"๋กœ๊ทธ ์ˆ˜์ง‘ยท์ •์ œ\", \"์ค‘๋ณต ์ œ๊ฑฐ, ์ฝ”๋“œยท์‹œ๊ฐ ํ‘œ๊ธฐ ํ†ต์ผ\"),\n", " (\"๋™์‹œ ๋ฐœ์ƒ ๋ฌถ์Œ\", \"๊ฐ™์€ ์ •๋น„ ๊ฑด์—์„œ ๋ฐœ์ƒํ•œ DTC ์„ธํŠธ(์ •๊ทœํ™”)\"), (\"๊ณ ์žฅ ์ง„๋‹จ\", \"์ถ”์ • ๊ณ ์žฅ ๋ถ€ํ’ˆ, ์‹ ๋ขฐ๋„\"),\n", " (\"์ •๋น„ ์กฐ์น˜\", \"์ •๋น„ ๋งค๋‰ด์–ผ ์ ˆ์ฐจ, ์ˆ˜๋ฆฌ์‹œ๊ฐ„, ๊ต์ฒด ๋ถ€ํ’ˆ\"), (\"์ •๋น„ ์ด๋ ฅ ์—ฐ๊ฒฐ\", \"์ •๋น„ ๊ฑด ID๋กœ DTC์™€ ์‹ค์ œ ๊ณ ์žฅ ๋ถ€ํ’ˆ ์—ฐ๊ฒฐ\"),\n", " (\"๊ณ ์žฅ๋ฅ  ์ง‘๊ณ„\", \"๋ถ€ํ’ˆ๋ณ„ 1000๊ฐ€๋™์‹œ๊ฐ„๋‹น ๊ณ ์žฅ ๊ฑด์ˆ˜\"), (\"๋‹ค๋นˆ๋„ ํ’ˆ๋ชฉ ์‹๋ณ„\", \"ํŒŒ๋ ˆํ† (ABC) ๋“ฑ๊ธ‰, ๊ด€๋ฆฌ ๋Œ€์ƒ ์„ ์ •\"),\n", " (\"์‚ฌํ›„๊ฒ€ํ† (AAR)\", \"์˜ค์ง„๋‹จ ์‚ฌ๋ก€, ๊ทœ์น™ ์กฐ์ • ์ด๋ ฅ\")]\n", "\n", "# 2) ์Šคํ‚ค๋งˆ(๋ฐ์ดํ„ฐ ๋”•์…”๋„ˆ๋ฆฌ): ๊ทธ๋ฃน / ํ•„๋“œ / ์ž๋ฃŒํ˜• / ๊ทœ์น™ / ์„ค๋ช…\n", "S = {\n", " \"dtc_log\": {\n", " \"ev_id\": (str, {\"pattern\": r\"^W\\d{5}$\"}, \"์ •๋น„ ๊ฑด ID(DTC์™€ ์ •๋น„ ์ด๋ ฅ์„ ์ž‡๋Š” ํ‚ค)\"),\n", " \"veh_id\": (str, {\"pattern\": r\"^V\\d{3}$\"}, \"์ฐจ๋Ÿ‰ ID(๊ฐ€์ƒ)\"),\n", " \"ts\": (float, {\"min\": 0, \"max\": 13000}, \"DTC ๋ฐœ์ƒ ์‹œ๊ฐ(๋ชจ์˜ ์‹œ์ž‘ ํ›„ ๊ฒฝ๊ณผ ์‹œ๊ฐ„, h)\"),\n", " \"code\": (str, {\"enum\": CODES}, \"DTC ์ฝ”๋“œ(๊ณ ์œ ยท๊ณต์œ ยท๊ณ„ํ†ต ์ฆ์ƒ 28์ข…)\"),\n", " \"co_count\": (int, {\"min\": 1, \"max\": 8}, \"๊ฐ™์€ ์ •๋น„ ๊ฑด์—์„œ ๋™์‹œ ๋ฐœ์ƒํ•œ DTC ์ˆ˜\"),\n", " },\n", " \"work_order\": {\n", " \"part_id\": (str, {\"enum\": PID}, \"์‹ค์ œ ๊ณ ์žฅ ๋ถ€ํ’ˆ(์ •๋น„ ์ด๋ ฅ) - ์˜ˆ์ธก๋Œ€์ƒ 1\"),\n", " \"system\": (str, {\"enum\": SYS}, \"๊ณ ์žฅ ๊ณ„ํ†ต\"),\n", " \"repair_h\": (float, {\"min\": 0.1, \"max\": 24}, \"์ˆ˜๋ฆฌ ์†Œ์š”์‹œ๊ฐ„(h)\"),\n", " \"unit_cost\": (float, {\"min\": 0, \"max\": 50}, \"๋ถ€ํ’ˆ ๋‹จ๊ฐ€์ง€์ˆ˜(๊ฐ€์ƒ)\"),\n", " \"unplanned\": (bool, {}, \"๋น„๊ณ„ํšยท๊ธด๊ธ‰ ์ •๋น„ ์—ฌ๋ถ€\"),\n", " \"month\": (int, {\"min\": 0, \"max\": 17}, \"์ •๋น„ ์›”(๋ชจ์˜ ์‹œ์ž‘ ๊ธฐ์ค€)\"),\n", " },\n", " \"vehicle\": {\n", " \"age\": (float, {\"min\": 0, \"max\": 40}, \"์ฐจ๋ น(๋…„)\"),\n", " \"op_hours\": (float, {\"min\": 0, \"max\": 400}, \"์›” ๊ฐ€๋™์‹œ๊ฐ„(h)\"),\n", " \"pm_gap\": (float, {\"min\": 0, \"max\": 500}, \"๋งˆ์ง€๋ง‰ ์˜ˆ๋ฐฉ์ •๋น„ ํ›„ ๊ฒฝ๊ณผ ๊ฐ€๋™์‹œ๊ฐ„(h)\"),\n", " \"terrain\": (float, {\"min\": 0, \"max\": 1}, \"์šด์šฉ ์ง€ํ˜• ๊ฐ€ํ˜น๋„(0~1)\"),\n", " \"load\": (float, {\"min\": 0, \"max\": 1}, \"์šด์šฉ ๋ถ€ํ•˜ ์ˆ˜์ค€(0~1)\"),\n", " \"temp\": (float, {\"min\": -40, \"max\": 60}, \"์ •๋น„ ์‹œ์  ๊ธฐ์˜จ(๋„)\"),\n", " },\n", " \"manual\": {\n", " \"code_ref\": (str, {\"enum\": CODES}, \"๋งค๋‰ด์–ผ ๊ธฐ์ค€ DTC ์ฝ”๋“œ\"),\n", " \"cand_parts\": (list, {}, \"์ฝ”๋“œ๋ณ„ ํ›„๋ณด ๊ณ ์žฅ ๋ถ€ํ’ˆ ๋ชฉ๋ก\"),\n", " \"step_no\": (int, {\"min\": 1, \"max\": 20}, \"์ •๋น„ ์กฐ์น˜ ์ ˆ์ฐจ ๋‹จ๊ณ„ ๋ฒˆํ˜ธ\"),\n", " \"safety_flag\": (bool, {}, \"์•ˆ์ „ ์ฃผ์˜ ํ•ญ๋ชฉ ํฌํ•จ ์—ฌ๋ถ€\"),\n", " },\n", " \"target\": {\n", " \"fail_rate\": (float, {\"min\": 0, \"max\": 100}, \"๋ถ€ํ’ˆ๋ณ„ 1000๊ฐ€๋™์‹œ๊ฐ„๋‹น ๊ณ ์žฅ๋ฅ  - ์˜ˆ์ธก๋Œ€์ƒ 2\"),\n", " \"confidence\": (float, {\"min\": 0, \"max\": 1}, \"์ง„๋‹จ ์ถ”์ฒœ ์‹ ๋ขฐ๋„ ์ ์ˆ˜ - ์˜ˆ์ธก๋Œ€์ƒ 3\"),\n", " },\n", "}\n", "\n", "\n", "def check(name, v, typ, rule):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 1] ์ „์ฐจ/์žฅ๊ฐ‘์ฐจ ์ „์ž์ œ์–ด์žฅ์น˜(ECU) ๊ณ ์žฅ ์ง„๋‹จ ์ฝ”๋“œ(DTC) ๋ฐ ์ •๋น„ ๋งค๋‰ด์–ผ ๋ฐ์ดํ„ฐ์…‹ ์ •์˜ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def validate(rec):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 1] ์ „์ฐจ/์žฅ๊ฐ‘์ฐจ ์ „์ž์ œ์–ด์žฅ์น˜(ECU) ๊ณ ์žฅ ์ง„๋‹จ ์ฝ”๋“œ(DTC) ๋ฐ ์ •๋น„ ๋งค๋‰ด์–ผ ๋ฐ์ดํ„ฐ์…‹ ์ •์˜ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "fleet, ev, log = make_data()\n", "f = fleet.set_index(\"veh\")\n", "recs = []\n", "for e in ev.sample(20, random_state=0).itertuples():\n", " first = log[log[\"ev_id\"] == e.ev_id].iloc[0]\n", " cs = e.codes\n", " recs.append({\n", " \"dtc_log\": {\"ev_id\": e.ev_id, \"veh_id\": e.veh, \"ts\": float(round(first[\"ts\"], 1)), \"code\": cs[0], \"co_count\": len(cs)},\n", " \"work_order\": {\"part_id\": e.part, \"system\": PSYS[e.part], \"repair_h\": float(REPH[e.part]), \"unit_cost\": float(COST[e.part]),\n", " \"unplanned\": bool(len(cs) >= 3), \"month\": int(e.month)},\n", " \"vehicle\": {\"age\": float(f.loc[e.veh, \"age\"]), \"op_hours\": float(round(f.loc[e.veh, \"base_h\"], 1)), \"pm_gap\": float(round(f.loc[e.veh, \"pm_gap\"], 1)),\n", " \"terrain\": float(round(f.loc[e.veh, \"terrain\"], 2)), \"load\": float(round(f.loc[e.veh, \"load\"], 2)), \"temp\": float(round(e.temp, 1))},\n", " \"manual\": {\"code_ref\": cs[0], \"cand_parts\": [p for p in PID if cs[0] in (SPEC[p], SHARED.get(p))] or [e.part], \"step_no\": 1,\n", " \"safety_flag\": PSYS[e.part] in CRIT_SYS},\n", " \"target\": {\"fail_rate\": float(RATE[e.part]), \"confidence\": 0.8}})\n", "ok = sum(not validate(r) for r in recs)\n", "print(f\"๊ฐ€์ƒ ๋ฐ์ดํ„ฐ {len(ev)}๊ฑด(DTC ๋กœ๊ทธ {len(log)}ํ–‰) ์ƒ์„ฑ, ํ‘œ๋ณธ {len(recs)}๊ฑด ์ค‘ ๊ฒ€์ฆ ํ†ต๊ณผ {ok}๊ฑด, ์˜ค๋ฅ˜ {len(recs) - ok}๊ฑด\")\n", "print(f\"์ „์ฒด DTC ๋กœ๊ทธ ์ฝ”๋“œ ํ—ˆ์šฉ๊ฐ’ ๊ฒ€์ฆ: {'ํ†ต๊ณผ' if log['code'].isin(CODES).all() else '์‹คํŒจ'}\")\n", "\n", "# 4) ์˜ค๋ฅ˜ ์ฃผ์ž… ์‹œํ—˜\n", "bad = json.loads(json.dumps(recs[0]))\n", "bad[\"dtc_log\"][\"code\"] = \"C999\" # ์ •์˜๋˜์ง€ ์•Š์€ DTC ์ฝ”๋“œ\n", "bad[\"dtc_log\"][\"veh_id\"] = \"TANK-7\" # ID ํ˜•์‹ ์˜ค๋ฅ˜\n", "bad[\"vehicle\"][\"load\"] = 1.8 # ๋ฒ”์œ„ ์ดˆ๊ณผ\n", "del bad[\"work_order\"][\"repair_h\"] # ํ•„์ˆ˜ ํ•„๋“œ ๋ˆ„๋ฝ\n", "bad[\"target\"][\"confidence\"] = \"๋†’์Œ\" # ์ž๋ฃŒํ˜• ์˜ค๋ฅ˜\n", "print(\"\\n[์˜ค๋ฅ˜ ์ฃผ์ž… ๊ฒฐ๊ณผ]\")\n", "for e in validate(bad):\n", " print(\" -\", e)\n", "\n", "# 5) ํ”„๋กœ์„ธ์Šค ๋งต๊ณผ ๋ฐ์ดํ„ฐ ๋”•์…”๋„ˆ๋ฆฌ\n", "print(\"\\n[์—…๋ฌด ํ”„๋กœ์„ธ์Šค ๋งต]\")\n", "print(\" -> \".join(f\"{i}.{p}\" for i, (p, _) in enumerate(PROCESS, 1)))\n", "for p, d in PROCESS:\n", " print(f\" {p:10s}: {d}\")\n", "rows = [(g, k, t.__name__, d) for g, fl in S.items() for k, (t, r, d) in fl.items()]\n", "dd = pd.DataFrame(rows, columns=[\"๊ทธ๋ฃน\", \"ํ•„๋“œ\", \"์ž๋ฃŒํ˜•\", \"์„ค๋ช…\"])\n", "print(f\"\\n[๋ฐ์ดํ„ฐ ๋”•์…”๋„ˆ๋ฆฌ] ํ”ผ์ฒ˜ {len(dd)}๊ฐœ (๋ชฉํ‘œ 20๊ฐœ ์ด์ƒ: {'๋‹ฌ์„ฑ' if len(dd) >= 20 else '๋ฏธ๋‹ฌ'})\")\n", "print(dd.to_string(index=False))\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 01 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "๊ฐ€์ƒ ๋ฐ์ดํ„ฐ 10681๊ฑด(DTC ๋กœ๊ทธ 22079ํ–‰) ์ƒ์„ฑ, ํ‘œ๋ณธ 20๊ฑด ์ค‘ ๊ฒ€์ฆ ํ†ต๊ณผ 20๊ฑด, ์˜ค๋ฅ˜ 0๊ฑด\n", "์ „์ฒด DTC ๋กœ๊ทธ ์ฝ”๋“œ ํ—ˆ์šฉ๊ฐ’ ๊ฒ€์ฆ: ํ†ต๊ณผ\n", "\n", "[์˜ค๋ฅ˜ ์ฃผ์ž… ๊ฒฐ๊ณผ]\n", " - dtc_log.veh_id: ํ˜•์‹ ๋ถˆ์ผ์น˜ 'TANK-7'\n", " - dtc_log.code: ํ—ˆ์šฉ๋˜์ง€ ์•Š์€ ๊ฐ’ 'C999'\n", " - work_order.repair_h: ํ•„์ˆ˜ ํ•„๋“œ ๋ˆ„๋ฝ\n", " - vehicle.load: ์ตœ๋Œ€๊ฐ’ 1 ์ดˆ๊ณผ\n", " - target.confidence: ์ž๋ฃŒํ˜• ์˜ค๋ฅ˜(str)\n", "\n", "[์—…๋ฌด ํ”„๋กœ์„ธ์Šค ๋งต]\n", "1.DTC ๋ฐœ์ƒ -> 2.๋กœ๊ทธ ์ˆ˜์ง‘ยท์ •์ œ -> 3.๋™์‹œ ๋ฐœ์ƒ ๋ฌถ์Œ -> 4.๊ณ ์žฅ ์ง„๋‹จ -> 5.์ •๋น„ ์กฐ์น˜ -> 6.์ •๋น„ ์ด๋ ฅ ์—ฐ๊ฒฐ -> 7.๊ณ ์žฅ๋ฅ  ์ง‘๊ณ„ -> 8.๋‹ค๋นˆ๋„ ํ’ˆ๋ชฉ ์‹๋ณ„ -> 9.์‚ฌํ›„๊ฒ€ํ† (AAR)\n", " DTC ๋ฐœ์ƒ : ์ฐจ๋Ÿ‰ ID, ๋ฐœ์ƒ ์‹œ๊ฐ, DTC ์ฝ”๋“œ, ๊ฐ€๋™์‹œ๊ฐ„ ๊ณ„๊ธฐ๊ฐ’\n", " ๋กœ๊ทธ ์ˆ˜์ง‘ยท์ •์ œ : ์ค‘๋ณต ์ œ๊ฑฐ, ์ฝ”๋“œยท์‹œ๊ฐ ํ‘œ๊ธฐ ํ†ต์ผ\n", " ๋™์‹œ ๋ฐœ์ƒ ๋ฌถ์Œ : ๊ฐ™์€ ์ •๋น„ ๊ฑด์—์„œ ๋ฐœ์ƒํ•œ DTC ์„ธํŠธ(์ •๊ทœํ™”)\n", " ๊ณ ์žฅ ์ง„๋‹จ : ์ถ”์ • ๊ณ ์žฅ ๋ถ€ํ’ˆ, ์‹ ๋ขฐ๋„\n", " ์ •๋น„ ์กฐ์น˜ : ์ •๋น„ ๋งค๋‰ด์–ผ ์ ˆ์ฐจ, ์ˆ˜๋ฆฌ์‹œ๊ฐ„, ๊ต์ฒด ๋ถ€ํ’ˆ\n", " ์ •๋น„ ์ด๋ ฅ ์—ฐ๊ฒฐ : ์ •๋น„ ๊ฑด ID๋กœ DTC์™€ ์‹ค์ œ ๊ณ ์žฅ ๋ถ€ํ’ˆ ์—ฐ๊ฒฐ\n", " ๊ณ ์žฅ๋ฅ  ์ง‘๊ณ„ : ๋ถ€ํ’ˆ๋ณ„ 1000๊ฐ€๋™์‹œ๊ฐ„๋‹น ๊ณ ์žฅ ๊ฑด์ˆ˜\n", " ๋‹ค๋นˆ๋„ ํ’ˆ๋ชฉ ์‹๋ณ„ : ํŒŒ๋ ˆํ† (ABC) ๋“ฑ๊ธ‰, ๊ด€๋ฆฌ ๋Œ€์ƒ ์„ ์ •\n", " ์‚ฌํ›„๊ฒ€ํ† (AAR) : ์˜ค์ง„๋‹จ ์‚ฌ๋ก€, ๊ทœ์น™ ์กฐ์ • ์ด๋ ฅ\n", "\n", "[๋ฐ์ดํ„ฐ ๋”•์…”๋„ˆ๋ฆฌ] ํ”ผ์ฒ˜ 23๊ฐœ (๋ชฉํ‘œ 20๊ฐœ ์ด์ƒ: ๋‹ฌ์„ฑ)\n", " ๊ทธ๋ฃน ํ•„๋“œ ์ž๋ฃŒํ˜• ์„ค๋ช…\n", " dtc_log ev_id str ์ •๋น„ ๊ฑด ID(DTC์™€ ์ •๋น„ ์ด๋ ฅ์„ ์ž‡๋Š” ํ‚ค)\n", " dtc_log veh_id str ์ฐจ๋Ÿ‰ ID(๊ฐ€์ƒ)\n", " dtc_log ts float DTC ๋ฐœ์ƒ ์‹œ๊ฐ(๋ชจ์˜ ์‹œ์ž‘ ํ›„ ๊ฒฝ๊ณผ ์‹œ๊ฐ„, h)\n", " dtc_log code str DTC ์ฝ”๋“œ(๊ณ ์œ ยท๊ณต์œ ยท๊ณ„ํ†ต ์ฆ์ƒ 28์ข…)\n", " dtc_log co_count int ๊ฐ™์€ ์ •๋น„ ๊ฑด์—์„œ ๋™์‹œ ๋ฐœ์ƒํ•œ DTC ์ˆ˜\n", "work_order part_id str ์‹ค์ œ ๊ณ ์žฅ ๋ถ€ํ’ˆ(์ •๋น„ ์ด๋ ฅ) - ์˜ˆ์ธก๋Œ€์ƒ 1\n", "work_order system str ๊ณ ์žฅ ๊ณ„ํ†ต\n", "work_order repair_h float ์ˆ˜๋ฆฌ ์†Œ์š”์‹œ๊ฐ„(h)\n", "work_order unit_cost float ๋ถ€ํ’ˆ ๋‹จ๊ฐ€์ง€์ˆ˜(๊ฐ€์ƒ)\n", "work_order unplanned bool ๋น„๊ณ„ํšยท๊ธด๊ธ‰ ์ •๋น„ ์—ฌ๋ถ€\n", "work_order month int ์ •๋น„ ์›”(๋ชจ์˜ ์‹œ์ž‘ ๊ธฐ์ค€)\n", " vehicle age float ์ฐจ๋ น(๋…„)\n", " vehicle op_hours float ์›” ๊ฐ€๋™์‹œ๊ฐ„(h)\n", " vehicle pm_gap float ๋งˆ์ง€๋ง‰ ์˜ˆ๋ฐฉ์ •๋น„ ํ›„ ๊ฒฝ๊ณผ ๊ฐ€๋™์‹œ๊ฐ„(h)\n", " vehicle terrain float ์šด์šฉ ์ง€ํ˜• ๊ฐ€ํ˜น๋„(0~1)\n", " vehicle load float ์šด์šฉ ๋ถ€ํ•˜ ์ˆ˜์ค€(0~1)\n", "... (์ดํ•˜ ์ƒ๋žต)\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 02. DTC ์‹œ๊ณ„์—ด ๋ฐœ์ƒ ๋กœ๊ทธ ์ •์ œ, ๋™์‹œ ๋ฐœ์ƒ ์ฝ”๋“œ ์ •๊ทœํ™” ์ „์ฒ˜๋ฆฌ ๋ชจ๋“ˆ ๊ตฌํ˜„\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 2.0์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "DTC ๋ฐœ์ƒ ๋กœ๊ทธ ์ •์ œ ๋ฐ ๋™์‹œ ๋ฐœ์ƒ ์ฝ”๋“œ ์ •๊ทœํ™” ์ „์ฒ˜๋ฆฌ ๋ชจ๋“ˆ์„ ๊ตฌํ˜„ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **DTC ์‹œ๊ณ„์—ด ๋ฐœ์ƒ ๋กœ๊ทธ๋ฅผ ์ •์ œํ•˜๊ณ  ๊ฐ™์€ ์ •๋น„ ๊ฑด์—์„œ ๋™์‹œ ๋ฐœ์ƒํ•œ ์ฝ”๋“œ๋ฅผ ์ •๊ทœํ™”ํ•˜๋Š” ์ „์ฒ˜๋ฆฌ ๋ชจ๋“ˆ์„ ๊ตฌํ˜„ํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **์ค‘๋ณต ํ–‰ ์ œ๊ฑฐ, ์ฐจ๋Ÿ‰ IDยทDTC ์ฝ”๋“œยท์‹œ๊ฐ ํ‘œ๊ธฐ ํ†ต์ผ, ๊ฐ€๋™์‹œ๊ฐ„ ๋‹จ์œ„ ํ†ต์ผ, ์Œ์ˆ˜ ๊ฐ€๋™์‹œ๊ฐ„ ๊ฒฐ์ธก ์ฒ˜๋ฆฌ, ๋ฏธ๋“ฑ๋ก ์ฐจ๋Ÿ‰ยท๋ฏธ์ •์˜ ์ฝ”๋“œ ๊ฒฉ๋ฆฌ, KNN ๊ฒฐ์ธก ๋ณด์ •, ๋™์‹œ ๋ฐœ์ƒ ์ฝ”๋“œ์˜ ์ •๋น„ ๊ฑด ๋ฌถ์Œยท์ •๋ ฌ ๋“ฑ ์ •์ œ ๊ทœ์น™ 10์ข…์„ ์ ์šฉ ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `src/etl/mnt03_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": [ "\"\"\"ํ‘œ๊ธฐ ๋ถˆ์ผ์น˜ยท์ค‘๋ณตยท๋‹จ์œ„ ํ˜ผ์žฌยท๊ฒฐ์ธกยท๋ฏธ๋“ฑ๋ก ์ฝ”๋“œ๊ฐ€ ์„ž์ธ ๊ฐ€์ƒ DTC ๋ฐœ์ƒ ๋กœ๊ทธ์— ์ •์ œ ๊ทœ์น™ 10์ข…์„ ์ ์šฉํ•˜๊ณ , ๊ฐ™์€ ์‹œ๊ฐ„๋Œ€์— ํ•จ๊ป˜ ๋ฐœ์ƒํ•œ DTC๋ฅผ ์ •๋น„ ๊ฑด ๋‹จ์œ„๋กœ ๋ฌถ์–ด ์ •๊ทœํ™”(์ค‘๋ณต ์ œ๊ฑฐยท์ •๋ ฌ)ํ•œ ๋งˆ์Šคํ„ฐ ๋ฐ์ดํ„ฐ๋ฅผ ๋งŒ๋“ ๋‹ค. ๊ธด๊ธ‰ยท๋น„๊ณ„ํš ์ •๋น„๋Š” ๋ณด์กดํ•˜๋ฉฐ ์ •ํ•ฉ์„ฑ ๊ฒ€์ฆ ํ†ต๊ณผ์œจ๊ณผ ์ •์ œ ์ „ํ›„ ํ†ต๊ณ„๋ฅผ ํ™•์ธํ•œ๋‹ค.\"\"\"\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from sklearn.impute import KNNImputer\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ƒ ์ฐจ๋Ÿ‰ 100๋Œ€์˜ 18๊ฐœ์›” ๊ณ ์žฅ ์ง„๋‹จ ์ฝ”๋“œ(DTC)ยท์ •๋น„ ์ด๋ ฅ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ๋ถ€๋Œ€ยท์žฅ๋น„ยท์ •๋น„ ์‹ค์  ์—†์Œ)\n", "# ๋ถ€ํ’ˆ 16์ข…(1000๊ฐ€๋™์‹œ๊ฐ„๋‹น ๊ณ ์žฅ๋ฅ  ์ฐจ๋“ฑ), DTC 28์ข…(๋ถ€ํ’ˆ ๊ณ ์œ  ์ฝ”๋“œ, ์ง ๋ถ€ํ’ˆ ๊ณต์œ  ์ฝ”๋“œ, ๊ณ„ํ†ต ์ฆ์ƒ ์ฝ”๋“œ)\n", "# ๊ณ ์žฅ 1๊ฑด(์ •๋น„ ์ด๋ ฅ)๋งˆ๋‹ค DTC๊ฐ€ 1~5๊ฐœ ๋™์‹œ ๋ฐœ์ƒํ•˜๊ณ , ์—ฐ์‡„ ๊ณ ์žฅ ์ฝ”๋“œ์™€ ์ผ์‹œ ์žก์Œ ์ฝ”๋“œ๊ฐ€ ์„ž์ธ๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "PARTS = [ # (ID, ์ด๋ฆ„, ๊ณ„ํ†ต, 1000๊ฐ€๋™์‹œ๊ฐ„๋‹น ๊ณ ์žฅ๋ฅ , ๊ฐ€์ƒ ๋‹จ๊ฐ€์ง€์ˆ˜, ์ˆ˜๋ฆฌ์‹œ๊ฐ„ h)\n", " (\"P01\", \"์—ฐ๋ฃŒ๋ถ„์‚ฌ๊ธฐ\", \"engine\", 12.0, 3.0, 2.0), (\"P02\", \"ํ„ฐ๋ณด์ฐจ์ €\", \"engine\", 9.0, 8.0, 5.0),\n", " (\"P03\", \"์ถ•์ „์ง€\", \"electric\", 6.5, 4.0, 1.5), (\"P04\", \"๋ฐœ์ „๊ธฐ\", \"electric\", 4.0, 9.0, 3.0),\n", " (\"P05\", \"๋ณ€์†๊ธฐ ์ œ์–ด๋ชจ๋“ˆ\", \"drive\", 3.0, 12.0, 6.0), (\"P06\", \"๋ƒ‰๊ฐ์ˆ˜ ํŽŒํ”„\", \"engine\", 2.2, 3.5, 3.0),\n", " (\"P07\", \"์œ ์•• ํŽŒํ”„\", \"hydraulic\", 1.6, 10.0, 4.0), (\"P08\", \"์†๋„ ์„ผ์„œ\", \"drive\", 1.2, 2.0, 1.5),\n", " (\"P09\", \"ECU ์ปค๋„ฅํ„ฐ\", \"electric\", 0.9, 2.5, 1.5), (\"P10\", \"์—ฐ๋ฃŒ ํ•„ํ„ฐ\", \"engine\", 0.7, 0.8, 0.7),\n", " (\"P11\", \"์ œ๋™ ๋ฐธ๋ธŒ\", \"brake\", 0.5, 6.0, 3.0), (\"P12\", \"์กฐํ–ฅ ์•ก์ถ”์—์ดํ„ฐ\", \"drive\", 0.4, 9.0, 5.0),\n", " (\"P13\", \"๊ณต๊ธฐ ํ•„ํ„ฐ\", \"engine\", 0.3, 0.5, 0.5), (\"P14\", \"ํ†ต์‹  ์ธํ„ฐํŽ˜์ด์Šค ๋ชจ๋“ˆ\", \"electric\", 0.25, 3.0, 1.5),\n", " (\"P15\", \"์œคํ™œ์œ  ์••๋ ฅ ์„ผ์„œ\", \"engine\", 0.2, 1.0, 1.0), (\"P16\", \"ํŒฌ ๊ตฌ๋™ ๋ชจํ„ฐ\", \"cooling\", 0.15, 2.0, 2.0)]\n", "PID = [p[0] for p in PARTS]\n", "PNAME = {p[0]: p[1] for p in PARTS}; PSYS = {p[0]: p[2] for p in PARTS}; RATE = {p[0]: p[3] for p in PARTS}\n", "COST = {p[0]: p[4] for p in PARTS}; REPH = {p[0]: p[5] for p in PARTS}\n", "PAIRS = [(\"P01\", \"P02\"), (\"P03\", \"P04\"), (\"P05\", \"P08\"), (\"P06\", \"P10\"), (\"P09\", \"P14\"), (\"P13\", \"P15\")]\n", "SHARED = {p: f\"S{k + 1:02d}\" for k, pr in enumerate(PAIRS) for p in pr} # ์ง ๋ถ€ํ’ˆ์ด ํ•จ๊ป˜ ์“ฐ๋Š” ๋ชจํ˜ธํ•œ ์ฝ”๋“œ\n", "SPEC = {p: f\"C{p[1:]}1\" for p in PID} # ๋ถ€ํ’ˆ ๊ณ ์œ  ์ฝ”๋“œ\n", "SYS = [\"engine\", \"electric\", \"drive\", \"hydraulic\", \"brake\", \"cooling\"]\n", "SYMP = {s: f\"G{i + 1:02d}\" for i, s in enumerate(SYS)} # ๊ณ„ํ†ต ์ฆ์ƒ ์ฝ”๋“œ\n", "CODES = sorted(set(SPEC.values()) | set(SHARED.values()) | set(SYMP.values()))\n", "CASCADE = {\"P01\": [(\"P10\", 0.20)], \"P02\": [(\"P06\", 0.20)], \"P03\": [(\"P04\", 0.25)], \"P04\": [(\"P09\", 0.25)],\n", " \"P05\": [(\"P08\", 0.30)], \"P06\": [(\"P16\", 0.30)], \"P09\": [(\"P14\", 0.20)], \"P12\": [(\"P07\", 0.20)]}\n", "CRIT_SYS = {\"engine\", \"drive\", \"brake\"} # ๊ธฐ๋™ยท์ œ๋™ ๋ถˆ๋Šฅ ์œ„ํ—˜ ๊ณ„ํ†ต(๊ฒฝ๋ณด ๋Œ€์ƒ)\n", "MONTHS = 18\n", "\n", "\n", "def make_fleet(n=100, seed=0):\n", " rng = np.random.default_rng(seed)\n", " f = pd.DataFrame({\"veh\": [f\"V{i:03d}\" for i in range(n)], \"age\": rng.integers(1, 26, n).astype(float),\n", " \"terrain\": rng.uniform(0, 1, n), \"load\": rng.uniform(0, 1, n), \"pm_gap\": rng.uniform(20, 200, n),\n", " \"humid\": rng.uniform(0, 1, n), \"base_h\": rng.uniform(55, 125, n)})\n", " f[\"mult\"] = np.exp(0.045 * (f[\"age\"] - 13) + 0.9 * (f[\"terrain\"] - .5) + 0.8 * (f[\"load\"] - .5)\n", " + 0.006 * (f[\"pm_gap\"] - 110) + 0.7 * (f[\"humid\"] - .5))\n", " return f\n", "\n", "\n", "def make_data(seed=1, months=MONTHS):\n", " \"\"\"๋ฐ˜ํ™˜: fleet(์ฐจ๋Ÿ‰ ์†์„ฑ), ev(์ •๋น„ ์ด๋ ฅ 1๊ฑด=1ํ–‰: ๋ฐœ์ƒ DTC ๋ชฉ๋ก, ์‹ค์ œ ๊ณ ์žฅ ๋ถ€ํ’ˆ), log(DTC ๋ฐœ์ƒ ๋กœ๊ทธ, ํ–‰=์ฝ”๋“œ 1๊ฑด)\"\"\"\n", " rng = np.random.default_rng(seed)\n", " fleet = make_fleet()\n", " V = len(fleet)\n", " temp = 12 + 14 * np.sin(2 * np.pi * (np.arange(months) - 3) / 12) # ์›”ํ‰๊ท  ๊ธฐ์˜จ(๊ฐ€์ƒ)\n", " hours = fleet[\"base_h\"].values[:, None] * (1 + 0.25 * np.sin(2 * np.pi * np.arange(months) / 12 + 1)) * rng.lognormal(0, .12, (V, months))\n", " fleet[\"hours\"] = hours.sum(axis=1) # ์ฐจ๋Ÿ‰๋ณ„ 18๊ฐœ์›” ์ด ๊ฐ€๋™์‹œ๊ฐ„\n", " fleet.attrs[\"month_h\"] = hours.sum(axis=0) # ์›”๋ณ„ ์ „์ฒด ๊ฐ€๋™์‹œ๊ฐ„(๊ณ ์žฅ๋ฅ  ๋ถ„๋ชจ)\n", " evs = []\n", " for p in PID:\n", " s = np.ones(months)\n", " if p in (\"P03\", \"P04\"): s = np.exp(-0.06 * (temp - 12)) # ํ•œ๋žญ๊ธฐ ์ „๊ธฐ๊ณ„ํ†ต ์ฆ๊ฐ€\n", " if p in (\"P06\", \"P16\"): s = np.exp(0.05 * (temp - 12)) # ํ˜น์„œ๊ธฐ ๋ƒ‰๊ฐ๊ณ„ํ†ต ์ฆ๊ฐ€\n", " cnt = rng.poisson(RATE[p] * hours / 1000 * fleet[\"mult\"].values[:, None] * s[None, :])\n", " for v, m in zip(*np.nonzero(cnt)):\n", " for _ in range(cnt[v, m]):\n", " evs.append((fleet[\"veh\"][v], int(m), m * 720 + rng.uniform(0, 720), p, float(temp[m] + rng.normal(0, 3))))\n", " ev = pd.DataFrame(evs, columns=[\"veh\", \"month\", \"t\", \"part\", \"temp\"]).sort_values(\"t\").reset_index(drop=True)\n", " ev[\"ev_id\"] = [f\"W{i:05d}\" for i in range(len(ev))]\n", " rows, codes = [], []\n", " for e in ev.itertuples():\n", " c, r = [], rng.random(3)\n", " if r[0] < 0.6: c.append(SPEC[e.part])\n", " if e.part in SHARED and r[1] < 0.6: c.append(SHARED[e.part])\n", " if r[2] < 0.5: c.append(SYMP[PSYS[e.part]])\n", " if not c: c.append(SHARED.get(e.part, SPEC[e.part]))\n", " for q, pr in CASCADE.get(e.part, []):\n", " if rng.random() < pr: c.append(SPEC[q])\n", " if rng.random() < 0.12: c.append(SPEC[PID[rng.integers(0, 16)]])\n", " c = list(dict.fromkeys(c))\n", " rng.shuffle(c) # ๋™์‹œ ๋ฐœ์ƒ ์ฝ”๋“œ์˜ ๊ธฐ๋ก ์ˆœ์„œ๋Š” ์ž„์˜\n", " codes.append(c)\n", " for k, cd in enumerate(c):\n", " rows.append((e.ev_id, e.veh, e.t + k * 0.1, cd))\n", " ev[\"codes\"] = codes\n", " log = pd.DataFrame(rows, columns=[\"ev_id\", \"veh\", \"ts\", \"code\"])\n", " return fleet, ev, log\n", "\n", "# 1) [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ์›์ฒœ ๋กœ๊ทธ: ์ƒ์„ฑ ๋กœ๊ทธ์— ์ผ๋ถ€๋Ÿฌ ์˜ค๋ฅ˜๋ฅผ ์„ž๋Š”๋‹ค\n", "fleet, ev, log = make_data()\n", "rng = np.random.default_rng(2)\n", "clean_n = len(log)\n", "start = pd.Timestamp(\"2025-01-01\")\n", "raw = pd.DataFrame({\"veh\": log[\"veh\"].values, \"ts\": [(start + pd.Timedelta(hours=t)).strftime(\"%Y-%m-%d %H:%M\") for t in log[\"ts\"]],\n", " \"code\": log[\"code\"].values, \"emergency\": False})\n", "vn = raw[\"veh\"].str[1:].astype(int).values\n", "raw[\"op_h\"] = (vn * 37 % 900) + log[\"ts\"].values * 0.15 # ์ฐจ๋Ÿ‰๋ณ„ ๋ˆ„์  ๊ฐ€๋™์‹œ๊ฐ„ ๊ณ„๊ธฐ๊ฐ’(๊ฐ€์ƒ)\n", "raw[\"op_unit\"] = \"h\"\n", "raw[\"op_h\"] = raw[\"op_h\"].round(1)\n", "idx = rng.choice(len(raw), 2600, replace=False)\n", "a, b, c, d_, e, f, g, h_, k = np.split(idx, [400, 900, 1500, 1800, 1880, 1960, 2040, 2300])\n", "raw.loc[a, \"veh\"] = rng.choice([\"v\", \" V\"], len(a)) + raw.loc[a, \"veh\"].str[1:] # ์ฐจ๋Ÿ‰ ID ํ‘œ๊ธฐ ๋ถˆ์ผ์น˜\n", "raw.loc[b, \"code\"] = [x.lower() if i % 2 else x[0] + \"-\" + x[1:] + \" \" for i, x in enumerate(raw.loc[b, \"code\"])] # ์ฝ”๋“œ ํ‘œ๊ธฐ ๋ถˆ์ผ์น˜\n", "raw.loc[c, \"ts\"] = pd.to_datetime(raw.loc[c, \"ts\"]).dt.strftime(\"%Y/%m/%d %H:%M:%S\") # ์‹œ๊ฐ ํ˜•์‹ ํ˜ผ์žฌ\n", "raw.loc[d_, \"op_h\"] = (raw.loc[d_, \"op_h\"] * 60).round(0); raw.loc[d_, \"op_unit\"] = \"min\" # ๋‹จ์œ„ ํ˜ผ์žฌ\n", "raw.loc[e, \"op_h\"] = -1.0 # ๋ถˆ๊ฐ€๋Šฅ ๊ฐ’\n", "raw.loc[f, \"veh\"] = \"V999\" # ๋ฏธ๋“ฑ๋ก ์ฐจ๋Ÿ‰\n", "raw.loc[g, \"code\"] = \"ZZ9\" # ๋ฏธ์ •์˜ ์ฝ”๋“œ\n", "raw.loc[h_, \"op_h\"] = np.nan # ๊ฒฐ์ธก\n", "burst = raw.iloc[k[:40]].copy() # ๊ธด๊ธ‰ยท๋น„๊ณ„ํš ์ •๋น„: DTC 6๊ฐœ ํญ์ฃผ\n", "bursts = []\n", "for _, r in burst.iterrows():\n", " for j, cd in enumerate(rng.choice(CODES, 6, replace=False)):\n", " bursts.append({**r, \"code\": cd, \"emergency\": True,\n", " \"ts\": (pd.to_datetime(r[\"ts\"]) + pd.Timedelta(minutes=j)).strftime(\"%Y-%m-%d %H:%M\")})\n", "raw = pd.concat([raw, pd.DataFrame(bursts), raw.sample(150, random_state=1)], ignore_index=True) # ํญ์ฃผ ๊ฑด + ์ค‘๋ณต 150๊ฑด\n", "raw = raw.sample(frac=1, random_state=3).reset_index(drop=True)\n", "print(f\"์›์ฒœ ๋กœ๊ทธ {len(raw)}ํ–‰ (์ •์ƒ {clean_n}ํ–‰ + ๊ธด๊ธ‰ ํญ์ฃผ {len(bursts)}ํ–‰ + ์ค‘๋ณต 150ํ–‰, ์˜ค๋ฅ˜ ์ฃผ์ž… {len(idx)}ํ–‰)\")\n", "\n", "# 2) ์ •์ œ ๊ทœ์น™ 10์ข…\n", "df, applied = raw.copy(), []\n", "def rule(name, n):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 2] DTC ์‹œ๊ณ„์—ด ๋ฐœ์ƒ ๋กœ๊ทธ ์ •์ œ, ๋™์‹œ ๋ฐœ์ƒ ์ฝ”๋“œ ์ •๊ทœํ™” ์ „์ฒ˜๋ฆฌ ๋ชจ๋“ˆ ๊ตฌํ˜„ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "rule(\"R1 ์ค‘๋ณต ํ–‰ ์ œ๊ฑฐ\", df.duplicated().sum()); df = df.drop_duplicates().reset_index(drop=True)\n", "v = df[\"veh\"].str.strip().str.upper(); rule(\"R2 ์ฐจ๋Ÿ‰ ID ํ‘œ๊ธฐ ํ†ต์ผ\", (v != df[\"veh\"]).sum()); df[\"veh\"] = v\n", "cd = df[\"code\"].str.strip().str.upper().str.replace(\"-\", \"\", regex=False); rule(\"R3 DTC ์ฝ”๋“œ ํ‘œ๊ธฐ ํ†ต์ผ\", (cd != df[\"code\"]).sum()); df[\"code\"] = cd\n", "t1 = pd.to_datetime(df[\"ts\"], format=\"%Y-%m-%d %H:%M\", errors=\"coerce\")\n", "t2 = pd.to_datetime(df[\"ts\"], format=\"%Y/%m/%d %H:%M:%S\", errors=\"coerce\"); rule(\"R4 ๋ฐœ์ƒ ์‹œ๊ฐ ํ˜•์‹ ํ†ต์ผ\", t1.isna().sum()); df[\"ts\"] = t1.fillna(t2)\n", "mn = df[\"op_unit\"] == \"min\"; df.loc[mn, \"op_h\"] = df.loc[mn, \"op_h\"] / 60; df[\"op_unit\"] = \"h\"; rule(\"R5 ๊ฐ€๋™์‹œ๊ฐ„ ๋‹จ์œ„ ํ†ต์ผ(๋ถ„ -> ์‹œ๊ฐ„)\", mn.sum())\n", "bad = df[\"op_h\"] < 0; df.loc[bad, \"op_h\"] = np.nan; rule(\"R6 ์Œ์ˆ˜ ๊ฐ€๋™์‹œ๊ฐ„ ๊ฒฐ์ธก ์ฒ˜๋ฆฌ\", bad.sum())\n", "unk = ~df[\"veh\"].isin(fleet[\"veh\"]) | ~df[\"code\"].isin(CODES); quarantine = df[unk].copy(); df = df[~unk].reset_index(drop=True)\n", "rule(\"R7 ๋ฏธ๋“ฑ๋ก ์ฐจ๋Ÿ‰ยท๋ฏธ์ •์˜ ์ฝ”๋“œ ๊ฒฉ๋ฆฌ(์‚ญ์ œ ์•„๋‹˜)\", unk.sum())\n", "df[\"hrs\"] = (df[\"ts\"] - start).dt.total_seconds() / 3600; df[\"vn\"] = df[\"veh\"].str[1:].astype(int)\n", "miss = df[\"op_h\"].isna().sum()\n", "df[\"op_h\"] = KNNImputer(n_neighbors=5).fit_transform(df[[\"hrs\", \"vn\", \"op_h\"]].assign(vn=df[\"vn\"] / 10))[:, 2]; rule(\"R8 ๊ฐ€๋™์‹œ๊ฐ„ ๊ฒฐ์ธก KNN ๋ณด์ •\", miss)\n", "df = df.sort_values([\"veh\", \"ts\"]).reset_index(drop=True)\n", "df[\"case\"] = (df[\"veh\"] != df[\"veh\"].shift()) | (df[\"ts\"].diff() > pd.Timedelta(hours=1)); df[\"case_id\"] = df[\"case\"].cumsum()\n", "before = len(df); df = df.drop_duplicates([\"case_id\", \"code\"]).reset_index(drop=True)\n", "cases = df.groupby(\"case_id\").agg(veh=(\"veh\", \"first\"), t=(\"ts\", \"first\"), codes=(\"code\", lambda s: tuple(sorted(s))), n=(\"code\", \"size\"), emergency=(\"emergency\", \"max\"))\n", "print(f\"(๋™์‹œ ๋ฐœ์ƒ ๋ฌถ์Œ ๊ณผ์ •์—์„œ ์ค‘๋ณต ์ฝ”๋“œ {before - len(df)}ํ–‰ ์ œ๊ฑฐ)\")\n", "rule(\"R9 ๋™์‹œ ๋ฐœ์ƒ ์ฝ”๋“œ ์ •๋น„ ๊ฑด ๋ฌถ์Œยท์ •๋ ฌ(์ƒ์„ฑ ๊ฑด์ˆ˜)\", len(cases))\n", "cases[\"outlier_flag\"] = (cases[\"n\"] > 4) & ~cases[\"emergency\"]\n", "rule(\"R10 ๊ธด๊ธ‰ยท๋น„๊ณ„ํš ์ •๋น„ ๋ณด์กด(์ผ๋ฐ˜ ์ด์ƒ ๊ฑด์€ ํ‘œ์‹œ๋งŒ)\", cases[\"emergency\"].sum())\n", "print(\"\\n[์ ์šฉ ๊ทœ์น™๊ณผ ์ฒ˜๋ฆฌ ๊ฑด์ˆ˜]\")\n", "for nme, n in applied:\n", " print(f\" {nme:40s} {n:5d}๊ฑด\")\n", "\n", "# 3) ์ •ํ•ฉ์„ฑ ๊ฒ€์ฆ\n", "checks = {\"์ค‘๋ณต 0๊ฑด\": not df.duplicated([\"case_id\", \"code\"]).any(), \"์ฐจ๋Ÿ‰ ID ํ˜•์‹\": df[\"veh\"].str.match(r\"^V\\d{3}$\").all(),\n", " \"์ฝ”๋“œ ํ—ˆ์šฉ ๋ฒ”์œ„\": df[\"code\"].isin(CODES).all(), \"์‹œ๊ฐ ๋ณ€ํ™˜\": df[\"ts\"].notna().all(), \"๊ฐ€๋™์‹œ๊ฐ„ ๋‹จ์œ„ h\": (df[\"op_unit\"] == \"h\").all(),\n", " \"๊ฐ€๋™์‹œ๊ฐ„ 0 ์ด์ƒ\": (df[\"op_h\"] >= 0).all(), \"๋ฏธ๋“ฑ๋ก ๊ฑด ๊ฒฉ๋ฆฌ ๋ณด๊ด€\": len(quarantine) == unk.sum(),\n", " \"๊ฒฐ์ธก 0๊ฑด\": df[[\"veh\", \"code\", \"ts\", \"op_h\"]].notna().all().all(), \"๋ฌถ์Œ ๋‚ด ์ฝ”๋“œ ์ •๋ ฌ\": all(list(c) == sorted(c) for c in cases[\"codes\"]),\n", " f\"๊ธด๊ธ‰ ์ •๋น„ {len(bursts) // 6}๊ฑด ๋ณด์กด\": int(cases[\"emergency\"].sum()) >= len(bursts) // 6}\n", "rate = np.mean(list(checks.values()))\n", "print(f\"\\n์ •ํ•ฉ์„ฑ ๊ฒ€์ฆ {sum(checks.values())}/{len(checks)} ํ†ต๊ณผ, ํ†ต๊ณผ์œจ {rate:.0%} (๋ชฉํ‘œ 100%: {'๋‹ฌ์„ฑ' if rate == 1 else '๋ฏธ๋‹ฌ'})\")\n", "for kx, vx in checks.items():\n", " if not vx:\n", " print(\" ์‹คํŒจ:\", kx)\n", "print(f\"์ •๋น„ ๊ฑด {len(cases)}๊ฑด ์ƒ์„ฑ(ํ‰๊ท  DTC {cases['n'].mean():.2f}๊ฐœ), ์ด์ƒ ํ‘œ์‹œ {int(cases['outlier_flag'].sum())}๊ฑด\")\n", "\n", "# 4) ์ •์ œ ์ „ํ›„ ๊ธฐ์ˆ ํ†ต๊ณ„ ๋น„๊ต\n", "num = pd.to_numeric(raw[\"op_h\"], errors=\"coerce\")\n", "cmp = pd.DataFrame({\"์ •์ œ ์ „\": [len(raw), raw[\"veh\"].nunique(), raw[\"code\"].nunique(), num.mean(), num.min(), num.isna().sum()],\n", " \"์ •์ œ ํ›„\": [len(df), df[\"veh\"].nunique(), df[\"code\"].nunique(), df[\"op_h\"].mean(), df[\"op_h\"].min(), df[\"op_h\"].isna().sum()]},\n", " index=[\"ํ–‰ ์ˆ˜\", \"์ฐจ๋Ÿ‰ ์ข…๋ฅ˜ ์ˆ˜\", \"์ฝ”๋“œ ์ข…๋ฅ˜ ์ˆ˜\", \"๊ฐ€๋™์‹œ๊ฐ„ ํ‰๊ท (h)\", \"๊ฐ€๋™์‹œ๊ฐ„ ์ตœ์†Œ(h)\", \"๊ฒฐ์ธก ์ˆ˜\"]).round(1)\n", "print(\"\\n[์ •์ œ ์ „ํ›„ ๊ธฐ์ˆ ํ†ต๊ณ„]\"); print(cmp.to_string())\n", "\n", "fig, ax = plt.subplots(1, 2, figsize=(10, 3.5))\n", "ax[0].hist(num.dropna(), bins=40, alpha=0.6, label=\"raw\"); ax[0].hist(df[\"op_h\"], bins=40, alpha=0.6, label=\"cleaned\")\n", "ax[0].set_title(\"Operating hours before/after unit fix\"); ax[0].legend()\n", "ax[1].bar([n.split()[0] for n, _ in applied], [n for _, n in applied]); ax[1].set_title(\"Records changed by rule\")\n", "plt.tight_layout(); plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 02 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "์›์ฒœ ๋กœ๊ทธ 22469ํ–‰ (์ •์ƒ 22079ํ–‰ + ๊ธด๊ธ‰ ํญ์ฃผ 240ํ–‰ + ์ค‘๋ณต 150ํ–‰, ์˜ค๋ฅ˜ ์ฃผ์ž… 2600ํ–‰)\n", "(๋™์‹œ ๋ฐœ์ƒ ๋ฌถ์Œ ๊ณผ์ •์—์„œ ์ค‘๋ณต ์ฝ”๋“œ 68ํ–‰ ์ œ๊ฑฐ)\n", "\n", "[์ ์šฉ ๊ทœ์น™๊ณผ ์ฒ˜๋ฆฌ ๊ฑด์ˆ˜]\n", " R1 ์ค‘๋ณต ํ–‰ ์ œ๊ฑฐ 150๊ฑด\n", " R2 ์ฐจ๋Ÿ‰ ID ํ‘œ๊ธฐ ํ†ต์ผ 400๊ฑด\n", " R3 DTC ์ฝ”๋“œ ํ‘œ๊ธฐ ํ†ต์ผ 500๊ฑด\n", " R4 ๋ฐœ์ƒ ์‹œ๊ฐ ํ˜•์‹ ํ†ต์ผ 600๊ฑด\n", " R5 ๊ฐ€๋™์‹œ๊ฐ„ ๋‹จ์œ„ ํ†ต์ผ(๋ถ„ -> ์‹œ๊ฐ„) 300๊ฑด\n", " R6 ์Œ์ˆ˜ ๊ฐ€๋™์‹œ๊ฐ„ ๊ฒฐ์ธก ์ฒ˜๋ฆฌ 80๊ฑด\n", " R7 ๋ฏธ๋“ฑ๋ก ์ฐจ๋Ÿ‰ยท๋ฏธ์ •์˜ ์ฝ”๋“œ ๊ฒฉ๋ฆฌ(์‚ญ์ œ ์•„๋‹˜) 160๊ฑด\n", " R8 ๊ฐ€๋™์‹œ๊ฐ„ ๊ฒฐ์ธก KNN ๋ณด์ • 340๊ฑด\n", " R9 ๋™์‹œ ๋ฐœ์ƒ ์ฝ”๋“œ ์ •๋น„ ๊ฑด ๋ฌถ์Œยท์ •๋ ฌ(์ƒ์„ฑ ๊ฑด์ˆ˜) 10504๊ฑด\n", " R10 ๊ธด๊ธ‰ยท๋น„๊ณ„ํš ์ •๋น„ ๋ณด์กด(์ผ๋ฐ˜ ์ด์ƒ ๊ฑด์€ ํ‘œ์‹œ๋งŒ) 40๊ฑด\n", "\n", "์ •ํ•ฉ์„ฑ ๊ฒ€์ฆ 10/10 ํ†ต๊ณผ, ํ†ต๊ณผ์œจ 100% (๋ชฉํ‘œ 100%: ๋‹ฌ์„ฑ)\n", "์ •๋น„ ๊ฑด 10504๊ฑด ์ƒ์„ฑ(ํ‰๊ท  DTC 2.10๊ฐœ), ์ด์ƒ ํ‘œ์‹œ 85๊ฑด\n", "\n", "[์ •์ œ ์ „ํ›„ ๊ธฐ์ˆ ํ†ต๊ณ„]\n", " ์ •์ œ ์ „ ์ •์ œ ํ›„\n", "ํ–‰ ์ˆ˜ 22469.0 22091.0\n", "์ฐจ๋Ÿ‰ ์ข…๋ฅ˜ ์ˆ˜ 252.0 100.0\n", "์ฝ”๋“œ ์ข…๋ฅ˜ ์ˆ˜ 82.0 28.0\n", "๊ฐ€๋™์‹œ๊ฐ„ ํ‰๊ท (h) 2496.2 1372.4\n", "๊ฐ€๋™์‹œ๊ฐ„ ์ตœ์†Œ(h) -1.0 3.8\n", "๊ฒฐ์ธก ์ˆ˜ 261.0 0.0\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 03. ๋‹จ์ผ ๊ณ ์žฅ ์ฝ”๋“œ ๊ธฐ๋ฐ˜ 1์ฐจ ๊ณ ์žฅ ๋ถ€ํ’ˆ ๋งคํ•‘ Baseline ์•Œ๊ณ ๋ฆฌ์ฆ˜ ๊ตฌํ˜„\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 2.5์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "๋‹จ์ผ ๊ณ ์žฅ ์ฝ”๋“œ ๊ธฐ๋ฐ˜ 1์ฐจ ๊ณ ์žฅ ๋ถ€ํ’ˆ ๋งคํ•‘ Baseline ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ๊ตฌํ˜„ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **๋‹จ์ผ ๊ณ ์žฅ ์ฝ”๋“œ ๊ธฐ๋ฐ˜ 1์ฐจ ๊ณ ์žฅ ๋ถ€ํ’ˆ ๋งคํ•‘ Baseline(์ฒซ ๊ธฐ๋ก ์ฝ”๋“œ์™€ ๊ฐ€์žฅ ์ž์ฃผ ํ•จ๊ป˜ ๋‚˜์˜จ ๋ถ€ํ’ˆ)์„ ๊ตฌํ˜„ํ•˜๊ณ , ํƒ์ƒ‰์  ๋ถ„์„์œผ๋กœ ๋‹ค๋นˆ๋„ ๊ณ ์žฅํ’ˆ๋ชฉ๊ณผ ๊ณ ์žฅ ์ฆ๊ฐ€ ์š”์ธ์„ ์ฐพ๋Š”๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **ํ•™์Šต ๊ธฐ๊ฐ„ ์ด๋ ฅ์œผ๋กœ ์ฝ”๋“œ์™€ ๋ถ€ํ’ˆ์˜ ๋Œ€์‘ํ‘œ๋ฅผ ๋งŒ๋“ค์–ด ๊ฒ€์ฆ ๊ธฐ๊ฐ„์˜ ๋ถ€ํ’ˆ ์ •ํ™•๋„๋ฅผ ์ธก์ •ํ•˜๊ณ , ๊ณต์œ ยท์ฆ์ƒ ์ฝ”๋“œ์ฒ˜๋Ÿผ ๋ชจํ˜ธํ•œ ์ฝ”๋“œ๊ฐ€ ์ฒซ ๊ธฐ๋ก์ผ ๋•Œ ์ •ํ™•๋„๊ฐ€ ๋–จ์–ด์ง€๋Š”์ง€ ํ™•์ธํ•˜๊ณ  ์œ ํšจ์„ฑ์„ ์ ๊ฒ€ํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `notebooks/mnt03_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": [ "# [TODO : ํ•™์ƒ ์‹ค์Šต 3] ๋‹จ์ผ ๊ณ ์žฅ ์ฝ”๋“œ ๊ธฐ๋ฐ˜ 1์ฐจ ๊ณ ์žฅ ๋ถ€ํ’ˆ ๋งคํ•‘ Baseline ์•Œ๊ณ ๋ฆฌ์ฆ˜ ๊ตฌํ˜„\n", "# ๋ฏธ์…˜ ์ง€์นจ ๋ฐ ๊ฒ€์ฆ ๊ธฐ์ค€์„ ํ™•์ธํ•˜๊ณ  ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ ๋ฐ ํŠœ๋‹ํ•˜์„ธ์š”.\n", "\"\"\"๋‹จ์ผ DTC ์ฝ”๋“œ๋งŒ ๋ณด๊ณ  ๊ฐ€์žฅ ํ”ํ•œ ๊ณ ์žฅ ๋ถ€ํ’ˆ์„ ๊ณ ๋ฅด๋Š” 1์ฐจ Baseline ๋งคํ•‘ ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ๊ตฌํ˜„ํ•ด ์ •ํ™•๋„์™€ ํ•œ๊ณ„๋ฅผ ํ™•์ธํ•˜๊ณ , ํƒ์ƒ‰์  ๋ถ„์„(EDA ์ฐจํŠธ 8์ข…, ๊ณ ์žฅ ๊ฑด์ˆ˜์™€ ํ”ผ์ฒ˜์˜ ์ƒ๊ด€๊ณ„์ˆ˜ยทp-value, ํŒŒ๋ ˆํ†  ABC ๋ถ„๋ฅ˜)์œผ๋กœ 1000๊ฐ€๋™์‹œ๊ฐ„๋‹น ๊ณ ์žฅ๋ฅ ์ด ๋†’์€ ๋‹ค๋นˆ๋„ ๊ณ ์žฅํ’ˆ๋ชฉ์„ ์‹๋ณ„ํ•œ๋‹ค.\"\"\"\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from scipy.stats import pearsonr\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ƒ ์ฐจ๋Ÿ‰ 100๋Œ€์˜ 18๊ฐœ์›” ๊ณ ์žฅ ์ง„๋‹จ ์ฝ”๋“œ(DTC)ยท์ •๋น„ ์ด๋ ฅ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ๋ถ€๋Œ€ยท์žฅ๋น„ยท์ •๋น„ ์‹ค์  ์—†์Œ)\n", "# ๋ถ€ํ’ˆ 16์ข…(1000๊ฐ€๋™์‹œ๊ฐ„๋‹น ๊ณ ์žฅ๋ฅ  ์ฐจ๋“ฑ), DTC 28์ข…(๋ถ€ํ’ˆ ๊ณ ์œ  ์ฝ”๋“œ, ์ง ๋ถ€ํ’ˆ ๊ณต์œ  ์ฝ”๋“œ, ๊ณ„ํ†ต ์ฆ์ƒ ์ฝ”๋“œ)\n", "# ๊ณ ์žฅ 1๊ฑด(์ •๋น„ ์ด๋ ฅ)๋งˆ๋‹ค DTC๊ฐ€ 1~5๊ฐœ ๋™์‹œ ๋ฐœ์ƒํ•˜๊ณ , ์—ฐ์‡„ ๊ณ ์žฅ ์ฝ”๋“œ์™€ ์ผ์‹œ ์žก์Œ ์ฝ”๋“œ๊ฐ€ ์„ž์ธ๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "PARTS = [ # (ID, ์ด๋ฆ„, ๊ณ„ํ†ต, 1000๊ฐ€๋™์‹œ๊ฐ„๋‹น ๊ณ ์žฅ๋ฅ , ๊ฐ€์ƒ ๋‹จ๊ฐ€์ง€์ˆ˜, ์ˆ˜๋ฆฌ์‹œ๊ฐ„ h)\n", " (\"P01\", \"์—ฐ๋ฃŒ๋ถ„์‚ฌ๊ธฐ\", \"engine\", 12.0, 3.0, 2.0), (\"P02\", \"ํ„ฐ๋ณด์ฐจ์ €\", \"engine\", 9.0, 8.0, 5.0),\n", " (\"P03\", \"์ถ•์ „์ง€\", \"electric\", 6.5, 4.0, 1.5), (\"P04\", \"๋ฐœ์ „๊ธฐ\", \"electric\", 4.0, 9.0, 3.0),\n", " (\"P05\", \"๋ณ€์†๊ธฐ ์ œ์–ด๋ชจ๋“ˆ\", \"drive\", 3.0, 12.0, 6.0), (\"P06\", \"๋ƒ‰๊ฐ์ˆ˜ ํŽŒํ”„\", \"engine\", 2.2, 3.5, 3.0),\n", " (\"P07\", \"์œ ์•• ํŽŒํ”„\", \"hydraulic\", 1.6, 10.0, 4.0), (\"P08\", \"์†๋„ ์„ผ์„œ\", \"drive\", 1.2, 2.0, 1.5),\n", " (\"P09\", \"ECU ์ปค๋„ฅํ„ฐ\", \"electric\", 0.9, 2.5, 1.5), (\"P10\", \"์—ฐ๋ฃŒ ํ•„ํ„ฐ\", \"engine\", 0.7, 0.8, 0.7),\n", " (\"P11\", \"์ œ๋™ ๋ฐธ๋ธŒ\", \"brake\", 0.5, 6.0, 3.0), (\"P12\", \"์กฐํ–ฅ ์•ก์ถ”์—์ดํ„ฐ\", \"drive\", 0.4, 9.0, 5.0),\n", " (\"P13\", \"๊ณต๊ธฐ ํ•„ํ„ฐ\", \"engine\", 0.3, 0.5, 0.5), (\"P14\", \"ํ†ต์‹  ์ธํ„ฐํŽ˜์ด์Šค ๋ชจ๋“ˆ\", \"electric\", 0.25, 3.0, 1.5),\n", " (\"P15\", \"์œคํ™œ์œ  ์••๋ ฅ ์„ผ์„œ\", \"engine\", 0.2, 1.0, 1.0), (\"P16\", \"ํŒฌ ๊ตฌ๋™ ๋ชจํ„ฐ\", \"cooling\", 0.15, 2.0, 2.0)]\n", "PID = [p[0] for p in PARTS]\n", "PNAME = {p[0]: p[1] for p in PARTS}; PSYS = {p[0]: p[2] for p in PARTS}; RATE = {p[0]: p[3] for p in PARTS}\n", "COST = {p[0]: p[4] for p in PARTS}; REPH = {p[0]: p[5] for p in PARTS}\n", "PAIRS = [(\"P01\", \"P02\"), (\"P03\", \"P04\"), (\"P05\", \"P08\"), (\"P06\", \"P10\"), (\"P09\", \"P14\"), (\"P13\", \"P15\")]\n", "SHARED = {p: f\"S{k + 1:02d}\" for k, pr in enumerate(PAIRS) for p in pr} # ์ง ๋ถ€ํ’ˆ์ด ํ•จ๊ป˜ ์“ฐ๋Š” ๋ชจํ˜ธํ•œ ์ฝ”๋“œ\n", "SPEC = {p: f\"C{p[1:]}1\" for p in PID} # ๋ถ€ํ’ˆ ๊ณ ์œ  ์ฝ”๋“œ\n", "SYS = [\"engine\", \"electric\", \"drive\", \"hydraulic\", \"brake\", \"cooling\"]\n", "SYMP = {s: f\"G{i + 1:02d}\" for i, s in enumerate(SYS)} # ๊ณ„ํ†ต ์ฆ์ƒ ์ฝ”๋“œ\n", "CODES = sorted(set(SPEC.values()) | set(SHARED.values()) | set(SYMP.values()))\n", "CASCADE = {\"P01\": [(\"P10\", 0.20)], \"P02\": [(\"P06\", 0.20)], \"P03\": [(\"P04\", 0.25)], \"P04\": [(\"P09\", 0.25)],\n", " \"P05\": [(\"P08\", 0.30)], \"P06\": [(\"P16\", 0.30)], \"P09\": [(\"P14\", 0.20)], \"P12\": [(\"P07\", 0.20)]}\n", "CRIT_SYS = {\"engine\", \"drive\", \"brake\"} # ๊ธฐ๋™ยท์ œ๋™ ๋ถˆ๋Šฅ ์œ„ํ—˜ ๊ณ„ํ†ต(๊ฒฝ๋ณด ๋Œ€์ƒ)\n", "MONTHS = 18\n", "\n", "\n", "def make_fleet(n=100, seed=0):\n", " rng = np.random.default_rng(seed)\n", " f = pd.DataFrame({\"veh\": [f\"V{i:03d}\" for i in range(n)], \"age\": rng.integers(1, 26, n).astype(float),\n", " \"terrain\": rng.uniform(0, 1, n), \"load\": rng.uniform(0, 1, n), \"pm_gap\": rng.uniform(20, 200, n),\n", " \"humid\": rng.uniform(0, 1, n), \"base_h\": rng.uniform(55, 125, n)})\n", " f[\"mult\"] = np.exp(0.045 * (f[\"age\"] - 13) + 0.9 * (f[\"terrain\"] - .5) + 0.8 * (f[\"load\"] - .5)\n", " + 0.006 * (f[\"pm_gap\"] - 110) + 0.7 * (f[\"humid\"] - .5))\n", " return f\n", "\n", "\n", "def make_data(seed=1, months=MONTHS):\n", " \"\"\"๋ฐ˜ํ™˜: fleet(์ฐจ๋Ÿ‰ ์†์„ฑ), ev(์ •๋น„ ์ด๋ ฅ 1๊ฑด=1ํ–‰: ๋ฐœ์ƒ DTC ๋ชฉ๋ก, ์‹ค์ œ ๊ณ ์žฅ ๋ถ€ํ’ˆ), log(DTC ๋ฐœ์ƒ ๋กœ๊ทธ, ํ–‰=์ฝ”๋“œ 1๊ฑด)\"\"\"\n", " rng = np.random.default_rng(seed)\n", " fleet = make_fleet()\n", " V = len(fleet)\n", " temp = 12 + 14 * np.sin(2 * np.pi * (np.arange(months) - 3) / 12) # ์›”ํ‰๊ท  ๊ธฐ์˜จ(๊ฐ€์ƒ)\n", " hours = fleet[\"base_h\"].values[:, None] * (1 + 0.25 * np.sin(2 * np.pi * np.arange(months) / 12 + 1)) * rng.lognormal(0, .12, (V, months))\n", " fleet[\"hours\"] = hours.sum(axis=1) # ์ฐจ๋Ÿ‰๋ณ„ 18๊ฐœ์›” ์ด ๊ฐ€๋™์‹œ๊ฐ„\n", " fleet.attrs[\"month_h\"] = hours.sum(axis=0) # ์›”๋ณ„ ์ „์ฒด ๊ฐ€๋™์‹œ๊ฐ„(๊ณ ์žฅ๋ฅ  ๋ถ„๋ชจ)\n", " evs = []\n", " for p in PID:\n", " s = np.ones(months)\n", " if p in (\"P03\", \"P04\"): s = np.exp(-0.06 * (temp - 12)) # ํ•œ๋žญ๊ธฐ ์ „๊ธฐ๊ณ„ํ†ต ์ฆ๊ฐ€\n", " if p in (\"P06\", \"P16\"): s = np.exp(0.05 * (temp - 12)) # ํ˜น์„œ๊ธฐ ๋ƒ‰๊ฐ๊ณ„ํ†ต ์ฆ๊ฐ€\n", " cnt = rng.poisson(RATE[p] * hours / 1000 * fleet[\"mult\"].values[:, None] * s[None, :])\n", " for v, m in zip(*np.nonzero(cnt)):\n", " for _ in range(cnt[v, m]):\n", " evs.append((fleet[\"veh\"][v], int(m), m * 720 + rng.uniform(0, 720), p, float(temp[m] + rng.normal(0, 3))))\n", " ev = pd.DataFrame(evs, columns=[\"veh\", \"month\", \"t\", \"part\", \"temp\"]).sort_values(\"t\").reset_index(drop=True)\n", " ev[\"ev_id\"] = [f\"W{i:05d}\" for i in range(len(ev))]\n", " rows, codes = [], []\n", " for e in ev.itertuples():\n", " c, r = [], rng.random(3)\n", " if r[0] < 0.6: c.append(SPEC[e.part])\n", " if e.part in SHARED and r[1] < 0.6: c.append(SHARED[e.part])\n", " if r[2] < 0.5: c.append(SYMP[PSYS[e.part]])\n", " if not c: c.append(SHARED.get(e.part, SPEC[e.part]))\n", " for q, pr in CASCADE.get(e.part, []):\n", " if rng.random() < pr: c.append(SPEC[q])\n", " if rng.random() < 0.12: c.append(SPEC[PID[rng.integers(0, 16)]])\n", " c = list(dict.fromkeys(c))\n", " rng.shuffle(c) # ๋™์‹œ ๋ฐœ์ƒ ์ฝ”๋“œ์˜ ๊ธฐ๋ก ์ˆœ์„œ๋Š” ์ž„์˜\n", " codes.append(c)\n", " for k, cd in enumerate(c):\n", " rows.append((e.ev_id, e.veh, e.t + k * 0.1, cd))\n", " ev[\"codes\"] = codes\n", " log = pd.DataFrame(rows, columns=[\"ev_id\", \"veh\", \"ts\", \"code\"])\n", " return fleet, ev, log\n", "\n", "fleet, ev, log = make_data()\n", "ev[\"first\"] = ev[\"codes\"].str[0]\n", "train, test = ev[ev[\"month\"] < 12], ev[ev[\"month\"] >= 12]\n", "\n", "# 1) Baseline: ์ฒซ ๋ฒˆ์งธ๋กœ ๊ธฐ๋ก๋œ ์ฝ”๋“œ -> ํ•™์Šต ๊ธฐ๊ฐ„์— ๊ทธ ์ฝ”๋“œ์™€ ๊ฐ€์žฅ ์ž์ฃผ ํ•จ๊ป˜ ๋‚˜์˜จ ๋ถ€ํ’ˆ\n", "mp = train.groupby(\"first\")[\"part\"].agg(lambda s: s.value_counts().index[0])\n", "pred = test[\"first\"].map(mp)\n", "acc = (pred == test[\"part\"]).mean()\n", "sys_acc = (pred.map(PSYS) == test[\"part\"].map(PSYS)).mean()\n", "print(f\"[๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ์ •๋น„ ๊ฑด {len(ev)}๊ฑด, DTC ๋กœ๊ทธ {len(log)}ํ–‰, ์ฐจ๋Ÿ‰ {len(fleet)}๋Œ€, 18๊ฐœ์›”\")\n", "print(f\"Baseline ๋ถ€ํ’ˆ ์ •ํ™•๋„ {acc:.1%}, ๊ณ ์žฅ ๊ณ„ํ†ต ์ •ํ™•๋„ {sys_acc:.1%} (๊ฒ€์ฆ {len(test)}๊ฑด)\")\n", "amb = test[\"first\"].isin(list(SHARED.values()) + list(SYMP.values()))\n", "print(f\" ๋ชจํ˜ธํ•œ ์ฝ”๋“œ(๊ณต์œ ยท์ฆ์ƒ) ์ฒซ ๊ธฐ๋ก ๊ฑด ์ •ํ™•๋„ {(pred[amb] == test['part'][amb]).mean():.1%} vs ๊ณ ์œ  ์ฝ”๋“œ {(pred[~amb] == test['part'][~amb]).mean():.1%}\")\n", "\n", "# 2) ๋ถ€ํ’ˆ๋ณ„ ๊ณ ์žฅ๋ฅ (1000๊ฐ€๋™์‹œ๊ฐ„๋‹น)๊ณผ ํŒŒ๋ ˆํ† (ABC)\n", "tot_h = fleet[\"hours\"].sum()\n", "cnt = ev[\"part\"].value_counts().reindex(PID)\n", "rate = (cnt / tot_h * 1000).round(3)\n", "par = pd.DataFrame({\"๋ถ€ํ’ˆ\": [PNAME[p] for p in PID], \"๊ฑด์ˆ˜\": cnt.values, \"๊ณ ์žฅ๋ฅ \": rate.values}, index=PID).sort_values(\"๊ฑด์ˆ˜\", ascending=False)\n", "par[\"๋ˆ„์ ๋น„์œจ\"] = par[\"๊ฑด์ˆ˜\"].cumsum() / par[\"๊ฑด์ˆ˜\"].sum()\n", "par[\"๋“ฑ๊ธ‰\"] = np.where(par[\"๋ˆ„์ ๋น„์œจ\"] <= 0.80, \"A\", np.where(par[\"๋ˆ„์ ๋น„์œจ\"] <= 0.95, \"B\", \"C\"))\n", "par.loc[par.index[0], \"๋“ฑ๊ธ‰\"] = \"A\"\n", "print(f\"\\n[ํŒŒ๋ ˆํ† ] ์ด ๊ฐ€๋™ {tot_h:,.0f}์‹œ๊ฐ„, ์ „์ฒด ๊ณ ์žฅ๋ฅ  {cnt.sum() / tot_h * 1000:.1f}๊ฑด/1000h\")\n", "print(par.head(7).assign(๋ˆ„์ ๋น„์œจ=lambda d: d[\"๋ˆ„์ ๋น„์œจ\"].map(\"{:.0%}\".format)).to_string())\n", "top20 = par.head(int(np.ceil(len(par) * 0.2)))\n", "print(f\"์ƒ์œ„ 20% ํ’ˆ๋ชฉ({len(top20)}์ข…: {', '.join(top20['๋ถ€ํ’ˆ'])})์ด ์ „์ฒด ๊ณ ์žฅ์˜ {top20['๊ฑด์ˆ˜'].sum() / cnt.sum():.0%}, A๋“ฑ๊ธ‰ {int((par['๋“ฑ๊ธ‰'] == 'A').sum())}์ข…\")\n", "\n", "# 3) ์ƒ๊ด€๋ถ„์„: ์ฐจ๋Ÿ‰๋ณ„ ์ •๋น„ ๊ฑด์ˆ˜์™€ ํ”ผ์ฒ˜\n", "vc = ev.groupby(\"veh\").size().reindex(fleet[\"veh\"]).fillna(0).values\n", "rows = []\n", "for col, lab in [(\"age\", \"์ฐจ๋ น\"), (\"pm_gap\", \"์˜ˆ๋ฐฉ์ •๋น„ ๊ฒฝ๊ณผ์‹œ๊ฐ„\"), (\"terrain\", \"์ง€ํ˜• ๊ฐ€ํ˜น๋„\"), (\"load\", \"์šด์šฉ ๋ถ€ํ•˜\"), (\"hours\", \"์ด ๊ฐ€๋™์‹œ๊ฐ„\"), (\"humid\", \"์Šต๋„ ์ง€์ˆ˜\")]:\n", " r, p = pearsonr(fleet[col], vc); rows.append((lab, r, p))\n", "mc = ev.groupby([\"month\", \"part\"]).size().unstack(fill_value=0)\n", "tm = ev.groupby(\"month\")[\"temp\"].mean()\n", "r, p = pearsonr(tm, mc[\"P03\"]); rows.append((\"์›”ํ‰๊ท  ๊ธฐ์˜จ-์ถ•์ „์ง€ ๊ณ ์žฅ\", r, p))\n", "cor = pd.DataFrame(rows, columns=[\"ํ”ผ์ฒ˜\", \"r\", \"p\"])\n", "cor[\"์œ ์˜\"] = cor[\"p\"] < 0.05\n", "print(\"\\n[์ƒ๊ด€๋ถ„์„] ํ”ผ์ฒ˜ | r | p-value | ์œ ์˜(p<0.05)\")\n", "for x in cor.itertuples():\n", " print(f\" {x.ํ”ผ์ฒ˜:14s} {x.r:+.2f} {x.p:.4f} {'์œ ์˜' if x.์œ ์˜ else '-'}\")\n", "nsig = int(cor[\"์œ ์˜\"].sum())\n", "\n", "# 4) EDA ์ฐจํŠธ 8์ข…\n", "fig, ax = plt.subplots(2, 4, figsize=(16, 7))\n", "ax[0, 0].bar(par.index, par[\"๊ณ ์žฅ๋ฅ \"]); ax[0, 0].set_title(\"1 Failure rate per 1000h\")\n", "ax[0, 1].plot(range(len(par)), par[\"๋ˆ„์ ๋น„์œจ\"], marker=\"o\"); ax[0, 1].axhline(0.8, ls=\"--\", color=\"k\"); ax[0, 1].set_title(\"2 Pareto cumulative share\")\n", "mc.sum(axis=1).plot(ax=ax[0, 2]); ax[0, 2].set_title(\"3 Monthly failures\")\n", "ev[\"codes\"].str.len().value_counts().sort_index().plot.bar(ax=ax[0, 3]); ax[0, 3].set_title(\"4 DTCs per maintenance case\")\n", "log[\"code\"].value_counts().head(10).plot.bar(ax=ax[1, 0]); ax[1, 0].set_title(\"5 Top DTC codes\")\n", "ev[\"part\"].map(PSYS).value_counts().plot.bar(ax=ax[1, 1]); ax[1, 1].set_title(\"6 Failures by system\")\n", "ax[1, 2].scatter(fleet[\"age\"], vc, s=8); ax[1, 2].set_title(\"7 Vehicle age vs failures\")\n", "ax[1, 3].scatter(fleet[\"pm_gap\"], vc, s=8); ax[1, 3].set_title(\"8 PM gap vs failures\")\n", "plt.tight_layout(); plt.show()\n", "\n", "print(f\"\\nEDA ์ฐจํŠธ 8์ข… ์ƒ์„ฑ(๋ชฉํ‘œ 8์ข… ์ด์ƒ: ๋‹ฌ์„ฑ), ์œ ์˜ํ•œ ํ”ผ์ฒ˜ {nsig}๊ฐœ (๋ชฉํ‘œ 5๊ฐœ ์ด์ƒ: {'๋‹ฌ์„ฑ' if nsig >= 5 else '๋ฏธ๋‹ฌ'})\")\n", "print(\"ํ•ต์‹ฌ ๋ฐœ๊ฒฌ: 1) ์†Œ์ˆ˜ ๋ถ€ํ’ˆ์— ๊ณ ์žฅ ์ง‘์ค‘ 2) ๊ณต์œ ยท์ฆ์ƒ ์ฝ”๋“œ๊ฐ€ ์ฒซ ๊ธฐ๋ก์ด๋ฉด Baseline์ด ์ž์ฃผ ํ‹€๋ฆผ 3) ๊ณ ๋ นยท๊ฐ€ํ˜น ์ง€ํ˜•ยท์˜ˆ๋ฐฉ์ •๋น„ ์ง€์—ฐ ์ฐจ๋Ÿ‰์˜ ๊ณ ์žฅ ์ฆ๊ฐ€\")\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 03 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ์ •๋น„ ๊ฑด 10681๊ฑด, DTC ๋กœ๊ทธ 22079ํ–‰, ์ฐจ๋Ÿ‰ 100๋Œ€, 18๊ฐœ์›”\n", "Baseline ๋ถ€ํ’ˆ ์ •ํ™•๋„ 68.7%, ๊ณ ์žฅ ๊ณ„ํ†ต ์ •ํ™•๋„ 96.8% (๊ฒ€์ฆ 3890๊ฑด)\n", " ๋ชจํ˜ธํ•œ ์ฝ”๋“œ(๊ณต์œ ยท์ฆ์ƒ) ์ฒซ ๊ธฐ๋ก ๊ฑด ์ •ํ™•๋„ 59.7% vs ๊ณ ์œ  ์ฝ”๋“œ 80.6%\n", "\n", "[ํŒŒ๋ ˆํ† ] ์ด ๊ฐ€๋™ 171,649์‹œ๊ฐ„, ์ „์ฒด ๊ณ ์žฅ๋ฅ  62.2๊ฑด/1000h\n", " ๋ถ€ํ’ˆ ๊ฑด์ˆ˜ ๊ณ ์žฅ๋ฅ  ๋ˆ„์ ๋น„์œจ ๋“ฑ๊ธ‰\n", "P01 ์—ฐ๋ฃŒ๋ถ„์‚ฌ๊ธฐ 2800 16.312 26% A\n", "P02 ํ„ฐ๋ณด์ฐจ์ € 1985 11.564 45% A\n", "P03 ์ถ•์ „์ง€ 1983 11.553 63% A\n", "P04 ๋ฐœ์ „๊ธฐ 1244 7.247 75% A\n", "P05 ๋ณ€์†๊ธฐ ์ œ์–ด๋ชจ๋“ˆ 665 3.874 81% B\n", "P06 ๋ƒ‰๊ฐ์ˆ˜ ํŽŒํ”„ 477 2.779 86% B\n", "P07 ์œ ์•• ํŽŒํ”„ 390 2.272 89% B\n", "์ƒ์œ„ 20% ํ’ˆ๋ชฉ(4์ข…: ์—ฐ๋ฃŒ๋ถ„์‚ฌ๊ธฐ, ํ„ฐ๋ณด์ฐจ์ €, ์ถ•์ „์ง€, ๋ฐœ์ „๊ธฐ)์ด ์ „์ฒด ๊ณ ์žฅ์˜ 75%, A๋“ฑ๊ธ‰ 4์ข…\n", "\n", "[์ƒ๊ด€๋ถ„์„] ํ”ผ์ฒ˜ | r | p-value | ์œ ์˜(p<0.05)\n", " ์ฐจ๋ น +0.41 0.0000 ์œ ์˜\n", " ์˜ˆ๋ฐฉ์ •๋น„ ๊ฒฝ๊ณผ์‹œ๊ฐ„ +0.57 0.0000 ์œ ์˜\n", " ์ง€ํ˜• ๊ฐ€ํ˜น๋„ +0.40 0.0000 ์œ ์˜\n", " ์šด์šฉ ๋ถ€ํ•˜ +0.35 0.0004 ์œ ์˜\n", " ์ด ๊ฐ€๋™์‹œ๊ฐ„ +0.32 0.0011 ์œ ์˜\n", " ์Šต๋„ ์ง€์ˆ˜ +0.07 0.5174 -\n", " ์›”ํ‰๊ท  ๊ธฐ์˜จ-์ถ•์ „์ง€ ๊ณ ์žฅ -0.95 0.0000 ์œ ์˜\n", "\n", "EDA ์ฐจํŠธ 8์ข… ์ƒ์„ฑ(๋ชฉํ‘œ 8์ข… ์ด์ƒ: ๋‹ฌ์„ฑ), ์œ ์˜ํ•œ ํ”ผ์ฒ˜ 6๊ฐœ (๋ชฉํ‘œ 5๊ฐœ ์ด์ƒ: ๋‹ฌ์„ฑ)\n", "ํ•ต์‹ฌ ๋ฐœ๊ฒฌ: 1) ์†Œ์ˆ˜ ๋ถ€ํ’ˆ์— ๊ณ ์žฅ ์ง‘์ค‘ 2) ๊ณต์œ ยท์ฆ์ƒ ์ฝ”๋“œ๊ฐ€ ์ฒซ ๊ธฐ๋ก์ด๋ฉด Baseline์ด ์ž์ฃผ ํ‹€๋ฆผ 3) ๊ณ ๋ นยท๊ฐ€ํ˜น ์ง€ํ˜•ยท์˜ˆ๋ฐฉ์ •๋น„ ์ง€์—ฐ ์ฐจ๋Ÿ‰์˜ ๊ณ ์žฅ ์ฆ๊ฐ€\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 04. ์—ฐ๊ด€ ๊ทœ์น™ ๋งˆ์ด๋‹(Apriori/FP-Growth) ๊ธฐ๋ฐ˜ ๋‹ค์ค‘ DTC ๋™์‹œ ๋ฐœ์ƒ ์‹œ ๊ทผ๋ณธ ์›์ธ ์˜ˆ์ธก ๋ชจ๋ธ ๊ณ ๋„ํ™”\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 3.0์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "์—ฐ๊ด€ ๊ทœ์น™ ๋งˆ์ด๋‹ ๊ธฐ๋ฐ˜ ๋‹ค์ค‘ DTC ๋™์‹œ ๋ฐœ์ƒ ์‹œ ๊ทผ๋ณธ ์›์ธ ์˜ˆ์ธก ๋ชจ๋ธ์„ ๊ณ ๋„ํ™”ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **์—ฐ๊ด€ ๊ทœ์น™ ๋งˆ์ด๋‹(Apriori)์œผ๋กœ ๋‹ค์ค‘ DTC ๋™์‹œ ๋ฐœ์ƒ ์‹œ ๊ทผ๋ณธ ์›์ธ ๋ถ€ํ’ˆ์„ ์˜ˆ์ธกํ•˜๋Š” ๋ชจ๋ธ์„ ๊ณ ๋„ํ™”ํ•˜๊ณ , ์›”๊ฐ„ ๊ณ ์žฅ ๊ฑด์ˆ˜ ๊ธฐ์ค€ ๋ชจ๋ธ์˜ ํŒŒ์ƒ ํ”ผ์ฒ˜๋ฅผ ์„ค๊ณ„ํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **๋™์‹œ ๋ฐœ์ƒ DTC ์„ธํŠธ์™€ ๊ทผ๋ณธ ์›์ธ ๋ถ€ํ’ˆ์„ ํ•ญ๋ชฉ์œผ๋กœ ๋ณด๊ณ  ์ตœ์†Œ ์ง€์ง€๋„ ๋ฏธ๋งŒ์„ ๊ฐ€์ง€์น˜๊ธฐํ•˜๋ฉฐ ๋นˆ๋ฐœ ํ•ญ๋ชฉ์ง‘ํ•ฉ๊ณผ ์—ฐ๊ด€ ๊ทœ์น™(์‹ ๋ขฐ๋„ยทํ–ฅ์ƒ๋„)์„ ๊ตฌ ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `src/features/mnt03_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": [ "\"\"\"Apriori ์—ฐ๊ด€ ๊ทœ์น™ ๋งˆ์ด๋‹์œผ๋กœ ์—ฌ๋Ÿฌ DTC๊ฐ€ ๋™์‹œ์— ๋‚˜ํƒ€๋‚ฌ์„ ๋•Œ์˜ ๊ทผ๋ณธ ์›์ธ ๋ถ€ํ’ˆ์„ ์˜ˆ์ธกํ•˜๋Š” ๋ชจ๋ธ์„ ๋งŒ๋“ค์–ด ๋‹จ์ผ ์ฝ”๋“œ Baseline๊ณผ ๋น„๊ตํ•˜๊ณ , ๋ถ€ํ’ˆ๋ณ„ ์›”๊ฐ„ ๊ณ ์žฅ ๊ฑด์ˆ˜ ์˜ˆ์ธก์šฉ ํŒŒ์ƒ ํ”ผ์ฒ˜ 10๊ฐœ ์ด์ƒ์œผ๋กœ ๊ธฐ์ค€ ๋ชจ๋ธ์„ ํ•™์Šตํ•ด ์‹œ๊ณ„์—ด ๊ต์ฐจ๊ฒ€์ฆ MAPE์™€ ํ”ผ์ฒ˜ ์ค‘์š”๋„๋ฅผ ํ™•์ธํ•œ๋‹ค.\"\"\"\n", "import itertools\n", "from collections import Counter\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from sklearn.linear_model import Ridge\n", "from sklearn.preprocessing import StandardScaler\n", "from sklearn.pipeline import make_pipeline\n", "from sklearn.inspection import permutation_importance\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ƒ ์ฐจ๋Ÿ‰ 100๋Œ€์˜ 18๊ฐœ์›” ๊ณ ์žฅ ์ง„๋‹จ ์ฝ”๋“œ(DTC)ยท์ •๋น„ ์ด๋ ฅ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ๋ถ€๋Œ€ยท์žฅ๋น„ยท์ •๋น„ ์‹ค์  ์—†์Œ)\n", "# ๋ถ€ํ’ˆ 16์ข…(1000๊ฐ€๋™์‹œ๊ฐ„๋‹น ๊ณ ์žฅ๋ฅ  ์ฐจ๋“ฑ), DTC 28์ข…(๋ถ€ํ’ˆ ๊ณ ์œ  ์ฝ”๋“œ, ์ง ๋ถ€ํ’ˆ ๊ณต์œ  ์ฝ”๋“œ, ๊ณ„ํ†ต ์ฆ์ƒ ์ฝ”๋“œ)\n", "# ๊ณ ์žฅ 1๊ฑด(์ •๋น„ ์ด๋ ฅ)๋งˆ๋‹ค DTC๊ฐ€ 1~5๊ฐœ ๋™์‹œ ๋ฐœ์ƒํ•˜๊ณ , ์—ฐ์‡„ ๊ณ ์žฅ ์ฝ”๋“œ์™€ ์ผ์‹œ ์žก์Œ ์ฝ”๋“œ๊ฐ€ ์„ž์ธ๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "PARTS = [ # (ID, ์ด๋ฆ„, ๊ณ„ํ†ต, 1000๊ฐ€๋™์‹œ๊ฐ„๋‹น ๊ณ ์žฅ๋ฅ , ๊ฐ€์ƒ ๋‹จ๊ฐ€์ง€์ˆ˜, ์ˆ˜๋ฆฌ์‹œ๊ฐ„ h)\n", " (\"P01\", \"์—ฐ๋ฃŒ๋ถ„์‚ฌ๊ธฐ\", \"engine\", 12.0, 3.0, 2.0), (\"P02\", \"ํ„ฐ๋ณด์ฐจ์ €\", \"engine\", 9.0, 8.0, 5.0),\n", " (\"P03\", \"์ถ•์ „์ง€\", \"electric\", 6.5, 4.0, 1.5), (\"P04\", \"๋ฐœ์ „๊ธฐ\", \"electric\", 4.0, 9.0, 3.0),\n", " (\"P05\", \"๋ณ€์†๊ธฐ ์ œ์–ด๋ชจ๋“ˆ\", \"drive\", 3.0, 12.0, 6.0), (\"P06\", \"๋ƒ‰๊ฐ์ˆ˜ ํŽŒํ”„\", \"engine\", 2.2, 3.5, 3.0),\n", " (\"P07\", \"์œ ์•• ํŽŒํ”„\", \"hydraulic\", 1.6, 10.0, 4.0), (\"P08\", \"์†๋„ ์„ผ์„œ\", \"drive\", 1.2, 2.0, 1.5),\n", " (\"P09\", \"ECU ์ปค๋„ฅํ„ฐ\", \"electric\", 0.9, 2.5, 1.5), (\"P10\", \"์—ฐ๋ฃŒ ํ•„ํ„ฐ\", \"engine\", 0.7, 0.8, 0.7),\n", " (\"P11\", \"์ œ๋™ ๋ฐธ๋ธŒ\", \"brake\", 0.5, 6.0, 3.0), (\"P12\", \"์กฐํ–ฅ ์•ก์ถ”์—์ดํ„ฐ\", \"drive\", 0.4, 9.0, 5.0),\n", " (\"P13\", \"๊ณต๊ธฐ ํ•„ํ„ฐ\", \"engine\", 0.3, 0.5, 0.5), (\"P14\", \"ํ†ต์‹  ์ธํ„ฐํŽ˜์ด์Šค ๋ชจ๋“ˆ\", \"electric\", 0.25, 3.0, 1.5),\n", " (\"P15\", \"์œคํ™œ์œ  ์••๋ ฅ ์„ผ์„œ\", \"engine\", 0.2, 1.0, 1.0), (\"P16\", \"ํŒฌ ๊ตฌ๋™ ๋ชจํ„ฐ\", \"cooling\", 0.15, 2.0, 2.0)]\n", "PID = [p[0] for p in PARTS]\n", "PNAME = {p[0]: p[1] for p in PARTS}; PSYS = {p[0]: p[2] for p in PARTS}; RATE = {p[0]: p[3] for p in PARTS}\n", "COST = {p[0]: p[4] for p in PARTS}; REPH = {p[0]: p[5] for p in PARTS}\n", "PAIRS = [(\"P01\", \"P02\"), (\"P03\", \"P04\"), (\"P05\", \"P08\"), (\"P06\", \"P10\"), (\"P09\", \"P14\"), (\"P13\", \"P15\")]\n", "SHARED = {p: f\"S{k + 1:02d}\" for k, pr in enumerate(PAIRS) for p in pr} # ์ง ๋ถ€ํ’ˆ์ด ํ•จ๊ป˜ ์“ฐ๋Š” ๋ชจํ˜ธํ•œ ์ฝ”๋“œ\n", "SPEC = {p: f\"C{p[1:]}1\" for p in PID} # ๋ถ€ํ’ˆ ๊ณ ์œ  ์ฝ”๋“œ\n", "SYS = [\"engine\", \"electric\", \"drive\", \"hydraulic\", \"brake\", \"cooling\"]\n", "SYMP = {s: f\"G{i + 1:02d}\" for i, s in enumerate(SYS)} # ๊ณ„ํ†ต ์ฆ์ƒ ์ฝ”๋“œ\n", "CODES = sorted(set(SPEC.values()) | set(SHARED.values()) | set(SYMP.values()))\n", "CASCADE = {\"P01\": [(\"P10\", 0.20)], \"P02\": [(\"P06\", 0.20)], \"P03\": [(\"P04\", 0.25)], \"P04\": [(\"P09\", 0.25)],\n", " \"P05\": [(\"P08\", 0.30)], \"P06\": [(\"P16\", 0.30)], \"P09\": [(\"P14\", 0.20)], \"P12\": [(\"P07\", 0.20)]}\n", "CRIT_SYS = {\"engine\", \"drive\", \"brake\"} # ๊ธฐ๋™ยท์ œ๋™ ๋ถˆ๋Šฅ ์œ„ํ—˜ ๊ณ„ํ†ต(๊ฒฝ๋ณด ๋Œ€์ƒ)\n", "MONTHS = 18\n", "\n", "\n", "def make_fleet(n=100, seed=0):\n", " rng = np.random.default_rng(seed)\n", " f = pd.DataFrame({\"veh\": [f\"V{i:03d}\" for i in range(n)], \"age\": rng.integers(1, 26, n).astype(float),\n", " \"terrain\": rng.uniform(0, 1, n), \"load\": rng.uniform(0, 1, n), \"pm_gap\": rng.uniform(20, 200, n),\n", " \"humid\": rng.uniform(0, 1, n), \"base_h\": rng.uniform(55, 125, n)})\n", " f[\"mult\"] = np.exp(0.045 * (f[\"age\"] - 13) + 0.9 * (f[\"terrain\"] - .5) + 0.8 * (f[\"load\"] - .5)\n", " + 0.006 * (f[\"pm_gap\"] - 110) + 0.7 * (f[\"humid\"] - .5))\n", " return f\n", "\n", "\n", "def make_data(seed=1, months=MONTHS):\n", " \"\"\"๋ฐ˜ํ™˜: fleet(์ฐจ๋Ÿ‰ ์†์„ฑ), ev(์ •๋น„ ์ด๋ ฅ 1๊ฑด=1ํ–‰: ๋ฐœ์ƒ DTC ๋ชฉ๋ก, ์‹ค์ œ ๊ณ ์žฅ ๋ถ€ํ’ˆ), log(DTC ๋ฐœ์ƒ ๋กœ๊ทธ, ํ–‰=์ฝ”๋“œ 1๊ฑด)\"\"\"\n", " rng = np.random.default_rng(seed)\n", " fleet = make_fleet()\n", " V = len(fleet)\n", " temp = 12 + 14 * np.sin(2 * np.pi * (np.arange(months) - 3) / 12) # ์›”ํ‰๊ท  ๊ธฐ์˜จ(๊ฐ€์ƒ)\n", " hours = fleet[\"base_h\"].values[:, None] * (1 + 0.25 * np.sin(2 * np.pi * np.arange(months) / 12 + 1)) * rng.lognormal(0, .12, (V, months))\n", " fleet[\"hours\"] = hours.sum(axis=1) # ์ฐจ๋Ÿ‰๋ณ„ 18๊ฐœ์›” ์ด ๊ฐ€๋™์‹œ๊ฐ„\n", " fleet.attrs[\"month_h\"] = hours.sum(axis=0) # ์›”๋ณ„ ์ „์ฒด ๊ฐ€๋™์‹œ๊ฐ„(๊ณ ์žฅ๋ฅ  ๋ถ„๋ชจ)\n", " evs = []\n", " for p in PID:\n", " s = np.ones(months)\n", " if p in (\"P03\", \"P04\"): s = np.exp(-0.06 * (temp - 12)) # ํ•œ๋žญ๊ธฐ ์ „๊ธฐ๊ณ„ํ†ต ์ฆ๊ฐ€\n", " if p in (\"P06\", \"P16\"): s = np.exp(0.05 * (temp - 12)) # ํ˜น์„œ๊ธฐ ๋ƒ‰๊ฐ๊ณ„ํ†ต ์ฆ๊ฐ€\n", " cnt = rng.poisson(RATE[p] * hours / 1000 * fleet[\"mult\"].values[:, None] * s[None, :])\n", " for v, m in zip(*np.nonzero(cnt)):\n", " for _ in range(cnt[v, m]):\n", " evs.append((fleet[\"veh\"][v], int(m), m * 720 + rng.uniform(0, 720), p, float(temp[m] + rng.normal(0, 3))))\n", " ev = pd.DataFrame(evs, columns=[\"veh\", \"month\", \"t\", \"part\", \"temp\"]).sort_values(\"t\").reset_index(drop=True)\n", " ev[\"ev_id\"] = [f\"W{i:05d}\" for i in range(len(ev))]\n", " rows, codes = [], []\n", " for e in ev.itertuples():\n", " c, r = [], rng.random(3)\n", " if r[0] < 0.6: c.append(SPEC[e.part])\n", " if e.part in SHARED and r[1] < 0.6: c.append(SHARED[e.part])\n", " if r[2] < 0.5: c.append(SYMP[PSYS[e.part]])\n", " if not c: c.append(SHARED.get(e.part, SPEC[e.part]))\n", " for q, pr in CASCADE.get(e.part, []):\n", " if rng.random() < pr: c.append(SPEC[q])\n", " if rng.random() < 0.12: c.append(SPEC[PID[rng.integers(0, 16)]])\n", " c = list(dict.fromkeys(c))\n", " rng.shuffle(c) # ๋™์‹œ ๋ฐœ์ƒ ์ฝ”๋“œ์˜ ๊ธฐ๋ก ์ˆœ์„œ๋Š” ์ž„์˜\n", " codes.append(c)\n", " for k, cd in enumerate(c):\n", " rows.append((e.ev_id, e.veh, e.t + k * 0.1, cd))\n", " ev[\"codes\"] = codes\n", " log = pd.DataFrame(rows, columns=[\"ev_id\", \"veh\", \"ts\", \"code\"])\n", " return fleet, ev, log\n", "\n", "# ---------------------------------------------------------------------------\n", "# ๊ณตํ†ต ๋ถ„์„ ์กฐ๊ฐ: ์ฝ”๋“œ ๋ฉ€ํ‹ฐํ•ซ ํ–‰๋ ฌ, ์ฐจ๋Ÿ‰ ๋งฅ๋ฝ ํ”ผ์ฒ˜, ํ•™์Šต/๊ฒ€์ฆ ๋ถ„ํ• (์•ž 12๊ฐœ์›” ํ•™์Šต, ๋’ค 6๊ฐœ์›” ๊ฒ€์ฆ)\n", "# ---------------------------------------------------------------------------\n", "CIDX = {c: i for i, c in enumerate(CODES)}\n", "SPLIT_M = 12\n", "\n", "\n", "def multi_hot(ev):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 4] ์—ฐ๊ด€ ๊ทœ์น™ ๋งˆ์ด๋‹(Apriori/FP-Growth) ๊ธฐ๋ฐ˜ ๋‹ค์ค‘ DTC ๋™์‹œ ๋ฐœ์ƒ ์‹œ ๊ทผ๋ณธ ์›์ธ ์˜ˆ์ธก ๋ชจ๋ธ ๊ณ ๋„ํ™” ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def context(ev, fleet):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 4] ์—ฐ๊ด€ ๊ทœ์น™ ๋งˆ์ด๋‹(Apriori/FP-Growth) ๊ธฐ๋ฐ˜ ๋‹ค์ค‘ DTC ๋™์‹œ ๋ฐœ์ƒ ์‹œ ๊ทผ๋ณธ ์›์ธ ์˜ˆ์ธก ๋ชจ๋ธ ๊ณ ๋„ํ™” ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def feats(ev, fleet):\n", " X = multi_hot(ev)\n", " n = X.sum(axis=1, keepdims=True) / 3\n", " return np.hstack([X, n, context(ev, fleet)])\n", "\n", "\n", "def monthly_counts(ev):\n", " return ev.groupby([\"month\", \"part\"]).size().unstack(fill_value=0).reindex(columns=PID, fill_value=0)\n", "\n", "\n", "def build_monthly(ev, fleet, top_n=8):\n", " \"\"\"๋ถ€ํ’ˆ๋ณ„ ์›”๊ฐ„ ๊ณ ์žฅ ๊ฑด์ˆ˜ ์˜ˆ์ธก์šฉ ํ‘œ: ์ง€์—ฐ๊ฐ’(lag), ์ด๋™ํ‰๊ท , ์ถ”์„ธ, ๊ธฐ๋Œ€ ๊ฑด์ˆ˜(๊ฐ€๋™์‹œ๊ฐ„ x ์„ค๊ณ„ ๊ณ ์žฅ๋ฅ ), ๊ธฐ์˜จ ์ƒํ˜ธ์ž‘์šฉ, ๊ณ„์ ˆ ์„ฑ๋ถ„\"\"\"\n", " mc = ev.groupby([\"month\", \"part\"]).size().unstack(fill_value=0)\n", " top = ev[\"part\"].value_counts().index[:top_n]\n", " mh, tm = fleet.attrs[\"month_h\"], ev.groupby(\"month\")[\"temp\"].mean().values\n", " rows = []\n", " for p in top:\n", " c = mc[p].values.astype(float)\n", " for m in range(3, MONTHS):\n", " L = np.log1p(c)\n", " rows.append({\"part\": p, \"month\": m, \"y\": c[m], \"lag1\": L[m - 1], \"lag2\": L[m - 2], \"lag3\": L[m - 3], \"roll3\": L[m - 3:m].mean(),\n", " \"trend\": L[m - 1] - L[m - 2], \"roll3_std\": L[m - 3:m].std(), \"log_expected\": np.log(mh[m] / 1000 * RATE[p]),\n", " \"rate_lag1\": c[m - 1] / (mh[m - 1] / 1000) / RATE[p], \"temp\": tm[m] / 10, \"cold_elec\": max(0, 12 - tm[m]) / 10 * (p in (\"P03\", \"P04\")),\n", " \"heat_cool\": max(0, tm[m] - 12) / 10 * (p in (\"P06\", \"P16\")), \"sin\": np.sin(2 * np.pi * m / 12), \"cos\": np.cos(2 * np.pi * m / 12)})\n", " D = pd.DataFrame(rows)\n", " return D, [c for c in D.columns if c not in (\"part\", \"month\", \"y\")], top\n", "\n", "fleet, ev, log = make_data()\n", "ev[\"first\"] = ev[\"codes\"].str[0]\n", "train, test = ev[ev[\"month\"] < 12], ev[ev[\"month\"] >= 12]\n", "\n", "\n", "# 1) Apriori: ํ•ญ๋ชฉ = DTC ์ฝ”๋“œ + ๊ทผ๋ณธ ์›์ธ ํ•ญ๋ชฉ(ROOT:๋ถ€ํ’ˆ). ์ˆ˜์ค€๋ณ„๋กœ ํ›„๋ณด๋ฅผ ๋งŒ๋“ค๊ณ  ์ตœ์†Œ ์ง€์ง€๋„ ๋ฏธ๋งŒ์€ ๊ฐ€์ง€์น˜๊ธฐํ•œ๋‹ค.\n", "def apriori(trans, min_sup, max_len=3):\n", " n = len(trans)\n", " cnt = Counter(i for t in trans for i in t)\n", " freq = {frozenset([i]): c / n for i, c in cnt.items() if c / n >= min_sup}\n", " allf, cur = dict(freq), list(freq)\n", " for k in range(2, max_len + 1):\n", " cands = {a | b for a, b in itertools.combinations(cur, 2) if len(a | b) == k}\n", " cands = {c for c in cands if all(frozenset(s) in allf for s in itertools.combinations(c, k - 1))} # ๋ถ€๋ถ„์ง‘ํ•ฉ ๊ฐ€์ง€์น˜๊ธฐ\n", " cc = Counter(c for t in trans for c in cands if c <= t)\n", " cur = [c for c, v in cc.items() if v / n >= min_sup]\n", " allf.update({c: cc[c] / n for c in cur})\n", " return allf\n", "\n", "\n", "trans = [frozenset(cs) | {\"ROOT:\" + p} for cs, p in zip(train[\"codes\"], train[\"part\"])]\n", "F = apriori(trans, min_sup=0.004)\n", "rules = []\n", "for s, sup in F.items():\n", " roots = [i for i in s if i.startswith(\"ROOT:\")]\n", " if len(roots) == 1 and len(s) >= 2:\n", " ante = s - set(roots)\n", " conf = sup / F[ante]\n", " lift = conf / F[frozenset(roots)]\n", " rules.append({\"ante\": ante, \"part\": roots[0][5:], \"sup\": sup, \"conf\": conf, \"lift\": lift})\n", "R = pd.DataFrame(rules)\n", "R = R[(R[\"conf\"] >= 0.5) & (R[\"lift\"] > 1.5)].sort_values(\"conf\", ascending=False).reset_index(drop=True)\n", "print(f\"[๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ํ•™์Šต ์ •๋น„ ๊ฑด {len(train)}๊ฑด: ๋นˆ๋ฐœ ํ•ญ๋ชฉ์ง‘ํ•ฉ {len(F)}๊ฐœ -> ์—ฐ๊ด€ ๊ทœ์น™ {len(R)}๊ฐœ (์‹ ๋ขฐ๋„ 0.5 ์ด์ƒ, ํ–ฅ์ƒ๋„ 1.5 ์ดˆ๊ณผ)\")\n", "for r in R.head(4).itertuples():\n", " print(f\" {{{', '.join(sorted(r.ante))}}} -> {PNAME[r.part]} ์ง€์ง€๋„ {r.sup:.3f} ์‹ ๋ขฐ๋„ {r.conf:.2f} ํ–ฅ์ƒ๋„ {r.lift:.1f}\")\n", "\n", "mp = train.groupby(\"first\")[\"part\"].agg(lambda s: s.value_counts().index[0])\n", "rl = sorted(zip(R[\"ante\"], R[\"part\"], R[\"conf\"]), key=lambda x: (-x[2], -len(x[0])))\n", "\n", "\n", "def rule_predict(codes, fallback):\n", " for a, p, c in rl:\n", " if a <= set(codes):\n", " return p, c\n", " return fallback, 0.0\n", "\n", "\n", "pr = [rule_predict(cs, mp.get(f, \"P01\")) for cs, f in zip(test[\"codes\"], test[\"first\"])]\n", "pred_rule = np.array([p for p, _ in pr]); pred_base = test[\"first\"].map(mp).values\n", "a_b, a_r = (pred_base == test[\"part\"].values).mean(), (pred_rule == test[\"part\"].values).mean()\n", "multi = test[\"codes\"].str.len().values >= 2\n", "print(f\"\\n๊ทผ๋ณธ ์›์ธ ์ •ํ™•๋„: ๋‹จ์ผ ์ฝ”๋“œ Baseline {a_b:.1%} -> ์—ฐ๊ด€ ๊ทœ์น™ {a_r:.1%} (์ฝ”๋“œ 2๊ฐœ ์ด์ƒ ๋™์‹œ ๋ฐœ์ƒ ๊ฑด {(pred_base[multi] == test['part'].values[multi]).mean():.1%} -> {(pred_rule[multi] == test['part'].values[multi]).mean():.1%})\")\n", "\n", "# 2) ํŒŒ์ƒ ํ”ผ์ฒ˜ + ์›”๊ฐ„ ๊ณ ์žฅ ๊ฑด์ˆ˜ ๊ธฐ์ค€ ๋ชจ๋ธ(์ƒ์œ„ 8๊ฐœ ๋ถ€ํ’ˆ, ๊ฑด์ˆ˜๋Š” ๋กœ๊ทธ ๋ณ€ํ™˜ํ•ด ํ•™์Šต)\n", "D, FE, top = build_monthly(ev, fleet)\n", "print(f\"\\n์›”๊ฐ„ ๊ณ ์žฅ ๊ฑด์ˆ˜ ์˜ˆ์ธก: ํŒŒ์ƒ ํ”ผ์ฒ˜ {len(FE)}๊ฐœ (๋ชฉํ‘œ 10๊ฐœ ์ด์ƒ: {'๋‹ฌ์„ฑ' if len(FE) >= 10 else '๋ฏธ๋‹ฌ'}), ๋Œ€์ƒ {len(top)}๊ฐœ ๋ถ€ํ’ˆ x {MONTHS - 3}๊ฐœ์›”\")\n", "\n", "\n", "def model():\n", " return make_pipeline(StandardScaler(), Ridge(alpha=3.0))\n", "\n", "\n", "def mape(a, p):\n", " return float(np.mean(np.abs(a - p) / a))\n", "\n", "\n", "# 3) ์‹œ๊ณ„์—ด ๊ต์ฐจ๊ฒ€์ฆ: ํ™•์žฅ ์œˆ๋„(๊ณผ๊ฑฐ๋กœ๋งŒ ํ•™์Šต, ๋’ค 2๊ฐœ์›” ๊ฒ€์ฆ)\n", "folds, mapes = [(range(3, s), range(s, s + 2)) for s in (11, 13, 15)], []\n", "for tr, te in folds:\n", " a, b = D[D[\"month\"].isin(tr)], D[D[\"month\"].isin(te)]\n", " mapes.append(mape(b[\"y\"].values, np.exp(model().fit(a[FE], np.log(a[\"y\"])).predict(b[FE]))))\n", "cv = float(np.mean(mapes))\n", "print(\"์‹œ๊ณ„์—ด ๊ต์ฐจ๊ฒ€์ฆ ํด๋“œ๋ณ„ MAPE: \" + \", \".join(f\"{m:.1%}\" for m in mapes) + f\" -> ํ‰๊ท  {cv:.1%} (๋ชฉํ‘œ 25% ์ดํ•˜: {'๋‹ฌ์„ฑ' if cv <= 0.25 else '๋ฏธ๋‹ฌ'})\")\n", "\n", "# 4) ํ”ผ์ฒ˜ ์ค‘์š”๋„(์ˆœ์—ด ์ค‘์š”๋„)๋กœ ์ฐจ์› ์ถ•์†Œ\n", "a, b = D[D[\"month\"] < 13], D[D[\"month\"] >= 13]\n", "m = model().fit(a[FE], np.log(a[\"y\"]))\n", "imp = permutation_importance(m, b[FE], np.log(b[\"y\"]), n_repeats=20, random_state=0, scoring=\"neg_mean_absolute_error\").importances_mean\n", "order = np.argsort(-imp)\n", "keep = [FE[i] for i in order[:8]]\n", "m5 = model().fit(a[keep], np.log(a[\"y\"]))\n", "full, red = mape(b[\"y\"].values, np.exp(m.predict(b[FE]))), mape(b[\"y\"].values, np.exp(m5.predict(b[keep])))\n", "print(\"ํ”ผ์ฒ˜ ์ค‘์š”๋„ ์ƒ์œ„ 5: \" + \", \".join(f\"{FE[i]}({imp[i]:.2f})\" for i in order[:5]))\n", "print(f\"์ฐจ์› ์ถ•์†Œ {len(FE)}๊ฐœ -> 8๊ฐœ: ๊ฒ€์ฆ MAPE {full:.1%} -> {red:.1%}\")\n", "\n", "fig, ax = plt.subplots(1, 2, figsize=(10, 3.5))\n", "ax[0].bar([\"baseline\", \"rules\"], [a_b, a_r]); ax[0].set_title(\"Root-cause accuracy\")\n", "ax[1].barh([FE[i] for i in order[:8]][::-1], imp[order[:8]][::-1]); ax[1].set_title(\"Permutation importance (log MAE)\")\n", "plt.tight_layout(); plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 04 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ํ•™์Šต ์ •๋น„ ๊ฑด 6791๊ฑด: ๋นˆ๋ฐœ ํ•ญ๋ชฉ์ง‘ํ•ฉ 188๊ฐœ -> ์—ฐ๊ด€ ๊ทœ์น™ 57๊ฐœ (์‹ ๋ขฐ๋„ 0.5 ์ด์ƒ, ํ–ฅ์ƒ๋„ 1.5 ์ดˆ๊ณผ)\n", " {G04} -> ์œ ์•• ํŽŒํ”„ ์ง€์ง€๋„ 0.020 ์‹ ๋ขฐ๋„ 1.00 ํ–ฅ์ƒ๋„ 29.1\n", " {G05} -> ์ œ๋™ ๋ฐธ๋ธŒ ์ง€์ง€๋„ 0.007 ์‹ ๋ขฐ๋„ 1.00 ํ–ฅ์ƒ๋„ 87.1\n", " {C051, G03} -> ๋ณ€์†๊ธฐ ์ œ์–ด๋ชจ๋“ˆ ์ง€์ง€๋„ 0.021 ์‹ ๋ขฐ๋„ 1.00 ํ–ฅ์ƒ๋„ 15.5\n", " {C071, G04} -> ์œ ์•• ํŽŒํ”„ ์ง€์ง€๋„ 0.013 ์‹ ๋ขฐ๋„ 1.00 ํ–ฅ์ƒ๋„ 29.1\n", "\n", "๊ทผ๋ณธ ์›์ธ ์ •ํ™•๋„: ๋‹จ์ผ ์ฝ”๋“œ Baseline 68.7% -> ์—ฐ๊ด€ ๊ทœ์น™ 78.3% (์ฝ”๋“œ 2๊ฐœ ์ด์ƒ ๋™์‹œ ๋ฐœ์ƒ ๊ฑด 67.1% -> 81.1%)\n", "\n", "์›”๊ฐ„ ๊ณ ์žฅ ๊ฑด์ˆ˜ ์˜ˆ์ธก: ํŒŒ์ƒ ํ”ผ์ฒ˜ 13๊ฐœ (๋ชฉํ‘œ 10๊ฐœ ์ด์ƒ: ๋‹ฌ์„ฑ), ๋Œ€์ƒ 8๊ฐœ ๋ถ€ํ’ˆ x 15๊ฐœ์›”\n", "์‹œ๊ณ„์—ด ๊ต์ฐจ๊ฒ€์ฆ ํด๋“œ๋ณ„ MAPE: 28.9%, 17.3%, 17.8% -> ํ‰๊ท  21.3% (๋ชฉํ‘œ 25% ์ดํ•˜: ๋‹ฌ์„ฑ)\n", "ํ”ผ์ฒ˜ ์ค‘์š”๋„ ์ƒ์œ„ 5: lag1(0.21), log_expected(0.18), lag2(0.14), cold_elec(0.09), rate_lag1(0.07)\n", "์ฐจ์› ์ถ•์†Œ 13๊ฐœ -> 8๊ฐœ: ๊ฒ€์ฆ MAPE 18.6% -> 21.6%\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 05. DTC ๊ธฐ๋ฐ˜ ์ •๋น„ ์กฐ์น˜ ๊ฐ€์ด๋“œ๋ผ์ธ ๋ฐ ์ถ”์ฒœ ์‹ ๋ขฐ๋„ ์ ์ˆ˜ ์‚ฐ์ถœ ์—”์ง„ ๊ฐœ๋ฐœ\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 3.5์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "DTC ๊ธฐ๋ฐ˜ ์ •๋น„ ์กฐ์น˜ ๊ฐ€์ด๋“œ๋ผ์ธ ๋ฐ ์ถ”์ฒœ ์‹ ๋ขฐ๋„ ์ ์ˆ˜ ์‚ฐ์ถœ ์—”์ง„์„ ๊ฐœ๋ฐœํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **DTC ์ž…๋ ฅ์œผ๋กœ ์ •๋น„ ์กฐ์น˜ ๊ฐ€์ด๋“œ๋ผ์ธ๊ณผ ์ถ”์ฒœ ์‹ ๋ขฐ๋„ ์ ์ˆ˜๋ฅผ ์‚ฐ์ถœํ•˜๋Š” ์—”์ง„์„ ๊ฐœ๋ฐœํ•˜๊ณ  ๊ธฐ์ค€ ๋ชจ๋ธ ๋Œ€๋น„ ๊ฐœ์„ ์„ ํ™•์ธํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **์ฝ”๋“œ ์œ ํ˜•๋ณ„ ๊ฐ€์ค‘์น˜๋ฅผ ๋‘” ๊ณ ์žฅ ๋ถ€ํ’ˆ ํ™•๋ฅ  ๋ชจ๋ธ๋กœ ์ƒ์œ„ 3๊ฐœ ํ›„๋ณด์™€ ์‹ ๋ขฐ๋„๋ฅผ ๋‚ด๊ณ , ํ›„๋ณด๋ณ„ ์ •๋น„ ์กฐ์น˜ ์ ˆ์ฐจ๋ฅผ ์ง„๋‹จ ์นด๋“œ๋กœ ์ถœ๋ ฅ ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `src/models/mnt03_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": [ "\"\"\"DTC ์กฐํ•ฉ๊ณผ ์ฐจ๋Ÿ‰ ๋งฅ๋ฝ์„ ์ž…๋ ฅ๋ฐ›์•„ ํ›„๋ณด ๊ณ ์žฅ ๋ถ€ํ’ˆ 3๊ฐœ์™€ ์ถ”์ฒœ ์‹ ๋ขฐ๋„ ์ ์ˆ˜, ์ •๋น„ ์กฐ์น˜ ๊ฐ€์ด๋“œ๋ผ์ธ์„ ์‚ฐ์ถœํ•˜๋Š” ์ถ”์ฒœ ์—”์ง„์„ ๊ฐœ๋ฐœํ•˜๊ณ , ์‹ ๋ขฐ๋„๊ฐ€ ์‹ค์ œ ์ •ํ™•๋„์™€ ๋งž๋Š”์ง€(๋ณด์ •) ํ™•์ธํ•œ๋‹ค. ๋ถ€ํ’ˆ๋ณ„ ์›”๊ฐ„ ๊ณ ์žฅ ๊ฑด์ˆ˜ ์˜ˆ์ธก์€ ์ „์›”๊ฐ’ ๊ธฐ์ค€ ๋ชจ๋ธ ๋Œ€๋น„ ๋ฆฟ์ง€ยท๊ทธ๋ž˜๋””์–ธํŠธ ๋ถ€์ŠคํŒ… ์•™์ƒ๋ธ”์˜ MAE/RMSE ๊ฐœ์„ ์œจ๊ณผ R2๋กœ ํ‰๊ฐ€ํ•œ๋‹ค.\"\"\"\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from sklearn.linear_model import LogisticRegression, Ridge\n", "from sklearn.ensemble import GradientBoostingRegressor\n", "from sklearn.preprocessing import StandardScaler\n", "from sklearn.pipeline import make_pipeline\n", "from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ƒ ์ฐจ๋Ÿ‰ 100๋Œ€์˜ 18๊ฐœ์›” ๊ณ ์žฅ ์ง„๋‹จ ์ฝ”๋“œ(DTC)ยท์ •๋น„ ์ด๋ ฅ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ๋ถ€๋Œ€ยท์žฅ๋น„ยท์ •๋น„ ์‹ค์  ์—†์Œ)\n", "# ๋ถ€ํ’ˆ 16์ข…(1000๊ฐ€๋™์‹œ๊ฐ„๋‹น ๊ณ ์žฅ๋ฅ  ์ฐจ๋“ฑ), DTC 28์ข…(๋ถ€ํ’ˆ ๊ณ ์œ  ์ฝ”๋“œ, ์ง ๋ถ€ํ’ˆ ๊ณต์œ  ์ฝ”๋“œ, ๊ณ„ํ†ต ์ฆ์ƒ ์ฝ”๋“œ)\n", "# ๊ณ ์žฅ 1๊ฑด(์ •๋น„ ์ด๋ ฅ)๋งˆ๋‹ค DTC๊ฐ€ 1~5๊ฐœ ๋™์‹œ ๋ฐœ์ƒํ•˜๊ณ , ์—ฐ์‡„ ๊ณ ์žฅ ์ฝ”๋“œ์™€ ์ผ์‹œ ์žก์Œ ์ฝ”๋“œ๊ฐ€ ์„ž์ธ๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "PARTS = [ # (ID, ์ด๋ฆ„, ๊ณ„ํ†ต, 1000๊ฐ€๋™์‹œ๊ฐ„๋‹น ๊ณ ์žฅ๋ฅ , ๊ฐ€์ƒ ๋‹จ๊ฐ€์ง€์ˆ˜, ์ˆ˜๋ฆฌ์‹œ๊ฐ„ h)\n", " (\"P01\", \"์—ฐ๋ฃŒ๋ถ„์‚ฌ๊ธฐ\", \"engine\", 12.0, 3.0, 2.0), (\"P02\", \"ํ„ฐ๋ณด์ฐจ์ €\", \"engine\", 9.0, 8.0, 5.0),\n", " (\"P03\", \"์ถ•์ „์ง€\", \"electric\", 6.5, 4.0, 1.5), (\"P04\", \"๋ฐœ์ „๊ธฐ\", \"electric\", 4.0, 9.0, 3.0),\n", " (\"P05\", \"๋ณ€์†๊ธฐ ์ œ์–ด๋ชจ๋“ˆ\", \"drive\", 3.0, 12.0, 6.0), (\"P06\", \"๋ƒ‰๊ฐ์ˆ˜ ํŽŒํ”„\", \"engine\", 2.2, 3.5, 3.0),\n", " (\"P07\", \"์œ ์•• ํŽŒํ”„\", \"hydraulic\", 1.6, 10.0, 4.0), (\"P08\", \"์†๋„ ์„ผ์„œ\", \"drive\", 1.2, 2.0, 1.5),\n", " (\"P09\", \"ECU ์ปค๋„ฅํ„ฐ\", \"electric\", 0.9, 2.5, 1.5), (\"P10\", \"์—ฐ๋ฃŒ ํ•„ํ„ฐ\", \"engine\", 0.7, 0.8, 0.7),\n", " (\"P11\", \"์ œ๋™ ๋ฐธ๋ธŒ\", \"brake\", 0.5, 6.0, 3.0), (\"P12\", \"์กฐํ–ฅ ์•ก์ถ”์—์ดํ„ฐ\", \"drive\", 0.4, 9.0, 5.0),\n", " (\"P13\", \"๊ณต๊ธฐ ํ•„ํ„ฐ\", \"engine\", 0.3, 0.5, 0.5), (\"P14\", \"ํ†ต์‹  ์ธํ„ฐํŽ˜์ด์Šค ๋ชจ๋“ˆ\", \"electric\", 0.25, 3.0, 1.5),\n", " (\"P15\", \"์œคํ™œ์œ  ์••๋ ฅ ์„ผ์„œ\", \"engine\", 0.2, 1.0, 1.0), (\"P16\", \"ํŒฌ ๊ตฌ๋™ ๋ชจํ„ฐ\", \"cooling\", 0.15, 2.0, 2.0)]\n", "PID = [p[0] for p in PARTS]\n", "PNAME = {p[0]: p[1] for p in PARTS}; PSYS = {p[0]: p[2] for p in PARTS}; RATE = {p[0]: p[3] for p in PARTS}\n", "COST = {p[0]: p[4] for p in PARTS}; REPH = {p[0]: p[5] for p in PARTS}\n", "PAIRS = [(\"P01\", \"P02\"), (\"P03\", \"P04\"), (\"P05\", \"P08\"), (\"P06\", \"P10\"), (\"P09\", \"P14\"), (\"P13\", \"P15\")]\n", "SHARED = {p: f\"S{k + 1:02d}\" for k, pr in enumerate(PAIRS) for p in pr} # ์ง ๋ถ€ํ’ˆ์ด ํ•จ๊ป˜ ์“ฐ๋Š” ๋ชจํ˜ธํ•œ ์ฝ”๋“œ\n", "SPEC = {p: f\"C{p[1:]}1\" for p in PID} # ๋ถ€ํ’ˆ ๊ณ ์œ  ์ฝ”๋“œ\n", "SYS = [\"engine\", \"electric\", \"drive\", \"hydraulic\", \"brake\", \"cooling\"]\n", "SYMP = {s: f\"G{i + 1:02d}\" for i, s in enumerate(SYS)} # ๊ณ„ํ†ต ์ฆ์ƒ ์ฝ”๋“œ\n", "CODES = sorted(set(SPEC.values()) | set(SHARED.values()) | set(SYMP.values()))\n", "CASCADE = {\"P01\": [(\"P10\", 0.20)], \"P02\": [(\"P06\", 0.20)], \"P03\": [(\"P04\", 0.25)], \"P04\": [(\"P09\", 0.25)],\n", " \"P05\": [(\"P08\", 0.30)], \"P06\": [(\"P16\", 0.30)], \"P09\": [(\"P14\", 0.20)], \"P12\": [(\"P07\", 0.20)]}\n", "CRIT_SYS = {\"engine\", \"drive\", \"brake\"} # ๊ธฐ๋™ยท์ œ๋™ ๋ถˆ๋Šฅ ์œ„ํ—˜ ๊ณ„ํ†ต(๊ฒฝ๋ณด ๋Œ€์ƒ)\n", "MONTHS = 18\n", "\n", "\n", "def make_fleet(n=100, seed=0):\n", " rng = np.random.default_rng(seed)\n", " f = pd.DataFrame({\"veh\": [f\"V{i:03d}\" for i in range(n)], \"age\": rng.integers(1, 26, n).astype(float),\n", " \"terrain\": rng.uniform(0, 1, n), \"load\": rng.uniform(0, 1, n), \"pm_gap\": rng.uniform(20, 200, n),\n", " \"humid\": rng.uniform(0, 1, n), \"base_h\": rng.uniform(55, 125, n)})\n", " f[\"mult\"] = np.exp(0.045 * (f[\"age\"] - 13) + 0.9 * (f[\"terrain\"] - .5) + 0.8 * (f[\"load\"] - .5)\n", " + 0.006 * (f[\"pm_gap\"] - 110) + 0.7 * (f[\"humid\"] - .5))\n", " return f\n", "\n", "\n", "def make_data(seed=1, months=MONTHS):\n", " \"\"\"๋ฐ˜ํ™˜: fleet(์ฐจ๋Ÿ‰ ์†์„ฑ), ev(์ •๋น„ ์ด๋ ฅ 1๊ฑด=1ํ–‰: ๋ฐœ์ƒ DTC ๋ชฉ๋ก, ์‹ค์ œ ๊ณ ์žฅ ๋ถ€ํ’ˆ), log(DTC ๋ฐœ์ƒ ๋กœ๊ทธ, ํ–‰=์ฝ”๋“œ 1๊ฑด)\"\"\"\n", " rng = np.random.default_rng(seed)\n", " fleet = make_fleet()\n", " V = len(fleet)\n", " temp = 12 + 14 * np.sin(2 * np.pi * (np.arange(months) - 3) / 12) # ์›”ํ‰๊ท  ๊ธฐ์˜จ(๊ฐ€์ƒ)\n", " hours = fleet[\"base_h\"].values[:, None] * (1 + 0.25 * np.sin(2 * np.pi * np.arange(months) / 12 + 1)) * rng.lognormal(0, .12, (V, months))\n", " fleet[\"hours\"] = hours.sum(axis=1) # ์ฐจ๋Ÿ‰๋ณ„ 18๊ฐœ์›” ์ด ๊ฐ€๋™์‹œ๊ฐ„\n", " fleet.attrs[\"month_h\"] = hours.sum(axis=0) # ์›”๋ณ„ ์ „์ฒด ๊ฐ€๋™์‹œ๊ฐ„(๊ณ ์žฅ๋ฅ  ๋ถ„๋ชจ)\n", " evs = []\n", " for p in PID:\n", " s = np.ones(months)\n", " if p in (\"P03\", \"P04\"): s = np.exp(-0.06 * (temp - 12)) # ํ•œ๋žญ๊ธฐ ์ „๊ธฐ๊ณ„ํ†ต ์ฆ๊ฐ€\n", " if p in (\"P06\", \"P16\"): s = np.exp(0.05 * (temp - 12)) # ํ˜น์„œ๊ธฐ ๋ƒ‰๊ฐ๊ณ„ํ†ต ์ฆ๊ฐ€\n", " cnt = rng.poisson(RATE[p] * hours / 1000 * fleet[\"mult\"].values[:, None] * s[None, :])\n", " for v, m in zip(*np.nonzero(cnt)):\n", " for _ in range(cnt[v, m]):\n", " evs.append((fleet[\"veh\"][v], int(m), m * 720 + rng.uniform(0, 720), p, float(temp[m] + rng.normal(0, 3))))\n", " ev = pd.DataFrame(evs, columns=[\"veh\", \"month\", \"t\", \"part\", \"temp\"]).sort_values(\"t\").reset_index(drop=True)\n", " ev[\"ev_id\"] = [f\"W{i:05d}\" for i in range(len(ev))]\n", " rows, codes = [], []\n", " for e in ev.itertuples():\n", " c, r = [], rng.random(3)\n", " if r[0] < 0.6: c.append(SPEC[e.part])\n", " if e.part in SHARED and r[1] < 0.6: c.append(SHARED[e.part])\n", " if r[2] < 0.5: c.append(SYMP[PSYS[e.part]])\n", " if not c: c.append(SHARED.get(e.part, SPEC[e.part]))\n", " for q, pr in CASCADE.get(e.part, []):\n", " if rng.random() < pr: c.append(SPEC[q])\n", " if rng.random() < 0.12: c.append(SPEC[PID[rng.integers(0, 16)]])\n", " c = list(dict.fromkeys(c))\n", " rng.shuffle(c) # ๋™์‹œ ๋ฐœ์ƒ ์ฝ”๋“œ์˜ ๊ธฐ๋ก ์ˆœ์„œ๋Š” ์ž„์˜\n", " codes.append(c)\n", " for k, cd in enumerate(c):\n", " rows.append((e.ev_id, e.veh, e.t + k * 0.1, cd))\n", " ev[\"codes\"] = codes\n", " log = pd.DataFrame(rows, columns=[\"ev_id\", \"veh\", \"ts\", \"code\"])\n", " return fleet, ev, log\n", "\n", "# ---------------------------------------------------------------------------\n", "# ๊ณตํ†ต ๋ถ„์„ ์กฐ๊ฐ: ์ฝ”๋“œ ๋ฉ€ํ‹ฐํ•ซ ํ–‰๋ ฌ, ์ฐจ๋Ÿ‰ ๋งฅ๋ฝ ํ”ผ์ฒ˜, ํ•™์Šต/๊ฒ€์ฆ ๋ถ„ํ• (์•ž 12๊ฐœ์›” ํ•™์Šต, ๋’ค 6๊ฐœ์›” ๊ฒ€์ฆ)\n", "# ---------------------------------------------------------------------------\n", "CIDX = {c: i for i, c in enumerate(CODES)}\n", "SPLIT_M = 12\n", "\n", "\n", "def multi_hot(ev):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 5] DTC ๊ธฐ๋ฐ˜ ์ •๋น„ ์กฐ์น˜ ๊ฐ€์ด๋“œ๋ผ์ธ ๋ฐ ์ถ”์ฒœ ์‹ ๋ขฐ๋„ ์ ์ˆ˜ ์‚ฐ์ถœ ์—”์ง„ ๊ฐœ๋ฐœ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def context(ev, fleet):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 5] DTC ๊ธฐ๋ฐ˜ ์ •๋น„ ์กฐ์น˜ ๊ฐ€์ด๋“œ๋ผ์ธ ๋ฐ ์ถ”์ฒœ ์‹ ๋ขฐ๋„ ์ ์ˆ˜ ์‚ฐ์ถœ ์—”์ง„ ๊ฐœ๋ฐœ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def feats(ev, fleet):\n", " X = multi_hot(ev)\n", " n = X.sum(axis=1, keepdims=True) / 3\n", " return np.hstack([X, n, context(ev, fleet)])\n", "\n", "\n", "def monthly_counts(ev):\n", " return ev.groupby([\"month\", \"part\"]).size().unstack(fill_value=0).reindex(columns=PID, fill_value=0)\n", "\n", "\n", "def build_monthly(ev, fleet, top_n=8):\n", " \"\"\"๋ถ€ํ’ˆ๋ณ„ ์›”๊ฐ„ ๊ณ ์žฅ ๊ฑด์ˆ˜ ์˜ˆ์ธก์šฉ ํ‘œ: ์ง€์—ฐ๊ฐ’(lag), ์ด๋™ํ‰๊ท , ์ถ”์„ธ, ๊ธฐ๋Œ€ ๊ฑด์ˆ˜(๊ฐ€๋™์‹œ๊ฐ„ x ์„ค๊ณ„ ๊ณ ์žฅ๋ฅ ), ๊ธฐ์˜จ ์ƒํ˜ธ์ž‘์šฉ, ๊ณ„์ ˆ ์„ฑ๋ถ„\"\"\"\n", " mc = ev.groupby([\"month\", \"part\"]).size().unstack(fill_value=0)\n", " top = ev[\"part\"].value_counts().index[:top_n]\n", " mh, tm = fleet.attrs[\"month_h\"], ev.groupby(\"month\")[\"temp\"].mean().values\n", " rows = []\n", " for p in top:\n", " c = mc[p].values.astype(float)\n", " for m in range(3, MONTHS):\n", " L = np.log1p(c)\n", " rows.append({\"part\": p, \"month\": m, \"y\": c[m], \"lag1\": L[m - 1], \"lag2\": L[m - 2], \"lag3\": L[m - 3], \"roll3\": L[m - 3:m].mean(),\n", " \"trend\": L[m - 1] - L[m - 2], \"roll3_std\": L[m - 3:m].std(), \"log_expected\": np.log(mh[m] / 1000 * RATE[p]),\n", " \"rate_lag1\": c[m - 1] / (mh[m - 1] / 1000) / RATE[p], \"temp\": tm[m] / 10, \"cold_elec\": max(0, 12 - tm[m]) / 10 * (p in (\"P03\", \"P04\")),\n", " \"heat_cool\": max(0, tm[m] - 12) / 10 * (p in (\"P06\", \"P16\")), \"sin\": np.sin(2 * np.pi * m / 12), \"cos\": np.cos(2 * np.pi * m / 12)})\n", " D = pd.DataFrame(rows)\n", " return D, [c for c in D.columns if c not in (\"part\", \"month\", \"y\")], top\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๋ถ€ํ’ˆ๋ณ„ ์ •๋น„ ์กฐ์น˜ ๊ฐ€์ด๋“œ๋ผ์ธ(์š”์•ฝ 3๋‹จ๊ณ„). ์‹ค์ œ ์ •๋น„ ๊ต๋ฒ”์ด ์•„๋‹ˆ๋ผ ๊ต์œก์šฉ์œผ๋กœ ๋งŒ๋“  ์˜ˆ์‹œ ๋ฌธ๊ตฌ๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "GUIDE = {\n", " \"P01\": \"์—ฐ๋ฃŒ ์••๋ ฅ ์ธก์ • -> ๋ถ„์‚ฌ๊ธฐ ๋ˆ„์„คยท๋ง‰ํž˜ ํ™•์ธ -> ๋ถˆ๋Ÿ‰ ์‹œ ๋ถ„์‚ฌ๊ธฐ ๊ต์ฒด\", \"P02\": \"ํก๊ธฐ ์••๋ ฅยท์ด๋ฌผ ์ ๊ฒ€ -> ์ถ• ์œ ๊ฒฉ ํ™•์ธ -> ๋ถˆ๋Ÿ‰ ์‹œ ํ„ฐ๋ณด์ฐจ์ € ๊ต์ฒด\",\n", " \"P03\": \"๋‹จ์ž ์ „์••ยท๋ถ€์‹ ์ ๊ฒ€ -> ์ถฉ์ „ ๊ณ„ํ†ต ํ™•์ธ -> ๋ถˆ๋Ÿ‰ ์‹œ ์ถ•์ „์ง€ ๊ต์ฒด\", \"P04\": \"๋ฐœ์ „ ์ „์•• ์ธก์ • -> ๋ฒจํŠธยท๋ฐฐ์„  ์ ๊ฒ€ -> ๋ถˆ๋Ÿ‰ ์‹œ ๋ฐœ์ „๊ธฐ ๊ต์ฒด\",\n", " \"P05\": \"์ œ์–ด๋ชจ๋“ˆ ์†Œํ”„ํŠธ์›จ์–ดยท์ปค๋„ฅํ„ฐ ์ ๊ฒ€ -> ๋ณ€์† ์ด๋ ฅ ํ™•์ธ -> ๋ถˆ๋Ÿ‰ ์‹œ ๋ชจ๋“ˆ ๊ต์ฒด\", \"P06\": \"๋ƒ‰๊ฐ์ˆ˜๋Ÿ‰ยท๋ˆ„์ˆ˜ ์ ๊ฒ€ -> ํŽŒํ”„ ์ž‘๋™์Œ ํ™•์ธ -> ๋ถˆ๋Ÿ‰ ์‹œ ํŽŒํ”„ ๊ต์ฒด\",\n", " \"P07\": \"์œ ์•• ์˜ค์ผ๋Ÿ‰ยท์••๋ ฅ ์ ๊ฒ€ -> ํŽŒํ”„ ๋ˆ„์„ค ํ™•์ธ -> ๋ถˆ๋Ÿ‰ ์‹œ ํŽŒํ”„ ๊ต์ฒด\", \"P08\": \"์„ผ์„œ ๋ฐฐ์„ ยท๊ฐ„๊ทน ์ ๊ฒ€ -> ์‹ ํ˜ธ ํŒŒํ˜• ํ™•์ธ -> ๋ถˆ๋Ÿ‰ ์‹œ ์„ผ์„œ ๊ต์ฒด\",\n", " \"P09\": \"์ปค๋„ฅํ„ฐ ์ ‘์ด‰ยท๋ถ€์‹ ์ ๊ฒ€ -> ํ•€ ์ƒํƒœ ํ™•์ธ -> ๋ถˆ๋Ÿ‰ ์‹œ ์ปค๋„ฅํ„ฐ ๊ต์ฒด\", \"P10\": \"ํ•„ํ„ฐ ์˜ค์—ผ๋„ ์ ๊ฒ€ -> ์—ฐ๋ฃŒ ํ๋ฆ„ ํ™•์ธ -> ๋ถˆ๋Ÿ‰ ์‹œ ํ•„ํ„ฐ ๊ต์ฒด\",\n", " \"P11\": \"์ œ๋™ ์••๋ ฅ ์ ๊ฒ€ -> ๋ฐธ๋ธŒ ์ž‘๋™ ํ™•์ธ -> ๋ถˆ๋Ÿ‰ ์‹œ ๋ฐธ๋ธŒ ๊ต์ฒด(์•ˆ์ „ ํ™•์ธ ํ•„์ˆ˜)\", \"P12\": \"์กฐํ–ฅ ๋ฐ˜์‘ ์ ๊ฒ€ -> ์•ก์ถ”์—์ดํ„ฐ ์ž‘๋™ ํ™•์ธ -> ๋ถˆ๋Ÿ‰ ์‹œ ๊ต์ฒด(์•ˆ์ „ ํ™•์ธ ํ•„์ˆ˜)\",\n", " \"P13\": \"ํ•„ํ„ฐ ์ฐจ์•• ์ ๊ฒ€ -> ์˜ค์—ผ ํ™•์ธ -> ๋ถˆ๋Ÿ‰ ์‹œ ํ•„ํ„ฐ ๊ต์ฒด\", \"P14\": \"ํ†ต์‹  ๋งํฌ ์ƒํƒœ ์ ๊ฒ€ -> ๋ชจ๋“ˆ ์ „์› ํ™•์ธ -> ๋ถˆ๋Ÿ‰ ์‹œ ๋ชจ๋“ˆ ๊ต์ฒด\",\n", " \"P15\": \"์••๋ ฅ ์„ผ์„œ ์‹ ํ˜ธ ์ ๊ฒ€ -> ์œคํ™œ์œ ๋Ÿ‰ ํ™•์ธ -> ๋ถˆ๋Ÿ‰ ์‹œ ์„ผ์„œ ๊ต์ฒด\", \"P16\": \"ํŒฌ ๋ชจํ„ฐ ์ „๋ฅ˜ ์ ๊ฒ€ -> ํšŒ์ „ ํ™•์ธ -> ๋ถˆ๋Ÿ‰ ์‹œ ๋ชจํ„ฐ ๊ต์ฒด\"}\n", "\n", "fleet, ev, log = make_data()\n", "train, test = ev[ev[\"month\"] < SPLIT_M], ev[ev[\"month\"] >= SPLIT_M]\n", "\n", "# 1) ์ถ”์ฒœ ์—”์ง„: DTC ๋ฉ€ํ‹ฐํ•ซ + ๋งฅ๋ฝ -> ๋ถ€ํ’ˆ๋ณ„ ํ™•๋ฅ (= ์ถ”์ฒœ ์‹ ๋ขฐ๋„ ์ ์ˆ˜)\n", "Xtr, Xte = feats(train, fleet), feats(test, fleet)\n", "clf = LogisticRegression(C=3, max_iter=500).fit(Xtr, train[\"part\"])\n", "P = clf.predict_proba(Xte)\n", "cls = clf.classes_\n", "top1 = cls[P.argmax(axis=1)]\n", "conf = P.max(axis=1)\n", "y = test[\"part\"].values\n", "top3 = np.mean([t in cls[np.argsort(-p)[:3]] for t, p in zip(y, P)])\n", "mp = train.assign(first=train[\"codes\"].str[0]).groupby(\"first\")[\"part\"].agg(lambda s: s.value_counts().index[0])\n", "base_acc = (test[\"codes\"].str[0].map(mp).values == y).mean()\n", "print(f\"[๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ์ถ”์ฒœ ์—”์ง„ ๊ฒ€์ฆ {len(test)}๊ฑด: top-1 ์ •ํ™•๋„ {(top1 == y).mean():.1%} (๋‹จ์ผ ์ฝ”๋“œ Baseline {base_acc:.1%}), top-3 ํฌํ•จ๋ฅ  {top3:.1%}\")\n", "\n", "# 2) ์‹ ๋ขฐ๋„ ์ ์ˆ˜ ๋ณด์ •: ์‹ ๋ขฐ๋„ ๊ตฌ๊ฐ„๋ณ„ ์‹ค์ œ ์ •ํ™•๋„\n", "bins = [0, 0.5, 0.7, 0.9, 1.01]\n", "lab = [\"~0.5\", \"0.5~0.7\", \"0.7~0.9\", \"0.9~\"]\n", "cb = pd.cut(conf, bins, labels=lab, right=False)\n", "cal = pd.DataFrame({\"conf\": conf, \"ok\": top1 == y, \"b\": cb}).groupby(\"b\", observed=True).agg(๊ฑด์ˆ˜=(\"ok\", \"size\"), ํ‰๊ท ์‹ ๋ขฐ๋„=(\"conf\", \"mean\"), ์‹ค์ œ์ •ํ™•๋„=(\"ok\", \"mean\"))\n", "print(\"\\n[์‹ ๋ขฐ๋„ ๋ณด์ •] ๊ตฌ๊ฐ„ | ๊ฑด์ˆ˜ | ํ‰๊ท  ์‹ ๋ขฐ๋„ | ์‹ค์ œ ์ •ํ™•๋„\")\n", "for k, r in cal.iterrows():\n", " print(f\" {k:8s} {int(r['๊ฑด์ˆ˜']):5d} {r['ํ‰๊ท ์‹ ๋ขฐ๋„']:.2f} {r['์‹ค์ œ์ •ํ™•๋„']:.2f}\")\n", "ece = float((cal[\"๊ฑด์ˆ˜\"] * (cal[\"ํ‰๊ท ์‹ ๋ขฐ๋„\"] - cal[\"์‹ค์ œ์ •ํ™•๋„\"]).abs()).sum() / cal[\"๊ฑด์ˆ˜\"].sum())\n", "print(f\"๊ธฐ๋Œ€ ๋ณด์ • ์˜ค์ฐจ(ECE) {ece:.3f}\")\n", "\n", "# 3) ์ •๋น„ ์กฐ์น˜ ๊ฐ€์ด๋“œ๋ผ์ธ ์นด๋“œ(์ƒ์œ„ ํ›„๋ณด 3๊ฐœ์™€ ์‹ ๋ขฐ๋„)\n", "print(\"\\n[์ง„๋‹จ ์นด๋“œ ์˜ˆ์‹œ] ์ •๋น„ ์กฐ์น˜ ๊ฐ€์ด๋“œ๋ผ์ธ\")\n", "for i in (int(np.argmin(np.abs(conf - 0.95))), int(np.argmin(np.abs(conf - 0.55)))):\n", " order = np.argsort(-P[i])[:3]\n", " print(f\" ์ž…๋ ฅ DTC {test['codes'].iloc[i]} -> \" + \", \".join(f\"{PNAME[cls[j]]} {P[i, j]:.0%}\" for j in order))\n", " print(f\" ์กฐ์น˜: {GUIDE[cls[order[0]]]}\" + (\"\" if conf[i] >= 0.7 else \" (์‹ ๋ขฐ๋„ ๋‚ฎ์Œ: ์ˆ™๋ จ ์ •๋น„๊ด€ ํ™•์ธ ๊ถŒ๊ณ )\"))\n", "\n", "# 4) ์›”๊ฐ„ ๊ณ ์žฅ ๊ฑด์ˆ˜ ์˜ˆ์ธก: ๊ธฐ์ค€(์ „์›”๊ฐ’) vs ๊ณ ๋„ํ™”(๋ฆฟ์ง€ + GBR ์•™์ƒ๋ธ”, ์†Œ๊ทœ๋ชจ ๊ฒฉ์ž ํƒ์ƒ‰)\n", "D, FE, top = build_monthly(ev, fleet)\n", "a, b = D[D[\"month\"] < 13], D[D[\"month\"] >= 13]\n", "ya, yb = np.log(a[\"y\"]), b[\"y\"].values\n", "naive = np.expm1(b[\"lag1\"].values)\n", "def ens(tr, te, al, dep):\n", " g = GradientBoostingRegressor(n_estimators=150, max_depth=dep, learning_rate=0.05, random_state=0).fit(tr[FE], np.log(tr[\"y\"]))\n", " r = make_pipeline(StandardScaler(), Ridge(alpha=al)).fit(tr[FE], np.log(tr[\"y\"]))\n", " return np.exp(0.5 * g.predict(te[FE]) + 0.5 * r.predict(te[FE]))\n", "\n", "\n", "inner_tr, inner_va = a[a[\"month\"] < 11], a[a[\"month\"] >= 11] # ํ•˜์ดํผํŒŒ๋ผ๋ฏธํ„ฐ๋Š” ํ•™์Šต ๊ตฌ๊ฐ„ ์•ˆ์—์„œ๋งŒ ์„ ํƒ(๊ฒ€์ฆ ๊ตฌ๊ฐ„ ๋ฏธ์‚ฌ์šฉ)\n", "score = {(al, dep): mean_absolute_error(inner_va[\"y\"], ens(inner_tr, inner_va, al, dep)) for al in (1, 3, 10) for dep in (2, 3)}\n", "al, dep = min(score, key=score.get)\n", "adv = ens(a, b, al, dep)\n", "met = lambda p: (mean_absolute_error(yb, p), mean_squared_error(yb, p) ** 0.5, r2_score(yb, p))\n", "m0, m1 = met(naive), met(adv)\n", "print(f\"\\n[์›”๊ฐ„ ๊ณ ์žฅ ๊ฑด์ˆ˜ ์˜ˆ์ธก] ๊ฒ€์ฆ {len(b)}๊ฑด(์ƒ์œ„ {len(top)}๊ฐœ ๋ถ€ํ’ˆ x 5๊ฐœ์›”), ์„ ํƒ ํ•˜์ดํผํŒŒ๋ผ๋ฏธํ„ฐ ridge alpha={al}, GBR depth={dep}\")\n", "print(f\" ๊ธฐ์ค€(์ „์›”๊ฐ’) MAE {m0[0]:.2f} RMSE {m0[1]:.2f} R2 {m0[2]:.3f}\")\n", "print(f\" ๊ณ ๋„ํ™”(์•™์ƒ๋ธ”) MAE {m1[0]:.2f} RMSE {m1[1]:.2f} R2 {m1[2]:.3f}\")\n", "imp_mae, imp_rmse = 1 - m1[0] / m0[0], 1 - m1[1] / m0[1]\n", "print(f\"๊ฐœ์„ ์œจ MAE {imp_mae:.1%}, RMSE {imp_rmse:.1%} (๋ชฉํ‘œ 20% ์ด์ƒ: {'๋‹ฌ์„ฑ' if min(imp_mae, imp_rmse) >= 0.2 else '๋ฏธ๋‹ฌ'}), R2 {m1[2]:.3f} (๋ชฉํ‘œ 0.85 ์ด์ƒ: {'๋‹ฌ์„ฑ' if m1[2] >= 0.85 else '๋ฏธ๋‹ฌ'})\")\n", "\n", "fig, ax = plt.subplots(1, 2, figsize=(10, 3.5))\n", "ax[0].plot([0, 1], [0, 1], \"k--\"); ax[0].plot(cal[\"ํ‰๊ท ์‹ ๋ขฐ๋„\"], cal[\"์‹ค์ œ์ •ํ™•๋„\"], marker=\"o\"); ax[0].set_title(\"Confidence calibration\")\n", "ax[1].scatter(yb, adv, s=12, label=\"ensemble\"); ax[1].scatter(yb, naive, s=12, label=\"naive\"); ax[1].plot([0, yb.max()], [0, yb.max()], \"k--\")\n", "ax[1].set_title(\"Monthly failure count: actual vs predicted\"); ax[1].legend()\n", "plt.tight_layout(); plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 05 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ์ถ”์ฒœ ์—”์ง„ ๊ฒ€์ฆ 3890๊ฑด: top-1 ์ •ํ™•๋„ 83.9% (๋‹จ์ผ ์ฝ”๋“œ Baseline 68.7%), top-3 ํฌํ•จ๋ฅ  99.5%\n", "\n", "[์‹ ๋ขฐ๋„ ๋ณด์ •] ๊ตฌ๊ฐ„ | ๊ฑด์ˆ˜ | ํ‰๊ท  ์‹ ๋ขฐ๋„ | ์‹ค์ œ ์ •ํ™•๋„\n", " ~0.5 155 0.46 0.48\n", " 0.5~0.7 1123 0.60 0.61\n", " 0.7~0.9 387 0.78 0.74\n", " 0.9~ 2225 0.99 1.00\n", "๊ธฐ๋Œ€ ๋ณด์ • ์˜ค์ฐจ(ECE) 0.014\n", "\n", "[์ง„๋‹จ ์นด๋“œ ์˜ˆ์‹œ] ์ •๋น„ ์กฐ์น˜ ๊ฐ€์ด๋“œ๋ผ์ธ\n", " ์ž…๋ ฅ DTC ['G01', 'C101', 'S01'] -> ์—ฐ๋ฃŒ๋ถ„์‚ฌ๊ธฐ 95%, ํ„ฐ๋ณด์ฐจ์ € 5%, ์—ฐ๋ฃŒ ํ•„ํ„ฐ 0%\n", " ์กฐ์น˜: ์—ฐ๋ฃŒ ์••๋ ฅ ์ธก์ • -> ๋ถ„์‚ฌ๊ธฐ ๋ˆ„์„คยท๋ง‰ํž˜ ํ™•์ธ -> ๋ถˆ๋Ÿ‰ ์‹œ ๋ถ„์‚ฌ๊ธฐ ๊ต์ฒด\n", " ์ž…๋ ฅ DTC ['G01'] -> ์—ฐ๋ฃŒ๋ถ„์‚ฌ๊ธฐ 55%, ํ„ฐ๋ณด์ฐจ์ € 34%, ์—ฐ๋ฃŒ ํ•„ํ„ฐ 5%\n", " ์กฐ์น˜: ์—ฐ๋ฃŒ ์••๋ ฅ ์ธก์ • -> ๋ถ„์‚ฌ๊ธฐ ๋ˆ„์„คยท๋ง‰ํž˜ ํ™•์ธ -> ๋ถˆ๋Ÿ‰ ์‹œ ๋ถ„์‚ฌ๊ธฐ ๊ต์ฒด (์‹ ๋ขฐ๋„ ๋‚ฎ์Œ: ์ˆ™๋ จ ์ •๋น„๊ด€ ํ™•์ธ ๊ถŒ๊ณ )\n", "\n", "[์›”๊ฐ„ ๊ณ ์žฅ ๊ฑด์ˆ˜ ์˜ˆ์ธก] ๊ฒ€์ฆ 40๊ฑด(์ƒ์œ„ 8๊ฐœ ๋ถ€ํ’ˆ x 5๊ฐœ์›”), ์„ ํƒ ํ•˜์ดํผํŒŒ๋ผ๋ฏธํ„ฐ ridge alpha=10, GBR depth=3\n", " ๊ธฐ์ค€(์ „์›”๊ฐ’) MAE 15.50 RMSE 21.96 R2 0.860\n", " ๊ณ ๋„ํ™”(์•™์ƒ๋ธ”) MAE 10.62 RMSE 14.47 R2 0.939\n", "๊ฐœ์„ ์œจ MAE 31.5%, RMSE 34.1% (๋ชฉํ‘œ 20% ์ด์ƒ: ๋‹ฌ์„ฑ), R2 0.939 (๋ชฉํ‘œ 0.85 ์ด์ƒ: ๋‹ฌ์„ฑ)\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 06. ์ •๋น„๋ณ‘์šฉ ๋ชจ๋ฐ”์ผ/์›น ๊ณ ์žฅ ์ฝ”๋“œ ์ง„๋‹จ ๋ฐ ์ •๋น„ ์กฐ์น˜ ์ ˆ์ฐจ ๋Œ€์‹œ๋ณด๋“œ ๊ตฌํ˜„\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 3.0์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "์ •๋น„๋ณ‘์šฉ ๋ชจ๋ฐ”์ผ/์›น ๊ณ ์žฅ ์ฝ”๋“œ ์ง„๋‹จ ๋Œ€์‹œ๋ณด๋“œ๋ฅผ ๊ตฌํ˜„ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **์ •๋น„๋ณ‘์šฉ ๊ณ ์žฅ ์ฝ”๋“œ ์ง„๋‹จ ํ™”๋ฉด(์ง„๋‹จ ์นด๋“œ์™€ ์›น ๋Œ€์‹œ๋ณด๋“œ ๊ตฌ์„ฑ)์„ ๊ตฌํ˜„ํ•˜๊ณ , ์˜ˆ์ธก๋งˆ๋‹ค ๊ธฐ์—ฌ๋„ ์ƒ์œ„ 3๊ฐœ ์ธ์ž์™€ ์ž์—ฐ์–ด ์š”์•ฝ์„ ์ž๋™ ์‚ฐ์ถœํ•˜๋Š” ์„ค๋ช…๊ฐ€๋Šฅ์„ฑ(XAI) ๋ชจ๋“ˆ์„ ๋งŒ๋“ ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **๋ชจ๋ธ์˜ ์ž…๋ ฅ ์ธ์ž๋ณ„ ๊ธฐ์—ฌ๋„๋ฅผ ๊ณ„์‚ฐํ•ด ๊ฑด๋งˆ๋‹ค ์ƒ์œ„ 3๊ฐœ ๊ทผ๊ฑฐ๋ฅผ ๋ฝ‘๊ณ , ์ธ์ž๋ฅผ ํ‰๊ท ๊ฐ’์œผ๋กœ ๋˜๋Œ๋ ธ์„ ๋•Œ ์˜ˆ์ธก ํ™•๋ฅ ์ด ๋‚ฎ์•„์ง€๋Š”์ง€๋กœ ๊ทผ๊ฑฐ ์œ ํšจ์„ฑ์„ ์ ๊ฒ€ ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `src/explainability/mnt03_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": [ "\"\"\"์ •๋น„๋ณ‘์ด ๋ชจ๋ฐ”์ผ/์›น ํ™”๋ฉด์—์„œ DTC๋ฅผ ์ž…๋ ฅํ•˜๋ฉด ํ›„๋ณด ๊ณ ์žฅ ๋ถ€ํ’ˆยท์‹ ๋ขฐ๋„ยท์ •๋น„ ์กฐ์น˜ ์ ˆ์ฐจ์™€ ํŒ๋‹จ ๊ทผ๊ฑฐ(๊ธฐ์—ฌ๋„ ์ƒ์œ„ 3๊ฐœ ์ธ์ž)๋ฅผ ๋ณด์—ฌ ์ฃผ๋Š” ์ง„๋‹จ ๋Œ€์‹œ๋ณด๋“œ๋ฅผ ๊ตฌํ˜„ํ•œ๋‹ค. ๋ชจ๋“  ์˜ˆ์ธก ๊ฑด์— ์ƒ์œ„ 3๊ฐœ ์ธ์ž๋ฅผ ์ž๋™ ์‚ฐ์ถœํ•˜๊ณ  ๊ทผ๊ฑฐ ์ œ๊ฑฐ ์‹œ ํ™•๋ฅ ์ด ์‹ค์ œ๋กœ ๋–จ์–ด์ง€๋Š”์ง€ ์ ๊ฒ€ํ•˜๋ฉฐ, ์ž์—ฐ์–ด ์š”์•ฝ๊ณผ ๊ฒฝ๋ณด ํ˜„ํ™ฉ ํ™”๋ฉด์„ ๋งŒ๋“ ๋‹ค.\"\"\"\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from sklearn.linear_model import LogisticRegression\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ƒ ์ฐจ๋Ÿ‰ 100๋Œ€์˜ 18๊ฐœ์›” ๊ณ ์žฅ ์ง„๋‹จ ์ฝ”๋“œ(DTC)ยท์ •๋น„ ์ด๋ ฅ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ๋ถ€๋Œ€ยท์žฅ๋น„ยท์ •๋น„ ์‹ค์  ์—†์Œ)\n", "# ๋ถ€ํ’ˆ 16์ข…(1000๊ฐ€๋™์‹œ๊ฐ„๋‹น ๊ณ ์žฅ๋ฅ  ์ฐจ๋“ฑ), DTC 28์ข…(๋ถ€ํ’ˆ ๊ณ ์œ  ์ฝ”๋“œ, ์ง ๋ถ€ํ’ˆ ๊ณต์œ  ์ฝ”๋“œ, ๊ณ„ํ†ต ์ฆ์ƒ ์ฝ”๋“œ)\n", "# ๊ณ ์žฅ 1๊ฑด(์ •๋น„ ์ด๋ ฅ)๋งˆ๋‹ค DTC๊ฐ€ 1~5๊ฐœ ๋™์‹œ ๋ฐœ์ƒํ•˜๊ณ , ์—ฐ์‡„ ๊ณ ์žฅ ์ฝ”๋“œ์™€ ์ผ์‹œ ์žก์Œ ์ฝ”๋“œ๊ฐ€ ์„ž์ธ๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "PARTS = [ # (ID, ์ด๋ฆ„, ๊ณ„ํ†ต, 1000๊ฐ€๋™์‹œ๊ฐ„๋‹น ๊ณ ์žฅ๋ฅ , ๊ฐ€์ƒ ๋‹จ๊ฐ€์ง€์ˆ˜, ์ˆ˜๋ฆฌ์‹œ๊ฐ„ h)\n", " (\"P01\", \"์—ฐ๋ฃŒ๋ถ„์‚ฌ๊ธฐ\", \"engine\", 12.0, 3.0, 2.0), (\"P02\", \"ํ„ฐ๋ณด์ฐจ์ €\", \"engine\", 9.0, 8.0, 5.0),\n", " (\"P03\", \"์ถ•์ „์ง€\", \"electric\", 6.5, 4.0, 1.5), (\"P04\", \"๋ฐœ์ „๊ธฐ\", \"electric\", 4.0, 9.0, 3.0),\n", " (\"P05\", \"๋ณ€์†๊ธฐ ์ œ์–ด๋ชจ๋“ˆ\", \"drive\", 3.0, 12.0, 6.0), (\"P06\", \"๋ƒ‰๊ฐ์ˆ˜ ํŽŒํ”„\", \"engine\", 2.2, 3.5, 3.0),\n", " (\"P07\", \"์œ ์•• ํŽŒํ”„\", \"hydraulic\", 1.6, 10.0, 4.0), (\"P08\", \"์†๋„ ์„ผ์„œ\", \"drive\", 1.2, 2.0, 1.5),\n", " (\"P09\", \"ECU ์ปค๋„ฅํ„ฐ\", \"electric\", 0.9, 2.5, 1.5), (\"P10\", \"์—ฐ๋ฃŒ ํ•„ํ„ฐ\", \"engine\", 0.7, 0.8, 0.7),\n", " (\"P11\", \"์ œ๋™ ๋ฐธ๋ธŒ\", \"brake\", 0.5, 6.0, 3.0), (\"P12\", \"์กฐํ–ฅ ์•ก์ถ”์—์ดํ„ฐ\", \"drive\", 0.4, 9.0, 5.0),\n", " (\"P13\", \"๊ณต๊ธฐ ํ•„ํ„ฐ\", \"engine\", 0.3, 0.5, 0.5), (\"P14\", \"ํ†ต์‹  ์ธํ„ฐํŽ˜์ด์Šค ๋ชจ๋“ˆ\", \"electric\", 0.25, 3.0, 1.5),\n", " (\"P15\", \"์œคํ™œ์œ  ์••๋ ฅ ์„ผ์„œ\", \"engine\", 0.2, 1.0, 1.0), (\"P16\", \"ํŒฌ ๊ตฌ๋™ ๋ชจํ„ฐ\", \"cooling\", 0.15, 2.0, 2.0)]\n", "PID = [p[0] for p in PARTS]\n", "PNAME = {p[0]: p[1] for p in PARTS}; PSYS = {p[0]: p[2] for p in PARTS}; RATE = {p[0]: p[3] for p in PARTS}\n", "COST = {p[0]: p[4] for p in PARTS}; REPH = {p[0]: p[5] for p in PARTS}\n", "PAIRS = [(\"P01\", \"P02\"), (\"P03\", \"P04\"), (\"P05\", \"P08\"), (\"P06\", \"P10\"), (\"P09\", \"P14\"), (\"P13\", \"P15\")]\n", "SHARED = {p: f\"S{k + 1:02d}\" for k, pr in enumerate(PAIRS) for p in pr} # ์ง ๋ถ€ํ’ˆ์ด ํ•จ๊ป˜ ์“ฐ๋Š” ๋ชจํ˜ธํ•œ ์ฝ”๋“œ\n", "SPEC = {p: f\"C{p[1:]}1\" for p in PID} # ๋ถ€ํ’ˆ ๊ณ ์œ  ์ฝ”๋“œ\n", "SYS = [\"engine\", \"electric\", \"drive\", \"hydraulic\", \"brake\", \"cooling\"]\n", "SYMP = {s: f\"G{i + 1:02d}\" for i, s in enumerate(SYS)} # ๊ณ„ํ†ต ์ฆ์ƒ ์ฝ”๋“œ\n", "CODES = sorted(set(SPEC.values()) | set(SHARED.values()) | set(SYMP.values()))\n", "CASCADE = {\"P01\": [(\"P10\", 0.20)], \"P02\": [(\"P06\", 0.20)], \"P03\": [(\"P04\", 0.25)], \"P04\": [(\"P09\", 0.25)],\n", " \"P05\": [(\"P08\", 0.30)], \"P06\": [(\"P16\", 0.30)], \"P09\": [(\"P14\", 0.20)], \"P12\": [(\"P07\", 0.20)]}\n", "CRIT_SYS = {\"engine\", \"drive\", \"brake\"} # ๊ธฐ๋™ยท์ œ๋™ ๋ถˆ๋Šฅ ์œ„ํ—˜ ๊ณ„ํ†ต(๊ฒฝ๋ณด ๋Œ€์ƒ)\n", "MONTHS = 18\n", "\n", "\n", "def make_fleet(n=100, seed=0):\n", " rng = np.random.default_rng(seed)\n", " f = pd.DataFrame({\"veh\": [f\"V{i:03d}\" for i in range(n)], \"age\": rng.integers(1, 26, n).astype(float),\n", " \"terrain\": rng.uniform(0, 1, n), \"load\": rng.uniform(0, 1, n), \"pm_gap\": rng.uniform(20, 200, n),\n", " \"humid\": rng.uniform(0, 1, n), \"base_h\": rng.uniform(55, 125, n)})\n", " f[\"mult\"] = np.exp(0.045 * (f[\"age\"] - 13) + 0.9 * (f[\"terrain\"] - .5) + 0.8 * (f[\"load\"] - .5)\n", " + 0.006 * (f[\"pm_gap\"] - 110) + 0.7 * (f[\"humid\"] - .5))\n", " return f\n", "\n", "\n", "def make_data(seed=1, months=MONTHS):\n", " \"\"\"๋ฐ˜ํ™˜: fleet(์ฐจ๋Ÿ‰ ์†์„ฑ), ev(์ •๋น„ ์ด๋ ฅ 1๊ฑด=1ํ–‰: ๋ฐœ์ƒ DTC ๋ชฉ๋ก, ์‹ค์ œ ๊ณ ์žฅ ๋ถ€ํ’ˆ), log(DTC ๋ฐœ์ƒ ๋กœ๊ทธ, ํ–‰=์ฝ”๋“œ 1๊ฑด)\"\"\"\n", " rng = np.random.default_rng(seed)\n", " fleet = make_fleet()\n", " V = len(fleet)\n", " temp = 12 + 14 * np.sin(2 * np.pi * (np.arange(months) - 3) / 12) # ์›”ํ‰๊ท  ๊ธฐ์˜จ(๊ฐ€์ƒ)\n", " hours = fleet[\"base_h\"].values[:, None] * (1 + 0.25 * np.sin(2 * np.pi * np.arange(months) / 12 + 1)) * rng.lognormal(0, .12, (V, months))\n", " fleet[\"hours\"] = hours.sum(axis=1) # ์ฐจ๋Ÿ‰๋ณ„ 18๊ฐœ์›” ์ด ๊ฐ€๋™์‹œ๊ฐ„\n", " fleet.attrs[\"month_h\"] = hours.sum(axis=0) # ์›”๋ณ„ ์ „์ฒด ๊ฐ€๋™์‹œ๊ฐ„(๊ณ ์žฅ๋ฅ  ๋ถ„๋ชจ)\n", " evs = []\n", " for p in PID:\n", " s = np.ones(months)\n", " if p in (\"P03\", \"P04\"): s = np.exp(-0.06 * (temp - 12)) # ํ•œ๋žญ๊ธฐ ์ „๊ธฐ๊ณ„ํ†ต ์ฆ๊ฐ€\n", " if p in (\"P06\", \"P16\"): s = np.exp(0.05 * (temp - 12)) # ํ˜น์„œ๊ธฐ ๋ƒ‰๊ฐ๊ณ„ํ†ต ์ฆ๊ฐ€\n", " cnt = rng.poisson(RATE[p] * hours / 1000 * fleet[\"mult\"].values[:, None] * s[None, :])\n", " for v, m in zip(*np.nonzero(cnt)):\n", " for _ in range(cnt[v, m]):\n", " evs.append((fleet[\"veh\"][v], int(m), m * 720 + rng.uniform(0, 720), p, float(temp[m] + rng.normal(0, 3))))\n", " ev = pd.DataFrame(evs, columns=[\"veh\", \"month\", \"t\", \"part\", \"temp\"]).sort_values(\"t\").reset_index(drop=True)\n", " ev[\"ev_id\"] = [f\"W{i:05d}\" for i in range(len(ev))]\n", " rows, codes = [], []\n", " for e in ev.itertuples():\n", " c, r = [], rng.random(3)\n", " if r[0] < 0.6: c.append(SPEC[e.part])\n", " if e.part in SHARED and r[1] < 0.6: c.append(SHARED[e.part])\n", " if r[2] < 0.5: c.append(SYMP[PSYS[e.part]])\n", " if not c: c.append(SHARED.get(e.part, SPEC[e.part]))\n", " for q, pr in CASCADE.get(e.part, []):\n", " if rng.random() < pr: c.append(SPEC[q])\n", " if rng.random() < 0.12: c.append(SPEC[PID[rng.integers(0, 16)]])\n", " c = list(dict.fromkeys(c))\n", " rng.shuffle(c) # ๋™์‹œ ๋ฐœ์ƒ ์ฝ”๋“œ์˜ ๊ธฐ๋ก ์ˆœ์„œ๋Š” ์ž„์˜\n", " codes.append(c)\n", " for k, cd in enumerate(c):\n", " rows.append((e.ev_id, e.veh, e.t + k * 0.1, cd))\n", " ev[\"codes\"] = codes\n", " log = pd.DataFrame(rows, columns=[\"ev_id\", \"veh\", \"ts\", \"code\"])\n", " return fleet, ev, log\n", "\n", "# ---------------------------------------------------------------------------\n", "# ๊ณตํ†ต ๋ถ„์„ ์กฐ๊ฐ: ์ฝ”๋“œ ๋ฉ€ํ‹ฐํ•ซ ํ–‰๋ ฌ, ์ฐจ๋Ÿ‰ ๋งฅ๋ฝ ํ”ผ์ฒ˜, ํ•™์Šต/๊ฒ€์ฆ ๋ถ„ํ• (์•ž 12๊ฐœ์›” ํ•™์Šต, ๋’ค 6๊ฐœ์›” ๊ฒ€์ฆ)\n", "# ---------------------------------------------------------------------------\n", "CIDX = {c: i for i, c in enumerate(CODES)}\n", "SPLIT_M = 12\n", "\n", "\n", "def multi_hot(ev):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 6] ์ •๋น„๋ณ‘์šฉ ๋ชจ๋ฐ”์ผ/์›น ๊ณ ์žฅ ์ฝ”๋“œ ์ง„๋‹จ ๋ฐ ์ •๋น„ ์กฐ์น˜ ์ ˆ์ฐจ ๋Œ€์‹œ๋ณด๋“œ ๊ตฌํ˜„ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def context(ev, fleet):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 6] ์ •๋น„๋ณ‘์šฉ ๋ชจ๋ฐ”์ผ/์›น ๊ณ ์žฅ ์ฝ”๋“œ ์ง„๋‹จ ๋ฐ ์ •๋น„ ์กฐ์น˜ ์ ˆ์ฐจ ๋Œ€์‹œ๋ณด๋“œ ๊ตฌํ˜„ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def feats(ev, fleet):\n", " X = multi_hot(ev)\n", " n = X.sum(axis=1, keepdims=True) / 3\n", " return np.hstack([X, n, context(ev, fleet)])\n", "\n", "\n", "def monthly_counts(ev):\n", " return ev.groupby([\"month\", \"part\"]).size().unstack(fill_value=0).reindex(columns=PID, fill_value=0)\n", "\n", "\n", "def build_monthly(ev, fleet, top_n=8):\n", " \"\"\"๋ถ€ํ’ˆ๋ณ„ ์›”๊ฐ„ ๊ณ ์žฅ ๊ฑด์ˆ˜ ์˜ˆ์ธก์šฉ ํ‘œ: ์ง€์—ฐ๊ฐ’(lag), ์ด๋™ํ‰๊ท , ์ถ”์„ธ, ๊ธฐ๋Œ€ ๊ฑด์ˆ˜(๊ฐ€๋™์‹œ๊ฐ„ x ์„ค๊ณ„ ๊ณ ์žฅ๋ฅ ), ๊ธฐ์˜จ ์ƒํ˜ธ์ž‘์šฉ, ๊ณ„์ ˆ ์„ฑ๋ถ„\"\"\"\n", " mc = ev.groupby([\"month\", \"part\"]).size().unstack(fill_value=0)\n", " top = ev[\"part\"].value_counts().index[:top_n]\n", " mh, tm = fleet.attrs[\"month_h\"], ev.groupby(\"month\")[\"temp\"].mean().values\n", " rows = []\n", " for p in top:\n", " c = mc[p].values.astype(float)\n", " for m in range(3, MONTHS):\n", " L = np.log1p(c)\n", " rows.append({\"part\": p, \"month\": m, \"y\": c[m], \"lag1\": L[m - 1], \"lag2\": L[m - 2], \"lag3\": L[m - 3], \"roll3\": L[m - 3:m].mean(),\n", " \"trend\": L[m - 1] - L[m - 2], \"roll3_std\": L[m - 3:m].std(), \"log_expected\": np.log(mh[m] / 1000 * RATE[p]),\n", " \"rate_lag1\": c[m - 1] / (mh[m - 1] / 1000) / RATE[p], \"temp\": tm[m] / 10, \"cold_elec\": max(0, 12 - tm[m]) / 10 * (p in (\"P03\", \"P04\")),\n", " \"heat_cool\": max(0, tm[m] - 12) / 10 * (p in (\"P06\", \"P16\")), \"sin\": np.sin(2 * np.pi * m / 12), \"cos\": np.cos(2 * np.pi * m / 12)})\n", " D = pd.DataFrame(rows)\n", " return D, [c for c in D.columns if c not in (\"part\", \"month\", \"y\")], top\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๋ถ€ํ’ˆ๋ณ„ ์ •๋น„ ์กฐ์น˜ ๊ฐ€์ด๋“œ๋ผ์ธ(์š”์•ฝ 3๋‹จ๊ณ„). ์‹ค์ œ ์ •๋น„ ๊ต๋ฒ”์ด ์•„๋‹ˆ๋ผ ๊ต์œก์šฉ์œผ๋กœ ๋งŒ๋“  ์˜ˆ์‹œ ๋ฌธ๊ตฌ๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "GUIDE = {\n", " \"P01\": \"์—ฐ๋ฃŒ ์••๋ ฅ ์ธก์ • -> ๋ถ„์‚ฌ๊ธฐ ๋ˆ„์„คยท๋ง‰ํž˜ ํ™•์ธ -> ๋ถˆ๋Ÿ‰ ์‹œ ๋ถ„์‚ฌ๊ธฐ ๊ต์ฒด\", \"P02\": \"ํก๊ธฐ ์••๋ ฅยท์ด๋ฌผ ์ ๊ฒ€ -> ์ถ• ์œ ๊ฒฉ ํ™•์ธ -> ๋ถˆ๋Ÿ‰ ์‹œ ํ„ฐ๋ณด์ฐจ์ € ๊ต์ฒด\",\n", " \"P03\": \"๋‹จ์ž ์ „์••ยท๋ถ€์‹ ์ ๊ฒ€ -> ์ถฉ์ „ ๊ณ„ํ†ต ํ™•์ธ -> ๋ถˆ๋Ÿ‰ ์‹œ ์ถ•์ „์ง€ ๊ต์ฒด\", \"P04\": \"๋ฐœ์ „ ์ „์•• ์ธก์ • -> ๋ฒจํŠธยท๋ฐฐ์„  ์ ๊ฒ€ -> ๋ถˆ๋Ÿ‰ ์‹œ ๋ฐœ์ „๊ธฐ ๊ต์ฒด\",\n", " \"P05\": \"์ œ์–ด๋ชจ๋“ˆ ์†Œํ”„ํŠธ์›จ์–ดยท์ปค๋„ฅํ„ฐ ์ ๊ฒ€ -> ๋ณ€์† ์ด๋ ฅ ํ™•์ธ -> ๋ถˆ๋Ÿ‰ ์‹œ ๋ชจ๋“ˆ ๊ต์ฒด\", \"P06\": \"๋ƒ‰๊ฐ์ˆ˜๋Ÿ‰ยท๋ˆ„์ˆ˜ ์ ๊ฒ€ -> ํŽŒํ”„ ์ž‘๋™์Œ ํ™•์ธ -> ๋ถˆ๋Ÿ‰ ์‹œ ํŽŒํ”„ ๊ต์ฒด\",\n", " \"P07\": \"์œ ์•• ์˜ค์ผ๋Ÿ‰ยท์••๋ ฅ ์ ๊ฒ€ -> ํŽŒํ”„ ๋ˆ„์„ค ํ™•์ธ -> ๋ถˆ๋Ÿ‰ ์‹œ ํŽŒํ”„ ๊ต์ฒด\", \"P08\": \"์„ผ์„œ ๋ฐฐ์„ ยท๊ฐ„๊ทน ์ ๊ฒ€ -> ์‹ ํ˜ธ ํŒŒํ˜• ํ™•์ธ -> ๋ถˆ๋Ÿ‰ ์‹œ ์„ผ์„œ ๊ต์ฒด\",\n", " \"P09\": \"์ปค๋„ฅํ„ฐ ์ ‘์ด‰ยท๋ถ€์‹ ์ ๊ฒ€ -> ํ•€ ์ƒํƒœ ํ™•์ธ -> ๋ถˆ๋Ÿ‰ ์‹œ ์ปค๋„ฅํ„ฐ ๊ต์ฒด\", \"P10\": \"ํ•„ํ„ฐ ์˜ค์—ผ๋„ ์ ๊ฒ€ -> ์—ฐ๋ฃŒ ํ๋ฆ„ ํ™•์ธ -> ๋ถˆ๋Ÿ‰ ์‹œ ํ•„ํ„ฐ ๊ต์ฒด\",\n", " \"P11\": \"์ œ๋™ ์••๋ ฅ ์ ๊ฒ€ -> ๋ฐธ๋ธŒ ์ž‘๋™ ํ™•์ธ -> ๋ถˆ๋Ÿ‰ ์‹œ ๋ฐธ๋ธŒ ๊ต์ฒด(์•ˆ์ „ ํ™•์ธ ํ•„์ˆ˜)\", \"P12\": \"์กฐํ–ฅ ๋ฐ˜์‘ ์ ๊ฒ€ -> ์•ก์ถ”์—์ดํ„ฐ ์ž‘๋™ ํ™•์ธ -> ๋ถˆ๋Ÿ‰ ์‹œ ๊ต์ฒด(์•ˆ์ „ ํ™•์ธ ํ•„์ˆ˜)\",\n", " \"P13\": \"ํ•„ํ„ฐ ์ฐจ์•• ์ ๊ฒ€ -> ์˜ค์—ผ ํ™•์ธ -> ๋ถˆ๋Ÿ‰ ์‹œ ํ•„ํ„ฐ ๊ต์ฒด\", \"P14\": \"ํ†ต์‹  ๋งํฌ ์ƒํƒœ ์ ๊ฒ€ -> ๋ชจ๋“ˆ ์ „์› ํ™•์ธ -> ๋ถˆ๋Ÿ‰ ์‹œ ๋ชจ๋“ˆ ๊ต์ฒด\",\n", " \"P15\": \"์••๋ ฅ ์„ผ์„œ ์‹ ํ˜ธ ์ ๊ฒ€ -> ์œคํ™œ์œ ๋Ÿ‰ ํ™•์ธ -> ๋ถˆ๋Ÿ‰ ์‹œ ์„ผ์„œ ๊ต์ฒด\", \"P16\": \"ํŒฌ ๋ชจํ„ฐ ์ „๋ฅ˜ ์ ๊ฒ€ -> ํšŒ์ „ ํ™•์ธ -> ๋ถˆ๋Ÿ‰ ์‹œ ๋ชจํ„ฐ ๊ต์ฒด\"}\n", "\n", "SYSKR = {\"engine\": \"์—”์ง„\", \"electric\": \"์ „๊ธฐ\", \"drive\": \"๊ตฌ๋™\", \"hydraulic\": \"์œ ์••\", \"brake\": \"์ œ๋™\", \"cooling\": \"๋ƒ‰๊ฐ\"}\n", "SHNAME = {SHARED[p[0]]: f\"{PNAME[p[0]]}ยท{PNAME[p[1]]} ๊ณต์œ \" for p in PAIRS}\n", "\n", "\n", "def code_name(c):\n", " if c.startswith(\"S\"):\n", " return f\"๊ณต์œ  ์ฝ”๋“œ {c}({SHNAME[c]})\"\n", " if c.startswith(\"G\"):\n", " return f\"{SYSKR[[s for s, v in SYMP.items() if v == c][0]]} ๊ณ„ํ†ต ์ฆ์ƒ ์ฝ”๋“œ {c}\"\n", " return f\"{PNAME['P' + c[1:3]]} ๊ณ ์œ  ์ฝ”๋“œ {c}\"\n", "\n", "\n", "FNAME = [code_name(c) for c in CODES] + [\"๋™์‹œ ๋ฐœ์ƒ ์ฝ”๋“œ ์ˆ˜\", \"์ฐจ๋ น\", \"์˜ˆ๋ฐฉ์ •๋น„ ๊ฒฝ๊ณผ์‹œ๊ฐ„\", \"์ง€ํ˜• ๊ฐ€ํ˜น๋„\", \"์šด์šฉ ๋ถ€ํ•˜\", \"๊ธฐ์˜จ\"]\n", "NC = len(CODES)\n", "\n", "fleet, ev, log = make_data()\n", "train, test = ev[ev[\"month\"] < SPLIT_M], ev[ev[\"month\"] >= SPLIT_M].reset_index(drop=True)\n", "Xtr, Xte = feats(train, fleet), feats(test, fleet)\n", "clf = LogisticRegression(C=3, max_iter=500).fit(Xtr, train[\"part\"])\n", "P = clf.predict_proba(Xte)\n", "cls, mu = clf.classes_, Xtr.mean(axis=0)\n", "pred = P.argmax(axis=1)\n", "\n", "\n", "def explain(i, k=3):\n", " \"\"\"์˜ˆ์ธก ๋ถ€ํ’ˆ์— ๋Œ€ํ•œ ์ž…๋ ฅ ์ธ์ž๋ณ„ ๊ธฐ์—ฌ๋„ = ๊ณ„์ˆ˜ x (์ž…๋ ฅ๊ฐ’ - ํ•™์Šต ํ‰๊ท ). ๊ฐ’์ด ํฐ ์ธ์ž k๊ฐœ๋ฅผ ๋ฐ˜ํ™˜.\"\"\"\n", " contrib = clf.coef_[pred[i]] * (Xte[i] - mu)\n", " contrib[:NC][Xte[i][:NC] == 0] = -np.inf # ์ž…๋ ฅ๋˜์ง€ ์•Š์€ ์ฝ”๋“œ๋Š” ๊ทผ๊ฑฐ์—์„œ ์ œ์™ธ\n", " idx = np.argsort(-contrib)[:k]\n", " return [(FNAME[j], contrib[j]) for j in idx], idx\n", "\n", "\n", "def summary(i):\n", " f, _ = explain(i)\n", " return (f\"{PNAME[cls[pred[i]]]} ๊ณ ์žฅ ๊ฐ€๋Šฅ์„ฑ {P[i, pred[i]]:.0%}. ์ฃผ์š” ๊ทผ๊ฑฐ: \" + \", \".join(n for n, _ in f) + \".\")\n", "\n", "\n", "# 1) ์ „ ์˜ˆ์ธก ๊ฑด ๊ธฐ์—ฌ๋„ ์ž๋™ ์‚ฐ์ถœ + ๊ทผ๊ฑฐ ์œ ํšจ์„ฑ ์ ๊ฒ€\n", "ok3, drop = 0, 0\n", "for i in range(len(test)):\n", " f, idx = explain(i)\n", " ok3 += len(f) == 3\n", " x2 = Xte[i].copy(); x2[idx[0]] = mu[idx[0]] # ๊ฐ€์žฅ ํฐ ๊ทผ๊ฑฐ๋ฅผ ํ‰๊ท ๊ฐ’์œผ๋กœ ๋˜๋Œ๋ ค ๋ณธ๋‹ค\n", " drop += clf.predict_proba(x2[None])[0, pred[i]] < P[i, pred[i]]\n", "print(f\"[๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ์˜ˆ์ธก {len(test)}๊ฑด ์ค‘ ๊ธฐ์—ฌ๋„ ์ƒ์œ„ 3๊ฐœ ์ธ์ž ์‚ฐ์ถœ {ok3}๊ฑด (์‚ฐ์ถœ๋ฅ  {ok3 / len(test):.0%}, ๋ชฉํ‘œ 100%: {'๋‹ฌ์„ฑ' if ok3 == len(test) else '๋ฏธ๋‹ฌ'})\")\n", "print(f\"๊ทผ๊ฑฐ ์œ ํšจ์„ฑ: ์ƒ์œ„ ์ธ์ž๋ฅผ ํ‰๊ท ๊ฐ’์œผ๋กœ ๋˜๋Œ๋ฆฌ๋ฉด ์˜ˆ์ธก ํ™•๋ฅ ์ด ๋‚ฎ์•„์ง€๋Š” ๋น„์œจ {drop / len(test):.1%}\")\n", "\n", "# 2) ๋ชจ๋ฐ”์ผ ์ง„๋‹จ ์นด๋“œ 2๊ฑด: ์‹ ๋ขฐ๋„ ๋†’์Œ / ๋‚ฎ์Œ\n", "print(\"\\n[๋ชจ๋ฐ”์ผ ์ง„๋‹จ ์นด๋“œ]\")\n", "for tgt in (0.95, 0.55):\n", " i = int(np.argmin(np.abs(P.max(axis=1) - tgt)))\n", " f, _ = explain(i)\n", " print(f\" ์ž…๋ ฅ DTC {test['codes'][i]} (์‹ค์ œ ๊ณ ์žฅ {PNAME[test['part'][i]]})\")\n", " print(f\" ์ง„๋‹จ {PNAME[cls[pred[i]]]} ์‹ ๋ขฐ๋„ {P[i, pred[i]]:.0%} | ๊ทผ๊ฑฐ: \" + \" / \".join(f\"{n}({v:+.1f})\" for n, v in f))\n", " print(f\" ์กฐ์น˜ ์ ˆ์ฐจ: {GUIDE[cls[pred[i]]]}\")\n", " print(f\" ์š”์•ฝ: {summary(i)}\")\n", "\n", "# 3) ์›น ๋Œ€์‹œ๋ณด๋“œ: ์ตœ๊ทผ ๋‹ฌ ํ˜„ํ™ฉ๊ณผ ๊ฒฝ๋ณด\n", "last = test[test[\"month\"] == MONTHS - 1]\n", "li = last.index.values\n", "alert = [(PSYS[cls[pred[i]]] in CRIT_SYS) and P[i, pred[i]] >= 0.7 for i in li]\n", "print(f\"\\n[์›น ๋Œ€์‹œ๋ณด๋“œ] ์ตœ๊ทผ ๋‹ฌ ์ •๋น„ ๊ฑด {len(last)}๊ฑด, ๊ธฐ๋™ยท์ œ๋™ ๊ณ„ํ†ต ๊ฒฝ๋ณด {sum(alert)}๊ฑด, ์‹ ๋ขฐ๋„ ๋‚ฎ์Œ(0.7 ๋ฏธ๋งŒ) {int((P[li].max(axis=1) < 0.7).sum())}๊ฑด\")\n", "cnt = pd.Series([cls[pred[i]] for i in li]).value_counts().head(3)\n", "print(\" ๋‹ค๋นˆ๋„ ์ง„๋‹จ ๋ถ€ํ’ˆ: \" + \", \".join(f\"{PNAME[p]} {n}๊ฑด\" for p, n in cnt.items()))\n", "\n", "fig, ax = plt.subplots(2, 2, figsize=(11, 7))\n", "cnt5 = pd.Series([cls[pred[i]] for i in li]).value_counts().head(8)\n", "ax[0, 0].bar(cnt5.index, cnt5.values); ax[0, 0].set_title(\"Diagnosed parts (last month)\")\n", "mon = ev.groupby(\"month\").size(); ax[0, 1].plot(mon.index, mon.values, marker=\"o\"); ax[0, 1].set_title(\"Monthly maintenance cases\")\n", "j = int(np.argmin(np.abs(P.max(axis=1) - 0.55))); f, _ = explain(j)\n", "ax[1, 0].barh([f\"factor {k + 1}\" for k in range(3)][::-1], [v for _, v in f][::-1]); ax[1, 0].set_title(\"Top-3 contributions (low-confidence case)\")\n", "ax[1, 1].hist(P.max(axis=1), bins=20); ax[1, 1].axvline(0.7, ls=\"--\", color=\"k\"); ax[1, 1].set_title(\"Confidence distribution (line = 0.7)\")\n", "plt.tight_layout(); plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 06 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ์˜ˆ์ธก 3890๊ฑด ์ค‘ ๊ธฐ์—ฌ๋„ ์ƒ์œ„ 3๊ฐœ ์ธ์ž ์‚ฐ์ถœ 3890๊ฑด (์‚ฐ์ถœ๋ฅ  100%, ๋ชฉํ‘œ 100%: ๋‹ฌ์„ฑ)\n", "๊ทผ๊ฑฐ ์œ ํšจ์„ฑ: ์ƒ์œ„ ์ธ์ž๋ฅผ ํ‰๊ท ๊ฐ’์œผ๋กœ ๋˜๋Œ๋ฆฌ๋ฉด ์˜ˆ์ธก ํ™•๋ฅ ์ด ๋‚ฎ์•„์ง€๋Š” ๋น„์œจ 92.5%\n", "\n", "[๋ชจ๋ฐ”์ผ ์ง„๋‹จ ์นด๋“œ]\n", " ์ž…๋ ฅ DTC ['G01', 'C101', 'S01'] (์‹ค์ œ ๊ณ ์žฅ ์—ฐ๋ฃŒ๋ถ„์‚ฌ๊ธฐ)\n", " ์ง„๋‹จ ์—ฐ๋ฃŒ๋ถ„์‚ฌ๊ธฐ ์‹ ๋ขฐ๋„ 95% | ๊ทผ๊ฑฐ: ๊ณต์œ  ์ฝ”๋“œ S01(์—ฐ๋ฃŒ๋ถ„์‚ฌ๊ธฐยทํ„ฐ๋ณด์ฐจ์ € ๊ณต์œ )(+4.3) / ์—”์ง„ ๊ณ„ํ†ต ์ฆ์ƒ ์ฝ”๋“œ G01(+3.0) / ์—ฐ๋ฃŒ ํ•„ํ„ฐ ๊ณ ์œ  ์ฝ”๋“œ C101(+1.6)\n", " ์กฐ์น˜ ์ ˆ์ฐจ: ์—ฐ๋ฃŒ ์••๋ ฅ ์ธก์ • -> ๋ถ„์‚ฌ๊ธฐ ๋ˆ„์„คยท๋ง‰ํž˜ ํ™•์ธ -> ๋ถˆ๋Ÿ‰ ์‹œ ๋ถ„์‚ฌ๊ธฐ ๊ต์ฒด\n", " ์š”์•ฝ: ์—ฐ๋ฃŒ๋ถ„์‚ฌ๊ธฐ ๊ณ ์žฅ ๊ฐ€๋Šฅ์„ฑ 95%. ์ฃผ์š” ๊ทผ๊ฑฐ: ๊ณต์œ  ์ฝ”๋“œ S01(์—ฐ๋ฃŒ๋ถ„์‚ฌ๊ธฐยทํ„ฐ๋ณด์ฐจ์ € ๊ณต์œ ), ์—”์ง„ ๊ณ„ํ†ต ์ฆ์ƒ ์ฝ”๋“œ G01, ์—ฐ๋ฃŒ ํ•„ํ„ฐ ๊ณ ์œ  ์ฝ”๋“œ C101.\n", " ์ž…๋ ฅ DTC ['G01'] (์‹ค์ œ ๊ณ ์žฅ ํ„ฐ๋ณด์ฐจ์ €)\n", " ์ง„๋‹จ ์—ฐ๋ฃŒ๋ถ„์‚ฌ๊ธฐ ์‹ ๋ขฐ๋„ 55% | ๊ทผ๊ฑฐ: ์—”์ง„ ๊ณ„ํ†ต ์ฆ์ƒ ์ฝ”๋“œ G01(+3.0) / ๊ธฐ์˜จ(+0.1) / ์šด์šฉ ๋ถ€ํ•˜(+0.1)\n", " ์กฐ์น˜ ์ ˆ์ฐจ: ์—ฐ๋ฃŒ ์••๋ ฅ ์ธก์ • -> ๋ถ„์‚ฌ๊ธฐ ๋ˆ„์„คยท๋ง‰ํž˜ ํ™•์ธ -> ๋ถˆ๋Ÿ‰ ์‹œ ๋ถ„์‚ฌ๊ธฐ ๊ต์ฒด\n", " ์š”์•ฝ: ์—ฐ๋ฃŒ๋ถ„์‚ฌ๊ธฐ ๊ณ ์žฅ ๊ฐ€๋Šฅ์„ฑ 55%. ์ฃผ์š” ๊ทผ๊ฑฐ: ์—”์ง„ ๊ณ„ํ†ต ์ฆ์ƒ ์ฝ”๋“œ G01, ๊ธฐ์˜จ, ์šด์šฉ ๋ถ€ํ•˜.\n", "\n", "[์›น ๋Œ€์‹œ๋ณด๋“œ] ์ตœ๊ทผ ๋‹ฌ ์ •๋น„ ๊ฑด 457๊ฑด, ๊ธฐ๋™ยท์ œ๋™ ๊ณ„ํ†ต ๊ฒฝ๋ณด 238๊ฑด, ์‹ ๋ขฐ๋„ ๋‚ฎ์Œ(0.7 ๋ฏธ๋งŒ) 148๊ฑด\n", " ๋‹ค๋นˆ๋„ ์ง„๋‹จ ๋ถ€ํ’ˆ: ์—ฐ๋ฃŒ๋ถ„์‚ฌ๊ธฐ 189๊ฑด, ํ„ฐ๋ณด์ฐจ์ € 72๊ฑด, ๋ƒ‰๊ฐ์ˆ˜ ํŽŒํ”„ 40๊ฑด\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 07. ๋ณตํ•ฉ ๊ณ ์žฅ ์˜์‹ฌ ์‹œ ์ˆ™๋ จ ์ •๋น„๊ด€ 2์ฐจ ์ •๋ฐ€ ์ง„๋‹จ(HITL) ์›Œํฌํ”Œ๋กœ์šฐ ์„ค๊ณ„\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 3.5์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "๋ณตํ•ฉ ๊ณ ์žฅ ์˜์‹ฌ ์‹œ ์ˆ™๋ จ ์ •๋น„๊ด€ 2์ฐจ ์ •๋ฐ€ ์ง„๋‹จ(HITL) ์›Œํฌํ”Œ๋กœ์šฐ๋ฅผ ์„ค๊ณ„ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **๋ณตํ•ฉ ๊ณ ์žฅ์ด ์˜์‹ฌ๋  ๋•Œ ์ˆ™๋ จ ์ •๋น„๊ด€ 2์ฐจ ์ •๋ฐ€ ์ง„๋‹จ(HITL) ์›Œํฌํ”Œ๋กœ์šฐ๋ฅผ ์‹œ๋ฎฌ๋ ˆ์ด์…˜์œผ๋กœ ์„ค๊ณ„ํ•˜๊ณ  ์˜ค์ง„๋‹จ ๋น„์šฉ ์ ˆ๊ฐ ํšจ๊ณผ๋ฅผ ๊ฒ€์ฆํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **์‹ ๋ขฐ๋„๊ฐ€ ๊ธฐ์ค€ ๋ฏธ๋งŒ์ด๊ฑฐ๋‚˜ 3๊ฐœ ๊ณ„ํ†ต ์ด์ƒ DTC๊ฐ€ ๊ฒน์นœ ๊ฑด์„ ์ •๋น„๊ด€ ๋Œ€๊ธฐ์—ด์— ์˜ฌ๋ฆฌ๊ณ , ์—”์ง„ ์•ˆ์„ ์ž๋™ ํ™•์ •ํ•˜์ง€ ์•Š์€ ์ฑ„ ์ •๋น„๊ด€์˜ ํ™•์ •ยท์ •์ •ยท๋ฌด์‘๋‹ต(์—”์ง„ ์•ˆ ์œ ์ง€, ์žฌํ™•์ธ ํ‘œ์‹œ)์„ ์‚ฌ์œ ์™€ ํ•จ๊ป˜ ํ•ด์‹œ ์ฒด์ธ ๊ฐ์‚ฌ ๋กœ๊ทธ๋กœ ๋‚จ๊ธด๋‹ค ๊ณผ์ •์„ ์ง„ํ–‰ํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 5๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `src/optimization/mnt03_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": [ "\"\"\"๋ณตํ•ฉ ๊ณ ์žฅ์ด ์˜์‹ฌ๋˜๋Š” ์ •๋น„ ๊ฑด(์‹ ๋ขฐ๋„ ๋‚ฎ์Œ, ์„œ๋กœ ๋‹ค๋ฅธ ๊ณ„ํ†ต์˜ DTC ๋™์‹œ ๋ฐœ์ƒ)์„ ์ˆ™๋ จ ์ •๋น„๊ด€ 2์ฐจ ์ •๋ฐ€ ์ง„๋‹จ ๋Œ€๊ธฐ์—ด์— ์˜ฌ๋ ค ์Šน์ธยท์ •์ •ยท๋ฌด์‘๋‹ต(์—”์ง„ ์•ˆ ์œ ์ง€ยท์žฌํ™•์ธ ํ‘œ์‹œ)์„ ์‚ฌ์œ ์™€ ํ•จ๊ป˜ ํ•ด์‹œ ์ฒด์ธ ๊ฐ์‚ฌ ๋กœ๊ทธ์— ๋‚จ๊ธฐ๋Š” HITL ์›Œํฌํ”Œ๋กœ์šฐ ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ๋ฅผ ๊ตฌํ˜„ํ•œ๋‹ค. ์ž๋™ ํ™•์ • 0๊ฑด, ์˜ค์ง„๋‹จ ๋น„์šฉ ์ ˆ๊ฐ๋ฅ , ์‹ ๋ขฐ๋„ ๊ธฐ์ค€ยท์ •๋น„๊ด€ ์ˆ˜์šฉ๋Ÿ‰ยท๊ณ ์žฅ ๊ธ‰์ฆ์— ๋Œ€ํ•œ ๋ฏผ๊ฐ๋„๋ฅผ ํ™•์ธํ•œ๋‹ค.\"\"\"\n", "import hashlib\n", "import json\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from sklearn.linear_model import LogisticRegression\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ƒ ์ฐจ๋Ÿ‰ 100๋Œ€์˜ 18๊ฐœ์›” ๊ณ ์žฅ ์ง„๋‹จ ์ฝ”๋“œ(DTC)ยท์ •๋น„ ์ด๋ ฅ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ๋ถ€๋Œ€ยท์žฅ๋น„ยท์ •๋น„ ์‹ค์  ์—†์Œ)\n", "# ๋ถ€ํ’ˆ 16์ข…(1000๊ฐ€๋™์‹œ๊ฐ„๋‹น ๊ณ ์žฅ๋ฅ  ์ฐจ๋“ฑ), DTC 28์ข…(๋ถ€ํ’ˆ ๊ณ ์œ  ์ฝ”๋“œ, ์ง ๋ถ€ํ’ˆ ๊ณต์œ  ์ฝ”๋“œ, ๊ณ„ํ†ต ์ฆ์ƒ ์ฝ”๋“œ)\n", "# ๊ณ ์žฅ 1๊ฑด(์ •๋น„ ์ด๋ ฅ)๋งˆ๋‹ค DTC๊ฐ€ 1~5๊ฐœ ๋™์‹œ ๋ฐœ์ƒํ•˜๊ณ , ์—ฐ์‡„ ๊ณ ์žฅ ์ฝ”๋“œ์™€ ์ผ์‹œ ์žก์Œ ์ฝ”๋“œ๊ฐ€ ์„ž์ธ๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "PARTS = [ # (ID, ์ด๋ฆ„, ๊ณ„ํ†ต, 1000๊ฐ€๋™์‹œ๊ฐ„๋‹น ๊ณ ์žฅ๋ฅ , ๊ฐ€์ƒ ๋‹จ๊ฐ€์ง€์ˆ˜, ์ˆ˜๋ฆฌ์‹œ๊ฐ„ h)\n", " (\"P01\", \"์—ฐ๋ฃŒ๋ถ„์‚ฌ๊ธฐ\", \"engine\", 12.0, 3.0, 2.0), (\"P02\", \"ํ„ฐ๋ณด์ฐจ์ €\", \"engine\", 9.0, 8.0, 5.0),\n", " (\"P03\", \"์ถ•์ „์ง€\", \"electric\", 6.5, 4.0, 1.5), (\"P04\", \"๋ฐœ์ „๊ธฐ\", \"electric\", 4.0, 9.0, 3.0),\n", " (\"P05\", \"๋ณ€์†๊ธฐ ์ œ์–ด๋ชจ๋“ˆ\", \"drive\", 3.0, 12.0, 6.0), (\"P06\", \"๋ƒ‰๊ฐ์ˆ˜ ํŽŒํ”„\", \"engine\", 2.2, 3.5, 3.0),\n", " (\"P07\", \"์œ ์•• ํŽŒํ”„\", \"hydraulic\", 1.6, 10.0, 4.0), (\"P08\", \"์†๋„ ์„ผ์„œ\", \"drive\", 1.2, 2.0, 1.5),\n", " (\"P09\", \"ECU ์ปค๋„ฅํ„ฐ\", \"electric\", 0.9, 2.5, 1.5), (\"P10\", \"์—ฐ๋ฃŒ ํ•„ํ„ฐ\", \"engine\", 0.7, 0.8, 0.7),\n", " (\"P11\", \"์ œ๋™ ๋ฐธ๋ธŒ\", \"brake\", 0.5, 6.0, 3.0), (\"P12\", \"์กฐํ–ฅ ์•ก์ถ”์—์ดํ„ฐ\", \"drive\", 0.4, 9.0, 5.0),\n", " (\"P13\", \"๊ณต๊ธฐ ํ•„ํ„ฐ\", \"engine\", 0.3, 0.5, 0.5), (\"P14\", \"ํ†ต์‹  ์ธํ„ฐํŽ˜์ด์Šค ๋ชจ๋“ˆ\", \"electric\", 0.25, 3.0, 1.5),\n", " (\"P15\", \"์œคํ™œ์œ  ์••๋ ฅ ์„ผ์„œ\", \"engine\", 0.2, 1.0, 1.0), (\"P16\", \"ํŒฌ ๊ตฌ๋™ ๋ชจํ„ฐ\", \"cooling\", 0.15, 2.0, 2.0)]\n", "PID = [p[0] for p in PARTS]\n", "PNAME = {p[0]: p[1] for p in PARTS}; PSYS = {p[0]: p[2] for p in PARTS}; RATE = {p[0]: p[3] for p in PARTS}\n", "COST = {p[0]: p[4] for p in PARTS}; REPH = {p[0]: p[5] for p in PARTS}\n", "PAIRS = [(\"P01\", \"P02\"), (\"P03\", \"P04\"), (\"P05\", \"P08\"), (\"P06\", \"P10\"), (\"P09\", \"P14\"), (\"P13\", \"P15\")]\n", "SHARED = {p: f\"S{k + 1:02d}\" for k, pr in enumerate(PAIRS) for p in pr} # ์ง ๋ถ€ํ’ˆ์ด ํ•จ๊ป˜ ์“ฐ๋Š” ๋ชจํ˜ธํ•œ ์ฝ”๋“œ\n", "SPEC = {p: f\"C{p[1:]}1\" for p in PID} # ๋ถ€ํ’ˆ ๊ณ ์œ  ์ฝ”๋“œ\n", "SYS = [\"engine\", \"electric\", \"drive\", \"hydraulic\", \"brake\", \"cooling\"]\n", "SYMP = {s: f\"G{i + 1:02d}\" for i, s in enumerate(SYS)} # ๊ณ„ํ†ต ์ฆ์ƒ ์ฝ”๋“œ\n", "CODES = sorted(set(SPEC.values()) | set(SHARED.values()) | set(SYMP.values()))\n", "CASCADE = {\"P01\": [(\"P10\", 0.20)], \"P02\": [(\"P06\", 0.20)], \"P03\": [(\"P04\", 0.25)], \"P04\": [(\"P09\", 0.25)],\n", " \"P05\": [(\"P08\", 0.30)], \"P06\": [(\"P16\", 0.30)], \"P09\": [(\"P14\", 0.20)], \"P12\": [(\"P07\", 0.20)]}\n", "CRIT_SYS = {\"engine\", \"drive\", \"brake\"} # ๊ธฐ๋™ยท์ œ๋™ ๋ถˆ๋Šฅ ์œ„ํ—˜ ๊ณ„ํ†ต(๊ฒฝ๋ณด ๋Œ€์ƒ)\n", "MONTHS = 18\n", "\n", "\n", "def make_fleet(n=100, seed=0):\n", " rng = np.random.default_rng(seed)\n", " f = pd.DataFrame({\"veh\": [f\"V{i:03d}\" for i in range(n)], \"age\": rng.integers(1, 26, n).astype(float),\n", " \"terrain\": rng.uniform(0, 1, n), \"load\": rng.uniform(0, 1, n), \"pm_gap\": rng.uniform(20, 200, n),\n", " \"humid\": rng.uniform(0, 1, n), \"base_h\": rng.uniform(55, 125, n)})\n", " f[\"mult\"] = np.exp(0.045 * (f[\"age\"] - 13) + 0.9 * (f[\"terrain\"] - .5) + 0.8 * (f[\"load\"] - .5)\n", " + 0.006 * (f[\"pm_gap\"] - 110) + 0.7 * (f[\"humid\"] - .5))\n", " return f\n", "\n", "\n", "def make_data(seed=1, months=MONTHS):\n", " \"\"\"๋ฐ˜ํ™˜: fleet(์ฐจ๋Ÿ‰ ์†์„ฑ), ev(์ •๋น„ ์ด๋ ฅ 1๊ฑด=1ํ–‰: ๋ฐœ์ƒ DTC ๋ชฉ๋ก, ์‹ค์ œ ๊ณ ์žฅ ๋ถ€ํ’ˆ), log(DTC ๋ฐœ์ƒ ๋กœ๊ทธ, ํ–‰=์ฝ”๋“œ 1๊ฑด)\"\"\"\n", " rng = np.random.default_rng(seed)\n", " fleet = make_fleet()\n", " V = len(fleet)\n", " temp = 12 + 14 * np.sin(2 * np.pi * (np.arange(months) - 3) / 12) # ์›”ํ‰๊ท  ๊ธฐ์˜จ(๊ฐ€์ƒ)\n", " hours = fleet[\"base_h\"].values[:, None] * (1 + 0.25 * np.sin(2 * np.pi * np.arange(months) / 12 + 1)) * rng.lognormal(0, .12, (V, months))\n", " fleet[\"hours\"] = hours.sum(axis=1) # ์ฐจ๋Ÿ‰๋ณ„ 18๊ฐœ์›” ์ด ๊ฐ€๋™์‹œ๊ฐ„\n", " fleet.attrs[\"month_h\"] = hours.sum(axis=0) # ์›”๋ณ„ ์ „์ฒด ๊ฐ€๋™์‹œ๊ฐ„(๊ณ ์žฅ๋ฅ  ๋ถ„๋ชจ)\n", " evs = []\n", " for p in PID:\n", " s = np.ones(months)\n", " if p in (\"P03\", \"P04\"): s = np.exp(-0.06 * (temp - 12)) # ํ•œ๋žญ๊ธฐ ์ „๊ธฐ๊ณ„ํ†ต ์ฆ๊ฐ€\n", " if p in (\"P06\", \"P16\"): s = np.exp(0.05 * (temp - 12)) # ํ˜น์„œ๊ธฐ ๋ƒ‰๊ฐ๊ณ„ํ†ต ์ฆ๊ฐ€\n", " cnt = rng.poisson(RATE[p] * hours / 1000 * fleet[\"mult\"].values[:, None] * s[None, :])\n", " for v, m in zip(*np.nonzero(cnt)):\n", " for _ in range(cnt[v, m]):\n", " evs.append((fleet[\"veh\"][v], int(m), m * 720 + rng.uniform(0, 720), p, float(temp[m] + rng.normal(0, 3))))\n", " ev = pd.DataFrame(evs, columns=[\"veh\", \"month\", \"t\", \"part\", \"temp\"]).sort_values(\"t\").reset_index(drop=True)\n", " ev[\"ev_id\"] = [f\"W{i:05d}\" for i in range(len(ev))]\n", " rows, codes = [], []\n", " for e in ev.itertuples():\n", " c, r = [], rng.random(3)\n", " if r[0] < 0.6: c.append(SPEC[e.part])\n", " if e.part in SHARED and r[1] < 0.6: c.append(SHARED[e.part])\n", " if r[2] < 0.5: c.append(SYMP[PSYS[e.part]])\n", " if not c: c.append(SHARED.get(e.part, SPEC[e.part]))\n", " for q, pr in CASCADE.get(e.part, []):\n", " if rng.random() < pr: c.append(SPEC[q])\n", " if rng.random() < 0.12: c.append(SPEC[PID[rng.integers(0, 16)]])\n", " c = list(dict.fromkeys(c))\n", " rng.shuffle(c) # ๋™์‹œ ๋ฐœ์ƒ ์ฝ”๋“œ์˜ ๊ธฐ๋ก ์ˆœ์„œ๋Š” ์ž„์˜\n", " codes.append(c)\n", " for k, cd in enumerate(c):\n", " rows.append((e.ev_id, e.veh, e.t + k * 0.1, cd))\n", " ev[\"codes\"] = codes\n", " log = pd.DataFrame(rows, columns=[\"ev_id\", \"veh\", \"ts\", \"code\"])\n", " return fleet, ev, log\n", "\n", "# ---------------------------------------------------------------------------\n", "# ๊ณตํ†ต ๋ถ„์„ ์กฐ๊ฐ: ์ฝ”๋“œ ๋ฉ€ํ‹ฐํ•ซ ํ–‰๋ ฌ, ์ฐจ๋Ÿ‰ ๋งฅ๋ฝ ํ”ผ์ฒ˜, ํ•™์Šต/๊ฒ€์ฆ ๋ถ„ํ• (์•ž 12๊ฐœ์›” ํ•™์Šต, ๋’ค 6๊ฐœ์›” ๊ฒ€์ฆ)\n", "# ---------------------------------------------------------------------------\n", "CIDX = {c: i for i, c in enumerate(CODES)}\n", "SPLIT_M = 12\n", "\n", "\n", "def multi_hot(ev):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 7] ๋ณตํ•ฉ ๊ณ ์žฅ ์˜์‹ฌ ์‹œ ์ˆ™๋ จ ์ •๋น„๊ด€ 2์ฐจ ์ •๋ฐ€ ์ง„๋‹จ(HITL) ์›Œํฌํ”Œ๋กœ์šฐ ์„ค๊ณ„ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def context(ev, fleet):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 7] ๋ณตํ•ฉ ๊ณ ์žฅ ์˜์‹ฌ ์‹œ ์ˆ™๋ จ ์ •๋น„๊ด€ 2์ฐจ ์ •๋ฐ€ ์ง„๋‹จ(HITL) ์›Œํฌํ”Œ๋กœ์šฐ ์„ค๊ณ„ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def feats(ev, fleet):\n", " X = multi_hot(ev)\n", " n = X.sum(axis=1, keepdims=True) / 3\n", " return np.hstack([X, n, context(ev, fleet)])\n", "\n", "\n", "def monthly_counts(ev):\n", " return ev.groupby([\"month\", \"part\"]).size().unstack(fill_value=0).reindex(columns=PID, fill_value=0)\n", "\n", "\n", "def build_monthly(ev, fleet, top_n=8):\n", " \"\"\"๋ถ€ํ’ˆ๋ณ„ ์›”๊ฐ„ ๊ณ ์žฅ ๊ฑด์ˆ˜ ์˜ˆ์ธก์šฉ ํ‘œ: ์ง€์—ฐ๊ฐ’(lag), ์ด๋™ํ‰๊ท , ์ถ”์„ธ, ๊ธฐ๋Œ€ ๊ฑด์ˆ˜(๊ฐ€๋™์‹œ๊ฐ„ x ์„ค๊ณ„ ๊ณ ์žฅ๋ฅ ), ๊ธฐ์˜จ ์ƒํ˜ธ์ž‘์šฉ, ๊ณ„์ ˆ ์„ฑ๋ถ„\"\"\"\n", " mc = ev.groupby([\"month\", \"part\"]).size().unstack(fill_value=0)\n", " top = ev[\"part\"].value_counts().index[:top_n]\n", " mh, tm = fleet.attrs[\"month_h\"], ev.groupby(\"month\")[\"temp\"].mean().values\n", " rows = []\n", " for p in top:\n", " c = mc[p].values.astype(float)\n", " for m in range(3, MONTHS):\n", " L = np.log1p(c)\n", " rows.append({\"part\": p, \"month\": m, \"y\": c[m], \"lag1\": L[m - 1], \"lag2\": L[m - 2], \"lag3\": L[m - 3], \"roll3\": L[m - 3:m].mean(),\n", " \"trend\": L[m - 1] - L[m - 2], \"roll3_std\": L[m - 3:m].std(), \"log_expected\": np.log(mh[m] / 1000 * RATE[p]),\n", " \"rate_lag1\": c[m - 1] / (mh[m - 1] / 1000) / RATE[p], \"temp\": tm[m] / 10, \"cold_elec\": max(0, 12 - tm[m]) / 10 * (p in (\"P03\", \"P04\")),\n", " \"heat_cool\": max(0, tm[m] - 12) / 10 * (p in (\"P06\", \"P16\")), \"sin\": np.sin(2 * np.pi * m / 12), \"cos\": np.cos(2 * np.pi * m / 12)})\n", " D = pd.DataFrame(rows)\n", " return D, [c for c in D.columns if c not in (\"part\", \"month\", \"y\")], top\n", "\n", "fleet, ev, log = make_data()\n", "train, test = ev[ev[\"month\"] < SPLIT_M], ev[ev[\"month\"] >= SPLIT_M].reset_index(drop=True)\n", "clf = LogisticRegression(C=3, max_iter=500).fit(feats(train, fleet), train[\"part\"])\n", "P = clf.predict_proba(feats(test, fleet))\n", "cls = clf.classes_\n", "top1 = cls[P.argmax(axis=1)]\n", "conf = P.max(axis=1)\n", "CSYS = {c: {PSYS[p] for p in PID if c in (SPEC[p], SHARED.get(p)) or c == SYMP[PSYS[p]]} for c in CODES}\n", "nsys = test[\"codes\"].map(lambda cs: len(set().union(*[CSYS[c] for c in cs])))\n", "multi_sys = ((test[\"codes\"].str.len() >= 3) & (nsys >= 3)).values # 3๊ฐœ ์ด์ƒ ๊ณ„ํ†ต์— ๊ฑธ์นœ DTC๊ฐ€ ๋™์‹œ์— ๋ฐœ์ƒ\n", "THR, CAP = 0.8, 300 # ์šด์˜์ : ์‹ ๋ขฐ๋„ 0.8 ๋ฏธ๋งŒ ๋˜๋Š” ๋‹ค๊ณ„ํ†ต ๋™์‹œ ๋ฐœ์ƒ์ด๋ฉด ์ •๋ฐ€ ์ง„๋‹จ, ์ •๋น„๊ด€ ์›” ์ˆ˜์šฉ๋Ÿ‰ 300๊ฑด(์•„๋ž˜ ๋ฏผ๊ฐ๋„ ๋ถ„์„์œผ๋กœ ์„ ์ •)\n", "\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ • ๋น„์šฉ์ง€์ˆ˜: ์˜ค์ง„๋‹จ ์‹œ ํ—›๊ต์ฒด ๋ถ€ํ’ˆ๋น„ + ํ—›๊ต์ฒด ๊ณต์ˆ˜ + ์žฌ์ž…๊ณ  ํŒจ๋„ํ‹ฐ 3.0 + ์˜ฌ๋ฐ”๋ฅธ ์ˆ˜๋ฆฌ, ์ •๋ฐ€ ์ง„๋‹จ 1๊ฑด 1.0\n", "REVIEW_COST, REVISIT, SENIOR_ACC, NO_RESP = 1.0, 3.0, 0.93, 0.08\n", "\n", "\n", "def case_cost(pred, true):\n", " if pred == true:\n", " return REPH[true]\n", " return COST[pred] + REPH[pred] + REVISIT + REPH[true]\n", "\n", "\n", "class Audit:\n", " def __init__(self):\n", " self.log = []\n", "\n", " def add(self, **body):\n", " prev = self.log[-1][\"hash\"] if self.log else \"GENESIS\"\n", " body = {\"seq\": len(self.log), **body}\n", " self.log.append({**body, \"prev\": prev, \"hash\": hashlib.sha256((prev + json.dumps(body, sort_keys=True, ensure_ascii=False)).encode()).hexdigest()})\n", "\n", " def verify(self):\n", " prev = \"GENESIS\"\n", " for e in self.log:\n", " b = {k: v for k, v in e.items() if k not in (\"prev\", \"hash\")}\n", " if e[\"prev\"] != prev or hashlib.sha256((prev + json.dumps(b, sort_keys=True, ensure_ascii=False)).encode()).hexdigest() != e[\"hash\"]:\n", " return e[\"seq\"]\n", " prev = e[\"hash\"]\n", " return None\n", "\n", "\n", "def run(thr=THR, cap=CAP, surge=1.0, seed=0, audit=None, mode=\"hitl\"):\n", " \"\"\"mode: auto(์—”์ง„ top-1 ์ „๋ถ€ ํ™•์ •: ๋น„๊ต์šฉ, ์‹ค์ œ ์šด์šฉ ๊ธˆ์ง€) / hitl(๋ณตํ•ฉ ์˜์‹ฌ ๊ฑด์€ ์ •๋น„๊ด€ ๊ฒฐ์ • ์ „๊นŒ์ง€ ํ™•์ •ํ•˜์ง€ ์•Š์Œ)\"\"\"\n", " rng = np.random.default_rng(seed)\n", " idx_all = np.arange(len(test))\n", " if surge != 1.0:\n", " idx_all = np.concatenate([idx_all, rng.choice(idx_all, int(len(idx_all) * (surge - 1)), replace=True)])\n", " cost, wrong, queued, decided, pending_auto, silent = 0.0, 0, 0, 0, 0, 0\n", " for m in range(SPLIT_M, MONTHS):\n", " mi = idx_all[test[\"month\"].values[idx_all] == m]\n", " flag = [i for i in mi if conf[i] < thr or multi_sys[i]]\n", " flag.sort(key=lambda i: (PSYS[top1[i]] not in CRIT_SYS, conf[i])) # ์•ˆ์ „ ๊ณ„ํ†ต ์šฐ์„ , ์‹ ๋ขฐ๋„ ๋‚ฎ์€ ์ˆœ\n", " queue = set(flag[:cap]) if mode == \"hitl\" else set()\n", " for i in mi:\n", " true, final = test[\"part\"][i], top1[i]\n", " if i in queue:\n", " queued += 1\n", " audit.add(event=\"PROPOSE\", case=int(i), engine=str(top1[i]), conf=round(float(conf[i]), 3), status=\"PENDING\")\n", " cost += REVIEW_COST\n", " r = rng.random()\n", " if r < NO_RESP:\n", " dec, why = \"๋ฌด์‘๋‹ต\", \"์‘๋‹ต ์ง€์—ฐ -> ์—”์ง„ ์•ˆ ์œ ์ง€, ์žฌํ™•์ธ ํ•„์š” ํ‘œ์‹œ\"; silent += 1\n", " elif r < NO_RESP + (1 - NO_RESP) * SENIOR_ACC:\n", " dec, final, why = (\"์Šน์ธ\" if top1[i] == true else \"์ •์ •\"), true, \"์ถ”๊ฐ€ ๊ณ„์ธกยท๋ถ€ํ’ˆ ๋ถ„๋ฆฌ ์ ๊ฒ€์œผ๋กœ ํ™•์ธ\"\n", " else:\n", " second = cls[np.argsort(-P[i])[1]]\n", " dec, final, why = \"์ •์ •\", second, \"2์ˆœ์œ„ ํ›„๋ณด๋กœ ์ •์ •(๊ทผ๊ฑฐ ๋ถ€์กฑ)\"\n", " audit.add(event=\"DECIDE\", case=int(i), decision=dec, final=str(final), reason=why, by=\"์ˆ™๋ จ ์ •๋น„๊ด€(๋ชจ์˜)\")\n", " decided += 1\n", " cost += case_cost(final, true); wrong += final != true\n", " n = len(idx_all)\n", " return {\"cost\": cost, \"wrong\": wrong / n, \"queued\": queued, \"decided\": decided, \"silent\": silent, \"n\": n, \"auto\": pending_auto}\n", "\n", "\n", "aud = Audit()\n", "A, H = run(mode=\"auto\", audit=Audit()), run(audit=aud)\n", "print(f\"[๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฒ€์ฆ ์ •๋น„ ๊ฑด {A['n']}๊ฑด, ๋ณตํ•ฉ ๊ณ ์žฅ ์˜์‹ฌ ๊ฑด {int(((conf < THR) | multi_sys).sum())}๊ฑด (์‹ ๋ขฐ๋„ {THR} ๋ฏธ๋งŒ ๋˜๋Š” 3๊ฐœ ๊ณ„ํ†ต ์ด์ƒ DTC)\")\n", "print(f\"์—”์ง„ ๋‹จ๋… ์ž๋™ ํ™•์ •(๋น„๊ต์šฉ): ๋น„์šฉ์ง€์ˆ˜ {A['cost']:.0f}, ์˜ค์ง„๋‹จ์œจ {A['wrong']:.1%}\")\n", "print(f\"HITL(์ •๋น„๊ด€ ๋Œ€๊ธฐ์—ด {H['queued']}๊ฑด): ๋น„์šฉ์ง€์ˆ˜ {H['cost']:.0f}, ์˜ค์ง„๋‹จ์œจ {H['wrong']:.1%}\")\n", "cut = 1 - H[\"cost\"] / A[\"cost\"]\n", "print(f\"์˜ค์ง„๋‹จ ๋น„์šฉ ์ ˆ๊ฐ๋ฅ  {cut:.1%} (๋ชฉํ‘œ 15% ์ด์ƒ: {'๋‹ฌ์„ฑ' if cut >= 0.15 else '๋ฏธ๋‹ฌ'})\")\n", "\n", "# ์Šน์ธ ์ฒด๊ณ„ ์ ๊ฒ€\n", "print(f\"\\n์ž๋™ ํ™•์ • 0๊ฑด ํ™•์ธ: ๋Œ€๊ธฐ์—ด ๊ฑด์€ ๋ชจ๋‘ ์ •๋น„๊ด€ ๊ฒฐ์ • ๊ธฐ๋ก ํ›„ ํ™•์ • (์ž๋™ ํ™•์ • {H['auto']}๊ฑด)\")\n", "print(f\"๊ฒฐ์ • ๋กœ๊น…๋ฅ  {H['decided'] / max(H['queued'], 1):.0%} ({H['decided']}/{H['queued']}), ๋ฌด์‘๋‹ต {H['silent']}๊ฑด์€ ์—”์ง„ ์•ˆ ์œ ์ง€ + ์žฌํ™•์ธ ํ‘œ์‹œ\")\n", "print(f\"๊ฐ์‚ฌ ๋กœ๊ทธ ๋ฌด๊ฒฐ์„ฑ: {'์ •์ƒ' if aud.verify() is None else '์ด์ƒ'}\", end=\", \")\n", "aud.log[7][\"reason\"] = \"์‚ฌํ›„ ์ˆ˜์ •\"\n", "print(f\"์‚ฌํ›„ ๋ณ€์กฐ ์‹œ seq {aud.verify()}์—์„œ ํƒ์ง€\")\n", "print(\"๊ฒฐ์ • ์‚ฌ์œ  ์˜ˆ:\", \"; \".join(sorted({e[\"reason\"] for e in aud.log if e[\"event\"] == \"DECIDE\"})[:3]))\n", "\n", "# ๋ฏผ๊ฐ๋„ ๋ถ„์„\n", "print(\"\\n[๋ฏผ๊ฐ๋„ 1] ์‹ ๋ขฐ๋„ ๊ธฐ์ค€(๋ฏธ๋งŒ์ด๋ฉด ์ •๋ฐ€ ์ง„๋‹จ) -> ์ ˆ๊ฐ๋ฅ , ์ •๋น„๊ด€ ๋Œ€๊ธฐ์—ด(6๊ฐœ์›”)\")\n", "rows = []\n", "for thr in (0.5, 0.6, 0.7, 0.8, 0.9):\n", " r = run(thr=thr, cap=10 ** 6, audit=Audit()); rows.append((thr, 1 - r[\"cost\"] / A[\"cost\"], r[\"queued\"]))\n", " print(f\" {thr:.1f}: ์ ˆ๊ฐ {rows[-1][1]:.1%}, ๋Œ€๊ธฐ์—ด {r['queued']}๊ฑด\")\n", "print(\"[๋ฏผ๊ฐ๋„ 2] ์ •๋น„๊ด€ ์›” ์ˆ˜์šฉ๋Ÿ‰ -> ์ ˆ๊ฐ๋ฅ  (์ „์‹œ ์ „ํ™˜ ๊ฐ€์ •: ๊ณ ์žฅ 1.5๋ฐฐ ๊ธ‰์ฆ)\")\n", "for cap in (50, 100, 200, 300):\n", " r1, r2 = run(cap=cap, audit=Audit()), run(cap=cap, surge=1.5, audit=Audit())\n", " b2 = run(mode=\"auto\", surge=1.5, audit=Audit())\n", " print(f\" ์›” {cap}๊ฑด: ํ‰์‹œ {1 - r1['cost'] / A['cost']:.1%}, ๊ธ‰์ฆ {1 - r2['cost'] / b2['cost']:.1%}\")\n", "\n", "plt.figure(figsize=(6, 3.5))\n", "plt.plot([r[0] for r in rows], [r[1] * 100 for r in rows], marker=\"o\"); plt.xlabel(\"confidence threshold\"); plt.ylabel(\"cost saving (%)\")\n", "plt.title(\"HITL sensitivity: confidence threshold\"); plt.tight_layout(); plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 07 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฒ€์ฆ ์ •๋น„ ๊ฑด 3890๊ฑด, ๋ณตํ•ฉ ๊ณ ์žฅ ์˜์‹ฌ ๊ฑด 1555๊ฑด (์‹ ๋ขฐ๋„ 0.8 ๋ฏธ๋งŒ ๋˜๋Š” 3๊ฐœ ๊ณ„ํ†ต ์ด์ƒ DTC)\n", "์—”์ง„ ๋‹จ๋… ์ž๋™ ํ™•์ •(๋น„๊ต์šฉ): ๋น„์šฉ์ง€์ˆ˜ 17546, ์˜ค์ง„๋‹จ์œจ 16.1%\n", "HITL(์ •๋น„๊ด€ ๋Œ€๊ธฐ์—ด 1501๊ฑด): ๋น„์šฉ์ง€์ˆ˜ 14809, ์˜ค์ง„๋‹จ์œจ 4.2%\n", "์˜ค์ง„๋‹จ ๋น„์šฉ ์ ˆ๊ฐ๋ฅ  15.6% (๋ชฉํ‘œ 15% ์ด์ƒ: ๋‹ฌ์„ฑ)\n", "\n", "์ž๋™ ํ™•์ • 0๊ฑด ํ™•์ธ: ๋Œ€๊ธฐ์—ด ๊ฑด์€ ๋ชจ๋‘ ์ •๋น„๊ด€ ๊ฒฐ์ • ๊ธฐ๋ก ํ›„ ํ™•์ • (์ž๋™ ํ™•์ • 0๊ฑด)\n", "๊ฒฐ์ • ๋กœ๊น…๋ฅ  100% (1501/1501), ๋ฌด์‘๋‹ต 120๊ฑด์€ ์—”์ง„ ์•ˆ ์œ ์ง€ + ์žฌํ™•์ธ ํ‘œ์‹œ\n", "๊ฐ์‚ฌ ๋กœ๊ทธ ๋ฌด๊ฒฐ์„ฑ: ์ •์ƒ, ์‚ฌํ›„ ๋ณ€์กฐ ์‹œ seq 7์—์„œ ํƒ์ง€\n", "๊ฒฐ์ • ์‚ฌ์œ  ์˜ˆ: 2์ˆœ์œ„ ํ›„๋ณด๋กœ ์ •์ •(๊ทผ๊ฑฐ ๋ถ€์กฑ); ์‚ฌํ›„ ์ˆ˜์ •; ์‘๋‹ต ์ง€์—ฐ -> ์—”์ง„ ์•ˆ ์œ ์ง€, ์žฌํ™•์ธ ํ•„์š” ํ‘œ์‹œ\n", "\n", "[๋ฏผ๊ฐ๋„ 1] ์‹ ๋ขฐ๋„ ๊ธฐ์ค€(๋ฏธ๋งŒ์ด๋ฉด ์ •๋ฐ€ ์ง„๋‹จ) -> ์ ˆ๊ฐ๋ฅ , ์ •๋น„๊ด€ ๋Œ€๊ธฐ์—ด(6๊ฐœ์›”)\n", " 0.5: ์ ˆ๊ฐ 1.9%, ๋Œ€๊ธฐ์—ด 162๊ฑด\n", " 0.6: ์ ˆ๊ฐ 9.4%, ๋Œ€๊ธฐ์—ด 784๊ฑด\n", " 0.7: ์ ˆ๊ฐ 14.6%, ๋Œ€๊ธฐ์—ด 1285๊ฑด\n", " 0.8: ์ ˆ๊ฐ 16.9%, ๋Œ€๊ธฐ์—ด 1555๊ฑด\n", " 0.9: ์ ˆ๊ฐ 17.7%, ๋Œ€๊ธฐ์—ด 1671๊ฑด\n", "[๋ฏผ๊ฐ๋„ 2] ์ •๋น„๊ด€ ์›” ์ˆ˜์šฉ๋Ÿ‰ -> ์ ˆ๊ฐ๋ฅ  (์ „์‹œ ์ „ํ™˜ ๊ฐ€์ •: ๊ณ ์žฅ 1.5๋ฐฐ ๊ธ‰์ฆ)\n", " ์›” 50๊ฑด: ํ‰์‹œ 2.8%, ๊ธ‰์ฆ 2.8%\n", " ์›” 100๊ฑด: ํ‰์‹œ 7.0%, ๊ธ‰์ฆ 4.4%\n", " ์›” 200๊ฑด: ํ‰์‹œ 13.8%, ๊ธ‰์ฆ 9.3%\n", " ์›” 300๊ฑด: ํ‰์‹œ 15.6%, ๊ธ‰์ฆ 12.7%\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 08. 1,000๊ฑด ๋™์‹œ ๋‹ค๋ฐœ ๊ณ ์žฅ ์ฝ”๋“œ ์ฃผ์ž… ์‹œ ์—ฐ๊ด€ ๊ณ ์žฅ ์ถ”๋ก  ๋ถ€ํ•˜ ํ…Œ์ŠคํŠธ\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 3.0์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "1,000๊ฑด ๋™์‹œ ๊ณ ์žฅ ์ฝ”๋“œ ์ฃผ์ž… ์‹œ ์—ฐ๊ด€ ๊ณ ์žฅ ์ถ”๋ก  ๋ถ€ํ•˜ ํ…Œ์ŠคํŠธ๋ฅผ ์ˆ˜ํ–‰ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **1,000๊ฑด์˜ DTC ์ง„๋‹จ ์š”์ฒญ์„ ๋™์‹œ์— ์ฃผ์ž…ํ•ด ์—ฐ๊ด€ ๊ณ ์žฅ ์ถ”๋ก ์˜ ์ฒ˜๋ฆฌ ์‹œ๊ฐ„๊ณผ ๊ฒฝ๋ณด ์ •ํ™•๋„๋ฅผ ํ™•์ธํ•˜๋Š” ๋ถ€ํ•˜ ํ…Œ์ŠคํŠธ๋ฅผ ์ˆ˜ํ–‰ํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **๊ฐ€์ƒ ์ง„๋‹จ ์š”์ฒญ 1,000๊ฑด์— ํ‘œ๊ธฐ ์˜ค๋ฅ˜, ๋นˆ ์š”์ฒญ, ๋ฏธ์ •์˜ ์ฝ”๋“œ, ๋ฏธ๋“ฑ๋ก ์ฐจ๋Ÿ‰, ์ค‘๋ณต ์ฝ”๋“œ ๊ฐ™์€ ์˜ค๋ฅ˜๋ฅผ ์„ž์–ด ์ผ๊ด„ ์ฒ˜๋ฆฌํ•˜๊ณ , ์ •์ƒ ์ฒ˜๋ฆฌยท๊ฒฉ๋ฆฌ ๊ฑด์ˆ˜์™€ ์œ ์‹ค ์—ฌ๋ถ€๋ฅผ ์„ผ๋‹ค ๊ณผ์ •์„ ์ง„ํ–‰ํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `src/dashboard/mnt03_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": [ "\"\"\"1,000๊ฑด์˜ DTC ์ง„๋‹จ ์š”์ฒญ์„ ๋™์‹œ์— ์ฃผ์ž…ํ•ด ์—ฐ๊ด€ ๊ณ ์žฅ ์ถ”๋ก (๋ถ€ํ’ˆ ์ง„๋‹จ + ๊ธฐ๋™ยท์ œ๋™ ๊ณ„ํ†ต ๊ฒฝ๋ณด)์˜ ์ฒ˜๋ฆฌ ์‹œ๊ฐ„๊ณผ ๊ฒฝ๋ณด ์ •ํ™•๋„๋ฅผ ์ธก์ •ํ•˜๋Š” ๋ถ€ํ•˜ ํ…Œ์ŠคํŠธ๋ฅผ ์ˆ˜ํ–‰ํ•œ๋‹ค. ํ˜•์‹์ด ๊นจ์ง„ ์ฝ”๋“œยท๋นˆ ์š”์ฒญยท๋ฏธ๋“ฑ๋ก ์ฐจ๋Ÿ‰์„ ์„ž์€ ์˜ค๋ฅ˜ ์ฃผ์ž… ์š”์ฒญ์ด ๊ฒฉ๋ฆฌ ์ฒ˜๋ฆฌ๋˜๋Š”์ง€, ์š”์ฒญ ์ˆ˜๋ฅผ 100~5,000๊ฑด์œผ๋กœ ๋Š˜๋ ธ์„ ๋•Œ ์‹œ๊ฐ„์ด ์–ด๋–ป๊ฒŒ ๋ณ€ํ•˜๋Š”์ง€ ํ™•์ธํ•œ๋‹ค.\"\"\"\n", "import time\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from concurrent.futures import ThreadPoolExecutor\n", "from sklearn.linear_model import LogisticRegression\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ƒ ์ฐจ๋Ÿ‰ 100๋Œ€์˜ 18๊ฐœ์›” ๊ณ ์žฅ ์ง„๋‹จ ์ฝ”๋“œ(DTC)ยท์ •๋น„ ์ด๋ ฅ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ๋ถ€๋Œ€ยท์žฅ๋น„ยท์ •๋น„ ์‹ค์  ์—†์Œ)\n", "# ๋ถ€ํ’ˆ 16์ข…(1000๊ฐ€๋™์‹œ๊ฐ„๋‹น ๊ณ ์žฅ๋ฅ  ์ฐจ๋“ฑ), DTC 28์ข…(๋ถ€ํ’ˆ ๊ณ ์œ  ์ฝ”๋“œ, ์ง ๋ถ€ํ’ˆ ๊ณต์œ  ์ฝ”๋“œ, ๊ณ„ํ†ต ์ฆ์ƒ ์ฝ”๋“œ)\n", "# ๊ณ ์žฅ 1๊ฑด(์ •๋น„ ์ด๋ ฅ)๋งˆ๋‹ค DTC๊ฐ€ 1~5๊ฐœ ๋™์‹œ ๋ฐœ์ƒํ•˜๊ณ , ์—ฐ์‡„ ๊ณ ์žฅ ์ฝ”๋“œ์™€ ์ผ์‹œ ์žก์Œ ์ฝ”๋“œ๊ฐ€ ์„ž์ธ๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "PARTS = [ # (ID, ์ด๋ฆ„, ๊ณ„ํ†ต, 1000๊ฐ€๋™์‹œ๊ฐ„๋‹น ๊ณ ์žฅ๋ฅ , ๊ฐ€์ƒ ๋‹จ๊ฐ€์ง€์ˆ˜, ์ˆ˜๋ฆฌ์‹œ๊ฐ„ h)\n", " (\"P01\", \"์—ฐ๋ฃŒ๋ถ„์‚ฌ๊ธฐ\", \"engine\", 12.0, 3.0, 2.0), (\"P02\", \"ํ„ฐ๋ณด์ฐจ์ €\", \"engine\", 9.0, 8.0, 5.0),\n", " (\"P03\", \"์ถ•์ „์ง€\", \"electric\", 6.5, 4.0, 1.5), (\"P04\", \"๋ฐœ์ „๊ธฐ\", \"electric\", 4.0, 9.0, 3.0),\n", " (\"P05\", \"๋ณ€์†๊ธฐ ์ œ์–ด๋ชจ๋“ˆ\", \"drive\", 3.0, 12.0, 6.0), (\"P06\", \"๋ƒ‰๊ฐ์ˆ˜ ํŽŒํ”„\", \"engine\", 2.2, 3.5, 3.0),\n", " (\"P07\", \"์œ ์•• ํŽŒํ”„\", \"hydraulic\", 1.6, 10.0, 4.0), (\"P08\", \"์†๋„ ์„ผ์„œ\", \"drive\", 1.2, 2.0, 1.5),\n", " (\"P09\", \"ECU ์ปค๋„ฅํ„ฐ\", \"electric\", 0.9, 2.5, 1.5), (\"P10\", \"์—ฐ๋ฃŒ ํ•„ํ„ฐ\", \"engine\", 0.7, 0.8, 0.7),\n", " (\"P11\", \"์ œ๋™ ๋ฐธ๋ธŒ\", \"brake\", 0.5, 6.0, 3.0), (\"P12\", \"์กฐํ–ฅ ์•ก์ถ”์—์ดํ„ฐ\", \"drive\", 0.4, 9.0, 5.0),\n", " (\"P13\", \"๊ณต๊ธฐ ํ•„ํ„ฐ\", \"engine\", 0.3, 0.5, 0.5), (\"P14\", \"ํ†ต์‹  ์ธํ„ฐํŽ˜์ด์Šค ๋ชจ๋“ˆ\", \"electric\", 0.25, 3.0, 1.5),\n", " (\"P15\", \"์œคํ™œ์œ  ์••๋ ฅ ์„ผ์„œ\", \"engine\", 0.2, 1.0, 1.0), (\"P16\", \"ํŒฌ ๊ตฌ๋™ ๋ชจํ„ฐ\", \"cooling\", 0.15, 2.0, 2.0)]\n", "PID = [p[0] for p in PARTS]\n", "PNAME = {p[0]: p[1] for p in PARTS}; PSYS = {p[0]: p[2] for p in PARTS}; RATE = {p[0]: p[3] for p in PARTS}\n", "COST = {p[0]: p[4] for p in PARTS}; REPH = {p[0]: p[5] for p in PARTS}\n", "PAIRS = [(\"P01\", \"P02\"), (\"P03\", \"P04\"), (\"P05\", \"P08\"), (\"P06\", \"P10\"), (\"P09\", \"P14\"), (\"P13\", \"P15\")]\n", "SHARED = {p: f\"S{k + 1:02d}\" for k, pr in enumerate(PAIRS) for p in pr} # ์ง ๋ถ€ํ’ˆ์ด ํ•จ๊ป˜ ์“ฐ๋Š” ๋ชจํ˜ธํ•œ ์ฝ”๋“œ\n", "SPEC = {p: f\"C{p[1:]}1\" for p in PID} # ๋ถ€ํ’ˆ ๊ณ ์œ  ์ฝ”๋“œ\n", "SYS = [\"engine\", \"electric\", \"drive\", \"hydraulic\", \"brake\", \"cooling\"]\n", "SYMP = {s: f\"G{i + 1:02d}\" for i, s in enumerate(SYS)} # ๊ณ„ํ†ต ์ฆ์ƒ ์ฝ”๋“œ\n", "CODES = sorted(set(SPEC.values()) | set(SHARED.values()) | set(SYMP.values()))\n", "CASCADE = {\"P01\": [(\"P10\", 0.20)], \"P02\": [(\"P06\", 0.20)], \"P03\": [(\"P04\", 0.25)], \"P04\": [(\"P09\", 0.25)],\n", " \"P05\": [(\"P08\", 0.30)], \"P06\": [(\"P16\", 0.30)], \"P09\": [(\"P14\", 0.20)], \"P12\": [(\"P07\", 0.20)]}\n", "CRIT_SYS = {\"engine\", \"drive\", \"brake\"} # ๊ธฐ๋™ยท์ œ๋™ ๋ถˆ๋Šฅ ์œ„ํ—˜ ๊ณ„ํ†ต(๊ฒฝ๋ณด ๋Œ€์ƒ)\n", "MONTHS = 18\n", "\n", "\n", "def make_fleet(n=100, seed=0):\n", " rng = np.random.default_rng(seed)\n", " f = pd.DataFrame({\"veh\": [f\"V{i:03d}\" for i in range(n)], \"age\": rng.integers(1, 26, n).astype(float),\n", " \"terrain\": rng.uniform(0, 1, n), \"load\": rng.uniform(0, 1, n), \"pm_gap\": rng.uniform(20, 200, n),\n", " \"humid\": rng.uniform(0, 1, n), \"base_h\": rng.uniform(55, 125, n)})\n", " f[\"mult\"] = np.exp(0.045 * (f[\"age\"] - 13) + 0.9 * (f[\"terrain\"] - .5) + 0.8 * (f[\"load\"] - .5)\n", " + 0.006 * (f[\"pm_gap\"] - 110) + 0.7 * (f[\"humid\"] - .5))\n", " return f\n", "\n", "\n", "def make_data(seed=1, months=MONTHS):\n", " \"\"\"๋ฐ˜ํ™˜: fleet(์ฐจ๋Ÿ‰ ์†์„ฑ), ev(์ •๋น„ ์ด๋ ฅ 1๊ฑด=1ํ–‰: ๋ฐœ์ƒ DTC ๋ชฉ๋ก, ์‹ค์ œ ๊ณ ์žฅ ๋ถ€ํ’ˆ), log(DTC ๋ฐœ์ƒ ๋กœ๊ทธ, ํ–‰=์ฝ”๋“œ 1๊ฑด)\"\"\"\n", " rng = np.random.default_rng(seed)\n", " fleet = make_fleet()\n", " V = len(fleet)\n", " temp = 12 + 14 * np.sin(2 * np.pi * (np.arange(months) - 3) / 12) # ์›”ํ‰๊ท  ๊ธฐ์˜จ(๊ฐ€์ƒ)\n", " hours = fleet[\"base_h\"].values[:, None] * (1 + 0.25 * np.sin(2 * np.pi * np.arange(months) / 12 + 1)) * rng.lognormal(0, .12, (V, months))\n", " fleet[\"hours\"] = hours.sum(axis=1) # ์ฐจ๋Ÿ‰๋ณ„ 18๊ฐœ์›” ์ด ๊ฐ€๋™์‹œ๊ฐ„\n", " fleet.attrs[\"month_h\"] = hours.sum(axis=0) # ์›”๋ณ„ ์ „์ฒด ๊ฐ€๋™์‹œ๊ฐ„(๊ณ ์žฅ๋ฅ  ๋ถ„๋ชจ)\n", " evs = []\n", " for p in PID:\n", " s = np.ones(months)\n", " if p in (\"P03\", \"P04\"): s = np.exp(-0.06 * (temp - 12)) # ํ•œ๋žญ๊ธฐ ์ „๊ธฐ๊ณ„ํ†ต ์ฆ๊ฐ€\n", " if p in (\"P06\", \"P16\"): s = np.exp(0.05 * (temp - 12)) # ํ˜น์„œ๊ธฐ ๋ƒ‰๊ฐ๊ณ„ํ†ต ์ฆ๊ฐ€\n", " cnt = rng.poisson(RATE[p] * hours / 1000 * fleet[\"mult\"].values[:, None] * s[None, :])\n", " for v, m in zip(*np.nonzero(cnt)):\n", " for _ in range(cnt[v, m]):\n", " evs.append((fleet[\"veh\"][v], int(m), m * 720 + rng.uniform(0, 720), p, float(temp[m] + rng.normal(0, 3))))\n", " ev = pd.DataFrame(evs, columns=[\"veh\", \"month\", \"t\", \"part\", \"temp\"]).sort_values(\"t\").reset_index(drop=True)\n", " ev[\"ev_id\"] = [f\"W{i:05d}\" for i in range(len(ev))]\n", " rows, codes = [], []\n", " for e in ev.itertuples():\n", " c, r = [], rng.random(3)\n", " if r[0] < 0.6: c.append(SPEC[e.part])\n", " if e.part in SHARED and r[1] < 0.6: c.append(SHARED[e.part])\n", " if r[2] < 0.5: c.append(SYMP[PSYS[e.part]])\n", " if not c: c.append(SHARED.get(e.part, SPEC[e.part]))\n", " for q, pr in CASCADE.get(e.part, []):\n", " if rng.random() < pr: c.append(SPEC[q])\n", " if rng.random() < 0.12: c.append(SPEC[PID[rng.integers(0, 16)]])\n", " c = list(dict.fromkeys(c))\n", " rng.shuffle(c) # ๋™์‹œ ๋ฐœ์ƒ ์ฝ”๋“œ์˜ ๊ธฐ๋ก ์ˆœ์„œ๋Š” ์ž„์˜\n", " codes.append(c)\n", " for k, cd in enumerate(c):\n", " rows.append((e.ev_id, e.veh, e.t + k * 0.1, cd))\n", " ev[\"codes\"] = codes\n", " log = pd.DataFrame(rows, columns=[\"ev_id\", \"veh\", \"ts\", \"code\"])\n", " return fleet, ev, log\n", "\n", "# ---------------------------------------------------------------------------\n", "# ๊ณตํ†ต ๋ถ„์„ ์กฐ๊ฐ: ์ฝ”๋“œ ๋ฉ€ํ‹ฐํ•ซ ํ–‰๋ ฌ, ์ฐจ๋Ÿ‰ ๋งฅ๋ฝ ํ”ผ์ฒ˜, ํ•™์Šต/๊ฒ€์ฆ ๋ถ„ํ• (์•ž 12๊ฐœ์›” ํ•™์Šต, ๋’ค 6๊ฐœ์›” ๊ฒ€์ฆ)\n", "# ---------------------------------------------------------------------------\n", "CIDX = {c: i for i, c in enumerate(CODES)}\n", "SPLIT_M = 12\n", "\n", "\n", "def multi_hot(ev):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 8] 1,000๊ฑด ๋™์‹œ ๋‹ค๋ฐœ ๊ณ ์žฅ ์ฝ”๋“œ ์ฃผ์ž… ์‹œ ์—ฐ๊ด€ ๊ณ ์žฅ ์ถ”๋ก  ๋ถ€ํ•˜ ํ…Œ์ŠคํŠธ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def context(ev, fleet):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 8] 1,000๊ฑด ๋™์‹œ ๋‹ค๋ฐœ ๊ณ ์žฅ ์ฝ”๋“œ ์ฃผ์ž… ์‹œ ์—ฐ๊ด€ ๊ณ ์žฅ ์ถ”๋ก  ๋ถ€ํ•˜ ํ…Œ์ŠคํŠธ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def feats(ev, fleet):\n", " X = multi_hot(ev)\n", " n = X.sum(axis=1, keepdims=True) / 3\n", " return np.hstack([X, n, context(ev, fleet)])\n", "\n", "\n", "def monthly_counts(ev):\n", " return ev.groupby([\"month\", \"part\"]).size().unstack(fill_value=0).reindex(columns=PID, fill_value=0)\n", "\n", "\n", "def build_monthly(ev, fleet, top_n=8):\n", " \"\"\"๋ถ€ํ’ˆ๋ณ„ ์›”๊ฐ„ ๊ณ ์žฅ ๊ฑด์ˆ˜ ์˜ˆ์ธก์šฉ ํ‘œ: ์ง€์—ฐ๊ฐ’(lag), ์ด๋™ํ‰๊ท , ์ถ”์„ธ, ๊ธฐ๋Œ€ ๊ฑด์ˆ˜(๊ฐ€๋™์‹œ๊ฐ„ x ์„ค๊ณ„ ๊ณ ์žฅ๋ฅ ), ๊ธฐ์˜จ ์ƒํ˜ธ์ž‘์šฉ, ๊ณ„์ ˆ ์„ฑ๋ถ„\"\"\"\n", " mc = ev.groupby([\"month\", \"part\"]).size().unstack(fill_value=0)\n", " top = ev[\"part\"].value_counts().index[:top_n]\n", " mh, tm = fleet.attrs[\"month_h\"], ev.groupby(\"month\")[\"temp\"].mean().values\n", " rows = []\n", " for p in top:\n", " c = mc[p].values.astype(float)\n", " for m in range(3, MONTHS):\n", " L = np.log1p(c)\n", " rows.append({\"part\": p, \"month\": m, \"y\": c[m], \"lag1\": L[m - 1], \"lag2\": L[m - 2], \"lag3\": L[m - 3], \"roll3\": L[m - 3:m].mean(),\n", " \"trend\": L[m - 1] - L[m - 2], \"roll3_std\": L[m - 3:m].std(), \"log_expected\": np.log(mh[m] / 1000 * RATE[p]),\n", " \"rate_lag1\": c[m - 1] / (mh[m - 1] / 1000) / RATE[p], \"temp\": tm[m] / 10, \"cold_elec\": max(0, 12 - tm[m]) / 10 * (p in (\"P03\", \"P04\")),\n", " \"heat_cool\": max(0, tm[m] - 12) / 10 * (p in (\"P06\", \"P16\")), \"sin\": np.sin(2 * np.pi * m / 12), \"cos\": np.cos(2 * np.pi * m / 12)})\n", " D = pd.DataFrame(rows)\n", " return D, [c for c in D.columns if c not in (\"part\", \"month\", \"y\")], top\n", "\n", "fleet, ev, log = make_data()\n", "train, test = ev[ev[\"month\"] < SPLIT_M], ev[ev[\"month\"] >= SPLIT_M].reset_index(drop=True)\n", "clf = LogisticRegression(C=3, max_iter=500).fit(feats(train, fleet), train[\"part\"])\n", "cls, FL = clf.classes_, fleet.set_index(\"veh\")\n", "VALID = set(CODES)\n", "\n", "\n", "def make_requests(n, seed=0, bad_rate=0.05):\n", " \"\"\"[๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฒ€์ฆ ๊ธฐ๊ฐ„ ์ •๋น„ ๊ฑด์—์„œ n๊ฑด์„ ๋ฝ‘์•„ DTC ์š”์ฒญ์œผ๋กœ ๋งŒ๋“ ๋‹ค. bad_rate ๋น„์œจ๋งŒํผ ์˜ค๋ฅ˜๋ฅผ ์ฃผ์ž…ํ•œ๋‹ค.\"\"\"\n", " rng = np.random.default_rng(seed)\n", " rows = test.iloc[rng.choice(len(test), n, replace=n > len(test))]\n", " reqs, kinds = [], []\n", " for e in rows.itertuples():\n", " r, kind = {\"veh\": e.veh, \"codes\": list(e.codes), \"temp\": e.temp, \"true\": e.part}, \"์ •์ƒ\"\n", " u = rng.random()\n", " if u < bad_rate * 0.3: r[\"codes\"] = [c.lower() + \" \" for c in r[\"codes\"]]; kind = \"ํ‘œ๊ธฐ ์˜ค๋ฅ˜\"\n", " elif u < bad_rate * 0.5: r[\"codes\"] = [\"ZZ9\"]; kind = \"๋ฏธ์ •์˜ ์ฝ”๋“œ\"\n", " elif u < bad_rate * 0.7: r[\"codes\"] = []; kind = \"๋นˆ ์š”์ฒญ\"\n", " elif u < bad_rate * 0.85: r[\"veh\"] = \"V999\"; kind = \"๋ฏธ๋“ฑ๋ก ์ฐจ๋Ÿ‰\"\n", " elif u < bad_rate: r[\"codes\"] = r[\"codes\"] * 3; kind = \"์ค‘๋ณต ์ฝ”๋“œ\"\n", " reqs.append(r); kinds.append(kind)\n", " return reqs, kinds\n", "\n", "\n", "def normalize(r):\n", " \"\"\"ํ‘œ๊ธฐ ์ •๋ฆฌ(๊ณต๋ฐฑยท๋Œ€์†Œ๋ฌธ์ž) ํ›„ ๊ฒ€์ฆ. ๋ฌธ์ œ๊ฐ€ ์žˆ์œผ๋ฉด ์˜ˆ์™ธ ๋Œ€์‹  ์‚ฌ์œ ๋ฅผ ๋Œ๋ ค์ค€๋‹ค.\"\"\"\n", " codes = sorted({c.strip().upper() for c in r[\"codes\"]})\n", " if r[\"veh\"] not in FL.index: return None, \"๋ฏธ๋“ฑ๋ก ์ฐจ๋Ÿ‰\"\n", " if not codes or any(c not in VALID for c in codes): return None, \"๋นˆ ์š”์ฒญยท๋ฏธ์ •์˜ ์ฝ”๋“œ\"\n", " return codes, \"\"\n", "\n", "\n", "def build_x(batch):\n", " X = np.zeros((len(batch), len(CODES) + 6))\n", " for i, (r, cs) in enumerate(batch):\n", " for c in cs: X[i, CIDX[c]] = 1\n", " v = FL.loc[r[\"veh\"]]\n", " X[i, len(CODES):] = [len(cs) / 3, v[\"age\"] / 25, v[\"pm_gap\"] / 200, v[\"terrain\"], v[\"load\"], (r[\"temp\"] - 12) / 15]\n", " return X\n", "\n", "\n", "def serve_batch(reqs):\n", " ok, rej = [], []\n", " for k, r in enumerate(reqs):\n", " cs, why = normalize(r)\n", " (ok if cs else rej).append((k, r, cs) if cs else (k, why))\n", " X = build_x([(r, cs) for _, r, cs in ok])\n", " P = clf.predict_proba(X) if len(ok) else np.zeros((0, len(cls)))\n", " top = cls[P.argmax(axis=1)] if len(ok) else []\n", " out = [{\"k\": k, \"part\": p, \"conf\": float(P[i].max()), \"alert\": PSYS[p] in CRIT_SYS} for i, ((k, _, _), p) in enumerate(zip(ok, top))]\n", " return out, rej\n", "\n", "\n", "def serve_one(r):\n", " cs, why = normalize(r)\n", " if not cs: return None\n", " p = cls[clf.predict_proba(build_x([(r, cs)]))[0].argmax()]\n", " return {\"part\": p, \"alert\": PSYS[p] in CRIT_SYS}\n", "\n", "\n", "# 1) 1,000๊ฑด ๋™์‹œ ์ฃผ์ž…\n", "reqs, kinds = make_requests(1000)\n", "t0 = time.perf_counter(); out, rej = serve_batch(reqs)\n", "agg = pd.DataFrame(out).groupby(\"part\").size().sort_values(ascending=False).head(5) # ๋Œ€์‹œ๋ณด๋“œ ์ง‘๊ณ„๊นŒ์ง€ ํฌํ•จ\n", "alerts = sum(o[\"alert\"] for o in out); dt = time.perf_counter() - t0\n", "print(f\"[๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] 1,000๊ฑด ๋™์‹œ ์ฃผ์ž…: ์ •์ƒ ์ฒ˜๋ฆฌ {len(out)}๊ฑด, ๊ฒฉ๋ฆฌ(์˜ค๋ฅ˜) {len(rej)}๊ฑด, ์ฒ˜๋ฆฌ ์‹œ๊ฐ„ {dt:.2f}์ดˆ (๋ชฉํ‘œ 2์ดˆ ์ด๋‚ด: {'๋‹ฌ์„ฑ' if dt <= 2 else '๋ฏธ๋‹ฌ'})\")\n", "print(f\" ์˜ค๋ฅ˜ ์ฃผ์ž… {sum(k != '์ •์ƒ' for k in kinds)}๊ฑด ์œ ํ˜•: \" + \", \".join(f\"{k} {v}\" for k, v in pd.Series(kinds).value_counts().items() if k != \"์ •์ƒ\"))\n", "print(\" ๊ฒฉ๋ฆฌ ์‚ฌ์œ : \" + \", \".join(f\"{k} {v}๊ฑด\" for k, v in pd.Series([w for _, w in rej]).value_counts().items()))\n", "\n", "# 2) ๊ฒฝ๋ณด ์ •ํ™•๋„(๊ธฐ๋™ยท์ œ๋™ ๊ณ„ํ†ต ๊ณ ์žฅ ๊ฒฝ๋ณด)\n", "truth = np.array([PSYS[reqs[o[\"k\"]][\"true\"]] in CRIT_SYS for o in out]); pa = np.array([o[\"alert\"] for o in out])\n", "acc = (truth == pa).mean(); rec = (pa & truth).sum() / truth.sum(); prec = (pa & truth).sum() / pa.sum()\n", "part_acc = np.mean([o[\"part\"] == reqs[o[\"k\"]][\"true\"] for o in out])\n", "print(f\"\\n๊ฒฝ๋ณด ์ •ํ™•๋„ {acc:.1%} (๋ชฉํ‘œ 95% ์ด์ƒ: {'๋‹ฌ์„ฑ' if acc >= 0.95 else '๋ฏธ๋‹ฌ'}), ์žฌํ˜„์œจ {rec:.1%}, ์ •๋ฐ€๋„ {prec:.1%}, ๊ฒฝ๋ณด {alerts}๊ฑด\")\n", "print(f\"์ฐธ๊ณ : ๋ถ€ํ’ˆ ๋‹จ์œ„ ์ง„๋‹จ ์ •ํ™•๋„ {part_acc:.1%}, ์ƒ์œ„ ๋ถ€ํ’ˆ \" + \", \".join(f\"{PNAME[p]} {n}\" for p, n in agg.items()))\n", "\n", "# 3) ๋‹จ๊ฑด ํ˜ธ์ถœ ๋ฐฉ์‹๊ณผ ๋น„๊ต(8๊ฐœ ์ž‘์—…์ž ๋™์‹œ ํ˜ธ์ถœ)\n", "t0 = time.perf_counter()\n", "with ThreadPoolExecutor(8) as ex:\n", " res = list(ex.map(serve_one, reqs))\n", "dt1 = time.perf_counter() - t0\n", "print(f\"\\n๋‹จ๊ฑด ํ˜ธ์ถœ 8์Šค๋ ˆ๋“œ {dt1:.2f}์ดˆ vs ์ผ๊ด„ ์ฒ˜๋ฆฌ {dt:.2f}์ดˆ (์ผ๊ด„์ด {dt1 / dt:.1f}๋ฐฐ ๋น ๋ฆ„), ๋‹จ๊ฑด ๋ฐฉ์‹ ์˜ˆ์™ธ 0๊ฑด: {all(r is None or 'part' in r for r in res)}\")\n", "\n", "# 4) ์š”์ฒญ ์ˆ˜ ํ™•์žฅ ์‹œํ—˜\n", "print(\"[์š”์ฒญ ์ˆ˜๋ณ„ ์ผ๊ด„ ์ฒ˜๋ฆฌ ์‹œ๊ฐ„]\")\n", "ns, ts = [100, 500, 1000, 2000, 5000], []\n", "for n in ns:\n", " rq, _ = make_requests(n, seed=n)\n", " t0 = time.perf_counter(); serve_batch(rq); ts.append(time.perf_counter() - t0)\n", " print(f\" {n:5d}๊ฑด: {ts[-1]:.2f}์ดˆ ({n / ts[-1]:.0f}๊ฑด/์ดˆ)\")\n", "\n", "plt.figure(figsize=(6, 3.5)); plt.plot(ns, ts, marker=\"o\"); plt.axhline(2, ls=\"--\", color=\"k\")\n", "plt.xlabel(\"requests\"); plt.ylabel(\"seconds\"); plt.title(\"Batch inference time (line = 2 s)\"); plt.tight_layout(); plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 08 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] 1,000๊ฑด ๋™์‹œ ์ฃผ์ž…: ์ •์ƒ ์ฒ˜๋ฆฌ 965๊ฑด, ๊ฒฉ๋ฆฌ(์˜ค๋ฅ˜) 35๊ฑด, ์ฒ˜๋ฆฌ ์‹œ๊ฐ„ 0.11์ดˆ (๋ชฉํ‘œ 2์ดˆ ์ด๋‚ด: ๋‹ฌ์„ฑ)\n", " ์˜ค๋ฅ˜ ์ฃผ์ž… 65๊ฑด ์œ ํ˜•: ํ‘œ๊ธฐ ์˜ค๋ฅ˜ 23, ๋นˆ ์š”์ฒญ 15, ๋ฏธ์ •์˜ ์ฝ”๋“œ 11, ๋ฏธ๋“ฑ๋ก ์ฐจ๋Ÿ‰ 9, ์ค‘๋ณต ์ฝ”๋“œ 7\n", " ๊ฒฉ๋ฆฌ ์‚ฌ์œ : ๋นˆ ์š”์ฒญยท๋ฏธ์ •์˜ ์ฝ”๋“œ 26๊ฑด, ๋ฏธ๋“ฑ๋ก ์ฐจ๋Ÿ‰ 9๊ฑด\n", "\n", "๊ฒฝ๋ณด ์ •ํ™•๋„ 99.8% (๋ชฉํ‘œ 95% ์ด์ƒ: ๋‹ฌ์„ฑ), ์žฌํ˜„์œจ 100.0%, ์ •๋ฐ€๋„ 99.6%, ๊ฒฝ๋ณด 567๊ฑด\n", "์ฐธ๊ณ : ๋ถ€ํ’ˆ ๋‹จ์œ„ ์ง„๋‹จ ์ •ํ™•๋„ 85.1%, ์ƒ์œ„ ๋ถ€ํ’ˆ ์—ฐ๋ฃŒ๋ถ„์‚ฌ๊ธฐ 303, ์ถ•์ „์ง€ 218, ํ„ฐ๋ณด์ฐจ์ € 126, ๋ฐœ์ „๊ธฐ 119, ๋ณ€์†๊ธฐ ์ œ์–ด๋ชจ๋“ˆ 53\n", "\n", "๋‹จ๊ฑด ํ˜ธ์ถœ 8์Šค๋ ˆ๋“œ 0.33์ดˆ vs ์ผ๊ด„ ์ฒ˜๋ฆฌ 0.11์ดˆ (์ผ๊ด„์ด 3.1๋ฐฐ ๋น ๋ฆ„), ๋‹จ๊ฑด ๋ฐฉ์‹ ์˜ˆ์™ธ 0๊ฑด: True\n", "[์š”์ฒญ ์ˆ˜๋ณ„ ์ผ๊ด„ ์ฒ˜๋ฆฌ ์‹œ๊ฐ„]\n", " 100๊ฑด: 0.01์ดˆ (15586๊ฑด/์ดˆ)\n", " 500๊ฑด: 0.03์ดˆ (18195๊ฑด/์ดˆ)\n", " 1000๊ฑด: 0.06์ดˆ (16478๊ฑด/์ดˆ)\n", " 2000๊ฑด: 0.30์ดˆ (6703๊ฑด/์ดˆ)\n", " 5000๊ฑด: 0.33์ดˆ (15293๊ฑด/์ดˆ)\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 09. AAR ๊ธฐ๋ฐ˜ ์—ฐ๊ด€ ๊ณ ์žฅ ์˜ค์ง„๋‹จ ์‚ฌ๋ก€ ๋ถ„์„ ๋ฐ ๊ณ ์žฅ ๋ฃฐ๋ฒ ์ด์Šค ์žฌํŠœ๋‹\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 3.0์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "AAR ๊ธฐ๋ฐ˜ ์—ฐ๊ด€ ๊ณ ์žฅ ์˜ค์ง„๋‹จ ์‚ฌ๋ก€๋ฅผ ๋ถ„์„ํ•˜๊ณ  ๋ฃฐ๋ฒ ์ด์Šค๋ฅผ ์žฌํŠœ๋‹ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **AAR ๊ธฐ๊ฐ„์˜ ์—ฐ๊ด€ ๊ณ ์žฅ ์˜ค์ง„๋‹จ ์‚ฌ๋ก€๋ฅผ ์›์ธ๋ณ„๋กœ ๋ถ„์„ํ•˜๊ณ  ๊ณ ์žฅ ๋ฃฐ๋ฒ ์ด์Šค(์ฝ”๋“œ ์œ ํ˜• ๊ฐ€์ค‘์น˜ยท์˜ˆ์™ธ ๊ทœ์น™)๋ฅผ ์žฌํŠœ๋‹ํ•ด ํšจ๊ณผ๋ฅผ ์ง€ํ‘œ๋กœ ์‚ฐ์ถœํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **AAR ๊ธฐ๊ฐ„ ์˜ค์ง„๋‹จ ๊ฑด์„ ๋ชจํ˜ธ ์ฝ”๋“œ ์ž…๋ ฅ, ์—ฐ์‡„ ๊ณ ์žฅ ์ฝ”๋“œ ์˜ค์ธ, ๋ณต์ˆ˜ ๊ณ ์œ  ์ฝ”๋“œ ์ถฉ๋Œ ๋“ฑ์œผ๋กœ ๋ถ„๋ฅ˜ํ•˜๊ณ  ๋Œ€ํ‘œ ์‚ฌ๋ก€ ์ •๋ฆฌํ•˜๊ณ  ๊ทœ๊ฒฉํ™”ํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `reports/mnt03_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(์‚ฌํ›„๊ฒ€ํ† ) ๋Œ€์ƒ ๊ธฐ๊ฐ„์˜ ์—ฐ๊ด€ ๊ณ ์žฅ ์˜ค์ง„๋‹จ ์‚ฌ๋ก€๋ฅผ ์›์ธ๋ณ„(๋ชจํ˜ธ ์ฝ”๋“œ๋งŒ ์ž…๋ ฅ, ์—ฐ์‡„ ๊ณ ์žฅ ์ฝ”๋“œ ์˜ค์ธ, ์žก์Œ ์ฝ”๋“œ ์˜ค์ธ, ๊ธฐํƒ€)๋กœ ๋ถ„๋ฅ˜ํ•˜๊ณ , ๊ณ ์žฅ ๋ฃฐ๋ฒ ์ด์Šค์˜ ์ฝ”๋“œ ์œ ํ˜•๋ณ„ ๊ฐ€์ค‘์น˜์™€ ์‚ฌ์ „ํ™•๋ฅ  ๋ณด์ •์„ AAR ๊ธฐ๊ฐ„ ์ž๋ฃŒ๋กœ ์žฌํŠœ๋‹ํ•œ ๋’ค ๋ณ„๋„ ๊ฒ€์ฆ ๊ธฐ๊ฐ„์—์„œ ํšจ๊ณผ๋ฅผ ํ™•์ธํ•œ๋‹ค. ๋น„์šฉ ์ ˆ๊ฐ์•ก, ์žฌ์ž‘์—… ์‹œ๊ฐ„ ๋‹จ์ถ•๋ฅ , ํ—›๊ต์ฒด ๋ถ€ํ’ˆ ๊ฐ์†Œ์œจ์˜ 3๋Œ€ ํšจ๊ณผ ์ง€ํ‘œ์™€ ๋‹จ๊ณ„๋ณ„ ๋„์ž… ๋กœ๋“œ๋งต์„ ์‚ฐ์ถœํ•œ๋‹ค.\"\"\"\n", "import itertools\n", "from collections import Counter\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ƒ ์ฐจ๋Ÿ‰ 100๋Œ€์˜ 18๊ฐœ์›” ๊ณ ์žฅ ์ง„๋‹จ ์ฝ”๋“œ(DTC)ยท์ •๋น„ ์ด๋ ฅ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ๋ถ€๋Œ€ยท์žฅ๋น„ยท์ •๋น„ ์‹ค์  ์—†์Œ)\n", "# ๋ถ€ํ’ˆ 16์ข…(1000๊ฐ€๋™์‹œ๊ฐ„๋‹น ๊ณ ์žฅ๋ฅ  ์ฐจ๋“ฑ), DTC 28์ข…(๋ถ€ํ’ˆ ๊ณ ์œ  ์ฝ”๋“œ, ์ง ๋ถ€ํ’ˆ ๊ณต์œ  ์ฝ”๋“œ, ๊ณ„ํ†ต ์ฆ์ƒ ์ฝ”๋“œ)\n", "# ๊ณ ์žฅ 1๊ฑด(์ •๋น„ ์ด๋ ฅ)๋งˆ๋‹ค DTC๊ฐ€ 1~5๊ฐœ ๋™์‹œ ๋ฐœ์ƒํ•˜๊ณ , ์—ฐ์‡„ ๊ณ ์žฅ ์ฝ”๋“œ์™€ ์ผ์‹œ ์žก์Œ ์ฝ”๋“œ๊ฐ€ ์„ž์ธ๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "PARTS = [ # (ID, ์ด๋ฆ„, ๊ณ„ํ†ต, 1000๊ฐ€๋™์‹œ๊ฐ„๋‹น ๊ณ ์žฅ๋ฅ , ๊ฐ€์ƒ ๋‹จ๊ฐ€์ง€์ˆ˜, ์ˆ˜๋ฆฌ์‹œ๊ฐ„ h)\n", " (\"P01\", \"์—ฐ๋ฃŒ๋ถ„์‚ฌ๊ธฐ\", \"engine\", 12.0, 3.0, 2.0), (\"P02\", \"ํ„ฐ๋ณด์ฐจ์ €\", \"engine\", 9.0, 8.0, 5.0),\n", " (\"P03\", \"์ถ•์ „์ง€\", \"electric\", 6.5, 4.0, 1.5), (\"P04\", \"๋ฐœ์ „๊ธฐ\", \"electric\", 4.0, 9.0, 3.0),\n", " (\"P05\", \"๋ณ€์†๊ธฐ ์ œ์–ด๋ชจ๋“ˆ\", \"drive\", 3.0, 12.0, 6.0), (\"P06\", \"๋ƒ‰๊ฐ์ˆ˜ ํŽŒํ”„\", \"engine\", 2.2, 3.5, 3.0),\n", " (\"P07\", \"์œ ์•• ํŽŒํ”„\", \"hydraulic\", 1.6, 10.0, 4.0), (\"P08\", \"์†๋„ ์„ผ์„œ\", \"drive\", 1.2, 2.0, 1.5),\n", " (\"P09\", \"ECU ์ปค๋„ฅํ„ฐ\", \"electric\", 0.9, 2.5, 1.5), (\"P10\", \"์—ฐ๋ฃŒ ํ•„ํ„ฐ\", \"engine\", 0.7, 0.8, 0.7),\n", " (\"P11\", \"์ œ๋™ ๋ฐธ๋ธŒ\", \"brake\", 0.5, 6.0, 3.0), (\"P12\", \"์กฐํ–ฅ ์•ก์ถ”์—์ดํ„ฐ\", \"drive\", 0.4, 9.0, 5.0),\n", " (\"P13\", \"๊ณต๊ธฐ ํ•„ํ„ฐ\", \"engine\", 0.3, 0.5, 0.5), (\"P14\", \"ํ†ต์‹  ์ธํ„ฐํŽ˜์ด์Šค ๋ชจ๋“ˆ\", \"electric\", 0.25, 3.0, 1.5),\n", " (\"P15\", \"์œคํ™œ์œ  ์••๋ ฅ ์„ผ์„œ\", \"engine\", 0.2, 1.0, 1.0), (\"P16\", \"ํŒฌ ๊ตฌ๋™ ๋ชจํ„ฐ\", \"cooling\", 0.15, 2.0, 2.0)]\n", "PID = [p[0] for p in PARTS]\n", "PNAME = {p[0]: p[1] for p in PARTS}; PSYS = {p[0]: p[2] for p in PARTS}; RATE = {p[0]: p[3] for p in PARTS}\n", "COST = {p[0]: p[4] for p in PARTS}; REPH = {p[0]: p[5] for p in PARTS}\n", "PAIRS = [(\"P01\", \"P02\"), (\"P03\", \"P04\"), (\"P05\", \"P08\"), (\"P06\", \"P10\"), (\"P09\", \"P14\"), (\"P13\", \"P15\")]\n", "SHARED = {p: f\"S{k + 1:02d}\" for k, pr in enumerate(PAIRS) for p in pr} # ์ง ๋ถ€ํ’ˆ์ด ํ•จ๊ป˜ ์“ฐ๋Š” ๋ชจํ˜ธํ•œ ์ฝ”๋“œ\n", "SPEC = {p: f\"C{p[1:]}1\" for p in PID} # ๋ถ€ํ’ˆ ๊ณ ์œ  ์ฝ”๋“œ\n", "SYS = [\"engine\", \"electric\", \"drive\", \"hydraulic\", \"brake\", \"cooling\"]\n", "SYMP = {s: f\"G{i + 1:02d}\" for i, s in enumerate(SYS)} # ๊ณ„ํ†ต ์ฆ์ƒ ์ฝ”๋“œ\n", "CODES = sorted(set(SPEC.values()) | set(SHARED.values()) | set(SYMP.values()))\n", "CASCADE = {\"P01\": [(\"P10\", 0.20)], \"P02\": [(\"P06\", 0.20)], \"P03\": [(\"P04\", 0.25)], \"P04\": [(\"P09\", 0.25)],\n", " \"P05\": [(\"P08\", 0.30)], \"P06\": [(\"P16\", 0.30)], \"P09\": [(\"P14\", 0.20)], \"P12\": [(\"P07\", 0.20)]}\n", "CRIT_SYS = {\"engine\", \"drive\", \"brake\"} # ๊ธฐ๋™ยท์ œ๋™ ๋ถˆ๋Šฅ ์œ„ํ—˜ ๊ณ„ํ†ต(๊ฒฝ๋ณด ๋Œ€์ƒ)\n", "MONTHS = 18\n", "\n", "\n", "def make_fleet(n=100, seed=0):\n", " rng = np.random.default_rng(seed)\n", " f = pd.DataFrame({\"veh\": [f\"V{i:03d}\" for i in range(n)], \"age\": rng.integers(1, 26, n).astype(float),\n", " \"terrain\": rng.uniform(0, 1, n), \"load\": rng.uniform(0, 1, n), \"pm_gap\": rng.uniform(20, 200, n),\n", " \"humid\": rng.uniform(0, 1, n), \"base_h\": rng.uniform(55, 125, n)})\n", " f[\"mult\"] = np.exp(0.045 * (f[\"age\"] - 13) + 0.9 * (f[\"terrain\"] - .5) + 0.8 * (f[\"load\"] - .5)\n", " + 0.006 * (f[\"pm_gap\"] - 110) + 0.7 * (f[\"humid\"] - .5))\n", " return f\n", "\n", "\n", "def make_data(seed=1, months=MONTHS):\n", " \"\"\"๋ฐ˜ํ™˜: fleet(์ฐจ๋Ÿ‰ ์†์„ฑ), ev(์ •๋น„ ์ด๋ ฅ 1๊ฑด=1ํ–‰: ๋ฐœ์ƒ DTC ๋ชฉ๋ก, ์‹ค์ œ ๊ณ ์žฅ ๋ถ€ํ’ˆ), log(DTC ๋ฐœ์ƒ ๋กœ๊ทธ, ํ–‰=์ฝ”๋“œ 1๊ฑด)\"\"\"\n", " rng = np.random.default_rng(seed)\n", " fleet = make_fleet()\n", " V = len(fleet)\n", " temp = 12 + 14 * np.sin(2 * np.pi * (np.arange(months) - 3) / 12) # ์›”ํ‰๊ท  ๊ธฐ์˜จ(๊ฐ€์ƒ)\n", " hours = fleet[\"base_h\"].values[:, None] * (1 + 0.25 * np.sin(2 * np.pi * np.arange(months) / 12 + 1)) * rng.lognormal(0, .12, (V, months))\n", " fleet[\"hours\"] = hours.sum(axis=1) # ์ฐจ๋Ÿ‰๋ณ„ 18๊ฐœ์›” ์ด ๊ฐ€๋™์‹œ๊ฐ„\n", " fleet.attrs[\"month_h\"] = hours.sum(axis=0) # ์›”๋ณ„ ์ „์ฒด ๊ฐ€๋™์‹œ๊ฐ„(๊ณ ์žฅ๋ฅ  ๋ถ„๋ชจ)\n", " evs = []\n", " for p in PID:\n", " s = np.ones(months)\n", " if p in (\"P03\", \"P04\"): s = np.exp(-0.06 * (temp - 12)) # ํ•œ๋žญ๊ธฐ ์ „๊ธฐ๊ณ„ํ†ต ์ฆ๊ฐ€\n", " if p in (\"P06\", \"P16\"): s = np.exp(0.05 * (temp - 12)) # ํ˜น์„œ๊ธฐ ๋ƒ‰๊ฐ๊ณ„ํ†ต ์ฆ๊ฐ€\n", " cnt = rng.poisson(RATE[p] * hours / 1000 * fleet[\"mult\"].values[:, None] * s[None, :])\n", " for v, m in zip(*np.nonzero(cnt)):\n", " for _ in range(cnt[v, m]):\n", " evs.append((fleet[\"veh\"][v], int(m), m * 720 + rng.uniform(0, 720), p, float(temp[m] + rng.normal(0, 3))))\n", " ev = pd.DataFrame(evs, columns=[\"veh\", \"month\", \"t\", \"part\", \"temp\"]).sort_values(\"t\").reset_index(drop=True)\n", " ev[\"ev_id\"] = [f\"W{i:05d}\" for i in range(len(ev))]\n", " rows, codes = [], []\n", " for e in ev.itertuples():\n", " c, r = [], rng.random(3)\n", " if r[0] < 0.6: c.append(SPEC[e.part])\n", " if e.part in SHARED and r[1] < 0.6: c.append(SHARED[e.part])\n", " if r[2] < 0.5: c.append(SYMP[PSYS[e.part]])\n", " if not c: c.append(SHARED.get(e.part, SPEC[e.part]))\n", " for q, pr in CASCADE.get(e.part, []):\n", " if rng.random() < pr: c.append(SPEC[q])\n", " if rng.random() < 0.12: c.append(SPEC[PID[rng.integers(0, 16)]])\n", " c = list(dict.fromkeys(c))\n", " rng.shuffle(c) # ๋™์‹œ ๋ฐœ์ƒ ์ฝ”๋“œ์˜ ๊ธฐ๋ก ์ˆœ์„œ๋Š” ์ž„์˜\n", " codes.append(c)\n", " for k, cd in enumerate(c):\n", " rows.append((e.ev_id, e.veh, e.t + k * 0.1, cd))\n", " ev[\"codes\"] = codes\n", " log = pd.DataFrame(rows, columns=[\"ev_id\", \"veh\", \"ts\", \"code\"])\n", " return fleet, ev, log\n", "\n", "# ---------------------------------------------------------------------------\n", "# ๊ณตํ†ต ๋ถ„์„ ์กฐ๊ฐ: ์ฝ”๋“œ ๋ฉ€ํ‹ฐํ•ซ ํ–‰๋ ฌ, ์ฐจ๋Ÿ‰ ๋งฅ๋ฝ ํ”ผ์ฒ˜, ํ•™์Šต/๊ฒ€์ฆ ๋ถ„ํ• (์•ž 12๊ฐœ์›” ํ•™์Šต, ๋’ค 6๊ฐœ์›” ๊ฒ€์ฆ)\n", "# ---------------------------------------------------------------------------\n", "CIDX = {c: i for i, c in enumerate(CODES)}\n", "SPLIT_M = 12\n", "\n", "\n", "def multi_hot(ev):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 9] AAR ๊ธฐ๋ฐ˜ ์—ฐ๊ด€ ๊ณ ์žฅ ์˜ค์ง„๋‹จ ์‚ฌ๋ก€ ๋ถ„์„ ๋ฐ ๊ณ ์žฅ ๋ฃฐ๋ฒ ์ด์Šค ์žฌํŠœ๋‹ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def context(ev, fleet):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 9] AAR ๊ธฐ๋ฐ˜ ์—ฐ๊ด€ ๊ณ ์žฅ ์˜ค์ง„๋‹จ ์‚ฌ๋ก€ ๋ถ„์„ ๋ฐ ๊ณ ์žฅ ๋ฃฐ๋ฒ ์ด์Šค ์žฌํŠœ๋‹ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def feats(ev, fleet):\n", " X = multi_hot(ev)\n", " n = X.sum(axis=1, keepdims=True) / 3\n", " return np.hstack([X, n, context(ev, fleet)])\n", "\n", "\n", "def monthly_counts(ev):\n", " return ev.groupby([\"month\", \"part\"]).size().unstack(fill_value=0).reindex(columns=PID, fill_value=0)\n", "\n", "\n", "def build_monthly(ev, fleet, top_n=8):\n", " \"\"\"๋ถ€ํ’ˆ๋ณ„ ์›”๊ฐ„ ๊ณ ์žฅ ๊ฑด์ˆ˜ ์˜ˆ์ธก์šฉ ํ‘œ: ์ง€์—ฐ๊ฐ’(lag), ์ด๋™ํ‰๊ท , ์ถ”์„ธ, ๊ธฐ๋Œ€ ๊ฑด์ˆ˜(๊ฐ€๋™์‹œ๊ฐ„ x ์„ค๊ณ„ ๊ณ ์žฅ๋ฅ ), ๊ธฐ์˜จ ์ƒํ˜ธ์ž‘์šฉ, ๊ณ„์ ˆ ์„ฑ๋ถ„\"\"\"\n", " mc = ev.groupby([\"month\", \"part\"]).size().unstack(fill_value=0)\n", " top = ev[\"part\"].value_counts().index[:top_n]\n", " mh, tm = fleet.attrs[\"month_h\"], ev.groupby(\"month\")[\"temp\"].mean().values\n", " rows = []\n", " for p in top:\n", " c = mc[p].values.astype(float)\n", " for m in range(3, MONTHS):\n", " L = np.log1p(c)\n", " rows.append({\"part\": p, \"month\": m, \"y\": c[m], \"lag1\": L[m - 1], \"lag2\": L[m - 2], \"lag3\": L[m - 3], \"roll3\": L[m - 3:m].mean(),\n", " \"trend\": L[m - 1] - L[m - 2], \"roll3_std\": L[m - 3:m].std(), \"log_expected\": np.log(mh[m] / 1000 * RATE[p]),\n", " \"rate_lag1\": c[m - 1] / (mh[m - 1] / 1000) / RATE[p], \"temp\": tm[m] / 10, \"cold_elec\": max(0, 12 - tm[m]) / 10 * (p in (\"P03\", \"P04\")),\n", " \"heat_cool\": max(0, tm[m] - 12) / 10 * (p in (\"P06\", \"P16\")), \"sin\": np.sin(2 * np.pi * m / 12), \"cos\": np.cos(2 * np.pi * m / 12)})\n", " D = pd.DataFrame(rows)\n", " return D, [c for c in D.columns if c not in (\"part\", \"month\", \"y\")], top\n", "\n", "fleet, ev, log = make_data()\n", "train, aar, val = ev[ev[\"month\"] < 12], ev[(ev[\"month\"] >= 12) & (ev[\"month\"] < 15)].reset_index(drop=True), ev[ev[\"month\"] >= 15].reset_index(drop=True)\n", "\n", "# 1) ๋ฃฐ๋ฒ ์ด์Šค: ์ ์ˆ˜(๋ถ€ํ’ˆ) = sum[์ฝ”๋“œ ์œ ํ˜• ๊ฐ€์ค‘์น˜ x P(๋ถ€ํ’ˆ | ์ฝ”๋“œ)] + ์‚ฌ์ „ํ™•๋ฅ  ๋ณด์ •. ํ•™์Šต ๊ธฐ๊ฐ„ ์กฐ๊ฑด๋ถ€ํ™•๋ฅ ํ‘œ๋ฅผ ์“ด๋‹ค.\n", "Xtr = multi_hot(train)\n", "Y = pd.get_dummies(train[\"part\"]).reindex(columns=PID, fill_value=0).values.astype(float)\n", "COND = (Xtr.T @ Y) / np.maximum(Xtr.sum(axis=0)[:, None], 1) # P(๋ถ€ํ’ˆ | ์ฝ”๋“œ)\n", "PRIOR = np.log(Y.mean(axis=0) + 1e-9)\n", "CTYPE = np.array([0 if c in SPEC.values() else 1 if c in SHARED.values() else 2 for c in CODES]) # 0 ๊ณ ์œ  / 1 ๊ณต์œ  / 2 ๊ณ„ํ†ต ์ฆ์ƒ\n", "V1 = {\"w\": (1.0, 1.0, 1.0), \"beta\": 0.0} # ๊ธฐ์กด ๋ฃฐ๋ฒ ์ด์Šค(๋ชจ๋“  ์ฝ”๋“œ ๋™์ผ ๊ฐ€์ค‘, ์‚ฌ์ „ํ™•๋ฅ  ๋ฏธ๋ฐ˜์˜)\n", "\n", "\n", "def predict(ev_, p):\n", " W = np.array(p[\"w\"])[CTYPE][:, None] * COND\n", " S = multi_hot(ev_) @ W + p[\"beta\"] * PRIOR[None, :]\n", " base = np.array(PID)[S.argmax(axis=1)]\n", " ov = p.get(\"override\", {}) # AAR์—์„œ ๋งŒ๋“  ์˜ˆ์™ธ ๊ทœ์น™(DTC ์กฐํ•ฉ -> ๋ถ€ํ’ˆ)์ด ์šฐ์„ \n", " return np.array([ov.get(frozenset(c), b) for c, b in zip(ev_[\"codes\"], base)])\n", "\n", "\n", "def cost(pred, true):\n", " return sum(REPH[t] if p == t else COST[p] + REPH[p] + 3.0 + REPH[t] for p, t in zip(pred, true))\n", "\n", "\n", "def err_stats(pred, true):\n", " wrong = pred != true.values\n", " return int(wrong.sum()), sum(REPH[p] + 3.0 + REPH[t] for p, t in zip(pred[wrong], true[wrong])), int(wrong.sum())\n", "\n", "\n", "# 2) AAR: ์˜ค์ง„๋‹จ ์›์ธ ๋ถ„๋ฅ˜\n", "pa = predict(aar, V1)\n", "casc = {(p, q) for p, lst in CASCADE.items() for q, _ in lst}\n", "cause = []\n", "for cs, t, q in zip(aar[\"codes\"], aar[\"part\"], pa):\n", " if t == q: continue\n", " sp = [c for c in cs if c in SPEC.values()]\n", " if not sp: cause.append(\"๋ชจํ˜ธ ์ฝ”๋“œ(๊ณต์œ ยท์ฆ์ƒ)๋งŒ ์ž…๋ ฅ\")\n", " elif len(sp) >= 2 and ((t, q) in casc or (q, t) in casc): cause.append(\"์—ฐ์‡„ ๊ณ ์žฅ ์ฝ”๋“œ ์˜ค์ธ\")\n", " elif len(sp) >= 2: cause.append(\"๋ณต์ˆ˜ ๊ณ ์œ  ์ฝ”๋“œ ์ถฉ๋Œ(์žก์Œ ํฌํ•จ)\")\n", " else: cause.append(\"๊ธฐํƒ€(๊ณ ์œ  ์ฝ”๋“œ 1๊ฐœ, ํ™•๋ฅ  ํŽธํ–ฅ)\")\n", "C = pd.Series(cause).value_counts()\n", "print(f\"[๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] AAR ๊ธฐ๊ฐ„(12~14์›”) ์ •๋น„ {len(aar)}๊ฑด: ๊ธฐ์กด ๋ฃฐ๋ฒ ์ด์Šค ์˜ค์ง„๋‹จ {len(cause)}๊ฑด ({len(cause) / len(aar):.1%})\")\n", "for k, v in C.items():\n", " print(f\" {k:24s} {v:4d}๊ฑด ({v / len(cause):.0%})\")\n", "ex = [(cs, PNAME[t], PNAME[q]) for cs, t, q in zip(aar[\"codes\"], aar[\"part\"], pa) if t != q and sum(c in SPEC.values() for c in cs) >= 2][:1]\n", "print(\" ์‚ฌ๋ก€:\", f\"์ž…๋ ฅ DTC {ex[0][0]} -> ์˜ค์ง„๋‹จ {ex[0][2]}, ์‹ค์ œ {ex[0][1]}\" if ex else \"-\")\n", "\n", "# 3) ์žฌํŠœ๋‹: (๊ฐ€) ์ฝ”๋“œ ์œ ํ˜• ๊ฐ€์ค‘์น˜ยท์‚ฌ์ „ํ™•๋ฅ  ๊ฒฉ์ž ํƒ์ƒ‰, (๋‚˜) AAR ์˜ค์ง„๋‹จ ์กฐํ•ฉ์—์„œ ์˜ˆ์™ธ ๊ทœ์น™ ์ถ”์ถœ. ๋ชจ๋‘ AAR ๊ธฐ๊ฐ„ ์ž๋ฃŒ๋งŒ ์“ฐ๊ณ  ๊ฒ€์ฆ ๊ธฐ๊ฐ„์€ ๊ฑด๋“œ๋ฆฌ์ง€ ์•Š๋Š”๋‹ค.\n", "best = None\n", "for w in itertools.product((0.5, 1.0, 1.5, 2.0), repeat=3):\n", " for beta in (0.0, 0.2, 0.5, 1.0):\n", " p = {\"w\": w, \"beta\": beta}\n", " c = cost(predict(aar, p), aar[\"part\"])\n", " if best is None or c < best[0]: best = (c, p)\n", "V2 = dict(best[1])\n", "pa2 = predict(aar, V2)\n", "bad_sets = {frozenset(c) for c, t, q in zip(aar[\"codes\"], aar[\"part\"], pa2) if t != q}\n", "grp = {}\n", "for c, t in zip(aar[\"codes\"], aar[\"part\"]):\n", " grp.setdefault(frozenset(c), Counter())[t] += 1\n", "V2[\"override\"] = {k: v.most_common(1)[0][0] for k, v in grp.items()\n", " if k in bad_sets and sum(v.values()) >= 3 and v.most_common(1)[0][1] / sum(v.values()) >= 0.6}\n", "print(f\"\\n์žฌํŠœ๋‹ ๊ฒฐ๊ณผ: ์ฝ”๋“œ ์œ ํ˜• ๊ฐ€์ค‘์น˜(๊ณ ์œ ยท๊ณต์œ ยท์ฆ์ƒ) {V1['w']} -> {V2['w']}, ์‚ฌ์ „ํ™•๋ฅ  ๋ณด์ • {V1['beta']} -> {V2['beta']}, AAR ์˜ˆ์™ธ ๊ทœ์น™ {len(V2['override'])}๊ฐœ ์ถ”๊ฐ€\")\n", "rows = []\n", "for nm, d in ((\"AAR ๊ธฐ๊ฐ„(ํŠœ๋‹)\", aar), (\"๊ฒ€์ฆ ๊ธฐ๊ฐ„(15~17์›”)\", val)):\n", " p1, p2 = predict(d, V1), predict(d, V2)\n", " rows.append((nm, (p1 == d[\"part\"].values).mean(), (p2 == d[\"part\"].values).mean(), cost(p1, d[\"part\"]), cost(p2, d[\"part\"])))\n", " print(f\" {nm:14s} ์ •ํ™•๋„ {rows[-1][1]:.1%} -> {rows[-1][2]:.1%}, ๋น„์šฉ์ง€์ˆ˜ {rows[-1][3]:.0f} -> {rows[-1][4]:.0f}\")\n", "\n", "# 4) ROI 3๋Œ€ ์ง€ํ‘œ(๊ฒ€์ฆ ๊ธฐ๊ฐ„ 3๊ฐœ์›”, ๊ฐ€์ • ๋‹จ๊ฐ€ ๊ธฐ์ค€ ์ƒ๋Œ€๊ฐ’)\n", "p1, p2 = predict(val, V1), predict(val, V2)\n", "n1, h1, _ = err_stats(p1, val[\"part\"]); n2, h2, _ = err_stats(p2, val[\"part\"])\n", "saved = cost(p1, val[\"part\"]) - cost(p2, val[\"part\"])\n", "wasted1 = sum(COST[p] for p, t in zip(p1, val[\"part\"]) if p != t); wasted2 = sum(COST[p] for p, t in zip(p2, val[\"part\"]) if p != t)\n", "print(\"\\n[ROI 3๋Œ€ ์ •๋Ÿ‰ ํšจ๊ณผ ์ง€ํ‘œ] (๊ฒ€์ฆ 3๊ฐœ์›”, ๊ฐ€์ • ๋‹จ๊ฐ€)\")\n", "print(f\" 1) ๋น„์šฉ ์ ˆ๊ฐ: ๋น„์šฉ์ง€์ˆ˜ {saved:.0f} ์ ˆ๊ฐ ({saved / cost(p1, val['part']):.1%}), ์—ฐํ™˜์‚ฐ ์•ฝ {saved * 4:.0f}\")\n", "print(f\" 2) ์‹œ๊ฐ„ ๋‹จ์ถ•: ์˜ค์ง„๋‹จ ์žฌ์ž‘์—… {h1:.0f}h -> {h2:.0f}h ({1 - h2 / h1:.1%} ๋‹จ์ถ•)\")\n", "print(f\" 3) ํ—›๊ต์ฒด ๋ถ€ํ’ˆ ๋น„์šฉ: {wasted1:.0f} -> {wasted2:.0f} ({1 - wasted2 / wasted1:.1%} ๊ฐ์†Œ), ์˜ค์ง„๋‹จ {n1}๊ฑด -> {n2}๊ฑด\")\n", "print(\"[๋„์ž… ๋กœ๋“œ๋งต] ๋‹จ๊ธฐ(1~3์›”): ๊ฐ€์ค‘์น˜ ๊ฐฑ์‹  + ์ •๋น„๊ด€ ํ™•์ธ ๋ณ‘ํ–‰ / ์ค‘๊ธฐ(3~12์›”): AAR ๋ถ„๊ธฐ ๋ฐ˜๋ณตยท์‹ ๊ทœ ์ฝ”๋“œ ๋ฐ˜์˜ / ์žฅ๊ธฐ(1๋…„~): ๊ตฐ์ˆ˜ ์ •๋ณด์ฒด๊ณ„ ์ด๋ ฅ ์—ฐ๋™\")\n", "\n", "fig, ax = plt.subplots(1, 2, figsize=(10, 3.5))\n", "ax[0].barh([f\"cause {i + 1}\" for i in range(len(C))][::-1], C.values[::-1]); ax[0].set_title(\"Misdiagnosis causes (AAR period)\")\n", "ax[1].bar([\"before\", \"after\"], [rows[1][1], rows[1][2]]); ax[1].set_ylim(0.5, 1); ax[1].set_title(\"Validation accuracy\")\n", "plt.tight_layout(); plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 09 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] AAR ๊ธฐ๊ฐ„(12~14์›”) ์ •๋น„ 2336๊ฑด: ๊ธฐ์กด ๋ฃฐ๋ฒ ์ด์Šค ์˜ค์ง„๋‹จ 522๊ฑด (22.3%)\n", " ๋ชจํ˜ธ ์ฝ”๋“œ(๊ณต์œ ยท์ฆ์ƒ)๋งŒ ์ž…๋ ฅ 243๊ฑด (47%)\n", " ๊ธฐํƒ€(๊ณ ์œ  ์ฝ”๋“œ 1๊ฐœ, ํ™•๋ฅ  ํŽธํ–ฅ) 237๊ฑด (45%)\n", " ์—ฐ์‡„ ๊ณ ์žฅ ์ฝ”๋“œ ์˜ค์ธ 26๊ฑด (5%)\n", " ๋ณต์ˆ˜ ๊ณ ์œ  ์ฝ”๋“œ ์ถฉ๋Œ(์žก์Œ ํฌํ•จ) 16๊ฑด (3%)\n", " ์‚ฌ๋ก€: ์ž…๋ ฅ DTC ['S04', 'G01', 'C081', 'C101'] -> ์˜ค์ง„๋‹จ ์—ฐ๋ฃŒ๋ถ„์‚ฌ๊ธฐ, ์‹ค์ œ ์—ฐ๋ฃŒ ํ•„ํ„ฐ\n", "\n", "์žฌํŠœ๋‹ ๊ฒฐ๊ณผ: ์ฝ”๋“œ ์œ ํ˜• ๊ฐ€์ค‘์น˜(๊ณ ์œ ยท๊ณต์œ ยท์ฆ์ƒ) (1.0, 1.0, 1.0) -> (2.0, 1.0, 0.5), ์‚ฌ์ „ํ™•๋ฅ  ๋ณด์ • 0.0 -> 0.0, AAR ์˜ˆ์™ธ ๊ทœ์น™ 31๊ฐœ ์ถ”๊ฐ€\n", " AAR ๊ธฐ๊ฐ„(ํŠœ๋‹) ์ •ํ™•๋„ 77.7% -> 82.9%, ๋น„์šฉ์ง€์ˆ˜ 11938 -> 10805\n", " ๊ฒ€์ฆ ๊ธฐ๊ฐ„(15~17์›”) ์ •ํ™•๋„ 78.1% -> 80.1%, ๋น„์šฉ์ง€์ˆ˜ 8449 -> 7832\n", "\n", "[ROI 3๋Œ€ ์ •๋Ÿ‰ ํšจ๊ณผ ์ง€ํ‘œ] (๊ฒ€์ฆ 3๊ฐœ์›”, ๊ฐ€์ • ๋‹จ๊ฐ€)\n", " 1) ๋น„์šฉ ์ ˆ๊ฐ: ๋น„์šฉ์ง€์ˆ˜ 616 ์ ˆ๊ฐ (7.3%), ์—ฐํ™˜์‚ฐ ์•ฝ 2466\n", " 2) ์‹œ๊ฐ„ ๋‹จ์ถ•: ์˜ค์ง„๋‹จ ์žฌ์ž‘์—… 3038h -> 2713h (10.7% ๋‹จ์ถ•)\n", " 3) ํ—›๊ต์ฒด ๋ถ€ํ’ˆ ๋น„์šฉ: 1778 -> 1441 (18.9% ๊ฐ์†Œ), ์˜ค์ง„๋‹จ 341๊ฑด -> 309๊ฑด\n", "[๋„์ž… ๋กœ๋“œ๋งต] ๋‹จ๊ธฐ(1~3์›”): ๊ฐ€์ค‘์น˜ ๊ฐฑ์‹  + ์ •๋น„๊ด€ ํ™•์ธ ๋ณ‘ํ–‰ / ์ค‘๊ธฐ(3~12์›”): AAR ๋ถ„๊ธฐ ๋ฐ˜๋ณตยท์‹ ๊ทœ ์ฝ”๋“œ ๋ฐ˜์˜ / ์žฅ๊ธฐ(1๋…„~): ๊ตฐ์ˆ˜ ์ •๋ณด์ฒด๊ณ„ ์ด๋ ฅ ์—ฐ๋™\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 10. ๋ฌด๊ธฐ์ฒด๊ณ„ ๊ณ ์žฅ ์ฝ”๋“œ(DTC) ๊ธฐ๋ฐ˜ ์—ฐ๊ด€ ๊ณ ์žฅ ์˜ˆ์ธก ๋ฐ ์ •๋น„ ๊ฐ€์ด๋“œ ๋„๊ตฌ ์™„์„ฑ\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 3.5์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "๋ฌด๊ธฐ์ฒด๊ณ„ ๊ณ ์žฅ ์ฝ”๋“œ(DTC) ๊ธฐ๋ฐ˜ ์—ฐ๊ด€ ๊ณ ์žฅ ์˜ˆ์ธก ๋ฐ ์ •๋น„ ๊ฐ€์ด๋“œ ๋„๊ตฌ๋ฅผ ์™„์„ฑํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **DTC ์ž…๋ ฅ๋ถ€ํ„ฐ ์—ฐ๊ด€ ๊ณ ์žฅ ์˜ˆ์ธก, ์ •๋น„ ๊ฐ€์ด๋“œ, ์Šน์ธ ๋Œ€์ƒ ์„ ๋ณ„, ์žฌํ•™์Šต๊นŒ์ง€ ํ•œ ํŒŒ์ผ๋กœ ํ†ตํ•ฉํ•˜๊ณ  ์ž์ฒด ์ ๊ฒ€๊ณผ ๋ธŒ๋ฆฌํ•‘ ์š”์•ฝ์„ ์ถœ๋ ฅํ•˜๋Š” ๋„๊ตฌ๋ฅผ ์™„์„ฑํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **์›”๋ณ„ ์ •๋น„ ์‹ค์ ์œผ๋กœ ์ง„๋‹จ ๋ชจ๋ธ์„ ํ•™์Šตํ•˜๊ณ , ์‹ ๊ทœ ์‹ค์ ์œผ๋กœ ๋„์ „์ž ๋ชจ๋ธ์„ ๋งŒ๋“ค์–ด ์ฑ”ํ”ผ์–ธ๊ณผ ๊ฒ€์ฆ ์˜ค์ฐจ๋ฅผ ๋น„๊ตํ•œ ๋’ค ๋น„์Šทํ•˜๊ฑฐ๋‚˜ ๋‚˜์„ ๋•Œ๋งŒ ๊ต์ฒด ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `docs/mnt03_operations_guide.md ๋ฐ pipeline/mnt03_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": [ "\"\"\"DTC ๋กœ๊ทธ ๊ฒ€์ฆยท์ •์ œ, ์ง„๋‹จ ๋ชจ๋ธ ์ž๋™ ์žฌํ•™์Šต(์ฑ”ํ”ผ์–ธยท๋„์ „์ž ๋น„๊ต ํ›„ ๊ต์ฒด), ๊ณ ์žฅ ์ฝ”๋“œ ๊ธฐ๋ฐ˜ ์—ฐ๊ด€ ๊ณ ์žฅ ์ง„๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ์‚ฐ์ถœ, ์‹ ๋ขฐ๋„ ๋‚ฎ์€ ๊ฑด์˜ ์ •๋น„๊ด€ ์Šน์ธ ๋Œ€๊ธฐ ๋ถ„๋ฆฌ, ๋‹ค๋นˆ๋„ ๊ณ ์žฅํ’ˆ๋ชฉ ์›”๊ฐ„ ์†Œ์š” ์˜ˆ์ธก๊นŒ์ง€ ์ •๋น„ ๊ฐ€์ด๋“œ ๋„๊ตฌ๋ฅผ ํ•œ ํŒŒ์ผ๋กœ ํ†ตํ•ฉ ์‹คํ–‰ํ•˜๊ณ , ์ž์ฒด ์ ๊ฒ€ยท์šด์˜ ๋ฌธ์„œ ์™„๋น„ ์ ๊ฒ€ยท์ •๋น„ ๋‹ด๋‹น ์žฅ๊ต ๋ธŒ๋ฆฌํ•‘ ์š”์•ฝ์„ ์ถœ๋ ฅํ•œ๋‹ค.\"\"\"\n", "import time\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from sklearn.linear_model import LogisticRegression, Ridge\n", "from sklearn.preprocessing import StandardScaler\n", "from sklearn.pipeline import make_pipeline\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ƒ ์ฐจ๋Ÿ‰ 100๋Œ€์˜ 18๊ฐœ์›” ๊ณ ์žฅ ์ง„๋‹จ ์ฝ”๋“œ(DTC)ยท์ •๋น„ ์ด๋ ฅ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ๋ถ€๋Œ€ยท์žฅ๋น„ยท์ •๋น„ ์‹ค์  ์—†์Œ)\n", "# ๋ถ€ํ’ˆ 16์ข…(1000๊ฐ€๋™์‹œ๊ฐ„๋‹น ๊ณ ์žฅ๋ฅ  ์ฐจ๋“ฑ), DTC 28์ข…(๋ถ€ํ’ˆ ๊ณ ์œ  ์ฝ”๋“œ, ์ง ๋ถ€ํ’ˆ ๊ณต์œ  ์ฝ”๋“œ, ๊ณ„ํ†ต ์ฆ์ƒ ์ฝ”๋“œ)\n", "# ๊ณ ์žฅ 1๊ฑด(์ •๋น„ ์ด๋ ฅ)๋งˆ๋‹ค DTC๊ฐ€ 1~5๊ฐœ ๋™์‹œ ๋ฐœ์ƒํ•˜๊ณ , ์—ฐ์‡„ ๊ณ ์žฅ ์ฝ”๋“œ์™€ ์ผ์‹œ ์žก์Œ ์ฝ”๋“œ๊ฐ€ ์„ž์ธ๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "PARTS = [ # (ID, ์ด๋ฆ„, ๊ณ„ํ†ต, 1000๊ฐ€๋™์‹œ๊ฐ„๋‹น ๊ณ ์žฅ๋ฅ , ๊ฐ€์ƒ ๋‹จ๊ฐ€์ง€์ˆ˜, ์ˆ˜๋ฆฌ์‹œ๊ฐ„ h)\n", " (\"P01\", \"์—ฐ๋ฃŒ๋ถ„์‚ฌ๊ธฐ\", \"engine\", 12.0, 3.0, 2.0), (\"P02\", \"ํ„ฐ๋ณด์ฐจ์ €\", \"engine\", 9.0, 8.0, 5.0),\n", " (\"P03\", \"์ถ•์ „์ง€\", \"electric\", 6.5, 4.0, 1.5), (\"P04\", \"๋ฐœ์ „๊ธฐ\", \"electric\", 4.0, 9.0, 3.0),\n", " (\"P05\", \"๋ณ€์†๊ธฐ ์ œ์–ด๋ชจ๋“ˆ\", \"drive\", 3.0, 12.0, 6.0), (\"P06\", \"๋ƒ‰๊ฐ์ˆ˜ ํŽŒํ”„\", \"engine\", 2.2, 3.5, 3.0),\n", " (\"P07\", \"์œ ์•• ํŽŒํ”„\", \"hydraulic\", 1.6, 10.0, 4.0), (\"P08\", \"์†๋„ ์„ผ์„œ\", \"drive\", 1.2, 2.0, 1.5),\n", " (\"P09\", \"ECU ์ปค๋„ฅํ„ฐ\", \"electric\", 0.9, 2.5, 1.5), (\"P10\", \"์—ฐ๋ฃŒ ํ•„ํ„ฐ\", \"engine\", 0.7, 0.8, 0.7),\n", " (\"P11\", \"์ œ๋™ ๋ฐธ๋ธŒ\", \"brake\", 0.5, 6.0, 3.0), (\"P12\", \"์กฐํ–ฅ ์•ก์ถ”์—์ดํ„ฐ\", \"drive\", 0.4, 9.0, 5.0),\n", " (\"P13\", \"๊ณต๊ธฐ ํ•„ํ„ฐ\", \"engine\", 0.3, 0.5, 0.5), (\"P14\", \"ํ†ต์‹  ์ธํ„ฐํŽ˜์ด์Šค ๋ชจ๋“ˆ\", \"electric\", 0.25, 3.0, 1.5),\n", " (\"P15\", \"์œคํ™œ์œ  ์••๋ ฅ ์„ผ์„œ\", \"engine\", 0.2, 1.0, 1.0), (\"P16\", \"ํŒฌ ๊ตฌ๋™ ๋ชจํ„ฐ\", \"cooling\", 0.15, 2.0, 2.0)]\n", "PID = [p[0] for p in PARTS]\n", "PNAME = {p[0]: p[1] for p in PARTS}; PSYS = {p[0]: p[2] for p in PARTS}; RATE = {p[0]: p[3] for p in PARTS}\n", "COST = {p[0]: p[4] for p in PARTS}; REPH = {p[0]: p[5] for p in PARTS}\n", "PAIRS = [(\"P01\", \"P02\"), (\"P03\", \"P04\"), (\"P05\", \"P08\"), (\"P06\", \"P10\"), (\"P09\", \"P14\"), (\"P13\", \"P15\")]\n", "SHARED = {p: f\"S{k + 1:02d}\" for k, pr in enumerate(PAIRS) for p in pr} # ์ง ๋ถ€ํ’ˆ์ด ํ•จ๊ป˜ ์“ฐ๋Š” ๋ชจํ˜ธํ•œ ์ฝ”๋“œ\n", "SPEC = {p: f\"C{p[1:]}1\" for p in PID} # ๋ถ€ํ’ˆ ๊ณ ์œ  ์ฝ”๋“œ\n", "SYS = [\"engine\", \"electric\", \"drive\", \"hydraulic\", \"brake\", \"cooling\"]\n", "SYMP = {s: f\"G{i + 1:02d}\" for i, s in enumerate(SYS)} # ๊ณ„ํ†ต ์ฆ์ƒ ์ฝ”๋“œ\n", "CODES = sorted(set(SPEC.values()) | set(SHARED.values()) | set(SYMP.values()))\n", "CASCADE = {\"P01\": [(\"P10\", 0.20)], \"P02\": [(\"P06\", 0.20)], \"P03\": [(\"P04\", 0.25)], \"P04\": [(\"P09\", 0.25)],\n", " \"P05\": [(\"P08\", 0.30)], \"P06\": [(\"P16\", 0.30)], \"P09\": [(\"P14\", 0.20)], \"P12\": [(\"P07\", 0.20)]}\n", "CRIT_SYS = {\"engine\", \"drive\", \"brake\"} # ๊ธฐ๋™ยท์ œ๋™ ๋ถˆ๋Šฅ ์œ„ํ—˜ ๊ณ„ํ†ต(๊ฒฝ๋ณด ๋Œ€์ƒ)\n", "MONTHS = 18\n", "\n", "\n", "def make_fleet(n=100, seed=0):\n", " rng = np.random.default_rng(seed)\n", " f = pd.DataFrame({\"veh\": [f\"V{i:03d}\" for i in range(n)], \"age\": rng.integers(1, 26, n).astype(float),\n", " \"terrain\": rng.uniform(0, 1, n), \"load\": rng.uniform(0, 1, n), \"pm_gap\": rng.uniform(20, 200, n),\n", " \"humid\": rng.uniform(0, 1, n), \"base_h\": rng.uniform(55, 125, n)})\n", " f[\"mult\"] = np.exp(0.045 * (f[\"age\"] - 13) + 0.9 * (f[\"terrain\"] - .5) + 0.8 * (f[\"load\"] - .5)\n", " + 0.006 * (f[\"pm_gap\"] - 110) + 0.7 * (f[\"humid\"] - .5))\n", " return f\n", "\n", "\n", "def make_data(seed=1, months=MONTHS):\n", " \"\"\"๋ฐ˜ํ™˜: fleet(์ฐจ๋Ÿ‰ ์†์„ฑ), ev(์ •๋น„ ์ด๋ ฅ 1๊ฑด=1ํ–‰: ๋ฐœ์ƒ DTC ๋ชฉ๋ก, ์‹ค์ œ ๊ณ ์žฅ ๋ถ€ํ’ˆ), log(DTC ๋ฐœ์ƒ ๋กœ๊ทธ, ํ–‰=์ฝ”๋“œ 1๊ฑด)\"\"\"\n", " rng = np.random.default_rng(seed)\n", " fleet = make_fleet()\n", " V = len(fleet)\n", " temp = 12 + 14 * np.sin(2 * np.pi * (np.arange(months) - 3) / 12) # ์›”ํ‰๊ท  ๊ธฐ์˜จ(๊ฐ€์ƒ)\n", " hours = fleet[\"base_h\"].values[:, None] * (1 + 0.25 * np.sin(2 * np.pi * np.arange(months) / 12 + 1)) * rng.lognormal(0, .12, (V, months))\n", " fleet[\"hours\"] = hours.sum(axis=1) # ์ฐจ๋Ÿ‰๋ณ„ 18๊ฐœ์›” ์ด ๊ฐ€๋™์‹œ๊ฐ„\n", " fleet.attrs[\"month_h\"] = hours.sum(axis=0) # ์›”๋ณ„ ์ „์ฒด ๊ฐ€๋™์‹œ๊ฐ„(๊ณ ์žฅ๋ฅ  ๋ถ„๋ชจ)\n", " evs = []\n", " for p in PID:\n", " s = np.ones(months)\n", " if p in (\"P03\", \"P04\"): s = np.exp(-0.06 * (temp - 12)) # ํ•œ๋žญ๊ธฐ ์ „๊ธฐ๊ณ„ํ†ต ์ฆ๊ฐ€\n", " if p in (\"P06\", \"P16\"): s = np.exp(0.05 * (temp - 12)) # ํ˜น์„œ๊ธฐ ๋ƒ‰๊ฐ๊ณ„ํ†ต ์ฆ๊ฐ€\n", " cnt = rng.poisson(RATE[p] * hours / 1000 * fleet[\"mult\"].values[:, None] * s[None, :])\n", " for v, m in zip(*np.nonzero(cnt)):\n", " for _ in range(cnt[v, m]):\n", " evs.append((fleet[\"veh\"][v], int(m), m * 720 + rng.uniform(0, 720), p, float(temp[m] + rng.normal(0, 3))))\n", " ev = pd.DataFrame(evs, columns=[\"veh\", \"month\", \"t\", \"part\", \"temp\"]).sort_values(\"t\").reset_index(drop=True)\n", " ev[\"ev_id\"] = [f\"W{i:05d}\" for i in range(len(ev))]\n", " rows, codes = [], []\n", " for e in ev.itertuples():\n", " c, r = [], rng.random(3)\n", " if r[0] < 0.6: c.append(SPEC[e.part])\n", " if e.part in SHARED and r[1] < 0.6: c.append(SHARED[e.part])\n", " if r[2] < 0.5: c.append(SYMP[PSYS[e.part]])\n", " if not c: c.append(SHARED.get(e.part, SPEC[e.part]))\n", " for q, pr in CASCADE.get(e.part, []):\n", " if rng.random() < pr: c.append(SPEC[q])\n", " if rng.random() < 0.12: c.append(SPEC[PID[rng.integers(0, 16)]])\n", " c = list(dict.fromkeys(c))\n", " rng.shuffle(c) # ๋™์‹œ ๋ฐœ์ƒ ์ฝ”๋“œ์˜ ๊ธฐ๋ก ์ˆœ์„œ๋Š” ์ž„์˜\n", " codes.append(c)\n", " for k, cd in enumerate(c):\n", " rows.append((e.ev_id, e.veh, e.t + k * 0.1, cd))\n", " ev[\"codes\"] = codes\n", " log = pd.DataFrame(rows, columns=[\"ev_id\", \"veh\", \"ts\", \"code\"])\n", " return fleet, ev, log\n", "\n", "# ---------------------------------------------------------------------------\n", "# ๊ณตํ†ต ๋ถ„์„ ์กฐ๊ฐ: ์ฝ”๋“œ ๋ฉ€ํ‹ฐํ•ซ ํ–‰๋ ฌ, ์ฐจ๋Ÿ‰ ๋งฅ๋ฝ ํ”ผ์ฒ˜, ํ•™์Šต/๊ฒ€์ฆ ๋ถ„ํ• (์•ž 12๊ฐœ์›” ํ•™์Šต, ๋’ค 6๊ฐœ์›” ๊ฒ€์ฆ)\n", "# ---------------------------------------------------------------------------\n", "CIDX = {c: i for i, c in enumerate(CODES)}\n", "SPLIT_M = 12\n", "\n", "\n", "def multi_hot(ev):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 10] ๋ฌด๊ธฐ์ฒด๊ณ„ ๊ณ ์žฅ ์ฝ”๋“œ(DTC) ๊ธฐ๋ฐ˜ ์—ฐ๊ด€ ๊ณ ์žฅ ์˜ˆ์ธก ๋ฐ ์ •๋น„ ๊ฐ€์ด๋“œ ๋„๊ตฌ ์™„์„ฑ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def context(ev, fleet):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 10] ๋ฌด๊ธฐ์ฒด๊ณ„ ๊ณ ์žฅ ์ฝ”๋“œ(DTC) ๊ธฐ๋ฐ˜ ์—ฐ๊ด€ ๊ณ ์žฅ ์˜ˆ์ธก ๋ฐ ์ •๋น„ ๊ฐ€์ด๋“œ ๋„๊ตฌ ์™„์„ฑ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def feats(ev, fleet):\n", " X = multi_hot(ev)\n", " n = X.sum(axis=1, keepdims=True) / 3\n", " return np.hstack([X, n, context(ev, fleet)])\n", "\n", "\n", "def monthly_counts(ev):\n", " return ev.groupby([\"month\", \"part\"]).size().unstack(fill_value=0).reindex(columns=PID, fill_value=0)\n", "\n", "\n", "def build_monthly(ev, fleet, top_n=8):\n", " \"\"\"๋ถ€ํ’ˆ๋ณ„ ์›”๊ฐ„ ๊ณ ์žฅ ๊ฑด์ˆ˜ ์˜ˆ์ธก์šฉ ํ‘œ: ์ง€์—ฐ๊ฐ’(lag), ์ด๋™ํ‰๊ท , ์ถ”์„ธ, ๊ธฐ๋Œ€ ๊ฑด์ˆ˜(๊ฐ€๋™์‹œ๊ฐ„ x ์„ค๊ณ„ ๊ณ ์žฅ๋ฅ ), ๊ธฐ์˜จ ์ƒํ˜ธ์ž‘์šฉ, ๊ณ„์ ˆ ์„ฑ๋ถ„\"\"\"\n", " mc = ev.groupby([\"month\", \"part\"]).size().unstack(fill_value=0)\n", " top = ev[\"part\"].value_counts().index[:top_n]\n", " mh, tm = fleet.attrs[\"month_h\"], ev.groupby(\"month\")[\"temp\"].mean().values\n", " rows = []\n", " for p in top:\n", " c = mc[p].values.astype(float)\n", " for m in range(3, MONTHS):\n", " L = np.log1p(c)\n", " rows.append({\"part\": p, \"month\": m, \"y\": c[m], \"lag1\": L[m - 1], \"lag2\": L[m - 2], \"lag3\": L[m - 3], \"roll3\": L[m - 3:m].mean(),\n", " \"trend\": L[m - 1] - L[m - 2], \"roll3_std\": L[m - 3:m].std(), \"log_expected\": np.log(mh[m] / 1000 * RATE[p]),\n", " \"rate_lag1\": c[m - 1] / (mh[m - 1] / 1000) / RATE[p], \"temp\": tm[m] / 10, \"cold_elec\": max(0, 12 - tm[m]) / 10 * (p in (\"P03\", \"P04\")),\n", " \"heat_cool\": max(0, tm[m] - 12) / 10 * (p in (\"P06\", \"P16\")), \"sin\": np.sin(2 * np.pi * m / 12), \"cos\": np.cos(2 * np.pi * m / 12)})\n", " D = pd.DataFrame(rows)\n", " return D, [c for c in D.columns if c not in (\"part\", \"month\", \"y\")], top\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๋ถ€ํ’ˆ๋ณ„ ์ •๋น„ ์กฐ์น˜ ๊ฐ€์ด๋“œ๋ผ์ธ(์š”์•ฝ 3๋‹จ๊ณ„). ์‹ค์ œ ์ •๋น„ ๊ต๋ฒ”์ด ์•„๋‹ˆ๋ผ ๊ต์œก์šฉ์œผ๋กœ ๋งŒ๋“  ์˜ˆ์‹œ ๋ฌธ๊ตฌ๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "GUIDE = {\n", " \"P01\": \"์—ฐ๋ฃŒ ์••๋ ฅ ์ธก์ • -> ๋ถ„์‚ฌ๊ธฐ ๋ˆ„์„คยท๋ง‰ํž˜ ํ™•์ธ -> ๋ถˆ๋Ÿ‰ ์‹œ ๋ถ„์‚ฌ๊ธฐ ๊ต์ฒด\", \"P02\": \"ํก๊ธฐ ์••๋ ฅยท์ด๋ฌผ ์ ๊ฒ€ -> ์ถ• ์œ ๊ฒฉ ํ™•์ธ -> ๋ถˆ๋Ÿ‰ ์‹œ ํ„ฐ๋ณด์ฐจ์ € ๊ต์ฒด\",\n", " \"P03\": \"๋‹จ์ž ์ „์••ยท๋ถ€์‹ ์ ๊ฒ€ -> ์ถฉ์ „ ๊ณ„ํ†ต ํ™•์ธ -> ๋ถˆ๋Ÿ‰ ์‹œ ์ถ•์ „์ง€ ๊ต์ฒด\", \"P04\": \"๋ฐœ์ „ ์ „์•• ์ธก์ • -> ๋ฒจํŠธยท๋ฐฐ์„  ์ ๊ฒ€ -> ๋ถˆ๋Ÿ‰ ์‹œ ๋ฐœ์ „๊ธฐ ๊ต์ฒด\",\n", " \"P05\": \"์ œ์–ด๋ชจ๋“ˆ ์†Œํ”„ํŠธ์›จ์–ดยท์ปค๋„ฅํ„ฐ ์ ๊ฒ€ -> ๋ณ€์† ์ด๋ ฅ ํ™•์ธ -> ๋ถˆ๋Ÿ‰ ์‹œ ๋ชจ๋“ˆ ๊ต์ฒด\", \"P06\": \"๋ƒ‰๊ฐ์ˆ˜๋Ÿ‰ยท๋ˆ„์ˆ˜ ์ ๊ฒ€ -> ํŽŒํ”„ ์ž‘๋™์Œ ํ™•์ธ -> ๋ถˆ๋Ÿ‰ ์‹œ ํŽŒํ”„ ๊ต์ฒด\",\n", " \"P07\": \"์œ ์•• ์˜ค์ผ๋Ÿ‰ยท์••๋ ฅ ์ ๊ฒ€ -> ํŽŒํ”„ ๋ˆ„์„ค ํ™•์ธ -> ๋ถˆ๋Ÿ‰ ์‹œ ํŽŒํ”„ ๊ต์ฒด\", \"P08\": \"์„ผ์„œ ๋ฐฐ์„ ยท๊ฐ„๊ทน ์ ๊ฒ€ -> ์‹ ํ˜ธ ํŒŒํ˜• ํ™•์ธ -> ๋ถˆ๋Ÿ‰ ์‹œ ์„ผ์„œ ๊ต์ฒด\",\n", " \"P09\": \"์ปค๋„ฅํ„ฐ ์ ‘์ด‰ยท๋ถ€์‹ ์ ๊ฒ€ -> ํ•€ ์ƒํƒœ ํ™•์ธ -> ๋ถˆ๋Ÿ‰ ์‹œ ์ปค๋„ฅํ„ฐ ๊ต์ฒด\", \"P10\": \"ํ•„ํ„ฐ ์˜ค์—ผ๋„ ์ ๊ฒ€ -> ์—ฐ๋ฃŒ ํ๋ฆ„ ํ™•์ธ -> ๋ถˆ๋Ÿ‰ ์‹œ ํ•„ํ„ฐ ๊ต์ฒด\",\n", " \"P11\": \"์ œ๋™ ์••๋ ฅ ์ ๊ฒ€ -> ๋ฐธ๋ธŒ ์ž‘๋™ ํ™•์ธ -> ๋ถˆ๋Ÿ‰ ์‹œ ๋ฐธ๋ธŒ ๊ต์ฒด(์•ˆ์ „ ํ™•์ธ ํ•„์ˆ˜)\", \"P12\": \"์กฐํ–ฅ ๋ฐ˜์‘ ์ ๊ฒ€ -> ์•ก์ถ”์—์ดํ„ฐ ์ž‘๋™ ํ™•์ธ -> ๋ถˆ๋Ÿ‰ ์‹œ ๊ต์ฒด(์•ˆ์ „ ํ™•์ธ ํ•„์ˆ˜)\",\n", " \"P13\": \"ํ•„ํ„ฐ ์ฐจ์•• ์ ๊ฒ€ -> ์˜ค์—ผ ํ™•์ธ -> ๋ถˆ๋Ÿ‰ ์‹œ ํ•„ํ„ฐ ๊ต์ฒด\", \"P14\": \"ํ†ต์‹  ๋งํฌ ์ƒํƒœ ์ ๊ฒ€ -> ๋ชจ๋“ˆ ์ „์› ํ™•์ธ -> ๋ถˆ๋Ÿ‰ ์‹œ ๋ชจ๋“ˆ ๊ต์ฒด\",\n", " \"P15\": \"์••๋ ฅ ์„ผ์„œ ์‹ ํ˜ธ ์ ๊ฒ€ -> ์œคํ™œ์œ ๋Ÿ‰ ํ™•์ธ -> ๋ถˆ๋Ÿ‰ ์‹œ ์„ผ์„œ ๊ต์ฒด\", \"P16\": \"ํŒฌ ๋ชจํ„ฐ ์ „๋ฅ˜ ์ ๊ฒ€ -> ํšŒ์ „ ํ™•์ธ -> ๋ถˆ๋Ÿ‰ ์‹œ ๋ชจํ„ฐ ๊ต์ฒด\"}\n", "\n", "CONFIG = {\"conf_review\": 0.8, \"retrain_tolerance\": 0.01, \"mape_target\": 0.25}\n", "OPS_GUIDE = { # docs/mnt03_operations_guide.md ์š”์•ฝ\n", " \"1. ๋ชฉ์ ๊ณผ ๋ฒ”์œ„\": \"DTC ๊ธฐ๋ฐ˜ ๊ณ ์žฅ ๋ถ€ํ’ˆ ์ง„๋‹จ ๋ณด์กฐ์™€ ๋‹ค๋นˆ๋„ ๊ณ ์žฅํ’ˆ๋ชฉ ์‹๋ณ„. ์ตœ์ข… ์ •๋น„ ํŒ๋‹จ์€ ์ˆ™๋ จ ์ •๋น„๊ด€.\",\n", " \"2. ์ž…๋ ฅ ๋ฐ์ดํ„ฐ\": \"DTC ๋กœ๊ทธ(์ฐจ๋Ÿ‰ ID, ์‹œ๊ฐ, ์ฝ”๋“œ), ์ •๋น„ ์ด๋ ฅ(์ •๋น„ ๊ฑด ID, ๊ณ ์žฅ ๋ถ€ํ’ˆ), ์ฐจ๋Ÿ‰ ์†์„ฑ. ์‹ค์ œ ๋ถ€๋Œ€ ์ž๋ฃŒ ์™ธ๋ถ€ AI ์ž…๋ ฅ ๊ธˆ์ง€.\",\n", " \"3. ์ผ์ผ ์šด์šฉ ์ ˆ์ฐจ\": \"๋กœ๊ทธ ๊ฒ€์ฆ -> ์ •๋น„ ๊ฑด ๋ฌถ์Œ -> ์ง„๋‹จยท์‹ ๋ขฐ๋„ ์‚ฐ์ถœ -> ๋‚ฎ์€ ์‹ ๋ขฐ๋„ ๊ฑด ์Šน์ธ ๋Œ€๊ธฐ -> ์กฐ์น˜ ํ›„ ์ •๋น„ ์ด๋ ฅ ์ž…๋ ฅ.\",\n", " \"4. ์Šน์ธ(HITL) ๊ธฐ์ค€\": \"์‹ ๋ขฐ๋„ 0.8 ๋ฏธ๋งŒยท๋‹ค๊ณ„ํ†ต ๋™์‹œ ๋ฐœ์ƒ์€ ์ˆ™๋ จ ์ •๋น„๊ด€ ๊ฒฐ์ • ์ „ ํ™•์ • ๊ธˆ์ง€, ๋ฌด์‘๋‹ต ์‹œ ์žฌํ™•์ธ ํ‘œ์‹œ, ๊ฒฐ์ • ์‚ฌ์œ  ๊ธฐ๋ก.\",\n", " \"5. ์žฌํ•™์Šต ์ ˆ์ฐจ\": \"์›” 1ํšŒ ์‹ ๊ทœ ์ •๋น„ ์ด๋ ฅ์œผ๋กœ ๋„์ „์ž ํ•™์Šต, ๊ฒ€์ฆ ์ •ํ™•๋„๊ฐ€ ๋‚˜๋น ์ง€์ง€ ์•Š์„ ๋•Œ๋งŒ ๊ต์ฒด.\",\n", " \"6. ์žฅ์•  ๋Œ€์‘\": \"๋ชจ๋ธ ์˜ค๋ฅ˜ ์‹œ ๋‹จ์ผ ์ฝ”๋“œ ๋งคํ•‘ํ‘œ๋กœ ๋Œ€์ฒด, ์†์ƒ ๋กœ๊ทธ๋Š” ์ˆ˜๋™ ํ™•์ธ ๋ชฉ๋ก์œผ๋กœ ๋ถ„๋ฆฌ.\",\n", " \"7. ์—ฐ๋™ ์ธํ„ฐํŽ˜์ด์Šค\": \"๊ตญ๋ฐฉ ๊ตฐ์ˆ˜ ์ •๋ณด์ฒด๊ณ„์™€ DTCยท์ •๋น„ ์ด๋ ฅ ํ‘œ์ค€ ํ•„๋“œ(์žฅ๋น„์ฝ”๋“œยท๋ถ„๋ฅ˜๊ตฐยท์‹œ๊ฐ)๋ฅผ CSV/API๋กœ ๊ตํ™˜ํ•˜๋Š” ์•ˆ์„ ์ฐจ๊ธฐ ๊ณผ์ œ๋กœ ๋‘”๋‹ค.\",\n", "}\n", "\n", "fleet, ev, log = make_data()\n", "OLD, NEW, OPS = ev[ev[\"month\"] < 12], ev[(ev[\"month\"] >= 12) & (ev[\"month\"] < 15)], ev[ev[\"month\"] >= 15].reset_index(drop=True)\n", "t_start = time.perf_counter()\n", "\n", "\n", "def fit(d):\n", " return LogisticRegression(C=3, max_iter=500).fit(feats(d, fleet), d[\"part\"])\n", "\n", "\n", "def retrain(champion, old, new):\n", " \"\"\"๋„์ „์ž๋ฅผ (๊ธฐ์กด + ์‹ ๊ทœ ์•ž 2/3 ๊ธฐ๊ฐ„)์œผ๋กœ ํ•™์Šตํ•˜๊ณ , ์‹ ๊ทœ ๋’ค 1/3 ๊ธฐ๊ฐ„์—์„œ ์ฑ”ํ”ผ์–ธ๊ณผ ์ •ํ™•๋„๋ฅผ ๋น„๊ตํ•ด ๋น„์Šทํ•˜๊ฑฐ๋‚˜ ๋‚ซ๋‹ค๋ฉด ๊ต์ฒด.\"\"\"\n", " cut = new[\"month\"].min() + 2\n", " hold = new[new[\"month\"] >= cut]\n", " challenger = fit(pd.concat([old, new[new[\"month\"] < cut]], ignore_index=True))\n", " a_old = (champion.predict(feats(hold, fleet)) == hold[\"part\"].values).mean()\n", " a_new = (challenger.predict(feats(hold, fleet)) == hold[\"part\"].values).mean()\n", " swap = a_new >= a_old - CONFIG[\"retrain_tolerance\"]\n", " return (challenger if swap else champion), {\"์ฑ”ํ”ผ์–ธ\": round(float(a_old), 3), \"๋„์ „์ž\": round(float(a_new), 3), \"๊ต์ฒด\": bool(swap)}\n", "\n", "\n", "def validate(df):\n", " ok = df[\"codes\"].map(lambda cs: len(cs) > 0 and all(c in CODES for c in cs)) & df[\"veh\"].isin(fleet[\"veh\"])\n", " return df[ok].copy(), df[~ok].copy()\n", "\n", "\n", "def predict_safe(model, d):\n", " \"\"\"๋ชจ๋ธ ์˜ค๋ฅ˜ ์‹œ ๋‹จ์ผ ์ฝ”๋“œ -> ์ตœ๋‹ค ๋ถ€ํ’ˆ ๋งคํ•‘ํ‘œ๋กœ ๋Œ€์ฒดํ•œ๋‹ค.\"\"\"\n", " try:\n", " P = model.predict_proba(feats(d, fleet))\n", " return model.classes_[P.argmax(axis=1)], P.max(axis=1), \"๋ชจ๋ธ\"\n", " except Exception:\n", " mp = OLD.assign(first=OLD[\"codes\"].str[0]).groupby(\"first\")[\"part\"].agg(lambda s: s.value_counts().index[0])\n", " return d[\"codes\"].str[0].map(mp).fillna(\"P01\").values, np.full(len(d), 0.5), \"๋งคํ•‘ํ‘œ ๋Œ€์ฒด\"\n", "\n", "\n", "# ---------------- 1. ํ†ตํ•ฉ ์‹คํ–‰ ----------------\n", "champion = fit(OLD)\n", "model, rt = retrain(champion, OLD, NEW)\n", "print(f\"[๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ์žฌํ•™์Šต: {rt} -> {'๋„์ „์ž๋กœ ๊ต์ฒด' if rt['๊ต์ฒด'] else '์ฑ”ํ”ผ์–ธ ์œ ์ง€'}\")\n", "ops_all = OPS.copy()\n", "ops_all.at[3, \"codes\"] = [\"ZZ9\"]; ops_all.at[40, \"codes\"] = [] # ์†์ƒ ๋กœ๊ทธ 2๊ฑด ์ฃผ์ž…\n", "ops, manual = validate(ops_all)\n", "ops = ops.reset_index(drop=True)\n", "pred, conf, src = predict_safe(model, ops)\n", "queue = ops[conf < CONFIG[\"conf_review\"]]\n", "alerts = [(i, p) for i, p in enumerate(pred) if PSYS[p] in CRIT_SYS and conf[i] >= CONFIG[\"conf_review\"]]\n", "acc = (pred == ops[\"part\"].values).mean()\n", "hi = conf >= CONFIG[\"conf_review\"]\n", "print(f\"์šด์šฉ ์ •๋น„ ๊ฑด {len(ops_all)}๊ฑด: ์ง„๋‹จ {len(ops)}๊ฑด, ์ˆ˜๋™ ํ™•์ธ {len(manual)}๊ฑด, ์ถœ์ฒ˜ '{src}', ์ •ํ™•๋„ {acc:.1%} (์‹ ๋ขฐ๋„ 0.8 ์ด์ƒ {hi.sum()}๊ฑด์€ {(pred[hi] == ops['part'].values[hi]).mean():.1%})\")\n", "\n", "# ๋‹ค๋นˆ๋„ ํ’ˆ๋ชฉ ์›”๊ฐ„ ์†Œ์š” ์˜ˆ์ธก(๋งˆ์ง€๋ง‰ ๋‹ฌ): ๊ณผ๊ฑฐ ์›”๋กœ ํ•™์Šตํ•œ ๋ฆฟ์ง€๋กœ ๋‹ค์Œ ๋‹ฌ ๊ฑด์ˆ˜ ์˜ˆ์ธก\n", "D, FE, top = build_monthly(ev, fleet)\n", "a, b = D[D[\"month\"] < MONTHS - 1], D[D[\"month\"] == MONTHS - 1]\n", "fc = np.exp(make_pipeline(StandardScaler(), Ridge(alpha=3.0)).fit(a[FE], np.log(a[\"y\"])).predict(b[FE]))\n", "fmape = float(np.mean(np.abs(b[\"y\"].values - fc) / b[\"y\"].values))\n", "print(\"๋‹ค๋นˆ๋„ ํ’ˆ๋ชฉ ๋‹ค์Œ ๋‹ฌ ์†Œ์š” ์˜ˆ์ธก: \" + \", \".join(f\"{PNAME[p]} {f:.0f}๊ฑด(์‹ค์ œ {y:.0f})\" for p, f, y in zip(b[\"part\"].values[:3], fc[:3], b[\"y\"].values[:3])) + f\" / 8๊ฐœ ํ’ˆ๋ชฉ MAPE {fmape:.1%}\")\n", "sec = time.perf_counter() - t_start\n", "\n", "# ---------------- 2. ์ž์ฒด ์ ๊ฒ€ ----------------\n", "_, _, bad_src = predict_safe(None, ops.head(3))\n", "checks = {\n", " \"๋กœ๊ทธ ์œ ์‹ค 0๊ฑด\": len(ops) + len(manual) == len(ops_all),\n", " \"์†์ƒ ๋กœ๊ทธ ์ˆ˜๋™ ํ™•์ธ ๋ถ„๋ฆฌ\": len(manual) == 2,\n", " \"์žฌํ•™์Šต ํŒŒ์ดํ”„๋ผ์ธ ์‹คํ–‰\": \"๊ต์ฒด\" in rt,\n", " \"์‹ ๋ขฐ๋„ 0~1 ๋ฒ”์œ„\": bool(((conf >= 0) & (conf <= 1)).all()),\n", " \"๋ชจ๋ธ ์žฅ์•  ์‹œ ๋งคํ•‘ํ‘œ ๋Œ€์ฒด\": bad_src == \"๋งคํ•‘ํ‘œ ๋Œ€์ฒด\",\n", " \"๋ชจ๋“  ์ง„๋‹จ์— ์กฐ์น˜ ๊ฐ€์ด๋“œ ์—ฐ๊ฒฐ\": all(p in GUIDE for p in pred),\n", " \"์ €์‹ ๋ขฐ ๊ฑด ์ž๋™ ํ™•์ • ๊ธˆ์ง€(๋Œ€๊ธฐ)\": len(queue) == int((conf < CONFIG[\"conf_review\"]).sum()),\n", " f\"์†Œ์š” ์˜ˆ์ธก MAPE {CONFIG['mape_target']:.0%} ์ดํ•˜\": fmape <= CONFIG[\"mape_target\"],\n", "}\n", "print(f\"\\n์ž์ฒด ์ ๊ฒ€ {sum(checks.values())}/{len(checks)} ํ†ต๊ณผ, ์ž๋™ ์žฌํ•™์Šต ํŒŒ์ดํ”„๋ผ์ธ {'์ •์ƒ ์‹คํ–‰' if checks['์žฌํ•™์Šต ํŒŒ์ดํ”„๋ผ์ธ ์‹คํ–‰'] else '์‹คํŒจ'}, ์ „์ฒด ์‹คํ–‰ {sec:.1f}์ดˆ\")\n", "for k, v in checks.items():\n", " if not v:\n", " print(\" ์‹คํŒจ:\", k)\n", "doc = sum(bool(v.strip()) for v in OPS_GUIDE.values()) / len(OPS_GUIDE)\n", "print(f\"์šด์˜ ๋ฌธ์„œ {len(OPS_GUIDE)}๊ฐœ ์žฅ ์ž‘์„ฑ๋ฅ  {doc:.0%} (๋ชฉํ‘œ 100%: {'๋‹ฌ์„ฑ' if doc == 1 else '๋ฏธ๋‹ฌ'}) - \" + \", \".join(k.split(\". \")[1] for k in OPS_GUIDE))\n", "\n", "# ---------------- 3. ํ™”๋ฉด๊ณผ ๋ธŒ๋ฆฌํ•‘ ----------------\n", "fig, ax = plt.subplots(1, 2, figsize=(11, 4))\n", "cnt = pd.Series(pred).value_counts().head(8)\n", "ax[0].bar(cnt.index, cnt.values); ax[0].set_title(\"Diagnosed parts (operating period)\")\n", "ax[1].hist(conf, bins=20); ax[1].axvline(CONFIG[\"conf_review\"], ls=\"--\", color=\"k\"); ax[1].set_title(\"Confidence (line = review threshold)\")\n", "plt.tight_layout(); plt.show()\n", "\n", "fr = (ev[\"part\"].value_counts() / fleet[\"hours\"].sum() * 1000)\n", "cum = ev[\"part\"].value_counts(normalize=True).cumsum()\n", "a_cnt = int((cum <= 0.8).sum()) + 1\n", "print(\"\\n[์ •๋น„ ๋‹ด๋‹น ์žฅ๊ต ๋ธŒ๋ฆฌํ•‘ ์š”์•ฝ] [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ]\")\n", "print(f\"1. 18๊ฐœ์›” ์ •๋น„ {len(ev)}๊ฑด: ๊ณ ์žฅ๋ฅ  ์ƒ์œ„ \" + \", \".join(f\"{PNAME[p]} {v:.1f}๊ฑด/1000h\" for p, v in fr.sort_values(ascending=False).head(3).items()))\n", "print(f\"2. ์ƒ์œ„ {a_cnt}๊ฐœ ํ’ˆ๋ชฉ์ด ์ „์ฒด ๊ณ ์žฅ์˜ 80% ์ด์ƒ: ์˜ˆ๋น„ ์ˆ˜๋ฆฌ๋ถ€์† ์šฐ์„  ํ™•๋ณด ๋Œ€์ƒ\")\n", "print(f\"3. ์šด์šฉ ๊ธฐ๊ฐ„ ์ง„๋‹จ {len(ops)}๊ฑด ์ค‘ ์‹ ๋ขฐ๋„ 0.8 ์ด์ƒ {int(hi.sum())}๊ฑด(์ •ํ™•๋„ {(pred[hi] == ops['part'].values[hi]).mean():.1%}), ์ •๋น„๊ด€ ์Šน์ธ ๋Œ€๊ธฐ {len(queue)}๊ฑด(์ž๋™ ํ™•์ • 0๊ฑด)\")\n", "print(f\"4. ๊ธฐ๋™ยท์ œ๋™ ๊ณ„ํ†ต ๊ฒฝ๋ณด {len(alerts)}๊ฑด, ์†์ƒ ๋กœ๊ทธ {len(manual)}๊ฑด ์ˆ˜๋™ ํ™•์ธ\")\n", "print(f\"5. ์ง„๋‹จ ๋ชจ๋ธ: {'์‹ ๊ทœ ์ •๋น„ ์ด๋ ฅ์œผ๋กœ ๊ต์ฒด' if rt['๊ต์ฒด'] else '๊ธฐ์กด ์œ ์ง€'}, ์†Œ์š” ์˜ˆ์ธก MAPE {fmape:.1%}\")\n", "print(\"6. ๊ฑด์˜: ๋‹ค๋นˆ๋„ ํ’ˆ๋ชฉ ์˜ˆ๋ฐฉ์ •๋น„ ์ฃผ๊ธฐ ์žฌ๊ฒ€ํ† , ๊ตญ๋ฐฉ ๊ตฐ์ˆ˜ ์ •๋ณด์ฒด๊ณ„์™€ DTCยท์ •๋น„ ์ด๋ ฅ ํ‘œ์ค€ ํ•„๋“œ ์—ฐ๋™ ์ธํ„ฐํŽ˜์ด์Šค ์ •์˜\")\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 10 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ์žฌํ•™์Šต: {'์ฑ”ํ”ผ์–ธ': 0.845, '๋„์ „์ž': 0.847, '๊ต์ฒด': True} -> ๋„์ „์ž๋กœ ๊ต์ฒด\n", "์šด์šฉ ์ •๋น„ ๊ฑด 1554๊ฑด: ์ง„๋‹จ 1552๊ฑด, ์ˆ˜๋™ ํ™•์ธ 2๊ฑด, ์ถœ์ฒ˜ '๋ชจ๋ธ', ์ •ํ™•๋„ 83.2% (์‹ ๋ขฐ๋„ 0.8 ์ด์ƒ 931๊ฑด์€ 99.2%)\n", "๋‹ค๋นˆ๋„ ํ’ˆ๋ชฉ ๋‹ค์Œ ๋‹ฌ ์†Œ์š” ์˜ˆ์ธก: ์—ฐ๋ฃŒ๋ถ„์‚ฌ๊ธฐ 133๊ฑด(์‹ค์ œ 147), ํ„ฐ๋ณด์ฐจ์ € 97๊ฑด(์‹ค์ œ 109), ์ถ•์ „์ง€ 44๊ฑด(์‹ค์ œ 35) / 8๊ฐœ ํ’ˆ๋ชฉ MAPE 22.7%\n", "\n", "์ž์ฒด ์ ๊ฒ€ 8/8 ํ†ต๊ณผ, ์ž๋™ ์žฌํ•™์Šต ํŒŒ์ดํ”„๋ผ์ธ ์ •์ƒ ์‹คํ–‰, ์ „์ฒด ์‹คํ–‰ 18.1์ดˆ\n", "์šด์˜ ๋ฌธ์„œ 7๊ฐœ ์žฅ ์ž‘์„ฑ๋ฅ  100% (๋ชฉํ‘œ 100%: ๋‹ฌ์„ฑ) - ๋ชฉ์ ๊ณผ ๋ฒ”์œ„, ์ž…๋ ฅ ๋ฐ์ดํ„ฐ, ์ผ์ผ ์šด์šฉ ์ ˆ์ฐจ, ์Šน์ธ(HITL) ๊ธฐ์ค€, ์žฌํ•™์Šต ์ ˆ์ฐจ, ์žฅ์•  ๋Œ€์‘, ์—ฐ๋™ ์ธํ„ฐํŽ˜์ด์Šค\n", "\n", "[์ •๋น„ ๋‹ด๋‹น ์žฅ๊ต ๋ธŒ๋ฆฌํ•‘ ์š”์•ฝ] [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ]\n", "1. 18๊ฐœ์›” ์ •๋น„ 10681๊ฑด: ๊ณ ์žฅ๋ฅ  ์ƒ์œ„ ์—ฐ๋ฃŒ๋ถ„์‚ฌ๊ธฐ 16.3๊ฑด/1000h, ํ„ฐ๋ณด์ฐจ์ € 11.6๊ฑด/1000h, ์ถ•์ „์ง€ 11.6๊ฑด/1000h\n", "2. ์ƒ์œ„ 5๊ฐœ ํ’ˆ๋ชฉ์ด ์ „์ฒด ๊ณ ์žฅ์˜ 80% ์ด์ƒ: ์˜ˆ๋น„ ์ˆ˜๋ฆฌ๋ถ€์† ์šฐ์„  ํ™•๋ณด ๋Œ€์ƒ\n", "3. ์šด์šฉ ๊ธฐ๊ฐ„ ์ง„๋‹จ 1552๊ฑด ์ค‘ ์‹ ๋ขฐ๋„ 0.8 ์ด์ƒ 931๊ฑด(์ •ํ™•๋„ 99.2%), ์ •๋น„๊ด€ ์Šน์ธ ๋Œ€๊ธฐ 621๊ฑด(์ž๋™ ํ™•์ • 0๊ฑด)\n", "4. ๊ธฐ๋™ยท์ œ๋™ ๊ณ„ํ†ต ๊ฒฝ๋ณด 710๊ฑด, ์†์ƒ ๋กœ๊ทธ 2๊ฑด ์ˆ˜๋™ ํ™•์ธ\n", "5. ์ง„๋‹จ ๋ชจ๋ธ: ์‹ ๊ทœ ์ •๋น„ ์ด๋ ฅ์œผ๋กœ ๊ต์ฒด, ์†Œ์š” ์˜ˆ์ธก MAPE 22.7%\n", "6. ๊ฑด์˜: ๋‹ค๋นˆ๋„ ํ’ˆ๋ชฉ ์˜ˆ๋ฐฉ์ •๋น„ ์ฃผ๊ธฐ ์žฌ๊ฒ€ํ† , ๊ตญ๋ฐฉ ๊ตฐ์ˆ˜ ์ •๋ณด์ฒด๊ณ„์™€ DTCยท์ •๋น„ ์ด๋ ฅ ํ‘œ์ค€ ํ•„๋“œ ์—ฐ๋™ ์ธํ„ฐํŽ˜์ด์Šค ์ •์˜\n", "```\n", "
\n" ] } ], "metadata": { "colab": { "provenance": [] }, "kernelspec": { "display_name": "Python 3", "name": "python3" }, "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 0 }