IlmHamroh
Data Science va sun'iy intellekt/Regressiya9/12-dars20 daqiqa
Mundarija (22)

13.9-dars: Kategoriyalar va belgi muhandisligi

13-QISM — REGRESSIYA · 9-dars


1. Kirish va motivatsiya

Real ma'lumotda belgilar kamdan-kam "tayyor son" bo'ladi: hudud, tarif, kun, oy, mahsulot kodi — bularning hammasi kategoriya, va ularni modelga qanday kiritish natijaga modelni almashtirishdan ko'ra ko'proq ta'sir qiladi. Chiziqli modelda bu ayniqsa muhim: u o'zaro ta'sir va nochiziqlikni o'zi topmaydi 13.5-bob.

Bu darsda: one-hot kodlash va tayanch daraja, ko'p darajali kategoriyalar bilan nima qilish (min_frequency, target encoding), sana/vaqt belgilari va siklik kodlash, agregat belgilar, belgi muhandisligining tartibi va leakagedan qochish 12.9-bob.

Real vaziyat. Taksi xizmati safar narxini bashorat qilmoqchi. Birinchi model: masofa, davomiylik, hudud kodi (1..48 raqam sifatida) — MAE 3 200 so'm. Hudud one-hot qilindi: 2 400. Soat "siklik" (sin/cos) kodlandi va "ish kuni" belgisi qo'shildi: 1 950. Masofa × tirbandlik o'zaro ta'siri: 1 780. Model o'zgarmadi — faqat belgilar o'zgardi.

Bu darsda belgi muhandisligini o'rganamiz.

Bu darsda:

  • Kategoriyalarni kodlash
  • Ko'p darajali kategoriyalar
  • Sana va vaqt belgilari
  • Siklik kodlash
  • Agregat belgilar
  • Tartib va leakage
  • Tuzoqlar
  • Amaliy: taksi narxi

ℹ Misollar real numpy/pandas/sklearn bilan (Python 3.14).


2. Nazariya — chuqur tushuntirish

2.1. Kategoriyalarni kodlash

text
XATO:  hudud → 1, 2, 3, 4      (modelga "4 > 1" va "3 - 2 = 1" deb ko'rinadi)

TO'G'RI:
  one-hot (drop_first)  — nominal kategoriya uchun standart 13.3-bob
  ordinal               — TARTIBLI kategoriya uchun (kam, o'rta, ko'p)
  target/mean encoding  — juda ko'p daraja bo'lsa (CV ichida! — 12.9)
  hashing               — juda ko'p daraja va oqim ma'lumot

OneHotEncoder(handle_unknown="ignore", drop="first", min_frequency=...)

Kategoriyani raqam sifatida berish — eng keng tarqalgan xato: model unga sun'iy tartib va masofa yuklaydi. Chiziqli modelda bu koeffitsiyentni ma'nosiz qiladi. Tartibli kategoriyalarda (kam < o'rta < ko'p) ordinal kodlash to'g'ri, lekin qadamlar teng deb qabul qilinishini eslang.

2.2. Ko'p darajali kategoriyalar

text
Muammo: 3 000 mahsulot kodi → 3 000 ustun (siyrak, overfitting, sekin)

Yechimlar:
  1. min_frequency — kam uchraydiganlarni "boshqa" ga birlashtirish
  2. Guruhlash — domen bilimi bilan (mahsulot → kategoriya)
  3. Target encoding — daraja o'rtachasi bilan almashtirish (silliqlash bilan, CV ichida)
  4. Frequency encoding — uchrash chastotasi
  5. Embedding — neyron tarmoqlarda (25-qism)

Ko'p darajali kategoriya (high cardinality) — amaliyotdagi tipik muammo. Eng xavfsiz birinchi qadam — min_frequency bilan kam uchraydiganlarni birlashtirish; keyin domen bo'yicha guruhlash. Target encoding kuchli, lekin leakage manbai: u faqat pipeline/CV ichida va silliqlash bilan qilinadi 12.9-bob.

2.3. Sana va vaqt belgilari

text
Sanadan chiqariladigan belgilar:
  yil, oy, kun, hafta kuni, yil kuni, hafta raqami
  ish kuni / dam olish, bayram
  oy boshi/oxiri, chorak
  "boshlanishdan o'tgan kunlar" (trend uchun)

pandas: df["sana"].dt.dayofweek, .dt.month, .dt.is_month_end
Diqqat: vaqt qatorida bu belgilar LEAKAGE manbai bo'lishi mumkin (12.3)

Sana — eng boy belgi manbai: bitta ustundan o'nlab foydali belgi chiqadi. Lekin tanlov domendan kelib chiqishi kerak: chakana savdoda hafta kuni va bayramlar, energiya iste'molida soat va harorat, moliyada chorak oxiri muhim.

2.4. Siklik kodlash

text
Muammo: soat 23 va soat 0 — qo'shni, lekin son sifatida eng uzoq
         oy 12 va oy 1 ham shunday

Yechim:
  sin_soat = sin(2pi · soat / 24)
  cos_soat = cos(2pi · soat / 24)
  → doira bo'ylab joylashadi, 23 va 0 yaqin bo'ladi

Muqobil: soatni KATEGORIYA sifatida (24 ustun) yoki splayn (13.5)

Siklik kodlash — vaqt belgilarida standart usul: ikki ustun (sin, cos) davriy tuzilmani saqlaydi. Lekin u silliq shaklni taxmin qiladi; agar naqsh keskin bo'lsa (masalan, ish vaqti 9:00 da keskin boshlansa), kategoriya yoki splayn yaxshiroq ishlaydi.

2.5. Agregat belgilar

text
Guruh bo'yicha statistika: mijozning o'rtacha cheki, hududning median narxi,
                            oxirgi 7 kundagi o'rtacha talab

Kuchli, lekin LEAKAGE xavfi yuqori:
  · agregat maqsadni o'z ichiga olmasligi kerak 12.9-bob
  · vaqt kesimidan OLDINGI ma'lumotdan hisoblanishi kerak 12.2-bob
  · CV ichida qayta hisoblanishi kerak

Xavfsiz variant: agregatni O'QUV qismida hisoblab, test'ga xarita sifatida qo'llash

Agregat belgilar ko'pincha eng katta foyda beradi (ayniqsa, tranzaksion ma'lumotda), lekin ular leakagening eng ko'p uchraydigan manbai. Oltin qoida: agregat faqat o'tmish ma'lumotidan va faqat o'quv qismidan hisoblanadi (12.2, 12.9).

