IlmHamroh
Data Science va sun'iy intellekt/Maxsus mavzular10/12-dars52 daqiqa
Mundarija (24)

28.10-dars: Katta ma'lumot bilan ishlash

28-QISM — MAXSUS MAVZULAR · 10-dars


1. Kirish va motivatsiya

Oldingi darsda geografik ma'lumot bilan ishladik: koordinatalar, masofalar, hududlar va fazoviy qo'shnichilik. U yerda ham, kursning deyarli hamma joyida ham bitta jimgina faraz bor edi: butun ma'lumot operativ xotiraga sig'adi. pd.read_csv chaqiriladi, jadval xotirada paydo bo'ladi, groupby bir soniyada ishlaydi. Amalda bu faraz ertami-kechmi buziladi: savdo tarmog'ining besh yillik cheklari, bankning kunlik tranzaksiyalari, mobil ilovaning har bir bosishi (click) logi — bular gigabaytlar va terabaytlar.

Shunday paytda ko'pchilik birinchi bo'lib "Spark kerak" deydi. Ba'zan bu to'g'ri. Lekin ko'p hollarda muammo boshqa joyda: 10 GB lik CSV aslida 1.5 GB lik ma'lumot bo'lib chiqadi — faqat har bir ustun keraksiz keng turda (int64, object) saqlangan. Yoki butun faylni o'qish umuman shart emas — kerakli yig'indilarni fayl bo'yicha bir marta o'tib hisoblash mumkin. Yoki aniq javob shart emas — "taxminan 2.1 million noyob foydalanuvchi, xato 1%" degan javob 12 KB xotirada olinadi.

Bu darsda "katta ma'lumot" bilan ishlashning asosiy g'oyalarini noldan quramiz: dtype optimallash, chunk bo'yicha o'qish va oqimli agregatsiya, bir o'tishli algoritmlar (Welford, rezervuar namuna), taxminiy tuzilmalar (HyperLogLog, Count-Min Sketch, Bloom filter), map-reduce g'oyasi va sqlite bilan diskdagi so'rovlar. Spark, Dask, Polars, DuckDB va Parquet — bu muhitda o'rnatilmagan, shuning uchun ular nazariya va ma'lumotnoma bloklarida; lekin misollarda aynan ular ichida ishlaydigan mexanizmlarni ko'ramiz.

Real vaziyat. Chakana savdo tarmog'ining tahlilchisi 30 GB lik yillik cheklar faylini noutbukda ochmoqchi bo'ldi — 16 GB xotira yetmadi, jarayon yiqildi. Jamoa bulutda Spark klasteri uchun byudjet so'radi. Tekshirib ko'rilganda: savol "har do'kon va mahsulot guruhi bo'yicha oylik tushum" edi. Fayl 200 ming qatorlik bo'laklarda o'qildi, faqat 5 ta kerakli ustun, to'g'ri turlar bilan — va javob noutbukda 4 daqiqada, 300 MB xotirada tayyor bo'ldi. Klaster kerak emas edi. Boshqa tomondan, xuddi shu tarmoq keyinchalik har kuni 500 million click hodisasi ustida tavsiya modelini qayta o'qitishni boshlaganda — bitta mashina haqiqatan yetmay qoldi va taqsimlangan vosita zarur bo'ldi. Farqni ko'ra bilish — bu darsning asosiy ko'nikmasi.

Bu darsda "katta" qachon boshlanishini, undan oldin nimalarni sinab ko'rish kerakligini va katta vositalar ichida nima bo'layotganini o'rganamiz.

Bu darsda:

  • "Katta" qachon boshlanadi: xotira, vaqt, bitta mashina chegarasi
  • Xotirani tejash: dtype optimallash, category, memory_usage(deep=True)
  • Chunk bo'yicha o'qish va oqimli agregatsiya — qaysi statistika bo'laklanadi, qaysi biri yo'q
  • Bir o'tishli algoritmlar: Welford va sonli barqarorlik, rezervuar namuna
  • Taxminiy tuzilmalar: HyperLogLog, Count-Min Sketch, Bloom filter — xato va xotira murosasi
  • Map-reduce: map, shuffle, reduce; kombinator va issiq kalit
  • Diskdagi so'rovlar: sqlite, indeks, EXPLAIN QUERY PLAN
  • Spark, Dask, Polars, DuckDB, Parquet — qachon va nima uchun
  • Qachon katta vosita kerak emas
  • Tuzoqlar

ℹ Misollar real numpy/pandas/scipy/sqlite3 bilan (Python 3.14). Ma'lumot sintetik, urug' bilan yaratiladi; fayllar faqat vaqtinchalik papkada.


2. Nazariya — chuqur tushuntirish

2.1. "Katta" qachon boshlanadi?

"Katta ma'lumot" — qator soni emas, resurs bilan nisbat. Bir xil 50 GB fayl bir vazifa uchun muammo, boshqasi uchun emas.

text
DARAJA           BELGISI                              ODATIY YECHIM
---------------  -----------------------------------  ----------------------------
1. Xotiraga      jadval RAM ning ~1/3 qismidan kichik  oddiy pandas/numpy
   sig'adi       (amallar nusxa yaratadi: x2-x3 zaxira)
2. Turlar bilan  xom holda sig'maydi, lekin to'g'ri   dtype, category, usecols
   sig'adi       dtype va kerakli ustunlar bilan sig'adi
3. Diskda,       sig'maydi, lekin savol bir o'tishda   chunk, oqimli agregatsiya,
   bir o'tish    javob beradi (yig'indi, sanoq)        sqlite/DuckDB, Parquet
4. Bitta mashina ko'p o'tish yoki og'ir hisob,          ko'p yadroli (Polars,
   sekin         lekin bitta disk yetadi               DuckDB, Dask lokal)
5. Bitta mashina ma'lumot bitta diskka/mashinaga       Spark, Dask klaster,
   yetmaydi      sig'maydi yoki hisob soatlab ketadi   bulut omborlari

QOIDA: pastki darajadagi yechimni sinab ko'rmasdan yuqorisiga o'tmang.
Har daraja yuqoriga - murakkablik, narx va xato manbalari ko'payadi.

Uch savol bilan boshlang:

  1. Savol nima? "Har do'kon bo'yicha yig'indi" — bir o'tishda hal bo'ladi. "Ikki jadvalni katta kalit bo'yicha birlashtirib, keyin modelni o'qitish" — ancha og'ir.
  2. Hammasi kerakmi? Ko'pincha 1% tasodifiy namuna model prototipi uchun yetarli (18-qismdagi o'rganish egri chiziqlari: ma'lumot ikki barobar oshganda sifat qancha o'sadi?).
  3. Aniq javob kerakmi? "Noyob foydalanuvchilar soni" uchun 1% xato ko'pincha mutlaqo qabul qilinadi.

2.2. Xotirani tejash: dtype

pandas standart holatda butun sonlarni int64, kasrlarni float64, matnni Python obyektlari sifatida saqlaydi. Har bir qiymat uchun 8 bayt (matnda esa har satr alohida obyekt: ~50 bayt va undan ko'p).

text
TUR        BAYT  DIAPAZON / ANIQLIK
int8        1    -128 .. 127
int16       2    -32768 .. 32767
int32       4    -2.1e9 .. 2.1e9
int64       8    -9.2e18 .. 9.2e18
float32     4    ~7 o'nlik raqam aniqlik; 16 777 216 gacha butunlar ANIQ
float64     8    ~15-16 o'nlik raqam
category    kod (int8/int16) + noyob qiymatlar bir marta
str/object  har qiymat alohida Python satri (~49 bayt + uzunlik)

memory_usage()           - faqat ko'rsatkichlar (object uchun 8 bayt)  -> ALDAYDI
memory_usage(deep=True)  - satrlarning o'zini ham sanaydi               -> HAQIQIY

Qoidalar:

  • Butun sonlar: pd.to_numeric(s, downcast="integer") diapazonga qarab eng kichik turni tanlaydi. Lekin kelajakdagi qiymatlar ham sig'ishini o'ylang (bugun id 30 000 gacha — ertaga-chi?).
  • Kasrlar: float32 narx, miqdor, ehtimollar uchun odatda yetarli. Pul hisobida (buxgalteriya) esa aniq yig'indi kerak bo'lsa float64 yoki butun tiyinlar (int64).
  • Matn: noyob qiymatlar kam bo'lsa (do'kon, shahar, to'lov turi) — category. Noyob qiymatlar ko'p bo'lsa (chek kodi, izoh) — category yordam bermaydi, hatto kattalashtiradi.
  • Ustunlar: usecols bilan faqat kerakli ustunlarni o'qing — eng arzon tejash.

Tuzoq: kichik butun tur jim to'lib ketadi. int8 ustunni 20 ga ko'paytirsangiz, 127 dan oshgan natijalar manfiy songa aylanadi — xato ham, ogohlantirish ham yo'q. Hisoblashdan oldin kengroq turga o'tkazing.

2.3. Chunk bo'yicha o'qish va oqimli agregatsiya

text
pd.read_csv(yol, chunksize=50_000)  -> har safar 50 000 qatorli DataFrame
xotirada bir vaqtning o'zida FAQAT bitta bo'lak + yig'uvchi holat

BO'LAKLANADIGAN (birlashtiriladigan) statistikalar:
  sanoq, yig'indi, min, max           -> oddiy qo'shish / min / max
  o'rtacha                            -> (yig'indi, sanoq) juftligi
  dispersiya                          -> (n, o'rtacha, M2) - Welford/Chan
  guruh bo'yicha yig'indi             -> lug'at/Series ni qo'shib borish

BO'LAKLANMAYDIGAN (aniq):
  mediana, kvantillar                 -> bo'laklar medianasidan hosil bo'lmaydi!
  noyob qiymatlar soni                -> barcha qiymatlar to'plami kerak
  tartiblash, rang (rank)             -> global ko'rinish kerak
  YECHIM: taxminiy sketch (HyperLogLog, t-digest) yoki diskda saralash/sqlite

"O'rtachalar o'rtachasi" — o'rtacha emas. Bo'laklar hajmi har xil bo'lsa, natija noto'g'ri. Har doim (yig'indi, sanoq) ni yig'ing va oxirida bo'ling.

2.4. Bir o'tishli algoritmlar

Welford algoritmi — o'rtacha va dispersiyani bitta o'tishda, sonli barqaror hisoblaydi.

