Asosiy qismga o'tish

NumPy nima va nega kerak

1-bob 6 kod bloki 1528 so'z

Sahna: bir million son

Sizga do'konning bir yillik savdosi berildi: bir million chek, har birida summa. Birinchi savol oddiy — o'rtacha chek qancha?

Python bilan buni allaqachon bilasiz: sum(summalar) / len(summalar). Ishlaydi. Lekin keyin savollar ko'payadi: har chekdan QQS ni ayiring, chegirmani qo'llang, eng katta 10 tasini toping, bir oyni boshqasi bilan solishtiring. Har biri — yana bir sikl, yana bir million qadam.

NumPy (Numerical Python) — aynan shu ish uchun yaratilgan kutubxona: ko'p sonlar ustida bir xil amalni tez bajarish. pandas ham, Matplotlib ham, scikit-learn ham, PyTorch ham ichida NumPy array lardan foydalanadi. Shuning uchun bu kitob undan boshlanadi: keyingi ikki qism shu poydevorga quriladi.

NumPy ni chaqirish

python3 qator
import numpy as np

print(np.__version__)
Chiqish
2.2.6

np — butun dunyo ishlatadigan qisqartma. Boshqa nom bersangiz ham kod ishlaydi, lekin hujjatlar, kitoblar va savol-javob saytlaridagi har bir misol np. bilan yozilgan — o'qishda adashmaslik uchun shu nomni ishlating.

Avval o'lchaymiz: list va array

Gapga ishonmaymiz — o'lchaymiz. Bir xil bir million sonni Python list da va NumPy array da saqlab, ikkalasining yig'indisini hisoblaymiz va vaqtni yozib boramiz.

python25 qator
import time

rng = np.random.default_rng(0)
summalar = rng.integers(5_000, 500_000, size=1_000_000)
royxat = summalar.tolist()


def olchov(ish, takror=5):
    eng_tez = float("inf")
    for _ in range(takror):
        boshi = time.perf_counter()
        ish()
        eng_tez = min(eng_tez, time.perf_counter() - boshi)
    return eng_tez * 1000


natija = {
    "yig'indi — list": olchov(lambda: sum(royxat)),
    "yig'indi — array": olchov(lambda: summalar.sum()),
    "* 0.88 — list": olchov(lambda: [s * 0.88 for s in royxat]),
    "* 0.88 — array": olchov(lambda: summalar * 0.88),
}
for nom, ms in natija.items():
    print(f"{nom:<17} {ms:8.2f} ms")
print(f"\nyig'indi bir xilmi: {sum(royxat) == summalar.sum()}")
Chiqish
yig'indi — list       3.27 ms
yig'indi — array      0.15 ms
* 0.88 — list        24.85 ms
* 0.88 — array        0.87 ms

yig'indi bir xilmi: True

Har ish 5 marta takrorlandi va eng tezi olindi — kompyuter boshqa ish bilan band bo'lgan bir lahzalik sekinlashuv natijani buzmasligi uchun. Aniq millisekundlar sizning kompyuteringizda boshqacha chiqadi; muhimi — farqning yo'nalishi va kattaligi. Rasmda ko'rinadi (jigarrang — list, ko'k — array; vaqt shu kitob qurilgan kompyuterda o'lchangan):

python10 qator
fig, ax = plt.subplots(figsize=(8, 3.2))
nomlar = list(natija)
ranglar = ["#b06000", "#1a73e8", "#b06000", "#1a73e8"]
ax.barh(nomlar, list(natija.values()), color=ranglar)
ax.invert_yaxis()
ax.set_xlabel("millisekund (kamroq — yaxshiroq)")
ax.set_title("Bir million son: list va NumPy array")
for i, ms in enumerate(natija.values()):
    ax.text(ms, i, f" {ms:.1f}", va="center")
plt.show()

