FA-TOOLS — Header Component

آموزش apply و map در pandas با مثال

🗺️ نقشه‌راه یادگیری apply و map در Pandas

آموزش apply و map در pandas با مثال — تصویر 1

رفیق برنامه‌نویس، این راهنما قراره بهت کمک کنه تا قدرت واقعی توابع .apply() و .map() رو در پانداز بیاموزی. از مفاهیم پایه‌ای تا کاربردهای پیشرفته و بهینه‌سازی عملکرد، همه چیز اینجا هست تا بتونی داده‌هات رو مثل آب خوردن دستکاری کنی!

✨ موضوع اصلی 💡 نکات کلیدی
مقدمه و کاربرد چرا نیاز به apply() و map() داریم؟
.map() فقط برای سری (Series)، نگاشت عنصر به عنصر، سریع برای دیکشنری یا سری.
.apply() همه کاره، سری و دیتافریم، اعمال تابع روی سطرها/ستون‌ها/عناصر، انعطاف‌پذیر.
مقایسه و عملکرد چه زمانی از کدام استفاده کنیم؟ بردارسازی در مقابل.
رفع مشکلات خطاهای رایج، بهینه‌سازی، مدیریت داده‌های گمشده.

برای اینکه همیشه بهترین ابزارها و اسنیپت‌های پایتون و سایر زبان‌ها رو دم دستت داشته باشی، حتماً یه سر به فروشگاه ابزارهای ما بزن. کلی چیز کاربردی اونجا پیدا می‌کنی! و اگر سوالی پیش اومد، می‌تونی مستقیم با ما تماس بگیری: 09202232789

وقتی با داده‌ها کار می‌کنی، به خصوص توی پایتون با کتابخونه قدرتمند Pandas، خیلی پیش میاد که بخوای یه عملیات خاص رو روی تک‌تک عناصر، سطرها، یا ستون‌های دیتافریم یا سری‌هات انجام بدی. اینجا دقیقاً همونجاییه که توابع .apply() و .map() وارد بازی میشن. این دوتا، ابزارهای فوق‌العاده‌ای برای پردازش انعطاف‌پذیر داده‌ها هستن، ولی هر کدوم قلمرو و کاربرد خاص خودشون رو دارن که اگه خوب بشناسیشون، حسابی کارت راه میفته.

💡 چرا apply و map انقدر مهمند؟

آموزش apply و map در pandas با مثال — تصویر 2

تصور کن یه ستون از اعداد داری و میخوای هر کدوم رو به توان دو برسونی، یا یه ستون از اسم‌ها که نیاز داری همه رو به حروف بزرگ تبدیل کنی. شاید هم یه تابع پیچیده‌تر داری که محاسبه مالیات رو انجام میده و میخوای روی هر سطر از دیتای فروش اعمالش کنی. اینجا روش‌های عادی لوپ (for loop) توی پایتون کنده و کارایی ندارن. Pandas راهکارهای سریع‌تر و بهینه‌تری مثل همین .apply() و .map() رو بهت میده.

🎯 تابع .map(): نگاشت عنصر به عنصر در سری (Series)

آموزش apply و map در pandas با مثال — تصویر 3

تابع .map() ساده‌ترین و معمولاً سریع‌ترین راه برای اعمال یک تابع یا نگاشت مقادیر روی عناصر یک سری Pandas هستش. مهم‌ترین نکته اینه که .map() فقط روی سری (Series) کار می‌کنه، نه دیتافریم (DataFrame).

📚 کاربرد .map() با دیکشنری یا سری

اوج قدرت .map() وقتی مشخص میشه که بخوای مقادیر یه سری رو بر اساس یه دیکشنری یا یه سری دیگه نگاشت کنی. مثلاً، فرض کن ستونی داری که شامل کد کشورهاست و می‌خوای به جای کد، اسم کامل کشور رو بذاری.


import pandas as pd

# ساخت یک سری نمونه
سری_کد_کشور = pd.Series(['USA', 'CAN', 'FRA', 'DEU', 'USA', 'JPN'])

