Asosiy qismga o'tish

Ma'lumot formatlari: CSV dan Parquet gacha

4-bob 11 kod bloki 3030 so'z

Bu bobdagi kod shu fayllar bilan ishlaydio'z kompyuteringizda ishga tushiring

Yo'qolgan nol

Hamkasbingiz sizga mijozlar ro'yxatini yubordi — oddiy Excel fayli. Siz uni ochdingiz va telefon raqamlariga qaradingiz:

matn2 qator
901234567
907654321

Bir narsa yetishmayapti. O'zbekistonda telefon raqami 90 bilan emas, 090 bilan boshlanadi.

Boshidagi nol yo'qolgan.

Hech kim uni o'chirmadi. Excel "bu son ekan" deb qaror qildi va sonlarda boshida nol turmaydi. Fayl toza ko'rinadi, xato xabari yo'q, hamma ustun joyida. Faqat 200 ta telefon raqami endi noto'g'ri.

Bu — CSV ning birinchi va eng xavfli kamchiligi: u ma'lumot turini saqlamaydi. Har safar o'qiganda tur qaytadan taxmin qilinadi, va taxmin xato bo'lishi mumkin.

Shu bobda buni o'z ko'zingiz bilan ko'rasiz — va nol yo'qolmaydigan formatni o'rganasiz.

CSV nima uchun yetarli emas

CSV hamma joyda ishlaydi va Excel da ochiladi. Shuning uchun u ma'lumot almashish uchun yaxshi. Lekin ma'lumotni saqlash va qayta ishlash uchun uning to'rtta jiddiy kamchiligi bor.

Birinchisi eng xavflisi: CSV turlarni bilmaydi.

python8 qator
import pandas as pd
import io

xom = "id,kod,narx,sana,faol\n1,007,10.5,2026-09-01,true\n2,042,20.0,2026-09-02,false\n"
d = pd.read_csv(io.StringIO(xom))
print("CSV dan o'qilgandan keyingi turlar:")
for ustun, tur in d.dtypes.items():
    print(f"  {ustun:8} {str(tur):10} namuna: {d[ustun].iloc[0]!r}")
Chiqish
CSV dan o'qilgandan keyingi turlar:
  id       int64      namuna: np.int64(1)
  kod      int64      namuna: np.int64(7)
  narx     float64    namuna: np.float64(10.5)
  sana     object     namuna: '2026-09-01'
  faol     bool       namuna: np.True_

kod ustuniga qarang. Faylda 007 yozilgan edi — o'qilgandan keyin 7 bo'ldi. Boshlang'ich nollar yo'qoldi.

Nega bunday bo'ldi? CSV — bu oddiy matn fayli. Uning ichida 007 va "007" orasida farq yo'q; hammasi belgilar. Faylda "bu ustun matn" deb yozilgan joy yo'q.

Shuning uchun pandas har o'qishda taxmin qiladi: ustunga qaraydi, hamma qiymat raqamga o'xshasa — "demak bu son" deydi. Va sonlarda boshida nol turmaydi.

Diqqat qiling: bu pandas ning xatosi emas. U qo'lidan kelganini qildi. Ma'lumot esa faylning o'zida yo'q edi.

sana ustuniga ham qarang — u object, ya'ni matn bo'lib qoldi. Endi undan oy yoki hafta kunini olish uchun avval aylantirish kerak, va aylantirishni unutish oson.

Taxmin har o'qishda qaytadan qilinadi

Eng nozik tomoni shu: taxmin faylga bog'liq emas, ma'lumotga bog'liq.

Bugun faylingizdagi barcha kodlar raqam — pandas "son" deb o'qiydi. Ertaga bitta yangi qator qo'shiladi va unda kod A007 bo'ladi — endi pandas butun ustunni "matn" deb o'qiydi.

Bir kunda ustun turi o'z-o'zidan o'zgardi. Kodingiz o'zgarmadi. Va agar siz o'sha ustunda arifmetika qilayotgan bo'lsangiz, bugun ishlagan kod ertaga yiqiladi.

1-bobdagi naqsh yana qaytadi: hech kim hech narsa buzmadi, lekin tizim buzildi.

Qator-qator va ustun-ustun saqlash

Ikkinchi kamchilik tuzilishda. CSV qator bo'yicha saqlaydi, Parquet esa ustun bo'yicha.

