अध्याय 04

कॉलम और पंक्तियाँ चुनना — नाम से या स्थान से

टेबल के किसी हिस्से की ओर इशारा करना: ब्रैकेट्स से कॉलम, loc से लेबल द्वारा और iloc से पोज़ीशन द्वारा पंक्तियाँ, at से एकल सेल — और हमेशा यह जानना कि आप किस तरीके का उपयोग कर रहे हैं।

45 मिनटPython 3.12
  1. 1समस्या
  2. 2समझें
  3. 3उदाहरण
  4. 4अनुमान
  5. 5स्वयं करें
  6. 6चुनौती

वह समस्या जिसे हम हल कर रहे हैं

आपने दुकान की ऑर्डर फ़ाइल पढ़ ली है और उसकी जाँच भी कर ली है, जैसा कि अध्याय तीन में सिखाया गया था। अब लोग आपसे इसके बारे में सवाल पूछने लगे हैं।

स्टॉक टीम कहती है: "हमें हर ऑर्डर का बस प्रोडक्ट और क्वांटिटी (संख्या) भेज दीजिए — और कुछ नहीं।" अकाउंटेंट कहते हैं: "ऑर्डर 1006 वास्तव में क्या था, और उसकी कीमत क्या थी?" और आपके मैनेजर कहते हैं: "मुझे वो आखिरी तीन ऑर्डर दिखाओ जो आए थे।"

इनमें से कोई भी पूरी टेबल पर की जाने वाली गणना नहीं है। इनमें से प्रत्येक सवाल इशारा करने (pointing) के बारे में है: कुछ कॉलमों की ओर, एक पंक्ति की ओर, आखिरी कुछ पंक्तियों की ओर, या किसी एक सेल की ओर। आठ पंक्तियों के लिए पूरी टेबल प्रिंट करके उंगली रखकर पढ़ना काम कर जाता है। लेकिन आठ हज़ार पंक्तियों के लिए यह काम नहीं करता, और प्रोग्राम के अंदर तो बिल्कुल नहीं करता, क्योंकि प्रोग्राम की कोई उंगली नहीं होती।

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

इसलिए यह अध्याय डेटा चुनने (selecting) के बारे में है, लेकिन मुख्य रूप से यह हमेशा यह जानने के बारे में है कि आप इन दोनों में से किसका उपयोग कर रहे हैं।

इस अध्याय के अंत में आप कर पाएंगे

  • एक कॉलम चुनकर Series पाना, या कई कॉलम चुनकर DataFrame पाना — और यह जानना कि आपके हाथ में क्या आया है
  • .loc से लेबल द्वारा और .iloc से पोज़ीशन द्वारा पंक्तियाँ चुनना, और यह समझाना कि उनके स्लाइस अलग तरीके से क्यों समाप्त होते हैं
  • .loc[rows, columns] के साथ पंक्तियों और कॉलमों को एक साथ चुनना और परिणाम के आकार (shape) का पहले से अनुमान लगाना — यहाँ तक कि .loc या .at से एक एकल मान प्राप्त करना
  • set_index() से पंक्तियों को सार्थक लेबल देना, और देखना कि यह .loc के अर्थ को कैसे बदल देता है
  • यह समझाना कि पांडास 3 में किसी सिलेक्शन को बदलने से मूल टेबल क्यों नहीं बदलती, और मूल टेबल को सही तरीके से बदलना; सिलेक्शन से उत्पन्न होने वाले KeyError और IndexError संदेशों को समझना और ठीक करना

ज़रूरी शर्तें: CSV फ़ाइल पढ़ना, और पढ़ने के बाद जांचना।


पहले फ़ाइल बनाएँ

अपने प्रोजेक्ट फ़ोल्डर में ठीक इन नौ पंक्तियों के साथ orders.csv बनाएँ। इस फ़ाइल का उपयोग बाकी के पूरे कोर्स में किया जाएगा, इसलिए इसे ऐसी जगह सहेजें जहाँ आप इसे फिर से ढूँढ सकें:

text
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

प्रत्येक पंक्ति एक ऑर्डर है: एक ऑर्डर नंबर, तारीख, दुकान की वह शाखा जिसने इसे बेचा, क्या बेचा गया, उत्पाद की श्रेणी (category), कितनी संख्या, और एक वस्तु की कीमत।


कोड लिखने से पहले

डेटा चुनना इतना छोटा काम लगता है कि लोग तुरंत ब्रैकेट्स टाइप करना शुरू कर देते हैं। पहले इन चरणों पर एक मिनट बिताएँ, क्योंकि इस अध्याय में सिलेक्शन की हर ग़लती इनमें से किसी एक को छोड़ने से ही होती है।

1. प्रश्न को एक वाक्य में कहें, और संज्ञाओं (nouns) को रेखांकित करें। संज्ञाएँ आपको बताती हैं कि आप किस चीज़ की ओर इशारा कर रहे हैं:

  • "हर ऑर्डर का product और quantity" — दो कॉलम, सभी पंक्तियाँ
  • "order 1006" — एक पंक्ति, उसके ऑर्डर नंबर से पहचानी गई
  • "आखिरी तीन ऑर्डर" — पोज़ीशन के अनुसार पंक्तियाँ, अंत से गिनी गईं
  • "order 1006 की price" — एक सेल: एक पंक्ति और एक कॉलम एक साथ

