Saqlash va yakuniy loyiha: yurak kasalligi ma'lumoti
Sahna: grafik ekranda qolib ketmasin
Grafik faqat daftarda turmaydi: u hisobotga, taqdimotga, saytga boradi. Bu bobning birinchi qismi — rasmni to'g'ri saqlash. Ikkinchi qismi — butun kitobning yakuni: real tibbiy ma'lumotda boshidan oxirigacha EDA (Exploratory Data Analysis — ma'lumotni o'rganish tahlili), 1–3 qismdagi hamma narsa bilan.
savefig: format va hajm
import os import numpy as np import pandas as pd x = np.linspace(0, 10, 200) fig, ax = plt.subplots(figsize=(6, 3.5)) ax.plot(x, np.sin(x), label="sin") ax.plot(x, np.cos(x), label="cos") ax.set(title="Saqlash sinovi", xlabel="x", ylabel="qiymat") ax.legend() hajmlar = {} for nom, kwargs in [("rasm_100.png", {"dpi": 100}), ("rasm_300.png", {"dpi": 300}), ("rasm.svg", {}), ("rasm.pdf", {}), ("rasm.jpg", {"dpi": 100})]: fig.savefig(ISH / nom, bbox_inches="tight", **kwargs) hajmlar[nom] = round(os.path.getsize(ISH / nom) / 1024, 1) plt.close(fig) pd.Series(hajmlar, name="KB")
| KB | |
|---|---|
| rasm_100.png | 36.1 |
| rasm_300.png | 133 |
| rasm.svg | 28.3 |
| rasm.pdf | 13.8 |
| rasm.jpg | 21.7 |
| Format | Turi | Qachon |
|---|---|---|
| PNG | piksel | sayt, Word, Telegram; dpi=150–300 bosma uchun |
| SVG | vektor | sayt — har qanday kattalashtirishda tiniq; kichik grafiklarda hajm ham kichik |
| vektor | LaTeX, bosma hisobot | |
| JPG | piksel, siqilgan | fotosuratlar; grafik uchun yo'q — chiziq va matn chetida "loyqa" dog'lar paydo bo'ladi |
dpi=300 PNG — dpi=100 dan bir necha barobar katta (piksellar soni 9 barobar). Vektor formatda (SVG, PDF) rasm piksel emas, chiziq va matn buyruqlari sifatida saqlanadi: hajm dpi ga bog'liq emas, kattalashtirganda tiniqlik yo'qolmaydi.
bbox_inches="tight" — rasm chegarasini hamma yozuvlarni sig'diradigan qilib qayta hisoblaydi. Usiz uzun o'q nomlari yoki tashqaridagi legend ko'pincha kesilib qoladi.
Yana ikki foydali argument: transparent=True — shaffof fon (qorong'i slaydlar uchun), facecolor="white" — aksincha, shaffof bo'lib qolmasligi uchun.
Yakuniy loyiha: yurak kasalligi
Ma'lumot
1988-yilda Klivlend klinikasida (AQSh) 303 bemor tekshirilgan: yoshi, qon bosimi, xolesterini, yurak urishi, EKG natijalari va — oxirida — shifokorlarning tashxisi. Bu ma'lumot o'n yillardan beri ML o'qitishning klassik misoli.
Manba: Janosi A., Steinbrunn W., Pfisterer M., Detrano R. (1989). Heart Disease [Dataset]. UCI Machine Learning Repository. Litsenziya: CC BY 4.0.
Muhim: bu o'quv tahlili, tibbiy xulosa emas. Ma'lumot eski, kichik va bitta klinikadan. Bu yerda topilgan naqshlarni sog'lig'ingiz haqida qaror uchun ishlatmang.
yurak = pd.read_csv(DATA / "yurak.csv") print(yurak.shape) yurak.head()
(303, 14)
| age | sex | cp | trestbps | chol | fbs | restecg | thalach | exang | oldpeak | slope | ca | thal | num | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 63 | 1 | 1 | 145 | 233 | 1 | 2 | 150 | 0 | 2.3 | 3 | 0 | 6 | 0 |
| 1 | 67 | 1 | 4 | 160 | 286 | 0 | 2 | 108 | 1 | 1.5 | 2 | 3 | 3 | 2 |
| 2 | 67 | 1 | 4 | 120 | 229 | 0 | 2 | 129 | 1 | 2.6 | 2 | 2 | 7 | 1 |
| 3 | 37 | 1 | 3 | 130 | 250 | 0 | 0 | 187 | 0 | 3.5 | 3 | 0 | 3 | 0 |
| 4 | 41 | 0 | 2 | 130 | 204 | 0 | 2 | 172 | 0 | 1.4 | 1 | 0 | 3 | 0 |
Ustun nomlari — asl inglizcha qisqartmalar (ilmiy maqolalarda shunday). Ularning ma'nosi:
| Ustun | Ma'nosi | Qiymatlar |
|---|---|---|
age |
yosh | yil |
sex |
jins | 1 — erkak, 0 — ayol |
cp |
ko'krak og'rig'i turi | 1 — tipik, 2 — atipik, 3 — og'riq emas, 4 — belgisiz (asymptomatic) |
trestbps |
tinch holatdagi qon bosimi | mm sim. ust. |
chol |
xolesterin | mg/dl |
fbs |
och qorindagi qand > 120 mg/dl | 1 — ha, 0 — yo'q |
thalach |
sinovdagi eng yuqori yurak urishi | zarba/daqiqa |
exang |
jismoniy yuklamada og'riq | 1 — ha, 0 — yo'q |
oldpeak |
EKG dagi ST siljishi | mm |
ca |
angiografiyada ko'ringan asosiy tomirlar | 0–3 |
thal |
talliy sinovi | 3 — normal, 6 — doimiy nuqson, 7 — qaytuvchi nuqson |
num |
tashxis | 0 — kasallik yo'q, 1–4 — kasallik darajasi |
1. So'roq (10-bob)
yurak.info() print("takror qatorlar:", yurak.duplicated().sum())
<class 'pandas.core.frame.DataFrame'> RangeIndex: 303 entries, 0 to 302 Data columns (total 14 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 age 303 non-null int64 1 sex 303 non-null int64 2 cp 303 non-null int64 3 trestbps 303 non-null int64 4 chol 303 non-null int64 5 fbs 303 non-null int64 6 restecg 303 non-null int64 7 thalach 303 non-null int64 8 exang 303 non-null int64 9 oldpeak 303 non-null float64 10 slope 303 non-null int64 11 ca 299 non-null float64 12 thal 301 non-null float64 13 num 303 non-null int64 dtypes: float64(3), int64(11) memory usage: 33.3 KB takror qatorlar: 0
Hamma ustun son, takror yo'q. ca va thal da bir necha bo'sh katak bor.
2. Bo'sh kataklar: qaror (12-bob)
bosh = yurak.isna().any(axis=1) print("bo'sh katagi bor qatorlar:", bosh.sum(), f"({bosh.mean():.1%})") toza = yurak.dropna().copy() toza["kasal"] = (toza["num"] > 0).astype(int) print(toza.shape) toza["kasal"].value_counts().rename({0: "sog'lom", 1: "kasal"})
bo'sh katagi bor qatorlar: 6 (2.0%) (297, 15)
| kasal | count |
|---|---|
| sog'lom | 160 |
| kasal | 137 |
Bo'sh katakli qatorlar 2% — kam va tasodifiy ko'rinadi. To'ldirish uchun tibbiy asos yo'q (ca — tasvirdan sanalgan tomirlar, "o'rtacha" bilan to'ldirib bo'lmaydi), shuning uchun bu qatorlarni olib tashlaymiz va qarorni yozib qo'yamiz.
num 0–4 darajalarni ajratadi, lekin har darajada bemorlar kam. Bizning savol oddiyroq — "kasallik bormi?" — shuning uchun yangi ustun: kasal = num > 0 (13-bob). Ikki guruh deyarli teng — taqqoslash uchun qulay.
3. Yosh: kasallar keksaroqmi?
fig, ax = plt.subplots(figsize=(8, 3.4)) for qiymat, nom, rang in [(0, "sog'lom", "#137333"), (1, "kasal", "#c5221f")]: ax.hist(toza.loc[toza["kasal"] == qiymat, "age"], bins=range(28, 80, 3), alpha=0.55, color=rang, label=nom) ax.set(title="Yosh taqsimoti", xlabel="yosh", ylabel="bemorlar") ax.legend() plt.show() toza.groupby("kasal")["age"].agg(["mean", "median"]).round(1).rename(index={0: "sog'lom", 1: "kasal"})
| kasal | mean | median |
|---|---|---|
| sog'lom | 52.6 | 52 |
| kasal | 56.8 | 58 |

Kasal guruh o'rtacha bir necha yil keksaroq, lekin ikki histogramma katta qismda ustma-ust: 50–60 yoshlilar ikkala guruhda ham ko'p. Yoshning o'zi tashxisni ajratmaydi — u bir nechta belgilardan biri.
4. Eng yuqori yurak urishi
fig, ax = plt.subplots(figsize=(8, 4)) for qiymat, nom, rang, belgi in [(0, "sog'lom", "#137333", "o"), (1, "kasal", "#c5221f", "x")]: q = toza[toza["kasal"] == qiymat] ax.scatter(q["age"], q["thalach"], c=rang, marker=belgi, s=26, alpha=0.7, label=nom) ax.set(title="Yosh va sinovdagi eng yuqori yurak urishi", xlabel="yosh", ylabel="zarba/daqiqa") ax.legend() plt.show() print(toza.groupby("kasal")["thalach"].median().rename({0: "sog'lom", 1: "kasal"}).to_dict())
{"sog'lom": 161.0, 'kasal': 142.0}

Ikki naqsh: yosh oshgan sari eng yuqori yurak urishi tushadi (ikkala guruhda), va har yoshda kasal bemorlar (qizil x) pastroqda. Median farqi — taxminan 20 zarba. Bu yerda marker ham farqli — rang ajratolmaydigan o'quvchi uchun (21-bob).
5. Ko'krak og'rig'i turi: kutilmagan natija
ulush = pd.crosstab(toza["cp"], toza["kasal"], normalize="index").mul(100).round(0) ulush.index = ["1 tipik", "2 atipik", "3 og'riq emas", "4 belgisiz"] ax = ulush.plot(kind="barh", stacked=True, color=["#137333", "#c5221f"], figsize=(8, 3), legend=False, title="Og'riq turi bo'yicha kasallik ulushi, %") ax.bar_label(ax.containers[1], fmt="%.0f%%", label_type="center", color="white", weight="bold") plt.show() pd.crosstab(toza["cp"], toza["kasal"], margins=True)
| cp | 0 | 1 | All |
|---|---|---|---|
| 1 | 16 | 7 | 23 |
| 2 | 40 | 9 | 49 |
| 3 | 65 | 18 | 83 |
| 4 | 39 | 103 | 142 |
| All | 160 | 137 | 297 |

Eng yuqori kasallik ulushi — og'riq belgisi yo'q (4) bemorlarda. Bu g'alati tuyuladi, lekin sababi tanlovda: bu bemorlar klinikaga og'riq bilan emas, boshqa belgilar yoki yo'llanma bilan kelgan — ularni tekshirishga sabab bo'lgan narsa allaqachon xavf belgisi. Ma'lumot kim tekshirilganini aks ettiradi — umumiy aholini emas. Bu — har qanday tahlilning eng muhim savoli: ma'lumot qanday yig'ilgan?
6. Hammasi birga: korrelyatsiya
ustunlar = ["age", "sex", "cp", "trestbps", "chol", "thalach", "exang", "oldpeak", "ca", "thal", "kasal"] korr = toza[ustunlar].corr() fig, ax = plt.subplots(figsize=(8, 6.4)) rasm = ax.imshow(korr, cmap="RdBu_r", vmin=-1, vmax=1) ax.set_xticks(range(len(ustunlar)), ustunlar, rotation=45, ha="right") ax.set_yticks(range(len(ustunlar)), ustunlar) for i in range(len(ustunlar)): for j in range(len(ustunlar)): ax.text(j, i, f"{korr.iloc[i, j]:.2f}", ha="center", va="center", fontsize=7.5, color="white" if abs(korr.iloc[i, j]) > 0.5 else "black") fig.colorbar(rasm, label="korrelyatsiya") ax.set_title("Belgilar orasidagi korrelyatsiya") plt.show() korr["kasal"].drop("kasal").sort_values().round(2)
| kasal | |
|---|---|
| thalach | -0.42 |
| chol | 0.08 |
| trestbps | 0.15 |
| age | 0.23 |
| sex | 0.28 |
| cp | 0.41 |
| exang | 0.42 |
| oldpeak | 0.42 |
| ca | 0.46 |
| thal | 0.53 |

Bu rasm 21-bobdagi qoidalarni bajaradi: ikki tomonlama shkala (RdBu_r), markaz — 0 (vmin=-1, vmax=1), har katakda son. Oxirgi qator — har belgining tashxis bilan aloqasi: thal, ca, cp, oldpeak, exang — musbat (qiymat oshsa, kasallik ehtimoli oshadi), thalach — manfiy (3- va 4-qadamda ko'rganimizdek). Xolesterin va qon bosimi esa bu ma'lumotda kuchsiz bog'langan.
Ikki ogohlantirish. Birinchisi: cp va thal — kategoriyalar (raqam faqat nom), ular uchun korrelyatsiya taxminiy. Ikkinchisi: korrelyatsiya — sabab emas. "ca yuqori → kasallik" degani tomirdagi nuqson kasallikni keltirib chiqaradi degani emas: ikkalasi ham bitta jarayonning belgisi bo'lishi mumkin.
7. Yakuniy hisobot varag'i
fig, axd = plt.subplot_mosaic([["balans", "yosh", "urish"], ["og'riq", "korr", "korr"]], figsize=(14, 8), layout="constrained") toza["kasal"].map({0: "sog'lom", 1: "kasal"}).value_counts().plot( kind="bar", ax=axd["balans"], color=["#137333", "#c5221f"], rot=0, title="Bemorlar", xlabel="") for qiymat, rang, nom in [(0, "#137333", "sog'lom"), (1, "#c5221f", "kasal")]: q = toza[toza["kasal"] == qiymat] axd["yosh"].hist(q["age"], bins=range(28, 80, 4), alpha=0.55, color=rang, label=nom) axd["urish"].scatter(q["age"], q["thalach"], c=rang, s=12, alpha=0.6, label=nom) axd["yosh"].set_title("Yosh") axd["yosh"].legend() axd["urish"].legend() axd["urish"].set(title="Yosh va eng yuqori yurak urishi", xlabel="yosh", ylabel="zarba/daq") ulush.plot(kind="barh", stacked=True, ax=axd["og'riq"], color=["#137333", "#c5221f"], legend=False, title="Og'riq turi: kasallik ulushi, %") kuch = korr["kasal"].drop("kasal").sort_values() axd["korr"].barh(kuch.index, kuch.values, color=["#1a73e8" if v < 0 else "#c5221f" for v in kuch]) axd["korr"].axvline(0, color="black", linewidth=0.8) axd["korr"].set(title="Har belgining tashxis bilan korrelyatsiyasi", xlim=(-0.6, 0.6)) for ax in axd.values(): ax.spines[["top", "right"]].set_visible(False) fig.suptitle(f"Yurak kasalligi: {len(toza)} bemor, Klivlend 1988 (o'quv tahlili)", fontsize=15, weight="bold") fig.savefig(ISH / "yurak_hisobot.png", dpi=150, bbox_inches="tight", facecolor="white") plt.show() fig.savefig(ISH / "yurak_hisobot.svg", bbox_inches="tight") print("PNG:", round(os.path.getsize(ISH / "yurak_hisobot.png") / 1024), "KB | SVG:", round(os.path.getsize(ISH / "yurak_hisobot.svg") / 1024), "KB")
PNG: 154 KB | SVG: 132 KB

Bu varaqda SVG hali PNG dan kichik. Lekin vektor faylda har nuqta — alohida buyruq, shuning uchun nuqtalar ko'paysa, SVG tez o'sadi. O'lchab ko'ramiz:
rng = np.random.default_rng(0) for n in [300, 5_000, 50_000]: nuqta = rng.normal(size=(n, 2)) fig, ax = plt.subplots(figsize=(6, 4)) ax.scatter(nuqta[:, 0], nuqta[:, 1], s=4) fig.savefig(ISH / "sinov.png", dpi=150) fig.savefig(ISH / "sinov.svg") plt.close(fig) print(f"{n:>6} nuqta: PNG {os.path.getsize(ISH / 'sinov.png') / 1024:7.0f} KB | SVG {os.path.getsize(ISH / 'sinov.svg') / 1024:7.0f} KB")
300 nuqta: PNG 21 KB | SVG 43 KB 5000 nuqta: PNG 72 KB | SVG 532 KB 50000 nuqta: PNG 57 KB | SVG 5220 KB
PNG hajmi nuqtalar soniga deyarli bog'liq emas — u faqat piksellar sonidan iborat. SVG esa nuqtalar bilan birga o'sadi: 300 nuqtada ham u PNG dan katta, 50 000 da esa yuz barobardan ko'p. Hisobot varag'ida SVG kichikroq chiqqani — u katta rasm (PNG ning piksellari ko'p), nuqtalari esa atigi 297 ta. Qoida: chiziq va ustunli grafik (bobning boshidagi sin/cos kabi) — SVG, ko'p nuqtali scatter — PNG.
Varaq tarkibi: 19-bobdagi subplot_mosaic, 20-bobdagi pandas grafiklari, 21-bobdagi tozalik (chegaralarsiz, rang ma'noli), va bu bobdagi savefig.
8. Xulosalar — raqamlar bilan
k = toza.groupby("kasal") xulosa = { "bemorlar": len(toza), "kasallar ulushi, %": round(toza["kasal"].mean() * 100, 1), "o'rtacha yosh farqi, yil": round(k["age"].mean().diff().iloc[-1], 1), "yurak urishi mediani farqi": round(k["thalach"].median().diff().iloc[-1], 1), "belgisiz og'riqda kasallik, %": ulush.loc["4 belgisiz", 1], "eng kuchli musbat belgi": korr["kasal"].drop("kasal").idxmax(), "eng kuchli manfiy belgi": korr["kasal"].drop("kasal").idxmin(), } pd.Series(xulosa, name="qiymat")
| qiymat | |
|---|---|
| bemorlar | 297 |
| kasallar ulushi, % | 46.1 |
| o'rtacha yosh farqi, yil | 4.1 |
| yurak urishi mediani farqi | -19 |
| belgisiz og'riqda kasallik, % | 73 |
| eng kuchli musbat belgi | thal |
| eng kuchli manfiy belgi | thalach |
Xulosalar matnda emas, koddan chiqadi: ma'lumot yangilansa yoki bitta qadam o'zgarsa, ular ham o'zi yangilanadi. Hisobotga yozadiganingiz — shu jadval, plus cheklovlar: kichik namuna, bitta klinika, 1988-yil, korrelyatsiya ≠ sabab.
Kitob yakuni
Uch qismda bitta yo'lni bosib o'tdik:
- NumPy — sonlar jadvali va uning ustidagi tez amallar: shakl, indekslash, broadcasting, aggregatsiya.
- pandas — nomli, aralash turli jadval: o'qish, so'roq, tozalash, guruhlash, birlashtirish, vaqt.
- Matplotlib — savolga mos grafik, panjara, sozlash va hisobot.
Ularni bog'laydigan bitta odat: har qadamdan keyin tekshirish — shape, dtypes, isna().sum(), qatorlar soni, jami summa, va rasm. Kutubxonalar xato kamdan-kam beradi; noto'g'ri, lekin ishonarli natijani esa tez-tez beradi.
Keyingi qadam — ML kitobi: bu yerda tayyorlagan jadval va grafiklar u yerda modelga kirish ma'lumoti bo'ladi. Yurak ma'lumotiga u yerda yana qaytish mumkin: "bemorda kasallik bormi?" — klassifikatsiya masalasi.
Mashqlar
- Erkak va ayollar orasida kasallik ulushini solishtiring (
sex). Natijani qanday talqin qilasiz — tanlov haqidagi 5-qadamni eslang. thalbo'yicha kasallik ulushini 5-qadamdagi kabi grafikda chizing.- Yakuniy varaqqa yettinchi chizma qo'shing:
oldpeakning kasal va sog'lom guruhlardagi boxplot i. - Hisobot varag'ini SVG formatda saqlang va hajmini PNG bilan solishtiring.
Javoblar
pd.crosstab(toza["sex"], toza["kasal"], normalize="index")— erkaklarda ulush ancha yuqori; lekin bu ma'lumotda erkaklar ham ko'p va ular boshqa sabablar bilan yuborilgan bo'lishi mumkin — sabab haqida xulosa chiqarib bo'lmaydi.pd.crosstab(toza["thal"], toza["kasal"], normalize="index").plot(kind="barh", stacked=True).subplot_mosaicga yangi nom qo'shing vaboxplot([sog'lom, kasal]).- 7-qadamda o'lchadik: bu varaqda SVG kichikroq, lekin 5 000 nuqtadan keyin u PNG dan ko'p barobar katta bo'ladi. Varaqni scatter siz saqlasangiz, SVG yanada kichrayadi.
Bu bobni bepul o'qidingiz.
Kitob o'rgatadi, lekin sizning kodingiz nega ishlamaganini ayta olmaydi. Kursda uy vazifangiz tekshiriladi.