python14 qator
diagramma(
    {
        "CSV (qator bo'yicha)": (0, 0, 4.4, 0.7, "manba"),
        "1-qator: Aziz | A | 85": (0, 0.9, 4.4, 0.6, "oddiy"),
        "2-qator: Bobur | A | 92": (0, 1.7, 4.4, 0.6, "oddiy"),
        "3-qator: Dilnoza | B | 78": (0, 2.5, 4.4, 0.6, "oddiy"),
        "Parquet (ustun bo'yicha)": (5.6, 0, 4.4, 0.7, "saqlash"),
        "ism: Aziz, Bobur, Dilnoza": (5.6, 0.9, 4.4, 0.6, "oddiy"),
        "guruh: A, A, B": (5.6, 1.7, 4.4, 0.6, "oddiy"),
        "baho: 85, 92, 78": (5.6, 2.5, 4.4, 0.6, "oddiy"),
    },
    nom="qator_ustun.png", kenglik=13,
    izoh="Chapda har qator birga yotadi. O'ngda har ustun birga yotadi — bitta ustunni o'qish uchun qolganini o'qish shart emas.",
)

Chapda har qator birga yotadi. O'ngda har ustun birga yotadi — bitta ustunni o'qish uchun qolganini o'qish shart emas.

Chapda har qator birga yotadi. O'ngda har ustun birga yotadi — bitta ustunni o'qish uchun qolganini o'qish shart emas.

Farqi amalda nimani beradi:

Nega bu farq qiladi? Kutubxonani yana bir marta eslang.

CSV — bu kundalik daftar. Har kun uchun bitta bet: sana, ob-havo, nima yedingiz, kim keldi. Hammasi birga.

Endi savol: "bir yilda necha kun yomg'ir yog'di?"

Daftarni ochib, har betni varaqlashingiz kerak — chunki ob-havo har betda boshqa narsalar orasida yozilgan.

Parquet — bu alohida jadvallar. Bitta varaqda faqat sanalar. Ikkinchisida faqat ob-havo. Uchinchisida faqat taomlar.

O'sha savol uchun endi bitta varaq yetadi. Qolganlarini qo'lga ham olmaysiz.

Ikki amaliy natija:

  • Bitta ustun kerak bo'lsa — Parquet faqat o'shani diskdan ko'taradi. CSV esa butun faylni o'qib, keraksiz qismini tashlaydi. (9-bobda buni o'lchadik: Parquet OLAP so'rovida 13 barobar tez chiqdi.)
  • Siqish yaxshiroq — bir ustundagi qiymatlar o'xshash bo'ladi. Ob-havo varag'ida faqat "quyoshli, bulutli, yomg'ir" takrorlanadi. Takroriy narsani siqish esa oson.

Ikkalasini ham o'lchaymiz.

O'lchov: hajm

python32 qator
import pandas as pd
from pathlib import Path

ISH.mkdir(parents=True, exist_ok=True)
d = pd.read_csv(DATA / "ijara.csv")
print(f"ma'lumot: {len(d):,} qator, {len(d.columns)} ustun")

yollar = {}
d.to_csv(ISH / "t.csv", index=False)
yollar["CSV"] = ISH / "t.csv"

import gzip, shutil
with open(ISH / "t.csv", "rb") as f_in, gzip.open(ISH / "t.csv.gz", "wb", compresslevel=6) as f_out:
    shutil.copyfileobj(f_in, f_out)
yollar["CSV + gzip"] = ISH / "t.csv.gz"

for siqish in ["snappy", "gzip", "zstd"]:
    try:
        yol = ISH / f"t_{siqish}.parquet"
        d.to_parquet(yol, compression=siqish, index=False)
        yollar[f"Parquet ({siqish})"] = yol
    except Exception as e:
        print(f"  {siqish}: {type(e).__name__}")

asos = yollar["CSV"].stat().st_size
qatorlar = []
for nom, yol in yollar.items():
    hajm = yol.stat().st_size
    qatorlar.append({"format": nom, "hajm, KB": round(hajm / 1024, 1),
                     "CSV ga nisbatan": f"{hajm / asos:.2f}x",
                     "tejaldi": f"{100 * (1 - hajm / asos):.0f}%"})
show(pd.DataFrame(qatorlar))
Chiqish
ma'lumot: 12,000 qator, 14 ustun
formathajm, KBCSV ga nisbatantejaldi
CSV704.91.00x0%
CSV + gzip215.90.31x69%
Parquet (snappy)212.60.30x70%
Parquet (gzip)171.80.24x76%
Parquet (zstd)169.30.24x76%

