2025, Sep 30 23:32

pandas में समूहों का लेक्सिकोग्राफिक ऑर्डर: tuple से एग्रीगेट, explode से क्रम

pandas में समूहों को लेक्सिकोग्राफिक ऑर्डर में सॉर्ट करें: समूह-भीतर sort, tuple एग्रीगेट, उस पर sort और explode. groupby टाई‑ब्रेकिंग व परफॉर्मेंस टिप्स शामिल.

pandas में जब आपको समूहों को केवल किसी एक एग्रीगेट से नहीं, बल्कि उनके मानों को आरोही क्रम में सही टाई-ब्रेकिंग के साथ क्रमबद्ध करना हो, तो प्रति-समूह साधारण न्यूनतम काफ़ी नहीं होता। यहाँ ज़रूरत है समूहों के बीच लेक्सिकोग्राफिक ऑर्डर की: पहले सबसे छोटे मान की तुलना करें; अगर बराबरी हो, तो अगले मान की जाँच करें, और इसी तरह तब तक चलते रहें जब तक क्रम तय न हो जाए।

उदाहरण डेटासेट

यह छोटा डेटाफ़्रेम देखें। लक्ष्य है समूहों को उनके arrive मानों के आधार पर क्रमबद्ध करना—टाई होने पर अगले-क्रम के मान टाई-ब्रेकर्स की तरह काम करें—और फिर उसी क्रम में पंक्तियाँ सूचीबद्ध करना।

import pandas as pd
import numpy as np
tbl = pd.DataFrame({
    "group_id": [5, 1, 9, 9, 5, 7, 7, 7, 9, 1, 5],
    "arrive":   [227, 60, 60, 88, 55, 55, 276, 46, 46, 35, 35]
})

सीधी-सादी तरकीबों में दिक्कत कहाँ आती है

हर समूह के भीतर सॉर्ट करना आसान है, लेकिन यहाँ काम है समूहों के बीच उनकी मान-श्रृंखला के आधार पर सॉर्ट करना। केवल प्रति-समूह न्यूनतम लेने से दिक्कत आती है जब दो समूहों का पहला मान समान हो लेकिन बाद में वे अलग हो जाएँ। groupby के साथ transform और nth मिलाकर कोशिश करना भी यहाँ नहीं चलता, क्योंकि transform में "nth" पास करने पर त्रुटि आती है—यह किसी समूह के लिए शून्य या एक से अधिक मान लौटा सकता है।

एक तरीका यह है कि मददगार कॉलम बनाए जाएँ जो प्रति-समूह पहला, दूसरा और तीसरा arrive मान स्पष्ट रूप से रखें और फिर उन्हीं कॉलमों पर सॉर्ट किया जाए। यह चलता है, पर अगर किसी समूह में अनेक मान हों तो यह तरीका अच्छी तरह स्केल नहीं करता।

इंपरेटिव वर्कअराउंड (चलता है, पर भारी पड़ता है)

नीचे दिया स्निपेट पोज़िशन-आधारित कॉलम बनाकर उन पर सॉर्ट करने का विस्तृत तरीका दिखाता है। तर्क सही है, लेकिन बड़े समूहों में N पोज़िशनल कॉलम बनाए रखना व्यवहारिक नहीं रहता।

# प्रत्येक समूह के भीतर क्रम तय करने के लिए मान के आधार पर सॉर्ट करें
wrk = tbl.sort_values("arrive").copy()
wrk["rank_in_group"] = wrk.groupby("group_id")["arrive"].cumcount()
# पहले तीन स्थानों को कॉलम में निकालें
wrk["a1"] = wrk["a2"] = wrk["a3"] = np.nan
wrk.loc[wrk["rank_in_group"] == 0, "a1"] = wrk.loc[wrk["rank_in_group"] == 0, "arrive"]
wrk.loc[wrk["rank_in_group"] == 1, "a2"] = wrk.loc[wrk["rank_in_group"] == 1, "arrive"]
wrk.loc[wrk["rank_in_group"] == 2, "a3"] = wrk.loc[wrk["rank_in_group"] == 2, "arrive"]
# इन स्थानिक मानों को उसी समूह की सभी पंक्तियों में फैलाएँ
wrk[["a1", "a2", "a3"]] = (
    wrk.groupby("group_id")[ ["a1", "a2", "a3"] ].transform("max")
)
# छोटे समूहों में छूटे हुए स्थानों को पिछले मानों से भरें
wrk["a2"] = wrk["a2"].fillna(wrk["a1"])
wrk["a3"] = wrk["a3"].fillna(wrk["a2"])
# अंत में स्थानिक कुंजियों से सॉर्ट करें, फिर सहायक कॉलम हटा दें
out_verbose = wrk.sort_values(["a1", "a2", "a3", "group_id"]) \
               .drop(columns=["a1", "a2", "a3", "rank_in_group"]) 

