Tozalash: iflos ma'lumotdan ishonchli jadvalgacha
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.
iflos = pd.read_csv(DATA / "iflos_savdo.csv") print(iflos.shape) show(iflos.sample(6, random_state=3))
(6180, 9)
| item_id | order_id | order_date | region | category | product_name | quantity | unit_price | status |
|---|---|---|---|---|---|---|---|---|
| 19 538 | 9 868 | 2025-02-02 | Тошкент шаҳри | Elektronika | Noutbuk Lenovo IdeaPad | 1 | 8 580 000 | delivered |
| 18 037 | 9 063 | 06/06/25 | Xorazm | Uy-ro'zg'or | Tozalash vositasi | 2 | 28000 | delivered |
| 37 102 | 18 725 | 22.03.2025 | Toshkent shahri | Uy-ro'zg'or | Kastryulka 5L | 1 | 149 000 | delivered |
| 26 071 | 13 161 | 11/03/25 | Toshkent viloyati | Oziq-ovqat | Un 10kg | 1 | 95000 | delivered |
| 32 259 | 16 298 | 2025-05-24 | Toshkent shahri | Kiyim | Futbolka | 1 | 84 000 | delivered |
| 27 889 | 14 068 | 2025-03-28 | Qashqadaryo | Kiyim | Kepka | 1 | 70000 | delivered |
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
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())
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).
print("region variantlari:", iflos.region.nunique(), "ta:") print(iflos.region.value_counts().head(12).to_string())
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.
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")
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
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())
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
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())
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
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())
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
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():,}")
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
print(toza.isna().sum()[toza.isna().sum() > 0].to_string())
category 180 quantity 120 status 120
Har ustun uchun alohida qaror, bitta "hammasini to'ldir" yo'q:
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)}")
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
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))
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.
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()))
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
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))
{'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
tozalagaitem_statusmos kelmaydigan qatorlarni topish qadamini qo'shing:statusfaqatdelivered,cancelled,returned,noma'lumbo'lishi kerak. Boshqacha yozilgan (masalanDelivered) bo'lsa nima qilasiz?- Sana funksiyasini buzib ko'ring:
%d/%m/%yo'rniga%m/%d/%yqo'ying. Nechta sana "o'zgardi" va xato chiqdimi? Nega bu xavfli? - Hududlar lug'atiga kirill variantlarini qo'lda yozdik.
ТошкентniToshkentga aylantiruvchi umumiy transliteratsiya funksiyasi yozing (harf → harf lug'ati) va uni 14 hududga sinang. iflos_savdoda 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.