Asosiy qismga o'tish

Tozalash: iflos ma'lumotdan ishonchli jadvalgacha

18-bob 13 kod bloki 2477 so'z

Ishning 30 foizi

Hozirgacha orders.csv toza edi: turlar to'g'ri, nomlar bir xil, dublikat yo'q. Real fayl bunday emas. iflos_savdo.csv — o'sha do'konning birinchi yarim yillik savdosi, lekin "hayotdagidek": uch xil sana formati, hudud nomi besh xil yozilgan, narx matn sifatida, bo'sh kataklar, takrorlangan qatorlar, va bir nechta aniq xato. Vazifa: uni tozalab, ishonchli jadval olish va har qadamda nechta qator o'zgarganini bilish.

python3 qator
iflos = pd.read_csv(DATA / "iflos_savdo.csv")
print(iflos.shape)
show(iflos.sample(6, random_state=3))
Chiqish
(6180, 9)
item_idorder_idorder_dateregioncategoryproduct_namequantityunit_pricestatus
19 5389 8682025-02-02Тошкент шаҳриElektronikaNoutbuk Lenovo IdeaPad18 580 000delivered
18 0379 06306/06/25XorazmUy-ro'zg'orTozalash vositasi228000delivered
37 10218 72522.03.2025Toshkent shahriUy-ro'zg'orKastryulka 5L1149 000delivered
26 07113 16111/03/25Toshkent viloyatiOziq-ovqatUn 10kg195000delivered
32 25916 2982025-05-24Toshkent shahriKiyimFutbolka184 000delivered
27 88914 0682025-03-28QashqadaryoKiyimKepka170000delivered

Bir qarashda: order_date da 15.03.2025 ham, 2025-03-15 ham; region da kirill ham; unit_price da 1 850 000. Tozalashdan oldin ro'yxat tuzamiz.

Diagnostika: nima buzilgan

python4 qator
print(iflos.dtypes.to_string(), "\n")
print("bo'sh qiymatlar:\n" + iflos.isna().sum()[iflos.isna().sum() > 0].to_string(), "\n")
print("to'liq dublikat qatorlar:", iflos.duplicated().sum())
print("item_id takrorlangan:", iflos.item_id.duplicated().sum())
Chiqish
item_id           int64
order_id          int64
order_date       object
region           object
category         object
product_name     object
quantity        float64
unit_price       object
status           object 

bo'sh qiymatlar:
category    185
quantity    124
status      125 

to'liq dublikat qatorlar: 180
item_id takrorlangan: 180

unit_price object (matn): demak ichida matn bor. quantity float64: bo'sh qiymat borligi uchun (int ustunda NaN bo'lmaydi, pandas float ga o'tkazadi).

python2 qator
print("region variantlari:", iflos.region.nunique(), "ta:")
print(iflos.region.value_counts().head(12).to_string())
Chiqish
region variantlari: 61 ta:
region
Toshkent shahri      2008
Samarqand             436
Toshkent viloyati     399
Farg'ona              326
Andijon               285
Namangan              228
Buxoro                223
Qashqadaryo           220
toshkent shahri       212
Toshkent shahri       162
Navoiy                162
Jizzax                150

14 hudud, 30 dan ortiq "variant": Toshkent shahri, toshkent shahri, Toshkent shahri, TOSHKENT SHAHRI, Тошкент шаҳри. Odam uchun bitta narsa, kompyuter uchun beshta.

python3 qator
print("sana formatlari (namuna):", iflos.order_date.sample(6, random_state=1).tolist())
print("quantity taqsimoti:", iflos.quantity.describe()[["min", "50%", "max"]].to_dict())
print("matnli narxlar:", iflos.unit_price.astype(str).str.contains(" ").sum(), "ta")
Chiqish
sana formatlari (namuna): ['2025-02-24', '14/03/25', '2025-02-09', '23.05.2025', '2025-06-01', '2025-06-23']
quantity taqsimoti: {'min': -1.0, '50%': 1.0, 'max': 999.0}
matnli narxlar: 1919 ta

quantity min −1, max 999: kiritish xatosi. Diagnostika ro'yxati tayyor:

# Muammo Ustun Yechim
1 Dublikat qatorlar hammasi drop_duplicates
2 Uch xil sana formati order_date pd.to_datetime format bilan
3 Hudud nomi variantlari region str.strip().str.lower() + lug'at
4 Narx matn, bo'shliq bilan unit_price str.replace(" ", "") + astype(int)
5 Bo'sh qiymatlar category, quantity, status Qaror: to'ldirish / tashlash / belgilash
6 Manfiy va 999 miqdor quantity Qaror: tashlash yoki bo'sh deb belgilash

