अनुपस्थित डेटा — रिक्तियों को खोजना और उनका अर्थ तय करना
टेबल में हर रिक्ति को पहचानना (यहाँ तक कि - या ? जैसे टेक्स्ट में छिपे मान भी), यह समझना कि NaN कैसे योग, औसत और डेटा टाइप बदलता है, और सोच-समझकर हर कॉलम के लिए निर्णय लेना — रिक्ति को हटाएं, भरें या वैसे ही रखें।
- 1समस्या
- 2समझें
- 3उदाहरण
- 4अनुमान
- 5स्वयं करें
- 6चुनौती
वह समस्या जिसे हम हल कर रहे हैं
मार्च के पहले सप्ताह के लिए दुकान के ऑर्डर्स को तीन शाखाओं में हाथ से टाइप किया गया और orders_raw.csv के रूप में एक्सपोर्ट किया गया। दुकान का मालिक शाम तक दो आंकड़े चाहता है: कुल कितनी यूनिट्स बिकीं, और कुल कितना पैसा आया।
आप फ़ाइल पढ़ते हैं और पिछले अध्यायों की आदत के अनुसार केवल पैमाना (scale) देखने के लिए पहले price कॉलम को जोड़ते हैं:
import pandas as pd
orders = pd.read_csv("orders_raw.csv")
print(orders["price"].sum())15.060.0850.08.0-15.0120.0यह कोई संख्या नहीं है। यह टेक्स्ट के रूप में एक साथ चिपकी हुई हर कीमत है — और पांडास ने आपको सचेत करने के लिए कोई एरर भी नहीं दिया। अब quantity कॉलम देखें:
print(orders["quantity"].sum())
print(orders["quantity"].mean())76.0
10.857142857142858यह ठीक लग रहा है, और यही बात इसे और भी ख़तरनाक बनाती है। फ़ाइल में कुल आठ ऑर्डर्स हैं, फिर भी 76 / 8 का मान 9.5 होता है, 10.86 नहीं। एक quantity खाली थी, और पांडास ने चुपचाप उसे कुल योग और औसत दोनों से बाहर कर दिया। किसी ने आपको इसके बारे में भी नहीं बताया।
तो फ़ाइल में रिक्तियाँ (gaps) हैं, और ये रिक्तियाँ दो प्रकार की होती हैं। कुछ दृश्यमान (visible) हैं: एक खाली सेल, जिसे पांडास एक विशेष "मिसिंग" मार्कर में बदल देता है और फिर अंकगणित करते समय चुपचाप नज़रअंदाज़ कर देता है। अन्य छिपी हुई (hidden) हैं: किसी ने सेल को खाली छोड़ने के बजाय - टाइप कर दिया, पांडास ने इसे सच मान लिया, और पूरा price कॉलम टेक्स्ट बन गया।
दोनों में से कोई भी प्रकार एरर नहीं देता। दोनों ही अंतिम उत्तर बदल देते हैं। यह अध्याय हर रिक्ति को खोजने, पांडास इसके साथ क्या करता है उसे समझने, और फिर डिफ़ॉल्ट व्यवहार पर छोड़ने के बजाय सोच-समझकर निर्णय लेने के बारे में है — रिक्ति को हटाएं (drop), भरें (fill), या वैसे ही रखें (keep)।
इस अध्याय के अंत में आप कर पाएंगे
- यह समझाना कि
NaNक्या है,NaN == NaNका मानFalseक्यों होता है, और एक सेल खाली होने पर पूर्ण संख्या वाला कॉलम दशमलव में क्यों बदल जाता है isna().sum()से प्रति कॉलम अनुपस्थित मानों की गिनती करना और अपूर्ण पंक्तियों को बाहर निकालना-या?जैसे छिपे हुए मार्करों कोna_values=याpd.to_numeric(errors="coerce")की मदद से वास्तविक अनुपस्थित मानों में बदलना- यह सटीक अनुमान लगाना कि
sum,mean,countऔर कॉलम अंकगणित अनुपस्थित मानों के साथ कैसा व्यवहार करते हैं dropna()से अनुपस्थित मानों को हटाना, और केवल इच्छित पंक्तियों को हटाने के लिएsubset=वhow=का उपयोग करनाfillna()से अनुपस्थित मानों को बदलना — एक मान से, प्रति कॉलम अलग मान से, या किसी सांख्यिकीय मान (statistic) से- प्रत्येक कॉलम के लिए सोच-समझकर निर्णय लेना कि उसे हटाएं, भरें या रखें — और उसका कारण बताना
- पांडास 3 के तहत काम न करने वाले
inplace=Trueके जाल से बचना
ज़रूरी शर्तें: पंक्तियों को फ़िल्टर करना।
पहले फ़ाइल बना लें
अपने प्रोजेक्ट फ़ोल्डर में orders_raw.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,,60.0
1003,2024-03-02,north,bag,accessories,2,850.0
1004,2024-03-02,,bottle,accessories,7,N/A
1005,2024-03-03,north,eraser,stationery,30,8.0
1006,2024-03-03,south,bag,accessories,1,-
1007,2024-03-04,east,pen,stationery,20,15.0
1008,2024-03-04,north,bottle,accessories,4,120.0यह पिछले अध्यायों की orders.csv के वही आठ ऑर्डर्स हैं, जिनमें चार चीज़ें बिगड़ी हुई हैं: ऑर्डर 1002 में कोई quantity नहीं है, ऑर्डर 1004 में कोई branch नहीं है और इसके price में N/A लिखा है, और ऑर्डर 1006 के price में - है।
कोड लिखने से पहले
मिसिंग डेटा एकमात्र ऐसा विषय है जहाँ पहले कोड लिखना सक्रिय रूप से ख़तरनाक होता है: dropna() और fillna() दोनों बिना किसी शिकायत के चलते हैं और दोनों आपके उत्तर को बदल देते हैं। इसलिए काम पहले देखने और निर्णय लेने से शुरू होता है।
1. सवाल को एक वाक्य में कहें। "सप्ताह के लिए बेची गई कुल मात्रा और कुल राजस्व (quantity × price) क्या था — और कितने ऑर्डर्स को गिना नहीं जा सका?" यह अंतिम हिस्सा बहुत महत्वपूर्ण है। एक कुल योग जो चुपचाप दो ऑर्डर्स को छोड़ देता है, वह उस कुल योग के समान नहीं है जो कहता है कि "8 में से 6 ऑर्डर्स गिने गए"।
2. किसी भी अन्य चीज़ से पहले देखें कि क्या आया है। shape और info() मिलकर आपको एक साथ हर कॉलम के बारे में बताते हैं:
print(orders.shape)
orders.info()(8, 7)
<class 'pandas.DataFrame'>
RangeIndex: 8 entries, 0 to 7
Data columns (total 7 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 order_id 8 non-null int64
1 date 8 non-null str
2 branch 7 non-null str
3 product 8 non-null str
4 category 8 non-null str
5 quantity 7 non-null float64
6 price 7 non-null str
dtypes: float64(1), int64(1), str(5)
memory usage: 580.0 bytesकॉलम-दर-कॉलम इसकी तुलना अपनी अपेक्षा से करें:
branch— 8 non-nullstrकी अपेक्षा थी, 7 non-null मिले। एक शाखा का नाम गायब है।quantity— 8 non-nullint64की अपेक्षा थी, 7 non-nullfloat64मिले। एक मात्रा गायब है, और यही कारण है कि यहfloat64बन गया (नीचे समझाया गया है)।price— 8 non-nullfloat64की अपेक्षा थी, 7 non-nullstrमिले। एक कीमत गायब है और कॉलम में कुछ टेक्स्ट आ गया है।
उस सूची की दो चीज़ें तथ्यों के बजाय सुराग हैं। किसी पूर्ण संख्या (integer) वाले कॉलम का float64 दिखना लगभग हमेशा एक रिक्ति का संकेत होता है। किसी संख्या वाले कॉलम का str दिखना हमेशा यह बताता है कि उसमें टेक्स्ट मिल गया है — और संख्या कॉलम में टेक्स्ट आमतौर पर एक छिपा हुआ "मिसिंग" मार्कर होता है।
3. अपनी मनचाही आउटपुट का एक प्रारूप (sketch) बनाएं। कुछ इस तरह:
Missing values per column: branch 1, quantity 1, price 2
Orders counted for revenue: 6 of 8
Total quantity (known): 76
Total revenue (counted): ...ध्यान दें कि यह कुल योग के साथ-साथ रिक्तियों की भी रिपोर्ट करता है। रिपोर्ट पढ़ने वाले को पता होना चाहिए कि राजस्व का यह आंकड़ा एक निचली सीमा (lower bound) है।
4. प्रत्येक रिक्ति के लिए उपकरण तय करें — कोड लिखने से पहले। प्रत्येक कॉलम के लिए पूछें: यहाँ खाली सेल का क्या अर्थ है, और क्या इसके बिना सवाल का जवाब दिया जा सकता है?
branch— रिक्ति का अर्थ है ऑर्डर हुआ था लेकिन शाखा दर्ज नहीं की गई। निर्णय:"Unknown"से भरें (fill), क्योंकि ऑर्डर वास्तविक है और इसका पैसा अभी भी गिना जाना चाहिए।quantity— रिक्ति का अर्थ है हम नहीं जानते कि कितने बेचे गए। निर्णय: इसे अनुपस्थित ही रखें (keep) और पंक्ति को राजस्व से बाहर रखें, क्योंकि अपनी ओर से कोई संख्या (0? या औसत?) गढ़ने से बिक्री की झूठी कल्पना बन जाएगी।price— रिक्ति का अर्थ है हम नहीं जानते कि यह कितने में बिका। निर्णय: उसी कारण से इसे अनुपस्थित ही रखें (keep) और राजस्व से बाहर रखें — और रिपोर्ट करें कि कितनी पंक्तियाँ बाहर रखी गईं।
5. तय करें कि आप बाद में क्या जाँचेंगे। मान हटाने या बदलने वाले प्रत्येक चरण के बाद, मिसिंग काउंट और shape को दोबारा प्रिंट करें। यदि कोई dropna() आपको 8 पंक्तियों से घटाकर 2 पर ले आता है, तो आप उसे रिपोर्ट बाहर जाने के बाद नहीं, बल्कि तुरंत देखना चाहेंगे।
इस अध्याय का शेष भाग आपको बारी-बारी से प्रत्येक उपकरण देता है, और अंत में हम इस योजना को एक प्रोग्राम में पिरोते हैं।
"अनुपस्थित" कैसा दिखता है: NaN
जब पांडास को कोई खाली सेल मिलता है, तो वह खाली स्ट्रिंग या शून्य संग्रहीत नहीं करता। वह NaN ("not a number") नामक एक विशेष फ्लोटिंग-पॉइंट मान संग्रहीत करता है। आप किसी Series में None डालकर खुद भी इसे बना सकते हैं:
import pandas as pd
quantity = pd.Series([12, None, 2])
print(quantity)0 12.0
1 NaN
2 2.0
dtype: float64इसके dtype को देखें। आपने इसे दो पूर्णांक और एक रिक्ति दी, और बदले में 12.0 व 2.0 के साथ float64 मिला। info() के सुराग के पीछे यही कारण है: NaN एक float मान है, और एक क्लासिक पूर्णांक कॉलम के पास इसे रखने का कोई तरीका नहीं होता। इसलिए जिस क्षण एक सेल खाली होता है, पांडास NaN के लिए जगह बनाने के लिए पूरे कॉलम को float64 में बदल देता है। यही कारण है कि हमारी फ़ाइल में quantity 12.0, 2.0 आदि के रूप में प्रिंट हुआ।
टेक्स्ट कॉलम अलग तरह से व्यवहार करते हैं। एक str कॉलम डेटा टाइप बदले बिना अनुपस्थित मान को रख सकता है:
branch = pd.Series(["north", None, "east"])
print(branch)0 north
1 NaN
2 east
dtype: strयह अभी भी NaN प्रिंट करता है, और dtype str ही रहता है। इसलिए पांडास 3 में आप संख्या और टेक्स्ट दोनों कॉलमों में NaN पाएंगे, और वही उपकरण दोनों में इसे खोजते हैं।
NaN किसी के बराबर नहीं है — यहाँ तक कि खुद के भी नहीं
अनुपस्थित मानों के बारे में यह सबसे आश्चर्यजनक तथ्य है, और यह उन्हें खोजने के सबसे स्वाभाविक तरीके को तोड़ देता है:
import numpy as np
import pandas as pd
orders = pd.read_csv("orders_raw.csv")
print(np.nan == np.nan)
print((orders["branch"] == np.nan).sum())False
0NaN का अर्थ है "कोई ऐसा मान जिसे हम नहीं जानते"। दो अज्ञात मान समान हैं या नहीं यह ज्ञात नहीं है, इसलिए तुलना हर बार False देती है। इससे == np.nan एक ऐसा परीक्षण बन जाता है जो कभी किसी चीज़ से मेल नहीं खाता: branch कॉलम में एक रिक्ति है, और गिनती 0 कहती है। इसके बजाय सीधे पांडास से पूछें।
कभी भी==से अनुपस्थित मान न खोजें।== np.nanबिना किसी एरर के चलता है और हमेशा कुछ नहीं पाता, जिससे रिक्तियों से भरा कॉलम भी पूर्ण दिखाई देता है।isna()का उपयोग करें।
अनुपस्थित मान खोजना: isna()
isna() वह सवाल पूछता है जो == नहीं पूछ सकता: "क्या यह सेल अनुपस्थित है?" किसी कॉलम पर यह True/False का एक कॉलम देता है — एक मास्क, ठीक फ़िल्टरिंग वाले अध्याय के मास्क की तरह:
print(orders["branch"].isna())0 False
1 False
2 False
3 True
4 False
5 False
6 False
7 False
Name: branch, dtype: boolपूरे DataFrame पर यह प्रत्येक सेल के लिए यही करता है। हालाँकि, आप शायद ही कभी आठ पंक्तियों के True/False को पढ़ना चाहेंगे। आपको एक गिनती चाहिए, और चूँकि True को 1 गिना जाता है, .sum() वह गिनती दे देता है:
print(orders.isna().sum())order_id 0
date 0
branch 1
product 0
category 0
quantity 1
price 1
dtype: int64किसी भी नई फ़ाइल पर info() के बाद यही लाइन चलानी चाहिए। यह Non-Null Count वाली ही जानकारी है, जिसे सीधे रिक्तियों की गिनती करने के लिए उलट दिया गया है।
लेकिन price को देखें: यह 1 कहता है। हम जानते हैं कि दो कीमतें ख़राब हैं — N/A और -। इस बात को ध्यान में रखें; हम अगले भाग में इस पर वापस आएंगे।
कौन सी पंक्तियाँ अपूर्ण हैं?
गिनती आपको बताती है कि कितने हैं; यह देखने के लिए कि कौन से हैं, मास्क का उपयोग फ़िल्टर के रूप में करें। isna().any(axis=1) प्रत्येक पंक्ति के लिए पूछता है, "क्या इस पंक्ति में कुछ भी अनुपस्थित है?":
incomplete = orders[orders.isna().any(axis=1)]
print(incomplete)order_id date branch product category quantity price
1 1002 2024-03-01 south notebook stationery NaN 60.0
3 1004 2024-03-02 NaN bottle accessories 7.0 NaNaxis=1 का अर्थ है प्रत्येक कॉलम में नीचे जाने के बजाय "प्रत्येक पंक्ति के आर-पार देखना"। और ध्यान दें कि इस सूची में कौन नहीं है: ऑर्डर 1006, जिसकी कीमत - है। जहाँ तक पांडास को पता है, वह पंक्ति पूर्ण है। इस विचार को थामे रखें। वास्तविक पंक्तियों को देखना उस अतिरिक्त लाइन के लायक है: इसी तरह आप नोटिस करते हैं कि, मान लीजिए, प्रत्येक अनुपस्थित मात्रा एक ही शाखा से आती है, जो पांडास के बजाय उस शाखा के लोगों से पूछने का सवाल है।
गिनती के बजाय अनुपात (share) के रूप में
आठ पंक्तियों के साथ गिनती ठीक है। अस्सी हज़ार के साथ, "312 गायब" का बहुत कम अर्थ है जब तक कि आप यह न जान लें कि यह 0.4% है। एक True/False कॉलम का .mean() मान True का अनुपात होता है:
print(orders.isna().mean())order_id 0.000
date 0.000
branch 0.125
product 0.000
category 0.000
quantity 0.125
price 0.125
dtype: float64branch, quantity और price के आठ सेल्स में से एक सेल गायब है — प्रत्येक में 12.5%। 90% खाली कॉलम और 0.1% खाली कॉलम पूरी तरह से अलग निर्णयों की मांग करते हैं, और इसी तरह आप उन्हें अलग पहचानते हैं।
छिपे हुए अनुपस्थित मान: जब रिक्ति टेक्स्ट के रूप में लिखी हो
isna() ने एक अनुपस्थित कीमत गिनी, लेकिन दो कीमतें अनुपयोगी हैं। दूसरा ऑर्डर 1006 में - है। पांडास के लिए, - अनुपस्थित नहीं है — यह टेक्स्ट का एक पूर्ण रूप से मान्य टुकड़ा है। और संख्याओं के कॉलम में टेक्स्ट का एक टुकड़ा पूरे कॉलम को str बनाने के लिए पर्याप्त है, जो कि ठीक वही है जो info() ने दिखाया था।
यहाँ बताया गया है कि आप कैसे पता लगाते हैं कि वास्तव में कॉलम में क्या है:
print(orders["price"].unique())<StringArray>
['15.0', '60.0', '850.0', nan, '8.0', '-', '120.0']
Length: 7, dtype: strप्रत्येक मान कोट्स में है — वे सभी टेक्स्ट हैं, जिसमें '15.0' भी शामिल है। और साथ-साथ दो प्रकार की रिक्तियाँ हैं: nan, जिसे पांडास ने पहचाना, और '-', जिसे उसने नहीं पहचाना।
N/A nan क्यों बना लेकिन - क्यों नहीं? क्योंकि read_csv में स्ट्रिंग्स की एक अंतर्निहित सूची होती है जिसे वह मिसिंग मानता है: एक खाली सेल, NA, N/A, n/a, NaN, null, None और कुछ अन्य। -, ?, missing, 0 और unknown उस सूची में नहीं हैं। डेटा टाइप करने वाले व्यक्ति ने "अज्ञात" के लिए जो कुछ भी इस्तेमाल किया, वही तय करता है कि आपको किस तरह की रिक्ति मिलती है।
छिपे हुए मानों को वास्तविक NaN में बदलने के दो तरीके हैं।
उपाय 1: read_csv को मार्कर के बारे में बताएं — na_values=
यदि आप मार्कर जानते हैं, तो सबसे साफ़ समाधान फ़ाइल पढ़ने के क्षण में ही होता है। na_values= आपकी स्ट्रिंग्स को अंतर्निहित सूची में जोड़ देता है:
orders = pd.read_csv("orders_raw.csv", na_values=["-"])
print(orders["price"].dtype)
print(orders.isna().sum())float64
order_id 0
date 0
branch 1
product 0
category 0
quantity 1
price 2
dtype: int64पहले की गिनती से इसकी तुलना करें। branch और quantity अपरिवर्तित हैं — na_values= सूची में जोड़ता है, उसे बदलता नहीं है, इसलिए खाली सेल्स और N/A अभी भी पहचाने जाते हैं। जो बदला है वह है price: यह अब float64 है, और यह 2 मिसिंग मान रिपोर्ट करता है — N/A और - दोनों अब वास्तविक NaN हैं। कॉलम फिर से संख्यात्मक है, इसलिए इस पर अंकगणित का मतलब वास्तव में अंकगणित होता है।
ध्यान दें कि na_values=["-"] प्रत्येक कॉलम पर लागू होता है। यहाँ हम यही चाहते हैं। यदि कोई - कहीं और एक वास्तविक मान हो सकता है — मान लीजिए कोई उत्पाद कोड — तो इसके बजाय एक dict पास करें, na_values={"price": ["-"]}, और केवल price कॉलम प्रभावित होगा।
उपाय 2: बाद में रूपांतरित करें — pd.to_numeric(errors="coerce")
कभी-कभी आप पहले से नहीं जानते कि कॉलम में कौन सी अजीब स्ट्रिंग्स हैं, या फ़ाइल किसी और के कोड द्वारा पढ़ी गई थी। तब आप पढ़ने के बाद कॉलम को रूपांतरित करते हैं। पहले देखें कि एक सादा रूपांतरण क्या करता है:
raw = pd.read_csv("orders_raw.csv")
price = pd.to_numeric(raw["price"])ValueError: Unable to parse string "-" at position 5वह एरर उपयोगी है: यह आपत्तिजनक मान और उसकी स्थिति का नाम बताता है। errors="coerce" जोड़ने का अर्थ है "वह सब कुछ जिसे आप संख्या में नहीं बदल सकते, उसे NaN बना दें":
price = pd.to_numeric(raw["price"], errors="coerce")
print(price)0 15.0
1 60.0
2 850.0
3 NaN
4 8.0
5 NaN
6 15.0
7 120.0
Name: price, dtype: float64टेबल को वास्तव में बदलने के लिए, परिणाम को वापस कॉलम में असाइन करें — to_numeric एक नई Series लौटाता है और raw को अपरिवर्तित छोड़ देता है:
raw["price"] = pd.to_numeric(raw["price"], errors="coerce")
print(raw["price"].dtype, raw["price"].isna().sum())float64 2कौन सा उपाय चुनें
errors="coerce" शक्तिशाली और सीधा है। यह गैर-संख्यात्मक किसी भी चीज़ को NaN में बदल देता है — जिसमें 1O0 (अक्षर O) या हज़ारों के कॉमा के साथ 1,200 के रूप में गलत टाइप की गई वास्तविक कीमत भी शामिल है। वे मिसिंग मान नहीं हैं; वे टाइपो वाले मान हैं, और उन्हें जबरन coerce करने से जानकारी चुपचाप नष्ट हो जाती है।
इसलिए आदत यह होनी चाहिए: पहले गिनें, बाद में गिनें, और अंतर की व्याख्या करें।
- आप मार्कर जानते हैं (
-,?,missing):read_csvमेंna_values=का उपयोग करें। यह सटीक है — केवल वही स्ट्रिंग मिसिंग बनती है। - अज्ञात कचरा है, और कॉलम का संख्यात्मक होना अनिवार्य है:
pd.to_numeric(errors="coerce")का उपयोग करें। यह सब कुछ पकड़ लेता है, इसलिए जाँचें कि इसने क्या पकड़ा। - कुछ मान टाइपो हैं जिन्हें आप सुधार सकते हैं: पहले
unique()को देखें, उन्हें ठीक करें, फिर रूपांतरित करें। Coerce करने से वास्तविक डेटा नष्ट हो जाएगा।
Coerce करने से पहले, raw["price"].unique() ने ठीक एक गैर-संख्यात्मक स्ट्रिंग ('-') और एक nan दिखाया था। Coerce करने के बाद, ठीक 2 NaN हैं। संख्याएं सहमत हैं, इसलिए कुछ और नहीं खोया। वह जाँच डेटा को साफ़ करने और उसे नुकसान पहुँचाने के बीच का पूरा अंतर है।
यहाँ से आगे हम na_values=["-"] के साथ पढ़ी गई फ़ाइल का उपयोग करते हैं।
गणना में पांडास NaN के साथ कैसा व्यवहार करता है
अब जब रिक्तियाँ वास्तविक NaN बन गई हैं, तो आपको यह जानना होगा कि पांडास उनके साथ क्या करता है — क्योंकि वह रुककर आपसे नहीं पूछता।
कॉलम सारांश NaN को छोड़ देते हैं
orders = pd.read_csv("orders_raw.csv", na_values=["-"])
q = orders["quantity"]
print("len :", len(q))
print("count:", q.count())
print("sum :", q.sum())
print("mean :", q.mean())
print("check:", q.sum() / q.count())len : 8
count: 7
sum : 76.0
mean : 10.857142857142858
check: 10.857142857142858याद रखने योग्य तीन व्यवहार:
lenपंक्तियों को गिनता है;count()मानों को गिनता है। उनके बीच का अंतर रिक्तियों की संख्या है। यहinfo()मेंNon-Null Countके समान विचार है।sum()NaNको छोड़ देता है। यह ज्ञात सात मात्राओं को जोड़ता है। यह उचित है — लेकिन इसका मतलब है कि "76" सभी ऑर्डर्स का नहीं, बल्कि केवल ज्ञात मात्राओं का कुल योग है।mean()count()से विभाजित करता है,lenसे नहीं। औसत ज्ञात सात मानों पर निकाला गया है। आप आमतौर पर यही चाहते हैं; रिक्ति को 0 मानने से अकारण ही औसत नीचे गिर जाता।
यदि आप चाहते हैं कि कुछ भी गायब होने पर आपको सूचित किया जाए, तो skipna=False किसी भी मान के NaN होते ही परिणाम को NaN बना देता है:
print(q.sum(skipna=False))nanकॉलमों के बीच अंकगणित NaN को फैलाता है
सारांश रिक्तियों को छोड़ देते हैं; अंकगणित उन्हें फैलाता है। NaN के साथ संयुक्त कुछ भी NaN देता है, क्योंकि "अज्ञात कीमत का 7 गुना" खुद अज्ञात है:
revenue = orders["quantity"] * orders["price"]
print(revenue)
print("total:", revenue.sum())
print("rows counted:", revenue.count(), "of", len(revenue))0 180.0
1 NaN
2 1700.0
3 NaN
4 240.0
5 NaN
6 300.0
7 480.0
dtype: float64
total: 2900.0
rows counted: 5 of 8दोनों व्यवहारों को एक साथ रखें और आप अध्याय की शुरुआत का जाल देख सकते हैं। revenue.sum() ने बिना किसी चेतावनी के 2900.0 प्रिंट किया — लेकिन आठ में से तीन ऑर्डर्स ने इसमें कुछ भी योगदान नहीं दिया। ऑर्डर 1002 में quantity गायब है, 1004 और 1006 में price गायब है। कुल योग केवल तभी ईमानदार होता है जब आप कहें "8 में से 5 ऑर्डर्स"। इसीलिए योजना में प्रत्येक कुल योग के बगल में एक गिनती मांगी गई थी।
अनुपस्थित मान हटाना: dropna()
dropna() उन पंक्तियों को हटा देता है जिनमें NaN होता है। बिना किसी तर्क के यह किसी भी कॉलम में किसी भी रिक्ति वाली प्रत्येक पंक्ति को हटा देता है:
print(orders.shape)
print(orders.dropna().shape)(8, 7)
(5, 7)आठ में से तीन पंक्तियाँ चली गईं — और ऑर्डर 1004 इसलिए चला गया क्योंकि इसकी branch गायब थी, भले ही मात्रा के सवाल के लिए यह पूरी तरह से अच्छी पंक्ति थी। एक सादे dropna() की यही समस्या है: यह "अपूर्ण कुछ भी हटा दें" का उत्तर देता है, जो शायद ही कभी मुख्य सवाल होता है।
subset= — केवल वही कॉलम जिनकी प्रश्न को आवश्यकता है
बताएं कि किन कॉलमों का मौजूद होना अनिवार्य है:
known_qty = orders.dropna(subset=["quantity"])
print(known_qty.shape)
print(known_qty["quantity"].sum())(7, 7)
76.0केवल ऑर्डर 1002 हटाया जाता है — एकमात्र पंक्ति जहाँ मात्रा वास्तव में अज्ञात है। राजस्व के लिए दोनों कॉलमों की आवश्यकता होती है:
countable = orders.dropna(subset=["quantity", "price"])
print(countable[["order_id", "quantity", "price"]])order_id quantity price
0 1001 12.0 15.0
2 1003 2.0 850.0
4 1005 30.0 8.0
6 1007 20.0 15.0
7 1008 4.0 120.0इंडेक्स पर ध्यान दें: 0, 2, 4, 6, 7। dropna() मूल लेबलों को बनाए रखता है, ठीक वैसे ही जैसे फ़िल्टरिंग करती है, ताकि आप हमेशा किसी पंक्ति को फ़ाइल में वापस ट्रैक कर सकें। यदि आपको फिर से 0, 1, 2… चाहिए, तो .reset_index(drop=True) जोड़ें।
how="all" — केवल वे पंक्तियाँ जो पूरी तरह खाली हैं
निर्यात की गई स्प्रेडशीट अक्सर कुछ ऐसी पंक्तियों के साथ समाप्त होती हैं जो हर कॉलम में खाली होती हैं। how="all" केवल उन्हीं को हटाता है और किसी भी ऐसी पंक्ति को छोड़ देता है जिसमें एक भी मान हो:
print(orders.dropna(how="all").shape)(8, 7)यहाँ कुछ भी नहीं हटाया गया — हमारी कोई भी पंक्ति पूरी तरह से खाली नहीं है — जिसकी आप ठीक-ठीक पुष्टि करना चाहते हैं।
dropna() एक नई टेबल लौटाता है
अधिकांश पांडास मेथड्स की तरह, dropna() orders को नहीं बदलता; यह आपको एक नया DataFrame सौंपता है। यदि आप एक पंक्ति पर अकेले orders.dropna() लिखते हैं, तो orders को कुछ नहीं होता। परिणाम को असाइन करें — एक नए नाम पर यदि आपको अभी भी पूरी टेबल की आवश्यकता होगी, जो हमारी जैसी रिपोर्ट में होगी (यह बताने के लिए कि कितने हटाए गए थे)।
अनुपस्थित मान बदलना: fillna()
कभी-कभी किसी रिक्ति का एक समझदारी भरा प्रतिस्थापन होता है। जहाँ NaN था, fillna() वहाँ एक मान रख देता है।
एक कॉलम के लिए एक मान
अनुपस्थित शाखा सबसे स्पष्ट मामला है। ऑर्डर हुआ और उसका पैसा वास्तविक है; हम बस यह नहीं जानते कि किस शाखा ने इसे लिया। इसे एक लेबल से भरने से पंक्ति सुरक्षित रहती है और बाद में शाखा द्वारा की जाने वाली किसी भी गिनती में रिक्ति स्पष्ट दिखाई देती है:
orders["branch"] = orders["branch"].fillna("Unknown")
print(orders["branch"].tolist())['north', 'south', 'north', 'Unknown', 'north', 'south', 'east', 'north']उस लाइन का आकार महत्वपूर्ण है: कॉलम लें, fillna करें, और उसे उसी कॉलम में वापस असाइन करें। fillna एक नई Series लौटाता है; सामने orders["branch"] = के बिना, कुछ भी संग्रहीत नहीं होता है।
अलग-अलग कॉलम के लिए अलग मान: एक dict
एक साथ कई कॉलमों को उनके अपने मानों से भरने के लिए {column: value} की एक डिक्शनरी पास करें। जिन कॉलमों का नाम नहीं दिया गया है उन्हें अछूता छोड़ दिया जाता है:
orders = pd.read_csv("orders_raw.csv", na_values=["-"])
filled = orders.fillna({"branch": "Unknown", "quantity": 0})
print(filled.isna().sum())order_id 0
date 0
branch 0
product 0
category 0
quantity 0
price 2
dtype: int64price में अभी भी इसकी दो रिक्तियाँ हैं, क्योंकि हमने इसका उल्लेख नहीं किया। orders.fillna(0) की तुलना में dict स्वरूप का यही लाभ है, जो कीमतों सहित हर कॉलम को 0 से भर देता — और 0 की कीमत का मतलब एक मुफ्त बैग है।
भरने से उत्तर बदल जाता है
मात्रा को 0 से भरना हानिरहित लग रहा था। औसतों की तुलना करें:
print("skip the gap :", orders["quantity"].mean())
print("fill with 0 :", orders["quantity"].fillna(0).mean())
print("fill with median:", orders["quantity"].fillna(orders["quantity"].median()).mean())skip the gap : 10.857142857142858
fill with 0 : 9.5
fill with median: 10.375एक कॉलम से तीन अलग-अलग औसत, और एकमात्र अंतर यह है कि आपने अज्ञात मात्रा का क्या अर्थ माना:
- 0 कहता है "इस ऑर्डर में कुछ नहीं बिका"। ऑर्डर 1002 के लिए, एक नोटबुक ऑर्डर जिसमें निश्चित रूप से कुछ बिका था, यह गलत है, और यह औसत को नीचे खींचता है।
- माध्यिका (median) (यहाँ 7) कहती है "एक सामान्य ऑर्डर मान लें"। यह औसत को लगभग स्थिर रखती है, लेकिन कुल योग में अब 7 नोटबुक शामिल हैं जिन्हें किसी ने नहीं गिना।
- छोड़ना (skipping) कहता है "हम नहीं जानते, इसलिए इसे बाहर छोड़ दें"। औसत उन सात ऑर्डर्स पर है जिन्हें हम जानते हैं।
सामान्य तौर पर इनमें से कोई भी "सही" नहीं है। प्रत्येक थोड़ा अलग सवाल का जवाब देता है। यही कारण है कि यह निर्णय किसी भी डिफ़ॉल्ट व्यवहार के बजाय आपकी योजना में लिखित रूप में होना चाहिए।
एक नियम जो अधिकांश समय काम करता है:
- एक लेबल (branch, category, name):
"Unknown"या इसी तरह के मान से भरें। पंक्ति रखी जाती है, और रिक्ति अपने समूह के रूप में दिखाई देती है। - एक गिनती जहाँ खाली होने का वास्तव में अर्थ कुछ नहीं है (रिटर्न, शिकायतें):
0से भरें — लेकिन केवल तभी जब स्रोत का वास्तव में खाली होने से अर्थ शून्य हो। - एक माप या पैसा (बेची गई मात्रा, कीमत, अंक):
NaNरखें, इसे गणना से बाहर रखें, और रिपोर्ट करें कि कितने बाहर रखे गए थे। कल्पित संख्याएँ कल्पित कुल योग बन जाती हैं। - सवाल के लिए आवश्यक कॉलम, लेकिन अधिकतर मौजूद: केवल उस सवाल के लिए
dropna(subset=[...])का उपयोग करें। एक छोटा सा नुकसान, और एक ईमानदार उत्तर।
पूर्णांक (whole numbers) वापस पाना
भरने के बाद, quantity में कोई रिक्ति नहीं है, लेकिन यह अभी भी float64 है — पांडास इसे अपने आप वापस नहीं बदलता। एक बार जब कोई NaN नहीं बचता है, तो astype(int) काम करता है:
filled["quantity"] = filled["quantity"].astype(int)
print(filled["quantity"].tolist())
print(filled["quantity"].dtype)[12, 0, 2, 7, 30, 1, 20, 4]
int64भरने से पहले इसे आज़माएं और यह विफल हो जाता है, क्योंकि ऐसा कोई पूर्णांक नहीं है जिसका अर्थ "अज्ञात" हो — नीचे "जब चीज़ें काम नहीं करतीं" देखें।
एक संपूर्ण उदाहरण
clean_orders.py "कोड लिखने से पहले" की योजना को एक प्रोग्राम में रखता है: ज्ञात मार्कर के साथ पढ़ें, प्रत्येक रिक्ति की रिपोर्ट करें, शाखा भरें, जिन पंक्तियों को नहीं गिना जा सकता उन्हें बाहर करें, और कुल योग के साथ यह रिपोर्ट करें कि वे कितने ऑर्डर्स को कवर करते हैं।
import pandas as pd
orders = pd.read_csv("orders_raw.csv", na_values=["-"])
# 1. Look first
print("Rows, columns:", orders.shape)
missing = orders.isna().sum()
print("Missing per column:")
print(missing[missing > 0])
print()
# 2. Decide per column: branch -> label; quantity and price -> keep, exclude
orders["branch"] = orders["branch"].fillna("Unknown")
# 3. Only rows with both numbers can be counted for revenue
countable = orders.dropna(subset=["quantity", "price"])
skipped = orders[orders[["quantity", "price"]].isna().any(axis=1)]
revenue = countable["quantity"] * countable["price"]
print("Orders counted for revenue:", len(countable), "of", len(orders))
print("Total quantity (known): ", int(orders["quantity"].sum()))
print("Total revenue (counted): ", revenue.sum())
print()
print("Not counted - check these with the branch:")
print(skipped[["order_id", "branch", "quantity", "price"]])Rows, columns: (8, 7)
Missing per column:
branch 1
quantity 1
price 2
dtype: int64
Orders counted for revenue: 5 of 8
Total quantity (known): 76
Total revenue (counted): 2900.0
Not counted - check these with the branch:
order_id branch quantity price
1 1002 south NaN 60.0
3 1004 Unknown 7.0 NaN
5 1006 south 1.0 NaNइसे इस तरह क्यों लिखा गया है:
na_values=["-"]पहली ही पंक्ति में है।unique()को देखकर मार्कर एक बार पाया गया था। इसेread_csvमें रखने का मतलब है कि कॉलम शुरू से ही संख्यात्मक है, और प्रोग्राम पढ़ने वाला कोई भी व्यक्ति देखता है कि फ़ाइल किस मार्कर का उपयोग करती है।missing[missing > 0]केवल उन्हीं कॉलमों को प्रिंट करता है जिनमें रिक्तियाँ हैं। चालीस कॉलम वाली फ़ाइल पर, यह एक पठनीय रिपोर्ट और शून्यों की दीवार के बीच का अंतर है।- शाखा भरी गई है, संख्याएँ नहीं। यह योजना की निर्णय सूची है, जिसे कोड के रूप में लिखा गया है। कोई पाठक इससे असहमत हो सकता है, लेकिन वह इसे देख सकता है।
dropna(subset=...)उन दो कॉलमों का नाम देता है जिनकी राजस्व को आवश्यकता होती है, इसलिए ऑर्डर 1004 को इसकी गायब कीमत के लिए बाहर रखा गया है, इसकी गायब शाखा के लिए नहीं।- छूटी हुई पंक्तियों को प्रिंट किया जाता है, केवल गिना नहीं जाता। "3 ऑर्डर्स नहीं गिने गए" यह सवाल आमंत्रित करता है "कौन से?" — रिपोर्ट में इसका उत्तर दें। वे तीन ऑर्डर्स एक ऐसी सूची हैं जिसे कोई व्यक्ति वास्तव में जाकर ठीक कर सकता है।
- "Total quantity (known)" और "(counted)" बताते हैं कि संख्याएँ क्या हैं। दोनों निचली सीमाएं (lower bounds) हैं; ये लेबल किसी को भी उन्हें पूर्ण मानने से रोकते हैं।
जब चीज़ें काम नहीं करतीं
TypeError: Cannot perform reduction 'mean' with string dtype आपने ऐसे कॉलम के औसत की मांग की जिसे पांडास ने टेक्स्ट के रूप में पढ़ा — आमतौर पर इसलिए क्योंकि एक सेल में -, ? या missing जैसा मार्कर होता है। अपराधी को खोजने के लिए df["col"].unique() चलाएं, फिर या तो na_values=["-"] के साथ दोबारा पढ़ें या pd.to_numeric(df["col"], errors="coerce") से रूपांतरित करें।
raw = pd.read_csv("orders_raw.csv")
print(raw["price"].mean())TypeError: Cannot perform reduction 'mean' with string dtypesum() ने अंकों की एक लंबी स्ट्रिंग लौटाई, जैसे 15.060.0850.0... वही कारण, बदतर लक्षण: टेक्स्ट कॉलम पर sum() संख्याओं को जोड़ने के बजाय स्ट्रिंग्स को आपस में जोड़ देता है, और कोई एरर नहीं देता। ऐसा दिखने वाला कोई भी कुल योग str कॉलम से आया है। कुछ भी जोड़ने से पहले df.dtypes की जाँच करें।
ValueError: Unable to parse string "-" at position 5 pd.to_numeric को ऐसा टेक्स्ट मिला जिसे वह रूपांतरित नहीं कर सका, और उसने आपको बिल्कुल सही बताया कि क्या और कहाँ समस्या है। या तो errors="coerce" जोड़ें (unique() से यह जाँचने के बाद कि अजीब मान वास्तव में "अज्ञात" मार्कर हैं, टाइपो नहीं), या पहले टाइपो को ठीक करें।
(df["col"] == np.nan).sum() 0 कहता है, लेकिन रिक्तियाँ हैं NaN कभी भी किसी के बराबर नहीं होता, यहाँ तक कि खुद के भी नहीं, इसलिए == np.nan किसी भी सेल से मेल नहीं खाता। df["col"].isna() का उपयोग करें।
IntCastingNaNError: Cannot convert non-finite values (NA or inf) to integer आपने ऐसे कॉलम पर astype(int) कॉल किया जिसमें अभी भी NaN है। "अज्ञात" अर्थ वाला कोई पूर्णांक नहीं है। पहले रिक्तियों को भरें या हटाएं, फिर रूपांतरित करें:
orders = pd.read_csv("orders_raw.csv", na_values=["-"])
orders["quantity"] = orders["quantity"].astype(int)pandas.errors.IntCastingNaNError: Cannot convert non-finite values (NA or inf) to integer.Replace or remove non-finite values or cast to an integer typethat supports these values (e.g. 'Int64')(पांडास के अपने संदेश में "integer.Replace" और "typethat" में स्पेस छूटे हुए हैं।) संदेश में बड़े अक्षर I वाले 'Int64' का भी उल्लेख है: एक अलग पांडास पूर्णांक प्रकार जो अनुपस्थित मानों को रख सकता है। यह उपयोगी है, लेकिन पहले भरना या हटाना सरल समाधान है जिसे अपनाया जाना चाहिए।
ChainedAssignmentError — और रिक्ति अभी भी वहीं है आपने inplace=True वाला तरीका लिखा जो आपको कई पुराने ट्यूटोरियल्स में मिलेगा:
orders = pd.read_csv("orders_raw.csv", na_values=["-"])
orders["quantity"].fillna(0, inplace=True)
print(orders["quantity"].isna().sum())1
ChainedAssignmentError: A value is being set on a copy of a DataFrame or Series through chained assignment using an inplace method.
Such inplace method 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).
For example, when doing 'df[col].method(value, inplace=True)', try using 'df.method({col: value}, inplace=True)' instead, to perform the operation inplace on the original object, or try to avoid an inplace operation using 'df[col] = df[col].method(value)'.
See the documentation for a more detailed explanation: https://pandas.pydata.org/pandas-docs/stable/user_guide/copy_on_write.htmlगिनती अभी भी 1 है: कुछ भी नहीं भरा गया था। पांडास 3 में, orders["quantity"] हमेशा अपनी खुद की कॉपी (Copy-on-Write) के रूप में व्यवहार करता है, इसलिए इसे "in place" भरने से वह कॉपी भर जाती है, जिसे बाद में फेंक दिया जाता है। पुराने पांडास में कभी-कभी यह काम कर जाता था, यही वजह है कि यह पैटर्न ऑनलाइन हर जगह है। इसके बजाय सीधे असाइनमेंट लिखें — यह हर संस्करण में काम करता है और स्पष्ट रूप से बताता है कि क्या बदलता है:
orders["quantity"] = orders["quantity"].fillna(0)
print(orders["quantity"].isna().sum())0dropna() ने आपकी अपेक्षा से कहीं अधिक पंक्तियाँ हटा दीं एक सादा dropna() किसी भी कॉलम में रिक्ति होने पर पंक्ति को हटा देता है — जिसमें अधिकतर खाली रहने वाला notes या discount कॉलम भी शामिल है जिसका आप उपयोग भी नहीं कर रहे थे। कई रिक्तियों वाले कॉलम को खोजने के लिए df.isna().sum() प्रिंट करें, फिर केवल अपने सवाल के लिए आवश्यक कॉलमों के साथ dropna(subset=[...]) का उपयोग करें।
TypeError: can't multiply sequence by non-int of type 'float' आपने एक संख्या कॉलम को एक टेक्स्ट कॉलम से गुणा किया (quantity * price जबकि price str है)। समाधान पहले एरर के समान ही है: पहले price को संख्यात्मक बनाएं।
चरण 4 / 6 — अनुमान
अपनी समझ की जाँच करें
एक quantity का मान खाली है। प्रिंट करने पर क्या दिखाई देगा?
from io import StringIO
import pandas as pd
# Stands in for orders_raw.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,,60.0
1003,2024-03-02,north,bag,accessories,2,850.0
1004,2024-03-02,,bottle,accessories,7,N/A
1005,2024-03-03,north,eraser,stationery,30,8.0
1006,2024-03-03,south,bag,accessories,1,-
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["quantity"].sum(), orders["quantity"].count(), len(orders))- A76 8 8
- B76.0 7 8
- Cnan 7 8
- D76.0 8 8
उद्देश्य उन ऑर्डर्स को खोजना है जिनमें कीमत (price) नहीं है। दो कीमतें गायब हैं। क्या प्रिंट होगा?
from io import StringIO
import pandas as pd
# Stands in for orders_raw.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,,60.0
1003,2024-03-02,north,bag,accessories,2,850.0
1004,2024-03-02,,bottle,accessories,7,N/A
1005,2024-03-03,north,eraser,stationery,30,8.0
1006,2024-03-03,south,bag,accessories,1,-
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), na_values=["-"])
missing_price = orders[orders["price"] == float("nan")]
print(len(missing_price))- A2
- B1
- C0 — `NaN` किसी के बराबर नहीं होता, यहाँ तक कि स्वयं के भी नहीं, इसलिए `==` इसे कभी नहीं ढूंढ पाता
- Dएक `TypeError`: आप `nan` के साथ तुलना नहीं कर सकते
price कॉलम को str के रूप में पढ़ा गया क्योंकि एक सेल में - और दूसरे में N/A है। कौन सी लाइन इसे संख्याओं में बदल देगी और दोनों रिक्तियों को NaN बना देगी?
- Aorders["price"] = orders["price"].astype(float)
- Borders["price"] = pd.to_numeric(orders["price"], errors="coerce")
- Corders["price"] = orders["price"].fillna(0)
- Dorders["price"] = orders["price"].dropna()
उत्तर देने के लिए अकाउंट आवश्यक है
अपने उत्तर जाँचने के लिए साइन इन करें
प्रश्न ऊपर दिए गए हैं, और मन में उत्तर सोचना ही मुख्य कार्य है। सही उत्तर, व्याख्या और तीन-स्तरीय संकेत देखने के लिए साइन इन करें।
आपकी बारी
दुकान ने होम डिलीवरी शुरू कर दी है, और पहले सप्ताह का राइडर्स लॉग deliveries.csv के रूप में आता है। इसे ठीक इस तरह बनाएं:
order_id,branch,distance_km,fee,rider
2001,north,3.5,60,R1
2002,north,,60,R2
2003,south,8.0,?,R1
2004,east,12.0,120,
2005,north,2.0,40,R2
2006,south,6.5,90,?
2007,east,,110,R3
2008,north,4.0,60,R3मालिक पूछता है: डिलीवरी फीस में कुल कितना पैसा एकत्र किया गया, कितनी डिलीवरी में फीस गायब है, और प्रति किलोमीटर औसत फीस क्या है?
कोई भी कोड लिखने से पहले, unique() के साथ फ़ाइल को देखें और तय करें कि छिपा हुआ मार्कर क्या है। फिर deliveries_report.py लिखें जो:
- फ़ाइल को इस तरह पढ़े कि छिपा हुआ मार्कर हर उस कॉलम में वास्तविक मिसिंग मान बन जाए जहाँ वह दिखाई देता है
- प्रति कॉलम मिसिंग मानों की संख्या और अपूर्ण पंक्तियों को प्रिंट करे
- अनुपस्थित
riderको"unassigned"से भरे - ज्ञात कुल फीस, बिना फीस वाली डिलीवरी की संख्या, और प्रति किलोमीटर फीस — कुल फीस को कुल दूरी से विभाजित करके — प्रिंट करे, जिसकी गणना केवल उन्हीं डिलीवरी से की गई हो जहाँ फीस और दूरी दोनों ज्ञात हों
आपके प्रोग्राम द्वारा प्रिंट की जाने वाली अंतिम तीन पंक्तियाँ बिल्कुल यही होनी चाहिए:
Fees collected (known): 540.0
Deliveries with no fee: 1
Fee per km: 13.21 from 5 of 8 deliveriesकोड से पहले, "कोड लिखने से पहले" जैसी एक निर्णय सूची भी लिखें: distance_km, fee और rider में से प्रत्येक के लिए रिक्ति का क्या अर्थ है, और क्या आप भरेंगे, हटाएंगे या रखेंगे?
फिर एक प्रयोग आज़माएं: dropna(subset=...) के बजाय एक सादे dropna() के साथ प्रति किलोमीटर फीस की गणना करें। क्या संख्या बदलती है? इसने कितनी पंक्तियों का उपयोग किया, और क्यों?
समाधान
योजना पहले। सवाल के तीन भाग हैं: एक कुल योग, रिक्तियों की गिनती, और एक अनुपात। info() पढ़ने पर fee str के रूप में दिखेगा — एक फीस कॉलम संख्या होना चाहिए, इसलिए इसमें टेक्स्ट छिपा हुआ है। प्रति कॉलम निर्णय:
rider— डिलीवरी हुई, राइडर दर्ज नहीं किया गया।"unassigned"से भरें।fee— हम नहीं जानते कि क्या शुल्क लिया गया था।NaNरखें; ज्ञात फीस जोड़ें और अज्ञात की गिनती करें।distance_km— हम नहीं जानते कि कितनी दूरी थी।NaNरखें; उन पंक्तियों को प्रति-किमी अनुपात से बाहर रखें।
अनुपात के लिए, उसी डिलीवरी के लिए दोनों संख्याएं ज्ञात होनी चाहिए — अन्यथा आप सात डिलीवरी की फीस को छह डिलीवरी की दूरियों से विभाजित कर देंगे।
चरण 1 — मार्कर खोजने के लिए सरलता से पढ़ें और देखें:
import pandas as pd
raw = pd.read_csv("deliveries.csv")
print(raw.shape)
print(raw.dtypes)
print(raw["fee"].unique())
print(raw["rider"].unique())(8, 5)
order_id int64
branch str
distance_km float64
fee str
rider str
dtype: object
<StringArray>
['60', '?', '120', '40', '90', '110']
Length: 6, dtype: str
<StringArray>
['R1', 'R2', nan, '?', 'R3']
Length: 5, dtype: str? के कारण fee str है। और ? एक राइडर के रूप में भी दिखाई देता है — वही मार्कर, दो कॉलमों में इस्तेमाल हुआ। distance_km पहले से ही float64 है: इसकी रिक्तियाँ खाली सेल्स हैं, जिन्हें पांडास ने खुद पहचान लिया। चूँकि मार्कर हर जगह समान है, इसलिए पढ़ते समय na_values=["?"] दोनों कॉलमों को एक साथ ठीक कर देता है।
पूरा प्रोग्राम, deliveries_report.py:
import pandas as pd
deliveries = pd.read_csv("deliveries.csv", na_values=["?"])
print("Types after reading:")
print(deliveries.dtypes)
print()
print("Missing per column:")
print(deliveries.isna().sum())
print()
print("Incomplete rows:")
print(deliveries[deliveries.isna().any(axis=1)])
print()
deliveries["rider"] = deliveries["rider"].fillna("unassigned")
fee_total = deliveries["fee"].sum()
fee_missing = deliveries["fee"].isna().sum()
print("Fees collected (known):", fee_total)
print("Deliveries with no fee:", fee_missing)
both = deliveries.dropna(subset=["fee", "distance_km"])
per_km = both["fee"].sum() / both["distance_km"].sum()
print("Fee per km:", round(per_km, 2), "from", len(both), "of", len(deliveries), "deliveries")Types after reading:
order_id int64
branch str
distance_km float64
fee float64
rider str
dtype: object
Missing per column:
order_id 0
branch 0
distance_km 2
fee 1
rider 2
dtype: int64
Incomplete rows:
order_id branch distance_km fee rider
1 2002 north NaN 60.0 R2
2 2003 south 8.0 NaN R1
3 2004 east 12.0 120.0 NaN
5 2006 south 6.5 90.0 NaN
6 2007 east NaN 110.0 R3
Fees collected (known): 540.0
Deliveries with no fee: 1
Fee per km: 13.21 from 5 of 8 deliveriesसंख्याओं को एक बार हाथ से जाँचें, क्योंकि इसी तरह आप साफ़ किए गए कुल योग पर भरोसा करना सीखते हैं। ज्ञात फीस: 60 + 60 + 120 + 40 + 90 + 110 + 60 = 540, सात डिलीवरी से — 2003 में कोई नहीं है। अनुपात के लिए, dropna(subset=["fee", "distance_km"]) 2003 (कोई फीस नहीं) और 2002 व 2007 (कोई दूरी नहीं) को छोड़ देता है। मानसिक गणना पर भरोसा करने के बजाय पांडास से पूछें कि कौन सी पंक्तियाँ बची हैं और उनका योग क्या है:
print(both["order_id"].tolist())
print(both["fee"].sum(), both["distance_km"].sum())[2001, 2004, 2005, 2006, 2008]
370.0 28.0370 / 28.0 = 13.21, जो कि प्रोग्राम द्वारा प्रिंट की गई संख्या है। ध्यान दें कि अनुपात ने क्या नहीं किया: इसने सात फीस से 540 को छह डिलीवरी की दूरियों से विभाजित नहीं किया। दोनों संख्याएं एक ही पाँच पंक्तियों से आती हैं, जो कि "प्रति किलोमीटर" आंकड़े का कोई अर्थ होने का एकमात्र तरीका है।
दो और निर्णय ध्यान देने योग्य हैं। गणना से पहले rider को भरा गया था, लेकिन इसने इसे कभी प्रभावित नहीं किया — subset= ने केवल fee और distance_km को देखा। और फीस के कुल योग को "(known)" लेबल किया गया है, जिसके ठीक नीचे मिसिंग फीस की गिनती प्रिंट की गई है, ताकि कोई भी 540 को एकत्र की गई कुल राशि समझने की भूल न करे।
प्रयोग। एक सादा dropna() 2004 और 2006 को भी हटा देता है — जिनकी फीस और दूरी दोनों ठीक थीं — क्योंकि उनका rider गायब था। यदि आप rider भरने से पहले इसे टेबल पर चलाते हैं:
deliveries = pd.read_csv("deliveries.csv", na_values=["?"])
strict = deliveries.dropna()
print(strict["order_id"].tolist())
print(round(strict["fee"].sum() / strict["distance_km"].sum(), 2))[2001, 2005, 2008]
16.84केवल तीन डिलीवरी बचती हैं, और अनुपात 13.21 से उछलकर 16.84 हो जाता है — इसलिए नहीं कि कोई फीस बदली, बल्कि इसलिए कि दो सबसे लंबी यात्राएं (12 और 6.5 किमी), जो प्रति किलोमीटर सस्ती हैं, एक लापता नाम के कारण बाहर फेंक दी गईं। राइडर कॉलम का सवाल से कोई लेना-देना नहीं है, इसलिए इसे यह तय नहीं करना चाहिए कि कौन सी पंक्तियाँ गिनी जाएं। subset= इसी के लिए है।
Step 6 of 6
चुनौती — the chapter quiz
सरल से कठिन — दस प्रश्न, अंतिम वाले जानबूझकर चुनौतीपूर्ण बनाए गए हैं।
Sign in to take the quiz