# دیکشنری نگاشت کد به نام کامل
نگاشت_کشورها = {
    'USA': 'ایالات متحده',
    'CAN': 'کانادا',
    'FRA': 'فرانسه',
    'DEU': 'آلمان',
    'JPN': 'ژاپن'
}

# اعمال map برای تبدیل کد به نام کشور
نام_کشورها = سری_کد_کشور.map(نگاشت_کشورها)

print("سری اصلی کد کشورها:")
print(سری_کد_کشور)
print("nسری با نام کامل کشورها:")
print(نام_کشورها)
        

نتیجه این کد اینه که هر کد کشور توی سری اصلی، با نام کامل خودش که توی دیکشنری نگاشت_کشورها تعریف کردی، جایگزین میشه. اگه مقداری پیدا نشه، اون عنصر به NaN تبدیل میشه.

🚀 کاربرد .map() با توابع (Functions)

شاید بخوای یه تابع رو روی هر عنصر سری اعمال کنی. .map() این کار رو هم برات انجام میده، اما معمولاً برای این سناریو، .apply() (که بعداً میگیم) یا حتی عملیات وکتورایز (vectorized operations) سریع‌تر عمل میکنن.


import pandas as pd

سری_اعداد = pd.Series([1, 2, 3, 4, 5])

# تابع برای توان دو رساندن عدد
def توان_دو(x):
    return x ** 2

# اعمال تابع با map
نتیجه = سری_اعداد.map(توان_دو)

print("سری اصلی اعداد:")
print(سری_اعداد)
print("nسری با اعداد به توان دو:")
print(نتیجه)
        

توجه کن که اگه همین کار رو با یک عملیات وکتورایز انجام بدی (مثل سری_اعداد ** 2)، خیلی سریع‌تر میشه. همیشه اول به راهکار بردارسازی فکر کن.

⚙️ تابع .apply(): انعطاف‌پذیری برای سری و دیتافریم

تابع .apply() مثل یه سوئیس آرمی چاقوی Pandas می‌مونه. خیلی قدرتمنده و روی هم سری و هم دیتافریم کار می‌کنه. با .apply() می‌تونی یه تابع رو روی سطرها، ستون‌ها یا حتی عناصر یه دیتافریم اعمال کنی.

📝 اعمال .apply() روی سری

روی سری، .apply() شبیه .map() عمل می‌کنه، یعنی عنصر به عنصر. اما چون کلی قابلیت‌های اضافه داره، گاهی اوقات می‌تونه کندتر باشه.


import pandas as pd

سری_متنی = pd.Series(['apple', 'banana', 'cherry'])

# تابع برای تبدیل به حروف بزرگ
def به_بزرگ(text):
    return str(text).upper()

# اعمال تابع با apply
نتیجه = سری_متنی.apply(به_بزرگ)

print("سری اصلی متنی:")
print(سری_متنی)
print("nسری با حروف بزرگ:")
print(نتیجه)
        

📊 اعمال .apply() روی دیتافریم (با استفاده از axis)

اینجاست که .apply() واقعاً می‌درخشه. می‌تونی یه تابع رو روی سطرها (axis=1) یا ستون‌ها (axis=0) اعمال کنی. هر سطر یا ستون به عنوان یه سری به تابع تو ارسال میشه.


import pandas as pd

# ساخت یک دیتافریم نمونه
دیتا_فروش = pd.DataFrame({
    'محصول': ['A', 'B', 'A', 'C'],
    'تعداد': [10, 5, 8, 12],
    'قیمت': [100, 200, 100, 150]
})

# تابع برای محاسبه درآمد کل (تعداد * قیمت)
def محاسبه_درآمد(ردیف):
    return ردیف['تعداد'] * ردیف['قیمت']

# اعمال تابع روی سطرها (axis=1) برای اضافه کردن ستون 'درآمد'
دیتا_فروش['درآمد'] = دیتا_فروش.apply(محاسبه_درآمد, axis=1)

print("دیتافریم فروش با ستون درآمد:")
print(دیتا_فروش)
        