2. टेबल में इशारा करने से पहले टेबल को देखें। आप केवल उन्हीं नामों की ओर इशारा कर सकते हैं जो मौजूद हैं। सिलेक्शन के लिए तीन चीज़ें मायने रखती हैं: आकार (size), कॉलमों के सटीक नाम, और इंडेक्स — यानी बाईं ओर के लेबल्स।

python
import pandas as pd

orders = pd.read_csv("orders.csv")

print(orders.shape)
print(list(orders.columns))
print(orders.index)
text
(8, 7)
['order_id', 'date', 'branch', 'product', 'category', 'quantity', 'price']
RangeIndex(start=0, stop=8, step=1)

कॉलम के नाम कोट्स के अंदर एक लिस्ट के रूप में आते हैं — ताकि आप देख सकें कि 'price' छोटे अक्षरों में है और इसमें कोई अतिरिक्त स्पेस नहीं है। इंडेक्स 0 से 7 तक एक RangeIndex है: लेबल्स केवल एक गिनती हैं। इसे याद रखें, क्योंकि यह वही संयोग है जिस पर यह पूरा अध्याय आधारित है। अभी, जिस पंक्ति का लेबल 2 है, वह पंक्ति पोज़ीशन 2 पर भी है। यह हमेशा सच नहीं रहेगा।

3. परिणाम पाने से पहले उसका रूप सोचें (sketch)। बाहर क्या आना चाहिए — एक संख्या, एक कॉलम, या एक छोटी टेबल? कितनी पंक्तियाँ और कितने कॉलम? स्टॉक टीम के अनुरोध का उत्तर 8 पंक्तियों और 2 कॉलमों की एक टेबल है। "order 1006" के लिए यह एक पंक्ति है। "order 1006 की कीमत" के लिए यह एक संख्या, 850.0 है। यदि आप अपने अपेक्षित आकार को जानते हैं, तो परिणाम देखते ही गलत सिलेक्शन तुरंत पकड़ में आ जाता है।

4. अपनी जानकारी के आधार पर टूल चुनें। यह सूची एक ही नज़र में पूरे अध्याय का सार है:

  • आपको कॉलम के नाम पता हैं → स्क्वायर ब्रैकेट्स: orders["quantity"], orders[["product", "quantity"]]
  • आपको पंक्ति का लेबल पता है, इंडेक्स में उसका नाम → .loc: orders.loc[2]
  • आपको पंक्ति की पोज़ीशन पता है — पहली, आखिरी, पाँचवीं → .iloc: orders.iloc[-3:]
  • आपको पंक्तियाँ और कॉलम एक साथ चाहिए → .loc[rows, cols] या .iloc[rows, cols]: orders.loc[2:4, ["product", "price"]]
  • आपको ठीक एक सेल चाहिए → .at[row, col] या .loc[row, col]: orders.at[5, "price"]

5. तय करें कि आप कैसे जाँच करेंगे। किसी भी सिलेक्शन के बाद, परिणाम का .shape या type() प्रिंट करें और अपने सोचे हुए आकार से तुलना करें। यह बस एक लाइन का कोड है, और यह नीचे दी गई अधिकांश ग़लतियों को गलत संख्याओं में बदलने से पहले ही पकड़ लेता है।

अध्याय का बाकी हिस्सा इन टूल्स को एक-एक करके समझाता है, प्रत्येक के अपने हल किए गए उदाहरण के साथ।


एक कॉलम: स्क्वायर ब्रैकेट्स में नाम

स्क्वायर ब्रैकेट्स में कॉलम का नाम लिखें और आपको वह कॉलम मिल जाएगा:

python
quantity = orders["quantity"]

print(type(quantity))
print(quantity)
text
<class 'pandas.Series'>
0    12
1     5
2     2
3     7
4    30
5     1
6    20
7     4
Name: quantity, dtype: int64

परिणाम में जो मिलता है वह एक Series है — ठीक वही ऑब्जेक्ट जो अध्याय दो में था: मान (values), बाईं ओर एक इंडेक्स, एक Name (वह कॉलम जिससे यह आया है) और एक dtype। ध्यान दें कि इंडेक्स भी इसके साथ आया है। 0 से 7 कोई सजावट नहीं हैं: वे अभी भी बताते हैं कि प्रत्येक संख्या किस पंक्ति से संबंधित है।

चूँकि यह एक Series है, इसलिए प्रत्येक Series मेथड इस पर सीधे काम करता है:

python
print(orders["quantity"].sum())
text
81

एक कॉलम चुनने का सामान्य कारण यही होता है: उससे कोई सवाल पूछना या गणना करना।

कई कॉलम: ब्रैकेट्स के अंदर एक लिस्ट

स्टॉक टीम के लिए, आपको दो कॉलम चाहिए। ब्रैकेट्स के अंदर नामों की एक लिस्ट रखें:

python
stock = orders[["product", "quantity"]]

