سرعت بخشیدن به کد ها با PANDA
سرعت بخشیدن به کد ها با PANDA
کتابخانه پاندا هدیه ای آسمانی به جامعه علوم داده است. از هر دانشمند داده ای بپرسید که چگونه آنها می توانند مجموعه داده های خود را در پایتون اداره کنند و بدون شک در مورد پاندا صحبت خواهند کرد.در ادامه با وبلاگ هاستینجا همراه باشید.
Pandas مظهر آنچه که یک کتابخانه بزرگ برنامه نویسی باید مانند آن باشد: آسان ، شهودی و گسترده ای از قابلیت های آن است.
با این حال ، انجام هزاران یا حتی میلیون ها محاسبه بر روی نام داده Pandas ، یک وظیفه منظم برای دانشمندان داده ، هنوز یک چالش است. شما فقط نمی توانید داده های خود را وارد کنید ، یک حلقه Python را برای حلقه بنویسید و انتظار داشته باشید که داده های شما در مدت زمان معقولی پردازش شوند.
پانداها برای عملیات برداری طراحی شده اند که در کل ردیف ها یا ستون ها در یک عکس کار می کنند – حلقه زدن از طریق هر سلول ، ردیف یا ستون به سادگی راهی نیست که کتابخانه برای استفاده از آن طراحی شود. به این ترتیب ، هنگام کار با پانداها باید به فکر کارها در زمینه عملکرد ماتریس باشید که بسیار همسان هستند.
این راهنما به شما می آموزد که چگونه از پانداها به روشی که برای استفاده طراحی شده است استفاده کنید و از نظر عملکرد ماتریس فکر کنید. در طول راه ، من به شما چند نکته و ترفندهای صرفه جویی در وقت را برای شما نشان خواهم داد که کد پاندای شما را سریعتر از حلقه های پایتون وحشت زده در پیش می گیرند!
راه اندازی ما
در طول این آموزش قصد داریم از مجموعه داده های کلاسیک Iris Flowers استفاده کنیم. بیایید شروع به چرخش توپ با بارگذاری مجموعه داده با ساحل دریایی و چاپ ۵ ردیف اول کنیم.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
import seaborn as sns import pandas as pd data = sns.load_dataset('iris') print(data.head()) ### Prints out # sepal_length sepal_width petal_length petal_width species # 0 5.1 3.5 1.4 0.2 setosa # 1 4.9 3.0 1.4 0.2 setosa # 2 4.7 3.2 1.3 0.2 setosa # 3 4.6 3.1 1.5 0.2 setosa # 4 5.0 3.6 1.4 0.2 setosa |
عالی!
حال بیایید یک پایه را برای اندازه گیری سرعت ما با یک حلقه برای پایتون ایجاد کنیم. ما با حلقه زدن در هر سطر ، محاسبه ای را انجام می دهیم که در مجموعه داده ما انجام شود و سپس سرعت کل عملیات را اندازه گیری کنیم. این به ما یک مبنای اولیه می دهد تا ببینیم چه مقدار بهینه سازی جدید ما به ما کمک می کند.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
import seaborn as sns import time data = sns.load_dataset('iris') def compute_class(petal_length): if petal_length <= 2: return 1 elif 2 < petal_length < 5: return 2 else: return 3 start = time.time() class_list = list() for i in range(len(data)): petal_length = data.iloc[i]['petal_length'] class_num = compute_class(petal_length) class_list.append(class_num) end = time.time() print("For-loop run time = {}".format(end - start)) |
در کد بالا ، ما یک تابع اساسی ایجاد کردیم که با استفاده از عبارت If-Else ، طبقه گل را بر اساس طول گلبرگ انتخاب می کنیم. ما یک حلقه for-loop نوشتیم که توابع در هر سطر را با حلقه زدن از طریق dataframe اعمال می کند و سپس کل زمان اجرای حلقه را اندازه گیری می کند.
در دستگاه من که دارای i7-8700k است ، این حلقه به طور متوسط ۰٫۰۱۳۴۵ ثانیه در طول ۵ دوره زمان می برد.
ایجاد حلقه با .iterrows ()
ساده ترین سرعت در عین حال بسیار ارزشمندی که می توانیم از خفاش خارج شویم استفاده از تابع .iterrows () داخلی Pandas است.
هنگامی که حلقه for-loop خود را در بخش قبلی نوشتیم ، از توابع range () استفاده می کردیم. با این حال ، هنگامی که ما در حال حلقه طیف وسیعی از مقادیر در پایتون هستیم ، ژنراتورها خیلی سریعتر می شوند. در اینجا می توانید اطلاعات بیشتری در مورد نحوه تولید ژنراتور کسب کنید و کارها را سریعتر کنید.
تابع .iterrows () از پاندا یک عملکرد ژنراتور را در داخل پیاده سازی می کند که ردیف Dataframe را در هر تکرار به دست می آورد. به طور دقیق تر ، .iterrows () برای هر سطر در DataFrame جفت (توله ها) از (index، Series) را به دست می آورد. این کاملاً شبیه به استفاده از چیزی مانند شمارش () در Python خام است اما خیلی سریعتر اجرا می شود
در زیر کد را برای استفاده از .iterrows () به جای حلقه معمولی برای حلقه تغییر داده ایم. در همان دستگاهی که برای آزمایش در قسمت قبلی از آن استفاده کردم ، میانگین زمان اجرا ۰٫۰۰۵۸۹۲ ثانیه بود – سرعت ۲٫۲۸ برابر!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
import seaborn as sns import time data = sns.load_dataset('iris') def compute_class(petal_length): if petal_length <= 2: return 1 elif 2 < petal_length < 5: return 2 else: return 3 start = time.time() class_list = list() for index, data_row in data.iterrows(): petal_length = data_row['petal_length'] class_num = compute_class(petal_length) class_list.append(class_num) end = time.time() print("Iterrows run time = {}".format(end - start)) |
سرعت بخشیدن به کد ها با PANDA
رها کردن حلقه ها به طور کامل با .apply ()
عملکرد .iterrows () سرعت بسیار خوبی را برای ما به ارمغان آورد ، اما ما هنوز به پایان رسیده ایم. همیشه به یاد داشته باشید که هنگام استفاده از کتابخانه ای که برای عملیات برداری طراحی شده است ، احتمالاً راهی برای انجام کارآمدترین کارها بدون هیچگونه حلقه ای وجود دارد.
عملکرد Pandas که این قابلیت را به ما ارائه می دهد تابع .apply () است. function.apply () عملکرد دیگری را به عنوان ورودی خود به کار می گیرد و آن را در امتداد محور DataFrame (ردیف ها ، ستون ها و غیره) اعمال می کند. در چنین مواردی که ما در حال عبور از توابع هستیم ، یک لامبدا اغلب مناسب است که همه چیز را با هم بسته بندی کند.
در کد زیر ، ما حلقه for-loop خود را کاملاً با .apply () و یک عملکرد lambda برای بسته بندی محاسبات مورد نظر خود جایگزین کرده ایم. در دستگاه من ، میانگین زمان اجرای این کد ۰٫۰۰۲۰۸۹۷ ثانیه است – سرعت ۶٫۴۴X نسبت به حلقه اصلی ما.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
import seaborn as sns import time data = sns.load_dataset('iris') def compute_class(petal_length): if petal_length <= 2: return 1 elif 2 < petal_length < 5: return 2 else: return 3 start = time.time() class_list = data.apply(lambda row: compute_class(row['petal_length']), axis=1) end = time.time() print(".apply() run time = {}".format(end - start)) |
دلیل این که سرعت بسیار سریعتر () بسیار سریعتر است این است که در داخل سعی در تکرار تکرارهای Cython دارد. اگر به نظر می رسد عملکرد شما برای Cython بهینه شده باشد ، .apply () سرعت بیشتری به شما می بخشد. به عنوان یک جایزه ، استفاده از توابع داخلی کدی بسیار تمیزتر و قابل خواندن را در پی دارد
برش نهایی
پیش از این ذکر کردم که اگر از کتابخانه ای استفاده می کنید که برای عملیات برداری طراحی شده باشد ، همیشه باید به دنبال راهی برای انجام هرگونه محاسبه بدون حلقه های فرعی باشید.
به طور مشابه ، بسیاری از کتابخانه هایی که به این روش طراحی شده اند ، از جمله Pandas ، توابع داخلی مناسب ای دارند که محاسبات دقیق مورد نظر خود را انجام می دهند – اما سریعتر می روند.
تابع .cut () از Pandas مجموعه ای از سطل های سطل آشغال را شامل می شود که محدوده ای از If-Else و مجموعه ای از برچسب های ما را تعریف می کند که برای بازگشت به هر محدوده مقدار را نشان می دهد. سپس دقیقاً همان عملی را که به صورت دستی با عملکرد compute_class نوشتیم انجام می دهد.
کد زیر را ببینید تا ببینید که چگونه کار (.) () کار می کند. ما دوباره جایزه شیرین شیرین از کد تمیزتر و قابل خواندن بیشتر را بدست آورده ایم. در پایان ، عملکردهای (.) () به طور متوسط ۰٫۰۰۱۴۲۳ ثانیه اجرا می شوند – سرعت بسیار زیاد ۹٫۳۹X نسبت به حلقه اصلی!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
import seaborn as sns import time import pandas as pd data = sns.load_dataset('iris') start = time.time() class_list = pd.cut(x=data.petal_length, bins=[0, 2, 5, 100], include_lowest=True, labels=[1, 2, 3]).astype(int) end = time.time() print(".cut() run time = {}".format(end - start)) |