अध्याय 02

Series और DataFrame — पांडास की दो मुख्य चीज़ें

कॉलम क्या है, टेबल क्या है, और क्यों एक कॉलम की सभी वैल्यूज़ का एक ही टाइप होना अनिवार्य है। df["col"] और df[["col"]] के अंतर के साथ।

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

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

पिछले अध्याय में एक लाइन लिखी गई थी, और उसने काम किया:

python
print(sales["quantity"].sum())
text
19

यह लाइन पढ़ने में आसान है, लेकिन इसके अंदर एक सवाल छिपा हुआ है। sales["quantity"] ने असल में क्या लौटाया? एक लिस्ट? लेकिन पायथन लिस्ट्स में .sum() नाम का कोई मेथड नहीं होता — पायथन में [12, 5, 2].sum() लिखने पर एरर आ जाता है।

यह उत्तर जानना बहुत ज़रूरी है, क्योंकि पांडास में आप हमेशा दो में से किसी एक चीज़ को थामे रहते हैं, और लगभग हर भ्रम इसी एक सवाल से शुरू होता है: इस समय मेरे हाथ में क्या है? केवल एक कॉलम, या फिर पूरी की पूरी टेबल?

यह अध्याय उन्हीं दो चीज़ों के बारे में है: उनके नाम, उनके आकार, और जो चीज़ उन्हें एक-दूसरे से अलग करती है।

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

  • एक वाक्य में यह बताना कि Series और DataFrame के बीच क्या अंतर है
  • एक Series बनाना और उसके इंडेक्स, वैल्यूज़ और टाइप का अलग-अलग निरीक्षण करना
  • यह समझाना कि एक कॉलम की सभी वैल्यूज़ का एक ही प्रकार (type) होना क्यों ज़रूरी है
  • df["col"] और df[["col"]] का उपयोग करते समय यह स्पष्ट जानना कि कौन क्या है
  • KeyError पढ़ना और कॉलम नाम में हुई सटीक गड़बड़ी को पहचानना

ज़रूरी शर्तें: पांडास इंस्टॉल करना और आपकी पहली टेबल।


Series — एक अकेला कॉलम

छोटी इकाई से शुरुआत करते हैं। एक Series वैल्यूज़ का एक क्रम है, जहाँ प्रत्येक वैल्यू एक नाम के साथ जुड़ी होती है:

python
import pandas as pd

quantity = pd.Series([12, 5, 2])

print(quantity)
text
0    12
1     5
2     2
dtype: int64

तीन वैल्यूज़ अंदर गईं, और तीन जोड़े बाहर आए। बाईं ओर दिखने वाले 0, 1, 2 इंडेक्स (index) हैं — प्रत्येक वैल्यू के लिए एक नाम। आपने उन्हें नहीं दिया था; पांडास ने खुद शून्य से गिनती शुरू की।

और नीचे की तरफ़, dtype: int64 — पूरी सीरीज़ का डेटा टाइप। एक ही टाइप, प्रत्येक वैल्यू के लिए अलग नहीं। यही चीज़ पांडास को तेज़ बनाती है, और यही इसका मुख्य नियम भी है।

इन तीनों भागों को अलग-अलग देखा जा सकता है:

python
import pandas as pd

quantity = pd.Series([12, 5, 2])

print(quantity.index)
print(quantity.values)
print(quantity.dtype)
text
RangeIndex(start=0, stop=3, step=1)
[12  5  2]
int64

RangeIndex का मतलब है कि इंडेक्स सिर्फ़ एक गिनती है — शून्य से शुरू होकर एक-एक आगे बढ़ती हुई। values बिना नामों के वास्तविक वैल्यूज़ हैं। और dtype पूरे कॉलम का डेटा टाइप है।

इंडेक्स का केवल संख्या होना ज़रूरी नहीं है:

python
import pandas as pd

quantity = pd.Series([12, 5, 2], index=["pen", "notebook", "bag"])

print(quantity)
print(quantity["notebook"])
text
pen         12
notebook     5
bag          2
dtype: int64
5

अब पंक्तियों के नाम उत्पादों के नाम पर हैं, और उस नाम से वैल्यू प्राप्त की जा सकती है। यह पायथन डिक्शनरी जैसा लगता है, और समानता वास्तविक भी है — लेकिन एक बड़े अंतर के साथ:

python
import pandas as pd

quantity = pd.Series([12, 5, 2], index=["pen", "notebook", "bag"])

print(quantity * 2)
text
pen         24
notebook    10
bag          4
dtype: int64

पूरे कॉलम को बिना किसी लूप के एक साथ दो से गुणा कर दिया गया। किसी डिक्शनरी के साथ ऐसा करने का मतलब है कि हर वैल्यू पर खुद लूप चलाना। किसी Series पर कोई ऑपरेशन चलाएँ और पांडास उसे हर वैल्यू पर लागू कर देता है — और आगे का हर अध्याय इसी व्यवहार पर टिका है।

DataFrame — कई कॉलम्स, एक साझा इंडेक्स

