Asosiy qismga o'tish

Saqlash va yakuniy loyiha: yurak kasalligi ma'lumoti

22-bob 11 kod bloki 3146 so'z

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

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

python18 qator
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.png36.1
rasm_300.png133
rasm.svg28.3
rasm.pdf13.8
rasm.jpg21.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
PDF 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.

python3 qator
yurak = pd.read_csv(DATA / "yurak.csv")
print(yurak.shape)
yurak.head()
Chiqish
(303, 14)
agesexcptrestbpscholfbsrestecgthalachexangoldpeakslopecathalnum
063111452331215002.33060
167141602860210811.52332
267141202290212912.62271
337131302500018703.53030
441021302040217201.41030

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)

python2 qator
yurak.info()
print("takror qatorlar:", yurak.duplicated().sum())
Chiqish
<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)

python6 qator
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"})
Chiqish
bo'sh katagi bor qatorlar: 6 (2.0%)
(297, 15)
kasalcount
sog'lom160
kasal137

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?

python7 qator
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"})
kasalmeanmedian
sog'lom52.652
kasal56.858

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

python8 qator
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())
Chiqish
{"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

python7 qator
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)
cp01All
116723
240949
3651883
439103142
All160137297

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

python14 qator
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
chol0.08
trestbps0.15
age0.23
sex0.28
cp0.41
exang0.42
oldpeak0.42
ca0.46
thal0.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

python26 qator
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")
Chiqish
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:

python9 qator
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")
Chiqish
   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

python11 qator
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
bemorlar297
kasallar ulushi, %46.1
o'rtacha yosh farqi, yil4.1
yurak urishi mediani farqi-19
belgisiz og'riqda kasallik, %73
eng kuchli musbat belgithal
eng kuchli manfiy belgithalach

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:

  1. NumPy — sonlar jadvali va uning ustidagi tez amallar: shakl, indekslash, broadcasting, aggregatsiya.
  2. pandas — nomli, aralash turli jadval: o'qish, so'roq, tozalash, guruhlash, birlashtirish, vaqt.
  3. 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

  1. Erkak va ayollar orasida kasallik ulushini solishtiring (sex). Natijani qanday talqin qilasiz — tanlov haqidagi 5-qadamni eslang.
  2. thal bo'yicha kasallik ulushini 5-qadamdagi kabi grafikda chizing.
  3. Yakuniy varaqqa yettinchi chizma qo'shing: oldpeak ning kasal va sog'lom guruhlardagi boxplot i.
  4. Hisobot varag'ini SVG formatda saqlang va hajmini PNG bilan solishtiring.

Javoblar

  1. 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.
  2. pd.crosstab(toza["thal"], toza["kasal"], normalize="index").plot(kind="barh", stacked=True).
  3. subplot_mosaic ga yangi nom qo'shing va boxplot([sog'lom, kasal]).
  4. 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.

Telegramda yozish Narxlar