iotlabkit 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,382 @@
1
+ """Template generator for Program 16: Linear Regression Ambient Temperature Prediction."""
2
+
3
+ import os
4
+ from typing import Dict
5
+
6
+
7
+ def generate(target_dir: str, mode: str = "learning") -> Dict[str, str]:
8
+ files = {}
9
+
10
+ files["requirements.txt"] = (
11
+ "pandas>=1.5.0\n"
12
+ "scikit-learn>=1.2.0\n"
13
+ "matplotlib>=3.6.0\n"
14
+ "joblib>=1.2.0\n"
15
+ )
16
+
17
+ files[".env.example"] = (
18
+ "# Linear Regression Model Settings\n"
19
+ "MODEL_PATH=model/temperature_linear_reg.joblib\n"
20
+ )
21
+
22
+ files[".gitignore"] = (
23
+ ".env\n"
24
+ "__pycache__/\n"
25
+ "*.py[cod]\n"
26
+ "model/*.joblib\n"
27
+ "output/*.png\n"
28
+ )
29
+
30
+ # Embed genuine sample dataset
31
+ data_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "data", "beijing_air", "beijing_sample.csv")
32
+ if os.path.exists(data_path):
33
+ with open(data_path, "r", encoding="utf-8") as f:
34
+ files["data/beijing_sample.csv"] = f.read()
35
+
36
+ # data/download_dataset.py
37
+ files["data/download_dataset.py"] = (
38
+ '"""Dataset Downloader for UCI Beijing PM2.5 / Air Quality Dataset (#501).\n\n'
39
+ "Downloads the complete 43,800+ row dataset directly from the official\n"
40
+ "UCI repository or GitHub mirror (CC BY 4.0 license).\n"
41
+ '"""\n\n'
42
+ "import os\n"
43
+ "import sys\n"
44
+ "import requests\n\n"
45
+ "DATASET_URL = 'https://raw.githubusercontent.com/at-tan/Forecasting_Air_Pollution/main/PRSA_data.csv'\n"
46
+ "TARGET_FILE = os.path.join(os.path.dirname(__file__), 'beijing_sample.csv')\n\n"
47
+ "def main():\n"
48
+ " print(f\"Downloading complete UCI Beijing PM2.5 dataset from:\\n {DATASET_URL}...\")\n"
49
+ " try:\n"
50
+ " resp = requests.get(DATASET_URL, timeout=20)\n"
51
+ " if resp.status_code == 200:\n"
52
+ " with open(TARGET_FILE, 'w', encoding='utf-8') as f:\n"
53
+ " f.write(resp.text)\n"
54
+ " lines = resp.text.strip().split('\\n')\n"
55
+ " print(f\"✓ Downloaded {len(lines) - 1} records to '{TARGET_FILE}'.\")\n"
56
+ " else:\n"
57
+ " print(f\"✗ Server returned HTTP {resp.status_code}\")\n"
58
+ " except Exception as e:\n"
59
+ " print(f\"✗ Download error: {e}\")\n"
60
+ " print(\"\\nManual Download Instructions:\")\n"
61
+ " print(\"1. Visit https://archive.ics.uci.edu/dataset/501/beijing+pm2.5+data\")\n"
62
+ " print(\"2. Download PRSA dataset and extract CSV into the 'data/' folder.\")\n\n"
63
+ "if __name__ == '__main__':\n"
64
+ " main()\n"
65
+ )
66
+
67
+ # column_inspection.py
68
+ files["column_inspection.py"] = (
69
+ '"""Rigorous Dataset Column Inspection Tool.\n\n'
70
+ "Inspects genuine columns in the Beijing dataset and documents feature availability:\n"
71
+ "Verifies that 'pm2.5', 'DEWP' (dew point), 'PRES' (pressure), and 'Iws' (wind speed) are present.\n"
72
+ "Documents that PM10 was not collected in the classic UCI Beijing PM2.5 dataset,\n"
73
+ "preventing runtime crashes or fabricated feature hallucination.\n"
74
+ '"""\n\n'
75
+ "import os\n"
76
+ "import pandas as pd\n\n"
77
+ "CSV_PATH = os.path.join(os.path.dirname(__file__), 'data', 'beijing_sample.csv')\n\n"
78
+ "def main():\n"
79
+ " if not os.path.exists(CSV_PATH):\n"
80
+ " print(f\"Dataset '{CSV_PATH}' not found.\")\n"
81
+ " return\n\n"
82
+ " df = pd.read_csv(CSV_PATH)\n"
83
+ " print('=' * 75)\n"
84
+ " print('🔍 Real Dataset Column Inspection: Beijing Air Quality (UCI #501)')\n"
85
+ " print('=' * 75)\n"
86
+ " print(f\"Total Columns Found ({len(df.columns)}):\\n {list(df.columns)}\\n\")\n"
87
+ " print('Feature Analysis & Verification:')\n"
88
+ " print(\" ✓ 'pm2.5' : PM2.5 concentration (ug/m^3) [AVAILABLE]\")\n"
89
+ " print(\" ✓ 'DEWP' : Dew Point Temperature (°C) [AVAILABLE]\")\n"
90
+ " print(\" ✓ 'TEMP' : Temperature (°C) — TARGET [AVAILABLE]\")\n"
91
+ " print(\" ✓ 'PRES' : Atmospheric Pressure (hPa) [AVAILABLE]\")\n"
92
+ " print(\" ✓ 'Iws' : Cumulated Wind Speed (m/s) [AVAILABLE]\")\n"
93
+ " print(\" ✗ 'PM10' : PM10 concentration [NOT IN THIS UCI DATASET]\")\n"
94
+ " print('=' * 75)\n"
95
+ " print(\"Explanation:\")\n"
96
+ " print(\" The original UCI Beijing PM2.5 monitoring station recorded fine particles (pm2.5),\")\n"
97
+ " print(\" dew point, pressure, and wind speed, but did NOT have a PM10 coarse particulate sensor.\")\n"
98
+ " print(\" Our implementation transparently uses all verified compatible features\")\n"
99
+ " print(\" without inventing non-existent PM10 values. If a multi-site CSV with PM10\")\n"
100
+ " print(\" is loaded, the training script automatically detects and includes it!\")\n"
101
+ " print('=' * 75 + '\\n')\n\n"
102
+ "if __name__ == '__main__':\n"
103
+ " main()\n"
104
+ )
105
+
106
+ # train.py
107
+ if mode == "learning":
108
+ train_doc = (
109
+ '"""Linear Regression Temperature Prediction Training - Program 16.\n\n'
110
+ "Trains a Multiple Linear Regression model on real meteorological and particulate data.\n"
111
+ "Predicts ambient temperature from Dew Point (DEWP), Pressure (PRES), PM2.5, and Wind Speed.\n"
112
+ "Calculates regression metrics: MAE, MSE, RMSE, and R² score, and saves the trained model.\n"
113
+ '"""\n'
114
+ )
115
+ else:
116
+ train_doc = '"""Linear Regression Model - Program 16 (Exam Mode)."""\n'
117
+
118
+ files["train.py"] = train_doc + (
119
+ "import os\n"
120
+ "import sys\n"
121
+ "import numpy as np\n"
122
+ "import pandas as pd\n"
123
+ "try:\n"
124
+ " import matplotlib.pyplot as plt\n"
125
+ " HAS_MATPLOTLIB = True\n"
126
+ "except ImportError:\n"
127
+ " HAS_MATPLOTLIB = False\n"
128
+ "import joblib\n"
129
+ "from sklearn.linear_model import LinearRegression\n"
130
+ "from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score\n"
131
+ "from sklearn.model_selection import train_test_split\n\n"
132
+ "DATA_PATH = os.path.join(os.path.dirname(__file__), 'data', 'beijing_sample.csv')\n"
133
+ "MODEL_DIR = os.path.join(os.path.dirname(__file__), 'model')\n"
134
+ "OUTPUT_DIR = os.path.join(os.path.dirname(__file__), 'output')\n\n"
135
+ "def main():\n"
136
+ " if not os.path.exists(DATA_PATH):\n"
137
+ " print(f\"✗ Error: Dataset file '{DATA_PATH}' not found.\")\n"
138
+ " print(\" Run `python data/download_dataset.py` to obtain official data.\")\n"
139
+ " sys.exit(1)\n\n"
140
+ " print('=' * 75)\n"
141
+ " print('📈 Program 16: Linear Regression Ambient Temperature Prediction')\n"
142
+ " print('=' * 75)\n"
143
+ " print(f\"Loading dataset from: {DATA_PATH}...\")\n"
144
+ " df = pd.read_csv(DATA_PATH)\n"
145
+ " print(f\"✓ Loaded {len(df)} authentic records. Inspecting columns...\")\n\n"
146
+ " # Dynamically discover compatible features:\n"
147
+ " # Check for target 'TEMP'\n"
148
+ " target_col = None\n"
149
+ " for c in ['TEMP', 'temp', 'temperature']:\n"
150
+ " if c in df.columns:\n"
151
+ " target_col = c\n"
152
+ " break\n"
153
+ " if not target_col:\n"
154
+ " raise ValueError(\"Target temperature column 'TEMP' not found in dataset!\")\n\n"
155
+ " # Identify compatible predictor features\n"
156
+ " candidate_features = [\n"
157
+ " ('pm2.5', ['pm2.5', 'PM2.5', 'pm25']),\n"
158
+ " ('PM10', ['PM10', 'pm10']),\n"
159
+ " ('DEWP', ['DEWP', 'dewp', 'dew_point']),\n"
160
+ " ('PRES', ['PRES', 'pres', 'pressure']),\n"
161
+ " ('wind', ['Iws', 'WSPM', 'wind_speed', 'wspm'])\n"
162
+ " ]\n\n"
163
+ " selected_features = []\n"
164
+ " for standard_name, aliases in candidate_features:\n"
165
+ " found = False\n"
166
+ " for a in aliases:\n"
167
+ " if a in df.columns:\n"
168
+ " selected_features.append(a)\n"
169
+ " print(f\" ✓ Included predictor: '{a}' ({standard_name})\")\n"
170
+ " found = True\n"
171
+ " break\n"
172
+ " if not found:\n"
173
+ " print(f\" - Notice: Column for '{standard_name}' is not in this dataset file (omitted cleanly).\")\n\n"
174
+ " if not selected_features:\n"
175
+ " raise ValueError(\"No compatible predictor features found!\")\n\n"
176
+ " # Preprocess missing values\n"
177
+ " all_cols = selected_features + [target_col]\n"
178
+ " # Convert to numeric, turning 'NA' strings into np.nan\n"
179
+ " for col in all_cols:\n"
180
+ " df[col] = pd.to_numeric(df[col], errors='coerce')\n\n"
181
+ " initial_count = len(df)\n"
182
+ " df = df.dropna(subset=all_cols)\n"
183
+ " clean_count = len(df)\n"
184
+ " print(f\"Cleaned observations: {clean_count} records (dropped {initial_count - clean_count} rows with missing measurements)\\n\")\n\n"
185
+ " X = df[selected_features]\n"
186
+ " y = df[target_col]\n\n"
187
+ " # Split 80% Train, 20% Test\n"
188
+ " X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.20, random_state=42)\n"
189
+ " print(f\"Training set: {len(X_train)} samples | Test set: {len(X_test)} samples\")\n\n"
190
+ " # Train Linear Regression\n"
191
+ " model = LinearRegression()\n"
192
+ " model.fit(X_train, y_train)\n\n"
193
+ " # Predict on test set\n"
194
+ " y_pred = model.predict(X_test)\n\n"
195
+ " # Evaluate Regression Metrics\n"
196
+ " mae = mean_absolute_error(y_test, y_pred)\n"
197
+ " mse = mean_squared_error(y_test, y_pred)\n"
198
+ " rmse = np.sqrt(mse)\n"
199
+ " r2 = r2_score(y_test, y_pred)\n\n"
200
+ " print('\\n' + '=' * 75)\n"
201
+ " print('📊 Linear Regression Evaluation Metrics (Test Set):')\n"
202
+ " print('=' * 75)\n"
203
+ " print(f\" Mean Absolute Error (MAE) : {mae:.3f} °C\")\n"
204
+ " print(f\" Mean Squared Error (MSE) : {mse:.3f} (°C)²\")\n"
205
+ " print(f\" Root Mean Squared Error (RMSE) : {rmse:.3f} °C\")\n"
206
+ " print(f\" Coefficient of Determination (R²) : {r2:.4f} ({r2 * 100:.2f}% variance explained)\")\n"
207
+ " print('=' * 75)\n\n"
208
+ " print('Learned Linear Regression Equation:')\n"
209
+ " terms = [f\"{coef:+.4f} * ({feat})\" for feat, coef in zip(selected_features, model.coef_)]\n"
210
+ " print(f\" Temperature = {model.intercept_:.4f} \" + \" \".join(terms) + \"\\n\")\n\n"
211
+ " # Save Model & Metadata\n"
212
+ " os.makedirs(MODEL_DIR, exist_ok=True)\n"
213
+ " os.makedirs(OUTPUT_DIR, exist_ok=True)\n"
214
+ " model_bundle = {\n"
215
+ " 'model': model,\n"
216
+ " 'features': selected_features,\n"
217
+ " 'target': target_col,\n"
218
+ " 'metrics': {'mae': mae, 'rmse': rmse, 'r2': r2}\n"
219
+ " }\n"
220
+ " model_path = os.path.join(MODEL_DIR, 'temperature_linear_reg.joblib')\n"
221
+ " joblib.dump(model_bundle, model_path)\n"
222
+ " print(f\"✓ Saved model bundle to: {model_path}\")\n\n"
223
+ " if HAS_MATPLOTLIB:\n"
224
+ " # Plot 1: Actual vs. Predicted Scatter Plot\n"
225
+ " plt.figure(figsize=(7, 6))\n"
226
+ " plt.scatter(y_test, y_pred, alpha=0.6, color='tab:blue', edgecolors='none', label='Test Observations')\n"
227
+ " # Diagonal ideal reference line\n"
228
+ " lims = [min(y_test.min(), y_pred.min()), max(y_test.max(), y_pred.max())]\n"
229
+ " plt.plot(lims, lims, 'r--', label='Ideal Fit (y = x)')\n"
230
+ " plt.title(f'Actual vs. Predicted Temperature (R² = {r2:.3f}, RMSE = {rmse:.2f}°C)')\n"
231
+ " plt.xlabel('Actual Temperature (°C)')\n"
232
+ " plt.ylabel('Predicted Temperature (°C)')\n"
233
+ " plt.grid(True, linestyle='--', alpha=0.6)\n"
234
+ " plt.legend()\n"
235
+ " p1 = os.path.join(OUTPUT_DIR, 'actual_vs_predicted.png')\n"
236
+ " plt.savefig(p1, dpi=150, bbox_inches='tight')\n"
237
+ " plt.close()\n"
238
+ " print(f\"✓ Saved Actual vs Predicted Plot: {p1}\")\n\n"
239
+ " # Plot 2: Residuals Distribution\n"
240
+ " residuals = y_test - y_pred\n"
241
+ " plt.figure(figsize=(7, 4.5))\n"
242
+ " plt.hist(residuals, bins=20, color='darkcyan', edgecolor='black', alpha=0.7)\n"
243
+ " plt.axvline(0, color='red', linestyle='--')\n"
244
+ " plt.title(f'Prediction Residuals Distribution (Mean Error: {residuals.mean():.2f}°C)')\n"
245
+ " plt.xlabel('Error (Actual - Predicted, °C)')\n"
246
+ " plt.ylabel('Frequency')\n"
247
+ " plt.grid(axis='y', linestyle='--', alpha=0.6)\n"
248
+ " p2 = os.path.join(OUTPUT_DIR, 'residuals_distribution.png')\n"
249
+ " plt.savefig(p2, dpi=150, bbox_inches='tight')\n"
250
+ " plt.close()\n"
251
+ " print(f\"✓ Saved Residuals Histogram: {p2}\")\n"
252
+ " else:\n"
253
+ " print(\"\\nℹ️ Notice: matplotlib not installed. Evaluation metrics (MAE, RMSE, R²) printed above. Install matplotlib to save PNG charts.\")\n\n"
254
+ " print('\\nReady for inference! Test: python predict.py --dewp -15 --pres 1020 --pm25 120 --wind 2.0')\n\n"
255
+ "if __name__ == '__main__':\n"
256
+ " main()\n"
257
+ )
258
+
259
+ # predict.py
260
+ if mode == "learning":
261
+ pred_doc = (
262
+ '"""Temperature Prediction Inference Script - Program 16.\n\n'
263
+ "Loads the trained Multiple Linear Regression model bundle and predicts ambient temperature\n"
264
+ "for user-specified values of Dew Point (DEWP), Atmospheric Pressure (PRES), PM2.5, and Wind Speed.\n"
265
+ '"""\n'
266
+ )
267
+ else:
268
+ pred_doc = '"""Temperature Inference - Program 16 (Exam Mode)."""\n'
269
+
270
+ files["predict.py"] = pred_doc + (
271
+ "import argparse\n"
272
+ "import os\n"
273
+ "import sys\n"
274
+ "import joblib\n"
275
+ "import pandas as pd\n\n"
276
+ "MODEL_PATH = os.path.join(os.path.dirname(__file__), 'model', 'temperature_linear_reg.joblib')\n\n"
277
+ "def main():\n"
278
+ " parser = argparse.ArgumentParser(description='Predict ambient temperature using Linear Regression')\n"
279
+ " parser.add_argument('--dewp', type=float, default=-16.0, help='Dew point temperature in °C')\n"
280
+ " parser.add_argument('--pres', type=float, default=1020.0, help='Atmospheric pressure in hPa')\n"
281
+ " parser.add_argument('--pm25', type=float, default=129.0, help='PM2.5 concentration in μg/m³')\n"
282
+ " parser.add_argument('--pm10', type=float, default=None, help='PM10 concentration in μg/m³ (optional)')\n"
283
+ " parser.add_argument('--wind', type=float, default=1.79, help='Cumulated wind speed in m/s')\n"
284
+ " args = parser.parse_args()\n\n"
285
+ " if not os.path.exists(MODEL_PATH):\n"
286
+ " print(f\"✗ Error: Model file '{MODEL_PATH}' not found.\")\n"
287
+ " print(\" Run `python train.py` first to train the Linear Regression model.\")\n"
288
+ " sys.exit(1)\n\n"
289
+ " bundle = joblib.load(MODEL_PATH)\n"
290
+ " model = bundle['model']\n"
291
+ " features = bundle['features']\n\n"
292
+ " # Build input row matching model feature names\n"
293
+ " row = {}\n"
294
+ " for f in features:\n"
295
+ " if f in ['DEWP', 'dewp', 'dew_point']:\n"
296
+ " row[f] = args.dewp\n"
297
+ " elif f in ['PRES', 'pres', 'pressure']:\n"
298
+ " row[f] = args.pres\n"
299
+ " elif f in ['pm2.5', 'PM2.5', 'pm25']:\n"
300
+ " row[f] = args.pm25\n"
301
+ " elif f in ['PM10', 'pm10']:\n"
302
+ " row[f] = args.pm10 if args.pm10 is not None else 100.0\n"
303
+ " elif f in ['Iws', 'WSPM', 'wind_speed', 'wspm']:\n"
304
+ " row[f] = args.wind\n"
305
+ " else:\n"
306
+ " row[f] = 0.0\n\n"
307
+ " input_df = pd.DataFrame([row])\n"
308
+ " predicted_temp = float(model.predict(input_df)[0])\n\n"
309
+ " print('=' * 65)\n"
310
+ " print('🌡️ Linear Regression Temperature Prediction')\n"
311
+ " print('=' * 65)\n"
312
+ " print('Input Sensor Readings:')\n"
313
+ " print(f\" - Dew Point Temperature (DEWP) : {args.dewp} °C\")\n"
314
+ " print(f\" - Atmospheric Pressure (PRES) : {args.pres} hPa\")\n"
315
+ " print(f\" - PM2.5 Particle Level : {args.pm25} μg/m³\")\n"
316
+ " if args.pm10 is not None:\n"
317
+ " print(f\" - PM10 Particle Level : {args.pm10} μg/m³\")\n"
318
+ " print(f\" - Wind Speed (Iws) : {args.wind} m/s\")\n"
319
+ " print('-' * 65)\n"
320
+ " print(f\"Predicted Ambient Temperature : {predicted_temp:.2f} °C\")\n"
321
+ " print(f\"Model R² Quality : {bundle['metrics']['r2']:.4f}\")\n"
322
+ " print(f\"Expected Estimation Error (RMSE) : ±{bundle['metrics']['rmse']:.2f} °C\")\n"
323
+ " print('=' * 65)\n\n"
324
+ "if __name__ == '__main__':\n"
325
+ " main()\n"
326
+ )
327
+
328
+ # README.md
329
+ if mode == "learning":
330
+ readme_content = (
331
+ "# Program 16 — Linear Regression Temperature Prediction\n\n"
332
+ "## 1. Overview\n"
333
+ "This practical builds a supervised Multiple Linear Regression model predicting ambient temperature:\n"
334
+ "- Uses the genuine **UCI Beijing Air Quality Dataset (#501)** (CC BY 4.0).\n"
335
+ "- Performs rigorous column inspection: explains that while Dew Point (`DEWP`), Pressure (`PRES`), `pm2.5`, and Wind Speed (`Iws`) exist, `PM10` was not recorded by this specific monitoring station.\n"
336
+ "- Evaluates regression metrics: **Mean Absolute Error (MAE)**, **Mean Squared Error (MSE)**, **Root Mean Squared Error (RMSE)**, and **Coefficient of Determination ($R^2$)**.\n"
337
+ "- Visualizes predicted vs actual temperature and residual errors.\n"
338
+ "- Exports model to `model/temperature_linear_reg.joblib` with CLI inference.\n\n"
339
+ "## 2. Regression Formula Primer\n"
340
+ "Multiple Linear Regression models the target temperature as a linear combination of predictors:\n"
341
+ "$$\\hat{y} = \\beta_0 + \\beta_1 X_{\\text{DEWP}} + \\beta_2 X_{\\text{PRES}} + \\beta_3 X_{\\text{pm2.5}} + \\beta_4 X_{\\text{wind}}$$\n"
342
+ "- **Dew Point (DEWP)** exhibits the strongest positive coefficient because moisture-saturated air holds heat.\n"
343
+ "- **Atmospheric Pressure (PRES)** typically exhibits a negative coefficient as cold anticyclonic winter air masses create high surface pressures.\n\n"
344
+ "## 3. Step-by-Step Execution Guide\n\n"
345
+ "### Step A: Setup & Inspect Dataset Columns\n"
346
+ "```bash\n"
347
+ "pip install -r requirements.txt\n"
348
+ "python column_inspection.py\n"
349
+ "```\n\n"
350
+ "### Step B: Train Model & Evaluate Loss Metrics\n"
351
+ "```bash\n"
352
+ "python train.py\n"
353
+ "```\n"
354
+ "Check the printed evaluation metrics (MAE, RMSE, $R^2$) and generated charts in `output/`:\n"
355
+ "- `actual_vs_predicted.png`\n"
356
+ "- `residuals_distribution.png`\n\n"
357
+ "### Step C: Predict Temperature on New Readings\n"
358
+ "```bash\n"
359
+ "python predict.py --dewp -12.0 --pres 1018.0 --pm25 85.0 --wind 3.5\n"
360
+ "```\n\n"
361
+ "## 4. Viva Questions and Answers\n"
362
+ "**Q1: What does the R² (Coefficient of Determination) score represent?** \n"
363
+ "A: $R^2 = 1 - \\frac{SS_{\\text{res}}}{SS_{\\text{tot}}}$ measures the proportion of total variance in the target variable that is explained by the regression model. $R^2 = 1.0$ indicates perfect prediction; $R^2 = 0.0$ indicates the model performs no better than guessing the mean.\n\n"
364
+ "**Q2: What is the physical reason Dew Point is strongly correlated with Temperature?** \n"
365
+ "A: Dew point is the temperature to which air must be cooled to become saturated. Because warmer air can hold substantially more moisture than cold air, higher ambient temperatures consistently coincide with higher dew points.\n"
366
+ )
367
+ else:
368
+ readme_content = (
369
+ "# Program 16 — Linear Regression Temperature Prediction (Exam Mode)\n\n"
370
+ "## Execution\n"
371
+ "1. Inspect columns: `python column_inspection.py`\n"
372
+ "2. Train model: `python train.py`\n"
373
+ "3. Predict: `python predict.py --dewp -16 --pres 1020 --pm25 120 --wind 2.0`\n\n"
374
+ "## Viva Q&A\n"
375
+ "- **Q: Why check dataset columns before training?** \n"
376
+ " A: To avoid assuming columns like PM10 exist when they are absent in the official dataset, ensuring transparency and preventing runtime errors.\n"
377
+ "- **Q: Difference between MAE and RMSE?** \n"
378
+ " A: MAE takes the linear average of absolute errors ($|y - \\hat{y}|$), while RMSE squares errors before averaging, penalizing large outlier errors more heavily.\n"
379
+ )
380
+
381
+ files["README.md"] = readme_content
382
+ return files