2.6. Tartib va amaliyot

text
1. Domen savoli: qanday belgi mantiqan foydali?
2. Oddiy belgilardan boshlash (baza — 12.6)
3. Har qo'shimchani CV bilan tekshirish 12.3-bob
4. Hammasi PIPELINE ichida 12.9-bob
5. Belgi sonini nazorat qilish (n >> p — 13.2)
6. Hujjatlashtirish: har belgi manbasi va ma'nosi

Regressiyada tipik to'plam:
  sonli (transformatsiya bilan) + one-hot kategoriya + sana + tanlangan o'zaro ta'sirlar

Belgi muhandisligi — iterativ jarayon: bir nechta belgi qo'shib, CV bilan tekshirib, foydasizlarini olib tashlash. Har qadamda leakage savolini bering: "bu belgi bashorat paytida mavjudmi?" 12.2-bob. Va har doim pipeline ichida 12.9-bob — aks holda CV natijasi optimistik bo'ladi.

2.7. Tuzoqlar

Asosiy tuzoqlar: kategoriyani raqam sifatida berish; handle_unknown ni qo'ymaslik; dummy tuzog'i (barcha darajalar + kesma); ko'p darajali kategoriyani to'g'ridan-to'g'ri one-hot qilish; target encoding ni CV dan tashqarida (leakage — 12.9); sana belgilarini kelajakdan olish; agregatni butun ma'lumotdan hisoblash; siklik naqshni chiziqli qoldirish; belgi sonini nazoratsiz oshirish 13.2-bob.

2.8. Belgilar — modeldan muhimroq

Chiziqli modelda natijani ko'pincha belgilar belgilaydi: kategoriyalar one-hot (raqam sifatida emas), ko'p darajalilar min_frequency yoki guruhlash bilan, sana dan hafta kuni/oy/bayram belgilari, davriy qiymatlar uchun siklik (sin/cos) kodlash, tranzaksiyalardan agregatlar. Ikki qoida o'zgarmaydi: hammasi pipeline ichida va har belgi uchun "bashorat paytida mavjudmi?" savoli (12.2, 12.9). Keyingi dars — logistik regressiya.


3. Tez ma'lumotnoma

python
import numpy as np
import pandas as pd
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import FunctionTransformer, OneHotEncoder, StandardScaler

OneHotEncoder(handle_unknown="ignore", drop="first", min_frequency=30)

# sana belgilari
d = df["sana"].dt
df["hafta_kuni"], df["oy"], df["ish_kuni"] = d.dayofweek, d.month, (d.dayofweek < 5)

# siklik
df["sin_soat"] = np.sin(2 * np.pi * df["soat"] / 24)
df["cos_soat"] = np.cos(2 * np.pi * df["soat"] / 24)

# agregat (faqat o'quvda hisoblanadi!)
xarita = tr.groupby("hudud")["narx"].median()
df["hudud_median"] = df["hudud"].map(xarita).fillna(tr["narx"].median())
QOIDA: kategoriya ≠ raqam · min_frequency · siklik kodlash · agregat o'tmishdan · pipeline

Belgilar xulosasi

One-hot (drop_first, handle_unknown) · ordinal faqat tartibli uchun
Ko'p daraja: min_frequency → guruhlash → target encoding (CV ichida)
Sana: hafta kuni, oy, bayram, trend · Siklik: sin/cos
Agregat: o'tmishdan va o'quvdan · Hammasi pipeline ichida

4. Batafsil misollar

Misollar real numpy/pandas/sklearn bilan (Python 3.14). Misollar bitta taksi ma'lumotidan foydalanadi.

Misol 1 — Kategoriya: raqam va one-hot

python
"""Kategoriyani raqam sifatida berish nega xato (real pandas/sklearn)."""