एक DataFrame कई Series का एक समूह है जो कंधे से कंधा मिलाकर चलते हैं और एक ही इंडेक्स साझा करते हैं।

python
import pandas as pd

sales = pd.DataFrame({
    "product": ["pen", "notebook", "bag"],
    "quantity": [12, 5, 2],
    "price": [15.0, 60.0, 850.0],
})

print(sales)
print(sales.shape)
print(sales.columns)
text
product  quantity  price
0       pen        12   15.0
1  notebook         5   60.0
2       bag         2  850.0
(3, 3)
Index(['product', 'quantity', 'price'], dtype='object')

वह वाक्यांश "एक ही इंडेक्स साझा करते हैं" ही इसका मुख्य सार है। इंडेक्स 0 पर मौजूद product, quantity और price सब एक ही चीज़ की बात कर रहे हैं — एक पेन। इंडेक्स ही वह धागा है जो तीन अलग-अलग कॉलम्स को पंक्तियों (rows) में पिरोता है।

एक टेबल, कई प्रकार — लेकिन एक कॉलम, एक प्रकार

python
import pandas as pd

sales = pd.DataFrame({
    "product": ["pen", "notebook", "bag"],
    "quantity": [12, 5, 2],
    "price": [15.0, 60.0, 850.0],
})

print(sales.dtypes)
text
product      object
quantity      int64
price       float64
dtype: object

तीन कॉलम, तीन प्रकार। int64 पूर्ण संख्याएँ हैं, float64 दशमलव संख्याएँ, और object — जिसका आमतौर पर मतलब टेक्स्ट होता है।

ध्यान दें कि यहाँ कोई str नहीं है। पांडास के डेटा टाइप्स NumPy से आते हैं, जहाँ प्रत्येक वैल्यू समान मात्रा में मेमोरी लेती है — जो संख्याओं के लिए संभव है, लेकिन अलग-अलग लंबाई के टेक्स्ट के लिए नहीं। इसलिए टेक्स्ट को संभालने के लिए पांडास पायथन ऑब्जेक्ट्स के पॉइंटर्स रखता है, और इसी व्यवस्था को object कहा जाता है।

अब सबसे महत्वपूर्ण नियम: एक कॉलम में प्रत्येक वैल्यू एक ही प्रकार साझा करती है। तो क्या होता है जब संख्याओं और टेक्स्ट को एक साथ मिला दिया जाए?

python
import pandas as pd

mixed = pd.Series([1, "two", 3.0])

print(mixed)
print(mixed.dtype)
text
0      1
1    two
2    3.0
dtype: object

पांडास कोई एरर नहीं देता — यह पूरे कॉलम को उपलब्ध सबसे लचीले प्रकार object में बदल देता है। इसका परिणाम तुरंत कोई तमाशा खड़ा नहीं करता, बल्कि यह चुपचाप होता है: कॉलम अब संख्यात्मक नहीं रहा, इसलिए इस पर .mean() काम नहीं करेगा और .sum() टेक्स्ट को आपस में जोड़ना शुरू कर देगा। किसी CSV की एक पंक्ति में एक भटकी हुई N/A वैल्यू ठीक यही करती है, और यही छठे अध्याय का मुख्य विषय है।

मेरे हाथ में क्या है — Series या DataFrame?

python
import pandas as pd

sales = pd.DataFrame({
    "product": ["pen", "notebook", "bag"],
    "quantity": [12, 5, 2],
    "price": [15.0, 60.0, 850.0],
})

column = sales["quantity"]

print(type(column))
print(type(sales))
print(column.mean())
text
<class 'pandas.core.series.Series'>
<class 'pandas.core.frame.DataFrame'>
6.333333333333333

और यह उस सवाल का जवाब है जिससे यह अध्याय शुरू हुआ था। sales["quantity"] कोई लिस्ट नहीं है, यह एक Series है — और एक Series में .sum(), .mean(), .max() और बाकी सभी मेथड्स होते हैं।

यहाँ एक ऐसा जोड़ा भी है जो पहली बार में हर किसी को हैरान करता है:

python
import pandas as pd

sales = pd.DataFrame({
    "product": ["pen", "notebook", "bag"],
    "quantity": [12, 5, 2],
    "price": [15.0, 60.0, 850.0],
})

print(sales["quantity"])
print("---")
print(sales[["quantity"]])
text
0    12
1     5
2     2
Name: quantity, dtype: int64
---
   quantity
0        12
1         5
2         2

केवल एक अतिरिक्त ब्रैकेट, और परिणाम बिल्कुल अलग तरह की चीज़ बन जाता है। पहला एक Series है — एक अकेला कॉलम, जिसका नाम और प्रकार नीचे प्रिंट होता है। दूसरा एक DataFrame है — एक टेबल जिसमें संयोगवश केवल एक कॉलम है, इसलिए कॉलम हेडिंग ऊपर बैठती है।

