2015-11-03 8 views
9

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

आपके लिए लोगों को सही तरीके से परीक्षण करने के लिए मैंने कोड का एक बड़ा टुकड़ा अपलोड किया। आपको इसे चलाने में सक्षम होना चाहिए। कोड में टिप्पणियों को समझाया जाना चाहिए कि मैं यहां क्या करने की कोशिश कर रहा हूं। किसी भी तरह की सहायता का स्वागत किया जाएगा।

# -*- coding: utf-8 -*- 

import pandas as pd 
import numpy as np 

# Filling dataframe with data 
# Just ignore this part for now, real data comes from csv files, this is an example of how it looks 
TimeOfDay_options = ['Day','Evening','Night'] 
TypeOfCargo_options = ['Goods','Passengers'] 
numpy.random.seed(1234) 
n = 10000 

df = pd.DataFrame() 
df['ID_number'] = np.random.randint(3, size=n) 
df['TimeOfDay'] = np.random.choice(TimeOfDay_options, size=n) 
df['TypeOfCargo'] = np.random.choice(TypeOfCargo_options, size=n) 
df['TrackStart'] = np.random.randint(400, size=n) * 900 
df['SectionStart'] = np.nan 
df['SectionStop'] = np.nan 

grouped_df = df.groupby(['ID_number','TimeOfDay','TypeOfCargo','TrackStart']) 
for index, group in grouped_df: 
    if len(group) == 1: 
     df.loc[group.index,['SectionStart']] = group['TrackStart'] 
     df.loc[group.index,['SectionStop']] = group['TrackStart'] + 899 

    if len(group) > 1: 
     track_start = group.loc[group.index[0],'TrackStart'] 
     track_end = track_start + 899 
     section_stops = np.random.randint(track_start, track_end, size=len(group)) 
     section_stops[-1] = track_end 
     section_stops = np.sort(section_stops) 
     section_starts = np.insert(section_stops, 0, track_start) 

     for i,start,stop in zip(group.index,section_starts,section_stops): 
      df.loc[i,['SectionStart']] = start 
      df.loc[i,['SectionStop']] = stop 

#%% This is what a random group looks like without errors 
#Note that each section neatly starts where the previous section ended 
#There are no gaps (The whole track is defined) 
grouped_df.get_group((2, 'Night', 'Passengers', 323100)) 

#%% Introducing errors to the data 
df.loc[2640,'SectionStart'] += 100 
df.loc[5390,'SectionStart'] += 7 

#%% This is what the same group looks like after introducing errors 
#Note that the 'SectionStop' of row 1525 is no longer similar to the 'SectionStart' of row 5592 
#This track now has a gap of 100, it is not completely defined from start to end 
grouped_df.get_group((2, 'Night', 'Passengers', 323100)) 

#%% Try to locate the errors 
#This is the part of the code I need to speed up 

def Full_coverage(group): 
    if len(group) > 1: 
     group.sort('SectionStart', ascending=True, inplace=True) #Sort the grouped data by column 'SectionStart' from low to high 

     #Some initial values, overwritten at the end of each loop 
     #These variables correspond to the first row of the group 
     start_km = group.iloc[0,4] 
     end_km = group.iloc[0,5] 
     end_km_index = group.index[0] 

     #Loop through all the rows in the group 
     #index is the index of the row 
     #i is the 'SectionStart' of the row 
     #j is the 'SectionStop' of the row 
     #The loop starts from the 2nd row in the group 
     for index, (i, j) in group.iloc[1:,[4,5]].iterrows(): 

      #The start of the next row must be equal to the end of the previous row in the group 
      if i != end_km: 

       #Add the faulty data to the error list 
       incomplete_coverage.append(('Expected startpoint: '+str(end_km)+' (row '+str(end_km_index)+')', \ 
            'Found startpoint: '+str(i)+' (row '+str(index)+')'))     

      #Overwrite these values for the next loop 
      start_km = i 
      end_km = j 
      end_km_index = index 

    return group 

#Check if the complete track is completely defined (from start to end) for each combination of: 
    #'ID_number','TimeOfDay','TypeOfCargo','TrackStart' 
incomplete_coverage = [] #Create empty list for storing the error messages 
df_grouped = df.groupby(['ID_number','TimeOfDay','TypeOfCargo','TrackStart']).apply(lambda x: Full_coverage(x)) 

#Print the error list 
print('\nFound incomplete coverage in the following rows:') 
for i,j in incomplete_coverage: 
    print(i) 
    print(j) 
    print() 

#%%Time the procedure -- It is very slow, taking about 6.6 seconds on my pc 
%timeit df.groupby(['ID_number','TimeOfDay','TypeOfCargo','TrackStart']).apply(lambda x: Full_coverage(x)) 
+0

क्या आपने यह देखने के लिए प्रोफाइलर का उपयोग करने का प्रयास किया है कि बाधा कहां है? – jakevdp

+0