print(type(stock))
print(stock)
text
<class 'pandas.DataFrame'>
    product  quantity
0       pen        12
1  notebook         5
2       bag         2
3    bottle         7
4    eraser        30
5       bag         1
6       pen        20
7    bottle         4

डबल ब्रैकेट्स कोई विशेष सिंटैक्स नहीं हैं, और इसे स्पष्ट रूप से समझना ज़रूरी है। बाहर की जोड़ी सिलेक्शन के लिए है; अंदर की जोड़ी एक साधारण पायथन लिस्ट है, ["product", "quantity"]। आप पहले लिस्ट बना सकते थे और फिर उसे पास कर सकते थे — wanted = ["product", "quantity"] और फिर orders[wanted] — और इसका मतलब बिल्कुल वही होता। कॉलम लिस्ट के क्रम में वापस आते हैं, इसलिए कॉलमों का क्रम बदलने के लिए भी इसी का उपयोग किया जाता है।

परिणाम एक DataFrame है, क्योंकि दो कॉलम वाली टेबल एक टेबल ही होती है। और यहाँ एक ऐसा अंतर है जो लोगों को महीनों तक उलझाए रखता है: एक ऐसी लिस्ट जिसमें केवल एक नाम हो, वह भी DataFrame ही देती है।

python
print(orders["product"].shape)
print(orders[["product"]].shape)
text
(8,)
(8, 1)

(8,) एक Series है — एक आयाम (one dimension), आठ मान। (8, 1) आठ पंक्तियों और एक कॉलम वाला DataFrame है। वे अलग तरह से प्रिंट होते हैं, उनके मेथड्स अलग होते हैं, और जब आप उन्हें किसी फ़ाइल में सहेजते हैं, तो DataFrame अपने कॉलम का हेडर बनाए रखता है। नियम सरल है: एक नाम Series देता है; एक लिस्ट DataFrame देती है — चाहे लिस्ट में कितने भी नाम क्यों न हों।

orders.quantity क्यों नहीं?

आप कई ट्यूटोरियल में डॉट के साथ कॉलम एक्सेस लिखा देखेंगे:

python
print(orders.branch.head(2))
text
0    north
1    south
Name: branch, dtype: str

यह यहाँ काम करता है, और छोटा भी है। लेकिन यह केवल तभी काम करता है जब कॉलम का नाम संयोग से एक वैध पायथन नाम हो जो पहले से किसी अन्य चीज़ के लिए उपयोग न किया गया हो — और एक DataFrame में सैकड़ों मेथड्स और एट्रिब्यूट्स होते हैं। यहाँ एक छोटी टेबल है जिसके कॉलमों के नाम count और size हैं, जो किसी दुकान के लिए पूरी तरह से उचित नाम हैं:

python
d = pd.DataFrame({"count": [1, 2], "size": [3, 4]})

print(d.size)
print(d["size"])
text
4
0    3
1    4
Name: size, dtype: int64

d.size ने आपको कॉलम बिल्कुल नहीं दिया। इसने बिना किसी चेतावनी के DataFrame का अपना size एट्रिब्यूट दे दिया — सेलों की कुल संख्या, 2 × 2 = 4। कोई एरर नहीं, बस एक गलत उत्तर। जिस नाम में स्पेस हो, जैसे unit price, उसे तो डॉट के साथ लिखा ही नहीं जा सकता। ब्रैकेट्स में इनमें से कोई समस्या नहीं होती, इसलिए अपने कोड के लिए नियम यह रखें: हमेशा df["col"] का उपयोग करें। टर्मिनल में तुरंत देखने के लिए डॉट ठीक है, उससे ज़्यादा कुछ नहीं।


लेबल से पंक्तियाँ: .loc

पंक्तियों को सादे ब्रैकेट्स से नहीं चुना जाता है। (नाम के साथ सादे ब्रैकेट्स का मतलब "कॉलम" होता है, और पांडास यह अंदाज़ा नहीं लगाएगा कि आपका क्या मतलब था।) पंक्तियों के लिए अपने दो टूल्स हैं, और पहला है .loc — label-based location (लेबल-आधारित स्थान)। आप इसे इंडेक्स से लेबल देते हैं:

python
print(orders.loc[2])
text
order_id           1003
date         2024-03-02
branch            north
product             bag
category    accessories
quantity              2
price             850.0
Name: 2, dtype: object

एक पंक्ति एक आड़ी Series के रूप में वापस आती है: कॉलम के नाम इसका इंडेक्स बन गए हैं, और इसका Name उस पंक्ति का लेबल, 2 है।

dtype: object को देखें। एक कॉलम का एक ही प्रकार होता है, लेकिन एक पंक्ति अलग-अलग कॉलमों के आर-पार चलती है — एक संख्या, कुछ टेक्स्ट, एक दशमलव मान — इसलिए एकमात्र प्रकार जो उन सभी को रख सकता है वह सामान्य object है। यही एक कारण है कि पांडास कॉलमों के रूप में सोचता है: एक पंक्ति प्रकारों को मिलाती है, एक कॉलम नहीं।

अब महत्वपूर्ण हिस्सा देखें। .loc से लेबलों की एक रेंज माँगें:

python
print(orders.loc[2:4])
text
order_id        date branch product     category  quantity  price
2      1003  2024-03-02  north     bag  accessories         2  850.0
3      1004  2024-03-02   east  bottle  accessories         7  120.0
4      1005  2024-03-03  north  eraser   stationery        30    8.0

तीन पंक्तियाँ: 2, 3 और 4। एक .loc स्लाइस अपने अंतिम मान को भी शामिल करता है। यह पायथन जानने वाले हर व्यक्ति को हैरान करता है, जहाँ range(2, 4) और list[2:4] 4 से पहले रुक जाते हैं। यह कोई असंगति नहीं है; इसका एक ठोस कारण है।

लेबल्स हमेशा संख्याएँ नहीं होते। यदि लेबल्स उत्पादों के नाम होते, तो आप loc["bag":"eraser"] जैसा कुछ लिखते। अंतिम मान को छोड़ने के लिए, आपको "eraser" के बाद वाले लेबल का नाम देना पड़ता — और नामों के साथ, बिना देखे यह जानना असंभव है कि "अगला नाम" कौन सा है। इसलिए लेबल्स के साथ, आप वह पहला नाम बताते हैं जो आपको चाहिए और वह आखिरी जो आपको चाहिए, और आपको दोनों मिलते हैं। पोज़ीशन अलग हैं: 4 के बाद अगली पोज़ीशन हमेशा 5 होती है, इसलिए वहाँ पायथन के "पहले रुकने" के नियम से कोई नुकसान नहीं होता।

पोज़ीशन से पंक्तियाँ: .iloc

दूसरा टूल है .iloc — integer location (पूर्णांक स्थान)। यह लेबल्स को पूरी तरह से अनदेखा करता है और पायथन लिस्ट की तरह ही 0 से स्थानों की गिनती करता है:

python
print(orders.iloc[2:4])
text
order_id        date branch product     category  quantity  price
2      1003  2024-03-02  north     bag  accessories         2  850.0
3      1004  2024-03-02   east  bottle  accessories         7  120.0

दो पंक्तियाँ, पोज़ीशन 2 और 3; पोज़ीशन 4 छूट गई है, जैसा कि पूरे पायथन में होता है। दोनों आउटपुट्स को अगल-बगल रखें: उसी टेबल पर, ब्रैकेट्स में उन्हीं संख्याओं के साथ, loc[2:4] ने तीन पंक्तियाँ दीं और iloc[2:4] ने दो।

चूँकि .iloc पोज़ीशनल है, इसलिए ऋणात्मक संख्याएँ अंत से गिनती करती हैं — जो सीधे आपके मैनेजर के सवाल का जवाब देती हैं:

python
print(orders.iloc[-3:])
text
order_id        date branch product     category  quantity  price
5      1006  2024-03-03  south     bag  accessories         1  850.0
6      1007  2024-03-04   east     pen   stationery        20   15.0
7      1008  2024-03-04  north  bottle  accessories         4  120.0

"आखिरी तीन" पोज़ीशन के बारे में एक सवाल है, इसलिए .iloc वह टूल है जो इससे मेल खाता है। .loc को बिल्कुल नहीं पता कि "आखिरी" का क्या मतलब है; वह केवल नाम जानता है।

लेबल और पोज़ीशन एक चीज़ नहीं हैं

अब तक, लेबल 2 और पोज़ीशन 2 एक ही पंक्ति थे, इसलिए आप उनके परिणामों से दोनों टूल्स में अंतर नहीं बता सकते थे। आखिरी तीन ऑर्डरों को उनकी अपनी टेबल के रूप में लें, और वे अलग हो जाते हैं:

python
last3 = orders.tail(3)

print(last3)
text
order_id        date branch product     category  quantity  price
5      1006  2024-03-03  south     bag  accessories         1  850.0
6      1007  2024-03-04   east     pen   stationery        20   15.0
7      1008  2024-03-04  north  bottle  accessories         4  120.0

पंक्तियों ने अपने लेबल बनाए रखे: 5, 6, 7। पांडास जानबूझकर उन्हें दोबारा नंबर नहीं देता — लेबल ही वह तरीका है जिससे टेबल को काटने के बाद भी कोई पंक्ति वही पंक्ति बनी रहती है। अब दोनों तरीकों से "पहली पंक्ति" माँगें:

python
print(last3.iloc[0])
text
order_id           1006
date         2024-03-03
branch            south
product             bag
category    accessories
quantity              1
price             850.0
Name: 5, dtype: object
python
last3.loc[0]
text
KeyError: 0

.iloc[0] का मतलब है "पहले स्थान वाली पंक्ति", जो कि 5 लेबल वाला ऑर्डर है। .loc[0] का मतलब है "वह पंक्ति जिसका लेबल 0 है" — और इस टेबल में ऐसी कोई पंक्ति नहीं है, इसलिए पांडास KeyError देता है। यह वही ग़लती है जो कोर्स में आगे चलकर परेशान करती है: जब आप पंक्तियों को फ़िल्टर करते हैं या उन्हें सॉर्ट करते हैं — दोनों चीज़ें कोर्स में आगे आ रही हैं — पोज़ीशन बदल जाती हैं और लेबल नहीं बदलते। आगे के लिए याद रखने वाला नियम:

  • जब सवाल पोज़ीशन या स्थान के बारे में हो — पहली, आखिरी, सॉर्ट करने के बाद शीर्ष तीन? .iloc का उपयोग करें।
  • जब सवाल पहचान के बारे में हो — यह विशेष ऑर्डर, यह उत्पाद? .loc का उपयोग करें।