text
NAIV (bitta o'tish, lekin XAVFLI):
  s = sum x,  s2 = sum x^2
  var = (s2 - s^2 / n) / (n - 1)
  muammo: x ~ 1e9 bo'lsa, s2 ~ n * 1e18 - ikki ULKAN sonning ayirmasi
          float64 aniqligi ~16 raqam -> kichik dispersiya "yutilib ketadi"
          (katastrofik ayirish: natija manfiy ham chiqishi mumkin)

WELFORD (bitta o'tish, BARQAROR):
  n = n + 1
  delta  = x - orta
  orta   = orta + delta / n
  M2     = M2 + delta * (x - orta)       <- yangi orta bilan!
  var    = M2 / (n - 1)
  faqat og'ishlar (kichik sonlar) yig'iladi -> aniqlik saqlanadi

BIRLASHTIRISH (Chan va boshq.) - parallel/map-reduce uchun:
  A = (n_a, orta_a, M2_a),  B = (n_b, orta_b, M2_b)
  n     = n_a + n_b
  delta = orta_b - orta_a
  orta  = orta_a + delta * n_b / n
  M2    = M2_a + M2_b + delta^2 * n_a * n_b / n

Rezervuar namuna (Algorithm R) — uzunligi oldindan noma'lum oqimdan k ta elementni teng ehtimol bilan tanlaydi, xotira O(k).

text
birinchi k element -> rezervuarga
i-element (0 dan sanaganda, i >= k):
  j = tasodifiy butun son [0, i] oralig'ida
  agar j < k: rezervuar[j] = element
ISBOT G'OYASI: i-element kiradi ehtimol k/(i+1); keyingi har qadamda
  undan keyin keladigan t-element uni siqib chiqarish ehtimoli 1/(t+1),
  saqlanib qolish (t/(t+1)); ko'paytma teleskopik -> oxirida k/N
  -> HAR BIR element uchun bir xil k/N

Rezervuar namuna oqimdagi ma'lumotdan tasodifiy namuna kerak bo'lganda (masalan, loglardan model uchun namuna, monitoring uchun har kuni 10 000 ta bashorat) ishlatiladi. Vaznli variant (A-Res: kalit u^(1/w)) har elementni o'z vazniga proporsional tanlaydi.

2.5. Taxminiy tuzilmalar (sketch lar)

Aniq javob uchun xotira ma'lumot hajmiga proporsional. Sketch lar qat'iy kichik xotirada, isbotlangan xato chegarasi bilan taxmin beradi. Umumiy xossa: ular birlashtiriladi (ikki kunning sketch idan ikki kunlik sketch) — shuning uchun taqsimlangan tizimlarda juda qulay.

text
HYPERLOGLOG - noyob elementlar soni (cardinality)
  har element -> 64 bitli xesh
  birinchi p bit -> registr raqami (m = 2^p registr)
  qolgan bitlarda boshidagi nollar soni + 1 = rho
  registr = max(registr, rho)      <- takror element hech narsani o'zgartirmaydi
  baho = alfa_m * m^2 / sum 2^(-registr)   (+ kichik diapazon tuzatishi)
  nisbiy xato ~ 1.04 / sqrt(m):   m = 4096 -> ~1.6%, xotira ~4 KB
  birlashtirish: registrlar bo'yicha max

COUNT-MIN SKETCH - chastota (qancha marta uchradi?)
  d qator x w ustun hisoblagich, d ta xesh funksiya
  qo'shish: har qatorda jadval[j][h_j(x)] += 1
  baho:     min_j jadval[j][h_j(x)]
  xato FAQAT yuqoriga (to'qnashuvlar qo'shadi, ayirmaydi): baho >= haqiqiy
  kafolat: baho <= haqiqiy + eps * N   ehtimoli >= 1 - delta
           w = ceil(e / eps),  d = ceil(ln(1 / delta))
  kamyob elementlar uchun NISBIY xato katta (eps*N ular uchun ulkan)

BLOOM FILTER - a'zolik (bu element to'plamda bormi?)
  m bitli massiv, k ta xesh
  qo'shish: k ta bitni 1 qilish;  tekshirish: k tasi ham 1 mi?
  "yo'q"  -> ANIQ yo'q (yolg'on manfiy YO'Q)
  "bor"   -> ehtimol bor (yolg'on musbat ulushi):
             FPR ~ (1 - e^(-k*n/m))^k
  optimal k = (m/n) * ln 2;  element boshiga 10 bit -> FPR ~ 0.8%
  o'chirib bo'lmaydi (counting Bloom filter - hisoblagichlar bilan)
Sketch Savol Xotira Xato turi
HyperLogLog nechta noyob? 2^p registr (KB lar) ikki tomonlama, ~`1.04/sqrt(m)`
Count-Min x necha marta? d * w hisoblagich faqat ortiqcha baho
Bloom filter x bormi? ~10 bit / element faqat yolg'on musbat
Rezervuar tasodifiy namuna k element namuna xatosi
t-digest / KLL kvantillar yuzlab markaz chetlarda aniqroq

Qayerda uchraydi: Redis PFCOUNT (HyperLogLog), ma'lumotlar bazalarining APPROX_COUNT_DISTINCT, Spark approx_count_distinct, Cassandra/HBase da Bloom filter (diskka bormasdan "bu kalit bu faylda yo'q" deyish), tarmoq trafigida eng faol IP larni topish (Count-Min + heap).

2.6. Map-reduce

Map-reduce — ma'lumotni ko'p mashinada qayta ishlashning asosiy g'oyasi. Hadoop uni mashhur qildi, Spark esa uni xotirada va ko'p bosqichli qildi, lekin skelet o'sha-o'sha.

text
KIRISH: fayl bo'laklarga (split) bo'lingan, har bo'lak - alohida mashinada

1. MAP:     har yozuv -> (kalit, qiymat) juftlari
            "juda yaxshi narx" -> (juda,1) (yaxshi,1) (narx,1)
2. KOMBINATOR (ixtiyoriy, map tomonida lokal reduce):
            (yaxshi,1) (yaxshi,1) (yaxshi,1) -> (yaxshi,3)
            SHART: amal assotsiativ va kommutativ (yig'indi, max, (sum,count))
3. SHUFFLE: har juft kalit bo'yicha reducer ga yuboriladi
            reducer = xesh(kalit) mod R
            TARMOQ orqali - eng qimmat bosqich
4. REDUCE:  bir kalitning barcha qiymatlari -> bitta natija
            (yaxshi, [3, 5, 2]) -> (yaxshi, 10)

MUAMMOLAR:
  shuffle hajmi   - kombinator bilan keskin kamayadi
  issiq kalit     - bitta kalit (masalan "va") bitta reducer ni to'ldiradi
                    (skew); yechim: kalitga tuz qo'shib ikki bosqichli yig'ish
  noto'g'ri kombinator - o'rtachalar o'rtachasi 2.3-bob
  deterministik bo'lmagan xesh - Python hash() jarayonlar orasida farq qiladi

Map-reduce da eng qimmat narsa — hisob emas, ma'lumotni ko'chirish (shuffle). Spark dagi "wide transformation" (groupBy, join, repartition) aynan shuffle ni anglatadi — optimallashtirish asosan shularni kamaytirishdan iborat.

2.7. Diskdagi so'rovlar: sqlite va indeks

Ma'lumot xotiraga sig'masa, uni ma'lumotlar bazasiga qo'yish va savolni SQL bilan berish mumkin — baza faqat kerakli sahifalarni diskdan o'qiydi.

text
INDEKSSIZ:   WHERE mijoz_id = 42   -> SCAN savdo (hamma qator o'qiladi)  O(N)
INDEKS BILAN: B-daraxt (mijoz_id -> qator joyi)
             WHERE mijoz_id = 42   -> SEARCH ... USING INDEX            O(log N + k)

EXPLAIN QUERY PLAN <so'rov>  - baza qanday bajarishini ko'rsatadi:
  SCAN jadval                          - to'liq o'qish
  SEARCH jadval USING INDEX i (x=?)     - indeks bo'yicha qidiruv
  USING COVERING INDEX                  - jadvalga umuman qaytmaydi

INDEKS NARXI:
  disk joyi (qo'shimcha B-daraxt), har INSERT/UPDATE sekinroq
  past selektivlik (qatorlarning ko'pchiligi mos) - indeks FOYDASIZ
  yoki hatto zararli: har qator uchun indeksdan jadvalga "sakrash"
KOMPOZIT INDEKS (dokon, kun): chap prefiks qoidasi -
  WHERE dokon = ? AND kun BETWEEN ? AND ?  - ishlaydi
  WHERE kun BETWEEN ? AND ?                - dokon siz ishlamaydi

Qatorli va ustunli formatlar. CSV va sqlite — qatorli (row-oriented): bitta qatorning hamma ustunlari yonma-yon. Tahliliy savollar esa odatda ko'p qator, kam ustun o'qiydi ("hamma cheklarning summasi"). Parquet — ustunli format: har ustun alohida, siqilgan bloklarda saqlanadi.

text
PARQUET:
  ustunli    -> faqat kerakli ustunlar o'qiladi (projection pushdown)
  siqilgan   -> bir turdagi qiymatlar yonma-yon: dictionary, RLE, snappy/zstd
                (CSV ga nisbatan odatda 5-10 barobar kichik)
  sxema bor  -> turlar faylda saqlanadi, har o'qishda qayta taxmin qilinmaydi
  row group statistikasi (min/max) -> WHERE kun > 300 bo'lsa, mos kelmaydigan
                bloklar umuman o'qilmaydi (predicate pushdown)
  bo'limlash (partitioning): savdo/yil=2025/oy=03/part-0.parquet
                -> faqat kerakli papkalar o'qiladi

2.8. Taqsimlangan va zamonaviy vositalar (ma'lumotnoma)

Bu vositalar bu muhitda o'rnatilmagan — quyidagilar g'oyalar va sintaksis bo'yicha ma'lumotnoma.

text
SPARK (Apache Spark, PySpark)
  klaster: driver (reja tuzadi) + executor lar (bo'limlarni qayta ishlaydi)
  RDD        - past darajali, taqsimlangan kolleksiya (map, reduceByKey)
  DataFrame  - sxemali jadval; Catalyst optimallashtiruvchisi rejani qayta yozadi
  LAZY       - transformatsiyalar (select, filter, groupBy) faqat REJA quradi;
               action (count, collect, write) chaqirilganda bajariladi
  bo'limlar (partitions) - parallellik birligi; juda kam -> yadrolar bo'sh,
               juda ko'p -> vazifalar boshqaruvi xarajati
  narrow transformatsiya (map, filter) - shuffle siz
  wide transformatsiya (groupBy, join) - SHUFFLE, bosqich chegarasi
  cache/persist - bir natija ko'p marta ishlatilsa

DASK       - pandas/numpy API sini bo'laklarga bo'lib, vazifalar grafiga
             aylantiradi; lokal ko'p yadroda yoki klasterda; lazy (.compute())
POLARS     - Rust da yozilgan, ustunli (Arrow), ko'p yadroli DataFrame;
             lazy rejim (scan_csv/scan_parquet -> collect) so'rovni optimallaydi;
             bitta mashinada pandas dan ko'pincha ancha tez
DUCKDB     - jarayon ichidagi (sqlite kabi) tahliliy SQL baza; ustunli,
             vektorlashgan; Parquet/CSV ni to'g'ridan-to'g'ri so'raydi;
             xotiradan katta ma'lumotda diskka to'kib (spill) ishlaydi

Umumiy g'oya hamma joyda bir xil: lazy reja → optimallash (keraksiz ustun va qatorlarni erta tashlash) → bo'laklarda parallel bajarish → natijalarni birlashtirish. Misollarda xuddi shu qismlarni kichik hajmda noldan ko'ramiz: chunk (bo'lim), birlashtiriladigan holat, map-reduce va indeks.

2.9. Qachon katta vosita kerak EMAS

text
AVVAL SHULARNI SINANG:
  1. kerakli ustunlar + to'g'ri dtype   -> ko'pincha 3-10 barobar kichik
  2. filtr erta (faqat kerakli davr/hudud)
  3. chunk bilan bir o'tish (yig'indi, sanoq, Welford)
  4. namuna: model prototipi uchun 1-5% tasodifiy (yoki qatlamli) namuna
  5. sqlite/DuckDB + Parquet - bitta faylda SQL
  6. bitta KATTA mashina (bulutda 256 GB RAM - soatiga bir necha dollar)

TAQSIMLANGAN VOSITA KERAK BO'LADI:
  ma'lumot bitta mashina diskiga sig'maydi yoki doimiy o'sib boradi
  og'ir join/groupBy ko'p terabaytda, muntazam (har kuni)
  jamoa allaqachon klaster va quvurlarga ega (Databricks, EMR)

NARXI: klaster boshqaruvi, serializatsiya, shuffle, qiyin debug,
       "kichik ma'lumotda sekinroq" (ishga tushirish xarajati)

Namuna haqida alohida: ko'p ML vazifalarida sifat ma'lumot hajmi bilan logarifmik o'sadi. 100 million qatordan 1 millionlik tasodifiy namuna ko'pincha deyarli bir xil model beradi. Buni taxmin qilmang — o'rganish egri chizig'i bilan o'lchang 18.8-bob: 0.1%, 1%, 10% namunada validatsiya metrikasi. Egri chiziq tekislangan bo'lsa, butun ma'lumot uchun klaster kerak emas.

2.10. Tuzoqlar

Asosiy tuzoqlar: memory_usage() ni deep=True siz o'lchash (matn ustunlari xotirasi ko'rinmaydi); kichik butun turda hisoblash (jim to'lib ketish); noyob qiymatlari ko'p ustunni category qilish; float32 da katta summalarni aniq buxgalteriya uchun yig'ish; bo'laklarda o'rtachalar o'rtachasini yoki medianalar medianasini olish; naiv E[x^2] - E[x]^2 formulasi bilan dispersiya (katastrofik ayirish); "oxirgi k ta" ni tasodifiy namuna deb olish; Python hash() ni taqsimlangan bo'limlashda ishlatish (jarayonlar orasida farq qiladi); kombinatorni assotsiativ bo'lmagan amal bilan ishlatish; issiq kalitni e'tiborsiz qoldirish; har ustunga indeks qo'yish (yozish sekinlashadi, past selektivlikda foyda yo'q); kichik ma'lumot uchun klaster ko'tarish.


3. Tez ma'lumotnoma

python
import numpy as np
import pandas as pd

# xotira
df.memory_usage(deep=True, index=False).sum()           # HAQIQIY bayt
df["miqdor"] = pd.to_numeric(df["miqdor"], downcast="integer")
df["narx"] = df["narx"].astype(np.float32)
df["dokon"] = df["dokon"].astype("category")            # noyob qiymat kam bo'lsa

# o'qishda turlar va ustunlar
turlar = {"dokon": "category", "miqdor": "int8", "narx": "float32"}
df = pd.read_csv(yol, usecols=list(turlar), dtype=turlar)

# chunk bo'yicha oqimli agregatsiya
jami, soni, guruh = 0.0, 0, pd.Series(dtype=float)
for bolak in pd.read_csv(yol, usecols=["dokon", "summa"], chunksize=100_000):
    jami += bolak["summa"].sum()
    soni += len(bolak)
    guruh = guruh.add(bolak.groupby("dokon")["summa"].sum(), fill_value=0)
ortacha = jami / soni                                    # o'rtachalar o'rtachasi EMAS


# Welford
def welford_qosh(holat, x):
    n, orta, m2 = holat
    n += 1
    d = x - orta
    orta += d / n
    m2 += d * (x - orta)
    return n, orta, m2


# sqlite
# db.execute("CREATE INDEX idx_mijoz ON savdo(mijoz_id)")
# db.execute("EXPLAIN QUERY PLAN SELECT ... WHERE mijoz_id = ?", (42,)).fetchall()

Katta vositalar (ma'lumotnoma; bu muhitda o'rnatilmagan)

python
# PySpark - lazy: select/filter/groupBy reja quradi, write/collect bajaradi
from pyspark.sql import SparkSession, functions as F

spark = SparkSession.builder.appName("savdo").getOrCreate()
df = spark.read.parquet("s3://savdo/yil=2025/")          # bo'limlangan Parquet
natija = (df.filter(F.col("kun") >= 300)                 # predicate pushdown
            .groupBy("dokon", "guruh")                   # wide -> shuffle
            .agg(F.sum("summa").alias("tushum"),
                 F.approx_count_distinct("mijoz_id").alias("mijozlar")))
natija.write.mode("overwrite").parquet("s3://hisobot/tushum/")

# Dask - pandas API, bo'laklar, lazy
import dask.dataframe as dd
ddf = dd.read_csv("savdo-*.csv", dtype={"dokon": "category"})
tushum = ddf.groupby("dokon")["summa"].sum().compute()

# Polars - lazy so'rov, ko'p yadroli
import polars as pl
tushum = (pl.scan_parquet("savdo/*.parquet")
            .filter(pl.col("kun") >= 300)
            .group_by("dokon").agg(pl.col("summa").sum())
            .collect())

# DuckDB - fayl ustida to'g'ridan-to'g'ri SQL
import duckdb
tushum = duckdb.sql("""
    SELECT dokon, SUM(summa) AS tushum
    FROM 'savdo/*.parquet' WHERE kun >= 300 GROUP BY dokon
""").df()

# Parquet (pyarrow yoki fastparquet kerak)
df.to_parquet("savdo.parquet", index=False)
qism = pd.read_parquet("savdo.parquet", columns=["dokon", "summa"])

Qaysi vaziyatda nima

Vaziyat Birinchi urinish Keyin
Jadval RAM ga deyarli sig'adi dtype + usecols + category Polars
Savol — yig'indi/sanoq/o'rtacha chunksize + birlashtiriladigan holat DuckDB
Dispersiya oqimda Welford (+ Chan birlashtirish) —
Oqimdan tasodifiy namuna rezervuar vaznli rezervuar
Noyob foydalanuvchilar soni HyperLogLog aniq: diskda saralash
Eng ko'p uchraydiganlar Count-Min + heap aniq: guruhlash
"Bu kalit bormi?" (tez rad) Bloom filter aniq tekshiruv faqat "bor" da
Bir xil filtrli ko'p so'rov sqlite/DuckDB + indeks Parquet bo'limlash
Terabaytlar, muntazam join Spark / Dask klaster bulut ombori

Katta ma'lumot xulosasi

katta = resursga nisbatan; avval dtype, usecols, filtr, namuna
chunk: faqat birlashtiriladigan holat (sum, count, Welford, sketch)
mediana/noyob soni - bo'laklanmaydi -> sketch yoki disk
sketch: kichik xotira, isbotlangan xato, birlashtiriladi
map-reduce: map -> (kombinator) -> shuffle -> reduce; shuffle eng qimmat
indeks: selektiv so'rovga; EXPLAIN QUERY PLAN bilan tekshir

4. Batafsil misollar

Misollar real numpy/pandas/scipy/sqlite3 bilan (Python 3.14). Har misol mustaqil ishlaydi; fayllar faqat tempfile.TemporaryDirectory() ichida yaratiladi.

Misol 1 — Xotirani tejash va chunk bo'yicha oqimli agregatsiya

python
"""Xotirani tejash: dtype optimallash, CSV ni chunk bo'yicha o'qish va oqimli agregatsiya."""

import tempfile
from pathlib import Path

import numpy as np
import pandas as pd

SHAHARLAR = ["Toshkent", "Samarqand", "Buxoro", "Namangan", "Andijon"]
DOKONLAR = [f"{s}-{i:02d}" for s in SHAHARLAR for i in range(1, 5)]
GURUHLAR = ["non", "sut", "gosht", "meva", "ichimlik", "maishiy"]
TOLOV = ["naqd", "karta", "QR"]


def yarat(n, seed=0):
    """Sintetik cheklar jadvali (bir yil, 20 do'kon)."""
    rng = np.random.default_rng(seed)
    kun = np.sort(rng.integers(1, 366, n))          # fayl vaqt tartibida
    return pd.DataFrame({
        "chek_id": np.arange(n, dtype=np.int64) + 50_000_000,
        "kun": kun,
        "dokon": rng.choice(DOKONLAR, n),
        "guruh": rng.choice(GURUHLAR, n, p=[0.25, 0.2, 0.1, 0.15, 0.2, 0.1]),
        "miqdor": rng.integers(1, 13, n),
        "narx": np.round(rng.lognormal(9.3, 0.7, n)            # so'm, yil
                         * (1 + 0.5 * (kun / 365) ** 2), -2),   # oxiriga qimmat
        "chegirma": rng.choice([0.0, 0.05, 0.1, 0.2], n,
                               p=[0.7, 0.15, 0.1, 0.05]),
        "tolov": rng.choice(TOLOV, n, p=[0.35, 0.5, 0.15]),
    })


def optimallash(df):
    """Butun -> eng kichik butun, kasr -> float32, kam noyob matn -> category."""
    natija = df.copy()
    for ustun in natija.columns:
        s = natija[ustun]
        if pd.api.types.is_integer_dtype(s):
            natija[ustun] = pd.to_numeric(s, downcast="integer")
        elif pd.api.types.is_float_dtype(s):
            natija[ustun] = s.astype(np.float32)
        elif s.nunique() / len(s) < 0.5:
            natija[ustun] = s.astype("category")
    return natija


def tushum(df):
    return (df["miqdor"].astype(np.float64) * df["narx"].astype(np.float64)
            * (1 - df["chegirma"].astype(np.float64)))


def main() -> None:
    n = 400_000
    df = yarat(n)
    opt = optimallash(df)

    print("=== 1. Xotira: ustunlar bo'yicha (memory_usage(deep=True)) ===")
    oldin = df.memory_usage(deep=True, index=False)
    keyin = opt.memory_usage(deep=True, index=False)
    sayoz = df.memory_usage(deep=False, index=False)
    print(f"  {'ustun':<9} {'tur':>8} {'bayt':>11} {'yangi tur':>10} "
          f"{'bayt':>10}")
    for u in df.columns:
        print(f"  {u:<9} {str(df[u].dtype):>8} {oldin[u]:>11,} "
              f"{str(opt[u].dtype):>10} {keyin[u]:>10,}")
    print(f"  jami: {oldin.sum():,} -> {keyin.sum():,} bayt "
          f"({oldin.sum() / keyin.sum():.1f} barobar kichik)")
    print(f"  deep=False (aldaydi): {sayoz.sum():,} bayt")

    print("\n=== 2. Optimallash to'g'riligini tekshirish ===")
    t64 = tushum(df).sum()
    t32 = (opt["miqdor"] * opt["narx"] * (1 - opt["chegirma"])).sum()
    print(f"  tushum float64: {t64:,.0f} so'm")
    print(f"  tushum float32: {float(t32):,.0f} so'm "
          f"(nisbiy farq {abs(float(t32) - t64) / t64:.1e})")
    print(f"  narx float32 da aniqmi: "
          f"{bool((opt['narx'].astype(np.float64) == df['narx']).all())}")
    print(f"  miqdor yig'indisi: int64 {int(df['miqdor'].sum()):,}, "
          f"int8 {int(opt['miqdor'].sum()):,}")
    toldi = opt["miqdor"] * 20
    print(f"  TUZOQ: int8 miqdor * 20 -> max {int(toldi.max())}, "
          f"min {int(toldi.min())} (tur {toldi.dtype})")
    kod = "CHK" + df["chek_id"].astype(str)
    print(f"  noyob matn (chek kodi): str {kod.memory_usage(deep=True, index=False):,}"
          f" bayt, category "
          f"{kod.astype('category').memory_usage(deep=True, index=False):,} bayt")

    turlar = {"chek_id": "int32", "kun": "int16", "dokon": "category",
              "guruh": "category", "miqdor": "int8", "narx": "float32",
              "chegirma": "float32", "tolov": "category"}
    kerakli = ["dokon", "guruh", "miqdor", "narx", "chegirma"]
    oqim_turlar = {"miqdor": "int64", "narx": "float64", "chegirma": "float64"}
    with tempfile.TemporaryDirectory() as papka:
        yol = Path(papka) / "cheklar.csv"
        df.to_csv(yol, index=False)

        print("\n=== 3. CSV ni o'qish: standart, turlar bilan, usecols ===")
        print(f"  fayl hajmi: {yol.stat().st_size:,} bayt")
        a = pd.read_csv(yol)
        b = pd.read_csv(yol, dtype=turlar)
        c = pd.read_csv(yol, usecols=kerakli,
                        dtype={u: turlar[u] for u in kerakli})
        for nom, d in [("standart", a), ("dtype bilan", b),
                       ("dtype + usecols", c)]:
            print(f"  {nom:<16} {d.memory_usage(deep=True, index=False).sum():>12,}"
                  f" bayt, {d.shape[1]} ustun")

        print("\n=== 4. Chunk bo'yicha oqimli agregatsiya (50 000 qator) ===")
        jami, soni, bolaklar, maks_bayt = 0.0, 0, 0, 0
        dokon_sum = pd.Series(dtype=np.float64)
        guruh_sum = pd.Series(dtype=np.float64)
        guruh_n = pd.Series(dtype=np.float64)
        bolak_med, bolak_p99 = [], []
        for bolak in pd.read_csv(yol, usecols=kerakli, dtype=oqim_turlar,
                                 chunksize=50_000):
            t = tushum(bolak)
            jami += t.sum()
            soni += len(t)
            bolaklar += 1
            maks_bayt = max(maks_bayt,
                            int(bolak.memory_usage(deep=True, index=False).sum()))
            dokon_sum = dokon_sum.add(t.groupby(bolak["dokon"]).sum(),
                                      fill_value=0)
            guruh_sum = guruh_sum.add(t.groupby(bolak["guruh"]).sum(),
                                      fill_value=0)
            guruh_n = guruh_n.add(t.groupby(bolak["guruh"]).size(),
                                  fill_value=0)
            bolak_med.append(float(t.median()))
            bolak_p99.append(float(t.quantile(0.99)))
        toliq = pd.read_csv(yol, usecols=kerakli, dtype=oqim_turlar)
        tt = tushum(toliq)
        print(f"  bo'laklar: {bolaklar}, bitta bo'lak maks {maks_bayt:,} bayt")
        print(f"  o'rtacha chek: oqim {jami / soni:,.4f}, to'liq {tt.mean():,.4f}")
        d_tol = tt.groupby(toliq["dokon"]).sum()
        g_tol = tt.groupby(toliq["guruh"]).mean()
        d_farq = float((dokon_sum - d_tol).abs().max() / d_tol.abs().max())
        g_farq = float(((guruh_sum / guruh_n) - g_tol).abs().max())
        print(f"  do'kon yig'indilari: maks nisbiy farq {d_farq:.1e}")
        print(f"  guruh o'rtachalari:  maks farq {g_farq:.1e} so'm")
        mos = (np.isclose(jami / soni, tt.mean(), rtol=1e-12)
               and d_farq < 1e-12 and g_farq < 1e-6)
        print(f"  oqim natijasi to'liq o'qish bilan mos: {mos}")
        print(f"  mediana: to'liq {tt.median():,.1f}; bo'lak medianalari: "
              f"o'rtachasi {np.mean(bolak_med):,.1f}, medianasi "
              f"{np.median(bolak_med):,.1f}")
        print(f"  99-persentil: to'liq {tt.quantile(0.99):,.1f}; bo'laklar "
              f"o'rtachasi {np.mean(bolak_p99):,.1f}")
        eng = d_tol.sort_values(ascending=False)
        print(f"  eng katta tushum: {eng.index[0]} {eng.iloc[0]:,.0f}, "
              f"eng kichik: {eng.index[-1]} {eng.iloc[-1]:,.0f}")
    print("  ⭐ Bo'laklarda faqat birlashtiriladigan holat: yig'indi, sanoq")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Xotira: ustunlar bo'yicha (memory_usage(deep=True)) ===
  ustun          tur        bayt  yangi tur       bayt
  chek_id      int64   3,200,000      int32  1,600,000
  kun          int64   3,200,000      int16    800,000
  dokon          str  23,838,808   category    401,192
  guruh          str  21,501,618   category    400,324
  miqdor       int64   3,200,000       int8    400,000
  narx       float64   3,200,000    float32  1,600,000
  chegirma   float64   3,200,000    float32  1,600,000
  tolov          str  21,279,529   category    400,158
  jami: 82,619,955 -> 7,201,674 bayt (11.5 barobar kichik)
  deep=False (aldaydi): 25,600,000 bayt

=== 2. Optimallash to'g'riligini tekshirish ===
  tushum float64: 41,160,657,890 so'm
  tushum float32: 41,160,658,944 so'm (nisbiy farq 2.6e-08)
  narx float32 da aniqmi: True
  miqdor yig'indisi: int64 2,598,836, int8 2,598,836
  TUZOQ: int8 miqdor * 20 -> max 120, min -116 (tur int8)
  noyob matn (chek kodi): str 24,000,000 bayt, category 25,600,000 bayt

=== 3. CSV ni o'qish: standart, turlar bilan, usecols ===
  fayl hajmi: 20,115,922 bayt
  standart           82,619,955 bayt, 8 ustun
  dtype bilan         7,202,510 bayt, 8 ustun
  dtype + usecols     4,402,244 bayt, 5 ustun

=== 4. Chunk bo'yicha oqimli agregatsiya (50 000 qator) ===
  bo'laklar: 8, bitta bo'lak maks 6,868,073 bayt
  o'rtacha chek: oqim 102,901.6447, to'liq 102,901.6447
  do'kon yig'indilari: maks nisbiy farq 0.0e+00
  guruh o'rtachalari:  maks farq 0.0e+00 so'm
  oqim natijasi to'liq o'qish bilan mos: True
  mediana: to'liq 70,200.0; bo'lak medianalari: o'rtachasi 70,833.1, medianasi 68,700.0
  99-persentil: to'liq 529,204.0; bo'laklar o'rtachasi 521,852.7
  eng katta tushum: Andijon-04 2,088,367,005, eng kichik: Andijon-03 2,030,503,430
  ⭐ Bo'laklarda faqat birlashtiriladigan holat: yig'indi, sanoq

Natija tahlili.

1-bo'lim — 400 ming chek, 8 ustun. Standart turlar bilan jadval 82.6 MB egallaydi, va uning 66.6 MB i — uchta matn ustuni (dokon, guruh, tolov): har bir qiymat alohida Python satri, qator boshiga ~55-60 bayt. category ga o'tkazilgach, har biri 0.4 MB ga tushdi — qator boshiga 1 bayt kod va 20 ta (yoki 6, 3 ta) noyob satr bir marta. Butun sonlar int32/int16/int8 ga, kasrlar float32 ga tushdi. Jami 11.5 barobar kichik. E'tibor bering: deep=False 25.6 MB deydi — matn ustunlari uchun faqat 8 baytlik ko'rsatkichlar sanalgan, haqiqiy xotiraning uchdan biri.

2-bo'lim — tejash bepul emas, uni tekshirish kerak. float32 dagi umumiy tushum float64 dan 2.6e-08 nisbiy farq qiladi — tahlil uchun ahamiyatsiz, lekin tiyingacha aniq buxgalteriya uchun yaroqsiz. Narxlar 100 so'mga yaxlitlangan butun sonlar, float32 da aniq saqlandi (True). int8 yig'indisi to'g'ri (2,598,836 — numpy yig'ishda kengroq turga o'tadi), lekin ko'paytirish int8 da qoldi: miqdor * 20 ning maksimumi 240 bo'lishi kerak edi, natija 120, minimumi -116 — jim to'lib ketish, hech qanday ogohlantirishsiz. Va noyob matn (CHK50000000 kabi chek kodlari) uchun category yordam bermadi: 24.0 MB → 25.6 MB — kattalashdi.

3-bo'lim — 20.1 MB lik CSV standart o'qishda xotirada 82.6 MB — fayldan 4 barobar katta. dtype ni o'qish paytida berish 7.2 MB, faqat kerakli 5 ustun esa 4.4 MB — faylning o'zidan ham kichik. Bu — eng arzon va eng samarali tejash, u kodni deyarli o'zgartirmaydi.

4-bo'lim — fayl 8 bo'lakda o'qildi, xotirada bir vaqtning o'zida faqat bitta bo'lak (6.9 MB). Yig'indi, sanoq va (yig'indi, sanoq) orqali o'rtachalar butun faylni o'qigan natija bilan aynan mos: o'rtacha chek 102,901.6447 ikkala usulda, do'kon va guruh bo'yicha farq 0. Mediana esa bo'laklanmaydi: fayl vaqt tartibida, narxlar yil oxiriga qimmatlashadi, shuning uchun bo'laklar taqsimoti har xil. To'liq mediana 70,200, bo'lak medianalarining o'rtachasi 70,833, medianasi 68,700 — ikkalasi ham noto'g'ri, va qaysi tomonga adashishi oldindan ma'lum emas. 99-persentilda ham xuddi shunday (529,204 va 521,853). Aniq kvantil uchun barcha qiymatlar yoki diskda saralash kerak; taxminiy uchun — kvantil sketch (t-digest, KLL).

Misol 2 — Welford, sonli barqarorlik va rezervuar namuna

python
"""Bir o'tishli algoritmlar: Welford (va Chan birlashtirishi), rezervuar namuna."""

import random

import numpy as np
from scipy import stats


class Welford:
    """O'rtacha va dispersiya - bitta o'tishda, O(1) xotira."""

    def __init__(self):
        self.n, self.orta, self.m2 = 0, 0.0, 0.0

    def qosh(self, x):
        self.n += 1
        delta = x - self.orta
        self.orta += delta / self.n
        self.m2 += delta * (x - self.orta)

    @property
    def dispersiya(self):
        return self.m2 / (self.n - 1)


def birlashtir(a, b):
    """Chan va boshq.: ikki (n, orta, m2) holatini birlashtirish."""
    n = a[0] + b[0]
    delta = b[1] - a[1]
    orta = a[1] + delta * b[0] / n
    m2 = a[2] + b[2] + delta * delta * a[0] * b[0] / n
    return n, orta, m2


def naiv(xs):
    """sum x va sum x^2 - bitta o'tish, lekin katastrofik ayirish."""
    n, s, s2 = 0, 0.0, 0.0
    for x in xs:
        n += 1
        s += x
        s2 += x * x
    return s / n, (s2 - s * s / n) / (n - 1)


def rezervuar(oqim, k, rnd):
    """Algorithm R: har element k/N ehtimol bilan."""
    namuna = []
    for i, x in enumerate(oqim):
        if i < k:
            namuna.append(x)
        else:
            j = rnd.randrange(i + 1)
            if j < k:
                namuna[j] = x
    return namuna


def yangiga_moyil(oqim, k, rnd):
    """NOTO'G'RI: har yangi element 1/2 ehtimol bilan kiradi."""
    namuna = []
    for i, x in enumerate(oqim):
        if i < k:
            namuna.append(x)
        elif rnd.random() < 0.5:
            namuna[rnd.randrange(k)] = x
    return namuna


def main() -> None:
    rng = np.random.default_rng(0)
    n = 200_000
    shovqin = rng.normal(0, 50, n)                 # std 50 so'm

    print("=== 1. Dispersiya: naiv formula va Welford (haqiqiy var ~2500) ===")
    print(f"  {'siljish':>8} {'ikki o_tish':>12} {'naiv':>14} {'Welford':>12} "
          f"{'naiv xato':>10} {'W xato':>9}")
    for siljish in [0.0, 1e6, 1e8, 1e9]:
        x = siljish + shovqin
        xs = x.tolist()
        ikki = float(np.var(x, ddof=1))
        _, v_naiv = naiv(xs)
        w = Welford()
        for v in xs:
            w.qosh(v)
        print(f"  {siljish:>8.0e} {ikki:>12.4f} {v_naiv:>14.4f} "
              f"{w.dispersiya:>12.4f} {abs(v_naiv - ikki) / ikki:>10.1e} "
              f"{abs(w.dispersiya - ikki) / ikki:>9.1e}")

    print("\n=== 2. Bo'laklar holatini birlashtirish (Chan) ===")
    x = 1e9 + shovqin
    bolaklar = np.array_split(x, 8)
    holatlar = []
    for b in bolaklar:
        orta = float(b.mean())
        holatlar.append((len(b), orta, float(np.sum((b - orta) ** 2))))
    jami = holatlar[0]
    for h in holatlar[1:]:
        jami = birlashtir(jami, h)
    ikki = float(np.var(x, ddof=1))
    print(f"  8 bo'lak, hajmlar {[len(b) for b in bolaklar][:3]} (+5 ta)")
    print(f"  birlashtirilgan var {jami[2] / (jami[0] - 1):.6f}, "
          f"to'liq ikki o'tish {ikki:.6f}")
    print(f"  o'rtacha: {jami[1]:.6f} vs {float(x.mean()):.6f}")
    print(f"  mos: {abs(jami[2] / (jami[0] - 1) - ikki) / ikki < 1e-9}")

    print("\n=== 3. Rezervuar namuna: bir xillik testi ===")
    N, k, takror = 200, 10, 4000
    rnd = random.Random(1)
    sanoq_r = np.zeros(N)
    sanoq_y = np.zeros(N)
    for _ in range(takror):
        sanoq_r[rezervuar(range(N), k, rnd)] += 1
        sanoq_y[yangiga_moyil(range(N), k, rnd)] += 1
    kutilgan = takror * k / N
    print(f"  oqim N={N}, namuna k={k}, {takror} takror; "
          f"har pozitsiya kutilgan {kutilgan:.0f} marta")
    print(f"  {'usul':<16} {'1-50':>6} {'51-100':>7} {'101-150':>8} "
          f"{'151-200':>8} {'chi^2':>9} {'p':>9}")
    for nom, s in [("rezervuar (R)", sanoq_r), ("yangiga moyil", sanoq_y)]:
        chorak = [s[i:i + 50].sum() / s.sum() for i in range(0, N, 50)]
        r = stats.chisquare(s)
        print(f"  {nom:<16} {chorak[0]:>6.3f} {chorak[1]:>7.3f} "
              f"{chorak[2]:>8.3f} {chorak[3]:>8.3f} {r.statistic:>9.1f} "
              f"{r.pvalue:>9.2e}")
    for nom, s in [("rezervuar (R)", sanoq_r), ("yangiga moyil", sanoq_y)]:
        bir_xil = stats.chisquare(s).pvalue > 0.01
        print(f"  {nom}: bir xil taqsimot rad etilmadi: {bir_xil}")

    print("\n=== 4. Amaliy: 300 000 tranzaksiyadan 2 000 lik namuna ===")
    summalar = np.round(rng.lognormal(11.5, 1.0, 300_000), -2)
    namuna = np.array(rezervuar(summalar.tolist(), 2000, random.Random(7)))
    se = namuna.std(ddof=1) / np.sqrt(len(namuna))
    print(f"  haqiqiy o'rtacha {summalar.mean():,.0f}, namuna "
          f"{namuna.mean():,.0f} +- {2 * se:,.0f} (2*SE)")
    print(f"  haqiqiy mediana {np.median(summalar):,.0f}, namuna "
          f"{np.median(namuna):,.0f}")
    ichida = abs(namuna.mean() - summalar.mean()) <= 2 * se
    print(f"  haqiqiy o'rtacha 2*SE oralig'ida: {ichida}")
    print(f"  xotira: {len(namuna)} element (oqim {len(summalar):,})")
    print("  ⭐ Oqimda: Welford - barqaror; rezervuar - haqiqatan tasodifiy")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Dispersiya: naiv formula va Welford (haqiqiy var ~2500) ===
   siljish  ikki o_tish           naiv      Welford  naiv xato    W xato
     0e+00    2506.0158      2506.0158    2506.0158    2.3e-14   4.2e-15
     1e+06    2506.0158      2506.0330    2506.0158    6.9e-06   3.2e-14
     1e+08    2506.0158      2293.7715    2506.0158    8.5e-02   1.0e-10
     1e+09    2506.0158     17616.1649    2506.0158    6.0e+00   1.5e-10

=== 2. Bo'laklar holatini birlashtirish (Chan) ===
  8 bo'lak, hajmlar [25000, 25000, 25000] (+5 ta)
  birlashtirilgan var 2506.015807, to'liq ikki o'tish 2506.015807
  o'rtacha: 1000000000.006534 vs 1000000000.006534
  mos: True

=== 3. Rezervuar namuna: bir xillik testi ===
  oqim N=200, namuna k=10, 4000 takror; har pozitsiya kutilgan 200 marta
  usul               1-50  51-100  101-150  151-200     chi^2         p
  rezervuar (R)     0.252   0.250    0.248    0.250     218.2  1.66e-01
  yangiga moyil     0.000   0.006    0.071    0.923  165435.3  0.00e+00
  rezervuar (R): bir xil taqsimot rad etilmadi: True
  yangiga moyil: bir xil taqsimot rad etilmadi: False

=== 4. Amaliy: 300 000 tranzaksiyadan 2 000 lik namuna ===
  haqiqiy o'rtacha 163,360, namuna 171,358 +- 9,968 (2*SE)
  haqiqiy mediana 98,900, namuna 100,900
  haqiqiy o'rtacha 2*SE oralig'ida: True
  xotira: 2000 element (oqim 300,000)
  ⭐ Oqimda: Welford - barqaror; rezervuar - haqiqatan tasodifiy

Natija tahlili.

1-bo'lim — bir xil shovqin (std 50 so'm, dispersiya 2506.0158), faqat qiymatlarga katta o'zgarmas son qo'shilgan (masalan, hisob qoldig'i ~1 milliard so'm atrofida). Siljish 0 da hamma usul bir xil. 1e6 da naiv formula nisbiy xatosi 6.9e-06 — hali sezilmaydi. 1e8 da naiv javob 2293.77 (8.5% xato), 1e9 da esa 17616.16 — haqiqiydan 7 barobar katta (nisbiy xato 6.0). Sabab — katastrofik ayirish: s2 va s^2/n ikkalasi ~`2e23, float64 esa ~16 o'nlik raqamni saqlaydi; ayirmada faqat yaxlitlash shovqini qoladi. Welford esa har siljishda to'g'ri (nisbiy xato <= 1.5e-10), chunki u faqat kichik og'ishlarni yig'adi. Naiv formula ba'zan hatto **manfiy** dispersiya beradi — keyin sqrtdanan`.

2-bo'lim — ma'lumot 8 bo'lakka bo'lindi (8 ta mashina yoki 8 ta chunk deb o'ylang), har bo'lakda (n, o'rtacha, M2) hisoblandi va Chan formulasi bilan birlashtirildi. Natija to'liq ikki o'tishli hisob bilan aynan mos: 2506.015807. Bu — map-reduce da dispersiya hisoblashning to'g'ri usuli: kombinator (n, o'rtacha, M2) uchligini chiqaradi.

3-bo'lim — Algorithm R 4000 takrorda har bir pozitsiyani deyarli teng tanladi: to'rt chorak ulushlari 0.252, 0.250, 0.248, 0.250; chi-kvadrat testi bir xillikni rad etmadi (p = 0.166). "Yangiga moyil" usul (har yangi element 1/2 ehtimol bilan kiradi) tabiiy ko'rinadi, lekin namunaning 92.3% i oqimning oxirgi choragidan, birinchi choragidan esa 0.0% — bu tasodifiy namuna emas, "oxirgi voqealar" namunasi. Oqimda vaqt bilan o'zgaradigan har qanday narsa (narx, xulq) bunday namunada buziladi.

4-bo'lim — 300 000 tranzaksiyadan atigi 2 000 ta element xotirada. Namuna o'rtachasi 171,358 +- 9,968, haqiqiy 163,360 — oraliq ichida. Mediana 100,900 va 98,900. Bu safar o'rtacha ko'proq adashdi (4.9% va 2.0%): qiyshiq taqsimotda o'rtacha dumdagi kamyob katta summalarga sezgir, uning SE si ham shunga katta. Namuna hajmi va aniqlik bog'liqligi — 04-qismdagi namuna olish nazariyasining o'zi, faqat endi namuna oqimdan olindi.

Misol 3 — Taxminiy tuzilmalar: HyperLogLog, Count-Min Sketch, Bloom filter

python
"""Sketch lar noldan: HyperLogLog, Count-Min Sketch, Bloom filter (numpy bilan)."""

import math
import sys

import numpy as np

MASKA = (1 << 64) - 1


def xesh(x, tuz=0):
    """splitmix64: butun sonlar massivi -> 64 bitli xesh (deterministik)."""
    z = x.astype(np.uint64) + np.uint64((0x9E3779B97F4A7C15 * (tuz + 1)) & MASKA)
    z = (z ^ (z >> np.uint64(30))) * np.uint64(0xBF58476D1CE4E5B9)
    z = (z ^ (z >> np.uint64(27))) * np.uint64(0x94D049BB133111EB)
    return z ^ (z >> np.uint64(31))


def bit_uzunligi(x):
    """Har element uchun int.bit_length() - vektorlashgan, aniq."""
    x = x.copy()
    n = np.zeros(x.shape, dtype=np.int64)
    for s in (32, 16, 8, 4, 2, 1):
        katta = x >= (np.uint64(1) << np.uint64(s))
        n += np.where(katta, s, 0)
        x = np.where(katta, x >> np.uint64(s), x)
    return n + (x > 0)


class HyperLogLog:
    def __init__(self, p, tuz=0):
        self.p, self.m, self.tuz = p, 1 << p, tuz
        self.reg = np.zeros(self.m, dtype=np.uint8)

    def qosh(self, ids):
        h = xesh(ids, self.tuz)
        idx = (h >> np.uint64(64 - self.p)).astype(np.int64)
        qolgan = h & np.uint64((1 << (64 - self.p)) - 1)
        rho = (64 - self.p) - bit_uzunligi(qolgan) + 1
        np.maximum.at(self.reg, idx, rho.astype(np.uint8))

    def baho(self):
        m = self.m
        alfa = 0.7213 / (1 + 1.079 / m)
        e = alfa * m * m / float(np.sum(2.0 ** -self.reg.astype(np.float64)))
        bosh = int(np.count_nonzero(self.reg == 0))
        if e <= 2.5 * m and bosh > 0:            # kichik diapazon tuzatishi
            e = m * math.log(m / bosh)
        return e

    def birlashtir(self, boshqa):
        yangi = HyperLogLog(self.p, self.tuz)
        yangi.reg = np.maximum(self.reg, boshqa.reg)
        return yangi


class CountMin:
    def __init__(self, w, d, tuz=100):
        self.w, self.d, self.tuz = w, d, tuz
        self.jadval = np.zeros((d, w), dtype=np.int64)

    def _ustun(self, ids, j):
        return (xesh(ids, self.tuz + j) % np.uint64(self.w)).astype(np.int64)

    def qosh(self, ids):
        for j in range(self.d):
            self.jadval[j] += np.bincount(self._ustun(ids, j), minlength=self.w)

    def baho(self, ids):
        return np.min([self.jadval[j][self._ustun(ids, j)]
                       for j in range(self.d)], axis=0)


class Bloom:
    def __init__(self, m, k):
        self.m, self.k = m, k
        self.bit = np.zeros(m, dtype=bool)

    def _joylar(self, ids):
        h1 = xesh(ids, 7)
        h2 = xesh(ids, 8) | np.uint64(1)
        return [((h1 + np.uint64(i) * h2) % np.uint64(self.m)).astype(np.int64)
                for i in range(self.k)]

    def qosh(self, ids):
        for j in self._joylar(ids):
            self.bit[j] = True

    def bormi(self, ids):
        natija = np.ones(len(ids), dtype=bool)
        for j in self._joylar(ids):
            natija &= self.bit[j]
        return natija


def main() -> None:
    rng = np.random.default_rng(0)

    print("=== 1. HyperLogLog: kunlik noyob foydalanuvchilar ===")
    faollik = rng.lognormal(0, 1.5, 600_000)
    ehtimol = faollik / faollik.sum()
    kun1 = rng.choice(600_000, 1_500_000, p=ehtimol)
    kun2 = rng.choice(600_000, 1_500_000, p=ehtimol) + 200_000
    aniq1 = len(np.unique(kun1))
    toplam = set(kun1.tolist())
    set_bayt = sys.getsizeof(toplam) + sum(sys.getsizeof(v) for v in toplam)
    print(f"  hodisalar {len(kun1):,}, aniq noyob {aniq1:,}")
    print(f"  aniq (Python set): {set_bayt:,} bayt")
    print(f"  {'p':>3} {'registr':>8} {'bayt':>7} {'baho':>10} "
          f"{'xato':>7} {'nazariya':>9}")
    for p in [8, 10, 12, 14]:
        hll = HyperLogLog(p)
        hll.qosh(kun1)
        e = hll.baho()
        print(f"  {p:>3} {hll.m:>8,} {hll.m:>7,} {e:>10,.0f} "
              f"{(e - aniq1) / aniq1:>+7.2%} {1.04 / math.sqrt(hll.m):>9.2%}")

    print("\n=== 2. HLL xatosi: 30 xil xesh tuzi bo'yicha (p=10 va 12) ===")
    noyob = np.unique(kun1)          # takror registrni o'zgartirmaydi
    for p in [10, 12]:
        xatolar = []
        for tuz in range(30):
            hll = HyperLogLog(p, tuz)
            hll.qosh(noyob)
            xatolar.append((hll.baho() - aniq1) / aniq1)
        xatolar = np.array(xatolar)
        print(f"  p={p}: o'rtacha xato {xatolar.mean():+.3%}, RMS "
              f"{np.sqrt(np.mean(xatolar ** 2)):.3%}, nazariya "
              f"{1.04 / math.sqrt(1 << p):.3%}")
    h1, h2 = HyperLogLog(12), HyperLogLog(12)
    h1.qosh(kun1)
    h2.qosh(kun2)
    aniq_birl = len(np.union1d(kun1, kun2))
    e = h1.birlashtir(h2).baho()
    print(f"  ikki kun birlashmasi: aniq {aniq_birl:,}, HLL (max registr) "
          f"{e:,.0f}, xato {(e - aniq_birl) / aniq_birl:+.2%}")

    print("\n=== 3. Count-Min Sketch: mahsulot ko'rishlari ===")
    n_mah = 100_000
    zipf = 1 / np.arange(1, n_mah + 1) ** 1.1
    korish = rng.choice(n_mah, 2_000_000, p=zipf / zipf.sum())
    haqiqiy = np.bincount(korish, minlength=n_mah)
    bor = np.flatnonzero(haqiqiy > 0)
    N = len(korish)
    print(f"  ko'rishlar N={N:,}, ko'rilgan mahsulotlar {len(bor):,}")
    print(f"  {'eps':>7} {'d':>2} {'w':>6} {'bayt':>9} {'min xato':>8} "
          f"{'<=eps*N':>8} {'o_rt xato':>9} {'kamyob nisbiy':>13}")
    for eps, d in [(0.001, 1), (0.001, 4), (0.0001, 4)]:
        w = math.ceil(math.e / eps)
        cms = CountMin(w, d)
        cms.qosh(korish)
        b = cms.baho(bor)
        xato = b - haqiqiy[bor]
        kamyob = haqiqiy[bor] <= 5
        nisbiy = float(np.mean(xato[kamyob] / haqiqiy[bor][kamyob]))
        print(f"  {eps:>7} {d:>2} {w:>6,} {d * w * 4:>9,} {int(xato.min()):>8} "
              f"{np.mean(xato <= eps * N):>8.3f} {xato.mean():>9.1f} "
              f"{nisbiy:>13.1f}")
    top_aniq = set(bor[np.argsort(-haqiqiy[bor], kind="stable")[:10]].tolist())
    top_cms = set(bor[np.argsort(-b, kind="stable")[:10]].tolist())
    print(f"  top-10 mos kelishi (eps=0.0001, d=4): {len(top_aniq & top_cms)}/10")
    print(f"  aniq lug'at: {len(bor):,} kalit x (8+8) bayt ~ "
          f"{len(bor) * 16:,} bayt (kamida)")

    print("\n=== 4. Bloom filter: qora ro'yxatdagi telefonlar ===")
    telefonlar = rng.choice(np.arange(998_900_000_000, 998_999_999_999),
                            400_000, replace=False)
    royxat, begona = telefonlar[:100_000], telefonlar[100_000:]
    n = len(royxat)
    print(f"  ro'yxatda n={n:,}, tekshiriladigan begona {len(begona):,}")
    print(f"  {'bit/el':>6} {'k':>3} {'bayt':>9} {'FPR o_lchangan':>15} "
          f"{'FPR nazariya':>13} {'yolg_on manfiy':>15}")
    for b_el, k in [(4, 3), (8, 6), (10, 7), (16, 11), (8, 1), (8, 12)]:
        m = b_el * n
        bf = Bloom(m, k)
        bf.qosh(royxat)
        fpr = float(np.mean(bf.bormi(begona)))
        nazariya = (1 - math.exp(-k * n / m)) ** k
        yolgon_manfiy = int(np.sum(~bf.bormi(royxat)))
        print(f"  {b_el:>6} {k:>3} {m // 8:>9,} {fpr:>15.4f} "
              f"{nazariya:>13.4f} {yolgon_manfiy:>15}")
    print(f"  optimal k = (m/n) ln2: 8 bit -> {8 * math.log(2):.1f}, "
          f"10 bit -> {10 * math.log(2):.1f}")
    print("  ⭐ Sketch: kichik xotira, oldindan ma'lum xato, birlashtiriladi")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. HyperLogLog: kunlik noyob foydalanuvchilar ===
  hodisalar 1,500,000, aniq noyob 335,460
  aniq (Python set): 26,170,312 bayt
    p  registr    bayt       baho    xato  nazariya
    8      256     256    329,306  -1.83%     6.50%
   10    1,024   1,024    327,440  -2.39%     3.25%
   12    4,096   4,096    335,930  +0.14%     1.62%
   14   16,384  16,384    336,431  +0.29%     0.81%

=== 2. HLL xatosi: 30 xil xesh tuzi bo'yicha (p=10 va 12) ===
  p=10: o'rtacha xato +0.482%, RMS 3.309%, nazariya 3.250%
  p=12: o'rtacha xato -0.175%, RMS 1.580%, nazariya 1.625%
  ikki kun birlashmasi: aniq 546,072, HLL (max registr) 549,674, xato +0.66%

=== 3. Count-Min Sketch: mahsulot ko'rishlari ===
  ko'rishlar N=2,000,000, ko'rilgan mahsulotlar 82,570
      eps  d      w      bayt min xato  <=eps*N o_rt xato kamyob nisbiy
    0.001  1  2,719    10,876       25    0.964     728.9         444.6
    0.001  4  2,719    43,504       25    1.000     133.7          80.9
   0.0001  4 27,183   434,928        0    1.000       4.0           2.4
  top-10 mos kelishi (eps=0.0001, d=4): 10/10
  aniq lug'at: 82,570 kalit x (8+8) bayt ~ 1,321,120 bayt (kamida)

=== 4. Bloom filter: qora ro'yxatdagi telefonlar ===
  ro'yxatda n=100,000, tekshiriladigan begona 300,000
  bit/el   k      bayt  FPR o_lchangan  FPR nazariya  yolg_on manfiy
       4   3    50,000          0.1464        0.1469               0
       8   6   100,000          0.0220        0.0216               0
      10   7   125,000          0.0082        0.0082               0
      16  11   200,000          0.0005        0.0005               0
       8   1   100,000          0.1179        0.1175               0
       8  12   100,000          0.0487        0.0483               0
  optimal k = (m/n) ln2: 8 bit -> 5.5, 10 bit -> 6.9
  ⭐ Sketch: kichik xotira, oldindan ma'lum xato, birlashtiriladi

Natija tahlili.

1-bo'lim — 1.5 million hodisada 335,460 noyob foydalanuvchi. Aniq hisob uchun Python set 26.2 MB oldi (jadval + har bir int obyekti). HyperLogLog p = 12 da 4 096 bayt bilan 335,930 deb baholadi — xato +0.14%, ya'ni ~6 400 barobar kam xotira. Kichik p da xato kattaroq (p = 10 da -2.39%) — lekin bu nazariy standart xato (3.25%) ichida. Bitta o'lchashdagi xato tasodifiy; uni nazariya bilan solishtirish uchun ko'p takror kerak.

2-bo'lim — aynan shu: 30 xil xesh tuzi bilan. p = 10 da RMS xato 3.309% (nazariya 3.250%), p = 12 da 1.580% (nazariya 1.625%) — formula 1.04 / sqrt(m) ishlaydi, o'rtacha xato esa nolga yaqin (siljish yo'q). Muhim tafsilot: bu yerda sketch ga faqat noyob id lar berildi — takror element registrni o'zgartirmaydi (max), shuning uchun natija oqimdagi bilan bir xil. Ikki kunning sketch lari registrlar bo'yicha max bilan birlashtirildi va ikki kunlik noyob soni +0.66% xato bilan topildi (549,674 va 546,072) — xom ma'lumotga qaytmasdan. Kunlik sketch larni saqlab, istalgan davr uchun noyob foydalanuvchilarni hisoblash mumkin.

3-bo'lim — Count-Min Sketch. Xato hech qachon manfiy emas (eng kichik xato 25, 25, 0 — hamma baho >= haqiqiy). d = 1 (bitta qator) da mahsulotlarning 96.4% ida xato eps*N = 2000 dan kichik, d = 4 da 100% — bir necha mustaqil xeshning minimumi to'qnashuvlarni keskin kamaytiradi (o'rtacha xato 728.9 → 133.7). eps = 0.0001 da o'rtacha xato atigi 4.0 ko'rish, xotira 435 KB (aniq lug'at kamida 1.3 MB). Lekin kamyob mahsulotlar (<= 5 ko'rish) uchun nisbiy xato katta: eps = 0.001, d = 4 da o'rtacha 80.9 barobar ortiqcha, eng aniq sozlamada ham 2.4 barobar. Count-Min — "eng ko'p uchraydiganlar" uchun vosita: top-10 to'liq topildi (10/10), dumdagi kamyob elementlar uchun esa emas.

4-bo'lim — Bloom filter. O'lchangan yolg'on musbat ulushi nazariya bilan juda yaqin: element boshiga 8 bit, k = 6 da 0.0220 va 0.0216; 10 bitda 0.0082 va 0.0082; 16 bitda 0.0005. Yolg'on manfiy hech qachon yo'q (0) — ro'yxatdagi raqam doimo "bor" deb topiladi. k ni noto'g'ri tanlash qimmat: 8 bitda k = 1 bo'lsa 0.1179, k = 12 bo'lsa 0.0487 — optimal k = 5.5 atrofidagidan 2-5 barobar yomon. Amalda Bloom filter "tez rad etish" uchun: 100 000 raqamlik ro'yxat 125 KB da, begona raqamlarning 99% dan ortig'iga "yo'q" javobi bazaga bormasdan beriladi, faqat "bor" javobida aniq tekshiruv qilinadi.

Misol 4 — Map-reduce noldan va sqlite bilan diskdagi so'rovlar

python
"""Map-reduce (ketma-ket simulyatsiya) va sqlite: indeks, EXPLAIN QUERY PLAN."""

import sqlite3
import tempfile
import zlib
from collections import Counter, defaultdict
from contextlib import closing
from pathlib import Path

import numpy as np
import pandas as pd

ASOSIY = ["va", "juda", "yaxshi", "mahsulot", "narx", "sifat", "tez",
          "yetkazib", "berildi", "yomon", "arzon", "qimmat", "kuryer",
          "buyurtma", "keldi", "kech", "rahmat", "tavsiya", "qilaman", "emas"]
BOGINLAR = ["ka", "lo", "mi", "ra", "su", "to", "na", "bi", "de", "yo"]


def hujjatlar_yarat(rng, n):
    """Sintetik mahsulot sharhlari (Zipf taqsimotli so'zlar)."""
    qolgan = sorted({a + b + c for a in BOGINLAR for b in BOGINLAR
                     for c in BOGINLAR})[:380]
    lugat = ASOSIY + qolgan
    vazn = 1 / np.arange(1, len(lugat) + 1) ** 1.0
    vazn /= vazn.sum()
    uzunlik = rng.integers(5, 21, n)
    sozlar = rng.choice(len(lugat), int(uzunlik.sum()), p=vazn)
    chegaralar = np.cumsum(uzunlik)[:-1]
    return [" ".join(lugat[i] for i in qism)
            for qism in np.split(sozlar, chegaralar)]


def bolim(kalit, r):
    """Deterministik bo'limlash (Python hash() jarayonlar orasida farq qiladi)."""
    return zlib.crc32(kalit.encode()) % r


def map_reduce(splitlar, mapper, r, kombinator=None, reducer=sum):
    stat = {"map": 0, "shuffle": 0}
    kirish = [defaultdict(list) for _ in range(r)]
    for split in splitlar:
        chiqish = [kv for yozuv in split for kv in mapper(yozuv)]
        stat["map"] += len(chiqish)
        if kombinator is not None:
            chiqish = kombinator(chiqish)
        for k, v in chiqish:                     # SHUFFLE
            kirish[bolim(k, r)][k].append(v)
            stat["shuffle"] += 1
    natija, yuk = {}, []
    for qism in kirish:                          # REDUCE
        yuk.append(sum(len(v) for v in qism.values()))
        for k in sorted(qism):
            natija[k] = reducer(qism[k])
    return natija, stat, yuk


def soz_mapper(hujjat):
    return [(s, 1) for s in hujjat.split()]


def yigindi_kombinator(juftlar):
    c = Counter()
    for k, v in juftlar:
        c[k] += v
    return sorted(c.items())


def qadamlar(db, sql, param=()):
    """So'rov bajarilishidagi VM qadamlar soni (100 qadam aniqlikda)."""
    hisob = [0]

    def f():
        hisob[0] += 1
        return 0

    db.set_progress_handler(f, 100)
    natija = db.execute(sql, param).fetchall()
    db.set_progress_handler(None, 0)
    return natija, hisob[0] * 100


def reja(db, sql, param=()):
    return " | ".join(r[3] for r in db.execute("EXPLAIN QUERY PLAN " + sql, param))


def hajm(db):
    return (db.execute("PRAGMA page_count").fetchone()[0]
            * db.execute("PRAGMA page_size").fetchone()[0])


def main() -> None:
    rng = np.random.default_rng(0)
    hujjatlar = hujjatlar_yarat(rng, 20_000)
    splitlar = [hujjatlar[i::8] for i in range(8)]

    print("=== 1. So'z sanash: map -> shuffle -> reduce (8 split, 4 reducer) ===")
    aniq = Counter(s for h in hujjatlar for s in h.split())
    for nom, komb in [("kombinatorsiz", None), ("kombinator bilan",
                                                 yigindi_kombinator)]:
        natija, stat, yuk = map_reduce(splitlar, soz_mapper, 4, komb)
        print(f"  {nom:<17} map {stat['map']:>7,}  shuffle {stat['shuffle']:>7,}"
              f"  reducer yuki {yuk}  aniq bilan mos: {natija == dict(aniq)}")
    eng = aniq.most_common(3)
    print(f"  eng ko'p so'zlar: {eng}, 'va' ulushi "
          f"{eng[0][1] / sum(aniq.values()):.3f}")

    print("\n=== 2. Reducer soni va issiq kalit (kombinatorsiz) ===")
    for r in [2, 4, 8, 16]:
        _, stat, yuk = map_reduce(splitlar, soz_mapper, r)
        print(f"  R={r:>2}: eng og'ir / o'rtacha yuk = "
              f"{max(yuk) / np.mean(yuk):.2f}, eng og'ir {max(yuk):,}")

    print("\n=== 3. Guruh o'rtachasi: noto'g'ri va to'g'ri kombinator ===")
    shahar = rng.choice(["Toshkent", "Samarqand", "Buxoro"], 60_000,
                        p=[0.6, 0.25, 0.15])
    summa = np.round(rng.lognormal(11, 0.8, 60_000), -2)
    summa[:5_000] *= 1.8                 # 1-split: bayram kuni, xaridlar katta
    yozuvlar = list(zip(shahar.tolist(), summa.tolist()))
    splitlar2 = [yozuvlar[:5_000], yozuvlar[5_000:45_000], yozuvlar[45_000:]]
    mapper = lambda y: [y]

    def ortacha_komb(juftlar):           # NOTO'G'RI: split o'rtachasi
        g = defaultdict(list)
        for k, v in juftlar:
            g[k].append(v)
        return [(k, float(np.mean(g[k]))) for k in sorted(g)]

    def juft_komb(juftlar):              # TO'G'RI: (yig'indi, sanoq)
        g = defaultdict(lambda: [0.0, 0])
        for k, v in juftlar:
            g[k][0] += v
            g[k][1] += 1
        return [(k, tuple(g[k])) for k in sorted(g)]

    yomon, _, _ = map_reduce(splitlar2, mapper, 2, ortacha_komb,
                             lambda vs: float(np.mean(vs)))
    yaxshi, _, _ = map_reduce(splitlar2, mapper, 2, juft_komb,
                              lambda vs: sum(v[0] for v in vs)
                              / sum(v[1] for v in vs))
    haqiqiy = pd.Series(summa).groupby(shahar).mean()
    for sh in haqiqiy.index:
        print(f"  {sh:<10} haqiqiy {haqiqiy[sh]:>9,.1f}  o'rtachalar "
              f"o'rtachasi {yomon[sh]:>9,.1f}  (sum, n) {yaxshi[sh]:>9,.1f}")

    print("\n=== 4. sqlite: indeks va EXPLAIN QUERY PLAN ===")
    n = 300_000
    dokon = rng.choice([f"D{i:02d}" for i in range(1, 21)], n)
    kun = rng.integers(1, 366, n)
    mijoz = rng.integers(1, 50_001, n)
    summa = np.round(rng.lognormal(11, 0.8, n), -2)
    qatorlar = list(zip(range(1, n + 1), dokon.tolist(), kun.tolist(),
                        mijoz.tolist(), summa.tolist()))
    q_mijoz = "SELECT COUNT(*), SUM(summa) FROM savdo WHERE mijoz_id = ?"
    q_oraliq = ("SELECT COUNT(*), SUM(summa) FROM savdo "
                "WHERE dokon = ? AND kun BETWEEN ? AND ?")
    q_keng = "SELECT COUNT(*), SUM(summa) FROM savdo WHERE kun >= ?"
    q_keng_scan = ("SELECT COUNT(*), SUM(summa) FROM savdo NOT INDEXED "
                   "WHERE kun >= ?")
    with tempfile.TemporaryDirectory() as papka:
        with closing(sqlite3.connect(Path(papka) / "savdo.db")) as db:
            db.execute("CREATE TABLE savdo (id INTEGER PRIMARY KEY, "
                       "dokon TEXT, kun INTEGER, mijoz_id INTEGER, summa REAL)")
            db.executemany("INSERT INTO savdo VALUES (?, ?, ?, ?, ?)", qatorlar)
            db.commit()
            h0 = hajm(db)
            r1, s1 = qadamlar(db, q_mijoz, (4242,))
            r2, s2 = qadamlar(db, q_oraliq, ("D07", 100, 130))
            print(f"  jadval: {n:,} qator, fayl {h0:,} bayt")
            print(f"  [indekssiz] mijoz: {reja(db, q_mijoz, (4242,))}; "
                  f"qadam {s1:,}")
            print(f"  [indekssiz] oraliq: {reja(db, q_oraliq, ('D07', 100, 130))}; "
                  f"qadam {s2:,}")
            db.execute("CREATE INDEX idx_mijoz ON savdo(mijoz_id)")
            db.execute("CREATE INDEX idx_dokon_kun ON savdo(dokon, kun)")
            db.execute("CREATE INDEX idx_kun ON savdo(kun)")
            db.commit()
            r1b, s1b = qadamlar(db, q_mijoz, (4242,))
            r2b, s2b = qadamlar(db, q_oraliq, ("D07", 100, 130))
            print(f"  [indeks]    mijoz: {reja(db, q_mijoz, (4242,))}; "
                  f"qadam {s1b:,}")
            print(f"  [indeks]    oraliq: "
                  f"{reja(db, q_oraliq, ('D07', 100, 130))}; qadam {s2b:,}")
            print(f"  natijalar bir xil: {r1 == r1b and r2 == r2b}; mijoz 4242: "
                  f"{r1b[0][0]} xarid; D07, 100-130 kun: {r2b[0][0]} chek")
            print(f"  fayl indekslar bilan: {hajm(db):,} bayt "
                  f"(+{hajm(db) / h0 - 1:.0%})")
            k1, s3 = qadamlar(db, q_keng, (2,))
            k2, s4 = qadamlar(db, q_keng_scan, (2,))
            print(f"  past selektivlik (kun >= 2, {k1[0][0] / n:.1%} qator): "
                  f"indeks {s3:,}, to'liq skan {s4:,} qadam")
            print(f"    reja: {reja(db, q_keng, (2,))}")
            sql_g = dict(db.execute("SELECT dokon, SUM(summa) FROM savdo "
                                    "GROUP BY dokon").fetchall())
    pd_g = pd.Series(summa).groupby(dokon).sum()
    farq = max(abs(sql_g[d] - pd_g[d]) / pd_g[d] for d in pd_g.index)
    print(f"  SQL GROUP BY va pandas groupby: maks nisbiy farq {farq:.1e}")
    print("  ⭐ Shuffle - eng qimmat; indeks - selektiv so'rov uchun")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. So'z sanash: map -> shuffle -> reduce (8 split, 4 reducer) ===
  kombinatorsiz     map 249,780  shuffle 249,780  reducer yuki [22889, 109676, 69343, 47872]  aniq bilan mos: True
  kombinator bilan  map 249,780  shuffle   3,200  reducer yuki [632, 800, 840, 928]  aniq bilan mos: True
  eng ko'p so'zlar: [('va', 38186), ('juda', 18768), ('yaxshi', 12490)], 'va' ulushi 0.153

=== 2. Reducer soni va issiq kalit (kombinatorsiz) ===
  R= 2: eng og'ir / o'rtacha yuk = 1.26, eng og'ir 157,548
  R= 4: eng og'ir / o'rtacha yuk = 1.76, eng og'ir 109,676
  R= 8: eng og'ir / o'rtacha yuk = 2.03, eng og'ir 63,411
  R=16: eng og'ir / o'rtacha yuk = 3.66, eng og'ir 57,162

=== 3. Guruh o'rtachasi: noto'g'ri va to'g'ri kombinator ===
  Buxoro     haqiqiy  88,038.1  o'rtachalar o'rtachasi 103,592.8  (sum, n)  88,038.1
  Samarqand  haqiqiy  88,420.5  o'rtachalar o'rtachasi 105,064.4  (sum, n)  88,420.5
  Toshkent   haqiqiy  87,765.2  o'rtachalar o'rtachasi 105,433.4  (sum, n)  87,765.2

=== 4. sqlite: indeks va EXPLAIN QUERY PLAN ===
  jadval: 300,000 qator, fayl 6,569,984 bayt
  [indekssiz] mijoz: SCAN savdo; qadam 900,000
  [indekssiz] oraliq: SCAN savdo; qadam 945,600
  [indeks]    mijoz: SEARCH savdo USING INDEX idx_mijoz (mijoz_id=?); qadam 100
  [indeks]    oraliq: SEARCH savdo USING INDEX idx_dokon_kun (dokon=? AND kun>? AND kun<?); qadam 9,300
  natijalar bir xil: True; mijoz 4242: 8 xarid; D07, 100-130 kun: 1320 chek
  fayl indekslar bilan: 17,534,976 bayt (+167%)
  past selektivlik (kun >= 2, 99.7% qator): indeks 1,795,100, to'liq skan 2,096,700 qadam
    reja: SEARCH savdo USING INDEX idx_kun (kun>?)
  SQL GROUP BY va pandas groupby: maks nisbiy farq 0.0e+00
  ⭐ Shuffle - eng qimmat; indeks - selektiv so'rov uchun

Natija tahlili.

1-bo'lim — 20 000 sharh, 8 split, 4 reducer. Kombinatorsiz map 249,780 ta (so'z, 1) juftini chiqardi va hammasi shuffle orqali reducer larga ketdi. Kombinator (har splitda lokal yig'indi) bilan shuffle 3,200 yozuvga tushdi — 78 barobar kam, chunki har split har bir so'zni bitta (so'z, sanoq) sifatida yuboradi (8 split x 400 so'z). Ikkala holatda ham natija Counter bilan aynan mos. Haqiqiy klasterda shuffle tarmoq va disk orqali o'tadi — bu farq soatlar farqi bo'lishi mumkin.

2-bo'lim — issiq kalit. va so'zi barcha so'zlarning 15.3% i. Uni qabul qilgan reducer boshqalardan ancha og'ir: R = 4 da eng og'ir yuk o'rtachadan 1.76 barobar, R = 16 da 3.66 barobar. Reducer larni ko'paytirish eng og'ir yukni 57,162 dan pastga tushira olmaydi — uning 38,186 tasi bitta va kalitining o'zi, va bitta kalit bo'linmaydi. Butun ish eng sekin reducer tugashini kutadi. Yechimlar: kombinator (1-bo'lim — yuk [632, 800, 840, 928] ga tekislandi), yoki issiq kalitga tasodifiy tuz qo'shib (va#0 .. va#7) ikki bosqichli yig'ish.

3-bo'lim — kombinator amali assotsiativ bo'lishi shart. Birinchi split (5 000 yozuv, bayram kuni — xaridlar 1.8 barobar katta) boshqalardan kichik va boshqacha. "O'rtachalar o'rtachasi" har splitga teng vazn berdi va Toshkent o'rtachasini 87,765 o'rniga 105,433 qildi — 20% xato, dastur esa hech qanday xato bermadi. (yig'indi, sanoq) juftligi esa aniq javob berdi.

4-bo'lim — sqlite. Indekssiz ikkala so'rov ham SCAN savdo — 300 000 qatorning hammasi o'qiladi (900,000 va 945,600 VM qadami). Indeks bilan reja SEARCH ... USING INDEX ga o'zgardi: bitta mijoz bo'yicha so'rov ~`100qadam (o'lchash aniqligi 100 qadam — ya'ni minglab barobar kam), kompozit(dokon, kun)indeks bilan oraliq so'rovi9,300qadam — ~100 barobar kam. Natijalar aynan bir xil. Narxi: fayl6.6 MBdan17.5 MB ga o'sdi (+167%, uchta indeks), har yangi qator endi uchta B-daraxtga ham yoziladi. Past selektivlikda (kun >= 2— qatorlarning99.7%i) sqlite baribir indeksni tanladi, lekin tejash atigi14% (1,795,100va2,096,700qadam); VM qadamlari diskdagi tasodifiy sahifa o'qishlarini hisobga olmaydi — haqiqiy katta jadvalda bunday indeks yo'li to'liq skandan sekinroq bo'lishi ham mumkin. Oxirgi qator: SQLGROUP BY` natijasi pandas bilan bir xil — diskdagi baza "xotiradagi pandas" o'rnini bosa oladi, faqat savol SQL da berilishi kerak.


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

Noto'g'ri fikr To'g'risi
"Fayl 20 GB, RAM 16 GB — sig'maydi" Standart o'qish fayldan ~4 barobar ko'p oladi, to'g'ri turlar bilan esa fayldan ham kichik bo'lishi mumkin
"memory_usage() xotirani ko'rsatadi" Faqat deep=True bilan; aks holda matn ustunlari ko'rinmaydi
"category doim tejaydi" Noyob qiymatlar ko'p bo'lsa kattalashtiradi
"Kichik tur — bepul tejash" int8 da ko'paytirish jim to'lib ketadi; float32 aniqlikni kamaytiradi
"Bo'laklar natijasini birlashtirsa bo'ldi" Faqat birlashtiriladigan holat: yig'indi, sanoq, (n, o'rtacha, M2); mediana — yo'q
"Bir o'tishli dispersiya — E[x^2] - E[x]^2" Katastrofik ayirish; Welford ishlating
"Oxirgi k ta — namuna" Rezervuar namuna; "yangiga moyil" usul namunani buzadi
"Taxminiy javob — ishonchsiz" Sketch xatosi oldindan ma'lum va nazorat qilinadi (1.04/sqrt(m), eps*N, FPR formulasi)
"Ko'p mashina — tezroq" Shuffle va issiq kalit bitta mashinaga "tiqilib" qolishi mumkin
"Indeks — har doim yaxshi" Selektiv so'rovga; joy va yozish narxi bor, past selektivlikda foyda kam
"Katta ma'lumot — Spark" Avval dtype, usecols, chunk, namuna, sqlite/DuckDB, katta mashina

6. Keng tarqalgan xatolar va yechimlari

1. Xotirani noto'g'ri o'lchash

python
df.memory_usage().sum()                                         # ⚠️ matn ko'rinmaydi
df.memory_usage(deep=True, index=False).sum()                   # ✅

2. Kichik turda hisoblash

python
df["jami"] = df["miqdor"] * df["narx_ming"]                     # ⚠️ int8 * int16 -> to'lib ketadi
df["jami"] = df["miqdor"].astype("int64") * df["narx_ming"]     # ✅ avval kengaytiring

3. Butun faylni o'qish

python
df = pd.read_csv("cheklar.csv")                                 # ⚠️ hamma ustun, standart turlar
df = pd.read_csv("cheklar.csv", usecols=kerakli, dtype=turlar)  # ✅

4. Bo'laklarda o'rtachalar o'rtachasi

python
ortacha = np.mean([b["summa"].mean() for b in bolaklar])        # ⚠️
ortacha = sum(b["summa"].sum() for b in bolaklar) / sum(len(b) for b in bolaklar)  # ✅

5. Naiv dispersiya

python
var = (s2 - s * s / n) / (n - 1)                                # ⚠️ katastrofik ayirish
w = Welford(); [w.qosh(x) for x in oqim]; var = w.dispersiya    # ✅

6. Deterministik bo'lmagan bo'limlash

python
reducer = hash(kalit) % R                                       # ⚠️ har jarayonda boshqacha
reducer = zlib.crc32(kalit.encode()) % R                        # ✅

7. Har ustunga indeks

python
for u in ustunlar: db.execute(f"CREATE INDEX i_{u} ON savdo({u})")    # ⚠️
db.execute("CREATE INDEX i_dk ON savdo(dokon, kun)")            # ✅ so'rovlarga qarab
# va EXPLAIN QUERY PLAN bilan tekshiring

7. Integratsiya — bu bilim qayerda kerak bo'ladi

  • 03-qism (o'tilgan): pandas — read_csv, groupby, turlar
  • 04-qism (o'tilgan): Namuna olish — rezervuar namuna xuddi shu nazariyaning oqimdagi varianti
  • 18.8-dars (o'tilgan): O'rganish egri chiziqlari — "butun ma'lumot kerakmi?" savoliga javob
  • 27.3-dars (o'tilgan): Ma'lumot quvuri — idempotent yuklash, sqlite
  • 27.11-27.12-darslar (o'tilgan): Monitoring — oqimdagi statistikalar, rezervuar namuna, sketch lar
  • 28.4-28.5-darslar (o'tilgan): Tavsiya tizimlari — foydalanuvchi-mahsulot o'zaro ta'sirlari eng tez o'sadigan ma'lumot
  • 28.11-dars: Sababiy xulosa — katta kuzatuv ma'lumoti sababni o'zi isbotlamaydi
  • 28.12-dars: Amaliyot — ko'p do'konli kunlik savdo
  • Loyihalar va karyera qismi: Intervyularda tez-tez so'raladi: "10 GB fayl, 8 GB RAM — nima qilasiz?"

8. Eng yaxshi amaliyotlar

  1. Avval o'lchang: memory_usage(deep=True), fayl hajmi, savol turi.

  2. Faqat kerakli ustunlar va to'g'ri turlar — o'qish paytida.

  3. Bo'laklarda faqat birlashtiriladigan holat; mediana va noyob soni uchun sketch yoki disk.

  4. Dispersiya — Welford; parallel holatlar — Chan formulasi.

  5. Oqimdan namuna — rezervuar; taxminiy javob yetarli bo'lsa — sketch.

  6. Map-reduce da shuffle ni kamaytiring: kombinator, issiq kalitga e'tibor.

  7. Diskda SQL: selektiv so'rovlarga indeks, EXPLAIN QUERY PLAN bilan tekshiruv.

  8. Taqsimlangan vositaga faqat bitta mashina chegarasi aniq ko'ringanda o'ting.


9. Amaliy topshiriq

Vazifa 1: Bashorat qiling

python
1.  # 20 MB CSV standart pd.read_csv da xotirada taxminan qancha?
2.  # memory_usage() va memory_usage(deep=True) qaysi ustunlarda farq qiladi?
3.  # 400 000 ta noyob chek kodini category qilsak?
4.  # int8 ustun * 20 - qiymat 12 bo'lsa natija?
5.  # bo'lak medianalarining o'rtachasi = mediana?
6.  # qiymatlar ~1e9, std 50 - naiv dispersiya formulasi?
7.  # "har yangi element 1/2 ehtimol bilan" namunasi qaysi qismdan ko'p?
8.  # HLL p=12 - nisbiy xato taxminan?
9.  # Count-Min baho haqiqiydan kichik bo'lishi mumkinmi?
10. # Bloom filter "yo'q" desa?
11. # kombinator bilan shuffle hajmi qanday o'zgaradi?
12. # WHERE kun >= 2 (99.7% qator) - indeks qancha yutuq beradi?
Javoblar
  1. ~80 MB (misolda 4 barobar)
  2. Matn (str/object) ustunlarida
  3. Kattalashadi (misolda 24.0 → 25.6 MB)
  4. -16 (240 - 256) — jim to'lib ketish
  5. Yo'q — faqat tasodifan yaqin bo'lishi mumkin
  6. Butunlay noto'g'ri (misolda 7 barobar katta), hatto manfiy bo'lishi mumkin
  7. Oxirgi qismdan (misolda 92.3% oxirgi chorakdan)
  8. 1.04 / sqrt(4096) ~ 1.6%
  9. Yo'q — faqat ortiqcha baholaydi
  10. Aniq yo'q (yolg'on manfiy yo'q)
  11. Keskin kamayadi (misolda 78 barobar)
  12. Juda kam (misolda 14% VM qadam), diskda esa sekinlashtirishi ham mumkin

Vazifa 2: Xatolarni tuzating

python
1.  df = pd.read_csv("cheklar.csv"); print(df.memory_usage().sum())

2.  ortacha = np.mean([b.summa.mean() for b in pd.read_csv(yol, chunksize=10**5)])

3.  var = (sum(x * x for x in oqim) - sum(oqim) ** 2 / n) / (n - 1)

4.  reducer = hash(soz) % 8

5.  namuna = oqim[-1000:]   # "tasodifiy" namuna
Javoblar
python
1.  df = pd.read_csv("cheklar.csv", usecols=kerakli, dtype=turlar)
    print(df.memory_usage(deep=True, index=False).sum())

2.  jami = soni = 0
    for b in pd.read_csv(yol, usecols=["summa"], chunksize=10**5):
        jami += b.summa.sum(); soni += len(b)
    ortacha = jami / soni

3.  w = Welford()
    for x in oqim: w.qosh(x)
    var = w.dispersiya

4.  reducer = zlib.crc32(soz.encode()) % 8

5.  namuna = rezervuar(oqim, 1000, random.Random(0))

Vazifa 3: Xotira va chunk

Modellang (1-misol asosida):

  1. chunksize ni 10 000, 50 000, 200 000 qiling — bo'lak xotirasi va natija o'zgaradimi?
  2. Oqimda har do'kon uchun Welford holatini saqlab, do'kon bo'yicha dispersiyani hisoblang va to'liq natija bilan solishtiring
  3. Fayl do'kon bo'yicha tartiblangan bo'lsa (sort_values("dokon")), bo'lak medianalari xatosi qanday o'zgaradi?
  4. narx ni butun so'mda int32 qilib saqlang — float32 ga nisbatan afzallik va xavf

Vazifa 4: Oqim algoritmlari

Modellang (2-misol asosida):

  1. Welford ni float32 da yozing — siljish 1e4 va 1e6 da xato
  2. Vaznli rezervuar (A-Res: kalit u ** (1 / w), eng katta k ta) — vazn 2 barobar bo'lgan element 2 barobar ko'p tanlanadimi?
  3. Oqimda faqat oxirgi 1 000 ta element uchun o'rtacha (sirpanuvchi oyna) — collections.deque bilan
  4. Chi-kvadrat testini pozitsiyalar juftligi uchun qiling: ikki element birga tanlanish ehtimoli ham to'g'rimi?

Vazifa 5: Sketch lar

Modellang (3-misol asosida):

  1. HLL da kichik diapazon tuzatishini o'chiring — noyob soni 1 000 bo'lganda xato
  2. Count-Min ga "conservative update" qo'shing (faqat eng kichik hisoblagichlarni oshirish) — xato qanchaga kamayadi?
  3. Count-Min + heap bilan oqimdagi eng ko'p 20 ta mahsulotni xotirada saqlang
  4. Counting Bloom filter yozing (bit o'rniga 4 bitli hisoblagich) va elementni o'chirishni qo'llab-quvvatlang

Vazifa 6: Map-reduce va sqlite

Modellang (4-misol asosida):

  1. Issiq kalit uchun "tuzlash": va ni va#0..va#7 ga bo'lib, ikki bosqichli yig'ishni yozing — eng og'ir reducer yuki
  2. Map-reduce bilan har do'kon uchun dispersiya (Chan kombinatori)
  3. pd.read_sql_query(..., chunksize=...) bilan bazadan bo'laklab o'qing
  4. (kun, dokon) tartibidagi indeks WHERE dokon = ? AND kun BETWEEN so'rovi uchun ishlaydimi? EXPLAIN QUERY PLAN bilan tekshiring

Vazifa 7: O'ylash

Marketing bo'limi: "Bizda ilova logi — kuniga 40 million hodisa, 3 yillik tarix. Har kuni: kunlik noyob foydalanuvchilar, eng ko'p ko'rilgan 100 mahsulot va har bir kampaniya uchun konversiya. Hozir tahlilchi har kuni butun tarixni pandas da ochishga urinadi va jarayon yiqiladi. Spark klaster sotib olamizmi?"

Javob

Qisqa javob: hozircha klaster shart emas. Muammo — har kuni butun tarixni qayta o'qish. Savollarning hammasi kunlik va birlashtiriladigan; ularni har kuni faqat yangi kun ustida hisoblab, kichik natijalarni saqlash kerak.

1. Savollarni ajratamiz.

python
# kunlik noyob foydalanuvchilar  -> HLL (p=14: 16 KB/kun, xato ~0.8%)
#                                   istalgan davr: kunlik sketch lar max bilan birlashadi
# eng ko'p ko'rilgan 100 mahsulot -> aniq: kunlik groupby (bir kun ~40 mln qator)
#                                   yoki Count-Min + heap (oqimda)
# kampaniya konversiyasi          -> (ko'rishlar, xaridlar) yig'indilari - aniq

2. Bir kunlik hajm. 40 million hodisa, masalan 6 ta ustun — to'g'ri turlar bilan (1-misol: category, int32) bu 0.5-1 GB atrofida; Parquet da faqat kerakli ustunlarni o'qish bundan ham kam. Noutbuk yoki oddiy server buni bir o'tishda (chunk yoki DuckDB) ko'taradi.

3. Tarix. Uch yillik xom log har kuni kerak emas — kunlik natijalar (sketch, yig'indi) jadvali saqlanadi: 1 095 kun x (16 KB HLL + kichik jadval) — bir necha o'n MB. "Oxirgi 90 kunlik noyob foydalanuvchilar" 90 ta HLL ning max i bilan soniyalarda chiqadi (3-misol: birlashma xatosi +0.66%).

4. Qachon klaster kerak bo'ladi. Xom tarix ustida muntazam og'ir ishlar paydo bo'lsa: masalan, har hafta 3 yillik log ustida foydalanuvchi-mahsulot matritsasini qurib, tavsiya modelini qayta o'qitish 28.5-bob, yoki katta jadvallarni kalit bo'yicha join qilish. Shunda ham avval DuckDB/Polars bilan bitta katta mashinani sinab ko'ring.

Rahbarga javob: "Hozirgi muammo hisob quvvatida emas, yondashuvda: har kuni 40 milliard qatorlik tarixni qayta o'qiyapmiz. Kunlik hisob faqat yangi kun ustida bo'ladi, natijalar esa kichik sketch va yig'indilar sifatida saqlanadi — bu bitta serverda bajariladi va bir necha daqiqa oladi. Noyob foydalanuvchilar ~1% xato bilan chiqadi — buni biznes bilan oldindan kelishamiz. Klaster masalasiga xom tarix ustida muntazam og'ir ML ishi paydo bo'lganda qaytamiz."

Nimani mustahkamlaydi: 2.1, 2.3, 2.5, 2.9-bo'limlar.


Xulosa

Bu darsda "katta ma'lumot" bilan ishlashning asosiy g'oyalarini noldan qurdik va ularni o'lchab tekshirdik.

Eng muhim uch fikr:

  1. "Katta" — resursga nisbatan, va ko'pincha kattalik — turlardan. 1-misolda 400 ming qatorlik jadval standart turlar bilan 82.6 MB — 20.1 MB lik fayldan 4 barobar ko'p; category, kichik butun turlar va float32 bilan 7.2 MB (11.5 barobar kam), kerakli ustunlar bilan 4.4 MB. Tejashning narxi bor: int8 da ko'paytirish jim to'lib ketdi, noyob matnni category qilish xotirani oshirdi. Chunk bo'yicha o'qish yig'indi va o'rtachada to'liq natija bilan aynan mos keldi, mediana esa bo'laklanmadi (70,200 o'rniga 70,833 yoki 68,700).

  2. Bir o'tishli va taxminiy algoritmlar — kichik xotira, oldindan ma'lum xato. 2-misolda naiv dispersiya qiymatlar ~`1e9bo'lganda 7 barobar adashdi, Welford esa1e-10 aniqlikda to'g'ri qoldi va bo'laklar bo'yicha aniq birlashdi. Rezervuar namuna haqiqatan bir xil (p = 0.166), "yangiga moyil" usul esa namunaning 92%ini oxirgi chorakdan oldi. 3-misolda HyperLogLog 4 KB da+0.14%xato berdi (Pythonset—26 MB), 30 ta tuzda RMS xato nazariyaga mos (1.58%va1.63%); Count-Min hech qachon kam baholamadi va top-10 ni to'liq topdi; Bloom filter yolg'on musbat ulushi formulaga mos (0.0082va0.0082`), yolg'on manfiy — nol.

  3. Taqsimlangan hisobning mohiyati — birlashtiriladigan holat va ma'lumotni kam ko'chirish. 4-misolda kombinator shuffle ni 78 barobar kamaytirdi, issiq kalit esa reducer ko'paytirilsa ham yukni tekislashga yo'l qo'ymadi; noto'g'ri kombinator (o'rtachalar o'rtachasi) 20% xato berdi. sqlite da indeks selektiv so'rovni minglab barobar tezlatdi, lekin faylni +167% kattalashtirdi va past selektivlikda deyarli foyda bermadi. Spark, Dask, Polars va DuckDB aynan shu g'oyalarni katta miqyosda bajaradi — lekin avval dtype, chunk, namuna va bitta yaxshi mashinani sinab ko'rish kerak.

Keyingi darsda Sababiy xulosa (causal inference): katta kuzatuv ma'lumoti "nima bilan nima birga keladi" degan savolga javob beradi, lekin "agar kupon bersak, xarid oshadimi?" degan savolga emas. Potentsial natijalar, chalkashtiruvchilar va DAG lar, propensity score, IPW va ikki karra mustahkam baholash, uplift modellashtirish va farqlar farqi — hammasi haqiqiy ta'siri ma'lum sintetik ma'lumotda o'lchab.

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
28.10-dars: Katta ma'lumot bilan ishlash — IlmHamroh