मुख्य सामग्री पर जाएँ

Pandas .apply(): यह क्या करता है, कब मददगार है, और तेज़ विकल्प

जानें कि Python pandas .apply क्या है और DataFrames के साथ इसे कैसे उपयोग करें। आज ही .apply() फ़ंक्शन का उपयोग करके DataFrames पर इटरेट करना सीखें!
· 5 मि॰ पढ़ें

AI के साथ खोजें

कई लोग  .apply() का सहारा “लूप से बचने” के लिए लेते हैं और इसे तेज़ व सीधे-सरल मानते हैं। व्यवहार में, पंक्ति-वार .apply(axis=1) अभी भी Python-स्तर का लूप है। यह बड़े डेटा पर धीमा हो सकता है, और कभी-कभी अपेक्षित रूप नहीं लौटाता। समाधान सरल है: सामान्य कार्यों के लिए वेक्टराइज़्ड pandas/NumPy ऑपरेशंस का उपयोग करें, और .apply() को केवल उसी लॉजिक के लिए रखें जिसे सच में कई कॉलमों की ज़रूरत हो।

यह गाइड दिखाता है कि आज .apply() कैसे काम करता है, आम गलतियों को रेखांकित करता है, और तेज़ व स्पष्ट ड्रॉप-इन पैटर्न देता है।

DataFrame.apply() और Series.apply() क्या हैं?

DataFrame.apply(func, axis=0) डिफ़ॉल्ट रूप से प्रत्येक कॉलम पर func को कॉल करता है। axis=1 के साथ, यह प्रत्येक पंक्ति पर func को कॉल करता है। Series.apply(func) प्रत्येक एलिमेंट पर (या हालिया संस्करणों में सिग्नेचर पर निर्भर करते हुए पूरे Series पर) func को कॉल करता है।

  • Row-wise बनाम column-wise: axis=1 का अर्थ है “प्रति पंक्ति”। axis=0 (डिफ़ॉल्ट) का अर्थ है “प्रति कॉलम”।
  • रिटर्न शेप नियम (pandas ≥0.23): यदि आपका फ़ंक्शन प्रति पंक्ति/कॉलम एक स्केलर लौटाता है, तो आपको एक Series मिलता है। यदि यह एक Series या dict लौटाता है, तो pandas उन keys को एक DataFrame में फैलाता है। यदि यह एक list/array लौटाता है, तो आपको सूचियों का Series मिलेगा जब तक कि आप result_type='expand' सेट न करें।
  • कड़े एरर्स (pandas ≥2.0): असंगत list-जैसी लंबाइयों पर अब चुपचाप असंगत परिणाम देने के बजाय एरर उठते हैं।

अनुशंसित पहला विकल्प: वेक्टराइज़्ड ऑपरेशंस

ज़्यादातर कार्य जिनमें कॉलमों का संयोजन या रूपांतरण शामिल है, वेक्टराइज़्ड एक्सप्रेशंस या बिल्ट-इन मेथड्स से तेज़ और स्पष्ट होते हैं। निम्न उदाहरण बिना .apply() के बेसबॉल टीम का रन डिफरेंशियल निकालता है।

import pandas as pd

team_stats = pd.DataFrame({
    "Team": ["ARI", "ATL", "BAL"],
    "League": ["NL", "NL", "AL"],
    "Year": [2012, 2012, 2012],
    "RunsScored": [734, 700, 712],
    "RunsAllowed": [688, 600, 705],
    "Wins": [81, 94, 93],
    "Games": [162, 162, 162],
    "Playoffs": [0, 1, 1],
})

# Vectorized: fast and idiomatic
team_stats["RunDiff"] = team_stats["RunsScored"] - team_stats["RunsAllowed"]
print(team_stats[["Team", "RunsScored", "RunsAllowed", "RunDiff"]])

जब सच में ज़रूरत हो तब Row-Wise .apply()

जब आपका लॉजिक वाकई कई कॉलमों में फैला हो और आसानी से वेक्टराइज़ न हो सके (जैसे, कई फ़ील्ड्स पर निर्भर सशर्त नियम), तब .apply(axis=1) का उपयोग करें।

कई कॉलमों से व्युत्पन्न मान की गणना करें

यह पैटर्न कई इनपुट्स का उपयोग कर प्रति पंक्ति एक मान निकालता है। ऊपर दिया वेक्टराइज़्ड तरीका अभी भी बेहतर है, पर यह सही row-wise उपयोग और स्पीड व आउटपुट को प्रभावित करने वाले विकल्प दिखाता है।

def compute_run_diff(row):
    # Treat the row as read-only; return a scalar
    return row["RunsScored"] - row["RunsAllowed"]

# Row-wise apply (Python-level loop; can be slow on large data)
team_stats["RunDiff_apply"] = team_stats.apply(compute_run_diff, axis=1)

जब फ़ंक्शन संख्यात्मक हो और केवल कच्ची एरेज़ की ज़रूरत हो, तो raw=True पास करने से आपका फ़ंक्शन NumPy array पाता है और कुछ pandas ओवरहेड छूट जाता है।