और सबसे बुरा मामला KeyError नहीं है। सबसे बुरा मामला तब होता है जब लेबल 0 टेबल में कहीं और मौजूद होता है: तब .loc[0] बिना किसी एरर के चुपचाप आपके मनचाहे रिकॉर्ड के बजाय कोई दूसरा रिकॉर्ड लौटा देता है।

.loc एक नाम माँगता है, .iloc एक स्थान माँगता है। ताज़ा पढ़ी गई टेबल पर दोनों संयोग से सहमत होते हैं, इसलिए जो कोड उन्हें आपस में मिला देता है वह पहले दिन आपके हर चेक को पास कर जाता है — और जैसे ही टेबल काटी, फ़िल्टर या सॉर्ट की जाती है, वह बिना किसी एरर के गलत पंक्ति लौटाने लगता है।

पंक्तियों को सार्थक लेबल देना: set_index

अकाउंटेंट ने order 1006 के बारे में पूछा था। डिफ़ॉल्ट इंडेक्स के साथ आपको यह जानना पड़ता कि order 1006 लेबल 5 के तहत आता है — जो फ़ाइल के बारे में एक तथ्य है, ऑर्डर के बारे में नहीं। किसी पंक्ति का असली नाम उसका ऑर्डर नंबर होता है, इसलिए ऑर्डर नंबर को इंडेक्स बनाएँ:

python
by_id = orders.set_index("order_id")

print(by_id.head(3))
print(by_id.shape)
text
date branch   product     category  quantity  price
order_id
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
(8, 6)

तीन चीज़ें बदल गईं। ऑर्डर नंबर बाईं ओर इंडेक्स में चले गए। इंडेक्स का अब एक नाम है, order_id, जो उसके ऊपर अपनी पंक्ति में प्रिंट हुआ है। और आकार (8, 7) के बजाय (8, 6) हो गया है — order_id अब कॉलम नहीं रहा, यह इंडेक्स है। यह भी ध्यान दें कि set_index ने एक नई टेबल लौटाई, जिसे हमने by_id नाम दिया; orders स्वयं अछूती रही।

अब .loc सवाल की भाषा बोलता है:

python
print(by_id.loc[1006])
text
date         2024-03-03
branch            south
product             bag
category    accessories
quantity              1
price             850.0
Name: 1006, dtype: object

और एक लेबल स्लाइस अब ऑर्डर नंबरों की एक रेंज है, जिसमें दोनों छोर शामिल हैं:

python
print(by_id.loc[1003:1005, ["product", "quantity"]])
text
product  quantity
order_id                  
1003         bag         2
1004      bottle         7
1005      eraser        30

.iloc में बिल्कुल कोई बदलाव नहीं हुआ है — पोज़ीशन पोज़ीशन ही रहती है, चाहे लेबल्स कुछ भी हों:

python
print(by_id.iloc[0:2])
text
date branch   product    category  quantity  price
order_id
1001      2024-03-01  north       pen  stationery        12   15.0
1002      2024-03-01  south  notebook  stationery         5   60.0

यहाँ दोनों टूल्स स्पष्ट रूप से अलग हो गए हैं: by_id.loc[0] अब एक KeyError होगा, क्योंकि 0 कोई ऑर्डर नंबर नहीं है, जबकि by_id.iloc[0] ऑर्डर 1001 है। यदि आपको कभी ऑर्डर नंबर को फिर से कॉलम के रूप में वापस चाहिए, तो by_id.reset_index() इसे वापस ले आता है।

ऐसा कॉलम चुनें जिसके मान अद्वितीय (unique) हों। इंडेक्स का उद्देश्य पंक्तियों को नाम देना है, और दो पंक्तियों द्वारा साझा किया गया नाम किसी एक की ओर इशारा नहीं कर सकता। देखें कि product के साथ क्या होता है, जो दोहराया गया है:

python
by_product = orders.set_index("product")

print(by_product.index.is_unique)
print(by_product.loc["bag"])
text
False
         order_id        date branch     category  quantity  price
product
bag          1003  2024-03-02  north  accessories         2  850.0
bag          1006  2024-03-03  south  accessories         1  850.0

कोई एरर नहीं — लेकिन loc["bag"] ने Series के रूप में एक पंक्ति के बजाय दो पंक्तियों वाला DataFrame लौटा दिया। जिस कोड को एक पंक्ति की उम्मीद थी (जैसे, एक संख्या के रूप में उसकी price पढ़ना), उसे अब दो पंक्तियाँ मिलती हैं। यही कारण है कि जब भी आप पंक्तियों को खोजने के लिए कोई इंडेक्स सेट करते हैं, तो index.is_unique से एक पंक्ति की जाँच करना हमेशा उपयोगी होता है।