नियम यह है कि आंतरिक चौकोर ब्रैकेट (square brackets) का अर्थ है कॉलम्स की एक लिस्ट, और लिस्ट मांगने पर आपको एक टेबल मिलती है। इसलिए sales[["product", "price"]] दो कॉलम वाली टेबल देता है।

इस अंतर को याद रखना क्यों ज़रूरी है? क्योंकि यहीं से एरर मैसेजेस अलग रास्ते पकड़ते हैं। एक Series में .mean() होता है लेकिन कॉलम नाम नहीं होते; एक DataFrame कॉलम्स चुनता है लेकिन कोई एक संख्या नहीं लौटाता। जब कोई मैसेज कहे कि ऑब्जेक्ट में ऐसा कोई एट्रिब्यूट नहीं है, तो पहला सवाल यही होना चाहिए: वास्तव में मेरे हाथ में दोनों में से कौन सी चीज़ है?

पूर्ण उदाहरण

inventory.py:

python
import pandas as pd

# उत्पादों के नाम इंडेक्स बन जाते हैं, ताकि किसी पंक्ति तक नाम से पहुँचा जा सके।
stock = pd.DataFrame(
    {
        "quantity": [12, 5, 2, 30],
        "price": [15.0, 60.0, 850.0, 8.0],
    },
    index=["pen", "notebook", "bag", "eraser"],
)

print(stock)
print()
print("Shape      :", stock.shape)
print("Columns    :", list(stock.columns))
print("Index      :", list(stock.index))
print()

# एक कॉलम एक Series है, इसलिए कोई भी ऑपरेशन एक साथ हर वैल्यू पर लागू होता है।
value = stock["quantity"] * stock["price"]
print("Value per product:")
print(value)
print()
print("Total value:", value.sum())
text
quantity  price
pen             12   15.0
notebook         5   60.0
bag              2  850.0
eraser          30    8.0

Shape      : (4, 2)
Columns    : ['quantity', 'price']
Index      : ['pen', 'notebook', 'bag', 'eraser']

Value per product:
pen          180.0
notebook     300.0
bag         1700.0
eraser       240.0
dtype: float64

Total value: 2420.0

यहाँ दो बातें ध्यान देने योग्य हैं।

पहला, stock["quantity"] * stock["price"] ने दो Series को गुणा किया और एक अन्य Series तैयार की। पांडास ने उन्हें इंडेक्स के आधार पर मिलाया — पेन की मात्रा पेन की कीमत के साथ, बैग की मात्रा बैग की कीमत के साथ। नाम से, पंक्ति के क्रम से नहीं। यह व्यवहार बाद में दो टेबलों को आपस में जोड़ने (join करने) की नींव है।

दूसरा, परिणामी कॉलम का डेटा प्रकार float64 है, हालाँकि quantity int64 थी। किसी पूर्ण संख्या को दशमलव से गुणा करने पर दशमलव मिलता है — सामान्य पायथन जैसा ही नियम; पांडास यहाँ अपनी तरफ़ से कुछ नया नहीं कर रहा है।


जब यह काम न करे

KeyError: 'Quantity' ऐसा कोई कॉलम मौजूद नहीं है। कॉलम के नाम केस-सेंसिटिव (case-sensitive) होते हैं, और CSV फ़ाइल से आने वाले नामों में अक्सर आगे या पीछे कोई स्पेस छूटा होता है। यह ठीक-ठीक देखने के लिए कि वहाँ क्या है, print(list(df.columns)) चलाएँ — उन्हें लिस्ट के रूप में प्रिंट करने से हर एक के दोनों ओर कोट्स लग जाते हैं, जिससे कोई भी स्पेस साफ़ दिख जाता है।

संख्यात्मक कॉलम पर गणना करते समय AttributeError: 'DataFrame' object has no attribute 'mean_price' df.column_name भी कॉलम लाता है, लेकिन केवल सरल नामों के लिए — तब नहीं जब नाम में स्पेस हो, संख्या से शुरू होता हो, या count जैसे किसी पांडास मेथड से टकराता हो। df["column_name"] हमेशा काम करता है, इसलिए हमेशा इसी को लिखने की आदत बनाएँ।

TypeError: unsupported operand type(s) कॉलम संभवतः संख्यात्मक नहीं है। df.dtypes की जाँच करें; वहाँ object होने का मतलब है कि कहीं न कहीं टेक्स्ट मिला हुआ है।

.sum() संख्याओं को जोड़ने के बजाय टेक्स्ट को जोड़ रहा है वही कारण — कॉलम का प्रकार object है। एक ही पंक्ति में एक N/A या एक खाली सेल पूरे कॉलम के प्रकार को बदलने के लिए काफ़ी है।

ValueError: All arrays must be of the same length pd.DataFrame({...}) को दी गई लिस्ट्स की लंबाई समान नहीं है। प्रत्येक कॉलम को समान संख्या में वैल्यूज़ की आवश्यकता होती है, क्योंकि पंक्तियों को एक साझा इंडेक्स पर संरेखित होना पड़ता है।