import numpy as np

def compute_run_diff_raw(values):
    # values is a NumPy array when raw=True
    # Order matches the column order we select
    rs, ra = values
    return rs - ra

team_stats["RunDiff_raw"] = team_stats[["RunsScored", "RunsAllowed"]].apply(
    compute_run_diff_raw, axis=1, raw=True
)

फ़ंक्शन इनपुट एक बार जाँचें

जब मैं कोई नया row फ़ंक्शन जोड़ता हूँ, तो पास हो रही वस्तु की पुष्टि करने के लिए मैं एक पंक्ति को एक बार प्रिंट करता हूँ।

def debug_row(row):
    # Print the first row only
    if row.name == team_stats.index[0]:
        print("Example row:", row.to_dict())
    return 0

_ = team_stats.apply(debug_row, axis=1)

केस स्टडी: जोड़, सीज़न टोटल्स, और टेक्स्ट फ्लैग्स

Rays-by-year आँकड़ों को देखते हुए, जहाँ संभव हो वहाँ .apply() के बजाय बिल्ट-इन मेथड्स और एलिमेंटवाइज़ टूल्स को प्राथमिकता दें।

rays_by_year = pd.DataFrame(
    {
        "Year": [2012, 2011, 2010, 2009, 2008],
        "RunsScored": [697, 707, 802, 803, 774],
        "RunsAllowed": [577, 614, 649, 754, 671],
        "Wins": [90, 91, 96, 84, 97],
        "Playoffs": [0, 1, 1, 0, 1],
    }
).set_index("Year")

कॉलम के जोड़ दक्षता से प्राप्त करें

DataFrame.sum() का उपयोग करें, .apply(sum) के बजाय। यह तेज़ और स्पष्ट है।

# Preferred
totals = rays_by_year.sum(axis=0)
print(totals)

# If you must use apply (not recommended here)
totals_apply = rays_by_year.apply(sum, axis=0)

किसी सीज़न में कुल बनाए और खाए रन की गणना करें

कॉलमों पर वेक्टराइज़्ड अंकगणित का उपयोग करें; .apply() की ज़रूरत नहीं।

rays_by_year["TotalRuns"] = rays_by_year["RunsScored"] + rays_by_year["RunsAllowed"]
print(rays_by_year[["RunsScored", "RunsAllowed", "TotalRuns"]].head())

0/1 फ्लैग को टेक्स्ट में बदलें

एक कॉलम पर एलिमेंटवाइज़ ट्रांसफॉर्म के लिए Series.map() या replace() को प्राथमिकता दें।

# Using map with a dict
rays_by_year["PlayoffsText"] = rays_by_year["Playoffs"].map({0: "No", 1: "Yes"})

# Equivalent with replace
rays_by_year["PlayoffsText2"] = rays_by_year["Playoffs"].replace({0: "No", 1: "Yes"})

.apply() से आउटपुट का शेप नियंत्रित करें

जब आपका फ़ंक्शन प्रति पंक्ति एक से अधिक मान लौटाता है, तो शेप स्पष्ट रखें। इससे अप्रत्याशित परिणामों और भविष्य के संस्करणों में टूटन से बचाव होता है।

सूची लौटाएँ और कॉलमों में विस्तार करें

एक सूची/array को कई कॉलमों में बाँटने के लिए result_type='expand' का उपयोग करें।

def wins_losses(row):
    # Derive wins and losses as two outputs
    wins = row["Wins"]
    losses = row["Games"] - row["Wins"] if "Games" in row else np.nan
    return [wins, losses]

# Example using team_stats, which has "Wins" and "Games"
expanded = team_stats.apply(wins_losses, axis=1, result_type="expand")
expanded.columns = ["WinsOut", "LossesOut"]
team_stats = pd.concat([team_stats, expanded], axis=1)

कॉलम के नाम अपने-आप देने के लिए Series या dict लौटाएँ

प्रत्येक पंक्ति से एक Series या dict लौटाने पर उन्हीं लेबल्स वाले कॉलम के साथ DataFrame बनता है।

def summary_row(row):
    return pd.Series(
        {
            "IsWinningSeason": row["Wins"] >= 90,
            "RunRatio": (row["RunsScored"] / row["RunsAllowed"]),
        }
    )

summary = team_stats.apply(summary_row, axis=1)
team_stats = pd.concat([team_stats, summary], axis=1)

वे प्रदर्शन सुझाव जो मायने रखते हैं

