अगर आप भी मेरी तरह हैं - तो आपने अपने डेटासेट में गुम हुए डेटा से कम से कम एक बार तो निपटा ही होगा। या दो बार। या एक से ज़्यादा बार...
कभी-कभी, उन कष्टप्रद NA को संभालने के लिए बस उन्हें हटाना होता है, यानी गायब डेटा वाली पंक्तियों को हटाना। हालाँकि, यह हमेशा इष्टतम नहीं हो सकता है, खासकर समय श्रृंखला डेटा के लिए, और वित्तीय के लिए तो और भी अधिक। बेशक, इस समस्या का अच्छी तरह से अध्ययन किया गया है, इसलिए हटाने के कई विकल्प मौजूद हैं।
मैं उनमें से कुछ पर गौर करूंगा (नीचे सूचीबद्ध) और उनके फायदे और नुकसान पर चर्चा करूंगा:
छोड़ने
एलओसीएफ (अंतिम अवलोकन आगे बढ़ाया गया)
माध्य (या समतुल्य) आरोपण
प्रक्षेप
स्पॉइलर अलर्ट: ऐसा कोई एक तरीका नहीं है जो सभी के लिए सही हो! मैं तर्क दूंगा कि LOCF आमतौर पर वित्त के लिए एक अच्छा विकल्प है, लेकिन इसकी कमियां भी हैं। इसे ध्यान में रखते हुए, मैं उन तरीकों और डेटा का वर्णन करूँगा जिनका उपयोग मैं उन्हें प्रदर्शित करने के लिए करूँगा।
नोट: यदि कोई अधिक स्पष्ट होना चाहे तो, 2-4 तक की सभी विधियां किसी न किसी आरोपण के उदाहरण हैं।
आइए कुछ उदाहरणों से शुरू करते हैं कि क्यों कोई व्यक्ति पहले स्थान पर गिरने के बारे में परवाह करेगा। उदाहरण के लिए, मैंने कुछ अति-सरलीकृत दैनिक स्टॉक मूल्य डेटा तैयार किया, यह मानते हुए कि यह बिना किसी बहाव के यादृच्छिक चाल का अनुसरण करता है (यानी औसत दीर्घकालिक मूल्य स्थिर रहना चाहिए) - सबसे सटीक नहीं है, लेकिन फिर भी एक सौम्य धारणा है।
np.random.seed(10) # needed for reproducibility price_moves = 3*pd.Series(np.random.randn(100)) # generating random "steps" with 0 mean price_vec = 100 + price_moves.cumsum() # generating brownian motion price_vec.plot()
खैर, कथानक काफी अच्छा लग रहा है।
मान लीजिए अब हम दैनिक मूल्य अंतर का अनुभवजन्य माध्य ज्ञात करना चाहते हैं -
price_vec.diff().mean() #sample mean >0.20030544816842052
जाहिर है कि यह गैर-शून्य है, जनरेटिंग श्रृंखला के विपरीत - लेकिन यह सिर्फ नमूना शोर है। अब तक तो सब ठीक है।
अब आइए कुछ डेटा बिंदुओं को हटाकर इस डेटा को थोड़ा विकृत करें:
price_vec_na_simple = price_vec.copy() price_vec_na_simple.iloc[90:95] = np.array([np.NaN, np.NaN, np.NaN, np.NaN, np.NaN]) # price_vec_na_simple.diff().mean() >0.1433356258183252
हमने तुरंत ही कुछ बातें नोटिस कीं -
माध्य किसी तरह गैर-एनए है, भले ही diff
वेक्टर में स्पष्ट रूप से एनएएस शामिल होंगे
माध्य उस माध्य से भिन्न है जो हमने पहले प्राप्त किया था
अब, #1 काफी आसान है - pd.mean
स्वचालित रूप से डिफ़ॉल्ट रूप से NA को हटा देता है।
लेकिन #2 के बारे में क्या? आइए हम इस बात पर पुनर्विचार करें कि हम क्या गणना कर रहे हैं।
यह दिखाना आसान है कि कम से कम NAs के बिना, औसत मूल्य अंतर बस (price_vec[99]-price_vec[0])/99
होना चाहिए - वास्तव में, जब हम मूल्य अंतरों को जोड़ते हैं, तो सभी "मध्यवर्ती" टुकड़े रद्द हो जाते हैं, जैसे (price_vec[1] - price_vec[0]) + (price_vec[2] - price_vec[1]) + ..
!
अब, गुम डेटा सम्मिलित होने के साथ, यदि हम पहले अंतर लेते हैं और फिर NA
छोड़ देते हैं, तो यह निरस्तीकरण टूट जाता है - कुछ आसान गणित से पता चलता है कि अब आप (price_vec[99] - price_vec[0] - price_vec[95] + price_vec[89])/93
गणना कर रहे हैं।
इसे दिखाने के लिए, ध्यान दें कि निम्नलिखित दो शब्द अब छोड़े गए हैं - price_vec[95] - price_vec[94]
और price_vec[90] - price_vec[89]
, क्योंकि (NA - any number)
का मूल्यांकन NA होता है और फिर उसे हटा दिया जाता है।
आइये इसकी पुष्टि करें:
(price_vec[99] - price_vec[0])/99 >0.20030544816842052 (price_vec[99] - price_vec[0] - price_vec[95] + price_vec[89])/93 >0.1433356258183252
अब, यह स्पष्ट हो गया है कि हम चीजों को कैसे ठीक कर सकते हैं - हमें सबसे पहले एनएएस को छोड़ना होगा और फिर diff
-
price_vec_na_simple.dropna().diff().mean() >0.21095999328376203
माध्य लगभग वहीं आ गया है जहां उसे होना चाहिए - एक छोटी सी विसंगति इसलिए हुई है क्योंकि अब माध्य में कम पद हैं - 99 के स्थान पर 94।
ठीक है, ऐसा लगता है कि अगर हम केवल माध्य की परवाह करते हैं, तो हम dropna
उपयोग करके ठीक हैं (जब तक हम इसे सही तरीके से करते हैं)? आखिरकार, 0.2
और 0.21
के बीच का अंतर स्पष्ट रूप से हमारे शोर सहनशीलता के भीतर है। खैर, बिल्कुल नहीं - आइए देखें क्यों।
LOCF का मतलब है लास्ट ऑब्जर्वेशन कैरीड फॉरवर्ड। इसके पीछे का विचार बहुत सरल है - अगर मैं कुछ समय अंतराल पर डेटा रिकॉर्ड करता हूं, जो नियमित हो भी सकता है और नहीं भी, अगर कुछ विशेष अंतराल का अवलोकन गायब है, तो हम बस यह मान लेते हैं कि हमारे चर में कुछ भी नहीं बदला है और इसे अंतिम गैर-गायब मान से बदल देते हैं (उदाहरण के लिए - [3, 5, NA, 8] → [3, 5, 5, 8])। कोई पूछ सकता है - पहले स्थान पर एक गायब अवलोकन के साथ अंतराल की परवाह क्यों करें, यानी इसे "ड्रॉपिंग" विधि की तरह न हटाएं? खैर, इसका उत्तर "ड्रॉपिंग" के अंतर्निहित दोष में निहित है जिसका मैंने ऊपर उल्लेख नहीं किया है।
मान लीजिए कि आप एक साथ कई मात्राएँ रिकॉर्ड करते हैं, खासकर वे जो आमतौर पर बहुत ज़्यादा तेज़ी से नहीं बदलती हैं - जैसे तापमान और आर्द्रता की प्रति घंटे की रिकॉर्डिंग। मान लीजिए कि आपके पास 10:00, 11:00 और 12:00 के लिए दोनों मान हैं, लेकिन 13:00 पर केवल आर्द्रता है। क्या आप बस उस "पंक्ति" को हटा देते हैं - यानी दिखावा करते हैं कि आपके पास 13:00 के लिए रीडिंग नहीं है? ठीक है, यह ठीक है अगर आपके पास केवल दो चर हैं - भले ही आपने अभी कुछ संभावित मूल्यवान जानकारी (13:00 आर्द्रता) को हटा दिया हो। लेकिन अगर आपके पास एक साथ कई ऐसी घटनाएँ या कई चर हैं, तो हटाने से आपके पास लगभग कोई डेटा नहीं रह सकता है!
एक बहुत ही आकर्षक विकल्प यह मान लेना है कि 12:00 और 13:00 के बीच तापमान में कुछ भी बदलाव नहीं हुआ है। आखिरकार, अगर कोई हमारे पास 12:30 बजे आता और हमसे पूछता - "वर्तमान तापमान क्या है", तो हम सही तरीके से 12:00 रीडिंग के साथ जवाब देते (अगर हम तुरंत नई रीडिंग प्राप्त करने में सक्षम नहीं हैं, तो निश्चित रूप से)। 13:00 मान के लिए समान तर्क का उपयोग क्यों न करें?
सबसे पहले, आइए पहले के डेटा पर अपने नए दृष्टिकोण का परीक्षण करें:
price_vec_na_simple.ffill().diff().mean() # ffill performs LOCF by default >0.20030544816842052
ऐसा लगता है कि हमने अपना पुराना मूल्य ठीक-ठाक प्राप्त कर लिया है! इसके अलावा, यदि आप मूल्य अंतर डेटा पर आगे शोध करना चाहते हैं - तो यह अब अधिक "व्यवस्थित" दिखता है क्योंकि इसमें हर दिन के लिए एक प्रविष्टि है, भले ही उनमें से पाँच प्रविष्टियाँ अब 0 हैं (क्यों? खुद देखने के लिए price_vec_na_simple.ffill().diff().iloc[90:95]
चलाने का प्रयास करें)।
इसके अलावा, वित्त में, गुम डेटा और आउटलाइयर डेटा अक्सर एक साथ आते हैं। मैं इसे स्पष्ट करना चाहूँगा:
#inflate two observations, delete three next ones price_moves_na[90] += 20 price_moves_na[91] += 30 price_moves_na[92] -= 50 # to "deflate" the price shock back price_vec_na = (100 + price_moves_na.cumsum()) price_vec_na[92:95] = [np.NaN, np.NaN, np.NaN] price_vec_na.tail(20).plot() price_vec_na.diff().dropna().mean() >0.7093365245831178
हम देख सकते हैं कि कीमत में तेज वृद्धि के बाद, डेटा लगातार 3 दिनों तक उपलब्ध नहीं है। यह इतना "कृत्रिम" उदाहरण नहीं है जितना यह लग सकता है! कल्पना करें कि स्पाइक के बाद ट्रेडिंग रुक गई, कम से कम इस विशेष एक्सचेंज पर। फिर चीजें थोड़ी शांत हो गईं, इसलिए कीमत सामान्य व्यवस्था में वापस आ गई। हो सकता है कि पर्दे के पीछे कुछ धीरे-धीरे चल रहा हो जिसने वास्तव में स्पाइक और स्पाइक के बाद की शांति के बीच बिंदुओं को "जोड़ा"। लेकिन आप यह नहीं जानते और इसके लिए आपके पास कोई डेटा नहीं है!
अगर हमारे पास कोई नया डेटा नहीं है तो सबसे स्वाभाविक धारणा क्या होगी? खैर, याद रखें कि हमारा डेटा-जनरेटिंग मॉडल मूल रूप से मूल्य परिवर्तनों पर आधारित था। इसलिए अगर कोई नया डेटा नहीं है, तो शायद कीमत बिल्कुल भी नहीं बदल रही है? यह बिल्कुल वही है जो LOCF (लास्ट ऑब्जर्वेशन कैरीड फॉरवर्ड) मानता है।
जिज्ञासु पाठक के लिए एक साइड नोट - शायद इस बात पर एक अधिक मौलिक दृष्टिकोण कि LOCF स्टॉक मूल्य डेटा के लिए विशेष रूप से उपयुक्त क्यों है, यह है कि इसे आमतौर पर मार्टिंगेल के रूप में मॉडल किया जाता है। मोटे तौर पर, एक मार्टिंगेल कुछ ऐसा है जहां कल के लिए हमारा सबसे अच्छा अनुमान वह है जो हम आज देखते हैं, या E[x_{t+1} | x_t] = x_t
ठीक है, अब वास्तविक डेटा पर वापस आते हैं! आइए LOCF के परिणामों को दृश्य और संख्यात्मक दोनों रूप से देखें:
price_vec_na.ffill().tail(20).plot() price_vec_na.ffill().diff().mean() >0.20030544816842052
तुरंत, हम LOCF के पक्ष और विपक्ष को देखते हैं (काफी शाब्दिक रूप से)! एक के लिए, औसत वापस वहीं आ जाता है जहाँ हम "उम्मीद" करते हैं - यानी अपरिवर्तित अनुभवजन्य मूल्य। हालाँकि, हम एक बदसूरत अवधि पेश करते हैं जहाँ कीमत "सामान्य" के अनुरूप नहीं होती है और 94 और 95 दिनों के बीच कीमत में कृत्रिम गिरावट होती है।
आइए LOCF से प्राप्त परिणामों की तुलना (माध्य) आरोपण से करें। यह NA हैंडलिंग के लिए एक बहुत ही सामान्य विकल्प है, खासकर गैर-समय श्रृंखला डेटा के लिए। हालाँकि, अगर इसे भोलेपन से किया जाए, तो वित्तीय डेटा के लिए उपयोग किए जाने पर इसमें कई कमियाँ हैं।
यदि आप केवल समस्त-नमूना माध्य का उपयोग करते हैं, तो आप एक स्पष्ट भविष्य-दृष्टिकोण पूर्वाग्रह का परिचय देते हैं - अर्थात आप भूतकाल के मानों को आरोपित करने के लिए भविष्य के आंकड़ों का उपयोग करते हैं।
किसी प्रकार के लुक-बैक या रोलिंग माध्य का उपयोग करना निश्चित रूप से बेहतर है - हालांकि, यह कभी-कभी मार्टिंगेल "बेसलाइन" दृष्टिकोण के साथ तनाव में आ सकता है, जिसका हमने पहले वर्णन किया था।
आइए इस पर थोड़ा और विस्तार से नज़र डालें। मैं अपने पुराने डेटा पर लुक-बैक इम्प्यूटेशन का उपयोग करूँगा -
price_vec_na_impute = price_vec_na.copy() price_vec_na_impute[price_vec_na_impute.isna()] = price_vec_na.iloc[:90].mean() price_vec_na_impute.diff().mean() >0.20030544816842052
हम LOCF की तरह ही "सही" मूल्य परिवर्तन माध्य को पुनः प्राप्त करते हैं। लेकिन हम 91वें और 92वें दिन के बीच एक कृत्रिम मूल्य गिरावट पेश करते हैं जो कुछ मायनों में पहले की तुलना में भी बदतर है। आखिरकार, वह तब हुआ जब या उसके बाद चीजें संभवतः शांत हो गई थीं, जबकि यह बस यह मानता है कि सब कुछ तुरंत सामान्य हो जाता है। इसके अलावा, व्यवहार में लुक-बैक विंडो को संतुलित करना कुछ हद तक चुनौतीपूर्ण हो सकता है ताकि हम a) हाल के रुझानों को पकड़ सकें लेकिन b) दीर्घकालिक प्रवृत्तियों (सामान्य पूर्वाग्रह-भिन्नता व्यापार) को भी पकड़ सकें।
मान लीजिए कि अब हम एक अधिक जटिल कार्य करना चाहते हैं - अनुभवजन्य डेटा से दो परिसंपत्तियों की मूल्य चालों के बीच सहसंबंध निकालना, जब मूल्य श्रृंखलाओं में से एक या दोनों में डेटा गायब हो। निश्चित रूप से, हम अभी भी ड्रॉपिंग का उपयोग कर सकते हैं, लेकिन:
यदि हम इसका उपयोग कर भी लें तो क्या यह सर्वोत्तम है?
क्या होगा यदि हमारे पास कई चर हों - तब कम से कम एक NA वाली सभी पंक्तियों को हटाने से हमारे पास कोई डेटा नहीं बचेगा!
सहसंबंध की गणना करने के कई कारण हो सकते हैं - यह लगभग हर बहु-चर मॉडल में EDA का पहला चरण है, इसका उपयोग किसी भी प्रकार के पोर्टफोलियो निर्माण में बहुत व्यापक रूप से किया जाता है, इत्यादि। इसलिए इस संख्या को यथासंभव सटीक रूप से मापना बहुत आवश्यक है!
उदाहरण के लिए, आइए पहले वाले से 0.4 के “अंतर्निहित” सहसंबंध के साथ दूसरा चर बनाएं। ऐसा करने के लिए, हम एक तरह के गॉसियन मिक्सचर मॉडल का उपयोग करेंगे। जो तस्वीर दिमाग में आ सकती है, वह दो सहसंबद्ध स्टॉक की है जो एक महत्वपूर्ण जोखिम कारक साझा करते हैं, लेकिन दूसरे स्टॉक में भी एक प्रमुख जोखिम कारक का जोखिम है जो पहले वाले में नहीं है। उदाहरण के लिए Google और Facebook के बारे में सोचें - पहला कारक तकनीकी क्षेत्र के बारे में सामान्य भावना हो सकती है और दूसरा प्रतिद्वंद्वी सोशल नेटवर्क के साथ प्रतिस्पर्धा हो सकती है।
np.random.seed(2) # needed to ensure a fixed second series price_moves_2 = pd.Series(np.random.randn(100)) price_vec_2 = 50+(0.4*price_moves/3 + np.sqrt(1-0.4**2)*price_moves_2).cumsum() # all this math to ensure we get a 0.4 "theoretical" correlation with the first one
आइए "बेसलाइन" अनुभवजन्य सहसंबंध की जांच करें - यानी, एनएएस और जंप के बिना।
pd.concat([price_vec, price_vec_2], axis = 1).diff().corr().iloc[0,1] >0.4866403018044526
अब यह "सैद्धांतिक" सहसंबंध के काफी करीब है - यह सर्वविदित है कि सहसंबंध का अनुभवजन्य मापन काफी बड़े शोर से ग्रस्त है।
अगले चरण के रूप में, हम NA के मामले की जांच करेंगे, लेकिन कोई आउटलेयर नहीं। हम यह भी तुलना करेंगे कि अगर हम diff
पहले और बाद में dropna
तो क्या होगा
pd.concat([price_vec_na_simple, price_vec_2], axis = 1).diff().corr().iloc[0,1] # implicit dropna after diff >0.5022675176281746 pd.concat([price_vec_na_simple, price_vec_2], axis = 1).dropna().diff().corr().iloc[0,1] >0.5287405341268966
दोनों परिणाम काफी करीब हैं और पहले प्राप्त "अनुभवजन्य" मूल्य से बहुत दूर नहीं हैं। आइए सत्यापित करें कि LOCF और आरोपण भी ठीक काम करते हैं:
pd.concat([price_vec_na_simple, price_vec_2], axis = 1).ffill().diff().corr().iloc[0,1] >0.5049380499525835 price_vec_na_simple_impute = price_vec_na_simple.copy() price_vec_na_simple_impute[price_vec_na_simple_impute.isna()] = price_vec_na_simple_impute.iloc[:90].mean() pd.concat([price_vec_na_simple_impute, price_vec_2], axis = 1).ffill().diff().corr().iloc[0,1] >0.4866728183859715
उपरोक्त 4 परिणामों की तुलना करने से, हम देखते हैं कि सभी विधियाँ काफी अच्छा प्रदर्शन करती हैं। शायद हमें आउटलायर मामले के लिए भी यही उम्मीद करनी चाहिए?
याद रखें, निरंतरता बनाए रखने के लिए हमें दूसरी मूल्य श्रृंखला को उसी मूल्य झटकों के संपर्क में लाना होगा जो पहले वाले ने अनुभव किए थे, लेकिन निम्नलिखित NAs के बिना । ऊपर दिए गए उदाहरण पर वापस जाएं - कल्पना करें कि कोई बड़ी घटना पहले जोखिम कारक में उछाल का कारण बनती है जो अंततः पहली परिसंपत्ति में व्यापार को रोक देती है। दूसरी परिसंपत्ति भी उनका अनुभव करेगी, निश्चित रूप से, लेकिन शायद कम हद तक, और इसलिए कोई रोक नहीं होगी और इस प्रकार कोई NAs नहीं होगा।
price_vec_na_2 = 50+(0.4*price_moves_na/3 + np.sqrt(1-0.4**2)*price_moves_2).cumsum()
आइये फिर से अपने सभी तरीकों के प्रदर्शन की तुलना करें -
pd.concat([price_vec_na, price_vec_na_2], axis = 1).diff().corr().iloc[0,1] >0.6527112906179914 pd.concat([price_vec_na, price_vec_na_2], axis = 1).dropna().diff().corr().iloc[0,1] >0.7122391279139506
सैद्धांतिक और अनुभवजन्य दोनों ही तरह के मूल्यों में बहुत अंतर है! LOCF और impute के बारे में क्या ख्याल है?
pd.concat([price_vec_na, price_vec_na_2], axis = 1).ffill().diff().corr().iloc[0,1] >0.33178239830519984 pd.concat([price_vec_na_impute, price_vec_na_2], axis = 1).dropna().diff().corr().iloc[0,1] >0.7280990594963112
अब हम अंततः देख सकते हैं कि LOCF का क्या महत्व है! यह स्पष्ट रूप से अन्य सभी विधियों से बेहतर प्रदर्शन करता है!
बेशक, यह 100% मजबूत नहीं है। एक के लिए, LOCF करने से हम लापता डेटा के समाप्त होने पर एक बड़ी कीमत में गिरावट लाते हैं। यदि यह दूसरे मूल्य वेक्टर पर कुछ आउटलेयर के साथ मेल खाता है, तो परिणाम काफी हद तक बदल सकते हैं। (*पाठक के लिए एक अभ्यास - price_vec_na_2[95]
के मूल्य कदम पर संकेत को पलटें और जांचें कि यह परिणामों को कैसे प्रभावित करता है)। यह बिल्कुल स्पष्ट नहीं है कि क्या यह केवल इस मूल्य में गिरावट को पेश करने के लिए "साफ" है, उदाहरण के लिए मूल्य शिखर price_vec_na[91]
और उसके बाद "सामान्य" मूल्य price_vec_na[95]
के बीच प्रक्षेप करना। हालांकि, विशेष रूप से एक "लाइव" उपयोग के लिए, प्रक्षेप वास्तव में संभव नहीं है! आखिरकार, अगर आज दिन #93 है, तो हम दिन #95 के अंत में दर्ज भविष्य के मूल्य का उपयोग करके कैसे प्रक्षेप कर सकते हैं? एक ऐतिहासिक अध्ययन के लिए - निश्चित रूप से, यह एक विकल्प है निष्कर्षतः, समय आयाम में प्रक्षेप संभव है, लेकिन कुछ हद तक संदिग्ध है।
मैंने एक छोटा सा केस स्टडी देने का प्रयास किया है, जिससे यह पता चले कि वित्तीय समय श्रृंखला में लुप्त डेटा को संभालने के लिए LOCF अक्सर सबसे आकर्षक और सरल विकल्प क्यों होता है।
संक्षेप में कहें तो इसके फायदे ये हैं:
कुछ नकारात्मक बातें:
एक प्रोप ट्रेडिंग शॉप में क्वांट के रूप में, मैं इसे अपने लगभग सभी अध्ययनों के लिए एक प्रभावी आधार रेखा के रूप में उपयोग करता हूं। कुछ स्थितियों में निश्चित रूप से अधिक सूक्ष्म उपायों की आवश्यकता होती है, लेकिन वे बहुत कम और दूर-दूर तक होते हैं और आमतौर पर उल्लिखित 3 अन्य तरीकों में से किसी के द्वारा भी वास्तव में 100% "समाधान" नहीं किया जाता है।