🐍 کتابخانه مرجع پایتون

🐼 Pandas پیشرفته

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

پیش‌نیاز: تسلط بر مباحث سطح متوسط (Intermediate) Pandas مانند `DataFrame`، `Series`، `groupby` پایه و `merge`.

🔹 زنجیره‌سازی متدها (Method Chaining)

زنجیره‌سازی متدها به شما اجازه می‌دهد چندین عملیات را به صورت پشت سر هم و در یک عبارت انجام دهید. این کار کد را خواناتر کرده و از ایجاد متغیرهای موقت غیرضروری جلوگیری می‌کند [citation:2].

import pandas as pd
import numpy as np

# ایجاد داده نمونه
df = pd.DataFrame({
    'name': ['Saba', 'Ali', 'Reza', 'Mina', 'Hossein'],
    'age': [22, 34, 19, 42, 31],
    'city': ['Tehran', 'Shiraz', 'Isfahan', 'Tehran', 'Mashhad'],
    'salary': [50000, 65000, 30000, 85000, 60000],
    'score': [88, 92, 65, 95, 78]
})

# روش معمول (با متغیرهای موقت)
df1 = df[df['score'] > 70]
df2 = df1.dropna(subset=['salary'])
df3 = df2.assign(salary_k=df2['salary'] / 1000)
result = df3.sort_values('salary_k', ascending=False)

# روش زنجیره‌ای (Method Chaining)
result_chained = (
    df
    .query("score > 70")
    .dropna(subset=['salary'])
    .assign(salary_k=lambda x: x['salary'] / 1000)
    .sort_values('salary_k', ascending=False)
)

print("Result using chaining:")
print(result_chained)

# نکته: در assign، برای دسترسی به DataFrame فعلی باید از lambda استفاده کنید

🔹 تابع pipe()

تابع pipe() به شما اجازه می‌دهد توابع سفارشی را درون زنجیره قرار دهید. این روش برای ماژولار کردن کد و قابل تست کردن آن بسیار مفید است [citation:2].

import pandas as pd
import numpy as np

df = pd.DataFrame({
    'name': ['Saba', 'Ali', 'Reza', 'Mina'],
    'age': [22, 34, 19, 42],
    'score': [88, 92, 65, 95]
})

# تعریف یک تابع برای نرمال‌سازی
def normalize_scores(df, col='score'):
    df[col] = (df[col] - df[col].mean()) / df[col].std()
    return df

# تعریف تابع برای فیلتر بر اساس سن
def filter_by_age(df, min_age=20):
    return df[df['age'] >= min_age]

# استفاده از pipe در زنجیره
result = (
    df
    .pipe(normalize_scores, col='score')
    .pipe(filter_by_age, min_age=25)
    .sort_values('score', ascending=False)
)

print("Result with pipe():")
print(result)

🔹 گروه‌بندی پیشرفته با agg()

با agg() می‌توانید چندین تابع تجمیع را همزمان روی ستون‌های مختلف اعمال کنید و خروجی را به صورت نام‌گذاری شده دریافت کنید [citation:4].

import pandas as pd
import numpy as np

# ایجاد داده فروش
np.random.seed(42)
df = pd.DataFrame({
    'store': np.random.choice(['Store A', 'Store B', 'Store C'], 200),
    'product': np.random.choice(['Laptop', 'Phone', 'Tablet'], 200),
    'sales': np.random.normal(1000, 200, 200),
    'quantity': np.random.randint(1, 20, 200)
})

# گروه‌بندی با چندین تابع تجمیع
result = (
    df.groupby(['store', 'product'], as_index=False)
    .agg(
        total_sales=('sales', 'sum'),
        avg_sales=('sales', 'mean'),
        total_quantity=('quantity', 'sum'),
        avg_quantity=('quantity', 'mean'),
        count=('sales', 'count')
    )
)

print("Advanced aggregation with named aggregations:")
print(result.head(10))

# گروه‌بندی با توابع سفارشی
def sales_range(x):
    return x.max() - x.min()

custom_agg = df.groupby('product').agg(
    sales_range=('sales', sales_range),
    sales_std=('sales', 'std')
)
print("\nCustom aggregation:")
print(custom_agg)

🔹 transform() برای افزودن آمار به سطرها

برخلاف agg() که خروجی را به تعداد گروه‌ها کاهش می‌دهد، transform() خروجی را با همان شکل (shape) ورودی برمی‌گرداند. این روش برای ایجاد ویژگی‌های جدید در هر سطر بسیار مفید است [citation:4].

import pandas as pd
import numpy as np