O'lchov: o'qish tezligi

python22 qator
import time

def olch(f, n=5):
    f()
    t0 = time.perf_counter()
    for _ in range(n):
        natija = f()
    return (time.perf_counter() - t0) / n * 1000, natija

qatorlar = []
ms, _ = olch(lambda: pd.read_csv(ISH / "t.csv"))
qatorlar.append({"amal": "CSV — butun fayl", "ms": round(ms, 1)})

ms, _ = olch(lambda: pd.read_parquet(ISH / "t_snappy.parquet"))
qatorlar.append({"amal": "Parquet — butun fayl", "ms": round(ms, 1)})

ms, _ = olch(lambda: pd.read_csv(ISH / "t.csv", usecols=["narx_mln", "broker"]))
qatorlar.append({"amal": "CSV — 2 ustun", "ms": round(ms, 1)})

ms, _ = olch(lambda: pd.read_parquet(ISH / "t_snappy.parquet", columns=["narx_mln", "broker"]))
qatorlar.append({"amal": "Parquet — 2 ustun", "ms": round(ms, 1)})
show(pd.DataFrame(qatorlar))
amalms
CSV — butun fayl11.2
Parquet — butun fayl6.4
CSV — 2 ustun6.8
Parquet — 2 ustun2.6

Ikki ustun o'qiganda farq eng katta bo'ladi: CSV baribir har qatorni to'liq o'qib, ajratib, keraksizini tashlashi kerak. Parquet esa faqat kerakli ikki ustunni diskdan oladi.

O'lchov: turlar saqlanadimi

Bu eng muhim farq va uni o'lchash oson:

python31 qator
import pandas as pd
from datetime import date

sinov = pd.DataFrame({
    "kod": ["007", "042", "100"],
    "sana": pd.to_datetime(["2026-09-01", "2026-09-02", "2026-09-03"]),
    "narx": [10.5, 20.0, 30.25],
    "faol": [True, False, True],
    "toifa": pd.Categorical(["A", "B", "A"]),
})
print("ASL turlar:")
for u, t in sinov.dtypes.items():
    print(f"  {u:7} {t}")

sinov.to_csv(ISH / "turlar.csv", index=False)
sinov.to_parquet(ISH / "turlar.parquet", index=False)
csv_qayta = pd.read_csv(ISH / "turlar.csv")
pq_qayta = pd.read_parquet(ISH / "turlar.parquet")

qatorlar = []
for u in sinov.columns:
    qatorlar.append({
        "ustun": u,
        "asl": str(sinov[u].dtype),
        "CSV dan keyin": str(csv_qayta[u].dtype),
        "Parquet dan keyin": str(pq_qayta[u].dtype),
        "CSV saqladimi": str(sinov[u].dtype) == str(csv_qayta[u].dtype),
    })
show(pd.DataFrame(qatorlar))
print("\nCSV dan keyin 'kod' birinchi qiymati:", repr(csv_qayta["kod"].iloc[0]))
print("Parquet dan keyin                    :", repr(pq_qayta["kod"].iloc[0]))
Chiqish
ASL turlar:
  kod     object
  sana    datetime64[ns]
  narx    float64
  faol    bool
  toifa   category

CSV dan keyin 'kod' birinchi qiymati: np.int64(7)
Parquet dan keyin                    : '007'
ustunaslCSV dan keyinParquet dan keyinCSV saqladimi
kodobjectint64objectFalse
sanadatetime64[ns]objectdatetime64[ns]False
narxfloat64float64float64True
faolboolboolboolTrue
toifacategoryobjectcategoryFalse

Parquet turlarni sxema bilan birga saqlaydi, shuning uchun qayta o'qiganda hech narsa o'zgarmaydi. 007 ham 007 bo'lib qoladi.

Parquet ichida nima bor

python17 qator
import pyarrow.parquet as pq

fayl = pq.ParquetFile(ISH / "t_snappy.parquet")
print("sxema (birinchi 6 ustun):")
for maydon in list(fayl.schema_arrow)[:6]:
    print(f"  {maydon.name:24} {maydon.type}")
print(f"\nqator guruhlari (row groups): {fayl.num_row_groups}")
print(f"jami qatorlar: {fayl.metadata.num_rows:,}")