पंक्तियाँ और कॉलम एक साथ

.loc और .iloc दोनों कॉमा से अलग की गई दो चीज़ें लेते हैं: पहले पंक्तियाँ, बाद में कॉलम। प्रत्येक भाग एक एकल लेबल, एक लिस्ट, या एक स्लाइस हो सकता है:

python
print(orders.loc[2:4, ["product", "price"]])
text
product  price
2     bag  850.0
3  bottle  120.0
4  eraser    8.0
python
print(orders.loc[[0, 3], ["branch", "product"]])
text
branch product
0  north     pen
3   east  bottle

स्लाइस कॉलमों के लिए भी काम करता है, .loc के समावेशी नियम के साथ — "branch से लेकर category तक का हर कॉलम"। केवल एक कोलन : का अर्थ है "सभी पंक्तियाँ":

python
print(orders.loc[:, "branch":"category"])
text
branch   product     category
0  north       pen   stationery
1  south  notebook   stationery
2  north       bag  accessories
3   east    bottle  accessories
4  north    eraser   stationery
5  south       bag  accessories
6   east       pen   stationery
7  north    bottle  accessories

.iloc पोज़ीशन के हिसाब से वही काम करता है। कॉलमों की गिनती भी 0 से की जाती है, इसलिए branch कॉलम 2 है और product कॉलम 3 है:

python
print(orders.iloc[[0, 3], [2, 3]])
text
branch product
0  north     pen
3   east  bottle

वही परिणाम जो दो ब्लॉक ऊपर .loc संस्करण का था — लेकिन ध्यान दें कि इसे पढ़ना कितना कठिन है। [2, 3] कुछ नहीं बताता; ["branch", "product"] सब कुछ साफ़ बताता है, और तब भी काम करता है यदि कोई फ़ाइल की शुरुआत में एक नया कॉलम जोड़ दे। जब आपको नाम पता हों तो नामों को प्राथमिकता दें।

कैसा आकार (shape) वापस मिलेगा?

आपको एक एकल मान, एक Series या एक DataFrame मिलता है, यह केवल इस बात पर निर्भर करता है कि आपने कॉमा के दोनों ओर क्या रखा है। एक एकल लेबल उस आयाम (dimension) को हटा देता है; एक लिस्ट या स्लाइस उसे बनाए रखता है:

python
print(type(by_id.loc[1006, "price"]).__name__)
print(type(by_id.loc[1006, ["product", "price"]]).__name__)
print(type(by_id.loc[[1003, 1006], "price"]).__name__)
print(type(by_id.loc[[1003, 1006], ["product", "price"]]).__name__)
text
float64
Series
Series
DataFrame
  • एक लेबल, एक लेबल → एक एकल मान
  • एक लेबल, एक लिस्ट या स्लाइस → एक Series (एक पंक्ति)
  • एक लिस्ट या स्लाइस, एक लेबल → एक Series (एक कॉलम)
  • एक लिस्ट या स्लाइस, एक लिस्ट या स्लाइस → एक DataFrame

यह योजना बनाने का चरण 3 है — उत्तर का आकार तय करना — जो एक नियम में बदल गया है। यदि आप टेबल वापस चाहते हैं, भले ही वह एक पंक्ति की हो, तो दोनों तरफ लिस्ट दें: by_id.loc[[1006], ["price"]]।

एक एकल मान: .loc और .at

अकाउंटेंट का सवाल — order 1006 की कीमत क्या थी? — एक सेल है: एक पंक्ति का लेबल और एक कॉलम का नाम।

python
print(by_id.loc[1006, "price"])
print(by_id.at[1006, "price"])
text
850.0
850.0

दोनों एक ही संख्या देते हैं। .at केवल एक पंक्ति और एक कॉलम स्वीकार करता है, और कुछ नहीं, जो इसे थोड़ा तेज़ बनाता है और आपके इरादे को स्पष्ट करता है: यह एक सेल है। (पोज़ीशनल जुड़वां .iat है, जैसे by_id.iat[5, 5]।) जो पढ़ने में बेहतर लगे उसका उपयोग करें; .loc भी बढ़िया है।

एक एकल मान एक सामान्य संख्या होती है, इसलिए आप इसके साथ सीधे गणना कर सकते हैं। ऑर्डर 1004 का कुल मूल्य — मात्रा गुणा कीमत:

python
print(by_id.at[1004, "quantity"] * by_id.at[1004, "price"])
text
840.0

चयन (selection) अपने आप में एक अलग ऑब्जेक्ट है

वास्तविक प्रोग्रामों में सिलेक्शन का उपयोग करने से पहले एक और बात समझनी होगी: जब आप किसी सिलेक्शन को बदलते हैं तो क्या होता है।

python
import pandas as pd

orders = pd.read_csv("orders.csv")

prices = orders["price"]
prices[0] = 999.0

print(prices.head(2))
print(orders.loc[0, "price"])
text
0    999.0
1     60.0
Name: price, dtype: float64
15.0