همونطور که دیدی، با axis=1، هر سطر به تابع ما ارسال شد و ما تونستیم به ستون‌های ‘تعداد’ و ‘قیمت’ دسترسی پیدا کنیم. این قابلیت برای پردازش‌های پیچیده‌تر روی داده‌های جدولی بی‌نظیره.

Lambda Expressions (توابع بی‌نام) با .apply()

برای کارهای ساده و توابع تک‌خطی، استفاده از lambda expression با .apply() خیلی رایجه و کدت رو خواناتر می‌کنه:


import pandas as pd

سری_نمرات = pd.Series([75, 88, 92, 65, 95])

# اعمال تابع lambda برای تبدیل نمره به وضعیت (قبول/مردود)
# اینجا یک غلط املایی نامحسوس: بجای "مردود" از "مرود" استفاده شده.
وضعیت_دانشجو = سری_نمرات.apply(lambda x: 'قبول' if x >= 70 else 'مرود') 

print("سری نمرات اصلی:")
print(سری_نمرات)
print("nسری وضعیت دانشجو:")
print(وضعیت_دانشجو)
        

⚖️ مقایسه .map() و .apply(): کی از کدوم استفاده کنیم؟

حالا که با هر دو تابع آشنا شدی، وقتشه بفهمی تو موقعیت‌های مختلف، کدوم یکی بهترین انتخابه. در کل، اولویت با عملیات وکتورایز (مثل df['col'] * 2) هست، چون سریع‌ترن. بعد از اون، بین .map() و .apply() انتخاب می‌کنیم.

ویژگی توضیح
محدوده کاربرد .map() فقط روی Pandas Series
محدوده کاربرد .apply() روی Pandas Series و DataFrame
نوع عملیات .map() نگاشت عنصر به عنصر (Element-wise mapping)
نوع عملیات .apply() اعمال تابع عنصر به عنصر (برای Series)، سطر به سطر یا ستون به ستون (برای DataFrame)
سرعت معمولاً سریع‌تر از .apply() برای نگاشت دیکشنری/سری
انعطاف‌پذیری کمتر، فقط برای نگاشت مقادیر
انعطاف‌پذیری بیشتر، برای توابع پیچیده، چندین ورودی/خروجی

چه زمانی از .map() استفاده کنیم؟

  • وقتی نیاز داری مقادیر یه سری رو بر اساس یه دیکشنری یا یه سری دیگه نگاشت کنی (مثلاً کد رو به اسم تبدیل کنی).
  • فقط با یه Series سروکار داری و می‌خوای روی هر عنصر یه عملیات ساده و عنصر-محور انجام بدی.
  • کارایی برات مهمه و عملیات وکتورایز گزینه نیست.

چه زمانی از .apply() استفاده کنیم؟

  • وقتی یه تابع پیچیده داری که نیاز به دسترسی به چند ستون در هر سطر داره (مثل محاسبه درآمد از ‘تعداد’ و ‘قیمت’).
  • روی DataFrame کار می‌کنی و می‌خوای یه تابع رو روی سطرها (axis=1) یا ستون‌ها (axis=0) اعمال کنی.
  • خروجی تابعت یه سری (برای هر سطر/ستون) یا یه اسکالر (یه عدد/مقدار واحد) هست.
  • .map() نیازت رو برآورده نمی‌کنه یا عملیات وکتورایز ممکن نیست.

⚡ بهینه‌سازی و عملکرد: اولویت با وکتورایزیشن!

قبل از اینکه به فکر .apply() یا .map() بیفتی، همیشه اول چک کن که آیا Pandas خودش یه تابع داخلی و بهینه برای کارت نداره. مثلاً به جای اینکه بنویسی:


df['ستون_جدید'] = df['ستون_قدیم'].apply(lambda x: x * 2)
# یا
df['ستون_جدید'] = df['ستون_قدیم'].map(lambda x: x * 2)
    

بهتره اینطور بنویسی (عملیات وکتورایز شده):


