کتابخانه Numpy در پایتون برای علوم داده

کتابخانه Numpy در پایتون، ابزارهای قدرتمندی برای محاسبات عددی مبتنی بر ماتریس‌ها و آرایه‌های چندبعدی در اختیار قرار می‌دهد. اسم این کتابخانه، Numpy، از مخفف عبارت Numerical Python می‌آید. این کتابخانه ابزارهای زیادی شامل انواع توابع ریاضی، توابع توزیع آماری، ابزارهای موردنیاز برای محاسبات جبر خطی و تبدیل فوریه (Fourier Transformations) و … را داراست.

هسته این کتابخانه مبتنی بر زبان C نوشته شده که باعث می‌شود محاسبات در Numpy با سرعت بالا انجام شود. همچنین این کتابخانه امکان محاسبات روی سیستم‌های توزیع‌شده و GPU (Graphics Processing Unit) را هم فراهم می‌کند. بسیاری از کتابخانه‌های مهم دیگر که در پایتون برای تحلیل داده و یادگیری ماشین استفاده می‌شوند؛ مانند Pandas، Matplotlib، SciPy و Sklearn از Numpy بهره می‌برند.

به همین دلیل اگر بخواهید از پایتون برای محاسبات ریاضی، تحلیل‌های آماری و یادگیری ماشین استفاده کنید، حتماً نیاز پیدا خواهید کرد که از این کتابخانه استفاده کنید. بر اساس تجربیاتم، من در این مقاله سعی می‌کنم مهم‌ترین مواردی را که نیاز دارید برای شروع کار با این کتابخانه بدانید، اشاره کنم.

لازم است یادآوری کنم که وب‌سایت این کتابخانه به نشانی زیر حاوی مطالب و محتوای آموزشی متنوعی است و توصیه می‌کنم، حتماً به آن سر بزنید:

https://numpy.org/

فراخوانی کتابخانه Numpy در پایتون

با فرض اینکه Numpy را روی سیستم خود نصب کرده باشید، با دستور import می‌توانید آن را فراخوانی کنید تا توابع آن برای استفاده در دسترس پایتون قرار گیرد. ازآنجاکه در پایتون هنگام استفاده از توابع یک کتابخانه لازم است همواره اسم آن کتابخانه را قبل از تابع بیاورید، می‌توانید یک اسم مخفف برای آن کتابخانه تعریف کنید. معمولاً در جامعه برنامه‌نویسان پایتون، Numpy را با np مخفف می‌کنند گرچه شما مختارید از هر اسم دیگری استفاده کنید.

در کد زیر من کتابخانه Numpy را با اسم مخفف np فراخوانی کردم و سپس نسخه آن را بررسی کردم. همان‌طور که مشخص است من از نسخه ۱٫۱۹٫۵ استفاده می‌کنم.

ساختار داده در کتابخانه Numpy

ساختاری که کتابخانه Numpy برای نگهداری داده‌ها از آن بهره می‌برد، آرایه است. فرض کنید من یک برداری از اعداد به نام a1 می‌خواهم ایجاد کنم که دارای پنج درایه شامل ۳٫۱، ۲٫۶، ۱٫۷، ۵٫۲، ۲٫۸ و ۱٫۹ است. برای این منظور لازم است از تابع array استفاده کنیم و درایه‌های بردار را در قالب لیست پایتون، به‌عنوان ورودی به آن بدهیم. به‌این‌ترتیب آرایه یک‌بعدی a1 ایجاد می‌شود:

اگر با استفاده از تابع type در پایتون، کلاس a1 را فراخوانی کنم، ndarray را برمی‌گرداند:

وقتی یک شیء از کلاس آرایه تعریف ‌می‌کنیم، این شیء یک سری ویژگی (attribute) دارد. برای فهم این موضوع، این‌طور تصور کنید که اشیاء در دنیای واقعی یک سری ویژگی دارند. برای مثال صندلی یک شیء است و پایه داشتن یک ویژگی صندلی محسوب می‌شود. یک صندلی ممکن است سه یا چهار یا بیشتر پایه داشته باشد. در آرایه یک‌بعدی هم ویژگی شکل (shape) بیانگر تعداد درایه‌های آرایه است:

دسترسی به درایه‌های آرایه