prices बदल गया। orders नहीं बदला। पांडास 3 में, प्रत्येक सिलेक्शन एक अलग कॉपी (प्रतिलिपि) के रूप में व्यवहार करता है: आप इसे स्वतंत्र रूप से बदल सकते हैं और मूल टेबल कभी नहीं बदलती। (आंतरिक रूप से पांडास वास्तव में डेटा की प्रतिलिपि तब तक नहीं बनाता जब तक आप उसमें कुछ लिखते नहीं हैं — इस तंत्र का नाम कॉपी-ऑन-राइट (Copy-on-Write) है — लेकिन आपको जिस नियम की आवश्यकता है वह इसका व्यवहार है, आंतरिक तंत्र नहीं।)

यही बात कई कॉलमों के चयन पर भी लागू होती है:

python
stock = orders[["product", "price"]]
stock["price"] = 0

print(orders["price"].head(2))
text
0    15.0
1    60.0
Name: price, dtype: float64

यह सुरक्षित व्यवहार है: एक फ़ंक्शन जो कोई सिलेक्शन लेता है और उसे संपादित करता है, वह आपकी मुख्य टेबल को आपके पीछे नुकसान नहीं पहुँचा सकता। लेकिन इसका एक परिणाम है जिसे आपको जानना आवश्यक है। यदि आपका इरादा मूल टेबल को बदलने का है, तो आपको इसे मूल टेबल पर ही, एक ही चरण में, .loc के साथ करना होगा:

python
orders.loc[0, "price"] = 16.0

print(orders.loc[0, "price"])
text
16.0

दो चरणों वाला लुभावना तरीका — पहले एक कॉलम चुनना, फिर उसमें एक सेल सेट करना — काम नहीं करता, और पांडास 3 आपको यह साफ़ बताता है:

python
orders["price"][0] = 1.0
text
ChainedAssignmentError: A value is being set on a copy of a DataFrame or Series through chained assignment.
Such chained assignment never works to update the original DataFrame or Series, because the intermediate object on which we are setting values always behaves as a copy (due to Copy-on-Write).

Try using '.loc[row_indexer, col_indexer] = value' instead, to perform the assignment in a single step.

See the documentation for a more detailed explanation: https://pandas.pydata.org/pandas-docs/stable/user_guide/copy_on_write.html#chained-assignment
python
print(orders.loc[0, "price"])
text
16.0

अभी भी 16.0 — असाइनमेंट एक अस्थायी कॉपी में चला गया और उसे छोड़ दिया गया। orders["price"] ने एक सिलेक्शन बनाया, और [0] = 1.0 ने उसे बदला, orders को नहीं। इसे चेन्ड असाइनमेंट (chained assignment) कहते हैं: = के बाईं ओर लगातार दो ब्रैकेट्स। इसका समाधान हमेशा वही होता है जो चेतावनी में बताया गया है: पंक्ति और कॉलम दोनों के साथ एक ही .loc, जैसे orders.loc[0, "price"] = 1.0।

यदि आप पुराने ट्यूटोरियल पढ़ते हैं, तो आप इस विषय को SettingWithCopyWarning और इस सलाह के साथ वर्णित देखेंगे कि कभी मूल टेबल बदलती थी और कभी नहीं। वह पांडास 1 और 2 था। पांडास 3 में नियम सरल और हमेशा एक जैसा है: एक सिलेक्शन कभी भी मूल को नहीं बदलता; मूल पर .loc बदलता है।


एक संपूर्ण उदाहरण

अध्याय की शुरुआत के तीनों अनुरोधों का उत्तर एक ही प्रोग्राम में दिया गया है। select_orders.py:

python
import pandas as pd

orders = pd.read_csv("orders.csv")

# 1. Check what arrived before pointing into it
print("Shape  :", orders.shape)
print("Columns:", list(orders.columns))

# 2. Name the rows by what they are
by_id = orders.set_index("order_id")
print("Order ids unique:", by_id.index.is_unique)
print()

# Stock team: two columns, every row
stock = orders[["product", "quantity"]]
print("Stock view:", stock.shape)
print(stock.head(3))
print()

# Accountant: one order by its number, then one cell
print(by_id.loc[1006, ["product", "quantity", "price"]])
print("Order 1006 total:", by_id.at[1006, "quantity"] * by_id.at[1006, "price"])
print()

# Manager: the last three orders, a question about position
print(by_id.iloc[-3:, [1, 2, 4]])
text
Shape  : (8, 7)
Columns: ['order_id', 'date', 'branch', 'product', 'category', 'quantity', 'price']
Order ids unique: True

Stock view: (8, 2)
    product  quantity
0       pen        12
1  notebook         5
2       bag         2

product       bag
quantity        1
price       850.0
Name: 1006, dtype: object
Order 1006 total: 850.0

         branch product  quantity
order_id
1006      south     bag         1
1007       east     pen        20
1008      north  bottle         4