import numpy as np
import pandas as pd
from sklearn.compose import ColumnTransformer
from sklearn.linear_model import Ridge
from sklearn.model_selection import KFold, cross_val_score
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder, StandardScaler


def yarat(seed: int = 5, n: int = 6000) -> pd.DataFrame:
    rng = np.random.default_rng(seed)
    hudud = rng.integers(0, 12, n)                        # 12 hudud
    hudud_qosh = np.array([0, 5200, -1800, 900, 3100, -2400,
                           400, 6100, -900, 2200, 1500, -3000])
    masofa = rng.gamma(3, 2.2, n).clip(0.5, 40)
    tirband = rng.beta(2, 3, n)
    soat = rng.integers(0, 24, n)
    narx = (4000 + 2100 * masofa + 9000 * tirband + hudud_qosh[hudud]
            + rng.normal(0, 1500, n))
    return pd.DataFrame({"hudud": hudud, "masofa": masofa, "tirband": tirband,
                         "soat": soat, "narx": narx})


def main() -> None:
    df = yarat()
    y = df["narx"]
    cv = KFold(5, shuffle=True, random_state=0)

    def mae(X, model) -> float:
        return -cross_val_score(model, X, y, cv=cv,
                                scoring="neg_mean_absolute_error").mean()

    print("=== 1. Hudud — raqam sifatida ===")
    X1 = df[["masofa", "tirband", "hudud"]]
    print(f"  CV MAE = {mae(X1, Ridge(alpha=1.0)):.1f} so'm")
    m = Ridge(alpha=1.0).fit(X1, y)
    print(f"  hudud koeffitsiyenti = {m.coef_[2]:.1f} "
          f"(modelga 'hudud 11 — hudud 1 dan 10 barobar ko'p' deb ko'rinadi)")

    print("\n=== 2. Hudud — one-hot ===")
    tayyor = ColumnTransformer([
        ("son", StandardScaler(), ["masofa", "tirband"]),
        ("kat", OneHotEncoder(handle_unknown="ignore", drop="first"), ["hudud"]),
    ])
    X2 = df[["masofa", "tirband", "hudud"]]
    quvur = Pipeline([("t", tayyor), ("m", Ridge(alpha=1.0))])
    print(f"  CV MAE = {mae(X2, quvur):.1f} so'm")

    print("\n=== 3. Hududsiz model (taqqoslash uchun) ===")
    X0 = df[["masofa", "tirband"]]
    print(f"  CV MAE = {mae(X0, Ridge(alpha=1.0)):.1f} so'm")

    print("\n=== 4. Hudud qo'shimchalari ===")
    quvur.fit(X2, y)
    nomlar = quvur.named_steps["t"].get_feature_names_out()
    w = quvur.named_steps["m"].coef_
    for nom, v in list(zip(nomlar, w))[2:7]:
        print(f"  {nom:<16}: {v:+8.1f}")
    print("  (hudud 0 — tayanch; haqiqiy qo'shimchalar: 5200, -1800, 900, 3100, -2400)")
    print("  ⭐ Kategoriya raqam emas")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Hudud — raqam sifatida ===
  CV MAE = 2541.5 so'm
  hudud koeffitsiyenti = -131.1 (modelga 'hudud 11 — hudud 1 dan 10 barobar ko'p' deb ko'rinadi)

=== 2. Hudud — one-hot ===
  CV MAE = 1191.7 so'm

=== 3. Hududsiz model (taqqoslash uchun) ===
  CV MAE = 2543.2 so'm

