2025, Sep 23 23:31

pandas DataFrame में year पोज़िशन से कॉलम नाम निकालें (lambda के बिना)

pandas DataFrame में 1-आधारित year पोज़िशन से कॉलम नाम मैप करने का वेक्टराइज़्ड तरीका। apply/lambda की गलतियाँ रोकें, NaN व सीमा-पार इंडेक्स को हैंडल करें.

एक कॉलम में मौजूद संख्यात्मक सूचक को हेडर नाम से मैप करना pandas में आम काम है, जो देखने में आसान लगता है। ऐसे वाइड डेटाफ़्रेम में, जहाँ कॉलम वर्षों का प्रतिनिधित्व करते हैं और अलग कॉलम 1-आधारित पोज़िशन (year) रखता है, मकसद उस पोज़िशन के अनुरूप कॉलम लेबल को नए कॉलम (year_name) में लिखना है। इसे हर पंक्ति पर lambda से करना अक्सर पहला विचार होता है, लेकिन इससे पेचीदे एरर आते हैं और बेवजह जटिलता बढ़ती है। अच्छी खबर यह है कि आपको lambda की ज़रूरत ही नहीं।

समस्या सेटअप

मान लीजिए एक डेटाफ़्रेम है, जिसमें वर्ष कॉलम हैं और year एक 1-आधारित इंडेक्स है जो लक्ष्य कॉलम की ओर इशारा करता है। itemName कॉलम इंडेक्स है।

          2020  2021  2022  2023  2024  year
itemName                                      
item1        5    20    10    10    50     3
item2       10    10    50    20    40     2
item3       12    35    73    10    54     4

उम्मीद किया गया नतीजा: year में दी गई पोज़िशन के अनुरूप हेडर को year_name में जोड़ना।

          2020  2021  2022  2023  2024  year year_name
itemName                                               
item1        5    20    10    10    50     3      2022
item2       10    10    50    20    40     2      2021
item3       12    35    73    10    54     4      2023

वह कोशिश जिससे त्रुटियाँ आती हैं

lambda के साथ row-wise apply देखने में सीधा समाधान लगता है, लेकिन यह टाइप और इंडेक्सिंग से जुड़ी दिक्कतें पैदा करता है। नीचे दिए गए स्निपेट वही विचार रखते हुए दिखाते हैं कि वे क्यों विफल होते हैं।

col_labels = repo[key].columns.tolist()
frame_out[["last_year_name"]] = frame_out[["_last_year"]].apply(
    lambda s: col_labels[s]
)

यह TypeError के साथ फेल होता है, जैसे “list indices must be integers or slices, not Series.” lambda को पूरी पंक्ति का स्लाइस (Series) मिलता है, और Series को सूची के इंडेक्स की तरह इस्तेमाल करना मान्य नहीं है।

col_labels = repo[key].columns.tolist()
frame_out[["last_year_name"]] = frame_out[["_last_year"]].apply(
    lambda s: col_labels[s.iloc[0].astype(int)]
)

इसके बाद “IndexError: list index out of range” मिल सकता है, और यदि आप दोहरे ब्रैकेट के साथ लक्ष्य को द्वि-आयामी मानते हुए एक-आयामी परिणाम असाइन करते हैं, तो “ValueError: Columns must be same length as key” भी आ सकता है।

असल में गड़बड़ी कहाँ है

axis=0 के साथ डेटाफ़्रेम पर .apply हर पंक्ति के लिए एक Series लौटाता है, न कि ऐसा स्केलर जिसे सीधे सूची इंडेक्सिंग में लगाया जा सके। यही वजह है कि col_labels[s] फेल होता है: s एक Series है। s.iloc[0] लेकर जबरन काम चलाने की कोशिश असली समस्या को ढक देती है और out-of-range इंडेक्सिंग का जोखिम बढ़ाती है। साथ ही, frame_out[["last_year_name"]] जैसे दोहरे ब्रैकेट से एक नया कॉलम टार्गेट करने पर दाईं ओर 1D Series देने से length mismatch त्रुटि भी हो सकती है।