1. Dublikatlar

python4 qator
n0 = len(iflos)
toza = iflos.drop_duplicates().copy()
print(f"dublikat: {n0 - len(toza)} qator olib tashlandi, qoldi {len(toza)}")
print("item_id hali takrorlanadimi:", toza.item_id.duplicated().sum())
Chiqish
dublikat: 180 qator olib tashlandi, qoldi 6000
item_id hali takrorlanadimi: 0

drop_duplicates() to'liq bir xil qatorlarni olib tashlaydi. Ba'zan dublikat "deyarli bir xil" bo'ladi (bitta ustun farq qiladi): drop_duplicates(subset=["item_id"]) faqat kalit bo'yicha. Qaysi nusxani qoldirish: keep="first" (default) yoki "last". Har doim oldin va keyin sanang.

2. Sana: uch formatdan bittaga

python14 qator
def sanaga(s):
    """'2025-03-15', '15.03.2025', '15/03/25' -> datetime. Uch formatni ketma-ket sinaydi."""
    for fmt in ("%Y-%m-%d", "%d.%m.%Y", "%d/%m/%y"):
        d = pd.to_datetime(s, format=fmt, errors="coerce")
        if d.notna().all():
            return d
    # aralash bo'lsa: har formatni alohida, keyin birlashtirish
    out = pd.Series(pd.NaT, index=s.index)
    for fmt in ("%Y-%m-%d", "%d.%m.%Y", "%d/%m/%y"):
        out = out.fillna(pd.to_datetime(s, format=fmt, errors="coerce"))
    return out

toza["order_date"] = sanaga(toza.order_date)
print(toza.order_date.dtype, "| bo'sh:", toza.order_date.isna().sum(), "| oraliq:", toza.order_date.min().date(), "-", toza.order_date.max().date())
Chiqish
datetime64[ns] | bo'sh: 0 | oraliq: 2025-01-01 - 2025-06-30