मूल विचार

समस्या का सार है: प्रति-समूह सॉर्ट किए गए मानों की लेक्सिकोग्राफिक तुलना। यदि आप हर समूह के सॉर्ट किए गए मानों को ऐसे एकल तुलना योग्य ऑब्जेक्ट में बदल दें जो क्रम को सुरक्षित रखे, तो पहले उन्हीं ऑब्जेक्ट्स को सॉर्ट किया जा सकता है और बाद में मूल पंक्तियों तक वापस फैलाया जा सकता है। ट्यूपल बिल्कुल यही करते हैं, और pandas सीधे कॉलमों को ट्यूपल में एग्रीगेट कर सकता है।

संक्षिप्त समाधान: ट्यूपल में एकत्र करें, सॉर्ट करें, फिर एक्सप्लोड करें

यह तरीका पहले समूहों के भीतर मानों को सॉर्ट करता है, फिर हर समूह को क्रमबद्ध ट्यूपल में बदलता है, उसके आधार पर सॉर्ट करता है और अंत में पंक्तियों में एक्सप्लोड कर देता है। नेटिव बिल्डिंग ब्लॉक्स से वही अपेक्षित समूहों-बीच का क्रम मिल जाता है।

lexi = (
    tbl.sort_values("arrive")
       .groupby("group_id", as_index=False)
       .agg(tuple)
)
# lexi
#    group_id           arrive
# 0         1         (35, 60)
# 1         5    (35, 55, 227)
# 2         7    (46, 55, 276)
# 3         9      (46, 60, 88)
result = (
    tbl.sort_values("arrive")
       .groupby("group_id", as_index=False)
       .agg(tuple)
       .sort_values("arrive")
       .explode("arrive")
)
# result
#    group_id arrive
# 1         5     35
# 1         5     55
# 1         5    227
# 0         1     35
# 0         1     60
# 2         7     46
# 2         7     55
# 2         7    276
# 3         9     46
# 3         9     60
# 3         9     88

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

समूहों-बीच का ऑर्डर, समूह-भीतर सॉर्टिंग से अलग समस्या है। हर समूह के सॉर्ट किए हुए मानों को एक क्रमबद्ध इकाई की तरह लेना लेक्सिकोग्राफिक तुलना के अनुरूप है और ढेर सारे पोज़िशनल कॉलम के सहारे बनाए गए नाज़ुक, कम-स्केलेबल ढांचे से बचाता है। साथ ही ध्यान रहे कि यह तरीका जितना सुरुचिपूर्ण है, groupby और Python ऑब्जेक्ट्स के साथ काम करना वेक्टराइज़्ड ऑपरेशन्स से धीमा हो सकता है; साधारण मामलों में भी ट्यूपल ऑब्जेक्ट्स को सॉर्ट करना, न्यूमेरिक सीरीज़ की तुलना में उल्लेखनीय रूप से धीमा पड़ सकता है। शुद्धता और संभावित परफॉर्मेंस ट्रेड-ऑफ—दोनों जानकर आप अपने वर्कलोड के लिए सही तरीका चुन सकते हैं।

मुख्य बातें

यदि आपको pandas में समूहों के बीच निर्धारक और टाई-सचेत ऑर्डर चाहिए, तो पहले प्रति-समूह मानों को सॉर्ट करें, उन्हें ट्यूपल में एग्रीगेट करें, उन्हीं ट्यूपल्स पर सॉर्ट करें और फिर पंक्तियों में एक्सप्लोड कर दें। यह कम कोड में लेक्सिकोग्राफिक ऑर्डरिंग का सही अर्थ पकड़ लेता है। अगर डेटा बड़ा है और विलंबता महत्वपूर्ण है, तो Python ऑब्जेक्ट्स के ओवरहेड को ध्यान में रखें और देखें कि क्या कोई अधिक वेक्टराइज़्ड तरीका आपकी सीमाओं में बेहतर बैठता है।

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