वेक्टराइज़्ड समाधान (lambda की ज़रूरत नहीं)

Columns इंडेक्स स्वयं इंडेक्सेबल है। चूँकि year 1-आधारित है, शून्य-आधारित पोज़िशनल इंडेक्सिंग से मेल कराने के लिए 1 घटाएँ और सीधे columns से चुनें। इससे प्रति-पंक्ति Python कॉल से बचाव होता है और shape/टाइप से जुड़ी सारी उलझनें दूर रहती हैं।

# पोज़िशन संकेतक से सीधे कॉलम लेबल पर मैपिंग
grid["year_name"] = grid.columns[grid["year"] - 1]  # .astype("Int64")

यह अभिव्यक्ति हर पंक्ति के लिए year - 1 पर मौजूद कॉलम लेबल चुनती है और उसे year_name में लिखती है। यदि परिणाम के लिए किसी विशिष्ट nullable integer dtype की ज़रूरत हो, तो वैकल्पिक Int64 कास्ट इस्तेमाल किया जा सकता है।

अमान्य या गायब मानों को सँभालना

यदि year में NaN या सीमा से बाहर पोज़िशन हो सकती है, तो reindex के साथ Series का उपयोग करें ताकि सुरक्षित रूप से एलाइन हो सके और पोज़िशन अमान्य होने पर अपवाद उठाने के बजाय NaN मिले।

grid["year_name"] = pd.Series(grid.columns).reindex(grid["year"] - 1).values

यह तरीका year में NaN या आख़िरी कॉलम से परे पोज़िशन जैसी स्थितियों को सहजता से सँभालता है—अपवाद फेंकने के बजाय year_name में NaN लौटाता है।

पुनरुत्पादनीय इनपुट

मान्य मान:

grid = pd.DataFrame.from_dict({
    "index": ["item1", "item2", "item3"],
    "columns": [2020, 2021, 2022, 2023, 2024, "year"],
    "data": [
        [5, 20, 10, 10, 50, 3],
        [10, 10, 50, 20, 40, 2],
        [12, 35, 73, 10, 54, 4],
    ],
    "index_names": ["itemName"],
    "column_names": [None],
}, "tight")

अमान्य मान:

grid = pd.DataFrame.from_dict({
    "index": ["item1", "item2", "item3"],
    "columns": [2020, 2021, 2022, 2023, 2024, "year"],
    "data": [
        [5, 20, 10, 10, 50, 3],
        [10, 10, 50, 20, 40, 20],
        [12, 35, 73, 10, 54, None],
    ],
    "index_names": ["itemName"],
    "column_names": [None],
}, "tight")

यह क्यों मायने रखता है

वेक्टराइज़्ड इंडेक्सिंग पर भरोसा रखने से काम संक्षिप्त रहता है, अस्पष्ट shape और dtype रूपांतरणों से बचाव होता है, और Series को scalar मानने से पैदा होने वाली त्रुटियाँ नहीं आतीं। यह एक कॉलम को टार्गेट करते समय दोहरे ब्रैकेट से होने वाले shape mismatch जैसे सामान्य असाइनमेंट जालों से भी बचाता है। हज़ारों पंक्तियों वाले डेटाफ़्रेम में प्रति-पंक्ति Python lambda छोड़ देना अधिक साफ़ और कम त्रुटिप्रवण होता है।

मुख्य बातें

जब 1-आधारित पोज़िशन को कॉलम लेबल से मैप करना हो, तो सीधे columns इंडेक्स का उपयोग करें: grid.columns[grid["year"] - 1]। यदि डेटा में NaN या सीमा से बाहर पोज़िशन हो सकती है, तो अपवादों के बजाय सुरक्षित रूप से NaN पाने के लिए reindex वाला पैटर्न अपनाएँ। एक नया कॉलम असाइन करते समय, “Columns must be same length as key” से बचने के लिए उसे एकल ब्रैकेट से टार्गेट करें। इन तरीकों के साथ रूपांतरण मज़बूत और पढ़ने में आसान रहता है।

यह लेख StackOverflow पर प्रश्न (लेखक: DGMS89) और mozway के उत्तर पर आधारित है।