یکی از مهارت‌های بسیار مهمی که در کار با آرایه‌ها لازم است بر روی آن مسلط باشید، برش زدن داده و جدا کردن زیرمجموعه‌ای از درایه‌های آرایه است. برای مثال اگر من بخواهم درایه ۳٫۱ را از آرایه a1 فراخوانی کنم، با استفاده از اندیس (Index) آن می‌توانم این کار را انجام دهم. اندیس‌ها در Numpy از صفر شروع می‌شود؛ یعنی اندیس درایه ۳٫۱ ، صفر است، اندیس درایه ۲٫۶، یک است و الی‌آخر. برای فراخوانی یک درایه لازم است اندیس آن درایه را به شکل زیر داخل کروشه قرار دهیم و فراخوانی کنیم:

این امکان وجود دارد که چندین درایه را هم‌زمان باهم از a1 فراخوانی کنید. برای این منظور لازم است اندیس درایه‌ها را در قالب لیست داخل کروشه قرار دهیم:

در Numpy اندیس منفی هم معنی می‌دهد. اندیس‌ها با شروع از صفر و از سمت چپ به راست افزایش می‌یابند. در مقابل اندیس منفی از انتهای آرایه شروع می‌شود و از راست به چپ کاهش می‌یابند. برای مثال اندیس درایه ۱٫۹، پنج است. بر اساس اندیس گذاری منفی، اندیس آن ۱- است. به‌طور مشابه، اندیس مثبت درایه ۲٫۸، چهار و اندیس منفی آن ۲- است. در کد زیر من عدد ۱٫۹ را با اندیس منفی فراخوانی کردم:

در Numpy می‌توان یک آرایه را به طریق زیر از اندیس شروع (start) تا اندیس پایان (end) با گام‌های مشخص (step) برش زد:

[start:end:step]

اگر اندیس شروع مشخص نشود به‌صورت پیش‌فرض صفر در نظر گرفته می‌شود، اگر اندیس پایان مشخص نشود طول بعد آرایه در نظر گرفته می‌شود، اگر تعداد گام‌ها مشخص نشود، یک در نظر گرفته می‌شود. درباره اندیس پایان توجه کنید که درایه متناظر با اندیس پایان، فراخوانی نمی‌شود. مثال‌های زیر نحوه به‌کارگیری برش زدن آرایه را با این روش نشان می‌دهد:

ایجاد ماتریس در Numpy

از تابع array برای ساخت ماتریس هم می‌توان استفاده کرد. به‌این‌ترتیب که هر یک از ردیف‌ها را در داخل یک لیست به آن بدهیم. ماتریس سه در سه a2 را به شکل زیر ایجاد کردم:

اگر ویژگی شکل این آرایه را استخراج کنیم، تعداد سطرها و ستون‌های ماتریس را برمی‌گرداند:

برای دسترسی به آرایه‌های ماتریس باید از دو اندیس استفاده کرد که اولی در تناظر با ردیف و دومی در تناظر با ستون آن درایه است. برای نمونه، عدد ۵ واقع در ردیف با اندیس صفر و ستون با اندیس دو است، پس آن را به ترتیب زیر فراخوانی کردم:

اگر بخواهم تمام درایه‌های واقع در ردیف با اندیس صفر را فراخوانی کنم، به یکی از دو شکل زیر می‌توان انجام داد:

اگر بخواهم تمام درایه‌های واقع در ستون با اندیس صفر را فراخوانی کنم، به طریق زیر می‌توان انجام داد:

ایجاد آرایه‌های چندبعدی

گرچه بردارها و ماتریس‌ها بسیار کاربردی هستند، این امکان وجود دارد با استفاده از تابع array، آرایه‌ها با ابعاد بالاتر هم ایجاد کرد. برای مثال در کد زیر من یک آرایه سه‌بعدی ایجاد کردم. در این مثال من فرض کردم سه محصول a، b و c داریم و می‌خواهیم اطلاعات تقاضای این سه محصول را در فصول سال‌های ۱۳۹۹ و ۱۴۰۰ در آرایه a3 ذخیره کنم.

همان‌طور که مشخص است این آرایه سه بعد دارد. بعد اول مربوط به محصولات مختلف است. برای هر محصول یک ماتریس دوبعدی ایجاد شده که ردیف‌های آن در تناظر با اطلاعات سال‌های ۱۳۹۹ و ۱۴۰۰، و ستون‌های آن در تناظر با فصل‌ها است.

حال اگر بخواهم اطلاعات محصول a را که اندیس صفر دارد، فراخوانی کنم به شکل زیر عمل می‌کنم:

اگر بخواهم بدانم تقاضای محصول b، در فصل چهارم سال ۱۴۰۰ چه بوده، با فراخوانی اندیس‌ها می‌توان انجام داد:

نوع داده در آرایه‌ها

یکی دیگر از ویژگی‌های آرایه‌ها، نوع داده (dtype) است. همه درایه‌های یک آرایه‌ باید از یک نوع داده باشند. برای مثال، درایه‌های a2 همگی float64 هستند که برای ذخیره‌سازی اعداد اعشاری استفاده می‌شود و ۶۴ بیت از حافظه RAM را اشغال می‌کند. داده‌های آرایه a2 از نوع int32 است که برای ذخیره‌سازی اعداد صحیح استفاده می‌شود و ۳۲ بیت از حافظه RAM را اشغال می‌کند.

با تابع astype می‌توان نوع داده را تغییر داد. در کد زیر من نوع داده را در آرایه a2 از اعداد صحیح به اعداد اعشاری تغییر دادم و داخل new_a2 ذخیره کردم:

ایجاد تغییر در درایه‌های آرایه ایجادشده

اگر بخواهم درایه ۲٫۶ را در آرایه a1 به عدد دیگری مثلاً صفر تغییر دهم، لازم است با استفاده از اندیس آن را فراخوانی کنم سپس با عملگر تخصیص (در پایتون، مساوی عملگر تخصیص است) عدد جدید را به آن درایه تخصیص دهم:

با همین روش می‌توانم یکی از درایه‌های ماتریس a2 را تغییر دهم. برای مثال در کد زیر عدد ۶ را که در ردیف با اندیس ۱ و ستون با اندیس ۲ است، به ۱۰- تغییر دادم:

تغییر در ابعاد آرایه

تابع reshape این اجازه را می‌دهد که نحوه چینش درایه‌ها را در آرایه به شکلی که موردنظر تغییر است، دهیم. برای نمونه آرایه a1 یک آرایه یک‌بعدی است که ۶ درایه دارد. من این آرایه را با استفاده از تابع reshape به یک آرایه دوبعدی ۲ در ۳ تبدیل کردم و آن را در new_a1 ذخیره کردم:

چسباندن آرایه‌ها

تابع concatenate، امکان به هم متصل کردن آرایه‌هایی را که ابعاد آنان با یکدیگر سازگار است فراهم می‌کند. آرگومان اول این تابع در قالب Tuple پایتون است که شامل آرایه‌هایی است که قرار است به یکدیگر چسبانده شوند. آرگومان axis تنظیم می‌کند که این اتصال در راستای کدام بعد باشد. برای نمونه ابعاد آرایه‌های a1 و new_a1 را در زیر محاسبه کردم:

هر دو آرایه سه ستون دارند درحالی‌که تعداد سطرهایشان متفاوت است. پس این دو آرایه را از بالا به پایین می‌توان به هم چسباند. چون تعداد ردیف‌های آنان باهم متفاوت است، نمی‌توان به‌صورت چپ به راست به هم چسباند. باید توجه کرد که در راهنمای تابع آمده که axis محوری است که “در راستای” آن اتصال صورت می‌گیرد. پس اگر با توجه به ابعاد آرایه‌های a1 و new_a1 من بخواهم آن‌ها را از بالا به پایین بچسبانم، درواقع در راستای ردیف باید این کار را انجام دهم. در Numpy محور ردیف معادل صفر و محور ستون معادل یک است.

لازم به ذکر است به‌جای تابع concatenate، دو تابع vstack و hstack وجود دارند که به ترتیب به برای چسباندن آرایه‌ها از بالا به پایین و چپ ‌به راست می‌توانند استفاده شوند. من مثال بالا را با vstack دوباره ایجاد کردم:

جستجو در آرایه

آرایه a1 را در نظر بگیرید:

اگر من بخواهم بفهمم اندیس درایه ۱٫۹ چیست، از تابع where می‌توان استفاده کرد:

همین‌طور می‌توان در تابع where شرط‌هایی را بررسی کرد. برای مثال در کد زیر من بررسی کردم که اندیس درایه‌هایی که بزرگ‌تر از ۲ هستند، کدام‌اند:

همچنین دو تابع argmin و argmax برای یافتن اندیس درایه‌های کمینه و بیشینه آرایه استفاده می‌شوند:

مرتب کردن آرایه‌

