پتانسیل کامل Pandas را با تسلط بر توابع سفارشی آزاد کنید. این راهنمای جامع تفاوتها، عملکرد و بهترین کاربردهای apply()، map() و applymap() را برای تحلیل حرفهای دادهها شرح میدهد.
تسلط بر Pandas: کاوش عمیق در توابع سفارشی با apply()، map() و applymap()
در دنیای علم و تحلیل داده، کتابخانه Pandas پایتون ابزاری ضروری است. این کتابخانه ساختارهای دادهای قدرتمند، انعطافپذیر و کارآمدی را فراهم میکند که برای کار با دادههای ساختاریافته هم آسان و هم شهودی طراحی شدهاند. در حالی که Pandas با مجموعهای غنی از توابع داخلی برای تجمیع، فیلتر و تبدیل ارائه میشود، زمانی در مسیر هر متخصص داده فرا میرسد که اینها کافی نیستند. شما نیاز دارید منطق سفارشی خود، یک قانون تجاری منحصر به فرد، یا یک تبدیل پیچیده را اعمال کنید که به راحتی در دسترس نیستند.اینجاست که توانایی اعمال توابع سفارشی به یک قدرت فوقالعاده تبدیل میشود. با این حال، Pandas چندین راه برای دستیابی به این هدف ارائه میدهد، عمدتاً از طریق متدهای apply()، map() و applymap(). برای تازه واردان، این توابع میتوانند به طرز گیجکنندهای شبیه به هم به نظر برسند. کدام یک را باید استفاده کنید؟ چه زمانی؟ و پیامدهای عملکردی انتخاب شما چیست؟
این راهنمای جامع این متدهای قدرتمند را رمزگشایی خواهد کرد. ما هر یک را به تفصیل بررسی میکنیم، موارد استفاده خاص آنها را درک میکنیم، و مهمتر از همه، یاد میگیریم چگونه ابزار مناسب برای کار را انتخاب کنیم تا کد Pandas تمیز، کارآمد و خوانا بنویسیم. ما موارد زیر را پوشش خواهیم داد:
- متد
map(): ایدهآل برای تبدیل عنصر به عنصر بر روی یک Series واحد. - متد
apply(): ابزار کارآمد و همهکاره برای عملیات سطر به سطر یا ستون به ستون بر روی یک DataFrame. - متد
applymap(): متخصص برای عملیات عنصر به عنصر در سراسر یک DataFrame. - ملاحظات عملکرد: تفاوت حیاتی بین این متدها و وکتورسازی واقعی.
- بهترین روشها: چارچوب تصمیمگیری برای کمک به شما در انتخاب کارآمدترین متد در هر زمان.
آمادهسازی صحنه: مجموعه داده نمونه ما
برای اینکه مثالهایمان عملی و واضح باشند، اجازه دهید با یک مجموعه داده ثابت و جهانی کار کنیم. ما یک DataFrame نمونه ایجاد خواهیم کرد که دادههای فروش آنلاین از یک شرکت تجارت الکترونیک بینالمللی تخیلی را نشان میدهد.
import pandas as pd
import numpy as np
data = {
'OrderID': [1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008],
'Product': ['Laptop', 'Mouse', 'Keyboard', 'Monitor', 'Webcam', 'Headphones', 'Docking Station', 'Mouse'],
'Category': ['Electronics', 'Accessories', 'Accessories', 'Electronics', 'Accessories', 'Audio', 'Electronics', 'Accessories'],
'Price_USD': [1200, 25, 75, 300, 50, 150, 250, 30],
'Quantity': [1, 2, 1, 2, 1, 1, 1, 3],
'Country': ['USA', 'Canada', 'USA', 'Germany', 'Japan', 'Canada', 'Germany', np.nan]
}
df = pd.DataFrame(data)
print(df)
این DataFrame ترکیبی خوبی از انواع دادهها (عددی، رشتهای و حتی یک مقدار گمشده) را برای نمایش قابلیتهای کامل توابع هدف ما ارائه میدهد.
متد map(): تبدیل عنصر به عنصر برای یک Series
map() چیست؟
متد map() ابزار تخصصی شما برای تغییر مقادیر در یک ستون واحد (یک Series Pandas) است. این متد بر اساس عنصر به عنصر عمل میکند. به آن فکر کنید که میگوید: "برای هر آیتم در این ستون، آن را در یک دیکشنری جستجو کنید یا آن را از طریق این تابع عبور دهید و با نتیجه جایگزین کنید."
این متد عمدتاً برای دو کار استفاده میشود:
- جایگزینی مقادیر بر اساس یک دیکشنری (یک نگاشت).
- اعمال یک تابع ساده به هر عنصر.
مورد استفاده ۱: نگاشت مقادیر با یک دیکشنری
این رایجترین و کارآمدترین استفاده از map() است. تصور کنید میخواهیم یک ستون 'Department' (دپارتمان) گستردهتر بر اساس ستون 'Category' (دسته بندی) خود ایجاد کنیم. میتوانیم یک نگاشت در یک دیکشنری پایتون تعریف کرده و از map() برای اعمال آن استفاده کنیم.
category_to_department = {
'Electronics': 'Technology',
'Accessories': 'Peripherals',
'Audio': 'Technology'
}
df['Department'] = df['Category'].map(category_to_department)
print(df[['Category', 'Department']])
خروجی:
Category Department
0 Electronics Technology
1 Accessories Peripherals
2 Accessories Peripherals
3 Electronics Technology
4 Accessories Peripherals
5 Audio Technology
6 Electronics Technology
7 Accessories Peripherals
توجه کنید که این چقدر ظریف کار میکند. هر مقدار در Series 'Category' در دیکشنری `category_to_department` جستجو میشود و مقدار مربوطه برای پر کردن ستون جدید 'Department' استفاده میگردد. اگر کلیدی در دیکشنری یافت نشود، map() یک مقدار NaN (Not a Number) تولید میکند که اغلب رفتار مطلوب برای دستهبندیهای بدون نگاشت است.
مورد استفاده ۲: اعمال یک تابع با map()
همچنین میتوانید یک تابع (از جمله یک تابع لامبدا) را به map() ارسال کنید. این تابع برای هر عنصر در Series اجرا خواهد شد. اجازه دهید یک ستون جدید ایجاد کنیم که یک برچسب توصیفی برای قیمت به ما میدهد.
def price_label(price):
if price > 200:
return 'High-Value'
elif price > 50:
return 'Mid-Value'
else:
return 'Low-Value'
df['Price_Label'] = df['Price_USD'].map(price_label)
# Using a lambda function for a simpler task:
# df['Product_Length'] = df['Product'].map(lambda x: len(x))
print(df[['Product', 'Price_USD', 'Price_Label']])
خروجی:
Product Price_USD Price_Label
0 Laptop 1200 High-Value
1 Mouse 25 Low-Value
2 Keyboard 75 Mid-Value
3 Monitor 300 High-Value
4 Webcam 50 Low-Value
5 Headphones 150 Mid-Value
6 Docking Station 250 High-Value
7 Mouse 30 Low-Value
چه زمانی از map() استفاده کنیم: یک خلاصه سریع
- شما در حال کار بر روی یک ستون واحد (یک Series) هستید.
- نیاز دارید مقادیر را بر اساس یک دیکشنری یا یک Series دیگر جایگزین کنید. این قدرت اصلی آن است.
- نیاز دارید یک تابع ساده عنصر به عنصر را به یک ستون واحد اعمال کنید.
متد apply(): ابزار کارآمد و همهکاره
apply() چیست؟
اگر map() یک متخصص است، apply() یک موتورخانه عمومی است. این متد انعطافپذیرتر است زیرا میتواند هم بر روی Series و هم بر روی DataFrames عمل کند. کلید درک apply() پارامتر axis است که عملکرد آن را هدایت میکند:
- روی یک Series: به صورت عنصر به عنصر کار میکند، بسیار شبیه به
map(). - روی یک DataFrame با
axis=0(پیشفرض): یک تابع را به هر ستون اعمال میکند. تابع هر ستون را به عنوان یک Series دریافت میکند. - روی یک DataFrame با
axis=1: یک تابع را به هر ردیف اعمال میکند. تابع هر ردیف را به عنوان یک Series دریافت میکند.
apply() بر روی یک Series
هنگامی که بر روی یک Series استفاده میشود، apply() بسیار شبیه به map() رفتار میکند. این متد تابعی را به هر عنصر اعمال میکند. به عنوان مثال، میتوانیم مثال برچسب قیمت خود را تکرار کنیم.
df['Price_Label_apply'] = df['Price_USD'].apply(price_label)
print(df['Price_Label_apply'].equals(df['Price_Label'])) # Output: True
در حالی که در اینجا به نظر میرسد قابل تعویض هستند، map() اغلب برای جایگزینیهای ساده دیکشنری و عملیات عنصر به عنصر بر روی یک Series کمی سریعتر است زیرا مسیری بهینهتر برای آن وظایف خاص دارد.
apply() بر روی یک DataFrame (ستون به ستون، axis=0)
این حالت پیشفرض برای یک DataFrame است. تابعی که ارائه میدهید یک بار برای هر ستون فراخوانی میشود. این برای تجمیعها یا تبدیلهای ستون به ستون مفید است.
بیایید تفاوت بین حداکثر و حداقل مقدار (بازه) را برای هر یک از ستونهای عددی خود پیدا کنیم.
numeric_cols = df[['Price_USD', 'Quantity']]
def get_range(column_series):
return column_series.max() - column_series.min()
column_ranges = numeric_cols.apply(get_range, axis=0)
print(column_ranges)
خروجی:
Price_USD 1175.0
Quantity 2.0
dtype: float64
در اینجا، تابع get_range ابتدا Series 'Price_USD' را دریافت کرد، محدوده آن را محاسبه کرد، سپس Series 'Quantity' را دریافت کرد و همین کار را انجام داد، و یک Series جدید با نتایج برگرداند.
apply() بر روی یک DataFrame (ردیف به ردیف، axis=1)
این مسلماً قدرتمندترین و رایجترین مورد استفاده برای apply() است. هنگامی که نیاز به محاسبه یک مقدار جدید بر اساس چندین ستون در یک ردیف دارید، apply() با axis=1 راهحل اصلی شماست.
تابعی که شما ارسال میکنید، هر ردیف را به عنوان یک Series دریافت خواهد کرد، که در آن ایندکس نام ستونها است. بیایید هزینه کل هر سفارش را محاسبه کنیم.
def calculate_total_cost(row):
# 'row' is a Series representing a single row
price = row['Price_USD']
quantity = row['Quantity']
return price * quantity
df['Total_Cost'] = df.apply(calculate_total_cost, axis=1)
print(df[['Product', 'Price_USD', 'Quantity', 'Total_Cost']])
خروجی:
Product Price_USD Quantity Total_Cost
0 Laptop 1200 1 1200
1 Mouse 25 2 50
2 Keyboard 75 1 75
3 Monitor 300 2 600
4 Webcam 50 1 50
5 Headphones 150 1 150
6 Docking Station 250 1 250
7 Mouse 30 3 90
این کاری است که map() به سادگی نمیتواند انجام دهد، زیرا به یک ستون واحد محدود است. بیایید یک مثال پیچیدهتر ببینیم. میخواهیم اولویت حمل و نقل هر سفارش را بر اساس دسته بندی و کشور آن دستهبندی کنیم.
def assign_shipping_priority(row):
if row['Category'] == 'Electronics' and row['Country'] == 'USA':
return 'High Priority'
elif row['Total_Cost'] > 500:
return 'High Priority'
elif row['Country'] == 'Japan':
return 'Medium Priority'
else:
return 'Standard'
df['Shipping_Priority'] = df.apply(assign_shipping_priority, axis=1)
print(df[['Category', 'Country', 'Total_Cost', 'Shipping_Priority']])
چه زمانی از apply() استفاده کنیم: یک خلاصه سریع
- هنگامی که منطق شما به چندین ستون در یک ردیف بستگی دارد (از
axis=1استفاده کنید). این ویژگی برجسته آن است. - هنگامی که نیاز به اعمال یک تابع تجمیعکننده بر روی ستونها یا ردیفها دارید.
- به عنوان یک ابزار عمومی برای اعمال تابع، زمانی که
map()مناسب نیست.
یک اشاره ویژه: متد applymap()
applymap() چیست؟
متد applymap() یک متخصص دیگر است، اما حوزه آن کل DataFrame است. این متد یک تابع را به تک تک عناصر یک DataFrame اعمال میکند. این متد بر روی یک Series کار نمیکند—بلکه فقط مخصوص DataFrame است.
آن را به عنوان اجرای همزمان map() بر روی هر ستون در نظر بگیرید. برای تبدیلهای گسترده و فراگیر، مانند فرمتبندی یا تبدیل نوع، در تمام سلولها مفید است.
DataFrame.applymap() در حال منسوخ شدن است. روش توصیه شده جدید استفاده از DataFrame.map() است. عملکرد یکسان است. ما در اینجا از applymap() برای سازگاری استفاده خواهیم کرد، اما برای کدهای آینده از این تغییر آگاه باشید.
یک مثال عملی
فرض کنید یک زیر-DataFrame داریم که فقط شامل ستونهای عددی ما است و میخواهیم همه آنها را برای یک گزارش به صورت رشتههای ارزی فرمت کنیم.
numeric_df = df[['Price_USD', 'Quantity', 'Total_Cost']]
# Using a lambda function to format each number
formatted_df = numeric_df.applymap(lambda x: f'${x:,.2f}')
print(formatted_df)
خروجی:
Price_USD Quantity Total_Cost
0 $1,200.00 $1.00 $1,200.00
1 $25.00 $2.00 $50.00
2 $75.00 $1.00 $75.00
3 $300.00 $2.00 $600.00
4 $50.00 $1.00 $50.00
5 $150.00 $1.00 $150.00
6 $250.00 $1.00 $250.00
7 $30.00 $3.00 $90.00
یک کاربرد رایج دیگر، پاکسازی یک DataFrame از دادههای رشتهای است، به عنوان مثال، با تبدیل همه چیز به حروف کوچک.
string_df = df[['Product', 'Category', 'Country']].copy() # Create a copy to avoid SettingWithCopyWarning
# Ensure all values are strings to prevent errors
string_df = string_df.astype(str)
lower_df = string_df.applymap(str.lower)
print(lower_df)
چه زمانی از applymap() استفاده کنیم: یک خلاصه سریع
- هنگامی که نیاز دارید یک تابع واحد و ساده را به هر عنصر در یک DataFrame اعمال کنید.
- برای کارهایی مانند تبدیل نوع داده، قالببندی رشته یا تبدیلهای ریاضی ساده در سراسر DataFrame.
- منسوخ شدن آن به نفع
DataFrame.map()در نسخههای جدیدتر Pandas را به خاطر بسپارید.
کاوش عمیق در عملکرد: وکتورسازی در مقابل تکرار
حلقه "پنهان"
این حیاتیترین مفهوم برای درک و نوشتن کد Pandas با عملکرد بالا است. در حالی که apply()، map() و applymap() راحت هستند، اساساً فقط پوششهای فانتزی اطراف یک حلقه پایتون هستند. هنگامی که از df.apply(..., axis=1) استفاده میکنید، Pandas ردیف به ردیف DataFrame شما را تکرار میکند و هر کدام را به تابع شما ارسال میکند. این فرآیند سربار قابل توجهی دارد و بسیار کندتر از عملیاتی است که در C یا Cython بهینهسازی شدهاند.
قدرت وکتورسازی
وکتورسازی روشی برای انجام عملیات بر روی آرایههای کامل (یا Series) به صورت یکجا است، نه بر روی عناصر منفرد. Pandas و کتابخانه زیربنایی آن، NumPy، به طور خاص برای سرعت فوقالعاده در عملیات وکتورسازی طراحی شدهاند.
بیایید به محاسبه 'Total_Cost' خود بازگردیم. ما از apply() استفاده کردیم، اما آیا راه وکتورایز شدهای وجود دارد؟
# Method 1: Using apply() (Iteration)
df['Total_Cost'] = df.apply(lambda row: row['Price_USD'] * row['Quantity'], axis=1)
# Method 2: Vectorized Operation
df['Total_Cost_Vect'] = df['Price_USD'] * df['Quantity']
# Check if the results are the same
print(df['Total_Cost'].equals(df['Total_Cost_Vect'])) # Output: True
روش دوم وکتورایز شده است. این روش کل Series 'Price_USD' را میگیرد و آن را در کل Series 'Quantity' در یک عملیات واحد و بسیار بهینهسازی شده ضرب میکند. اگر این دو روش را بر روی یک DataFrame بزرگ (میلیونها ردیف) زمانبندی کنید، رویکرد وکتورایز شده تنها سریعتر نخواهد بود—بلکه چندین مرتبه سریعتر خواهد بود. ما درباره ثانیهها در مقابل دقیقهها، یا دقیقهها در مقابل ساعتها صحبت میکنیم.
چه زمانی apply() اجتنابناپذیر است؟
اگر وکتورسازی اینقدر سریعتر است، چرا این متدهای دیگر وجود دارند؟ زیرا گاهی اوقات، منطق شما برای وکتورسازی بیش از حد پیچیده است. apply() ابزار لازم و صحیح است زمانی که:
- منطق شرطی پیچیده: منطق شما شامل دستورات پیچیده
if/elif/elseاست که به چندین ستون وابسته است، مانند مثالassign_shipping_priorityما. در حالی که برخی از اینها را میتوان باnp.select()به دست آورد، اما ممکن است ناخوانا شود. - توابع کتابخانههای خارجی: نیاز دارید تابعی از یک کتابخانه خارجی را به دادههای خود اعمال کنید. به عنوان مثال، اعمال تابعی از یک کتابخانه جغرافیایی برای محاسبه فاصله بر اساس ستونهای عرض جغرافیایی و طول جغرافیایی، یا تابعی از یک کتابخانه پردازش زبان طبیعی (مانند NLTK) برای انجام تحلیل احساسات بر روی یک ستون متنی.
- فرآیندهای تکراری: محاسبه برای یک ردیف مشخص به مقداری که در ردیف قبلی محاسبه شده است بستگی دارد (اگرچه این نادر است و اغلب نشانه نیاز به یک ساختار داده متفاوت است).
بهترین روش: ابتدا وکتورایز کنید، سپس apply()
این منجر به قاعده طلایی عملکرد Pandas میشود:
همیشه ابتدا به دنبال یک راهحل وکتورایز شده باشید. از apply() به عنوان جایگزین قدرتمند و انعطافپذیر خود استفاده کنید، زمانی که یک راهحل وکتورایز شده عملی یا ممکن نیست.
خلاصه و نکات کلیدی: انتخاب ابزار مناسب
بیایید دانش خود را در یک چارچوب تصمیمگیری روشن ادغام کنیم. هنگامی که با یک وظیفه تبدیل سفارشی روبرو هستید، این سوالات را از خود بپرسید:
جدول مقایسه
| متد | روی چه چیزی کار میکند | دامنه عملیات | تابع چه چیزی را دریافت میکند | مورد استفاده اصلی |
|---|---|---|---|---|
| وکتورسازی | Series, DataFrame | کل آرایه به صورت یکجا | N/A (عملیات مستقیم است) | عملیات حسابی، منطقی. بالاترین عملکرد. |
.map() |
فقط Series | عنصر به عنصر | یک عنصر واحد | جایگزینی مقادیر از یک دیکشنری. |
.apply() |
Series, DataFrame | ردیف به ردیف یا ستون به ستون | یک Series (یک ردیف یا ستون) | منطق پیچیده با استفاده از چندین ستون در هر ردیف. |
.applymap() |
فقط DataFrame | عنصر به عنصر | یک عنصر واحد | فرمتبندی یا تبدیل هر سلول در یک DataFrame. |
یک فلوچارت تصمیمگیری
- آیا عملیات من را میتوان با استفاده از عملگرهای حسابی (+، -، *، /) یا منطقی (&، |، ~) بر روی کل ستونها بیان کرد؟
→ بله؟ از رویکرد وکتورایز شده استفاده کنید. این سریعترین راه است. (به عنوان مثال، `df['col1'] * df['col2']`) - آیا من فقط روی یک ستون کار میکنم، و هدف اصلی من جایگزینی مقادیر بر اساس یک دیکشنری است؟
→ بله؟ ازSeries.map()استفاده کنید. این برای این کار بهینه شده است. - آیا نیاز دارم یک تابع را به تک تک عناصر در کل DataFrame خود اعمال کنم؟
→ بله؟ ازDataFrame.applymap()(یاDataFrame.map()در نسخههای جدیدتر Pandas) استفاده کنید. - آیا منطق من پیچیده است و برای محاسبه یک نتیجه واحد به مقادیری از چندین ستون در هر ردیف نیاز دارد؟
→ بله؟ ازDataFrame.apply(..., axis=1)استفاده کنید. این ابزار شما برای منطق پیچیده و ردیف به ردیف است.
نتیجهگیری
عبور از گزینههای اعمال توابع سفارشی در Pandas یک مرحله ضروری برای هر متخصص داده است. در حالی که در نگاه اول ممکن است قابل تعویض به نظر برسند، map()، apply() و applymap() ابزارهای متمایزی هستند که هر کدام نقاط قوت و موارد استفاده ایدهآل خود را دارند. با درک تفاوتهای آنها، میتوانید کدی بنویسید که نه تنها صحیح باشد، بلکه خواناتر، قابل نگهداریتر و به طور قابل توجهی کارآمدتر نیز باشد.
سلسله مراتب را به خاطر بسپارید: وکتورسازی را برای سرعت خام آن ترجیح دهید، از map() برای جایگزینی کارآمد Series استفاده کنید، applymap() را برای تبدیلهای گسترده DataFrame انتخاب کنید، و از قدرت و انعطافپذیری apply() برای منطق پیچیده ردیف به ردیف یا ستون به ستون که نمیتوان آن را وکتورایز کرد، بهره ببرید. با این دانش، اکنون بهتر میتوانید هر چالش دستکاری دادهای را که سر راهتان قرار میگیرد، حل کنید و دادههای خام را با مهارت و کارایی به بینشهای قدرتمند تبدیل کنید.