=== 4. Hudud qo'shimchalari ===
  kat__hudud_1    :  +5214.5
  kat__hudud_2    :  -1741.7
  kat__hudud_3    :  +1030.1
  kat__hudud_4    :  +3118.6
  kat__hudud_5    :  -2363.3
  (hudud 0 — tayanch; haqiqiy qo'shimchalar: 5200, -1800, 900, 3100, -2400)
  ⭐ Kategoriya raqam emas

Nima ko'rsatdi: 2.1-bo'lim.

Misol 2 — Siklik kodlash

python
"""Soat va oy kabi davriy belgilar (real numpy/pandas/sklearn)."""

import numpy as np
import pandas as pd
from sklearn.compose import ColumnTransformer
from sklearn.linear_model import Ridge
from sklearn.model_selection import KFold, cross_val_score
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder, StandardScaler


def yarat(seed: int = 9, n: int = 6000) -> pd.DataFrame:
    rng = np.random.default_rng(seed)
    soat = rng.integers(0, 24, n)
    masofa = rng.gamma(3, 2.2, n).clip(0.5, 40)
    # tungi tarif: 23:00-05:00 oralig'ida qimmat (0 va 23 qo'shni!)
    tungi = np.sin(2 * np.pi * (soat + 6) / 24)
    narx = 4000 + 2100 * masofa + 2600 * tungi + rng.normal(0, 1200, n)
    return pd.DataFrame({"soat": soat, "masofa": masofa, "narx": narx})


def main() -> None:
    df = yarat()
    y = df["narx"]
    cv = KFold(5, shuffle=True, random_state=0)

    def mae(X, model=None) -> float:
        model = model or Ridge(alpha=1.0)
        return -cross_val_score(model, X, y, cv=cv,
                                scoring="neg_mean_absolute_error").mean()

    print("=== 1. Soat — oddiy son ===")
    print(f"  CV MAE = {mae(df[['masofa', 'soat']]):.1f} so'm")
    print(f"  corr(soat, narx) = {df['soat'].corr(df['narx']):.3f} "
          f"(chiziqli bog'liqlik deyarli yo'q)")

    print("\n=== 2. Siklik kodlash (sin/cos) ===")
    X2 = df[["masofa"]].copy()
    X2["sin"] = np.sin(2 * np.pi * df["soat"] / 24)
    X2["cos"] = np.cos(2 * np.pi * df["soat"] / 24)
    print(f"  CV MAE = {mae(X2):.1f} so'm  (2 ta qo'shimcha ustun)")

    print("\n=== 3. Soat — kategoriya (24 ustun) ===")
    tayyor = ColumnTransformer([
        ("son", StandardScaler(), ["masofa"]),
        ("kat", OneHotEncoder(handle_unknown="ignore", drop="first"), ["soat"]),
    ])
    quvur = Pipeline([("t", tayyor), ("m", Ridge(alpha=1.0))])
    print(f"  CV MAE = {mae(df[['masofa', 'soat']], quvur):.1f} so'm "
          f"(23 ta qo'shimcha ustun)")

    print("\n=== 4. 23 va 0 soat yaqinmi ===")
    for s in [0, 1, 22, 23]:
        print(f"  soat {s:>2}: sin = {np.sin(2 * np.pi * s / 24):+.3f}, "
              f"cos = {np.cos(2 * np.pi * s / 24):+.3f}")
    masofa_son = abs(23 - 0)
    d_sin = np.hypot(np.sin(2 * np.pi * 23 / 24) - np.sin(0),
                     np.cos(2 * np.pi * 23 / 24) - np.cos(0))
    print(f"  son sifatida 23 va 0 orasidagi masofa: {masofa_son}")
    print(f"  siklik kodlashda: {d_sin:.3f} (1 va 0 orasidagiga teng)")
    print("  ⭐ Davriy belgilar sin/cos bilan kodlanadi")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Soat — oddiy son ===
  CV MAE = 1816.7 so'm
  corr(soat, narx) = -0.022 (chiziqli bog'liqlik deyarli yo'q)

=== 2. Siklik kodlash (sin/cos) ===
  CV MAE = 958.3 so'm  (2 ta qo'shimcha ustun)

=== 3. Soat — kategoriya (24 ustun) ===
  CV MAE = 964.5 so'm (23 ta qo'shimcha ustun)

=== 4. 23 va 0 soat yaqinmi ===
  soat  0: sin = +0.000, cos = +1.000
  soat  1: sin = +0.259, cos = +0.966
  soat 22: sin = -0.500, cos = +0.866
  soat 23: sin = -0.259, cos = +0.966
  son sifatida 23 va 0 orasidagi masofa: 23
  siklik kodlashda: 0.261 (1 va 0 orasidagiga teng)
  ⭐ Davriy belgilar sin/cos bilan kodlanadi

Nima ko'rsatdi: 2.4-bo'lim.

Misol 3 — Ko'p darajali kategoriya

python
"""min_frequency va target encoding (real pandas/sklearn)."""

import warnings

import numpy as np
import pandas as pd
from sklearn.base import BaseEstimator, TransformerMixin
from sklearn.compose import ColumnTransformer
from sklearn.linear_model import Ridge
from sklearn.model_selection import KFold, cross_val_score
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder, StandardScaler


class TargetEncoder(BaseEstimator, TransformerMixin):
    """Kategoriyani maqsad o'rtachasi bilan (silliqlash bilan) almashtiradi."""

    def __init__(self, silliq: float = 20.0):
        self.silliq = silliq

    def fit(self, X, y):
        y = np.asarray(y, dtype=float)
        k = np.asarray(X).ravel()
        d = pd.DataFrame({"k": k, "y": y}).groupby("k")["y"]
        n, o = d.count(), d.mean()
        self.umumiy_ = float(y.mean())
        self.xarita_ = ((n * o + self.silliq * self.umumiy_)
                        / (n + self.silliq)).to_dict()
        return self

    def transform(self, X):
        k = pd.Series(np.asarray(X).ravel())
        return k.map(self.xarita_).fillna(self.umumiy_).to_numpy().reshape(-1, 1)


def yarat(seed: int = 12, n: int = 8000) -> pd.DataFrame:
    rng = np.random.default_rng(seed)
    # 400 ta marshrut kodi, chastotasi juda notekis
    ogirlik = rng.pareto(1.2, 400) + 1
    kod = rng.choice(400, n, p=ogirlik / ogirlik.sum())
    kod_qosh = rng.normal(0, 2200, 400)
    masofa = rng.gamma(3, 2.2, n).clip(0.5, 40)
    narx = 4000 + 2100 * masofa + kod_qosh[kod] + rng.normal(0, 1400, n)
    return pd.DataFrame({"kod": kod.astype(str), "masofa": masofa, "narx": narx})


