در این فصل چه یاد میگیری#
در ترمِ یک با فهرست کار کردی و برای هر کاری حلقه نوشتی. برای چهار محصول عالی بود. برای دویستهزار ردیف نه — نه فقط چون کند است، بلکه چون کدش خوانا نیست و اشتباهش دیر پیدا میشود. در این فصل numpy را میبینی و اولین ساختارِ دادهٔ واقعیِ دنیای علمِ داده را: آرایه. یاد میگیری چرا آرایه از فهرست فرق دارد، shape چیست و چرا مهمترین چیزی است که باید همیشه بدانی، و چطور یک جدول در حافظه شکل میگیرد.

آخر این فصل میتوانی:
- یک
ndarrayبسازی وshapeوdtypeآن را بخوانی - بگویی آرایه از
listدر چه چیزی فرق دارد و چرا این تفاوت اهمیت دارد - آرایهٔ آماده بسازی با
zeros،arangeوlinspace - یک آرایهٔ دوبعدی بسازی و بگویی هر عددِ
shapeیعنی چه - خطای ناسازگاریِ شکل را تشخیص بدهی
قبل از شروع#
از ترمِ ۱ فصل ۳: list، اندیس از صفر، و برش.
از ترمِ ۱ فصل ۶: حلقه و الگوی انباشتگر — که در این ترم کمکم جایش را به چیزِ بهتری میدهد.
📓 نوتبوک: نوتبوک این فصل را در Colab باز کن — همهٔ کدهای این فصل آماده و بهترتیب داخلش هست.
۱. اولین آرایه#
numpy در Colab از قبل نصب است. فقط import میخواهد، و قرارداد جهانی این است که با نامِ np صدایش بزنی.
import numpy as np
prices = np.array([850000, 1200000, 430000, 2100000])
print(prices)
print(type(prices))
print(prices.shape)
print(prices.dtype)
[ 850000 1200000 430000 2100000]
<class 'numpy.ndarray'>
(4,)
int64
سه چیزِ تازه اینجاست:
ndarrayنامِ نوعِ آرایه درnumpyاست (کوتاهشدهٔ «آرایهٔ n-بُعدی»).shapeشکلِ آرایه است، بهصورتِtuple. اینجا(4,)یعنی «یک بُعد، با چهار عنصر». آن ویرگولِ تنها یادت میاندازد کهtupleاست، نه عدد.dtypeنوعِ عنصرها را میگوید — و اینجا اولین تفاوتِ بزرگ باlistاست: همهٔ عنصرهای یک آرایه یک نوع دارند. فهرست میتوانست عدد و متن را قاطی نگه دارد؛ آرایه نمیتواند.
این محدودیت، در واقع همان چیزی است که آرایه را سریع میکند: وقتی همه یک نوعاند، numpy میداند هر عنصر دقیقاً چند بایت است و میتواند کلِ بلوکِ حافظه را یکجا پردازش کند.
✅ چک کن:
shapeباید(4,)باشد وdtypeبایدint64. اگرdtypeراfloat64دیدی، یعنی یکی از اعدادت اعشاری بوده —numpyهمه را به بالاترین نوعِ مشترک میبرد.
۲. وقتی نوعها قاطی میشوند#
mixed = np.array([1, 2.5, 3])
text_like = np.array([1, "دو", 3])
print(mixed, mixed.dtype)
print(text_like, text_like.dtype)
[1. 2.5 3. ] float64
['1' 'دو' '3'] <U21
آرایهٔ اول همه را float کرد، چون عددِ صحیح در اعشاری جا میشود ولی برعکسش نه. آرایهٔ دوم — که یک متن داشت — همهچیز را متن کرد، حتی عددها. آن <U21 یعنی «رشتهٔ یونیکد»؛ عددِ کنارش فقط بیشترین طولی است که numpy برای این ستون کنار گذاشته، نه چیزِ مهمی.
این دومی یک تلهٔ واقعی است: هیچ خطایی نگرفتی، ولی حالا آرایهات از عدد به متن تبدیل شده و هیچ محاسبهای رویش کار نمیکند. در همین ترم، فصلِ ۵، دقیقاً همین اتفاق روی یک ستونِ قیمتِ واقعی میافتد — و آنجا میبینی چقدر بیسروصدا اتفاق میافتد.
۳. آرایههای آماده#
اغلب نمیخواهی عددها را با دست بنویسی:
print(np.zeros(5))
print(np.ones(3, dtype=int))
print(np.arange(0, 10, 2))
print(np.linspace(0, 1, 5))
[0. 0. 0. 0. 0.]
[1 1 1]
[0 2 4 6 8]
[0. 0.25 0.5 0.75 1. ]
zeros(n)وones(n)آرایهٔ پُر از صفر یا یک میسازند — معمولاً بهعنوانِ ظرفِ خالی برای پُر کردن.arange(start, stop, step)همانrangeاست ولی خروجیاش آرایه است و میتواند گامِ اعشاری داشته باشد.linspace(start, stop, n)فرق دارد و خیلی بهکارت میآید:nعددِ همفاصله بینِ دو مقدار میدهد و هر دو سر را شامل میشود. درarangeگام را میدهی؛ درlinspaceتعداد را.
💡 نکته: برای رسمِ نمودار تقریباً همیشه
linspaceمیخواهی، نهarange. وقتی میگویی «صد نقطه بینِ صفر و یک»،linspace(0, 1, 100)دقیقاً همان است و لازم نیست گام را حساب کنی.
۴. آرایهٔ دوبعدی: شکلِ یک جدول#
دادهٔ واقعی جدول است: ردیف و ستون. در numpy این یک آرایهٔ دوبعدی است.
table = np.array([
[850000, 12],
[1200000, 0],
[430000, 7],
[2100000, 3],
])
print(table)
print("shape:", table.shape)
print("تعداد بُعد:", table.ndim)
print("تعداد کل عنصر:", table.size)
[[ 850000 12]
[1200000 0]
[ 430000 7]
[2100000 3]]
shape: (4, 2)
تعداد بُعد: 2
تعداد کل عنصر: 8
shape اینجا (4, 2) است: چهار ردیف، دو ستون. ترتیبش همیشه همین است — اول ردیف، بعد ستون — و از این به بعد هر جا shape دیدی باید بتوانی بلافاصله بگویی هر عدد چه چیزی را میشمارد.
دسترسی هم دوبخشی میشود:
print(table[0])
print(table[0, 1])
print(table[:, 0])
print(table[1:3, :])
[850000 12]
12
[ 850000 1200000 430000 2100000]
[[1200000 0]
[ 430000 7]]
table[0] کلِ ردیفِ اول را میدهد. table[0, 1] یک عنصر: ردیفِ صفر، ستونِ یک. و مهمترینش table[:, 0] است: دونقطهٔ تنها یعنی «همه»، پس این یعنی «همهٔ ردیفها، ستونِ صفر» — یعنی یک ستونِ کامل. این الگو را زیاد میبینی.
قبل از ادامه، جواب بده: آرایهای با shape برابرِ (1000, 5) داری. arr[:, 2] چه شکلی دارد و چند عنصر است؟ جواب: shape آن (1000,) است و هزار عنصر دارد — ستونِ سوم برای همهٔ هزار ردیف. برشِ یک ستون، یک بُعد کم میکند.
۵. یک خطای واقعی: شکلها نمیخوانند#
a = np.array([1, 2, 3])
b = np.array([10, 20, 30, 40])
print(a + b)
ValueError: operands could not be broadcast together with shapes (3,) (4,)
پیام هر دو شکل را دقیقاً میگوید: (3,) و (4,). numpy نمیتواند حدس بزند کدام عنصر با کدام جمع شود، پس میایستد.
این رایجترین خطای کلِ کارِ داده است و آن کلمهٔ broadcast در پیام، اسمِ قاعدهای است که فصلِ بعد کاملاً بازش میکند.
🔧 اگر کار نکرد: هر وقت خطایی دیدی که کلمهٔ
shapeیاbroadcastدر آن بود، قبل از هر کاریshapeهر دو طرف را چاپ کن. در نود درصدِ موارد یکی از آنها همانی نیست که فکر میکردی — مثلاً(1000, 1)است بهجای(1000,). این عادت را از همینجا بساز؛ در ترمِ ۳ و در ژرفا بارها نجاتت میدهد.
۶. چرا اصلاً این کار را میکنیم#
در کدِ زیر سه چیزِ تازه هست که هیچکدام ربطی به numpy ندارند ولی از این به بعد مرتب میبینیشان:
- زیرخط داخلِ عدد.
2_000_000دقیقاً همان2000000است؛ پایتون زیرخطها را نادیده میگیرد و آنها فقط برای چشمِ تو هستند، مثلِ ویرگولِ هزارگان. برای عددهای بزرگ همیشه اینطور بنویس. time.perf_counter()یک کرنومتر است: عددی برحسبِ ثانیه میدهد که خودش بهتنهایی معنایی ندارد، ولی تفاضلِ دو بار صدا زدنش میگوید بینشان چقدر طول کشیده.{x:.3f}داخلِ f-string. تا حالا داخلِ آکولاد فقط مقدار میگذاشتی؛ هر چیزی بعد از دونقطه یک دستورِ قالببندی است..3fیعنی «عددِ اعشاری با سه رقم بعد از ممیز» و.0fیعنی «بدونِ اعشار». بعداً{x:>8}را هم میبینی که یعنی «در هشت خانه، چسبیده به راست» — فقط برای اینکه ستونهای خروجی زیرِ هم بنشینند.
import time
size = 2_000_000
py_list = list(range(size))
np_array = np.arange(size)
start = time.perf_counter()
doubled_list = [x * 2 for x in py_list]
list_time = time.perf_counter() - start
start = time.perf_counter()
doubled_array = np_array * 2
array_time = time.perf_counter() - start
print(f"list: {list_time:.3f} ثانیه")
print(f"array: {array_time:.3f} ثانیه")
print(f"نسبت: حدود {list_time / array_time:.0f} برابر")
list: 0.180 ثانیه
array: 0.004 ثانیه
نسبت: حدود 45 برابر
عددهای تو با اینها فرق میکنند — به ماشینِ Colab و شلوغیِ لحظه بستگی دارد. چیزی که ثابت است جهتش است: آرایه دهها برابر سریعتر است.
ولی به آن خطِ np_array * 2 دقت کن، چون مهمتر از سرعت است: هیچ حلقهای ننوشتیم. یک عملیات روی کلِ آرایه. این را در فصلِ بعد کامل میبینی و همانجاست که کارِ داده واقعاً شروع میشود.
📏 اندازه بگیر: این اولین باری است که در این دوره ادعایی دربارهٔ سرعت کردیم، و توجه کن که اندازهاش گرفتیم — نه اینکه بگوییم «numpy سریعتر است». همین الگو در کلِ دوره برقرار است: هر ادعایی که میشود امتحانش کرد، امتحان میشود. و توجه کن که عدد را گِرد گفتیم («حدود ۴۵ برابر»)، چون عددِ دقیق در هر اجرا فرق میکند و نوشتنِ سه رقمِ اعشار برایش، دقتی را ادعا میکند که وجود ندارد.
واژههای تازهٔ این فصل#
| کلمه | تلفظ به حروف فارسی | یعنی چه |
|---|---|---|
| numpy | نامپای | کتابخانهٔ پایهٔ کارِ عددی در پایتون |
| ndarray | اِندیآری | آرایهٔ n-بُعدیِ numpy؛ همهٔ عنصرها یک نوع دارند |
| shape | شِیپ | شکلِ آرایه بهصورتِ tuple؛ اول ردیف، بعد ستون |
| dtype | دیتایپ | نوعِ عنصرهای آرایه |
| linspace | لیناسپیس | n عددِ همفاصله بینِ دو مقدار، شاملِ هر دو سر |
تمرینها
اول خودت فکر کن یا امتحان کن — بعد اینجا را باز کن.
در فصل بعد#
حالا آرایه داری، ولی هنوز مثلِ فهرست با آن رفتار میکنی. فصلِ بعد قدرتِ اصلی را نشان میدهد: کارِ برداری. یاد میگیری روی کلِ یک آرایه یکجا عملیات انجام بدهی، شرط بگذاری و فقط عنصرهایی را که میخواهی جدا کنی — همه بدونِ یک خط حلقه. آن قاعدهٔ broadcast که در خطای بخشِ ۵ دیدی هم همانجا کامل توضیح داده میشود.
به آخر این فصل رسیدی!
اگر ساختی و جواب داد، این دکمه مال توست.