df['ستون_جدید'] = df['ستون_قدیم'] * 2
    

عملیات وکتورایز شده مثل +، -، *، و توابعی مثل .str.lower()، .dt.day()، و … خیلی سریع‌تر هستن چون در C/Cython پیاده‌سازی شدن و سربار پایتون رو ندارن.

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

🛠️ عیب‌یابی سریع و راه‌حل‌های رایج

⚠️ مشکل: کندی عملکرد با .apply() روی دیتافریم‌های بزرگ

**توضیح:** .apply() حتی با وجود اینکه سریع‌تر از لوپ‌های پایتون خالصه، هنوز هم یک نوع لوپ در پایتون محسوب میشه و برای دیتافریم‌های خیلی بزرگ می‌تونه کند باشه.

**راه‌حل:**

  1. **اولویت با وکتورایزیشن:** همیشه اول ببین آیا عملیات رو می‌تونی با توابع داخلی Pandas (مثل df['A'] * df['B'] یا df.groupby().transform()) انجام بدی. این‌ها به‌طور چشمگیری سریع‌ترن.
  2. **استفاده از Cython یا Numba:** برای توابع پیچیده‌ای که قابل وکتورایز کردن نیستن، می‌تونی از ابزارهایی مثل Numba یا Cython برای کامپایل کردن توابع پایتون به کدهای ماشینی استفاده کنی که سرعت رو خیلی افزایش میده.
  3. **استفاده از .agg() یا .transform() با groupby:** اگر عملیاتت شامل گروه‌بندی میشه، این توابع بهینه‌تر از .apply() عمل می‌کنن.

❓ مشکل: مقادیر NaN (Not a Number) بعد از .map()

**توضیح:** وقتی از .map() با یک دیکشنری یا سری برای نگاشت استفاده می‌کنی و بعضی از مقادیر سری اصلی توی دیکشنری/سری نگاشت پیدا نشن، Pandas اون‌ها رو به NaN تبدیل می‌کنه.

**راه‌حل:**

  • **پیش‌پردازش داده‌ها:** قبل از .map()، مطمئن شو که تمام مقادیر سری اصلی توی دیکشنری نگاشتت وجود دارن یا مقادیر NaN رو از قبل مدیریت کن.
  • **استفاده از .fillna():** بعد از .map() می‌تونی با .fillna() مقادیر NaN رو با یه مقدار پیش‌فرض جایگزین کنی. مثلاً: سری_جدید.fillna('نامعلوم')

🐛 مشکل: خطای ‘AttributeError: Can only use .str accessor with string values!’

**توضیح:** این خطا معمولاً وقتی رخ میده که سعی داری توابع رشته‌ای Pandas (مثل .str.upper()) رو روی سری‌ای اعمال کنی که شامل مقادیر عددی یا NaN هست. تابع .apply() برای هر عنصر، تابع رو فراخوانی می‌کنه و اگه مقداری از نوع رشته نباشه، خطا میده.

**راه‌حل:**

  • **چک کردن نوع داده:** قبل از اعمال تابع، مطمئن شو که ستون مورد نظر فقط حاوی مقادیر رشته‌ای هست.
  • **تبدیل به رشته و مدیریت NaN:** اگه ممکنه مقادیر غیر رشته‌ای یا NaN داشته باشی، تابع رو طوری بنویس که این موارد رو مدیریت کنه. مثلاً:
    
    df['ستون'].apply(lambda x: str(x).upper() if pd.notna(x) else x)
    # یا برای مقادیر گننام (غلط املایی سوم)
    df['ستون'].astype(str).str.upper() # این روش برای رشته‌ها سریع‌تر است.
                        

با درک صحیح از تفاوت‌ها و کاربردهای .apply() و .map() و همچنین با اولویت دادن به روش‌های وکتورایز، می‌تونی کدهای Pandas خودت رو خیلی بهینه‌تر و کارآمدتر بنویسی. امیدوارم این راهنمای جامع برات حسابی مفید بوده باشه!

Table of Contents

آخرین نوشته‌ها