def main() -> None:
    df = yarat()
    y = df["narx"]
    X = df[["masofa", "kod"]]
    cv = KFold(5, shuffle=True, random_state=0)

    def mae(model) -> float:
        # CV bo'laklarida ko'rilmagan kodlar uchun ogohlantirish chiqadi —
        # handle_unknown="ignore" ularni nol ustun sifatida kodlaydi
        with warnings.catch_warnings():
            warnings.simplefilter("ignore")
            return -cross_val_score(model, X, y, cv=cv,
                                    scoring="neg_mean_absolute_error").mean()

    print("=== 1. Ma'lumot ===")
    chastota = df["kod"].value_counts()
    print(f"  {df['kod'].nunique()} ta marshrut kodi, {len(df)} qator")
    print(f"  eng ko'p uchraydigani: {chastota.iloc[0]} marta, "
          f"mediana: {chastota.median():.0f}, bir marta uchraydigan: "
          f"{(chastota == 1).sum()} ta")

    print("\n=== 2. Kodsiz baza ===")
    baza = Pipeline([
        ("t", ColumnTransformer([("son", StandardScaler(), ["masofa"])],
                                remainder="drop")),
        ("m", Ridge(alpha=1.0)),
    ])
    print(f"  CV MAE = {mae(baza):.1f} so'm")

    print("\n=== 3. To'liq one-hot va min_frequency ===")
    for mf in [None, 5, 20, 100]:
        tayyor = ColumnTransformer([
            ("son", StandardScaler(), ["masofa"]),
            ("kat", OneHotEncoder(handle_unknown="ignore", drop="first",
                                  min_frequency=mf), ["kod"]),
        ])
        quvur = Pipeline([("t", tayyor), ("m", Ridge(alpha=1.0))])
        quvur.fit(X, y)
        ustun = len(quvur.named_steps["t"].get_feature_names_out())
        nom = "yo'q" if mf is None else str(mf)
        print(f"  min_frequency {nom:>4}: {ustun:>4} ustun, CV MAE {mae(quvur):7.1f}")

    print("\n=== 4. Target encoding (pipeline ichida) ===")
    te_natija = {}
    for silliq in [1, 10, 50]:
        tayyor = ColumnTransformer([
            ("son", StandardScaler(), ["masofa"]),
            ("te", TargetEncoder(silliq=silliq), ["kod"]),
        ])
        quvur = Pipeline([("t", tayyor), ("m", Ridge(alpha=1.0))])
        te_natija[silliq] = mae(quvur)
        print(f"  silliqlash {silliq:>3}: 2 ustun, CV MAE {te_natija[silliq]:7.1f}")

    baza_mae = mae(baza)
    eng_te = min(te_natija.values())
    tayyor = ColumnTransformer([
        ("son", StandardScaler(), ["masofa"]),
        ("kat", OneHotEncoder(handle_unknown="ignore", drop="first"), ["kod"]),
    ])
    toliq = mae(Pipeline([("t", tayyor), ("m", Ridge(alpha=1.0))]))
    print(f"\n  bazadan yaxshilanish: to'liq one-hot (396 ustun) "
          f"{(baza_mae - toliq) / baza_mae:.0%}, "
          f"target encoding (2 ustun) {(baza_mae - eng_te) / baza_mae:.0%}")
    print("  ⭐ Ma'lumot ko'p bo'lsa to'liq one-hot aniqroq;")
    print("     target encoding foydaning katta qismini 2 ustunda beradi")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Ma'lumot ===
  396 ta marshrut kodi, 8000 qator
  eng ko'p uchraydigani: 2368 marta, mediana: 7, bir marta uchraydigan: 12 ta

=== 2. Kodsiz baza ===
  CV MAE = 1760.6 so'm

=== 3. To'liq one-hot va min_frequency ===
  min_frequency yo'q:  396 ustun, CV MAE  1173.6
  min_frequency    5:  289 ustun, CV MAE  1244.8
  min_frequency   20:   58 ustun, CV MAE  1448.2
  min_frequency  100:    8 ustun, CV MAE  1656.6

=== 4. Target encoding (pipeline ichida) ===
  silliqlash   1: 2 ustun, CV MAE  1537.2
  silliqlash  10: 2 ustun, CV MAE  1458.3
  silliqlash  50: 2 ustun, CV MAE  1456.8

  bazadan yaxshilanish: to'liq one-hot (396 ustun) 33%, target encoding (2 ustun) 17%
  ⭐ Ma'lumot ko'p bo'lsa to'liq one-hot aniqroq;
     target encoding foydaning katta qismini 2 ustunda beradi

Nima ko'rsatdi: 2.2-bo'lim.

Misol 4 — Sana belgilari va agregatlar

python
"""Sanadan belgilar va leakage'siz agregat (real pandas/sklearn)."""

import numpy as np
import pandas as pd
from sklearn.compose import ColumnTransformer
from sklearn.linear_model import Ridge
from sklearn.metrics import mean_absolute_error
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder, StandardScaler