pd.to_datetime(..., format=..., errors="coerce"): formatga mos kelmagani NaT (bo'sh sana) bo'ladi, xato bermaydi. Uch formatni ketma-ket qo'llab, bo'shlarni to'ldiramiz. Xavfli joy: 15/03/25 ni %m/%d/%y deb o'qisa 2025-yil 3-oy o'rniga boshqa sana chiqadi va xato bermaydi. Formatni aniq yozing, pd.to_datetime ga "o'zi topsin" demang. Tekshiruv: oraliq 2025-01 dan 2025-06 gacha, kutilgandek.

3. Hudud nomlari: normalizatsiya + lug'at

python7 qator
KIRILL = {"тошкент шаҳри": "Toshkent shahri", "самарқанд": "Samarqand", "бухоро": "Buxoro", "фарғона": "Farg'ona", "андижон": "Andijon"}
regions = pd.read_csv(CSV / "regions.csv")
lotin = {r.lower(): r for r in regions.region}                   # "toshkent shahri" -> "Toshkent shahri"

norm = toza.region.astype(str).str.strip().str.lower()
toza["region"] = norm.map(lotin).fillna(norm.map(KIRILL))
print("variantlar:", iflos.region.nunique(), "->", toza.region.nunique(), "| tanilmagan:", toza.region.isna().sum())
Chiqish
variantlar: 61 -> 14 | tanilmagan: 0

Uch qadam: strip (bo'shliq), lower (harf), map (lug'at). Lug'at kalitlari ham kichik harfda. Tanilmagan 0: hammasi 14 hududga qaytdi. Real ishda tanilmaganlar bo'ladi: ularni value_counts bilan ko'rib, lug'atga qo'shasiz. Bu ish qo'lda, lekin bir marta.

4. Narx: matndan songa

python2 qator
toza["unit_price"] = toza.unit_price.astype(str).str.replace(" ", "", regex=False).str.replace(",", "").astype(int)
print(toza.unit_price.dtype, "| min", toza.unit_price.min(), "| max", f"{toza.unit_price.max():,}")
Chiqish
int64 | min 4000 | max 10,530,000

"1 850 000" → "1850000" → 1850000. astype(int) ichida hali matn qolsa xato beradi: bu yaxshi, jim o'tmaydi. Vergul, so'm belgisi, so'm so'zi bo'lsa ularni ham replace. Kasr bo'lsa astype(float).

5. Bo'sh qiymatlar: uch xil qaror

python1 qator
print(toza.isna().sum()[toza.isna().sum() > 0].to_string())
Chiqish
category    180
quantity    120
status      120

Har ustun uchun alohida qaror, bitta "hammasini to'ldir" yo'q:

python10 qator
products = pd.read_csv(CSV / "products.csv")
# category: mahsulot nomidan tiklash mumkin (products da bor) -> to'ldiramiz
kat = toza.product_name.map(products.set_index("product_name").category)
toza["category"] = toza.category.fillna(kat)
# status: noma'lum holatni taxmin qilmaymiz -> alohida belgilaymiz
toza["status"] = toza.status.fillna("noma'lum")
# quantity: miqdor noma'lum bo'lsa summa hisoblanmaydi -> tashlaymiz (nechta ekanini yozib)
n_q = toza.quantity.isna().sum()
toza = toza[toza.quantity.notna()].copy()
print(f"category: manbadan tiklandi | status: 'noma\\'lum' belgisi | quantity: {n_q} qator tashlandi, qoldi {len(toza)}")
Chiqish
category: manbadan tiklandi | status: 'noma\'lum' belgisi | quantity: 120 qator tashlandi, qoldi 5880

Qoida: to'ldirish faqat ishonchli manba bo'lsa (kategoriya mahsulot nomidan aniq), belgilash noma'lumlik ma'noli bo'lsa (holat), tashlash qiymat hisob uchun shart bo'lsa va tiklab bo'lmasa (miqdor). O'rtacha bilan to'ldirish (fillna(mean)) analitikada deyarli har doim xato: u taqsimotni buzadi va "ma'lumot bor" degan yolg'on taassurot beradi.

6. Mantiqsiz qiymatlar

python6 qator
xato = toza[(toza.quantity <= 0) | (toza.quantity > 100)]
print(len(xato), "qator: miqdor <= 0 yoki > 100")
print(xato.quantity.value_counts().to_dict())
toza = toza[(toza.quantity > 0) & (toza.quantity <= 100)].copy()
toza["quantity"] = toza.quantity.astype(int)
print("qoldi:", len(toza))
Chiqish
25 qator: miqdor <= 0 yoki > 100
{999.0: 13, -1.0: 12}
qoldi: 5855

Chegara qayerdan? Ma'lumotdan: describe() da 99% qatorlar 1–6 oralig'ida. 999 va −1 kiritish xatosi, ularni "tuzatib" bo'lmaydi (999 aslida 9 mi, 99 mi?). Tashlaymiz va hisobotda yozamiz: "25 qator (0.4%) mantiqsiz miqdor tufayli chiqarildi". Tashlangan qatorlar soni har doim hisobotda.

Tekshiruv: haqiqat bilan solishtirish

Bu datasetda toza versiya bor (answers/iflos_savdo_toza.csv). Real hayotda yo'q, lekin tekshiruv usuli bir xil: summalar, sonlar, oraliqlar.

python5 qator
haqiqat = pd.read_csv(DATA / "answers" / "iflos_savdo_toza.csv")
def summa(df): return (df.quantity * df.unit_price)[df.status == "delivered"].sum()
print(f"qatorlar: tozalangan {len(toza)}, haqiqat {len(haqiqat)}")
print(f"yetkazilgan summa: tozalangan {summa(toza)/1e9:.3f} mlrd, haqiqat {summa(haqiqat)/1e9:.3f} mlrd")
print(f"hudud bo'yicha buyurtma soni mos:", toza.region.value_counts().sort_index().equals(haqiqat[haqiqat.item_id.isin(toza.item_id)].region.value_counts().sort_index()))
Chiqish
qatorlar: tozalangan 5855, haqiqat 6000
yetkazilgan summa: tozalangan 4.869 mlrd, haqiqat 5.043 mlrd
hudud bo'yicha buyurtma soni mos: True

Qatorlar 6 000 dan kam, chunki bo'sh va mantiqsiz miqdorlilarni tashladik: bu bilib qilingan yo'qotish. Summa farqi shu qatorlardan. Real ishda "haqiqat" o'rniga: boshqa manba (buxgalteriya jami), oldingi oy hisoboti, yoki mantiq (o'rtacha chek 1–3 mln bo'lishi kerak).

Tozalash funksiyasi: bir marta yozing, har hafta ishlating

python20 qator
def tozala(df, regions, products):
    """iflos_savdo formatidagi faylni tozalaydi. Qaytaradi: toza df va qadamlar hisoboti."""
    log = {"kirdi": len(df)}
    df = df.drop_duplicates().copy();                         log["dublikat"] = log["kirdi"] - len(df)
    df["order_date"] = sanaga(df.order_date)
    lotin = {r.lower(): r for r in regions.region}
    n = df.region.astype(str).str.strip().str.lower()
    df["region"] = n.map(lotin).fillna(n.map(KIRILL));        log["tanilmagan_hudud"] = int(df.region.isna().sum())
    df["unit_price"] = df.unit_price.astype(str).str.replace(" ", "", regex=False).astype(int)
    df["category"] = df.category.fillna(df.product_name.map(products.set_index("product_name").category))
    df["status"] = df.status.fillna("noma'lum")
    ok = df.quantity.notna() & (df.quantity > 0) & (df.quantity <= 100)
    log["miqdor_tashlandi"] = int((~ok).sum())
    df = df[ok].copy(); df["quantity"] = df.quantity.astype(int)
    log["chiqdi"] = len(df)
    return df, log

toza2, log = tozala(pd.read_csv(DATA / "iflos_savdo.csv"), regions, products)
print(log)
print("funksiya va qo'lda tozalash bir xil:", len(toza2) == len(toza) and summa(toza2) == summa(toza))
Chiqish
{'kirdi': 6180, 'dublikat': 180, 'tanilmagan_hudud': 0, 'miqdor_tashlandi': 145, 'chiqdi': 5855}
funksiya va qo'lda tozalash bir xil: True

Hisobot (log) tozalashning bir qismi: har hafta yangi fayl kelganda "dublikat 180, miqdor 25 tashlandi" degan raqamlar odatdagidan farq qilsa, faylda yangi muammo bor. Tozalash bir marta qilinadigan ish emas, har fayl uchun takrorlanadigan jarayon: shuning uchun funksiya.

Xulosa

Tushuncha Asosiy fikr
Diagnostika oldin dtypes, isna().sum(), duplicated(), value_counts, describe: ro'yxat tuzing, keyin tozalang
Dublikat drop_duplicates, kerak bo'lsa subset; oldin/keyin sanang
Sana pd.to_datetime(format=..., errors="coerce"), formatni aniq bering; oraliqni tekshiring
Kategoriya nomlari strip + lower + lug'at map; tanilmaganlarni ko'rib lug'atni to'ldiring
Matnli son str.replace + astype; xato bersa yaxshi, jim o'tmaydi
Bo'sh qiymat Uch qaror: to'ldirish (ishonchli manba), belgilash, tashlash. O'rtacha bilan to'ldirish yo'q
Mantiqsiz qiymat Chegara ma'lumotdan (describe); tashlangan soni hisobotda
Funksiya + log Tozalash takrorlanadigan jarayon; har qadam nechta qatorni o'zgartirganini yozing

Talaba ishi

  1. tozala ga item_status mos kelmaydigan qatorlarni topish qadamini qo'shing: status faqat delivered, cancelled, returned, noma'lum bo'lishi kerak. Boshqacha yozilgan (masalan Delivered) bo'lsa nima qilasiz?
  2. Sana funksiyasini buzib ko'ring: %d/%m/%y o'rniga %m/%d/%y qo'ying. Nechta sana "o'zgardi" va xato chiqdimi? Nega bu xavfli?
  3. Hududlar lug'atiga kirill variantlarini qo'lda yozdik. Тошкент ni Toshkent ga aylantiruvchi umumiy transliteratsiya funksiyasi yozing (harf → harf lug'ati) va uni 14 hududga sinang.
  4. iflos_savdo da yetkazilgan buyurtmalar bo'yicha kategoriya × oy jadvali (pivot). 1-bobdagi iyun Elektronika hikoyasi bu yarim yillik iflos ma'lumotda ham ko'rinadimi?

Uy vazifasi 5

homework/uv_05.md: iflos_savdo.csv ni tozalab, uv_05_toza.csv va uv_05_log.json (qadamlar hisoboti) topshiring. Tekshiruv: qatorlar soni, yetkazilgan summa, hudud bo'yicha soni answers/ bilan solishtiriladi; log da har qadam bo'lishi shart.

Bu bobni bepul o'qidingiz.

Kitob o'rgatadi, lekin sizning kodingiz nega ishlamaganini ayta olmaydi. Kursda uy vazifangiz tekshiriladi.

Telegramda yozish Narxlar