DataFrames को जोड़ना — concat से ऊपर-नीचे जोड़ना, merge से मिलाना
अलग-अलग टेबल्स को एक साथ लाना: concat से एक ही तरह के महीनों का डेटा ऊपर-नीचे जोड़ना, और merge से दूसरी टेबल से जानकारी मिलाना — सही तरह का merge चुनना, कौन सी पंक्तियाँ मेल नहीं खातीं यह देखना, और डुप्लिकेट की (duplicate key) से अनजाने में पंक्तियाँ बढ़ने से रोकना।
- 1समस्या
- 2समझें
- 3उदाहरण
- 4अनुमान
- 5स्वयं करें
- 6चुनौती
वह समस्या जिसे हम हल कर रहे हैं
दुकान का डेटा कभी भी एक ही फ़ाइल में नहीं रहता। कभी भी नहीं।
मार्च के ऑर्डर्स orders.csv में हैं, वही टेबल जिसे आप अध्याय चार से इस्तेमाल करते आ रहे हैं। अप्रैल के ऑर्डर्स इस हफ़्ते एक अलग एक्सपोर्ट orders_april.csv के रूप में आए हैं। प्रत्येक उत्पाद पर दुकान की क्या लागत (cost) आती है — और कौन सा आपूर्तिकर्ता (supplier) उसे बेचता है — यह जानकारी क्रय टीम (purchasing team) अपनी फ़ाइल products.csv में रखती है। ऑर्डर्स की फ़ाइल में किसी ने लागत नहीं रखी, क्योंकि लागत किसी उत्पाद का तथ्य है, किसी ऑर्डर का नहीं।
अब दुकान का मालिक एक सवाल पूछता है: मार्च और अप्रैल मिलाकर प्रत्येक सप्लायर ने कुल कितना लाभ (profit) कमा कर दिया?
कोई भी एक फ़ाइल इसका उत्तर नहीं दे सकती। लाभ की गणना के लिए मात्रा और बिक्री मूल्य (ऑर्डर्स में), लागत (उत्पाद फ़ाइल में), और सप्लायर (वह भी उत्पाद फ़ाइल में) की आवश्यकता होती है। और ऑर्डर्स तो दो महीनों में बँटे हुए हैं।
स्प्रेडशीट में आप मार्च की पंक्तियों के नीचे अप्रैल की पंक्तियाँ कॉपी-पेस्ट करेंगे, फिर प्रत्येक पंक्ति की लागत खींचने के लिए एक नए कॉलम में VLOOKUP लिखेंगे, उसे नीचे तक ड्रैग करेंगे, और उम्मीद करेंगे कि सब ठीक रहे। पांडास यही दो काम मात्र दो लाइनों में कर देता है:
- stack करना: एक ही प्रकार की पंक्तियाँ रखने वाली टेबल्स को — जैसे मार्च और अप्रैल — एक लंबी टेबल में ऊपर-नीचे जोड़ना:
pd.concat - match करना: एक साझे कॉलम के आधार पर एक टेबल की प्रत्येक पंक्ति का दूसरी टेबल की सही पंक्ति से मिलान करना — प्रत्येक ऑर्डर को उसके उत्पाद से जोड़ना — और दूसरी टेबल के कॉलम साथ लाना:
pd.merge
कोड की ये पंक्तियाँ बहुत छोटी हैं। लेकिन यह अध्याय इसलिए महत्वपूर्ण है क्योंकि बिना किसी एरर के यहाँ बड़ी ग़लतियाँ हो सकती हैं: एक merge चुपचाप उन ऑर्डर्स को हटा (drop) सकता है जिनका उत्पाद लुकअप टेबल में नहीं है, या जब लुकअप टेबल में कोई उत्पाद दो बार सूचीबद्ध हो तो ऑर्डर्स को चुपचाप दोगुना या कई गुना (duplicate) कर सकता है। दोनों ही स्थितियाँ ऐसा कुल योग देती हैं जो देखने में बिल्कुल सही लगता है लेकिन वास्तव में गलत होता है। इसलिए इस अध्याय का अधिकांश भाग merge से पहले और बाद में यह जानने के बारे में है कि आपके पास ठीक कितनी पंक्तियाँ होनी चाहिए — और उसकी जांच कैसे करें।
इस अध्याय के अंत में आप कर पाएंगे
- दो ऑपरेशनों में अंतर समझना: पंक्तियों को ऊपर-नीचे जोड़ना (
concat) और किसी 'की' पर मिलान करना (merge) pd.concatसे टेबल्स को जोड़ना,ignore_index=Trueसे दोहराए गए इंडेक्स को ठीक करना, और बेमेल कॉलम होने पर क्या होता है यह देखनाpd.mergeसे किसी 'की' पर दो टेबल्स को जोड़ना, और ज़रूरत के अनुसारhow="inner","left","right"या"outer"का सोच-समझकर चयन करनाindicator=Trueसे merge का ऑडिट करना औरvalidate="many_to_one"से उसे सुरक्षित करना- डुप्लिकेट कीज़ से होने वाले रो विस्फोट (row explosion) को पहचानना और ठीक करना
- अलग-अलग नामों वाली कीज़ पर
left_on/right_onसे merge करना, और टकराने वाले कॉलम नामों कोsuffixesसे संभालना - merge की सामान्य त्रुटियों को पहचानना — की-टाइप का बेमेल होना, की-कॉलम का गायब होना — और उन अदृश्य गलतियों को पकड़ना जो कोई एरर नहीं देतीं
ज़रूरी शर्तें: कॉलम जोड़ना।
पहले फ़ाइलें बना लें
इस अध्याय में चार छोटी फ़ाइलों का उपयोग किया गया है। इन्हें अपनी स्क्रिप्ट के पास ही रखें।
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.0orders_april.csv — अप्रैल, वही कॉलम। स्टेपलर (stapler) पर ध्यान दें: दुकान ने इसे अप्रैल में बेचना शुरू किया।
order_id,date,branch,product,category,quantity,price
1009,2024-04-01,north,pen,stationery,15,15.0
1010,2024-04-01,east,notebook,stationery,3,60.0
1011,2024-04-02,south,stapler,stationery,6,95.0
1012,2024-04-02,north,bag,accessories,1,850.0products.csv — प्रति उत्पाद एक पंक्ति, क्रय टीम द्वारा रखी गई। वास्तविक जीवन की तरह यहाँ भी जानबूझकर दो चीजें "अधूरी" हैं: नया स्टेपलर अभी इसमें नहीं जोड़ा गया है, और एक मार्कर (marker) ऐसा है जिसे दुकान ने कभी नहीं बेचा।
product,cost,supplier
pen,9.0,Alpha
notebook,40.0,Alpha
bag,600.0,Bravo
bottle,80.0,Bravo
eraser,5.0,Alpha
marker,25.0,Alphabranches.csv — दुकान की तीनों शाखाओं का प्रबंधन कौन करता है। इस कॉलम का नाम branch नहीं बल्कि branch_name है, क्योंकि यह फ़ाइल किसी अन्य व्यक्ति ने बनाई थी।
branch_name,manager
north,Mira
south,Omar
east,Lenaकोड लिखने से पहले
टेबल्स को जोड़ना वह चरण है जहाँ जल्दबाज़ी में लिखा कोड अक्सर सबसे ज़्यादा आत्मविश्वास से भरा गलत उत्तर देता है। इसलिए योजना पहले बनती है, और इसमें ज़्यादातर वे सवाल होते हैं जिनका जवाब आप डेटा देखकर तय करते हैं।
1. सवाल को एक वाक्य में कहें
"मार्च और अप्रैल मिलाकर सप्लायर प्रति लाभ।" यह एक वाक्य ही अंतिम टेबल का स्वरूप बता देता है: प्रति सप्लायर एक पंक्ति, प्रति पंक्ति एक संख्या। दो सप्लायर हैं, Alpha और Bravo, इसलिए परिणाम में लगभग दो पंक्तियाँ होनी चाहिए। यदि तीन या एक पंक्ति बनती है, तो रास्ते में कुछ गड़बड़ हुई है।
2. तय करें कि प्रत्येक टेबल-जोड़ी को किस ऑपरेशन की ज़रूरत है
जोड़ने के केवल दो मुख्य प्रकार हैं, और परीक्षण बहुत सरल है: क्या दोनों टेबल्स में एक ही प्रकार की पंक्तियाँ हैं, या एक ही चीज़ के अलग-अलग विवरण हैं?
- समान कॉलम, अलग पंक्तियाँ — मार्च ऑर्डर्स + अप्रैल ऑर्डर्स।
pd.concat([a, b])का उपयोग करें। परिणाम नीचे की ओर बढ़ता है: अधिक पंक्तियाँ, वही कॉलम। - एक साझा 'की' (key) कॉलम, अलग विवरण — ऑर्डर्स + उत्पाद, जो
productसे जुड़े हैं।pd.merge(a, b, on="product")का उपयोग करें। परिणाम दाएं की ओर बढ़ता है: पंक्तियों की वही संख्या, अधिक कॉलम।
मार्च और अप्रैल दोनों "ऑर्डर्स" हैं — वही कॉलम, अधिक पंक्तियाँ — इसलिए उन्हें ऊपर-नीचे जोड़ा जाता है (stack)। ऑर्डर्स और उत्पाद अलग-अलग चीज़ें हैं जो उत्पाद के नाम से जुड़ी हैं, इसलिए उनका मिलान किया जाता है (match)।
3. जोड़ने से पहले हर टेबल को देखें
आपने अध्याय तीन में सीखा था कि फ़ाइल पढ़ते ही उसकी जांच कर लेनी चाहिए। कई फ़ाइलों के साथ यह आदत अनिवार्य हो जाती है: परिणाम के आकार का अनुमान लगाने के लिए आपको हर एक टेबल के आकार (shape) की जानकारी चाहिए।
import pandas as pd
march = pd.read_csv("orders.csv")
april = pd.read_csv("orders_april.csv")
products = pd.read_csv("products.csv")
for name, df in [("march", march), ("april", april), ("products", products)]:
print(name, df.shape, list(df.columns))march (8, 7) ['order_id', 'date', 'branch', 'product', 'category', 'quantity', 'price']
april (4, 7) ['order_id', 'date', 'branch', 'product', 'category', 'quantity', 'price']
products (6, 3) ['product', 'cost', 'supplier']मार्च और अप्रैल दोनों में बिल्कुल वही सात कॉलम उसी क्रम में हैं — ऊपर-नीचे जोड़ने के लिए बिल्कुल उपयुक्त। products उनके साथ ठीक एक कॉलम नाम साझा करता है: product। यही वह की (key) है, जिस कॉलम के आधार पर मिलान किया जाएगा।
जांचें कि 'की' का प्रकार (dtype) दोनों तरफ समान है या नहीं। एक ऐसी 'की' जो एक टेबल में संख्या हो और दूसरी में टेक्स्ट, कभी मेल नहीं खा सकती:
print(march["product"].dtype, products["product"].dtype)str strदोनों str हैं। इनकी तुलना की जा सकती है।
4. जांचें कि लुकअप टेबल की 'की' (key) यूनिक (unique) है
यह इस पूरे अध्याय की सबसे महत्वपूर्ण जांच है। प्रत्येक ऑर्डर के लिए एक लागत की आवश्यकता है। यदि products.csv में pen दो बार सूचीबद्ध है, तो पेन का प्रत्येक ऑर्डर दो बार मेल खाएगा और परिणाम में दो बार दिखाई देगा।
print(products["product"].is_unique)
print(products["product"].duplicated().sum())True
0is_unique का मान True है और शून्य डुप्लिकेट हैं: प्रति उत्पाद एक पंक्ति। प्रत्येक ऑर्डर अधिकतम एक उत्पाद पंक्ति से मेल खा सकता है।
5. जांचें कि किन 'कीज़' (keys) का कोई साथी नहीं मिलेगा
merge करने से पहले पूछें: क्या ऐसे ऑर्डर्स हैं जिनका उत्पाद products.csv में नहीं है, और ऐसे उत्पाद जिनका किसी ने ऑर्डर नहीं दिया? फ़िल्टरिंग अध्याय का isin दोनों का उत्तर देता है:
orders = pd.concat([march, april], ignore_index=True)
no_cost = ~orders["product"].isin(products["product"])
print(orders.loc[no_cost, ["order_id", "product"]])
never_sold = ~products["product"].isin(orders["product"])
print(products.loc[never_sold, "product"].tolist())order_id product
10 1011 stapler
['marker']एक ऑर्डर — स्टेपलर, 1011 — का रिकॉर्ड में कोई लागत मूल्य नहीं है। एक उत्पाद, मार्कर, कभी नहीं बिका। अब आप merge करने से पहले जानते हैं कि merge को किन परिस्थितियों से निपटना होगा।
6. परिणाम का अनुमान लगाएं, फिर निर्णय लें
अनुमान को संख्याओं में लिख लें:
ordersमें 8 + 4 = 12 पंक्तियाँ हैं।productsमें प्रति उत्पाद एक पंक्ति है, इसलिए मिलान ऑर्डर्स की संख्या बढ़ा नहीं सकता।- यदि प्रत्येक ऑर्डर रखा जाता है, तो मर्ज की गई टेबल में 12 पंक्तियाँ होंगी, और उनमें से एक (स्टेपलर) की लागत नहीं होगी।
- यदि केवल मेल खाने वाले ऑर्डर्स रखे जाते हैं, तो इसमें 11 पंक्तियाँ होंगी।
आप क्या चाहते हैं? लाभ की रिपोर्ट के लिए स्टेपलर ऑर्डर को चुपचाप हटा देने से बिक्री कम दिखाई देगी। बेहतर है कि उसे रखा जाए, देखा जाए कि उसकी लागत गायब है, और निर्णय लिया जाए — जो कि ठीक वही विकल्प है जिसे how= आर्ग्युमेंट तय करता है। merge के बाद, सबसे पहली चीज़ जो आप प्रिंट करते हैं वह आकार (shape) है, और आप उसकी तुलना इस अनुमान से करते हैं।
यही पूरी योजना है: एक वाक्य का सवाल, प्रत्येक जोड़ी के लिए ऑपरेशन, प्रत्येक टेबल का मुआयना, 'की' की विशिष्टता, बेमेल कीज़, और अपेक्षित पंक्तियों की संख्या। अध्याय का शेष भाग वे उपकरण हैं जो इसे क्रियान्वित करते हैं।
pd.concat के साथ पंक्तियों को ऊपर-नीचे जोड़ना (Stacking)
pd.concat टेबल्स की एक लिस्ट लेता है और उन्हें एक के नीचे एक रख देता है:
import pandas as pd
march = pd.read_csv("orders.csv")
april = pd.read_csv("orders_april.csv")
both = pd.concat([march, april])
print(both.shape)
print(both[["order_id", "date", "product", "quantity"]])(12, 7)
order_id date product quantity
0 1001 2024-03-01 pen 12
1 1002 2024-03-01 notebook 5
2 1003 2024-03-02 bag 2
3 1004 2024-03-02 bottle 7
4 1005 2024-03-03 eraser 30
5 1006 2024-03-03 bag 1
6 1007 2024-03-04 pen 20
7 1008 2024-03-04 bottle 4
0 1009 2024-04-01 pen 15
1 1010 2024-04-01 notebook 3
2 1011 2024-04-02 stapler 6
3 1012 2024-04-02 bag 1बारह पंक्तियाँ, वही सात कॉलम। पांडास ने कॉलमों को उनकी स्थिति के बजाय नाम के आधार पर संरेखित किया — यदि अप्रैल की फ़ाइल में कॉलम किसी अन्य क्रम में होते, तब भी वे सही शीर्षकों के नीचे ही आते।
लेकिन बाईं ओर इंडेक्स देखें। इंडेक्स 0 से 7 तक चलता है, फिर दोबारा 0 से शुरू होता है। concat ने प्रत्येक टेबल का अपना इंडेक्स सुरक्षित रखा, इसलिए लेबल 0 से 3 अब दो बार दिखाई दे रहे हैं। यह सिर्फ दिखने की समस्या नहीं है:
print(both.loc[0, ["order_id", "date"]])order_id date
0 1001 2024-03-01
0 1009 2024-04-01आपने पंक्ति 0 मांगी और आपको दो पंक्तियाँ मिलीं। कोई भी कोड जो यह मानकर चलता है कि एक लेबल का मतलब एक पंक्ति है — जैसे loc, इंडेक्स पर बाद का कोई merge, या किसी एक सेल को अपडेट करना — अब कुछ ऐसा करेगा जो आपका इरादा नहीं था। आप इसे जांच सकते हैं:
print(both.index.is_unique)Falseignore_index=True — पंक्तियों को दोबारा नंबर देना
जब पुराना इंडेक्स केवल पंक्ति संख्या (एक RangeIndex, जैसा कि अब तक के प्रत्येक read_csv में हुआ है) था, तो उसका कोई विशेष अर्थ नहीं होता, इसलिए उसे छोड़ दें और concat को परिणाम में शून्य से नए नंबर देने दें:
orders = pd.concat([march, april], ignore_index=True)
print(orders.index.is_unique)
print(orders[["order_id", "date", "product"]].tail(5))True
order_id date product
7 1008 2024-03-04 bottle
8 1009 2024-04-01 pen
9 1010 2024-04-01 notebook
10 1011 2024-04-02 stapler
11 1012 2024-04-02 bagअब लेबल 0 से 11 तक चलते हैं, प्रत्येक ठीक एक बार। फ़ाइलों को जोड़ते समय ignore_index=True को अपना डिफ़ॉल्ट नियम बना लें; इसे केवल तभी छोड़ें जब इंडेक्स का कोई खास अर्थ हो (उदाहरण के लिए, set_index("order_id") के बाद, जहाँ ऑर्डर आईडी पहले से ही यूनिक हैं)।
ट्रैक रखें कि हर पंक्ति कहाँ से आई है
जोड़ने के बाद टेबल में ऐसा कुछ नहीं होता जिससे पता चले कि कोई पंक्ति किस महीने की है। यहाँ तारीख से पता चल रहा है, लेकिन अक्सर ऐसा नहीं होता। कॉनकेटेनेट करने से पहले एक कॉलम जोड़ें — पिछले अध्याय की तकनीक — और यह जानकारी पंक्तियों के साथ सुरक्षित रहेगी:
march["month"] = "March"
april["month"] = "April"
orders = pd.concat([march, april], ignore_index=True)
print(orders["month"].value_counts())month
March 8
April 4
Name: count, dtype: int64यह गणना जांच का भी काम करती है: 8 + 4 = 12, कुछ भी नहीं खोया।
जब कॉलम मेल नहीं खाते
Concat कॉलमों को नाम से मिलाता है। जो कॉलम केवल कुछ टेबल्स में मौजूद होता है, वह भी परिणाम में दिखाई देता है — और जिन टेबल्स में वह नहीं था उनकी पंक्तियों में वहाँ NaN आ जाता है। मान लीजिए कि एक विशेष ऑर्डर हाथ से टाइप किया गया था, जिसमें discount कॉलम था लेकिन date, product, category या price नहीं थे:
manual = pd.DataFrame({
"order_id": [2001],
"branch": ["north"],
"quantity": [3],
"discount": [0.1],
})
mixed = pd.concat([march.head(2), manual], ignore_index=True)
print(mixed[["order_id", "branch", "price", "discount"]])
print(mixed.isna().sum())order_id branch price discount
0 1001 north 15.0 NaN
1 1002 south 60.0 NaN
2 2001 north NaN 0.1
order_id 0
date 1
branch 0
product 1
category 1
quantity 0
price 1
month 1
discount 2
dtype: int64कोई एरर नहीं। परिणाम में सभी कॉलमों का संघ (union) बनता है, और रिक्त स्थान NaN से भर जाते हैं। इसीलिए कॉनकेटेनेट करने के बाद भी उसी isna().sum() की आवश्यकता होती है जो आपने मिसिंग डेटा वाले अध्याय में सीखी थी: किसी एक फ़ाइल में अलग तरह से लिखा गया कॉलम नाम (जैसे quantity के मुकाबले Quantity) फेल नहीं होता, बल्कि दो आधे-खाली कॉलम बना देता है। यदि concat के बाद कॉलमों की संख्या किसी भी इनपुट से अधिक हो जाती है, तो समझ लें कि कुछ कॉलम ठीक से मेल नहीं खाए।
axis=1 — और आमतौर पर आपको इसकी ज़रूरत क्यों नहीं होती
pd.concat([a, b], axis=1) टेबल्स को ऊपर-नीचे जोड़ने के बजाय अगल-बगल रखता है, और पंक्तियों का मिलान इंडेक्स लेबल के आधार पर करता है। "ऑर्डर्स में उत्पाद के कॉलम जोड़ें" के लिए यह आकर्षक लग सकता है, लेकिन यह उत्पाद के नाम को बिल्कुल नहीं देखता — एक टेबल की पंक्ति 0 दूसरी टेबल की पंक्ति 0 के बगल में आ जाती है, भले ही उनमें कुछ भी हो। किसी कॉलम पर मिलान करने के लिए हमेशा merge का उपयोग करें।
pd.merge के साथ पंक्तियों का मिलान करना
merge दो टेबल्स और एक 'की' (key) लेता है। बाईं टेबल की प्रत्येक पंक्ति के लिए, यह दाईं टेबल में समान 'की' मान वाली पंक्तियाँ ढूंढता है, और उनके कॉलमों को एक साथ जोड़ देता है:
products = pd.read_csv("products.csv")
merged = pd.merge(orders, products, on="product")
print(merged.shape)
print(merged[["order_id", "product", "quantity", "price", "cost", "supplier"]])(11, 10)
order_id product quantity price cost supplier
0 1001 pen 12 15.0 9.0 Alpha
1 1002 notebook 5 60.0 40.0 Alpha
2 1003 bag 2 850.0 600.0 Bravo
3 1004 bottle 7 120.0 80.0 Bravo
4 1005 eraser 30 8.0 5.0 Alpha
5 1006 bag 1 850.0 600.0 Bravo
6 1007 pen 20 15.0 9.0 Alpha
7 1008 bottle 4 120.0 80.0 Bravo
8 1009 pen 15 15.0 9.0 Alpha
9 1010 notebook 3 60.0 40.0 Alpha
10 1012 bag 1 850.0 600.0 Bravoप्रत्येक ऑर्डर के पास अब उसकी लागत और सप्लायर आ गए हैं, बिना किसी लूप के और बिना इस बात की परवाह किए कि किसी फ़ाइल में पंक्तियों का क्रम क्या था। 'की' कॉलम product एक ही बार आता है, क्योंकि यह दोनों तरफ समान था।
अब आकार की तुलना योजना से करें। हमने सब कुछ सुरक्षित रहने पर 12 पंक्तियों का अनुमान लगाया था — और हमारे पास 11 हैं। स्टेपलर ऑर्डर 1011 गायब हो गया है। कोई चेतावनी नहीं, कोई एरर नहीं।
यह कोई बग नहीं है। यह डिफ़ॉल्ट प्रकार का merge है, how="inner", जो वही कर रहा है जिसके लिए उसे परिभाषित किया गया है: केवल वही कीज़ रखना जो दोनों टेबल्स में पाई जाती हैं। स्टेपलर products में नहीं है, इसलिए उसका ऑर्डर नहीं बचता; मार्कर orders में नहीं है, इसलिए वह भी नहीं दिखता। यदि आपने 12 पंक्तियों का पहले से अनुमान न लगाया होता, तो आपको इस छूटी हुई बिक्री का कभी पता ही नहीं चलता।
चार प्रकार के merge
how= आर्ग्युमेंट यह तय करता है कि उन पंक्तियों का क्या होगा जिनकी 'की' का दूसरी तरफ कोई साथी नहीं है। कीज़ के दो सेटों की कल्पना करें:
how="inner"(डिफ़ॉल्ट) केवल दोनों टेबल्स में मिलने वाली कीज़ रखता है। दोनों तरफ की बेमेल पंक्तियाँ हटा दी जाती हैं। जब आपको केवल पूर्ण रिकॉर्ड चाहिए हों तब इसका उपयोग करें।how="left"बाईं टेबल की प्रत्येक पंक्ति को सुरक्षित रखता है। बिना साथी वाली बाईं पंक्तियों को दाईं टेबल के कॉलमों मेंNaNमिलता है; बेमेल दाईं पंक्तियाँ हटा दी जाती हैं। मुख्य टेबल में नई जानकारी जोड़ने के लिए इसका उपयोग करें — यह सबसे आम विकल्प है।how="right"इसका उल्टा रूप है: दाईं टेबल की प्रत्येक पंक्ति रखी जाती है, बेमेल बाईं पंक्तियाँ हटा दी जाती हैं।how="outer"दोनों तरफ की प्रत्येक 'की' रखता है, और रिक्तियों कोNaNसे भरता है। दो सूचियों का आपस में मिलान और ऑडिट करने के लिए इसका उपयोग करें।
खुद से पूछने वाला सवाल यह है: किस टेबल की पंक्तियाँ किसी भी हाल में गायब नहीं होनी चाहिए? यहाँ, ऑर्डर्स टेबल। प्रत्येक ऑर्डर एक वास्तविक बिक्री है; उत्पाद फ़ाइल केवल एक लुकअप है। इसलिए सही merge एक left merge है, जिसमें ऑर्डर्स बाईं ओर हों:
merged = pd.merge(orders, products, on="product", how="left")
print(merged.shape)
print(merged[["order_id", "product", "cost", "supplier"]])(12, 10)
order_id product cost supplier
0 1001 pen 9.0 Alpha
1 1002 notebook 40.0 Alpha
2 1003 bag 600.0 Bravo
3 1004 bottle 80.0 Bravo
4 1005 eraser 5.0 Alpha
5 1006 bag 600.0 Bravo
6 1007 pen 9.0 Alpha
7 1008 bottle 80.0 Bravo
8 1009 pen 9.0 Alpha
9 1010 notebook 40.0 Alpha
10 1011 stapler NaN NaN
11 1012 bag 600.0 Bravoबारह पंक्तियाँ, जैसा कि अनुमान लगाया गया था। स्टेपलर का ऑर्डर अभी भी मौजूद है, लागत और सप्लायर के लिए NaN के साथ — जो कि पूरी तरह पारदर्शी है: लागत अज्ञात है, और आप इसे स्पष्ट रूप से देख सकते हैं। एक दिखाई देने वाली अनुपलब्ध वैल्यू (missing value) उस पूरी तरह गायब पंक्ति से लाख गुना बेहतर है जिसे आप देख ही नहीं सकते। यहाँ से मिसिंग डेटा अध्याय के उपकरण लागू होते हैं: आप isna().sum() से रिक्तियों की गिनती कर सकते हैं, क्रय टीम से पता चलने पर लागत भर सकते हैं, या इस ऑर्डर की अलग से रिपोर्ट कर सकते हैं।
एक right merge इसका ठीक उल्टा दर्पण है — हर उत्पाद सुरक्षित रहता है, और बिना किसी ऑर्डर वाला मार्कर भी शामिल होता है:
right = pd.merge(orders, products, on="product", how="right")
print(right.shape)
print(right[["order_id", "product", "cost"]].tail(3))(12, 10)
order_id product cost
9 1008.0 bottle 80.0
10 1005.0 eraser 5.0
11 NaN marker 25.0ध्यान दें कि order_id पूर्ण संख्याओं से बदलकर 1008.0, 1005.0 इत्यादि हो गया। मार्कर की पंक्ति में कोई ऑर्डर आईडी नहीं है, इसलिए कॉलम में अब एक NaN आ गया है — और जैसा कि आपने मिसिंग डेटा वाले अध्याय में देखा था, जिस पूर्णांक कॉलम में NaN आता है वह float64 बन जाता है। यदि merge के बाद अचानक आईडी के पीछे .0 जुड़ जाए, तो समझ लें कि कुछ पंक्तियों को अपना साथी नहीं मिला।
indicator=True — देखें कि हर पंक्ति कहाँ से आई है
एक outer merge दोनों तरफ की हर चीज़ को बनाए रखता है। indicator=True जोड़ें और पांडास एक नया कॉलम _merge जोड़ देता है, जो प्रत्येक पंक्ति के लिए बताता है कि उसकी 'की' दोनों टेबल्स में पाई गई (both), केवल बाईं ओर (left_only), या केवल दाईं ओर (right_only):
audit = pd.merge(orders, products, on="product", how="outer", indicator=True)
print(audit[["order_id", "product", "cost", "_merge"]])
print(audit["_merge"].value_counts())order_id product cost _merge
0 1003.0 bag 600.0 both
1 1006.0 bag 600.0 both
2 1012.0 bag 600.0 both
3 1004.0 bottle 80.0 both
4 1008.0 bottle 80.0 both
5 1005.0 eraser 5.0 both
6 NaN marker 25.0 right_only
7 1002.0 notebook 40.0 both
8 1010.0 notebook 40.0 both
9 1001.0 pen 9.0 both
10 1007.0 pen 9.0 both
11 1009.0 pen 9.0 both
12 1011.0 stapler NaN left_only
_merge
both 11
left_only 1
right_only 1
Name: count, dtype: int64यह इस merge का अपना रिपोर्ट कार्ड है। ग्यारह पंक्तियाँ मेल खा गईं; एक ऑर्डर का कोई उत्पाद रिकॉर्ड नहीं है (left_only: स्टेपलर); एक उत्पाद का कोई ऑर्डर नहीं है (right_only: मार्कर)। यह वही जानकारी है जो योजना के चरण 5 में थी, जिसे खुद merge ऑपरेशन ने तैयार कर दिया है।
Outer merge ने ऑर्डर्स के मूल क्रम को बनाए रखने के बजाय पंक्तियों को 'की' के आधार पर सॉर्ट भी कर दिया — bag, bottle, eraser, …। Inner और left merge बाईं टेबल का क्रम बनाए रखते हैं; outer इसका वादा नहीं करता। यदि क्रम महत्वपूर्ण है, तो अध्याय सात की तरह merge के बाद सॉर्ट करें।
_merge कॉलम साधारण डेटा है, इसलिए आप इस पर फ़िल्टर लगा सकते हैं। केवल समस्याओं को सूचीबद्ध करना एक लाइन का काम है:
problems = audit[audit["_merge"] != "both"]
print(problems[["order_id", "product", "_merge"]])order_id product _merge
6 NaN marker right_only
12 1011.0 stapler left_onlyजब भी आप ऐसा डेटा merge करें जो आपने खुद तैयार नहीं किया है, तो इस प्रकार का ऑडिट ज़रूर चलाएं। जब समस्याओं की सूची खाली हो, तो आपके पास इस बात का प्रमाण होता है कि दोनों फ़ाइलें पूरी तरह सहमत हैं।
अदृश्य ख़तरा: डुप्लिकेट कीज़ (Duplicate keys)
अब तक products में प्रति उत्पाद एक पंक्ति थी। मान लीजिए कि क्रय टीम थोड़ी अधिक लागत पर पेन का दूसरा सप्लायर जोड़ती है और सूची में एक पंक्ति जोड़ देती है, जिससे pen दो बार आ जाता है:
products_dup = pd.concat(
[products, pd.DataFrame({"product": ["pen"], "cost": [10.0], "supplier": ["Gamma"]})],
ignore_index=True,
)
print(products_dup["product"].is_unique)
exploded = pd.merge(march, products_dup, on="product", how="left")
print(march.shape, "->", exploded.shape)
print(exploded[["order_id", "product", "quantity", "cost", "supplier"]])False
(8, 8) -> (10, 10)
order_id product quantity cost supplier
0 1001 pen 12 9.0 Alpha
1 1001 pen 12 10.0 Gamma
2 1002 notebook 5 40.0 Alpha
3 1003 bag 2 600.0 Bravo
4 1004 bottle 7 80.0 Bravo
5 1005 eraser 30 5.0 Alpha
6 1006 bag 1 600.0 Bravo
7 1007 pen 20 9.0 Alpha
8 1007 pen 20 10.0 Gamma
9 1008 bottle 4 80.0 Bravoमार्च में 8 ऑर्डर्स थे; merge ने 10 पंक्तियाँ लौटा दीं। ऑर्डर 1001 और 1007 — पेन के दो ऑर्डर्स — प्रत्येक दो बार दिखाई देते हैं, प्रति पेन पंक्ति एक बार। कुछ भी विफल नहीं हुआ। लेकिन अब मात्रा का योग करें और पेन दो बार गिने गए हैं: 32 अतिरिक्त पेन जो कभी बेचे ही नहीं गए।
print(march["quantity"].sum(), exploded["quantity"].sum())81 11381 के मुकाबले 113। इसे रो विस्फोट (row explosion) कहा जाता है, और यह सबसे नुकसानदेह merge त्रुटि है क्योंकि इसका परिणाम बिल्कुल सामान्य दिखता है। इसके पीछे का नियम: merge बाईं ओर की प्रत्येक मेल खाने वाली पंक्ति को दाईं ओर की प्रत्येक मेल खाने वाली पंक्ति के साथ जोड़ता है। One-to-one या many-to-one में पंक्तियों की संख्या समान रहती है; लेकिन दोनों तरफ दोहराई गई 'की' इसे गुणा कर देती है।
पंक्तियाँ बढ़ाने वाला merge कभी खुद से यह बात नहीं बताता। इसका एकमात्र लक्षण वह कुल योग है जो बहुत बड़ा होता है, और बहुत बड़ा कुल योग शायद ही कभी गलत नज़र आता है। हर merge से पहले और बाद में len() की तुलना करें — यही वह एकमात्र जांच है जो इसे पकड़ती है।validate= — पांडास से खुद जांच करवाएं
आप उस संबंध को कोड में स्पष्ट रूप से बता सकते हैं जिसकी आप अपेक्षा करते हैं, और यदि डेटा उस नियम को तोड़ता है तो पांडास merge करने से मना कर देगा। कई ऑर्डर्स एक ही उत्पाद साझा करते हैं, इसलिए यह merge many-to-one होना चाहिए:
try:
pd.merge(march, products_dup, on="product", how="left", validate="many_to_one")
except Exception as error:
print(type(error).__name__ + ":", error)MergeError: Merge keys are not unique in right dataset; not a many-to-one merge
Duplicates in right:
product
pen ...एक MergeError, और यह दोषी 'की' को भी सूचीबद्ध करता है। अन्य संभावित मान "one_to_one", "one_to_many" और "many_to_many" हैं। validate= लिखने में कोई अतिरिक्त प्रयास नहीं लगता और यह एक मूक गलत उत्तर को एक स्पष्ट एरर में बदल देता है, इसलिए लुकअप टेबल के साथ हर merge में इसे ज़रूर शामिल करें।
समाधान: तय करें कि कौन सा डुप्लिकेट सही है
एरर आपको यह बताता है कि कुछ गलत है; लेकिन क्या सही है यह तय करना आपका काम है। पहले डुप्लिकेट्स को देखें:
print(products_dup[products_dup["product"].duplicated(keep=False)])product cost supplier
0 pen 9.0 Alpha
6 pen 10.0 Gammaduplicated(keep=False) प्रत्येक प्रति को चिह्नित करता है, केवल दूसरी को नहीं। अब एक व्यावसायिक निर्णय लेना होगा। यदि नवीनतम पंक्ति पुरानी पंक्ति का स्थान लेती है, तो अंतिम प्रति रखें:
products_clean = products_dup.drop_duplicates(subset="product", keep="last")
fixed = pd.merge(march, products_clean, on="product", how="left", validate="many_to_one")
print(fixed.shape)
print(fixed.loc[fixed["product"] == "pen", ["order_id", "cost", "supplier"]])(8, 10)
order_id cost supplier
0 1001 10.0 Gamma
6 1007 10.0 Gammaवापस 8 पंक्तियों पर, प्रति ऑर्डर एक। यदि इसके बजाय दोनों सप्लायर वास्तव में पेन बेचते हैं और किसी ऑर्डर में यह दर्ज होता है कि किस सप्लायर का पेन है, तो supplier ऑर्डर्स में भी होना चाहिए, और merge की 'की' दो कॉलम बन जाएगी: on=["product", "supplier"]। कोड यह निर्णय नहीं ले सकता; योजना ले सकती है।
जब की (key) कॉलम के नाम अलग हों
branches.csv अपने कॉलम को branch_name कहता है, जबकि ऑर्डर्स उसे branch कहते हैं। on= को दोनों तरफ एक ही नाम चाहिए होता है, इसलिए left_on= और right_on= का उपयोग करके प्रत्येक पक्ष का अलग-अलग नाम दें:
branches = pd.read_csv("branches.csv")
with_manager = pd.merge(orders, branches, left_on="branch", right_on="branch_name", how="left")
print(list(with_manager.columns))['order_id', 'date', 'branch', 'product', 'category', 'quantity', 'price', 'month', 'branch_name', 'manager']दोनों 'की' कॉलम रखे जाते हैं, क्योंकि पांडास यह नहीं जान सकता कि आप उन्हें एक ही कॉलम मानते हैं। branch और branch_name में अब एक जैसे मान हैं, इसलिए एक को हटा दें:
with_manager = with_manager.drop(columns="branch_name")
print(with_manager[["order_id", "branch", "manager"]].head(4))order_id branch manager
0 1001 north Mira
1 1002 south Omar
2 1003 north Mira
3 1004 east Lenaइसका दूसरा विकल्प merge से पहले नाम बदलना है — branches.rename(columns={"branch_name": "branch"}) — और फिर सीधे on="branch" का उपयोग करना। दोनों ही तरीके अच्छे हैं; पहले नाम बदलने से बाद में कॉलम हटाने की ज़रूरत नहीं पड़ती।
जब दोनों टेबल्स में एक ही नाम का कॉलम हो
यदि कोई ऐसा कॉलम जो 'की' नहीं है दोनों टेबल्स में मौजूद हो, तो पांडास एक टेबल में price नाम के दो कॉलम नहीं रख सकता, इसलिए वह उनके नाम में _x (बाईं ओर) और _y (दाईं ओर) प्रत्यय जोड़कर बदल देता है। मान लीजिए एक सूची-मूल्य टेबल है जो अपने कॉलम को भी price कहती है:
list_prices = pd.DataFrame({"product": ["pen", "bag"], "price": [14.0, 800.0]})
compared = pd.merge(march, list_prices, on="product")
print(compared[["order_id", "product", "price_x", "price_y"]])order_id product price_x price_y
0 1001 pen 15.0 14.0
1 1003 bag 850.0 800.0
2 1006 bag 850.0 800.0
3 1007 pen 15.0 14.0price_x और price_y काम तो करते हैं, लेकिन तीन हफ़्ते बाद किसी को याद नहीं रहेगा कि कौन सा क्या है। suffixes= के साथ उन्हें खुद स्पष्ट नाम दें:
compared = pd.merge(march, list_prices, on="product", suffixes=("_sold", "_list"))
print(compared[["order_id", "price_sold", "price_list"]])order_id price_sold price_list
0 1001 15.0 14.0
1 1003 850.0 800.0
2 1006 850.0 800.0
3 1007 15.0 14.0अब कॉलम अपना स्पष्ट अर्थ बताते हैं, और प्रत्येक ऑर्डर पर दी गई छूट बस price_list - price_sold है। (बाद में जो कोड compared["price"] मांगेगा उसे KeyError मिलेगा, क्योंकि merge के बाद किसी भी कॉलम का वह सटीक नाम नहीं रह जाता — इसलिए नाम सोच-समझकर चुनना ज़रूरी है।)
join — इंडेक्स के आधार पर संक्षेप में merge करना
DataFrames में एक .join() मेथड भी होता है। यह एक ऐसा merge है जो दाईं टेबल के इंडेक्स को अपनी 'की' के रूप में उपयोग करता है — यह तब सुविधाजनक होता है जब लुकअप टेबल पहले से ही 'की' द्वारा इंडेक्स की गई हो:
by_product = products.set_index("product")
joined = orders.join(by_product, on="product")
print(joined[["order_id", "product", "cost", "supplier"]].head(3))order_id product cost supplier
0 1001 pen 9.0 Alpha
1 1002 notebook 40.0 Alpha
2 1003 bag 600.0 Bravojoin डिफ़ॉल्ट रूप से how="left" पर काम करता है। यह वही काम करता है जो pd.merge(..., how="left"); लेकिन merge अधिक व्यापक उपकरण है, और इस पूरे कोर्स में उसी का उपयोग किया गया है।
एक संपूर्ण उदाहरण
वापस दुकान मालिक के सवाल पर आते हैं: मार्च और अप्रैल में प्रत्येक सप्लायर से मिलने वाला लाभ, जिसमें हर एक ऑर्डर का सही हिसाब हो।
supplier_profit.py:
import pandas as pd
march = pd.read_csv("orders.csv")
april = pd.read_csv("orders_april.csv")
products = pd.read_csv("products.csv")
# 1. Stack the two months, remembering where each row came from.
march["month"] = "March"
april["month"] = "April"
orders = pd.concat([march, april], ignore_index=True)
print("orders:", orders.shape)
# 2. The lookup key must be unique, or the merge will duplicate orders.
assert products["product"].is_unique
# 3. Attach cost and supplier to every order. Keep every order.
sales = pd.merge(
orders, products, on="product", how="left",
validate="many_to_one", indicator=True,
)
print("after merge:", sales.shape)
assert len(sales) == len(orders)
# 4. Report the orders that found no product row, then set them aside.
unmatched = sales[sales["_merge"] == "left_only"]
print("no cost on file:", unmatched["order_id"].tolist(), unmatched["product"].tolist())
sales = sales[sales["_merge"] == "both"].drop(columns="_merge")
# 5. Profit per row, then per supplier and month.
sales["profit"] = sales["quantity"] * (sales["price"] - sales["cost"])
report = (
sales.groupby(["supplier", "month"], as_index=False)["profit"]
.sum()
.sort_values(["supplier", "profit"], ascending=[True, False])
.reset_index(drop=True)
)
print()
print(report)
print()
print(sales.groupby("supplier")["profit"].sum().sort_values(ascending=False))orders: (12, 8)
after merge: (12, 11)
no cost on file: [1011] ['stapler']
supplier month profit
0 Alpha March 382.0
1 Alpha April 150.0
2 Bravo March 1190.0
3 Bravo April 250.0
supplier
Bravo 1440.0
Alpha 532.0
Name: profit, dtype: float64इसे इस तरह क्यों लिखा गया है:
- प्रत्येक संयोजन चरण के बाद आकार (shape) की पुष्टि की गई है।
orders: (12, 8)ऊपर-नीचे जोड़ने की पुष्टि करता है (8 + 4 पंक्तियाँ, 7 कॉलम +month)।after merge: (12, 11)पुष्टि करता है कि कुछ भी गायब या डुप्लिकेट नहीं हुआ — 3 नए कॉलम:cost,supplier,_merge। आकार जांचना इस बात का सबसे सस्ता और प्रभावी प्रमाण है कि प्रत्येक चरण ने योजना के अनुसार काम किया। - मान्यताओं को कोड के रूप में लिखा गया है।
assert products["product"].is_uniqueऔरvalidate="many_to_one"दोनों रो विस्फोट से रक्षा करते हैं;assert len(sales) == len(orders)योजना के अनुमान की रक्षा करता है। यदि कोई अगले महीने की उत्पाद फ़ाइल डुप्लिकेट के साथ भेजता है, तो स्क्रिप्ट बढ़ा-चढ़ाकर लाभ दिखाने के बजाय वहीं रुक जाएगी। how="left"स्टेपलर के ऑर्डर को सुरक्षित रखता है, औरindicator=Trueउसका नाम बताता है। स्क्रिप्ट इसे चुपचाप नहीं हटाती; यह[1011] ['stapler']प्रिंट करती है, ताकि मालिक को पता रहे कि जब तक क्रय टीम लागत नहीं जोड़ती, तब तक अप्रैल के योग में एक बिक्री गायब है। यह पंक्ति उत्तर का हिस्सा है, कोई डिबग कचरा नहीं।monthकॉलमconcatसे पहले जोड़ा गया था। अन्यथा रिपोर्ट में महीने-वार विभाजन के लिए तारीखों को पार्स और रूपांतरित करना पड़ता — जो संभव तो था, लेकिन उसी परिणाम के लिए अधिक मेहनत का काम होता।- लाभ की गणना merge के बाद की जाती है, क्योंकि इसके लिए दोनों फ़ाइलों के कॉलम चाहिए। यह स्वाभाविक क्रम है: संयोजन करें, फिर कॉलम जोड़ें (अध्याय नौ), फिर समूहित करें (अध्याय आठ), फिर सॉर्ट करें (अध्याय सात)।
उत्तर: Bravo ने 1,440 का लाभ दिया और Alpha ने 532, जबकि अप्रैल की एक बिक्री अभी भी अपनी लागत की प्रतीक्षा कर रही है। Bravo कम वस्तुएं बेचता है, लेकिन पेन और इरेज़र की तुलना में बैग और बोतलों पर प्रति वस्तु मार्जिन कहीं अधिक होता है।
जब यह काम न करे
ValueError: You are trying to merge on int64 and str columns for key 'order_id'. If you wish to proceed you should use pd.concat
दोनों तरफ 'की' का डेटा टाइप अलग है। यहाँ एक डिलीवरी फ़ाइल है जिसके ऑर्डर आईडी के आगे # लगाकर निर्यात किया गया था। इसे deliveries.csv के रूप में सहेजें — इसका उपयोग "आपकी बारी" में भी किया गया है। (यदि आपके पास मिसिंग डेटा अध्याय से उसी नाम का राइडर्स लॉग अभी भी है, तो उसे बदल दें; यह एक अलग फ़ाइल है।)
order_id,status
#1001,delivered
#1002,delivered
#1003,returned
#1005,delivered
#1006,pendingimport pandas as pd
march = pd.read_csv("orders.csv")
deliveries = pd.read_csv("deliveries.csv")
print(march["order_id"].dtype, deliveries["order_id"].dtype)
try:
pd.merge(march, deliveries, on="order_id", how="left")
except ValueError as error:
print("ValueError:", error)int64 str
ValueError: You are trying to merge on int64 and str columns for key 'order_id'. If you wish to proceed you should use pd.concat1001 और "#1001" अलग-अलग वैल्यूज़ हैं, और पांडास कोई अनुमान लगाने से मना कर देता है। (pd.concat के बारे में संकेत एक अलग स्थिति के लिए है; इसे अनदेखा करें।) इसका उपाय कीज़ को एक ही प्रकार और एक ही टेक्स्ट में बदलना है — # हटाएं, फिर बदलें:
deliveries["order_id"] = deliveries["order_id"].str.removeprefix("#").astype("int64")
status = pd.merge(march, deliveries, on="order_id", how="left")
print(status[["order_id", "product", "status"]])order_id product status
0 1001 pen delivered
1 1002 notebook delivered
2 1003 bag returned
3 1004 bottle NaN
4 1005 eraser delivered
5 1006 bag pending
6 1007 pen NaN
7 1008 bottle NaNजिन ऑर्डर्स का कोई डिलीवरी रिकॉर्ड नहीं है, उन्हें status के लिए NaN मिलता है — left merge उन्हें सुरक्षित रखता है, जो कि बिल्कुल वही है जो आप तब चाहते हैं जब सवाल हो "कौन से ऑर्डर्स डिलीवर नहीं हुए हैं?"
MergeError: Merge keys are not unique in right dataset; not a many-to-one merge आपने validate="many_to_one" का उपयोग किया और दाईं टेबल में एक 'की' दोहराई गई है। संदेश में डुप्लिकेट्स सूचीबद्ध हैं। एरर को हटाने के लिए validate को न हटाएं — बल्कि duplicated(keep=False) के साथ डुप्लिकेट पंक्तियों को देखें और तय करें कि कौन सी सही है, जैसा कि रो-विस्फोट अनुभाग में दिखाया गया था।
KeyError: 'branch' on= में दिया गया कॉलम किसी एक टेबल में मौजूद नहीं है:
branches = pd.read_csv("branches.csv")
try:
pd.merge(march, branches, on="branch")
except KeyError as error:
print("KeyError:", error)KeyError: 'branch'branches इसे branch_name कहता है। दोनों टेबल्स के लिए list(df.columns) प्रिंट करें; left_on="branch", right_on="branch_name" का उपयोग करें, या एक तरफ का नाम बदलें। कॉलम नाम के अंत में एक अतिरिक्त स्पेस ("branch ") भी यही एरर देता है और केवल लिस्ट रूप में ही दिखाई देता है।
merge तो चल गया, लेकिन नए कॉलम सभी NaN हैं कोई एरर नहीं, और हर पंक्ति बेमेल रही। कीज़ देखने में समान लगती हैं लेकिन होती नहीं हैं: अंत में स्पेस के साथ "north ", बड़े अक्षर के साथ "South", या एक फ़ाइल में टेक्स्ट के रूप में और दूसरी में उपसर्ग (prefix) के साथ वही आईडी। merge करने से पहले isin से जांचें:
branches_messy = pd.DataFrame({"branch_name": ["north ", "South", "east"], "manager": ["Mira", "Omar", "Lena"]})
print(march["branch"].isin(branches_messy["branch_name"]).sum(), "of", len(march), "orders match")
branches_messy["branch_name"] = branches_messy["branch_name"].str.strip().str.lower()
print(march["branch"].isin(branches_messy["branch_name"]).sum(), "of", len(march), "orders match")2 of 8 orders match
8 of 8 orders matchसफाई से पहले आठ में से दो मेल खाते थे, बाद में आठ में से आठ। merge करने से पहले दोनों तरफ 'की' को एक ही तरह से साफ करें — str.strip() और एक समान केस (case)।
merge के बाद पंक्तियों की संख्या बढ़ गई दाईं टेबल में डुप्लिकेट 'की' है; ऊपर "अदृश्य ख़तरा" देखें। हर merge से पहले और बाद में len() की तुलना करें, और validate= का उपयोग करें।
merge के बाद पंक्तियाँ गायब हो गईं आपने डिफ़ॉल्ट how="inner" का उपयोग किया, और कुछ कीज़ का कोई साथी नहीं था। मुख्य टेबल को बाईं ओर रखकर how="left" का उपयोग करें, और कौन सी पंक्तियाँ बेमेल थीं यह देखने के लिए indicator=True का उपयोग करें।
TypeError: concat() takes 1 positional argument but 2 were given
try:
pd.concat(march, deliveries)
except TypeError as error:
print("TypeError:", error)TypeError: concat() takes 1 positional argument but 2 were givenconcat एक आर्ग्युमेंट लेता है, जो कि टेबल्स की एक लिस्ट होती है। pd.concat([march, april]) लिखें — यहाँ वर्गाकार कोष्ठक [] ही मुख्य बात है।
concat के बाद दोहराया गया इंडेक्स, और loc दो पंक्तियाँ लौटाता है प्रत्येक इनपुट ने अपना 0, 1, 2, … बनाए रखा। ignore_index=True जोड़ें।
चरण 4 / 6 — अनुमान
अपनी समझ की जाँच करें
मार्च में 8 ऑर्डर्स हैं और अप्रैल में 4। क्या प्रिंट होगा?
from io import StringIO
import pandas as pd
# Stand in for orders.csv, orders_april.csv and products.csv,
# so this snippet runs on its own.
MARCH = """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
"""
APRIL = """order_id,date,branch,product,category,quantity,price
1009,2024-04-01,north,pen,stationery,15,15.0
1010,2024-04-01,east,notebook,stationery,3,60.0
1011,2024-04-02,south,stapler,stationery,6,95.0
1012,2024-04-02,north,bag,accessories,1,850.0
"""
PRODUCTS = """product,cost,supplier
pen,9.0,Alpha
notebook,40.0,Alpha
bag,600.0,Bravo
bottle,80.0,Bravo
eraser,5.0,Alpha
marker,25.0,Alpha
"""
march = pd.read_csv(StringIO(MARCH))
april = pd.read_csv(StringIO(APRIL))
products = pd.read_csv(StringIO(PRODUCTS))
both = pd.concat([march, april])
print(both.shape, both.index[-1])- A(12, 7) 11
- B(12, 7) 3
- C(12, 14) 3
- D(8, 7) 7
किसी ने उत्पाद सूची में pen के लिए दूसरी पंक्ति जोड़ दी। क्या प्रिंट होगा?
from io import StringIO
import pandas as pd
# Stand in for orders.csv, orders_april.csv and products.csv,
# so this snippet runs on its own.
MARCH = """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
"""
APRIL = """order_id,date,branch,product,category,quantity,price
1009,2024-04-01,north,pen,stationery,15,15.0
1010,2024-04-01,east,notebook,stationery,3,60.0
1011,2024-04-02,south,stapler,stationery,6,95.0
1012,2024-04-02,north,bag,accessories,1,850.0
"""
PRODUCTS = """product,cost,supplier
pen,9.0,Alpha
notebook,40.0,Alpha
bag,600.0,Bravo
bottle,80.0,Bravo
eraser,5.0,Alpha
marker,25.0,Alpha
"""
march = pd.read_csv(StringIO(MARCH))
april = pd.read_csv(StringIO(APRIL))
products = pd.read_csv(StringIO(PRODUCTS))
products = pd.concat([products, pd.DataFrame({"product": ["pen"], "cost": [10.0], "supplier": ["Charlie"]})])
report = pd.merge(march, products, on="product", how="left")
print(len(march), len(report))- A8 8
- B8 6
- C8 10 — प्रत्येक pen ऑर्डर दोनों pen पंक्तियों से मेल खा गया और दोगुना हो गया, बिना किसी एरर के
- Dडुप्लिकेट कीज़ के बारे में एक `MergeError`
मार्च के प्रत्येक ऑर्डर के लिए उसके उत्पाद की लागत (cost) चाहिए, और कोई भी ऑर्डर छूटना नहीं चाहिए, भले ही उत्पाद सूची में न हो। आप कौन सा how= उपयोग करेंगे?
- A`how="inner"`
- B`how="right"`
- C`how="outer"`
- D`how="left"`, जिसमें ऑर्डर्स बाईं (left) टेबल के रूप में हों
उत्तर देने के लिए अकाउंट आवश्यक है
अपने उत्तर जाँचने के लिए साइन इन करें
प्रश्न ऊपर दिए गए हैं, और मन में उत्तर सोचना ही मुख्य कार्य है। सही उत्तर, व्याख्या और तीन-स्तरीय संकेत देखने के लिए साइन इन करें।
आपकी बारी
डिलीवरी टीम ने deliveries.csv भेजा है ("जब यह काम न करे" में दिखाया गया) — जिनके पास रिकॉर्ड है उनके प्रति ऑर्डर एक पंक्ति, जिसमें ऑर्डर आईडी #1001 के रूप में लिखे हैं। मालिक मार्च और अप्रैल दोनों को मिलाकर एक डिलीवरी रिपोर्ट चाहता है।
delivery_report.py इस प्रकार लिखें कि:
- दोनों महीनों का प्रत्येक ऑर्डर सुरक्षित रहे — प्रति ऑर्डर ठीक एक पंक्ति, पंक्तियों की संख्या पर
assertसे जांची गई, और merge कोvalidate=से सुरक्षित तथाindicator=Trueसे ऑडिट किया गया हो। - कीज़ वास्तव में मेल खाती हों — डिलीवरी की को
order_idके समान प्रकार में साफ किया गया हो, और यदि डिलीवरी फ़ाइल में कोई ऑर्डर दो बार दिखाई दे तो स्क्रिप्ट रुक जाए। - लापता का मतलब "कोई रिकॉर्ड नहीं" हो — बिना डिलीवरी पंक्ति वाले ऑर्डर्स को स्टेटस
"no record"मिले, कभी भी मूकNaNनहीं। - यह इस क्रम में प्रिंट करे: स्टैक्ड आकार, मर्ज किया गया आकार,
_mergeकी गिनती, प्रति स्टेटस ऑर्डर्स की संख्या, बिना रिकॉर्ड वाले ऑर्डर्स (order_id,branch,product,order_idद्वारा सॉर्ट किए गए), और प्रति शाखा डिलीवर किए गए ऑर्डर्स की संख्या।
जब यह सही होगा, तो आउटपुट बिल्कुल ऐसा होगा:
orders: (12, 7)
after merge: (12, 9)
_merge
left_only 7
both 5
right_only 0
Name: count, dtype: int64
status
no record 7
delivered 3
returned 1
pending 1
Name: count, dtype: int64
order_id branch product
3 1004 east bottle
6 1007 east pen
7 1008 north bottle
8 1009 north pen
9 1010 east notebook
10 1011 south stapler
11 1012 north bag
branch
north 2
south 1
dtype: int64कोई भी कोड लिखने से पहले, केवल फ़ाइलों को देखकर उस आउटपुट की प्रत्येक संख्या की व्याख्या करें: स्टैकिंग के बाद 12 पंक्तियाँ क्यों, merge के बाद भी 12 क्यों, और बिना रिकॉर्ड वाले 7 ऑर्डर्स क्यों। यदि आप ऐसा नहीं कर सकते, तो योजना अभी पूरी नहीं हुई है।
फिर इसे जानबूझकर दो बार तोड़ें:
.str.removeprefix("#")हटाएं। आपको क्या एरर मिलता है, और कौन सी लाइन इसे उठाती है?deliveries.csvमें#1003के लिए दूसरी पंक्ति जोड़ें (जैसे#1003,delivered)। अबvalidate=क्या करता है? इसके बिना रिपोर्ट क्या दिखाती?
समाधान
योजना। सवाल है: "मार्च और अप्रैल के प्रत्येक ऑर्डर के लिए, उसकी डिलीवरी स्थिति क्या है?" — इसलिए परिणाम में प्रति ऑर्डर एक पंक्ति, कुल 12 पंक्तियाँ होंगी। मार्च और अप्रैल में समान कॉलम हैं: उन्हें concat और ignore_index=True के साथ जोड़ें (8 + 4 = 12)। डिलीवरी फ़ाइल ऑर्डर आईडी द्वारा की गई है, लेकिन # के साथ टेक्स्ट के रूप में, इसलिए पूर्णांक order_id से मेल खाने से पहले 'की' को साफ करने की आवश्यकता है। प्रत्येक ऑर्डर इसमें अधिक से अधिक एक बार दिखाई देना चाहिए, इसलिए संबंध one-to-one है (प्रत्येक ऑर्डर में अधिकतम एक डिलीवरी पंक्ति, प्रत्येक डिलीवरी पंक्ति में एक ऑर्डर)। प्रत्येक ऑर्डर का बचना अनिवार्य है, इसलिए ऑर्डर्स बाईं ओर आते हैं और merge how="left" होता है। पाँच डिलीवरी पंक्तियाँ मौजूद हैं, सभी मार्च के लिए, इसलिए 12 − 5 = 7 ऑर्डर्स बिना किसी रिकॉर्ड के बचेंगे।
delivery_report.py:
import pandas as pd
march = pd.read_csv("orders.csv")
april = pd.read_csv("orders_april.csv")
deliveries = pd.read_csv("deliveries.csv")
# 1. Stack the months.
orders = pd.concat([march, april], ignore_index=True)
print("orders:", orders.shape)
# 2. Clean the key, then check it.
deliveries["order_id"] = deliveries["order_id"].str.removeprefix("#").astype("int64")
assert deliveries["order_id"].is_unique, "an order appears twice in deliveries.csv"
# 3. Keep every order.
report = pd.merge(
orders, deliveries, on="order_id", how="left",
validate="one_to_one", indicator=True,
)
print("after merge:", report.shape)
assert len(report) == len(orders)
print(report["_merge"].value_counts())
# 4. Missing status means the delivery team has no record.
report["status"] = report["status"].fillna("no record")
# 5. Orders per status.
print()
print(report["status"].value_counts())
# 6. Orders with no record.
missing = report.loc[report["status"] == "no record", ["order_id", "branch", "product"]]
print()
print(missing.sort_values("order_id"))
# 7. Delivered orders per branch.
delivered = report[report["status"] == "delivered"]
print()
print(delivered.groupby("branch").size())orders: (12, 7)
after merge: (12, 9)
_merge
left_only 7
both 5
right_only 0
Name: count, dtype: int64
status
no record 7
delivered 3
returned 1
pending 1
Name: count, dtype: int64
order_id branch product
3 1004 east bottle
6 1007 east pen
7 1008 north bottle
8 1009 north pen
9 1010 east notebook
10 1011 south stapler
11 1012 north bag
branch
north 2
south 1
dtype: int64प्रत्येक अनुमान सही सिद्ध हुआ: जोड़ने के बाद 12 पंक्तियाँ, merge के बाद 12, बिना रिकॉर्ड वाली 7। _merge गणना इसकी स्वतंत्र रूप से पुष्टि करती है — 5 both, 7 left_only, 0 right_only (एक left merge कभी भी right_only नहीं बनाता)।
निर्णयों पर नोट्स:
validate="one_to_one", न कि"many_to_one"। ऑर्डर्स टेबल में भी अद्वितीय ऑर्डर आईडी होते हैं, इसलिए दोनों पक्ष अद्वितीय हैं। यह सख्त जांच ऑर्डर्स में भी डुप्लिकेट ऑर्डर आईडी को पकड़ लेगी — उदाहरण के लिए, यदि अप्रैल की फ़ाइल ने गलती से मार्च के किसी ऑर्डर को दोहरा दिया हो।is_uniqueपरassertmerge से पहले आता है, एक संदेश के साथ, ताकि विफलता merge के जटिल शब्दों के बजाय सामान्य भाषा में बताए कि क्या गलत है।fillna("no record")merge के बाद लागू किया जाता है, पहले कभी नहीं: लापता स्थितियाँ केवल तभी मौजूद होती हैं जब डिलीवरी पंक्ति के बिना वाले ऑर्डर्स को left merge द्वारा बनाए रखा जाता है।- ईस्ट (east) शाखा में कोई डिलीवर किया गया ऑर्डर नहीं है, इसलिए यह अंतिम टेबल में बिल्कुल नहीं दिखता। समूहीकरण केवल उन समूहों का उत्पादन करता है जो डेटा में मौजूद हैं — यदि मालिक को पूर्व के लिए 0 की आवश्यकता है, तो वह इस परिणाम के साथ शाखा सूची का merge होगा,
how="left", और एकfillna(0)।
दो गलतियों का परीक्षण। removeprefix के बिना, astype("int64") लाइन पहले ValueError: invalid literal for int() with base 10: '#1001' के साथ विफल हो जाती है, क्योंकि "#1001" को संख्या में नहीं बदला जा सकता — एरर सफाई वाली लाइन की ओर इशारा करता है, जहाँ सुधार होना चाहिए। यदि आप astype भी हटा देते हैं, तो merge स्वयं ValueError: You are trying to merge on int64 and str columns for key 'order_id' उठाता है। दूसरी #1003 पंक्ति के साथ, assert स्क्रिप्ट को AssertionError: an order appears twice in deliveries.csv के साथ रोक देता है; assert निकाल दें और validate="one_to_one" एरर देता है MergeError: Merge keys are not unique in right dataset; not a one-to-one merge। बिना किसी सुरक्षा के, रिपोर्ट 12 ऑर्डर्स के लिए 13 पंक्तियाँ दिखाती और ऑर्डर 1003 को दो बार गिनती — एक बार returned, एक बार delivered — और इसके नीचे का प्रत्येक कुल योग चुपचाप गलत हो जाता।
Step 6 of 6
चुनौती — the chapter quiz
सरल से कठिन — दस प्रश्न, अंतिम वाले जानबूझकर चुनौतीपूर्ण बनाए गए हैं।
Sign in to take the quiz