def yarat(seed: int = 15, n: int = 9000) -> pd.DataFrame:
    rng = np.random.default_rng(seed)
    sana = pd.Timestamp("2024-01-01") + pd.to_timedelta(rng.integers(0, 365, n), "D")
    soat = rng.integers(0, 24, n)
    hudud = rng.choice(["markaz", "shimol", "janub", "chekka"], n,
                       p=[0.4, 0.25, 0.2, 0.15])
    masofa = rng.gamma(3, 2.2, n).clip(0.5, 40)
    hafta_kuni = sana.dayofweek
    dam = (hafta_kuni >= 5).astype(float)
    tungi = np.sin(2 * np.pi * (soat + 6) / 24)
    hudud_qosh = pd.Series(hudud).map({"markaz": 3000.0, "shimol": 500.0,
                                       "janub": -400.0, "chekka": -2200.0}).to_numpy()
    narx = (4000 + 2100 * masofa + 2600 * tungi + 1800 * dam + hudud_qosh
            + rng.normal(0, 1300, n))
    return pd.DataFrame({"sana": sana, "soat": soat, "hudud": hudud,
                         "masofa": masofa, "narx": narx})


def belgilar(df: pd.DataFrame, xarita: dict | None = None,
             umumiy: float = 0.0) -> pd.DataFrame:
    X = pd.DataFrame(index=df.index)
    X["masofa"] = df["masofa"]
    X["sin_soat"] = np.sin(2 * np.pi * df["soat"] / 24)
    X["cos_soat"] = np.cos(2 * np.pi * df["soat"] / 24)
    X["hafta_kuni"] = df["sana"].dt.dayofweek
    X["dam_olish"] = (df["sana"].dt.dayofweek >= 5).astype(float)
    X["oy"] = df["sana"].dt.month
    X["hudud"] = df["hudud"]
    if xarita is not None:
        X["hudud_median"] = df["hudud"].map(xarita).fillna(umumiy)
    return X


def main() -> None:
    df = yarat()
    chegara = pd.Timestamp("2024-10-01")
    tr, te = df[df["sana"] < chegara], df[df["sana"] >= chegara]
    print("=== 1. Vaqt bo'yicha ajratish ===")
    print(f"  o'quv {len(tr)} qator, test {len(te)} qator")

    def quvur(X: pd.DataFrame) -> Pipeline:
        son = [c for c in X.columns if c != "hudud"]
        qadamlar = [("son", StandardScaler(), son)]
        if "hudud" in X.columns:
            qadamlar.append(("kat", OneHotEncoder(handle_unknown="ignore",
                                                  drop="first"), ["hudud"]))
        return Pipeline([("t", ColumnTransformer(qadamlar)),
                         ("m", Ridge(alpha=1.0))])

    print("\n=== 2. Belgilar to'plamlari ===")
    natija = {}
    toplamlar = {
        "faqat masofa": ["masofa"],
        "+ hudud": ["masofa", "hudud"],
        "+ siklik soat": ["masofa", "hudud", "sin_soat", "cos_soat"],
        "+ sana belgilari": ["masofa", "hudud", "sin_soat", "cos_soat",
                             "hafta_kuni", "dam_olish", "oy"],
    }
    Xtr_full, Xte_full = belgilar(tr), belgilar(te)
    for nom, ustunlar in toplamlar.items():
        Xtr, Xte = Xtr_full[ustunlar], Xte_full[ustunlar]
        m = quvur(Xtr).fit(Xtr, tr["narx"])
        natija[nom] = mean_absolute_error(te["narx"], m.predict(Xte))
        print(f"  {nom:<18}: test MAE {natija[nom]:7.1f} so'm")

    print("\n=== 3. Agregat belgi (faqat o'quvdan hisoblanadi) ===")
    xarita = tr.groupby("hudud")["narx"].median().to_dict()
    umumiy = float(tr["narx"].median())
    Xtr2 = belgilar(tr, xarita, umumiy)
    Xte2 = belgilar(te, xarita, umumiy)
    m = quvur(Xtr2).fit(Xtr2, tr["narx"])
    print(f"  + hudud median narxi: test MAE "
          f"{mean_absolute_error(te['narx'], m.predict(Xte2)):7.1f} so'm")
    print(f"  xarita: {({k: round(v) for k, v in xarita.items()})}")

    print("\n=== 4. Leakage tekshiruvi ===")
    xato_xarita = df.groupby("hudud")["narx"].median().to_dict()   # BUTUN ma'lumot
    farq = {k: round(xato_xarita[k] - xarita[k]) for k in xarita}
    print(f"  butun ma'lumotdan hisoblangan xarita farqi: {farq}")
    print("  agregat o'quvdan hisoblanishi kerak — aks holda test ma'lumoti sizadi")
    print("  ⭐ Belgi muhandisligi natijani modeldan ko'ra ko'proq o'zgartiradi")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Vaqt bo'yicha ajratish ===
  o'quv 6768 qator, test 2232 qator

=== 2. Belgilar to'plamlari ===
  faqat masofa      : test MAE  2465.4 so'm
  + hudud           : test MAE  1993.5 so'm
  + siklik soat     : test MAE  1227.9 so'm
  + sana belgilari  : test MAE  1052.0 so'm