इसे इस तरह क्यों लिखा गया है:

  • यह इशारा करने से पहले देखता है। shape और list(columns) पहले प्रिंट किए जाते हैं, ताकि किसी भी सिलेक्शन के विफल होने से पहले ग़लत वर्तनी या स्पेस वाला कॉलम नाम दिखाई दे जाए।
  • यह जाँचता है कि इंडेक्स अद्वितीय (unique) है ऑर्डर खोजने के लिए इसका उपयोग करने से पहले, ताकि by_id.loc[1006] गारंटी के साथ एक पंक्ति हो और by_id.at[...] एक संख्या।
  • प्रत्येक अनुरोध उस टूल का उपयोग करता है जो उसके सवाल से मेल खाता है। नाम से कॉलम के लिए ब्रैकेट्स; ऑर्डर की पहचान से खोजने के लिए .loc और .at; और पोज़ीशन से "आखिरी तीन" के लिए .iloc।
  • यह स्टॉक व्यू का आकार प्रिंट करता है — (8, 2) बिल्कुल वही स्केच है: हर ऑर्डर, दो कॉलम।
  • अंतिम सिलेक्शन सुधारने योग्य है। by_id में [1, 2, 4] का अर्थ branch, product, quantity है — ध्यान दें कि पोज़ीशन एक स्थान खिसक गई क्योंकि order_id इंडेक्स बन गया। पोज़ीशन नाज़ुक होती हैं। आपके अपने कोड में, by_id.iloc[-3:][["branch", "product", "quantity"]] या by_id.loc[by_id.index[-3:], ["branch", "product", "quantity"]] नामों के साथ वही बात अधिक स्पष्टता से कहेगा।

जब यह काम न करे

KeyError: 'Price' बिल्कुल उस नाम का कोई कॉलम नहीं है। कॉलम के नाम केस-सेंसिटिव होते हैं: price और Price अलग-अलग नाम हैं। list(df.columns) प्रिंट करें और वहाँ से नाम कॉपी करें। नामों की लिस्ट के साथ, orders[["product", "Price"]], संदेश KeyError: "['Price'] not in index" बन जाता है और ठीक बताता है कि कौन सा गायब है; डॉट के साथ, orders.Price, यह AttributeError: 'DataFrame' object has no attribute 'Price' होता है। एक ही कारण, एक ही समाधान।

KeyError: 'price' — हालाँकि फ़ाइल में price साफ़ दिख रहा है हेडर में संभवतः कॉमा के बाद स्पेस हैं, जैसे order_id, product, price, और वे स्पेस नामों का हिस्सा बन गए:

python
from io import StringIO

import pandas as pd

raw = "order_id, product, price\n1001, pen, 15.0\n"
padded = pd.read_csv(StringIO(raw))

print(list(padded.columns))
padded["price"]
text
['order_id', ' product', ' price']
KeyError: 'price'

लिस्ट ' price' में शुरुआती स्पेस दिखाती है, यही वजह है कि अध्याय तीन में आपको कॉलमों को लिस्ट के रूप में प्रिंट करने के लिए कहा गया था। फ़ाइल पढ़ते समय इसे pd.read_csv("file.csv", skipinitialspace=True) से ठीक करें, या बाद में df.columns = df.columns.str.strip() से।

KeyError: ('product', 'price') आपने orders["product", "price"] लिखा — ब्रैकेट्स की एक ही जोड़ी। अंदरूनी लिस्ट के बिना, पायथन दोनों नामों को एक टपल के रूप में पास करता है, और पांडास ('product', 'price') नाम का एक ही कॉलम ढूँढता है। orders[["product", "price"]] लिखें।

.loc से KeyError: 0 आपने .loc के साथ पोज़ीशन का उपयोग किया। या तो टेबल काटी गई थी (tail, फ़िल्टर, सॉर्ट) जिससे लेबल 0 चला गया, या आपने इंडेक्स सेट किया और लेबल्स अब ऑर्डर नंबर बन गए हैं। यदि आपका मतलब "पहली पंक्ति" है, तो .iloc[0] लिखें। जिस टेबल का इंडेक्स टेक्स्ट है, उस पर स्लाइस के साथ की गई यही ग़लती — orders.set_index("product").loc[0:2] — इसके बजाय TypeError: cannot do slice indexing on Index with these indexers [0] of type int देती है; समाधान वही है, .iloc[0:2]।

IndexError: single positional indexer is out-of-bounds .iloc को ऐसी पोज़ीशन दी गई थी जो मौजूद नहीं है — आठ पंक्तियों वाली टेबल पर orders.iloc[8], जिसकी पोज़ीशन 0 से 7 तक होती हैं। df.shape जाँचें; आखिरी पंक्ति हमेशा df.iloc[-1] होती है। कॉलम की पोज़ीशन बहुत बड़ी होने पर भी यही संदेश आता है, जैसे orders.iloc[:, 7]।

ValueError: Location based indexing can only have [integer, integer slice (START point is INCLUDED, END point is EXCLUDED), listlike of integers, boolean array] types आपने .iloc को कॉलम का नाम दे दिया: orders.iloc[0, "branch"]। .iloc केवल पोज़ीशन लेता है। लेबल के साथ .loc का उपयोग करें — orders.loc[0, "branch"] — या, यदि आपको वास्तव में पोज़ीशन की आवश्यकता है, तो उसे खोजें: orders.columns.get_loc("branch") से 2 मिलता है।