df = pd.DataFrame({
    'employee': ['Saba', 'Ali', 'Reza', 'Mina', 'Hossein'],
    'department': ['Engineering', 'Sales', 'Engineering', 'Sales', 'Marketing'],
    'salary': [75000, 65000, 82000, 58000, 72000]
})

# افزودن میانگین حقوق هر دپارتمان به هر سطر
df['dept_avg_salary'] = df.groupby('department')['salary'].transform('mean')

# محاسبه Z-Score (نرمال‌سازی) در هر گروه
df['salary_zscore'] = df.groupby('department')['salary'].transform(
    lambda x: (x - x.mean()) / x.std()
)

# درصد حقوق نسبت به کل دپارتمان
df['salary_pct'] = df.groupby('department')['salary'].transform(
    lambda x: x / x.sum() * 100
)

print("DataFrame with transform columns:")
print(df)

🔹 filter() برای فیلتر کردن گروه‌ها

با filter() می‌توانید کل گروه‌ها را بر اساس یک شرط، نگه دارید یا حذف کنید [citation:4].

import pandas as pd
import numpy as np

df = pd.DataFrame({
    'department': ['Engineering', 'Engineering', 'Sales', 'Sales', 'Sales', 'Marketing'],
    'employee': ['Saba', 'Ali', 'Reza', 'Mina', 'Hossein', 'Neda'],
    'salary': [75000, 82000, 65000, 58000, 72000, 50000]
})

# فقط گروه‌هایی که میانگین حقوق آنها بالای 70000 است
filtered = df.groupby('department').filter(lambda x: x['salary'].mean() > 70000)
print("Departments with average salary > 70000:")
print(filtered)

# فقط گروه‌هایی که حداقل 2 عضو دارند
filtered_by_size = df.groupby('department').filter(lambda x: len(x) >= 2)
print("\nDepartments with at least 2 members:")
print(filtered_by_size)

# ترکیب دو شرط
filtered_combined = df.groupby('department').filter(
    lambda x: (len(x) >= 2) and (x['salary'].mean() > 60000)
)
print("\nCombined filter (size >= 2 and avg > 60000):")
print(filtered_combined)

🔹 جدول محوری پیشرفته (pivot_table)

pivot_table ابزاری قدرتمند برای خلاصه‌سازی داده‌ها است که می‌تواند چندین تابع تجمیع، حاشیه (margins) و مقادیر گم‌شده را مدیریت کند [citation:8].

import pandas as pd
import numpy as np

# داده فروش
df = pd.DataFrame({
    'date': pd.date_range('2024-01-01', periods=100),
    'product': np.random.choice(['A', 'B', 'C'], 100),
    'region': np.random.choice(['North', 'South', 'East', 'West'], 100),
    'sales': np.random.randint(100, 500, 100),
    'quantity': np.random.randint(1, 20, 100)
})

# pivot_table پایه
pivot = df.pivot_table(
    values='sales',
    index='product',
    columns='region',
    aggfunc='sum'
)
print("Basic pivot table (sum of sales):")
print(pivot)

# با چندین تابع تجمیع
pivot_multi = df.pivot_table(
    values='sales',
    index='product',
    columns='region',
    aggfunc=['sum', 'mean', 'count']
)
print("\nPivot with multiple aggregations:")
print(pivot_multi)

# با حاشیه (margins) برای جمع کل
pivot_margins = df.pivot_table(
    values='sales',
    index='product',
    columns='region',
    aggfunc='sum',
    margins=True,
    margins_name='Total'
)
print("\nPivot with margins:")
print(pivot_margins)

# پر کردن مقادیر گم‌شده
pivot_fill = df.pivot_table(
    values='sales',
    index='product',
    columns='region',
    aggfunc='mean',
    fill_value=0
)
print("\nPivot with missing values filled:")
print(pivot_fill)

🔹 شاخص‌گذاری سلسله‌مراتبی (MultiIndex)

MultiIndex به شما اجازه می‌دهد داده‌های چندبعدی را در یک DataFrame دو بعدی ذخیره کنید. این روش برای داده‌های سلسله‌مراتبی و تحلیل‌های پیچیده بسیار مفید است [citation:9].

import pandas as pd
import numpy as np

# ایجاد MultiIndex
arrays = [
    ['California', 'California', 'New York', 'New York', 'Texas', 'Texas'],
    ['San Francisco', 'Los Angeles', 'New York City', 'Buffalo', 'Austin', 'Dallas']
]
index = pd.MultiIndex.from_arrays(arrays, names=['State', 'City'])

