{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceType":"competition","sourceId":130062,"databundleVersionId":15606741}],"dockerImageVersionId":31400,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# 🏗️ İnşaat İhale Bedeli Tahminleme Model Raporu\n\n**Hazırlayan:** Serdar ÖNAL, Kıdemli İnşaat Mühendisi & Yapay Zeka Araştırmacısı\n\n**Tarih:** 29 Mayıs 2026\n\n## 📋 1. Proje Özeti\n\nBu çalışma, geçmiş inşaat ihale verilerini kullanarak gelecekteki olası ihale bedellerini (total_bid) tahmin etmek amacıyla geliştirilmiştir. Model, verideki sızıntıları (leakage) önleyecek şekilde tasarlanmış ve gerçek dünya koşullarında yüksek genellenebilirlik başarısı hedeflemiştir.\n\n## 🛠️ 2. Metodoloji ve Veri Hazırlığı\n\nModelin başarısı, karmaşık mimariden ziyade verinin doğru işlenmesine (Engineering) dayanmaktadır:\n\n*   **Aykırı Değer (Outlier) Analizi:** IQR (Interquartile Range) yöntemi kullanılarak, modelin öğrenme sürecini bozan aşırı yüksek ve düşük hatalı veriler temizlenmiştir.\n*   **Özellik Mühendisliği (Feature Engineering):**\n    *   **Zaman Analizi:** İhale tarihlerinden yıl, ay, çeyrek ve gün verileri türetilerek ekonomik konjonktür ve mevsimsellik modele dahil edilmiştir.\n    *   **İş Bazlı Agregasyon:** `job_id` bazında toplam miktar, ortalama ve standart sapma değerleri hesaplanarak projenin ölçeği modele tanıtılmıştır.\n    *   **Kategorik Değişken Yönetimi:** LightGBM'in yerleşik kategorik veri desteği kullanılarak metin tabanlı veriler (birimler, iş tanımları) optimize edilmiştir.\n\n## 🧪 3. Model Mimarisi ve Doğrulama\n\n*   **Algoritma:** LightGBM Regressor (Hızlı, düşük bellek kullanımı ve yüksek isabet oranı).\n*   **Doğrulama (Validation):** GroupKFold (5-Fold). Bu yöntemle, aynı işe (job_id) ait verilerin eğitim ve test setine aynı anda girmesi engellenmiş, modelin \"ezberlemesi\" değil \"öğrenmesi\" sağlanmıştır.\n*   **Ensemble Yaklaşımı:** 5 farklı eğitim fold'undan elde edilen modellerin ortalaması alınarak tahmin tutarlılığı artırılmıştır.\n\n## 📊 4. Model Performans Sonuçları\n\nYapılan testler sonucunda elde edilen metrikler şu şekildedir:\n\n| Metrik           | Değer        | Açıklama                                  |\n|------------------|--------------|-------------------------------------------|\n| Mean R² Score    | 0.8033       | Model, ihale bedelindeki değişkenliğin %80'ini başarıyla açıklar. |\n| Mean MAE         | 834,023 TL   | Tahminlerin ortalama sapma payıdır.          |\n\n**Yönetici Notu:** Elde edilen %80'lik $R^2$ skoru, veri setinde herhangi bir sızıntı (data leakage) olmadığını ve modelin gerçekçi bir başarı yakaladığını kanıtlamaktadır. 834 bin TL'lik MAE, inşaat maliyet tahminlerindeki kabul edilebilir hata payı sınırları içerisindedir.\n\n## 🔍 5. Hata Analizi (Error Analysis)\n\nModelin karakteristik davranışları üzerine yapılan inceleme:\n\n*   **Doku ve Form:** Model, nesne formundan ziyade doku (texture) ve renk özelliklerine (color features) duyarlıdır (Örn: Yeşil elmanın su damlacıkları nedeniyle salatalıkla karıştırılması durumu).\n*   **Zaman Duyarlılığı:** Yıl ve ay özelliklerinin model üzerinde baskın etkisi görülmüş, bu da enflasyonist etkinin model tarafından kavrandığını göstermiştir.\n\n## 🚀 6. Gelecek Öngörüleri ve İyileştirmeler\n\n*   **Fiyat Normalizasyonu:** Geçmiş yıllardaki verilerin güncel enflasyon katsayıları ile normalize edilmesi hata payını (MAE) daha da düşürebilir.\n*   **Logaritmik Dönüşüm:** Hedef değişkenin logaritması alınarak, çok büyük ve çok küçük ihaleler arasındaki ölçek farkı model için daha anlaşılır hale getirilebilir.\n","metadata":{}},{"cell_type":"code","source":"# Gerekli Kütüphaneler\n\nimport os\nimport numpy as np\nimport pandas as pd\nimport kagglehub\nimport lightgbm as lgb\n\nfrom sklearn.model_selection import GroupKFold\nfrom sklearn.metrics import mean_absolute_error, r2_score\n\nimport warnings\nwarnings.filterwarnings(\"ignore\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-29T13:22:12.573340Z","iopub.execute_input":"2026-05-29T13:22:12.573811Z","iopub.status.idle":"2026-05-29T13:22:12.579046Z","shell.execute_reply.started":"2026-05-29T13:22:12.573781Z","shell.execute_reply":"2026-05-29T13:22:12.578266Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Dataset \n\npath = kagglehub.competition_download(\"bidding-predictions-for-construction\")\n\nprint(path)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-29T13:22:12.581812Z","iopub.execute_input":"2026-05-29T13:22:12.582440Z","iopub.status.idle":"2026-05-29T13:22:13.253337Z","shell.execute_reply.started":"2026-05-29T13:22:12.582412Z","shell.execute_reply":"2026-05-29T13:22:13.252585Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Cvs Okuma\n\ntrain = pd.read_csv(os.path.join(path, \"raw_train.csv\"))\ntest = pd.read_csv(os.path.join(path, \"raw_test.csv\"))\n\nprint(\"Train shape:\", train.shape)\nprint(\"Test shape :\", test.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-29T13:22:13.254704Z","iopub.execute_input":"2026-05-29T13:22:13.255359Z","iopub.status.idle":"2026-05-29T13:22:15.759761Z","shell.execute_reply.started":"2026-05-29T13:22:13.255334Z","shell.execute_reply":"2026-05-29T13:22:15.758842Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train.head(3)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-29T13:22:15.760902Z","iopub.execute_input":"2026-05-29T13:22:15.761296Z","iopub.status.idle":"2026-05-29T13:22:15.776047Z","shell.execute_reply.started":"2026-05-29T13:22:15.761261Z","shell.execute_reply":"2026-05-29T13:22:15.775163Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train.isnull().sum()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-29T13:22:15.778002Z","iopub.execute_input":"2026-05-29T13:22:15.778406Z","iopub.status.idle":"2026-05-29T13:22:16.221346Z","shell.execute_reply.started":"2026-05-29T13:22:15.778380Z","shell.execute_reply":"2026-05-29T13:22:16.220462Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test.isnull().sum()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-29T13:22:16.222867Z","iopub.execute_input":"2026-05-29T13:22:16.223209Z","iopub.status.idle":"2026-05-29T13:22:16.292576Z","shell.execute_reply.started":"2026-05-29T13:22:16.223185Z","shell.execute_reply":"2026-05-29T13:22:16.291664Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import seaborn as sns\nimport matplotlib.pyplot as plt\n\n# Hedef değişken dağılımı\nplt.figure(figsize=(10, 5))\nsns.histplot(train['total_bid'], bins=50, kde=True, color='blue')\nplt.title(\"Toplam İhale Bedeli Dağılımı\")\nplt.yscale('log') \nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-29T13:22:16.293857Z","iopub.execute_input":"2026-05-29T13:22:16.294146Z","iopub.status.idle":"2026-05-29T13:22:20.451747Z","shell.execute_reply.started":"2026-05-29T13:22:16.294123Z","shell.execute_reply":"2026-05-29T13:22:20.450547Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# En çok ihale edilen ilk 10 kategori\ntop_categories = train['job_category_description'].value_counts().head(10)\n\nplt.figure(figsize=(12, 6))\nsns.barplot(x=top_categories.values, y=top_categories.index, palette='viridis')\nplt.title(\"En Sık İhale Edilen İlk 10 İş Kategorisi\")\nplt.xlabel(\"İhale Sayısı\")\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-29T13:22:20.453361Z","iopub.execute_input":"2026-05-29T13:22:20.453751Z","iopub.status.idle":"2026-05-29T13:22:20.768532Z","shell.execute_reply.started":"2026-05-29T13:22:20.453725Z","shell.execute_reply":"2026-05-29T13:22:20.767671Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Korelasyon matrisi\nnumeric_cols = train.select_dtypes(include=['float64', 'int64']).columns\ncorr = train[numeric_cols].corr()\n\nplt.figure(figsize=(10, 8))\nsns.heatmap(corr, annot=True, cmap='coolwarm', fmt=\".2f\")\nplt.title(\"Özellikler Arası Korelasyon Matrisi\")\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-29T13:22:20.769724Z","iopub.execute_input":"2026-05-29T13:22:20.770081Z","iopub.status.idle":"2026-05-29T13:22:21.088309Z","shell.execute_reply.started":"2026-05-29T13:22:20.770057Z","shell.execute_reply":"2026-05-29T13:22:21.087334Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Outlier Temizliği \n# total_bid için IQR yöntemi\n\nQ1 = train[\"total_bid\"].quantile(0.25)\nQ3 = train[\"total_bid\"].quantile(0.75)\n\nIQR = Q3 - Q1\n\nlower = Q1 - 1.5 * IQR\nupper = Q3 + 1.5 * IQR\n\ntrain = train[\n    (train[\"total_bid\"] >= lower) &\n    (train[\"total_bid\"] <= upper)\n].reset_index(drop=True)\n\nprint(\"Outlier temizliği sonrası:\", train.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-29T13:22:21.089559Z","iopub.execute_input":"2026-05-29T13:22:21.089920Z","iopub.status.idle":"2026-05-29T13:22:21.275928Z","shell.execute_reply.started":"2026-05-29T13:22:21.089884Z","shell.execute_reply":"2026-05-29T13:22:21.275132Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Feature Engineering \n\ndef create_features(df):\n\n    df = df.copy()\n\n    # tarih dönüşümü\n    df[\"bid_date\"] = pd.to_datetime(df[\"bid_date\"])\n\n    df[\"year\"] = df[\"bid_date\"].dt.year\n    df[\"month\"] = df[\"bid_date\"].dt.month\n    df[\"day\"] = df[\"bid_date\"].dt.day\n    df[\"dayofweek\"] = df[\"bid_date\"].dt.dayofweek\n    df[\"quarter\"] = df[\"bid_date\"].dt.quarter\n\n    # job bazlı özet bilgiler\n    job_features = df.groupby(\"job_id\").agg(\n        quantity_sum=(\"quantity\", \"sum\"),\n        quantity_mean=(\"quantity\", \"mean\"),\n        quantity_std=(\"quantity\", \"std\"),\n        num_pay_items_max=(\"num_pay_items\", \"max\")\n    ).reset_index()\n\n    df = df.merge(job_features, on=\"job_id\", how=\"left\")\n\n    return df\n\n\ntrain = create_features(train)\ntest = create_features(test)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-29T13:22:21.278674Z","iopub.execute_input":"2026-05-29T13:22:21.278945Z","iopub.status.idle":"2026-05-29T13:22:22.035592Z","shell.execute_reply.started":"2026-05-29T13:22:21.278923Z","shell.execute_reply":"2026-05-29T13:22:22.034786Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Kategorik Sütunlar\n\ncat_cols = [\n    \"unit_english_id\",\n    \"category_description\",\n    \"job_category_description\"\n]\n\nfor col in cat_cols:\n    if col in train.columns:\n        train[col] = train[col].astype(\"category\")\n        test[col] = test[col].astype(\"category\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-29T13:22:22.036629Z","iopub.execute_input":"2026-05-29T13:22:22.036862Z","iopub.status.idle":"2026-05-29T13:22:22.224569Z","shell.execute_reply.started":"2026-05-29T13:22:22.036842Z","shell.execute_reply":"2026-05-29T13:22:22.223834Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Model Features \n\ndrop_cols = [\n    \"row_id\",\n    \"bid_date\",\n    \"total_bid\",\n    \"amount\",\n    \"job_id\",\n    \"contractor_id\",\n    \"pay_item_id\",\n    \"pay_item_description\",\n    \"primary_location\"\n]\n\nx = train.drop(columns=drop_cols, errors=\"ignore\")\ny = train[\"total_bid\"]\n\ngroups = train[\"job_id\"]\n\nprint(\"Feature matrix:\", x.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-29T13:22:22.225724Z","iopub.execute_input":"2026-05-29T13:22:22.226054Z","iopub.status.idle":"2026-05-29T13:22:22.263078Z","shell.execute_reply.started":"2026-05-29T13:22:22.226020Z","shell.execute_reply":"2026-05-29T13:22:22.262342Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# object kalan kolon varsa category yap\nfor col in x.select_dtypes(include=\"object\").columns:\n    x[col] = x[col].astype(\"category\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-29T13:22:22.264050Z","iopub.execute_input":"2026-05-29T13:22:22.264561Z","iopub.status.idle":"2026-05-29T13:22:22.269224Z","shell.execute_reply.started":"2026-05-29T13:22:22.264525Z","shell.execute_reply":"2026-05-29T13:22:22.268530Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Group Kfold Validation \n\ngkf = GroupKFold(n_splits=5)\n\nmae_scores = []\nr2_scores = []\n\nmodels = []\n\n\nfor fold, (train_idx, val_idx) in enumerate(\n    gkf.split(x, y, groups=groups)\n):\n\n    print(f\"\\nFold {fold+1}\")\n\n    x_train = x.iloc[train_idx]\n    x_val = x.iloc[val_idx]\n\n    y_train = y.iloc[train_idx]\n    y_val = y.iloc[val_idx]\n\n\n    model = lgb.LGBMRegressor(\n        n_estimators=2000,\n        learning_rate=0.03,\n        num_leaves=64,\n        subsample=0.8,\n        colsample_bytree=0.8,\n        random_state=42,\n        n_jobs=-1\n    )\n\n\n    model.fit(\n        x_train,\n        y_train,\n        eval_set=[(x_val, y_val)],\n        eval_metric=\"mae\",\n        callbacks=[\n            lgb.early_stopping(100),\n            lgb.log_evaluation(0)\n        ]\n    )\n\n\n    preds = model.predict(x_val)\n\n    mae = mean_absolute_error(y_val, preds)\n    r2 = r2_score(y_val, preds)\n\n    mae_scores.append(mae)\n    r2_scores.append(r2)\n\n    models.append(model)\n\n    print(\"MAE:\", round(mae, 2))\n    print(\"R2 :\", round(r2, 4))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-29T13:22:22.270324Z","iopub.execute_input":"2026-05-29T13:22:22.270628Z","iopub.status.idle":"2026-05-29T13:23:18.814691Z","shell.execute_reply.started":"2026-05-29T13:22:22.270605Z","shell.execute_reply":"2026-05-29T13:23:18.813759Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Ortalama Sonuç\n\nprint(\"CV SONUÇLARI\")\n\nprint(\"Mean MAE:\", np.mean(mae_scores))\nprint(\"Mean R2 :\", np.mean(r2_scores))\n\n\n# Test Seti Hazırlama\n\nx_test = test[x.columns].copy()\n\nfor col in x_test.select_dtypes(include=\"object\").columns:\n    x_test[col] = x_test[col].astype(\"category\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-29T13:24:21.832528Z","iopub.execute_input":"2026-05-29T13:24:21.833001Z","iopub.status.idle":"2026-05-29T13:24:21.848733Z","shell.execute_reply.started":"2026-05-29T13:24:21.832973Z","shell.execute_reply":"2026-05-29T13:24:21.847616Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Tahmin\n# 5 fold ortalaması\n\ntest_preds = np.zeros(len(x_test))\n\nfor model in models:\n    test_preds += model.predict(x_test) / len(models)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-29T13:23:18.835933Z","iopub.execute_input":"2026-05-29T13:23:18.837395Z","iopub.status.idle":"2026-05-29T13:23:22.340782Z","shell.execute_reply.started":"2026-05-29T13:23:18.837368Z","shell.execute_reply":"2026-05-29T13:23:22.340078Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Submission \n\n\nsubmission = pd.DataFrame({\n    \"row_id\": test[\"row_id\"],\n    \"total_bid\": test_preds\n})\n\n# Aynı row_id varsa ortalama al\nsubmission = (\n    submission\n    .groupby(\"row_id\", as_index=False)[\"total_bid\"]\n    .mean()\n)\n\nprint(submission.shape)\nprint(\n    \"Tekrarlayan row_id sayısı:\",\n    submission[\"row_id\"].duplicated().sum()\n)\n\nsubmission.to_csv(\n    \"submission.csv\",\n    index=False\n)\n\nsubmission.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-29T13:23:22.342002Z","iopub.execute_input":"2026-05-29T13:23:22.342455Z","iopub.status.idle":"2026-05-29T13:23:22.382037Z","shell.execute_reply.started":"2026-05-29T13:23:22.342429Z","shell.execute_reply":"2026-05-29T13:23:22.381328Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 1. Veri Seti Tanımı\n\n*   **Amaç:** İnşaat projeleri için toplam teklif değerlerini tahmin etmek.\n*   **Veri Kaynağı:** Kaggle'dan indirilen \"bidding-predictions-for-construction\" veri seti.\n*   **Sütunlar:**\n    *   `row_id`: Her bir satırı benzersiz olarak tanımlayan ID.\n    *   `job_id`: İnşaat projesini temsil eden ID.\n    *   `bid_date`: Teklifin oluşturulduğu tarih.\n    *   `quantity`: İş miktarı.\n    *   `quantity_mean`: Miktar ortalaması.\n    *   `quantity_std`: Miktar standart sapması.\n    *   `num_pay_items_max`: Maksimum ödeme öğesi sayısı.\n    *   `unit_english_id`: Birim ID'si.\n    *   `category_description`: Kategori açıklaması.\n    *   `total_bid`: Tahmin edilen toplam teklif değeri.\n\n### 2. Veri Ön İşleme ve Özellik Mühendisliği\n\n*   **Veri Temizleme:** Aykırı değerlerin (outlier) temizlenmesi yapılmıştır.\n*   **Tarih Dönüşümü:** `bid_date` sütunundan yıl, ay, gün, hafta içi gibi özellikler çıkarılmıştır.\n*   **Job Bazlı Özellikler:** Her bir `job_id` için, `quantity`, `quantity_mean`, `quantity_std` ve `num_pay_items_max` gibi özellikler hesaplanmıştır.\n*   **Kategori Sütunların Dönüştürülmesi:** Kategorik değişkenler `category` veri türüne dönüştürülmüştür.\n\n### 3. Model Oluşturma ve Eğitimi\n\n*   **Algoritma:** LightGBM (Hızlı ve Etkili Gradient Boosting Algoritması)\n*   **Çapraz Doğrulama:** GroupKFold yöntemi kullanılarak veri setinin 5 farklı gruba ayrılması yapılmıştır. Bu, modelin performansını daha güvenilir bir şekilde değerlendirmemizi sağlar.\n*   **Hiperparametre Optimizasyonu:** Modelin performansı, `eval_metric=\"mae\"` parametresi ile optimize edilmiştir.  `lgb.early_stopping(100)` kullanılarak erken durdurma (early stopping) mekanizması etkinleştirilmiştir.\n\n### 4. Sonuçlar ve Değerlendirme\n\n*   **MAE (Ortalama Mutlak Hata):** [Sonuç MAE değeri]\n*   **R2 Skoru:** [Sonuç R2 skoru değeri]\n*   **Test Set Tahmini:** Model, test verisi üzerinde tahmin yaparak toplam teklif değerlerini tahmin etmiştir.\n\n### 5. Sonraki Adımlar ve İyileştirme Önerileri\n\n*   **Daha Fazla Özellik Mühendisliği:** Daha fazla özellik türetilerek modelin performansı artırılabilir.\n*   **Hiperparametre Optimizasyonu:**  Modelin hiperparametreleri daha detaylı bir şekilde optimize edilebilir (örneğin, grid search veya random search kullanılarak).\n*   **Farklı Algoritmalar Denenmesi:** LightGBM'nin yanı sıra diğer gradient boosting algoritmaları da denenebilir.\n\n---","metadata":{}},{"cell_type":"code","source":"import joblib\n\n# En iyi skoru veren son modeli kaydetmek istersen:\njoblib.dump(models[-1], 'lgbm_bidding_model.pkl')\n\n# 2. Veya tüm fold modellerini bir liste olarak kaydet (Ensemble tahmin için):\njoblib.dump(models, 'all_folds_bidding_models.pkl')\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-29T13:41:09.553468Z","iopub.execute_input":"2026-05-29T13:41:09.554025Z","iopub.status.idle":"2026-05-29T13:41:09.829997Z","shell.execute_reply.started":"2026-05-29T13:41:09.553994Z","shell.execute_reply":"2026-05-29T13:41:09.829176Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}