# har ustun uchun statistika — so'rovni tezlashtiradi
guruh = fayl.metadata.row_group(0)
print("\nustun statistikasi (1-guruh):")
for i in range(min(4, guruh.num_columns)):
    ust = guruh.column(i)
    st = ust.statistics
    if st is not None:
        print(f"  {ust.path_in_schema:22} min={st.min}  max={st.max}  siqilgan={ust.total_compressed_size:>7} bayt")
Chiqish
sxema (birinchi 6 ustun):
  elon_id                  string
  sana                     string
  tuman                    string
  xonalar                  int64
  maydon                   double
  qavat                    int64

qator guruhlari (row groups): 1
jami qatorlar: 12,000

ustun statistikasi (1-guruh):
  elon_id                min=E100042  max=E999992  siqilgan=  94501 bayt
  sana                   min=2026-08-01  max=2026-08-30  siqilgan=   7776 bayt
  tuman                  min=Bektemir  max=Yunusobod  siqilgan=   6235 bayt
  xonalar                min=1  max=4  siqilgan=   3147 bayt

Statistika qatoriga e'tibor bering. Har ustun guruhi uchun min va max saqlanadi. Shuning uchun WHERE narx_mln > 50 so'rovi kelganda, engine max=12 bo'lgan guruhni umuman o'qimaydi. Bu predicate pushdown deyiladi va 18-bobda uni o'lchaymiz.

Qachon qaysi format

python9 qator
import pandas as pd
show(pd.DataFrame([
    {"holat": "Odam Excel da ochadi", "format": "CSV", "sabab": "Universal, tahrirlash oson"},
    {"holat": "API javobi, ichma-ich tuzilish", "format": "JSON", "sabab": "Ierarxiya saqlanadi"},
    {"holat": "Ma'lumot ombori, tahlil", "format": "Parquet", "sabab": "Ustunli, siqilgan, turli"},
    {"holat": "Oqim (streaming) xabari", "format": "Avro / JSON", "sabab": "Qatorli, sxema evolyutsiyasi"},
    {"holat": "Model artefakti", "format": "joblib / ONNX", "sabab": "26-bobda batafsil"},
    {"holat": "Jadval versiyalari, time travel", "format": "Iceberg", "sabab": "19-bob"},
]))
holatformatsabab
Odam Excel da ochadiCSVUniversal, tahrirlash oson
API javobi, ichma-ich tuzilishJSONIerarxiya saqlanadi
Ma'lumot ombori, tahlilParquetUstunli, siqilgan, turli
Oqim (streaming) xabariAvro / JSONQatorli, sxema evolyutsiyasi
Model artefaktijoblib / ONNX26-bobda batafsil
Jadval versiyalari, time travelIceberg19-bob

Parquet ham hamma dardga davo emas

Kichik fayllarda foyda yo'q. 1000 qator uchun Parquet ning metama'lumoti CSV dan ham ko'proq joy egallashi mumkin.

Qator-qator qo'shib bo'lmaydi. Parquet fayl "yozib tugatiladi". Har yangi qator uchun qo'shimcha qilolmaysiz — shuning uchun oqim uchun u to'g'ridan-to'g'ri yaramaydi (19-bobdagi Iceberg shu muammoni yechadi).

Odam o'qiy olmaydi. cat bilan ko'rib bo'lmaydi, grep ishlamaydi.

Kichik fayl da'vosini tekshiramiz:

python12 qator
import pandas as pd
qatorlar = []
for n in (100, 1_000, 10_000, 100_000):
    kichik = d.head(n) if n <= len(d) else pd.concat([d] * (n // len(d) + 1)).head(n)
    kichik.to_csv(ISH / "k.csv", index=False)
    kichik.to_parquet(ISH / "k.parquet", compression="snappy", index=False)
    c = (ISH / "k.csv").stat().st_size
    p = (ISH / "k.parquet").stat().st_size
    qatorlar.append({"qatorlar": f"{n:,}", "CSV, KB": round(c / 1024, 1),
                     "Parquet, KB": round(p / 1024, 1), "nisbat": f"{p / c:.2f}x",
                     "Parquet foydalimi": "ha" if p < c else "yo'q"})
show(pd.DataFrame(qatorlar))
qatorlarCSV, KBParquet, KBnisbatParquet foydalimi
100611.81.97xyo'q
1,00058.9320.54xha
10,000587.3181.60.31xha
100,0005 872.7788.90.13xha

Loyihamizga qo'llaymiz

shu faylni yaratingsrc/quvur/saqlash.py

python41 qator
"""Ma'lumotni o'qish va yozish — bitta joyda, format tanlovi bilan."""
from pathlib import Path

import pandas as pd

SIQISH = "snappy"


def yoz(df: pd.DataFrame, yol: str | Path) -> Path:
    """Kengaytmaga qarab format tanlaydi. Parquet uchun turlar saqlanadi."""
    p = Path(yol)
    p.parent.mkdir(parents=True, exist_ok=True)
    if p.suffix == ".parquet":
        df.to_parquet(p, compression=SIQISH, index=False)
    elif p.suffix == ".csv":
        df.to_csv(p, index=False, encoding="utf-8")
    else:
        raise ValueError(f"noma'lum format: {p.suffix} (.parquet yoki .csv kutilgan)")
    return p


def oqi(yol: str | Path, ustunlar: list[str] | None = None) -> pd.DataFrame:
    """Parquet bo'lsa faqat kerakli ustunlarni diskdan oladi."""
    p = Path(yol)
    if not p.exists():
        raise FileNotFoundError(f"ma'lumot topilmadi: {p}")
    if p.suffix == ".parquet":
        return pd.read_parquet(p, columns=ustunlar)
    if p.suffix == ".csv":
        return pd.read_csv(p, usecols=ustunlar)
    raise ValueError(f"noma'lum format: {p.suffix}")


def csv_dan_parquet(manba: str | Path, nishon: str | Path) -> dict[str, float]:
    """CSV ni Parquet ga o'tkazadi va tejamni qaytaradi."""
    df = pd.read_csv(manba)
    yoz(df, nishon)
    c = Path(manba).stat().st_size
    p = Path(nishon).stat().st_size
    return {"csv_kb": round(c / 1024, 1), "parquet_kb": round(p / 1024, 1),
            "tejam_foiz": round(100 * (1 - p / c), 1)}
python12 qator
python3 -c "
import sys; sys.path.insert(0, 'src')
from quvur.saqlash import csv_dan_parquet, oqi

for nom in ['ijara', 'ballon', 'buyurtma']:
    n = csv_dan_parquet(f'../datasets/{nom}.csv', f'../datasets/{nom}.parquet')
    print(f\"  {nom:10} {n['csv_kb']:>9.1f} KB -> {n['parquet_kb']:>8.1f} KB   tejam {n['tejam_foiz']:>5.1f}%\")

d = oqi('../datasets/ijara.parquet', ustunlar=['narx_mln', 'broker'])
print()
print('faqat 2 ustun o\'qildi:', list(d.columns), '|', len(d), 'qator')
"
Chiqish (qaytish kodi 0, 747 ms)
  ijara          704.9 KB ->    212.6 KB   tejam  69.8%
  ballon        2545.3 KB ->    731.3 KB   tejam  71.3%
  buyurtma      4384.3 KB ->   1236.9 KB   tejam  71.8%

faqat 2 ustun o'qildi: ['narx_mln', 'broker'] | 12000 qator

Xulosa

Tushuncha Asosiy fikr
CSV va turlar 007 → 7; sana matn bo'lib qoladi; ogohlantirish yo'q
Qatorli va ustunli Parquet bitta ustunni qolganini o'qimasdan oladi
Hajm Parquet CSV dan sezilarli kichik; siqish turi ham ta'sir qiladi
Ustun tanlash Eng katta tezlik farqi shu yerda
Sxema Parquet turlarni fayl ichida saqlaydi
Statistika Har guruhda min/max — so'rov keraksiz qismni o'qimaydi
Parquet cheklovi Kichik faylda foydasiz; qator qo'shib bo'lmaydi; odam o'qimaydi

Talaba ishi

  1. ijara.csv ni zstd va gzip bilan yozib, hajm va yozish vaqtini ham o'lchang. Qaysi biri sizning holatingizga mos?
  2. buyurtma.csv dan faqat summa_ming ustunini o'qing — CSV va Parquet dan. Farq ijara dagidan kattami yoki kichikmi? Nega?
  3. to_parquet(..., row_group_size=1000) bilan yozing va num_row_groups ni tekshiring. Guruh kichrayganda statistika qanday o'zgaradi?
  4. kod ustuni muammosini CSV da hal qiling: read_csv ga dtype={"kod": str} bering. Bu yechim nega ishonchsiz? (Maslahat: kim eslab qoladi?)

Bu bobni bepul o'qidingiz.

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

Telegramda yozish Narxlar