df = pd.DataFrame({
    'Population': [884363, 3967000, 8336817, 278349, 964177, 1345047],
    'GDP': [592.3, 710.9, 1500.1, 43.8, 148.6, 534.3]
}, index=index)

print("DataFrame with MultiIndex:")
print(df)

# انتخاب با loc
print("\nSelect California data:")
print(df.loc['California'])

# انتخاب با multiple levels
print("\nSelect New York City:")
print(df.loc[('New York', 'New York City')])

# استفاده از xs() برای انتخاب cross-section
print("\nSelect all cities in Texas:")
print(df.xs('Texas', level='State'))

# جمع‌بندی در هر سطح
print("\nSum by State:")
print(df.groupby(level='State').sum())

🔹 query() برای فیلتر پیشرفته

query() به شما اجازه می‌دهد با یک رشته شرط (مانند SQL) داده‌ها را فیلتر کنید. این روش کد را خواناتر و کوتاه‌تر می‌کند [citation:2][citation:14].

import pandas as pd
import numpy as np

df = pd.DataFrame({
    'name': ['Saba', 'Ali', 'Reza', 'Mina', 'Hossein', 'Neda'],
    'age': [22, 34, 19, 42, 31, 28],
    'city': ['Tehran', 'Shiraz', 'Isfahan', 'Tehran', 'Mashhad', 'Shiraz'],
    'salary': [50000, 65000, 30000, 85000, 60000, 72000],
    'score': [88, 92, 65, 95, 78, 85]
})

# query ساده
result1 = df.query("age > 30")
print("Age > 30:")
print(result1)

# چند شرط با and
result2 = df.query("age > 25 and salary > 60000")
print("\nAge > 25 and salary > 60000:")
print(result2)

# استفاده از متغیرها در query
min_age = 25
max_salary = 70000
result3 = df.query("age > @min_age and salary < @max_salary")
print(f"\nAge > {min_age} and salary < {max_salary}:")
print(result3)

# فیلتر با رشته‌ها
result4 = df.query("city in ['Tehran', 'Shiraz']")
print("\nCity in Tehran or Shiraz:")
print(result4)

🔹 ترکیب داده‌ها با validate

پارامتر validate در merge() به شما کمک می‌کند از اشتباهات رایج مانند تورم سطرها (row inflation) جلوگیری کنید [citation:2].

import pandas as pd

# DataFrame اول: کارمندان
employees = pd.DataFrame({
    'emp_id': [1, 2, 3, 4],
    'name': ['Saba', 'Ali', 'Reza', 'Mina'],
    'dept_id': [101, 102, 101, 103]
})

# DataFrame دوم: دپارتمان‌ها
departments = pd.DataFrame({
    'dept_id': [101, 102, 103],
    'dept_name': ['Engineering', 'Sales', 'Marketing']
})

# merge با validate برای اطمینان از one-to-many
result = employees.merge(
    departments,
    on='dept_id',
    how='left',
    validate='many_to_one'  # تأیید می‌کند که کلید در سمت راست یکتا است
)
print("Merged with validation:")
print(result)

# اگر validation نقض شود، خطا می‌دهد
try:
    # این خطا می‌دهد چون key در سمت چپ یکتا نیست
    invalid = employees.merge(
        departments,
        on='dept_id',
        how='left',
        validate='one_to_one'
    )
except Exception as e:
    print(f"\nValidation error: {e}")

🔹 مدیریت پیشرفته مقادیر گم‌شده

با استفاده از transform() و fillna() می‌توانید مقادیر گم‌شده را بر اساس گروه‌ها پر کنید [citation:7].

import pandas as pd
import numpy as np

df = pd.DataFrame({
    'department': ['Engineering', 'Engineering', 'Sales', 'Sales', 'Marketing', 'Marketing'],
    'employee': ['Saba', 'Ali', 'Reza', 'Mina', 'Hossein', 'Neda'],
    'salary': [75000, np.nan, 65000, 58000, np.nan, 50000]
})

print("DataFrame with missing values:")
print(df)

# پر کردن مقادیر گم‌شده با میانگین هر گروه
df['salary_filled'] = df.groupby('department')['salary'].transform(
    lambda x: x.fillna(x.mean())
)
print("\nAfter filling missing values with group mean:")
print(df)

# شناسایی و حذف گروه‌هایی که بیش از 50% داده‌های گم‌شده دارند
missing_ratio = df.groupby('department')['salary'].apply(
    lambda x: x.isna().mean()
)
print("\nMissing ratio by department:")
print(missing_ratio)

📌 نکات کلیدی

← قبلی: فهرست پیشرفته → بعدی: Seaborn ↑ بازگشت به فهرست پیشرفته