ग्रुपिंग और एग्रीगेशन — हर ग्रुप के लिए एक उत्तर
"प्रति शाखा", "प्रति श्रेणी" और "प्रति दिन" जैसे सवालों को groupby की एक लाइन में बदलना: कुंजी (key), वैल्यू और एग्रीगेशन, named agg के साथ एक साथ कई गणनाएं, size बनाम count, दो कुंजियों से ग्रुपिंग, और औसतों के औसत का जाल।
- 1समस्या
- 2समझें
- 3उदाहरण
- 4अनुमान
- 5स्वयं करें
- 6चुनौती
वह समस्या जिसे हम हल कर रहे हैं
दुकान के मालिक ने आपको एक लाइन का संदेश भेजा: "इस सप्ताह हमारी प्रत्येक शाखा में कितने उत्पाद बिके?"
आपके पास orders.csv है — आठ ऑर्डर, प्रत्येक पंक्ति में एक। पिछले चार अध्यायों में आपने जो सीखा है, उससे आप पहले ही इसका उत्तर दे सकते हैं। एक शाखा को फ़िल्टर करें, उसके कॉलम का योग निकालें, और इसे दोहराएँ:
import pandas as pd
orders = pd.read_csv("orders.csv")
for branch in ["north", "south", "east"]:
units = orders[orders["branch"] == branch]["quantity"].sum()
print(branch, units)north 48
south 6
east 27संख्याएँ सही हैं। लेकिन इस तरीके में तीन समस्याएँ हैं, और जैसे-जैसे डेटा बढ़ता है, प्रत्येक समस्या और गंभीर होती जाती है।
पहला, आपको शाखाओं के नाम पहले से जानने पड़े। आपने सूची खुद हाथ से टाइप की। जब अगले सप्ताह की फ़ाइल में किसी नई west शाखा से कोई ऑर्डर आएगा, तो यह लूप उसे देख ही नहीं पाएगा — कोई एरर नहीं आएगा, यह बस west को छोड़ देगा, और कुल योग अलग-अलग शाखाओं के योग से मेल नहीं खाएगा।
दूसरा, हर नए सवाल के लिए एक नया लूप लिखना होगा। "प्रति श्रेणी राजस्व (revenue)" के लिए हाथ से टाइप की गई दूसरी सूची पर दूसरा लूप चलाना होगा। "प्रति शाखा और प्रति श्रेणी बिक्री" के लिए लूप के अंदर लूप की आवश्यकता होगी।
तीसरा, उत्तर कोई टेबल नहीं है। यह केवल प्रिंट की गई टेक्स्ट की पंक्तियाँ हैं। आप इसे दोबारा सॉर्ट नहीं कर सकते, फ़िल्टर नहीं कर सकते, या इसे फिर से बनाए बिना किसी अन्य उत्तर के पास नहीं रख सकते।
आप वास्तव में जो कहना चाहते हैं वह एक ही लाइन में कहा जा सकता है: पंक्तियों को शाखा के अनुसार विभाजित करें, प्रत्येक टुकड़े के अंदर quantity का योग निकालें, और उन टुकड़ों को एक टेबल के रूप में वापस लौटाएँ। यही groupby है, और पांडास में लगभग हर सारांश इसी तरह लिखा जाता है — एक रिपोर्ट, डैशबोर्ड की संख्या, या "पैसा वास्तव में कहाँ से आ रहा है?" का पहला उत्तर।
इस अध्याय के अंत में आप कर पाएंगे
- "प्रति शाखा इकाइयाँ" जैसे प्रश्न को उसके तीन भागों में बदलना — कुंजी (key), वैल्यू कॉलम, एग्रीगेशन — और ग्रुपिंग से पहले कुंजी की जाँच करना
df.groupby(key)[column].sum()लिखना और वापस मिलने वाली Series को पढ़ना, जहाँ कुंजी उसका इंडेक्स होती है.agg()और named aggregation के साथ एक साथ कई सारांश निकालनाsize()औरcount()में अंतर समझना, और यह तय करना कि मिसिंग कुंजियों और मिसिंग वैल्यूज़ के साथ क्या होना चाहिए- दो कॉलमों के आधार पर ग्रुपिंग करना, परिणाम को समतल (flatten) या पुनर्गठित (reshape) करना, और "औसतों के औसत" की भूल से बचना
ज़रूरी शर्तें: पंक्तियों को सॉर्ट करना।
वह फ़ाइल जिसका हम उपयोग कर रहे हैं
पिछले अध्यायों वाली वही orders.csv — एक छोटी दुकान के आठ ऑर्डर। यदि आपके पास यह नहीं है, तो इसे अपने प्रोजेक्ट फ़ोल्डर में ठीक इन पंक्तियों के साथ बनाएँ:
order_id,date,branch,product,category,quantity,price
1001,2024-03-01,north,pen,stationery,12,15.0
1002,2024-03-01,south,notebook,stationery,5,60.0
1003,2024-03-02,north,bag,accessories,2,850.0
1004,2024-03-02,east,bottle,accessories,7,120.0
1005,2024-03-03,north,eraser,stationery,30,8.0
1006,2024-03-03,south,bag,accessories,1,850.0
1007,2024-03-04,east,pen,stationery,20,15.0
1008,2024-03-04,north,bottle,accessories,4,120.0प्रत्येक पंक्ति एक ऑर्डर है: यह किस शाखा से आया, कौन सा उत्पाद, उसकी श्रेणी, कितनी इकाइयाँ और एक इकाई की कीमत।
कोड लिखने से पहले
प्रत्येक ग्रुपिंग प्रश्न का ढांचा एक जैसा ही होता है। groupby टाइप करने से पहले सवाल को टुकड़ों में तोड़ें।
1. सवाल को तीन भागों में बाँटें
ग्रुपिंग के सवाल में हमेशा तीन चीज़ें होती हैं, और सवाल के शब्द ही बताते हैं कि कौन क्या है:
- कुंजी (Key) — वह कॉलम जिसके मान समूहों को परिभाषित करते हैं। यह per, by या for each (प्रति / के अनुसार) के बाद आने वाला शब्द होता है। "प्रति शाखा इकाइयाँ" में यह
branchहै। - वैल्यू (Value) — वह कॉलम जिसका सारांश निकाला जा रहा है: जिस चीज़ को मापा जा रहा है। यहाँ,
quantity। - एग्रीगेशन (Aggregation) — कई मान मिलकर एक कैसे बनते हैं: कुल योग, औसत, कितने, अधिकतम। यहाँ,
sum।
पैटर्न को पक्का करने के लिए कुछ और उदाहरण:
- "प्रति श्रेणी राजस्व" — कुंजी
category, वैल्यूrevenue, एग्रीगेशनsum - "प्रत्येक शाखा में औसत ऑर्डर आकार" — कुंजी
branch, वैल्यूquantity, एग्रीगेशनmean - "प्रत्येक दिन कितने ऑर्डर" — कुंजी
date, कोई वैल्यू कॉलम नहीं (पंक्तियाँ गिननी हैं), एग्रीगेशनsize - "प्रति उत्पाद सबसे बड़ा एकल ऑर्डर" — कुंजी
product, वैल्यूquantity, एग्रीगेशनmax
यदि आप इन तीनों भागों के नाम नहीं बता सकते, तो सवाल अभी कोड के लिए तैयार नहीं है। "कौन सी शाखा सबसे अच्छी है?" में कोई वैल्यू और कोई एग्रीगेशन नहीं है — इकाइयों के हिसाब से सबसे अच्छी? राजस्व के हिसाब से? या ऑर्डरों की संख्या के हिसाब से? गणना करने से पहले पूछें। तीनों सवालों के जवाब तीन अलग-अलग शाखाएँ हो सकते हैं।
2. ग्रुपिंग करने से पहले इनपुट की जाँच करें
ग्रुपिंग पंक्तियों को छिपा देती है। एक बार जब आठ ऑर्डर तीन योगों में बदल जाते हैं, तो किसी टूटी हुई या अमान्य पंक्ति को योग के अंदर देख पाना असंभव हो जाता है। इसलिए जाँच हमेशा पहले की जाती है:
import pandas as pd
orders = pd.read_csv("orders.csv")
print(orders.shape)
print(orders.dtypes)(8, 7)
order_id int64
date str
branch str
product str
category str
quantity int64
price float64
dtype: objectयहाँ दो बातें महत्वपूर्ण हैं। वैल्यू कॉलम, quantity, int64 है — संख्यात्मक, इसलिए sum टेक्स्ट को जोड़ने के बजाय गणितीय योग करेगा। कुंजी कॉलम, branch, str है, जो कुंजी के रूप में बिल्कुल सही है।
इसके बाद कुंजी के वास्तविक मानों को देखें। यह वह जाँच है जिसे अक्सर लोग छोड़ देते हैं, जबकि यह सबसे महत्वपूर्ण है:
print(orders["branch"].value_counts())
print(orders[["branch", "quantity"]].isna().sum())branch
north 4
south 2
east 2
Name: count, dtype: int64
branch 0
quantity 0
dtype: int64तीन अलग-अलग शाखाएँ, और किसी भी कॉलम में कुछ भी मिसिंग नहीं है। groupby प्रत्येक अद्वितीय मान के लिए अक्षर-दर-अक्षर एक ग्रुप बनाता है — इसलिए "north" और "north " दो अलग ग्रुप बन जाएंगे। value_counts() वह जगह है जहाँ आप गलत रिपोर्ट बनने से पहले ही इसे पकड़ सकते हैं।
यहाँ देखें कि जब यह जाँच छूट जाती है तो क्या होता है। तीन पंक्तियाँ जो सभी north शाखा के लिए हैं — एक सही टाइप की गई, एक के अंत में स्पेस है, और एक बड़े अक्षर से शुरू होती है:
messy = pd.DataFrame({"branch": ["north", "north ", "North"], "quantity": [12, 2, 30]})
print(messy.groupby("branch")["quantity"].sum())
messy["branch"] = messy["branch"].str.strip().str.lower()
print(messy.groupby("branch")["quantity"].sum())branch
North 30
north 12
north 2
Name: quantity, dtype: int64
branch
north 44
Name: quantity, dtype: int64कोई एरर नहीं आया, और जहाँ एक शाखा होनी चाहिए थी वहाँ तीन "शाखाएँ" बन गईं। पांडास को जो दिया गया था उसके अनुसार प्रत्येक ग्रुप की संख्या गणितीय रूप से सही है, फिर भी रिपोर्ट गलत है। ग्रुपिंग से पहले कुंजी को साफ़ करना — स्पेस हटाना, लोअरकेस करना — उन्हें 44 के वास्तविक योग के साथ एक ही ग्रुप बना देता है।
3. आउटपुट कैसा दिखेगा, इसका पहले ही खाका तैयार करें
तय करें कि उत्तर कैसा दिखना चाहिए, ताकि जब वह वैसा न दिखे तो आप तुरंत पहचान सकें:
branch units
east ?
north ?
south ?ऊपर की जाँचों से आप पहले से ही इसके बारे में तीन तथ्य जानते हैं: इसमें ठीक 3 पंक्तियाँ होंगी (प्रत्येक अलग शाखा के लिए एक), शाखाएँ वर्णमाला के क्रम में आएंगी (groupby अपनी कुंजियों को सॉर्ट करता है), और तीनों संख्याओं का योग मूल कॉलम के कुल योग के बराबर होना चाहिए। यह अंतिम बिंदु बाद में आपके काम का प्रमाण है।
4. सही टूल चुनें
- एक कॉलम से प्रति ग्रुप एक संख्या:
df.groupby(key)[col].sum()(याmean,max, ...) - एक कॉलम से कई संख्याएँ:
df.groupby(key)[col].agg(["sum", "mean"]) - कई कॉलमों से अपने नामों के साथ कई संख्याएँ:
df.groupby(key).agg(name=(col, "sum"), ...) - प्रत्येक ग्रुप में कितनी पंक्तियाँ हैं:
df.groupby(key).size() - एक ऐसा कॉलम जो अभी मौजूद नहीं है, जैसे कि राजस्व: पहले उसे बनाएँ, फिर ग्रुप करें
अब कोड पर आते हैं।
स्प्लिट, अप्लाई, कंबाइन (Split, apply, combine)
groupby क्रम से तीन काम करता है, और इन नामों को जानना ज़रूरी है क्योंकि प्रत्येक ग्रुपिंग समस्या इन्हीं तीन चरणों का संयोजन है:
- स्प्लिट (Split) — प्रत्येक अद्वितीय कुंजी मान के आधार पर पंक्तियों को टुकड़ों में बाँटना
- अप्लाई (Apply) — प्रत्येक टुकड़े पर अलग से गणना लागू करना
- कंबाइन (Combine) — परिणामों को कुंजी द्वारा अनुक्रमित (indexed) करके एक नए ऑब्जेक्ट में जोड़ना
आप स्प्लिट को स्वतंत्र रूप से देख सकते हैं। एक groupby ऑब्जेक्ट पर लूप चलाया जा सकता है, जो आपको प्रत्येक कुंजी और टेबल का उसका संबंधित टुकड़ा देता है:
for branch, part in orders.groupby("branch"):
print(branch, part.shape, list(part["order_id"]))east (2, 7) [1004, 1007]
north (4, 7) [1001, 1003, 1005, 1008]
south (2, 7) [1002, 1006]प्रत्येक टुकड़ा सभी सात कॉलमों वाला एक सामान्य DataFrame है — north टुकड़े में चार पंक्तियाँ हैं, अन्य दो में दो-दो। किसी को शाखाओं के नाम टाइप नहीं करने पड़े; पांडास ने उन्हें डेटा में ढूँढ लिया। अगले सप्ताह किसी नई west शाखा से आने वाला ऑर्डर बस चौथा टुकड़ा बन जाएगा।
वास्तविक कोड में आपको शायद ही कभी इस तरह लूप चलाने की आवश्यकता होगी। यह यहाँ इसलिए है ताकि मानसिक मॉडल स्पष्ट हो सके: नीचे जो कुछ भी है वह "प्रत्येक टुकड़े पर कुछ करना, फिर उत्तरों को एक साथ जोड़ना" ही है।
प्रति ग्रुप एक संख्या
सवाल "प्रति शाखा इकाइयाँ" अपने तीन भागों में — कुंजी branch, वैल्यू quantity, एग्रीगेशन sum — ठीक इसी क्रम में कोड में लिखा जाता है:
units = orders.groupby("branch")["quantity"].sum()
print(units)branch
east 27
north 48
south 6
Name: quantity, dtype: int64इसे बाएँ से दाएँ पढ़ें: branch के आधार पर ग्रुप करें, quantity लें, उसका योग करें। लूप वाली वही संख्याएँ, एक ही लाइन में, बिना किसी शाखा का नाम हाथ से टाइप किए।
अब इसे अपने पहले सोचे गए खाके से मिलाएँ। तीन पंक्तियाँ — हाँ। वर्णमाला क्रम — हाँ। और प्रमाण:
print(units.sum(), orders["quantity"].sum())81 81समूहों का योग पूरी तालिका के योग के बराबर है, इसलिए कोई पंक्ति नहीं छूटी और न ही किसी को दो बार गिना गया। यह एक लाइन की जाँच आदत बनाने लायक है: जब भी आप ग्रुप करें, परिणाम के कुल योग की तुलना इनपुट के कुल योग से करें।
परिणाम एक Series है, और कुंजी उसका इंडेक्स है
print(type(units))
print(units.index)
print(units["north"])<class 'pandas.Series'>
Index(['east', 'north', 'south'], dtype='str', name='branch')
48कंबाइन चरण ने शाखाओं को किसी कॉलम में नहीं, बल्कि इंडेक्स में डाल दिया। यही कारण है कि आउटपुट में मानों के ऊपर एक अलग लाइन पर branch दिखता है — यह इंडेक्स का नाम है। इसका यह भी अर्थ है कि आप शाखा के नाम से मान देख सकते हैं, जैसे units["north"], ठीक वैसे ही जैसे आपने चौथे अध्याय में .loc के साथ किया था।
चूँकि यह एक सामान्य Series है, इसलिए पिछले अध्यायों की सभी चीज़ें इस पर काम करती हैं। इसे सॉर्ट करने से "किस शाखा ने सबसे अधिक बिक्री की" का उत्तर मिलता है:
print(units.sort_values(ascending=False))
print(units.idxmax())branch
north 48
east 27
south 6
Name: quantity, dtype: int64
northidxmax() सबसे बड़े मान का लेबल लौटाता है — शाखा का नाम, संख्या नहीं। "कौन शीर्ष पर है" जानने के लिए आमतौर पर यही चाहिए होता है।
पहले कॉलम चुनना क्यों ज़रूरी है
यह प्रलोभन हो सकता है कि ["quantity"] छोड़ दिया जाए और पांडास को सब कुछ जोड़ने दिया जाए:
print(orders.groupby("branch").sum())order_id date ... quantity price
branch ...
east 2011 2024-03-022024-03-04 ... 27 135.0
north 4017 2024-03-012024-03-022024-03-032024-03-04 ... 48 993.0
south 2008 2024-03-012024-03-03 ... 6 910.0
[3 rows x 6 columns]कोई एरर नहीं आया — और इसमें लगभग हर संख्या निरर्थक है। order_id को जोड़ दिया गया, जैसे ऑर्डर नंबर कोई वित्तीय राशि हों। date स्ट्रिंग्स एक साथ चिपक गईं, क्योंकि टेक्स्ट के लिए "जोड़ने" का मतलब उन्हें जोड़ना (concatenate) होता है। price का भी योग कर दिया गया: north के लिए 993.0 चार अलग-अलग उत्पादों की कीमतों का योग है, जो कुछ भी नहीं दर्शाता।
orders.groupby("branch").sum() कोई एरर नहीं देता। यह आईडी नंबर जोड़ता है, तारीखों को चिपकाता है और इकाई कीमतों का योग करता है, और परिणाम को ऐसे लौटाता है जैसे कि यह एक वैध रिपोर्ट हो।केवल quantity ही सार्थक था, और आप केवल वही मांग सकते थे। यही कारण है कि नियम है एग्रीगेशन से पहले वैल्यू कॉलम चुनें: sum प्रत्येक डेटा टाइप के लिए वही करता है जो जोड़ का मतलब होता है, और अधिकांश कॉलमों के लिए वह ऐसा सवाल नहीं है जो किसी ने पूछा हो।
mean अधिक सख्त है, और निरर्थक परिणाम देने के बजाय साफ़ मना कर देता है:
print(orders.groupby("branch").mean())TypeError: dtype 'str' does not support operation 'mean'यह एरर उसी गलती को उजागर करता है। इसे ऐसे पढ़ें: आपने मुझसे टेक्स्ट कॉलम का औसत निकालने को कहा। इसका समाधान numeric_only=True नहीं है — वह फिर भी order_id और price का औसत निकालेगा — बल्कि यह बताना है कि आपका मतलब किस कॉलम से है।
एक साथ कई संख्याएँ: .agg()
एक सारांश शायद ही कभी पर्याप्त होता है। "प्रत्येक शाखा ने कितना बेचा, प्रति ऑर्डर औसतन कितना और कितने ऑर्डरों में?" — यह एक ही कॉलम के तीन एग्रीगेशन हैं। उनके नामों की एक लिस्ट .agg() में पास करें:
print(orders.groupby("branch")["quantity"].agg(["sum", "mean", "count"]))sum mean count
branch
east 27 13.5 2
north 48 12.0 4
south 6 3.0 2अब परिणाम एक DataFrame है — प्रति एग्रीगेशन एक कॉलम, प्रति शाखा एक पंक्ति। यह वह कहानी बताता है जिसे केवल कुल योग ने छिपा दिया था: east ने north की आधी से कुछ अधिक इकाइयाँ बेचीं, लेकिन उसका औसत ऑर्डर बड़ा है। north शाखा को अधिक ऑर्डर मिलते हैं; east को कम, लेकिन थोड़े बड़े ऑर्डर मिलते हैं।
लिस्ट में नाम स्ट्रिंग्स हैं: "sum", "mean", "count", "min", "max", "median", "nunique" (अलग मानों की संख्या), "first", "last"। ये वही मेथड्स हैं जिनका उपयोग आपने किसी एक कॉलम पर किया है, जो अब प्रत्येक टुकड़े पर लागू होते हैं।
Named aggregation: आपके कॉलम, आपके नाम
वास्तविक रिपोर्टें अलग-अलग कॉलमों को अलग-अलग तरीकों से सारांशित करती हैं, और पठनीय कॉलम नाम चाहती हैं। Named aggregation एक ही कॉल में दोनों काम करता है। प्रत्येक कीवर्ड तर्क एक आउटपुट कॉलम का नाम होता है, और उसका मान एक जोड़ा होता है: (इनपुट कॉलम, एग्रीगेशन)।
राजस्व (revenue) रिपोर्ट करने के लिए एक स्वाभाविक चीज़ है, और यह अभी कोई कॉलम नहीं है — यह प्रत्येक पंक्ति पर मात्रा गुणा कीमत है। नए कॉलम बनाने पर अगला अध्याय पूरी तरह केंद्रित है; अभी के लिए एक पंक्ति पर्याप्त है, और यह योजना के चरण 4 को कार्य में दिखाती है: यदि वैल्यू कॉलम मौजूद नहीं है, तो ग्रुपिंग से पहले उसे बनाएँ।
orders["revenue"] = orders["quantity"] * orders["price"]
summary = orders.groupby("branch").agg(
orders=("order_id", "count"),
units=("quantity", "sum"),
revenue=("revenue", "sum"),
)
print(summary)orders units revenue
branch
east 2 27 1140.0
north 4 48 2600.0
south 2 6 1150.0यह एक ऐसी रिपोर्ट है जिसे आप सीधे भेज सकते हैं। agg कॉल की प्रत्येक पंक्ति एक वाक्य की तरह पढ़ी जा सकती है: orders order_id का count है, units quantity का sum है, revenue revenue का sum है। आउटपुट कॉलम के नाम आपके द्वारा चुने गए हैं, न कि डिफ़ॉल्ट sum और count, इसलिए बाद में नाम बदलने की कोई ज़रूरत नहीं है।
और यह पिछले उत्तर को बदल देता है। इकाइयों के हिसाब से east स्पष्ट रूप से दूसरे स्थान पर था; लेकिन राजस्व के मामले में south आगे निकल जाता है — 850 का एक बैग बीस पेनों से भारी पड़ता है। यही कारण है कि चरण 1 ने वैल्यू का नाम तय करने पर ज़ोर दिया था: इकाइयों के हिसाब से "सर्वश्रेष्ठ शाखा" और राजस्व के हिसाब से "सर्वश्रेष्ठ शाखा" दो अलग-अलग सवाल हैं।
परिणाम एक DataFrame है, इसलिए यह उसी की तरह सॉर्ट होता है:
print(summary.sort_values("revenue", ascending=False))orders units revenue
branch
north 4 48 2600.0
south 2 6 1150.0
east 2 27 1140.0गिनती: size() बनाम count()
"प्रति शाखा कितने ऑर्डर" एक ही सवाल लगता है। लेकिन पांडास के पास दो उत्तर हैं, और वे ठीक उसी समय अलग होते हैं जब डेटा मिसिंग होता है।
size()प्रत्येक ग्रुप में पंक्तियों की गिनती करता है, चाहे उनमें कुछ भी हो।count()प्रत्येक ग्रुप में किसी कॉलम के गैर-मिसिंग मानों की गिनती करता है।
साफ़ फ़ाइल पर दोनों सहमत होते हैं। दोनों में अंतर देखने के लिए, यहाँ orders के पास हाथ से बनाई गई एक छोटी टेबल है, जिसमें एक मिसिंग शाखा और एक मिसिंग मात्रा है:
gaps = pd.DataFrame(
{
"order_id": [1001, 1002, 1003, 1004, 1005],
"branch": ["north", "south", None, "east", "north"],
"quantity": [12, 5, 2, None, 30],
}
)
print(gaps)order_id branch quantity
0 1001 north 12.0
1 1002 south 5.0
2 1003 NaN 2.0
3 1004 east NaN
4 1005 north 30.0(मिसिंग मान के कारण quantity फ्लोट में बदल गई — छठे अध्याय में समझाया गया था कि ऐसा क्यों होता है।)
print(gaps.groupby("branch").size())
print(gaps.groupby("branch")["quantity"].count())branch
east 1
north 2
south 1
dtype: int64
branch
east 0
north 2
south 1
Name: quantity, dtype: int64east में एक ऑर्डर है (size = 1) लेकिन शून्य ज्ञात मात्राएँ हैं (count = 0)। दोनों सही हैं; वे अलग-अलग सवालों के जवाब देते हैं। "कितने ऑर्डर हैं" के लिए size() का उपयोग करें, और "वास्तव में कितनों में मान मौजूद है" के लिए count() का उपयोग करें।
मिसिंग डेटा जो दो काम चुपचाप करता है
उन आउटपुट को फिर से देखें। ऑर्डर 1003, जिसमें कोई शाखा नहीं थी, उनमें से किसी में भी नहीं है। पाँच ऑर्डर अंदर गए थे; चार बाहर आए:
print(gaps.groupby("branch").size().sum(), len(gaps))4 5डिफ़ॉल्ट रूप से groupby उन पंक्तियों को छोड़ देता है जिनकी कुंजी मिसिंग होती है। "unknown" नाम का कोई ग्रुप नहीं बनता, इसलिए वह पंक्ति हर परिणाम से गायब हो जाती है। पहले बताई गई कुल योग जाँच इसे तुरंत पकड़ लेती है। यदि वे पंक्तियाँ महत्वपूर्ण हैं — और बिना शाखा का ऑर्डर भी एक ऑर्डर ही है — तो dropna=False के साथ उन्हें शामिल करें:
print(gaps.groupby("branch", dropna=False)["quantity"].sum())branch
east 0.0
north 42.0
south 5.0
NaN 2.0
Name: quantity, dtype: float64अब चारों समूह मौजूद हैं, और NaN समूह में वे 2 इकाइयाँ हैं जिन्हें किसी शाखा में नहीं रखा जा सका।
दूसरी शांत बात उसी आउटपुट में है: east 0.0 दिखाता है। east शाखा ने शून्य बिक्री नहीं की; उसकी एकमात्र मात्रा अज्ञात है। sum "जोड़ने के लिए कुछ नहीं" को शून्य मानता है। यदि "अज्ञात" को अज्ञात ही रहना चाहिए, तो min_count=1 के साथ ऐसा कहें — "केवल तभी योग निकालें जब कम से कम एक वास्तविक मान मौजूद हो":
print(gaps.groupby("branch", dropna=False)["quantity"].sum(min_count=1))branch
east NaN
north 42.0
south 5.0
NaN 2.0
Name: quantity, dtype: float64कौन सा सही है यह सवाल पर निर्भर करता है, और यही मुख्य बात है: इसे योजना बनाने के चरण में तय करें, दुर्घटना से नहीं। दोनों समस्याओं का समाधान आमतौर पर पहले ही होता है — ग्रुप करने से पहले मिसिंग मानों को साफ़ करें (अध्याय छह)।
दो कुंजियाँ: समूहों के अंदर समूह
"प्रति शाखा और प्रति श्रेणी राजस्व" में दो कुंजियाँ हैं। एक लिस्ट पास करें:
by_both = orders.groupby(["branch", "category"])["revenue"].sum()
print(by_both)branch category
east accessories 840.0
stationery 300.0
north accessories 2180.0
stationery 420.0
south accessories 850.0
stationery 300.0
Name: revenue, dtype: float64डेटा में वास्तव में आने वाले प्रत्येक संयोजन के लिए एक पंक्ति — यहाँ छह। इंडेक्स में अब दो स्तर हैं, branch और category; पांडास इसे MultiIndex कहता है, और पठनीयता बनाए रखने के लिए बाहरी स्तर को प्रति ब्लॉक केवल एक बार प्रिंट करता है। east के नीचे खाली स्थान का अर्थ है "अभी भी east"।
आप इसमें से सामान्य रूप से डेटा चुन सकते हैं:
print(by_both.loc["north"])
print(by_both.loc[("north", "stationery")])category
accessories 2180.0
stationery 420.0
Name: revenue, dtype: float64
420.0एक लेबल आपको north का पूरा हिस्सा देता है; दोनों लेबलों का एक टपल आपको एक विशिष्ट संख्या देता है।
साधारण कॉलम वापस पाना
MultiIndex संक्षिप्त होता है, लेकिन कई अगले चरणों के लिए — जैसे CSV में सहेजना, मास्क के साथ फ़िल्टर करना, या इसे किसी अन्य टेबल के साथ जोड़ना — सादे कॉलम अधिक सुविधाजनक होते हैं। इसके दो तरीके हैं, और वे एक ही टेबल देते हैं:
flat = orders.groupby(["branch", "category"], as_index=False)["revenue"].sum()
print(flat)
print(flat.equals(by_both.reset_index()))branch category revenue
0 east accessories 840.0
1 east stationery 300.0
2 north accessories 2180.0
3 north stationery 420.0
4 south accessories 850.0
5 south stationery 300.0
Trueas_index=False groupby को पहले स्थान पर कुंजियों को इंडेक्स में न ले जाने के लिए कहता है; reset_index() उन्हें बाद में वापस बाहर निकालता है। जो भी आपके संदर्भ में पढ़ने में बेहतर लगे, उसका उपयोग करें।
पढ़ने में आसान बनाना: unstack()
किसी व्यक्ति के पढ़ने के लिए, एक लंबी सूची की तुलना में ग्रिड अधिक सुविधाजनक होता है — किनारे पर शाखाएँ और शीर्ष पर श्रेणियाँ:
print(by_both.unstack())category accessories stationery
branch
east 840.0 300.0
north 2180.0 420.0
south 850.0 300.0unstack() आंतरिक इंडेक्स स्तर, category, को कॉलमों में बदल देता है। संख्याएं बिल्कुल वही छह हैं। यदि किसी शाखा ने कभी कोई श्रेणी नहीं बेची होती, तो उसका सेल NaN होता; unstack(fill_value=0) इसके बजाय वहाँ 0 लिखता है।
औसतों का औसत (average of averages) निकालने का जाल
एक आकर्षक सवाल: प्रत्येक शाखा में औसतन एक इकाई की कीमत क्या है? त्वरित उत्तर कीमत कॉलम का औसत निकालना लगता है:
print(orders.groupby("branch")["price"].mean())branch
east 67.50
north 248.25
south 455.00
Name: price, dtype: float64यह ऑर्डर लाइनों पर कीमतों का औसत है, और प्रत्येक लाइन को एक बार गिना जाता है — south शाखा का एकल बैग (850 पर 1 इकाई) उसकी नोटबुक्स (60 पर 5 इकाइयाँ) के बराबर भार रखता है। लेकिन ग्राहकों ने प्रत्येक लाइन में एक इकाई नहीं खरीदी। उन्होंने कई इकाइयाँ खरीदीं। बेची गई प्रति इकाई औसत मूल्य कुल राजस्व को कुल इकाइयों से विभाजित करके निकाला जाता है, और इसकी गणना योगों से की जानी चाहिए:
per_unit = orders.groupby("branch").agg(
units=("quantity", "sum"),
revenue=("revenue", "sum"),
)
per_unit["avg_unit_price"] = (per_unit["revenue"] / per_unit["units"]).round(2)
print(per_unit)units revenue avg_unit_price
branch
east 27 1140.0 42.22
north 48 2600.0 54.17
south 6 1150.0 191.67south का आंकड़ा 455 से गिरकर लगभग 192 पर आ जाता है। अंकगणित के रूप में कोई भी संख्या "गलत" नहीं है; केवल एक ही सवाल का सही जवाब देती है। नियम: जब आप जो चाहते हैं वह एक अनुपात (ratio) है, तो अंश और हर को अलग-अलग एग्रीगेट करें, फिर भाग दें। किसी ऐसी चीज़ का औसत निकालना जो पहले से ही एक औसत है, या ऐसी कीमत जो पहले से ही प्रति-इकाई है, प्रत्येक पंक्ति को समान भार देती है चाहे वह एक इकाई हो या तीस।
एक संपूर्ण उदाहरण
branch_report.py मालिक के सवाल का सही उत्तर देता है — प्रति शाखा: ऑर्डरों की संख्या, इकाइयाँ, राजस्व, कुल राजस्व में हिस्सेदारी का प्रतिशत, और इकाइयों के हिसाब से सबसे अधिक बिकने वाला उत्पाद — और अपने कुल योगों को स्वयं प्रमाणित करता है।
import pandas as pd
orders = pd.read_csv("orders.csv")
# 1. Check the input before grouping hides anything.
print("Rows, columns:", orders.shape)
print("Branches:", sorted(orders["branch"].unique()))
print("Missing in key/value:", int(orders[["branch", "quantity", "price"]].isna().sum().sum()))
print()
# 2. Create the value column that does not exist yet.
orders["revenue"] = orders["quantity"] * orders["price"]
# 3. One grouped call, one row per branch.
report = orders.groupby("branch").agg(
orders=("order_id", "count"),
units=("quantity", "sum"),
revenue=("revenue", "sum"),
)
report["share_pct"] = (report["revenue"] / report["revenue"].sum() * 100).round(1)
# 4. Best product per branch: group by both keys, then keep each branch's top row.
by_product = orders.groupby(["branch", "product"], as_index=False)["quantity"].sum()
top = by_product.sort_values("quantity", ascending=False).drop_duplicates("branch")
report["top_product"] = top.set_index("branch")["product"]
report = report.sort_values("revenue", ascending=False)
print(report)
print()
# 5. Prove nothing was lost or double-counted.
print("Revenue check:", report["revenue"].sum(), "==", orders["revenue"].sum())
print("Orders check :", report["orders"].sum(), "==", len(orders))Rows, columns: (8, 7)
Branches: ['east', 'north', 'south']
Missing in key/value: 0
orders units revenue share_pct top_product
branch
north 4 48 2600.0 53.2 eraser
south 2 6 1150.0 23.5 notebook
east 2 27 1140.0 23.3 pen
Revenue check: 4890.0 == 4890.0
Orders check : 8 == 8इसे इस तरह क्यों लिखा गया है:
- जाँचें सबसे पहले आती हैं और प्रिंट की जाती हैं। तीन अलग-अलग शाखाएँ और शून्य मिसिंग मान वे स्थितियाँ हैं जिनके तहत रिपोर्ट विश्वसनीय है। यदि अगले सप्ताह की फ़ाइल में चार शाखाएँ या दो मिसिंग मान हैं, तो आप किसी भी योग को पढ़ने से पहले इसे शीर्ष पर देख लेते हैं।
revenueग्रुपिंग से पहले बनाई गई है। प्रति पंक्तिquantity * priceका योग करना और फिर ग्रुप करना वास्तविक राजस्व देता है। बाद में कुल मात्रा को औसत कीमत से गुणा करना फिर से औसतों के औसत का जाल बन जाता।- एक
aggकॉल, नामित कॉलम। प्रत्येक आउटपुट कॉलम को एक बार घोषित किया जाता है, उसी नाम के साथ जिसे पाठक देखेगा। चौथा माप जोड़ना बस एक और पंक्ति का काम है। share_pctकी गणना परिणाम पर की जाती है, इनपुट पर नहीं। ग्रुपिंग के बाद,reportशाखा द्वारा अनुक्रमित एक सामान्य DataFrame है, इसलिए सामान्य कॉलम अंकगणित इस पर काम करता है। इसके प्रतिशत जुड़कर 100.0 बनते हैं।- शीर्ष उत्पाद दो-कुंजी ग्रुपिंग और सॉर्टिंग का उपयोग करता है।
groupby(["branch", "product"])प्रति शाखा-उत्पाद जोड़ी इकाइयाँ देता है;quantityको अवरोही क्रम में सॉर्ट करना और प्रति शाखा पहली पंक्ति रखना (drop_duplicates("branch")) प्रत्येक शाखा के सर्वश्रेष्ठ विक्रेता को छोड़ देता है।top.set_index("branch")["product"]असाइन करने से उत्पाद स्थिति के बजाय शाखा लेबल द्वारा संरेखित होते हैं — इंडेक्स मिलान का ध्यान रखता है। - अंतिम दो पंक्तियाँ प्रमाण हैं। समूहों का राजस्व और ऑर्डर गणना इनपुट के बराबर हैं। यदि कोई शाखा छूट गई होती, या फ़ाइल में कोई खाली शाखा होती, तो संख्याएँ भिन्न होतीं और रिपोर्ट बाहर जाने से पहले ही आपको पता चल जाता।
कुछ सामान्य समस्याएँ और समाधान
KeyError: 'Branch' कुंजी कॉलम की स्पेलिंग फ़ाइल से भिन्न है — यहाँ एक बड़ा B है। कॉलम के नाम केस-सेंसिटिव होते हैं, और groupby तुरंत उनकी जाँच करता है। list(orders.columns) प्रिंट करें और नाम को ठीक से कॉपी करें; लिस्ट के कोट्स 'branch ' जैसे अतिरिक्त स्पेस को भी उजागर करते हैं।
KeyError: 'Column not found: Quantity' एक कदम बाद वही गलती: कुंजी ठीक थी लेकिन groupby("branch")["Quantity"] में वैल्यू कॉलम मौजूद नहीं है। संदेश केवल नाम के बजाय "Column not found" कहता है, जिससे पता चलता है कि groupby के बाद का चयन विफल रहा।
KeyError: "Label(s) ['qty'] do not exist" Named aggregation में, प्रत्येक जोड़े का पहला आइटम एक वास्तविक कॉलम होना चाहिए: units=("qty", "sum") विफल हो जाता है क्योंकि कोई qty नहीं है। बाईं ओर का आउटपुट नाम (units) कुछ भी हो सकता है; दाईं ओर का इनपुट टेबल में होना आवश्यक है।
ValueError: Cannot subset columns with a tuple with more than one element. Use a list instead. आपने orders.groupby("branch")["quantity", "price"] लिखा। कोष्ठक के एक जोड़े के अंदर दो नाम एक टपल बनाते हैं। कई कॉलम चुनने के लिए आपको एक लिस्ट की आवश्यकता होती है — डबल ब्रैकेट्स, ठीक चौथे अध्याय की तरह: orders.groupby("branch")[["quantity", "price"]].sum()।
TypeError: dtype 'str' does not support operation 'mean' एग्रीगेशन किसी टेक्स्ट कॉलम पर चला — या तो इसलिए कि कोई कॉलम नहीं चुना गया था (groupby("branch").mean()), या इसलिए कि आपके द्वारा चुना गया कॉलम टेक्स्ट के रूप में आया था। दूसरे मामले में, info() वहाँ str दिखाएगा जहाँ आप संख्या की उम्मीद कर रहे थे; अध्याय छह का pd.to_numeric(..., errors="coerce") इसका समाधान है।
परिणाम पढ़ते समय KeyError: 'quantity' units = orders.groupby("branch")["quantity"].sum() के बाद, शाखाएँ units का इंडेक्स हैं और quantity केवल उसका नाम है। units["quantity"] quantity नाम की शाखा की खोज करता है। विशिष्ट शाखा के लिए units["north"] का उपयोग करें, या कुल योग के लिए units.sum() का उपयोग करें।
चरण 4 / 6 — अनुमान
अपनी समझ की जाँच करें
क्या प्रिंट होगा?
from io import StringIO
import pandas as pd
# Stands in for orders.csv, so this snippet runs on its own.
RAW = """order_id,date,branch,product,category,quantity,price
1001,2024-03-01,north,pen,stationery,12,15.0
1002,2024-03-01,south,notebook,stationery,5,60.0
1003,2024-03-02,north,bag,accessories,2,850.0
1004,2024-03-02,east,bottle,accessories,7,120.0
1005,2024-03-03,north,eraser,stationery,30,8.0
1006,2024-03-03,south,bag,accessories,1,850.0
1007,2024-03-04,east,pen,stationery,20,15.0
1008,2024-03-04,north,bottle,accessories,4,120.0
"""
orders = pd.read_csv(StringIO(RAW))
print(orders.groupby("branch")["quantity"].sum())- Abranch north 48 south 6 east 27 Name: quantity, dtype: int64
- Bbranch east 27 north 48 south 6 Name: quantity, dtype: int64
- C branch quantity 0 east 27 1 north 48 2 south 6
- D81
उद्देश्य प्रति शाखा कुल quantity और कुल price निकालना है। क्या होगा?
from io import StringIO
import pandas as pd
# Stands in for orders.csv, so this snippet runs on its own.
RAW = """order_id,date,branch,product,category,quantity,price
1001,2024-03-01,north,pen,stationery,12,15.0
1002,2024-03-01,south,notebook,stationery,5,60.0
1003,2024-03-02,north,bag,accessories,2,850.0
1004,2024-03-02,east,bottle,accessories,7,120.0
1005,2024-03-03,north,eraser,stationery,30,8.0
1006,2024-03-03,south,bag,accessories,1,850.0
1007,2024-03-04,east,pen,stationery,20,15.0
1008,2024-03-04,north,bottle,accessories,4,120.0
"""
orders = pd.read_csv(StringIO(RAW))
by_branch = orders.groupby("branch")["quantity", "price"].sum()- Aयह काम करता है और दो कॉलम वाली एक टेबल लौटाता है
- B`KeyError: ('quantity', 'price')`
- C`ValueError: Cannot subset columns with a tuple with more than one element. Use a list instead.`
- Dयह केवल quantity कॉलम लौटाता है
groupby के बाद size() और count() के बीच क्या अंतर है?
- Aकोई अंतर नहीं है; ये दोनों एक ही चीज़ के दो नाम हैं
- B`size()` प्रत्येक समूह में पंक्तियाँ गिनता है, मिसिंग वैल्यूज़ सहित; `count()` केवल मौजूद (नॉन-मिसिंग) वैल्यूज़ को गिनता है
- C`count()` पंक्तियाँ गिनता है; `size()` विशिष्ट मान गिनता है
- D`size()` प्रत्येक समूह द्वारा उपयोग की गई मेमोरी मापता है
उत्तर देने के लिए अकाउंट आवश्यक है
अपने उत्तर जाँचने के लिए साइन इन करें
प्रश्न ऊपर दिए गए हैं, और मन में उत्तर सोचना ही मुख्य कार्य है। सही उत्तर, व्याख्या और तीन-स्तरीय संकेत देखने के लिए साइन इन करें।
आपकी बारी
मालिक अब श्रेणी (category) और दिन (date) के अनुसार इसी तरह का उत्तर चाहता है। orders.csv का उपयोग करके, category_report.py लिखें जो ठीक ये चार काम करे:
- फ़ाइल को पढ़े और
shape, अलग-अलग श्रेणियों की सॉर्ट की गई सूची, औरcategory,quantityऔरpriceमें एक साथ मिसिंग मानों की संख्या प्रिंट करे - एक
revenueकॉलम बनाए, फिर प्रति श्रेणी एक पंक्ति वाली एक टेबल बनाए जिसमें राजस्व के अनुसार अवरोही क्रम में ये कॉलम हों:orders(ऑर्डर की संख्या),units(कुल मात्रा),revenue(कुल राजस्व),avg_order_units(प्रति ऑर्डर औसत मात्रा) औरavg_unit_price(इकाइयों द्वारा विभाजित राजस्व, 2 दशमलव स्थानों तक पूर्णांकित) - प्रति दिन राजस्व प्रिंट करे, फिर सबसे अधिक राजस्व वाला एकल दिन प्रिंट करे
- एक प्रमाण पंक्ति प्रिंट करे: श्रेणी तालिका का राजस्व, दैनिक तालिका का राजस्व और पूरी फ़ाइल का कुल राजस्व
कोड लिखने से पहले, कागज़ पर योजना बनाएँ: शर्त 2 और 3 के लिए, प्रत्येक कॉलम के लिए कुंजी, वैल्यू और एग्रीगेशन का नाम लिखें, और लिखें कि प्रत्येक परिणाम में कितनी पंक्तियाँ होनी चाहिए।
सही होने पर, प्रोग्राम ठीक यही प्रिंट करता है:
Rows, columns: (8, 7)
Categories: ['accessories', 'stationery']
Missing: 0
orders units revenue avg_order_units avg_unit_price
category
accessories 4 14 3870.0 3.50 276.43
stationery 4 67 1020.0 16.75 15.22
date
2024-03-01 480.0
2024-03-02 2540.0
2024-03-03 1090.0
2024-03-04 780.0
Name: revenue, dtype: float64
Best day: 2024-03-02 with 2540.0
Proof: 4890.0 4890.0 4890.0फिर इसे जानबूझकर तोड़ें, और देखें कि हर बार क्या होता है:
- ऑर्डर 1001 की श्रेणी को
"Stationery"(बड़े अक्षर S) में बदलें। आपकी टेबल में अब कितनी पंक्तियाँ हैं, और क्या प्रमाण अभी भी पास होता है? unitsके लिए"sum"को"mean"से बदलें। कौन सी संख्या बदलती है, और यह अब किस प्रश्न का उत्तर देती है?- इसके बजाय
priceके माध्य के रूप मेंavg_unit_priceकी गणना करें। कौन सी श्रेणी सबसे अधिक बदलती है, और क्यों?
समाधान
पहले योजना बनाएँ।
- शर्त 2: कुंजी
category।orders=order_idका count;units=quantityका sum;revenue=revenueका sum;avg_order_units=quantityका mean;avg_unit_price= किसी एक कॉलम का एग्रीगेशन नहीं है — यह एक अनुपात है, इसलिए इसकी गणना बाद मेंrevenueऔरunitsसे की जाती है। फ़ाइल में दो श्रेणियाँ हैं, इसलिए टेबल में 2 पंक्तियाँ होंगी। - शर्त 3: कुंजी
date, वैल्यूrevenue, एग्रीगेशनsum। चार अलग-अलग तारीखें हैं, इसलिए 4 पंक्तियाँ; सबसे अच्छा दिन अधिकतम का इंडेक्स लेबल है, जोidxmax()है। - प्रमाण: दोनों टेबलों का कुल राजस्व फ़ाइल के 4890.0 के बराबर होना चाहिए।
category_report.py:
import pandas as pd
orders = pd.read_csv("orders.csv")
print("Rows, columns:", orders.shape)
print("Categories:", sorted(orders["category"].unique()))
print("Missing:", int(orders[["category", "quantity", "price"]].isna().sum().sum()))
print()
orders["revenue"] = orders["quantity"] * orders["price"]
table = orders.groupby("category").agg(
orders=("order_id", "count"),
units=("quantity", "sum"),
revenue=("revenue", "sum"),
avg_order_units=("quantity", "mean"),
)
table["avg_unit_price"] = (table["revenue"] / table["units"]).round(2)
table = table.sort_values("revenue", ascending=False)
print(table)
print()
daily = orders.groupby("date")["revenue"].sum()
print(daily)
print("Best day:", daily.idxmax(), "with", daily.max())
print()
print("Proof:", table["revenue"].sum(), daily.sum(), orders["revenue"].sum())Rows, columns: (8, 7)
Categories: ['accessories', 'stationery']
Missing: 0
orders units revenue avg_order_units avg_unit_price
category
accessories 4 14 3870.0 3.50 276.43
stationery 4 67 1020.0 16.75 15.22
date
2024-03-01 480.0
2024-03-02 2540.0
2024-03-03 1090.0
2024-03-04 780.0
Name: revenue, dtype: float64
Best day: 2024-03-02 with 2540.0
Proof: 4890.0 4890.0 4890.0योजना के अनुसार दो पंक्तियाँ और चार पंक्तियाँ, और तीनों राजस्व योग बिल्कुल सहमत हैं। ध्यान दें कि टेबल क्या बताती है जो कोई भी कॉलम अकेले नहीं बता सकता: स्टेशनरी लगभग पाँच गुना अधिक इकाइयाँ बेचती है, लेकिन एक्सेसरीज़ लगभग चार गुना अधिक राजस्व लाती हैं।
तीनों टूटने वाले प्रयोग क्या दिखाते हैं:
- एक पंक्ति में
"Stationery"होने पर टेबल में 3 पंक्तियाँ हो जाती हैं — बड़े अक्षर वालाStationeryएक ऑर्डर के साथ अपना अलग ग्रुप बन जाता है। प्रमाण अभी भी पास होता है, क्योंकि कोई पंक्ति खोई नहीं थी; यह केवल गलत समूह में गई थी। यही कारण है कि विशिष्ट श्रेणियों की लाइन सबसे ऊपर प्रिंट की जाती है: यह अब तीन श्रेणियों को सूचीबद्ध करेगी, और आप किसी भी संख्या को पढ़ने से पहले समस्या देख लेंगे। "mean"के साथ,units3.5 और 16.75 हो जाता है — वही जोavg_order_unitsहै। यह अब "एक विशिष्ट ऑर्डर कितना बड़ा है" का उत्तर देता है, "हमने कुल कितना बेचा" का नहीं।avg_unit_priceभी बदल जाता है, क्योंकि यह राजस्व को गलत चीज़ से विभाजित करता है।priceका औसत निकालने पर एक्सेसरीज़ को(850 + 120 + 850 + 120) / 4 = 485.0मिलता है, जो वास्तविक 276.43 से बहुत अधिक है, क्योंकि एक और दो इकाइयों वाले बैग ऑर्डर सात और चार इकाइयों वाले बोतल ऑर्डर जितना ही भार रखते हैं। स्टेशनरी 15.22 से बढ़कर 24.5 हो जाती है — केवल इसलिए कम नाटकीय है क्योंकि इसकी सभी कीमतें कम थीं। ऑर्डर लाइनों पर प्रति-इकाई संख्या का औसत निकालना ही जाल है; योगों को विभाजित करना ही इसका समाधान है।
Step 6 of 6
चुनौती — the chapter quiz
सरल से कठिन — दस प्रश्न, अंतिम वाले जानबूझकर चुनौतीपूर्ण बनाए गए हैं।
Sign in to take the quiz