Ikkala ish ham bir xil natija beradi (yig'indi tengligini tekshirdik), lekin array ko'p barobar tezroq — bu kitob qurilgan kompyuterda o'nlab barobar. Ko'paytirishdagi farq ayniqsa katta: list bilan Python har bir son uchun alohida amal bajaradi, array bilan esa bitta buyruq bir million sonni birdan ko'paytiradi.

Nega array tez: xotira ichida nima bor

Farqning sababi — sonlar xotirada qanday turishida.

List xotirada ko'rsatkichlar saqlaydi, array esa sonlarning o'zini ketma-ket saqlaydi

List xotirada ko'rsatkichlar saqlaydi, array esa sonlarning o'zini ketma-ket saqlaydi

Python list sonning o'zini emas, unga ko'rsatkichni (adresni) saqlaydi. Har bir son — xotiraning qaysidir burchagidagi alohida obyekt: ichida turi, nechta joyda ishlatilayotgani (hisob) va qiymatning o'zi. Shuning uchun list ga bir vaqtning o'zida son, matn va boshqa list solsa bo'ladi — u moslashuvchan. Lekin moslashuvchanlikning narxi bor: har bir qo'shishda Python avval "bu nima, son mi, matn mi?" deb so'raydi.

NumPy array esa boshida bitta sarlavha saqlaydi — dtype (hamma elementning umumiy turi) va shape (o'lchami) — keyin sonlarning o'zini ketma-ket, bo'shliqsiz yozadi. Uchta natija chiqadi:

  1. Tur bitta — har element uchun "bu nima?" deb so'rash kerak emas.
  2. Joy ketma-ket — protsessor xotiradan bir martada bir nechta sonni o'qiydi.
  3. Sikl C tilida — summalar * 0.88 ni yozganingizda sikl Python da emas, NumPy ning C da yozilgan ichki qismida aylanadi.

Bu gapni ham o'lchab ko'ramiz — xotira hajmi bilan:

python8 qator
import sys

bitta_son = sys.getsizeof(7)
royxat_jami = sys.getsizeof(royxat) + sum(sys.getsizeof(s) for s in royxat)
print(f"bitta Python int obyekti: {bitta_son} bayt")
print(f"array ichida bitta son:  {summalar.itemsize} bayt ({summalar.dtype})")
print(f"list (ko'rsatkichlar + obyektlar): {royxat_jami / 1e6:.1f} MB")
print(f"array (faqat sonlar):              {summalar.nbytes / 1e6:.1f} MB")
Chiqish
bitta Python int obyekti: 28 bayt
array ichida bitta son:  8 bayt (int64)
list (ko'rsatkichlar + obyektlar): 36.0 MB
array (faqat sonlar):              8.0 MB

Bir xil million son: list 36 MB, array 8 MB. List da har son uchun 28 baytlik obyekt va unga 8 baytlik ko'rsatkich kerak; array da esa faqat sonning o'zi — 8 bayt.

Bir xil belgi — boshqa ma'no

NumPy bilan ishlashda yangi boshlovchilar eng ko'p qoqiladigan joy: * va + list va array da butunlay boshqa ish qiladi.

list da * va + takrorlaydi va ulaydi, array da esa har element bilan hisoblaydi

list da * va + takrorlaydi va ulaydi, array da esa har element bilan hisoblaydi

python7 qator
royxat_kichik = [1, 2, 3]
massiv = np.array([1, 2, 3])

print("list * 2: ", royxat_kichik * 2)
print("array * 2:", massiv * 2)
print("list + list:  ", [1, 2] + [10, 20])
print("array + array:", np.array([1, 2]) + np.array([10, 20]))
Chiqish
list * 2:  [1, 2, 3, 1, 2, 3]
array * 2: [2 4 6]
list + list:   [1, 2, 10, 20]
array + array: [11 22]

List matematikadan emas, ro'yxatdan o'ylaydi: "ikki marta" — ro'yxatni ikki marta yozish, "qo'shish" — ikki ro'yxatni ulash. Array esa sonlardan o'ylaydi: har amal har elementga qo'llanadi. Bu usul vectorization (vektorlashtirish) deyiladi — sikl yozmasdan butun ma'lumotga bitta amal.

Bitta tur — bitta qoida

Array ichidagi hamma element bir turda bo'lishi shart. Har xil turdagi qiymatlar bersangiz, NumPy xato bermaydi — hammasini sig'diradigan bitta turni o'zi tanlaydi. Bu ba'zan kutilmagan natija beradi:

python5 qator
print(np.array([1, 2, 3]).dtype)
print(np.array([1, 2, 3.5]).dtype)
aralash = np.array([1, 2, "3"])
print(aralash.dtype, aralash)
print(aralash.astype(int) * 2)
Chiqish
int64
float64
<U21 ['1' '2' '3']
[2 4 6]

Bitta kasr son butun massivni float64 ga o'tkazdi. Bitta matn esa hamma sonni matnga aylantirdi (<U21 — Unicode matn): '1' endi son emas. Qaytarish uchun turni o'zingiz aytasiz — astype(int), shundan keyin * 2 yana ko'paytiradi. Real hayotda bu shunday ko'rinadi: CSV fayldagi bitta katakda "noma'lum" deb yozilgan — butun ustun son emas, matn bo'lib qoladi. 9-bobda pandas bilan aynan shu muammoni uchratamiz.

Qachon NumPy kerak emas

NumPy — bolg'a, lekin hamma narsa mix emas:

Vaziyat Nima ishlatiladi Nega
Ko'p son, bir xil amal NumPy array tez va kam xotira
Har xil turdagi ustunli jadval (ism, sana, summa) pandas DataFrame har ustunning o'z turi bor (2-qism)
10–20 ta qiymat, tez-tez qo'shish/o'chirish Python list array ga element qo'shish har safar yangi massiv yaratadi
Matnlar ro'yxati bilan murakkab ish list yoki pandas .str NumPy matn uchun qurilmagan

Xulosa

  • NumPy — ko'p sonlar ustida bir xil amalni tez bajarish uchun; pandas, Matplotlib va ML kutubxonalari uning ustiga qurilgan.
  • Array tez, chunki: bitta tur, ketma-ket joy, sikl C da.
  • Array kam xotira oladi: har son o'z hajmida, ko'rsatkichsiz.
  • * va + array da har elementga qo'llanadi (vectorization); list da esa takrorlaydi va ulaydi.
  • Array da hamma element bir turda; aralash turlar eng "keng" turga — ba'zan matnga — o'tadi.

Mashqlar

  1. np.array([True, 2, 3]) ning dtype i qanday bo'ladi? Avval taxmin qiling, keyin tekshiring. Nega shunday?
  2. Bir million sonning kvadratini list (sikl) va array bilan hisoblab, vaqtini o'lchang. Qaysi biri tezroq va necha barobar?
  3. [0.1] * 3 va np.array([0.1]) * 3 natijalarini solishtiring. Qaysi biri "takrorlash", qaysi biri "ko'paytirish"?

Javoblar

  1. int64: True son sifatida 1 ga aylanadi, chunki butun son bool ni sig'diradi.
  2. Array tezroq; aniq barobar kompyuteringizga bog'liq — olchov funksiyasidan foydalaning.
  3. List — [0.1, 0.1, 0.1] (takrorlash), array — [0.3] (ko'paytirish, bitta element).

Bu bobni bepul o'qidingiz.

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

Telegramda yozish Narxlar