बाधा लागू फ़ंक्शन प्रतीत होती है, भले ही मैं फ़ंक्शन में लूप को हटा देता हूं, यह धीमा रहता है (~ 4.25 प्रति लूप)। मैं सोच रहा हूं कि फ़ंक्शन को लागू करने का कोई दूसरा तरीका है (लागू आदेश के बिना)। मैं agg कमांड का उपयोग कर इस कोड में डेटा पर कुछ अन्य प्रक्रियाएं करता हूं। यह बहुत तेज़ काम करता है, लेकिन मुझे नहीं पता कि एजीजी कमांड का उपयोग करके यह चेक (full_coverage) निष्पादित करना संभव है या नहीं। – Alex

+0

बाधा निश्चित रूप से आपके द्वारा लागू किए जा रहे फ़ंक्शन में है। आपके डेटा में 5300 से अधिक विशिष्ट समूह हैं। 5300 समूहों पर बस ''sort'' को कॉल करने में कई सेकंड लगेंगे।फिर उन 5300 समूहों में से प्रत्येक के भीतर सभी मूल्यों को फिर से शुरू करने में कुछ और सेकंड लगेंगे। मैं एक वेक्टरकृत ऑपरेशन के पक्ष में '' '' लूप को हटाने का सुझाव दूंगा - आप इस रणनीति के साथ रनटाइम को ~ 2-3 सेकंड तक प्राप्त करने में सक्षम हो सकते हैं। यदि यह अभी भी बहुत धीमा है, तो आपको यह समझने की आवश्यकता होगी कि प्रत्येक समूह में डेटा को सॉर्ट किए बिना इसे कैसे किया जाए। – jakevdp

उत्तर

5

समस्या, मेरा मानना ​​है कि आपके डेटा में 5300 विशिष्ट समूह हैं। इसके कारण, आपके फ़ंक्शन के भीतर कुछ भी धीमा हो जाएगा। आप समय बचाने के लिए अपने फ़ंक्शन में for लूप के बजाय शायद वेक्टरकृत ऑपरेशन का उपयोग कर सकते हैं, लेकिन return group की बजाय 0 सेकंडपर कुछ सेकंड बंद करने का एक आसान तरीका है। जब आप return group, पांडा वास्तव में आपके सॉर्ट किए गए समूहों को संयोजित करने वाला एक नया डेटा ऑब्जेक्ट बनाते हैं, जिसे आप उपयोग नहीं करते हैं। जब आप return 0, पांडा इसके बजाय 5300 शून्य जोड़ देंगे, जो बहुत तेज़ है।

उदाहरण के लिए

:

cols = ['ID_number','TimeOfDay','TypeOfCargo','TrackStart'] 
groups = df.groupby(cols) 
print(len(groups)) 
# 5353 

%timeit df.groupby(cols).apply(lambda group: group) 
# 1 loops, best of 3: 2.41 s per loop 

%timeit df.groupby(cols).apply(lambda group: 0) 
# 10 loops, best of 3: 64.3 ms per loop 

बस परिणाम आप का उपयोग नहीं करते लगभग 2.4 सेकंड ले जा रहा है के संयोजन; शेष समय आपके लूप में वास्तविक गणना है जिसे आपको सदिश करने का प्रयास करना चाहिए।


संपादित करें:

for पाश से पहले एक त्वरित अतिरिक्त vectorized जांच और group के बजाय 0 लौटने के साथ

, मैं के बारे में ~ 2sec, जो मूल रूप से प्रत्येक समूह छँटाई की लागत है करने के लिए नीचे समय मिला है। इस समारोह का प्रयास करें:

def Full_coverage(group): 
    if len(group) > 1: 
     group = group.sort('SectionStart', ascending=True) 

     # this condition is sufficient to find when the loop 
     # will add to the list 
     if np.any(group.values[1:, 4] != group.values[:-1, 5]): 
      start_km = group.iloc[0,4] 
      end_km = group.iloc[0,5] 
      end_km_index = group.index[0] 

      for index, (i, j) in group.iloc[1:,[4,5]].iterrows(): 
       if i != end_km: 
        incomplete_coverage.append(('Expected startpoint: '+str(end_km)+' (row '+str(end_km_index)+')', \ 
             'Found startpoint: '+str(i)+' (row '+str(index)+')'))     
       start_km = i 
       end_km = j 
       end_km_index = index 

    return 0 

cols = ['ID_number','TimeOfDay','TypeOfCargo','TrackStart'] 
%timeit df.groupby(cols).apply(Full_coverage) 
# 1 loops, best of 3: 1.74 s per loop 

संपादित करें 2: यहाँ एक उदाहरण है जो GroupBy बाहर तरह ले जाने के लिए और अनावश्यक छोरों को हटाने के लिए मेरी सुझाव को शामिल किया गया है। छोरों निकाला जा रहा है बहुत तेजी से दिए गए उदाहरण के लिए नहीं है, लेकिन अगर वहाँ incompletes के एक बहुत हैं तेजी से हो जाएगा:

def Full_coverage_new(group): 
    if len(group) > 1: 
     mask = group.values[1:, 4] != group.values[:-1, 5] 
     if np.any(mask): 
      err = ('Expected startpoint: {0} (row {1}) ' 
        'Found startpoint: {2} (row {3})') 
      incomplete_coverage.extend([err.format(group.iloc[i, 5], 
                group.index[i], 
                group.iloc[i + 1, 4], 
                group.index[i + 1]) 
             for i in np.where(mask)[0]]) 
    return 0 

incomplete_coverage = [] 
cols = ['ID_number','TimeOfDay','TypeOfCargo','TrackStart'] 
df_s = df.sort_values(['SectionStart','SectionStop']) 
df_s.groupby(cols).apply(Full_coverage_nosort) 
+0

वाह मेरी आखिरी टिप्पणी खरोंच, मैंने इसे हटा दिया। यह फ़ंक्शन मेरा समय 6.6 से 1.6 सेक/लूप तक नीचे आता है। अद्भुत प्रगति! क्या आपको लगता है कि लूप के बाहर डेटा को सॉर्ट करने का कोई तरीका है? मैं कुछ चीजों का परीक्षण कर रहा हूं, लेकिन मैं गलत परिणामों को वापस ले रहा हूं। – Alex

+0

हां - ग्रुपबी ऑर्डर को सुरक्षित रखता है, ताकि आप पहले डेटाफ्रेम को '" "स्टार्टस्टार्ट" '' से क्रमबद्ध कर सकें और फिर ऊपर दिए गए फ़ंक्शन को सॉर्ट चरण के साथ हटा दें। ग्रुपबी के बाद पूर्ण प्रकार के लिए, मुझे 0.4sec मिलता है। – jakevdp

+0

यह वही है जो मैं कोशिश कर रहा था, और जब यह प्रक्रिया को काफी तेज़ी से बढ़ाता है, तो सूची में संग्रहीत परिणाम गलत लगते हैं। मैंने समूह चलाने से पहले df.sort ('sectionStart', आरोही = सही, inplace = True) की कोशिश की, लेकिन त्रुटि सूची में अब 8 त्रुटियां हैं। स्पष्ट रूप से मैंने केवल डेटा में दो त्रुटियां पेश कीं। – Alex

0

मैंने पाया पांडा का पता लगाने आदेश (.loc या .iloc) भी प्रगति को धीमा कर रहे थे । लूप से सॉर्ट को स्थानांतरित करके और फ़ंक्शन की शुरुआत में डेटा को numpy arrays में परिवर्तित करके मुझे एक तेज़ परिणाम मिला। मुझे पता है कि डेटा अब डेटाफ्रेम नहीं है, लेकिन सूची में लौटाई गई इंडेक्स का उपयोग मूल डीएफ में डेटा खोजने के लिए किया जा सकता है।

अगर वहाँ प्रक्रिया में तेजी लाने के लिए भी आगे मैं मदद की सराहना करते हैं किसी भी तरह से है। मेरे पास अब तक क्या है:

def Full_coverage(group): 

    if len(group) > 1: 
     group_index = group.index.values 
     group = group.values 

     # this condition is sufficient to find when the loop will add to the list 
     if np.any(group[1:, 4] != group[:-1, 5]): 
      start_km = group[0,4] 
      end_km = group[0,5] 
      end_km_index = group_index[0] 

      for index, (i, j) in zip(group_index, group[1:,[4,5]]): 

       if i != end_km: 
        incomplete_coverage.append(('Expected startpoint: '+str(end_km)+' (row '+str(end_km_index)+')', \ 
             'Found startpoint: '+str(i)+' (row '+str(index)+')'))    
       start_km = i 
       end_km = j 
       end_km_index = index 

    return 0 

incomplete_coverage = [] 
df.sort(['SectionStart','SectionStop'], ascending=True, inplace=True) 
cols = ['ID_number','TimeOfDay','TypeOfCargo','TrackStart'] 
%timeit df.groupby(cols).apply(Full_coverage) 
# 1 loops, best of 3: 272 ms per loop 
+0

'' np.any'' चेक के साथ, आप शायद ''' 'लूप को पूरी तरह से हटा सकते हैं। इससे कई विसंगतियों के मामले में चीजें तेज हो जाएंगी। अन्यथा, ~ 5000 विशिष्ट समूहों पर तर्क के लिए ~ 0.2sec शायद उतना ही अच्छा है जितना आप उम्मीद कर सकते हैं। – jakevdp

+0

मैं लूप से छुटकारा पाने की कोशिश कर रहा हूं, लेकिन मुझे लूप का उपयोग किए बिना प्रत्येक त्रुटि के लिए एक अलग प्रिंटलाइन जोड़ने का कोई तरीका नहीं दिख रहा है। आपके पास कोई विचार है? – Alex

+0

मेरा संपादित उत्तर देखें। – jakevdp

संबंधित मुद्दे