पंक्ति-वार .apply(axis=1) सुविधाजनक है पर बड़े फ्रेम्स पर धीमा, क्योंकि यह आपके Python फ़ंक्शन को प्रति पंक्ति एक बार कॉल करता है। ये पैटर्न उस बाधा से बचाते हैं।

  • वेक्टराइज़्ड pandas/NumPy ऑपरेशंस और sum, mean, clip, where, astype और स्ट्रिंग/एक्सेसर मेथड्स को प्राथमिकता दें।
  • ऐसे संख्यात्मक row/column ऑपरेशंस के लिए जो एरे स्वीकार करते हैं, pandas ओवरहेड घटाने हेतु NumPy array पाने के लिए raw=True पास करें।
  • एकल कॉलम पर एलिमेंटवाइज़ काम के लिए, Series.map() या वेक्टराइज़्ड मेथड्स का उपयोग करें, DataFrame.apply(axis=1) के बजाय।
  • अपने फ़ंक्शन के भीतर दी गई पंक्ति/कॉलम में बदलाव करने से बचें; इसके बजाय नया मान लौटाएँ।

संस्करण संबंधी नोट्स जिन्हें जानना चाहिए (pandas 2.x)

हालिया pandas रिलीज़ ने .apply() से जुड़े व्यवहार को कड़ा किया है ताकि परिणाम अधिक पूर्वानुमेय हों।

  • List-जैसी रिटर्न्स: 2.0+ में, असंगत सूची लंबाइयाँ एरर उठाती हैं। समान लंबाइयाँ रखें और जब कई कॉलम चाहिए हों तो result_type='expand' सेट करें।
  • आउटपुट विस्तार: एक Series या dict लौटाने पर उन keys को कॉलम मानकर DataFrame में फैलाया जाता है (0.23 से स्थिर)।
  • Series.apply() में बदलाव: convert_dtype आर्ग्युमेंट अप्रचलित है। यदि मिश्रित प्रकार चाहिए हों, तो पहले object में कास्ट करें (उदाहरण, s.astype("object").apply(fn))। नए सिग्नेचर्स अधिक संदर्भों में नियंत्रित करने देते हैं कि fn को स्केलर्स मिलें या Series (उदाहरण, by_row)।

.apply() बनाम .map() बनाम .applymap() बनाम .agg()

अपनी ट्रांसफॉर्मेशन की "शेप" के अनुसार API चुनें।

  • Series.map(func_or_dict): एक कॉलम पर एलिमेंटवाइज़ ट्रांसफॉर्म। लुकअप्स या सरल फ़ंक्शंस के लिए बेहतर।
  • DataFrame.apply(func, axis=1): पंक्ति-वार लॉजिक जिसे कई कॉलम चाहिए।
  • DataFrame.apply(func, axis=0): कॉलम-वार लॉजिक (प्रत्येक इनपुट एक कॉलम का प्रतिनिधित्व करने वाली Series होती है)।
  • DataFrame.applymap(func): हर सेल पर एलिमेंटवाइज़। कम उपयोग करें; वेक्टराइज़्ड मेथड्स तेज़ हैं।
  • .agg()/.transform(): एग्रीगेशन और group-wise ट्रांसफॉर्म्स; groupby पाइपलाइंस में इन्हें प्राथमिकता दें।

सामान्य गलतियाँ और त्वरित समाधान

ये वे मुद्दे हैं जो प्रायः गलत परिणाम या सुस्ती का कारण बनते हैं, साथ ही उनके सुधार के तरीके।

  • प्रति-पंक्ति लॉजिक के लिए axis=1 भूल जाना: यदि आपका फ़ंक्शन पंक्तियों के बजाय अचानक कॉलम प्राप्त करने लगे, तो axis=1 जोड़ें।
  • अप्रत्याशित सूची वाले Series: जब प्रत्येक पंक्ति से list/array लौटा रहे हों, तो कॉलमों में बाँटने के लिए result_type='expand' सेट करें।
  • धीमा row-wise कोड: इसे वेक्टराइज़्ड एक्सप्रेशंस या बिल्ट-इंस से बदलें; यदि संभव न हो, तो ओवरहेड घटाने के लिए raw=True पर विचार करें।
  • इनपुट पंक्ति में बदलाव करना: इनपुट को केवल-पढ़ने योग्य मानेँ, और नए मान लौटाएँ।
  • Dtype drift: यदि आपका फ़ंक्शन कभी-कभी non-integers लौटाता है, तो integer कॉलम float या object में अपकास्ट हो सकते हैं। आवश्यकता हो तो बाद में astype से कास्ट करें।

निष्कर्ष

.apply() एक लचीला टूल है, लेकिन यह परफ़ॉर्मेंस का शॉर्टकट नहीं है। अंकगणित, एग्रीगेशन, और एकल कॉलम पर एलिमेंटवाइज़ ट्रांसफॉर्म्स के लिए वेक्टराइज़्ड ऑपरेशंस का उपयोग करें। DataFrame.apply(axis=1) तभी अपनाएँ जब आपका लॉजिक सच में प्रति पंक्ति कई कॉलमों की ज़रूरत रखता हो। जब इसे उपयोग करें, तो result_type से आउटपुट शेप नियंत्रित करें, संख्यात्मक फ़ंक्शंस के लिए raw=True पर विचार करें, और dtypes पर नज़र रखें। ये पैटर्न आधुनिक pandas पर पूर्वानुमेय परिणाम देते हैं और आपके डेटा के बढ़ने के साथ बेहतर स्केल करते हैं।

विषय