=== 3. Agregat belgi (faqat o'quvdan hisoblanadi) ===
  + hudud median narxi: test MAE  1052.1 so'm
  xarita: {'chekka': 14565, 'janub': 16415, 'markaz': 20292, 'shimol': 17740}

=== 4. Leakage tekshiruvi ===
  butun ma'lumotdan hisoblangan xarita farqi: {'chekka': 384, 'janub': -74, 'markaz': -29, 'shimol': -79}
  agregat o'quvdan hisoblanishi kerak — aks holda test ma'lumoti sizadi
  ⭐ Belgi muhandisligi natijani modeldan ko'ra ko'proq o'zgartiradi

Nima ko'rsatdi: 2.3, 2.5-bo'limlar.


5. To'g'ri va noto'g'ri tushunishlar

Noto'g'ri fikr To'g'risi
"Kategoriyani raqam qilish mumkin" Sun'iy tartib paydo bo'ladi
"One-hot har doim yaxshi" Ko'p darajada muammo
"Target encoding oddiy" CV ichida bo'lishi shart
"Soat — oddiy son" Davriy (sin/cos)
"Agregat belgi zararsiz" Leakage manbai
"Ko'p belgi — yaxshi" n >> p
"Sana bitta belgi" O'nlab belgi manbai
"Belgilar model tanlaganidan keyin" Avval belgilar

6. Keng tarqalgan xatolar va yechimlari

1. Kategoriya raqam sifatida

python
X["hudud"] = df["hudud"].astype(int)                              # ⚠️
OneHotEncoder(handle_unknown="ignore", drop="first")              # ✅

2. handle_unknown yo'q

python
OneHotEncoder(drop="first")            # test'da yangi daraja     # ⚠️
OneHotEncoder(drop="first", handle_unknown="infrequent_if_exist") # ✅

3. Ko'p darajani to'g'ridan-to'g'ri one-hot

python
pd.get_dummies(df["mahsulot"])         # 3000 ustun               # ⚠️
OneHotEncoder(min_frequency=30)        # yoki target encoding     # ✅

4. Target encoding CV dan tashqarida

python
df["kod_te"] = df.groupby("kod")["narx"].transform("mean")        # ⚠️
Pipeline([("te", TargetEncoder()), ("m", Ridge())])               # ✅

5. Siklik belgini chiziqli qoldirish

python
X["soat"] = df["soat"]                                            # ⚠️
X["sin"], X["cos"] = np.sin(2 * np.pi * s / 24), np.cos(...)      # ✅

6. Agregatni butun ma'lumotdan

python
xarita = df.groupby("hudud")["narx"].median()                     # ⚠️
xarita = tr.groupby("hudud")["narx"].median()                     # ✅

7. Kelajakdagi sana belgilari

python
X["oy_oxiri_savdo"] = ...              # kesimdan keyin ma'lum    # ⚠️
# faqat kesimgacha mavjud belgilar 12.2-bob                         # ✅

7. Integratsiya — bu bilim qayerda kerak bo'ladi

  • 12.9-dars (o'tilgan): Pipeline va leakage
  • 13.3-dars (o'tilgan): Kategoriya koeffitsiyentlari
  • 13.5-dars (o'tilgan): O'zaro ta'sirlar
  • 13.12-dars: To'liq loyiha
  • Feature engineering qismi: Kengaytirilgan usullar

8. Eng yaxshi amaliyotlar

  1. Kategoriyani hech qachon raqam qilib bermang.

  2. handle_unknown ni har doim qo'ying.

  3. Ko'p darajada min_frequency dan boshlang.

  4. Target encoding faqat pipeline ichida.

  5. Davriy belgilarni siklik kodlang.

  6. Agregatni o'quvdan hisoblang.

  7. Har belgini CV bilan tekshiring.

  8. Belgilarni hujjatlashtiring.


9. Amaliy topshiriq

Vazifa 1: Bashorat qiling

python
1.  # kategoriyani raqam qilish nega xato?
2.  # one-hot da drop_first nima uchun?
3.  # handle_unknown nima qiladi?
4.  # min_frequency nima?
5.  # target encoding qayerda bajariladi?
6.  # silliqlash nima uchun?
7.  # sanadan qanday belgilar?
8.  # siklik kodlash formulasi?
9.  # nega siklik kerak?
10. # agregat belgi qayerdan hisoblanadi?
11. # ordinal kodlash qachon?
12. # belgi qo'shishni qanday tekshirish?
Javoblar
  1. Sun'iy tartib va masofa
  2. Dummy tuzog'i
  3. Yangi darajada xato bermaydi
  4. Kam uchraydiganlarni birlashtiradi
  5. Pipeline/CV ichida
  6. Kam kuzatuvli darajalar uchun
  7. Hafta kuni, oy, bayram, trend
  8. sin(2pix/T), cos(2pix/T)
  9. 23 va 0 qo'shni
  10. O'quv qismidan, o'tmishdan
  11. Tartibli kategoriyada
  12. CV bilan

Vazifa 2: Xatolarni tuzating

python
1.  X["hudud"] = df["hudud"].astype(int)

2.  pd.get_dummies(df["mahsulot"])   # 5000 daraja

3.  df["kod_te"] = df.groupby("kod")["narx"].transform("mean")

4.  X["oy"] = df["sana"].dt.month   # faqat shu

5.  xarita = df.groupby("hudud")["narx"].mean()   # butun ma'lumot
Javoblar
python
1.  OneHotEncoder(handle_unknown="ignore", drop="first")

2.  OneHotEncoder(min_frequency=30, handle_unknown="infrequent_if_exist")

3.  Pipeline([("te", TargetEncoder()), ("m", Ridge())])

4.  # sin/cos oy, hafta kuni, bayram ham qo'shing

5.  xarita = tr.groupby("hudud")["narx"].mean()

Vazifa 3: Kodlash

Modellang:

  1. Raqam va one-hot
  2. CV taqqoslash
  3. Koeffitsiyentlar
  4. Xulosa

Vazifa 4: Siklik

Modellang:

  1. Davriy naqsh
  2. Uch yondashuv
  3. Masofalar
  4. Tanlov

Vazifa 5: Ko'p daraja

Modellang:

  1. 500 daraja
  2. min_frequency
  3. Target encoding
  4. Taqqoslash

Vazifa 6: Sana va agregat

Modellang:

  1. Sana belgilari
  2. Agregat
  3. Leakage tekshiruvi
  4. Yakuniy to'plam

Vazifa 7: O'ylash

"Belgi muhandisligi o'ladi — chuqur o'rganish belgilarni o'zi topadi" degan fikr keng tarqalgan. Bu jadval ma'lumotiga ham tegishlimi?

Javob

Qisqa javob: rasm, matn va audioda bu deyarli to'g'ri — tarmoqlar xom ma'lumotdan ierarxik belgilarni o'zi o'rganadi. Jadval ma'lumotida esa hali emas: domen belgilari (agregatlar, sana, o'zaro ta'sirlar) hali ham eng katta foyda manbai.

1. Nega jadvalda boshqacha

Sabab Izoh
Tuzilma yo'q Rasmda piksel qo'shnilari bor, jadvalda ustun tartibi ma'nosiz
Ma'lumot kam Tarmoq uchun yetarli emas
Domen bilimi tashqarida "Oxirgi 7 kun o'rtachasi" ma'lumotda yo'q — uni siz yaratasiz
Gradient boosting kuchli Jadvalda hali ham lider

2. Nima o'zgarmoqda

  • Avtomatik belgi yaratish vositalari (featuretools va o'xshashlari)
  • Jadval uchun transformer arxitekturalari (ba'zi hollarda raqobatbardosh)
  • Target/embedding kodlash neyron tarmoqlarda

3. Nima o'zgarmaydi

  • Agregatlarning vaqt kesimi (leakage nazorati) — 12.2
  • Domen mantiqi (nima o'lchanadi va qachon mavjud)
  • Belgi hujjatlari va barqarorligi

4. Amaliy tavsiya

  1. Jadvalda: oddiy belgilar + boosting dan boshlang
  2. Domen belgilariga vaqt sarflang — eng yuqori qaytim
  3. Avtomatik vositalarni qo'shimcha sifatida ishlating
  4. Har belgini leakage bo'yicha tekshiring

5. Xulosa

  1. Tuzilmali ma'lumotda avtomatik belgi ishlaydi
  2. Jadvalda domen belgilari yetakchi
  3. Leakage nazorati avtomatlashmaydi
  4. Belgi muhandisligi yaqin kelajakda saqlanadi

Nimani mustahkamlaydi: 2.5, 2.6-bo'limlar.


Xulosa

Bu darsda kategoriyalar va belgi muhandisligini o'rgandik.

Eng muhim uch fikr:

  1. Kategoriya — raqam emas. Hududni 1..12 sifatida berish modelga sun'iy tartib va masofa yuklaydi. To'g'ri yo'l: nominal kategoriya uchun one-hot (drop_first, handle_unknown), tartibli uchun ordinal. Ko'p darajali kategoriyada (yuzlab/minglab) avval min_frequency bilan kam uchraydiganlarni birlashtiring, keyin domen bo'yicha guruhlang; target encoding kuchli, lekin faqat pipeline/CV ichida va silliqlash bilan 12.9-bob.

  2. Vaqt belgilari — eng boy manba. Bitta sanadan hafta kuni, oy, bayram, dam olish, trend belgilari chiqadi. Davriy qiymatlar (soat, oy, hafta kuni) uchun siklik kodlash (sin, cos) kerak — aks holda 23 va 0 soat modelga "eng uzoq" bo'lib ko'rinadi.

  3. Agregatlar — eng katta foyda va eng katta xavf. Guruh statistikalari (hudud medianasi, mijozning o'rtacha cheki, oxirgi 7 kun o'rtachasi) kuchli belgilar, lekin ular leakagening asosiy manbai: faqat o'tmish ma'lumotidan va faqat o'quv qismidan hisoblanishi kerak 12.2-bob. Umumiy qoida: hamma narsa pipeline ichida, har belgi uchun "bashorat paytida mavjudmi?" savoli.

Keyingi darsda logistik regressiyani o'rganamiz: chiziqli modelni klassifikatsiyaga o'tkazish, sigmoid, log-loss va koeffitsiyentlarni odds ratio sifatida talqin qilish.

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
13.9-dars: Kategoriyalar va belgi muhandisligi — IlmHamroh