تابع sort برای مرتب کردن درایه‌های آرایه از کوچک به بزرگ استفاده می‌شود:

در همین رابطه، تابع argsort اندیس درایه‌های مرتب‌شده را برمی‌گرداند:

بنابراین به روش زیر از این تابع هم می‌توان استفاده کرد تا آرایه a1 را مرتب کرد:

ایجاد آرایه‌های خاص

تا به اینجا نحوه ساخت آرایه را با استفاده از تابع array بحث کردم. در این روش نیاز است تا تک‌به‌تک درایه‌ها در قالب لیست به تابع داده شود. بسیار پیش می‌آید که درایه‌های آرایه موردنظر از یک الگوی مشخصی پیروی می‌کند. در این موارد بهتر است به‌جای واردکردن تک‌به‌تک درایه‌ها، از توابعی استفاده کنیم که در کتابخانه Numpy برای تعریف آرایه‌های خاص وجود دارند.

مثلاً برای ساخت آرایه‌ای که همه درایه‌های آن صفر است؛ از تابع zeros می‌توان استفاده کرد. در کد زیر، یک ماتریس ۲ در ۳ ایجاد کردم که همه درایه‌های آن صفر است:

تابع ones برای ساخت آرایه‌ای استفاده می‌شود که همه درایه‌های آن ۱ است. آرگومان اول تابع شکل آرایه و آرگومان dtype نوع داده را تعیین می‌کند که من آن را در کد زیر عدد صحیح (int) قرار دادم:

پر کردن آرایه با یک عدد خاص

به شکل کلی می‌توان از تابع full برای ساخت آرایه‌ای استفاده کرد که درایه آن عدد دلخواهی است. آرگومان اول تابع full، شکل آرایه و آرگومان دوم آن عدد دلخواهی است که درایه‌های آن را می‌سازد:

ساخت آرایه‌ای که درایه‌های آن در یک بازه است

یکی از پرکاربردترین توابع Numpy برای ساختن آرایه‌ای که درایه‌های آن در یک بازه است، تابع arange است. آرگومان اول این تابع، نقطه شروع بازه، آرگومان دوم، انتهای بازه و آرگومان سوم تعداد گام‌هاست. اگر عدد  شروع بازه مشخص نشود به‌صورت پیش‌فرض صفر در نظر گرفته می‌شود. اگر تعداد گام‌ها مشخص نشود، یک در نظر گرفته می‌شود. همچنین عدد انتهای بازه در بازه در نظر گرفته نمی‌شود. به مثال‌های زیر توجه کنید:

یک روش پرکاربرد دیگر برای ساخت آرایه‌ای که درایه‌های آن در یک بازه است، استفاده از تابع linspace است. آرگومان اول، نقطه شروع بازه، آرگومان دوم، عدد انتهایی بازه و آرگومان سوم تابع، تعداد درایه‌های آرایه است. درایه‌هایی که ایجاد می‌شوند از یکدیگر فاصله برابر دارند. برای نمونه در کد زیر من یک آرایه با ۱۰ درایه ساختم که در بازه ۱ تا ۵ قرار دارند و از یکدیگر فاصله یکسانی دارند:

ساخت آرایه‌ای که درایه‌های آن از یک توزیع احتمالی آمده است

کتابخانه Numpy ماژولی به نام random دارد که دارای توابع متنوعی برای توزیع احتمال است. برای نمونه اگر بخواهیم یک ماتریس ۳ در ۳ داشته باشیم که درایه‌های آن از توزیع نرمال با میانگین ۰ و انحراف معیار ۱ آمده باشد، کد زیر آن را به دست می‌دهد:

اگر کد بالا را اجرا کنید، خواهید دید که به اعداد متفاوتی ازآنچه من رسیدم، خواهید رسید. همین‌طور اگر هر بار کد بالا را اجرا کنید، نتیجه آن متفاوت خواهد بود. اگر بخواهیم نتیجه ثابت بماند قبل از اجرای کد بالا لازم است از تابع seed استفاده کنید و داخل آن یک عدد دلخواه بگذارید. من در مثال بعدی از این روش استفاده کردم.

تابع دیگری برای تولید عدد تصادفی، randint است که در بازه دلخواه اعداد تصادفی صحیح تولید می‌کند. برای نمونه من در کد زیر یک ماتریس ۳ در ۳ ایجاد کردم که درایه‌های آن در بازه ۰ تا ۱۰ است:

تابع پرکاربرد دیگر، choice است که از یک جامعه آماری به‌صورت تصادفی به تعداد دلخواه انتخاب می‌کند. در کد زیر من پرتاب سه بار پرتاب سکه منصف را شبیه‌سازی کردم. آرگومان اول، همان جامعه آماری است که در قالب لیست به آن دادم و آرگومان size اندازه نمونه است:

محاسبات آماری پایه روی آرایه

کتابخانه Numpy توابع موردنیاز برای محاسبات آماری را در اختیار قرار می‌دهد. در جدول زیر توابع پایه‌ای را آورده‌ام:

بیشینه درایه‌های آرایه

max

کمینه درایه‌های آرایه

min

میانگین درایه‌ها

mean

انحراف معیار درایه‌ها

sd

جمع درایه‌ها

sum

در مثال‌های زیر من توابع بالا را روی آرایه a2 که قبلاً ایجاد کرده بودم، پیاده‌سازی کردم:

یک آرگومان کاربردی در این توابع axis است که اجازه می‌دهد محاسبات را در راستای یک بعد آرایه انجام دهید. در Numpy محور ردیف معادل صفر و محور ستون معادل یک است. در کد زیر، ابتدا جمع در راستای ردیف آرایه (جمع روی ستون) و سپس جمع در راستای ستون آرایه (جمع روی ردیف) را محاسبه کردم:

محاسبات پایه ماتریسی در Numpy

ماتریس a2 را در نظر بگیرید. می‌توان a2 را در یک عدد اسکالر مانند ۲ ضرب یا با آن جمع کرد:

می‌توان عملگرهای مقایسه‌ای را روی درایه‌های ماتریس a2 پیاده کرد. در مثال زیر، من بررسی کردم که آیا تک‌تک درایه‌های a2 از ۲ بزرگ‌تر هستند یا خیر. می‌بینید خروجی این عملگر یک ماتریس هم‌بعد با ماتریس a2 است که درایه‌های آن True یا False است.

اگر بخواهیم ترانهاده ماتریس a2 را محاسبه کنیم، از دستوری مانند زیر استفاده می‌کنیم:

کتابخانه Numpy دارای ماژولی به نام linalg است که توابع متنوعی برای محاسبات جبر خطی را شامل می‌شود. برای نمونه برای محاسبه دترمینان ماتریس از تابع det این ماژول به شکل زیر می‌توان استفاده کرد:

تابع inv برای محاسبه معکوس ماتریس استفاده می‌شود:

به‌شرط سازگار بودن ابعاد دو ماتریس، تابع dot برای محاسبه ضرب ماتریسی استفاده می‌گردد. در کد زیر، پس از بررسی ابعاد دو ماتریس a2 و new_a1 ضرب ماتریسی این دو را محاسبه کردم:

برای حل دستگاه معادلات خطی از تابع solve ماژول linalg می‌توان استفاده کرد. یک دستگاه معادلات خطی را می‌توان در قالب ماتریسی Ax = b در نظر گرفت که A ماتریس ضرایب و b ماتریس مقادیر سمت راست است. دستگاه معادلات خطی زیر را در نظر بگیرید:

x_{1} + 3 x_{2} + 5 x_{3} = 12

2 x_{2} -10x_{3} = -1

3x_{1} +7 x_{2} +9x_{3} = -2

در دستگاه معادلات خطی بالا، آرایه a2 که قبلاً ایجاد کرده بودم، همان ماتریس ضرایب و ماتریس b حاوی مقادیر سمت راست است. سپس آن را با تابع solve حل کردم:

بررسی سرعت محاسبات در Numpy نسبت به پایتون پایه

همان‌طور که در ابتدا اشاره کردم، هسته کتابخانه Numpy مبتنی بر زبان C نوشته شده که باعث می‌شود سرعت محاسبات در Numpy بالا باشد. در کد زیر من مثالی ساختم و آن را با ابزارهای پایه پایتون و Numpy پیاده‌سازی کردم. در این کد یک لیست (ساختار داده پایه پایتون) ایجاد کردم که حاوی ۶۰ میلیون عدد است، مشابه همین کار را با آرایه Numpy کردم. سپس درایه‌های آن را باهم جمع زدم. با ماژول time زمان اجرای محاسبه را بر روی هر دو ساختار داده به دست‌آوردم. همان‌طور که در زیر مشخص است زمان اجرای محاسبه تفاوت محسوسی دارد.

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *