2025, Oct 18 11:31

pandas और Python dict.get के साथ सुरक्षित लुकअप: KeyError-प्रूफ DataFrame मैपिंग

यह गाइड दिखाती है कि pandas और Python dict.get से lookup सुरक्षित कर KeyError से बचें: DataFrame में type, cost, location मैप करें, डिफ़ॉल्ट मान से स्कीमा स्थिर रखें.

जब आप किसी इनपुट DataFrame को स्थिर लुकअप टेबल के मेटाडेटा से समृद्ध करते हैं, तो सिर्फ एक गायब कुंजी पूरी रन को KeyError के साथ बिगाड़ सकती है। यह गाइड बताती है कि pandas और साधारण Python dict का इस्तेमाल करके उस मैपिंग को कैसे भरोसेमंद बनाया जाए—ताकि लॉजिक तेज और पढ़ने में साफ रहे।

समस्या को दोहराना

एक CSV-आधारित लुकअप से शुरू करें, जिसे आप सर्वर नाम को कुंजी बनाकर डिक्शनरी में बदलते हैं। उसके बाद, आने वाली पंक्तियों में server कॉलम को type, cost और location जैसे गुणों पर मैप करके लेबल जोड़ें।

import pandas as pd
ref_df = pd.read_csv(r'C:\Location\format1.csv', sep=',')
ref_df['label'] = 'apache'
assoc_map = (
    ref_df
    .set_index('server')
    .T
    .to_dict('list')
)
print(assoc_map)
# {'ABC123': ['IBM', 1000, 'East Coast', 'apache'],
#  'ABC456': ['Dell', 800, 'West Coast', 'apache'],
#  'XYZ123': ['HP', 900, 'West Coast', 'apache']}
events_df = pd.read_csv(r'C:\Location\my_data.csv')
print(events_df)
#    server  busy       datetime
# 0  ABC123   24%  6/1/2024 0:02
# 1  ABC456   45%  6/1/2024 4:01
# 2  GHI100   95%  6/1/2024 9:10
# कुंजी गायब होने पर डायरेक्ट इंडेक्सिंग क्रैश हो जाती है
events_df['type'] = events_df['server'].map(lambda s: assoc_map[s][0])
events_df['cost'] = events_df['server'].map(lambda s: assoc_map[s][1])
events_df['location'] = events_df['server'].map(lambda s: assoc_map[s][2])
# KeyError: 'GHI100'

जैसे ही इनपुट में कोई ऐसा server आता है जो डिक्शनरी में मौजूद नहीं है, सीधी assoc_map[s] लुकअप KeyError उछाल देती है।

समस्या क्यों आती है

असल वजह डिक्शनरी एक्सेस ही है। जो हिस्सा फेल होता है, वह है assoc_map[s]। उसके बाद का [0], [1], [2] तो सूची में इंडेक्सिंग है, जो डिक्शनरी लुकअप के सफल होने के बाद ही चलती। एक्सप्रेशन के गलत हिस्से को .get से घेरने की कोशिश करना भी कारगर नहीं होता। उदाहरण के लिए, assoc_map.get([s][0], None) देखने में लिस्ट इंडेक्स को सुरक्षित करता लगता है, लेकिन [s][0] अंततः s ही बनता है। यानी एक्सप्रेशन assoc_map.get(s, None) हो जाता है, जो कुंजी मौजूद होने पर पूरे गुणों की सूची लौटाता है, और न होने पर None देता है। उसे सीधे मैप करने पर या तो पूरी सूची कॉलम में गिर जाती है, या None आता है—जो आपकी जरूरत नहीं है।

अगर आप चरण-दर-चरण समझना पसंद करते हैं, तो lambda की जगह एक छोटा सा फ़ंक्शन लिखें ताकि आप print जोड़कर इनपुट और आउटपुट को अलग से देख सकें।

उपाय

डिक्शनरी एक्सेस पर .get लगाएं और असली मान के समान आकार (same shape) वाली एक डिफ़ॉल्ट सूची दें। फिर उस सूची पर इंडेक्स करें। इस तरह, कुंजी गायब होने पर भी इंडेक्सिंग हमेशा वैध रहती है।

# समान आकार का डिफ़ॉल्ट मान दें और .get के बाद इंडेक्स करें
# यहां हम प्लेसहोल्डर के रूप में खाली स्ट्रिंग्स इस्तेमाल कर रहे हैं
events_df['type'] = events_df['server'].map(lambda s: assoc_map.get(s, [""]*3)[0])
events_df['cost'] = events_df['server'].map(lambda s: assoc_map.get(s, [""]*3)[1])
events_df['location'] = events_df['server'].map(lambda s: assoc_map.get(s, [""]*3)[2])

एक जैसी पंक्तियां दोहराने से बचना चाहें, तो लक्षित कॉलम और उनकी पोजीशन पर लूप चला लें।

for idx, col in enumerate(['type', 'cost', 'location']):
    events_df[col] = events_df['server'].map(
        lambda s: assoc_map.get(s, [""]*3)[idx]
    )

इसी का एक और रूप स्पष्ट सदस्यता जांच करता है और कुंजी न मिलने पर प्लेसहोल्डर लौटाता है।

for idx, col in enumerate(['type', 'cost', 'location']):
    events_df[col] = events_df['server'].map(
        lambda s: assoc_map[s][idx] if s in assoc_map else ""
    )

आप एक ही पास में कई कॉलम भी अपडेट कर सकते हैं।

events_df.update(
    (
        col,
        events_df['server'].map(
            lambda s: assoc_map[s][pos] if s in assoc_map else ""
        )
    )
    for pos, col in enumerate(['type', 'cost', 'location'])
)

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

वास्तविक दुनिया का डेटा शायद ही कभी संदर्भ तालिकाओं से एकदम मेल खाता है। एक सुरक्षात्मक लुकअप सुनिश्चित करता है कि कोई अप्रत्याशित कुंजी पूरे लेबलिंग चरण को न बिगाड़े। डिक्शनरी एक्सेस पॉइंट पर dict.get का उपयोग, साथ में समान आकार वाले डिफ़ॉल्ट के साथ, आपकी मैपिंग को अनुमानित रखता है और DataFrame की स्कीमा स्थिर—भले ही अपस्ट्रीम स्रोत बदलते रहें।

समापन

pandas-समर्थित लुकअप से गुण मैप करते समय, .get को डिक्शनरी एक्सेस पर लगाएं, लिस्ट इंडेक्सिंग पर नहीं। सही लंबाई वाली डिफ़ॉल्ट सूची दें, उसी पर इंडेक्स करें, और ऐसे प्लेसहोल्डर चुनें जो आगे की प्रोसेसिंग के अनुकूल हों। व्यवहार को बारीकी से देखना हो तो lambdas की जगह छोटा फ़ंक्शन लिखकर इनपुट और मध्यवर्ती मानों को print करें। यह छोटा बदलाव KeyError को हटाते हुए कोड को स्पष्ट और वेक्टराइज़्ड-स्टाइल में बनाए रखता है।

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