python - पंडों के साथ तेजी से विराम चिह्न हटाने




regex string (2)

यह एक स्व-उत्तर वाली पोस्ट है। नीचे मैं एनएलपी डोमेन में एक आम समस्या को रेखांकित करता हूं और इसे हल करने के लिए कुछ प्रदर्शन करने वाले तरीकों का प्रस्ताव करता हूं।

पाठ की सफाई और पूर्व प्रसंस्करण के दौरान विराम चिह्न को हटाने के लिए अक्सर आवश्यकता उत्पन्न होती है। विराम चिह्न को string.punctuation में किसी भी वर्ण के रूप में परिभाषित किया गया है।

>>> import string
string.punctuation
'!"#$%&\'()*+,-./:;<=>[email protected][\\]^_`{|}~'

यह एक सामान्य पर्याप्त समस्या है और इसे विज्ञापन से पहले पूछा गया है। सबसे मुहावरेदार समाधान पांडा str.replace का उपयोग करता है। हालांकि, ऐसी स्थितियों के लिए जिनमें बहुत अधिक पाठ शामिल हैं, एक अधिक निष्पादन समाधान पर विचार करने की आवश्यकता हो सकती है।

सैकड़ों रिकॉर्ड के साथ काम करते समय str.replace लिए कुछ अच्छे, प्रदर्शन करने वाले विकल्प क्या हैं?


सेट अप

प्रदर्शन के उद्देश्य से, आइए इस DataFrame पर विचार करें।

df = pd.DataFrame({'text':['a..b?!??', '%hgh&12','abc123!!!', '$$$1234']})
df
        text
0   a..b?!??
1    %hgh&12
2  abc123!!!
3    $$$1234

नीचे, मैं विकल्पों को सूचीबद्ध करता हूं, एक-एक करके, प्रदर्शन के बढ़ते क्रम में

str.replace

इस विकल्प को अन्य, अधिक निष्पादन समाधानों की तुलना करने के लिए मानदंड के रूप में डिफ़ॉल्ट विधि स्थापित करने के लिए शामिल किया गया है।

यह पांडा में निर्मित str.replace फ़ंक्शन का उपयोग करता है जो रेगेक्स-आधारित प्रतिस्थापन करता है।

df['text'] = df['text'].str.replace(r'[^\w\s]+', '')

df
     text
0      ab
1   hgh12
2  abc123
3    1234

यह कोड करना बहुत आसान है, और काफी पठनीय है, लेकिन धीमा है।

regex.sub

इसमें re लाइब्रेरी से sub फ़ंक्शन का उपयोग करना शामिल है। प्रदर्शन के लिए एक रेगेक्स पैटर्न को पूर्व-संकलित करें, और सूची regex.sub अंदर regex.sub को कॉल करें। df['text'] को पहले से सूची में परिवर्तित करें यदि आप कुछ मेमोरी को छोड़ सकते हैं, तो आपको इसमें से थोड़ा अच्छा प्रदर्शन बूस्ट मिलेगा।

import re
p = re.compile(r'[^\w\s]+')
df['text'] = [p.sub('', x) for x in df['text'].tolist()]

df
     text
0      ab
1   hgh12
2  abc123
3    1234

नोट: यदि आपके डेटा में NaN मान हैं, तो यह (और साथ ही नीचे दी गई अगली विधि) काम नहीं करेगा। " अन्य विचार " पर अनुभाग देखें।

str.translate

अजगर की str.translate फ़ंक्शन सी में कार्यान्वित की जाती है, और इसलिए बहुत तेज़ है

यह कैसे काम करता है:

  1. सबसे पहले, अपने सभी स्ट्रिंग्स को एक साथ एक एकल (या अधिक) वर्ण विभाजक का उपयोग करके एक विशाल स्ट्रिंग बनाने के लिए शामिल करें जिसे आप चुनते हैं। आपको एक ऐसे चरित्र / विकल्प का उपयोग करना होगा जिसकी गारंटी आप अपने डेटा के अंदर नहीं दे सकते।
  2. बड़ी स्ट्रिंग पर str.translate प्रदर्शन करें, विराम चिह्न (चरण 1 से विभाजक को हटा) को हटा दें।
  3. विभाजक पर स्ट्रिंग को विभाजित करें जिसका उपयोग चरण 1 में शामिल होने के लिए किया गया था। परिणामी सूची में आपके प्रारंभिक कॉलम की लंबाई समान होनी चाहिए

यहाँ, इस उदाहरण में, हम पाइप विभाजक पर विचार करते हैं । यदि आपके डेटा में पाइप है, तो आपको एक और विभाजक चुनना होगा।

import string

punct = '!"#$%&\'()*+,-./:;<=>[email protected][\\]^_`{}~'   # `|` is not present here
transtab = str.maketrans(dict.fromkeys(punct, ''))

df['text'] = '|'.join(df['text'].tolist()).translate(transtab).split('|')

df
     text
0      ab
1   hgh12
2  abc123
3    1234

प्रदर्शन

str.translate अब तक का सर्वश्रेष्ठ प्रदर्शन करता है। ध्यान दें कि नीचे दिए गए ग्राफ़ में MaxU के उत्तर से एक और प्रकार Series.str.translate शामिल है।

(दिलचस्प बात यह है कि मैं इसे दूसरी बार फिर से चलाता हूं, और परिणाम पहले से थोड़ा अलग हैं। दूसरे रन के दौरान, ऐसा लगता है कि re.sub वास्तव में कम मात्रा में डेटा के लिए str.translate पर जीत रहा था।)

translate का उपयोग करने के साथ एक अंतर्निहित जोखिम होता है (विशेषकर, यह तय करने की प्रक्रिया को स्वचालित करने की समस्या कि कौन सा विभाजक उपयोग करने के लिए गैर-तुच्छ है), लेकिन व्यापार-बंद जोखिम के लायक हैं।

अन्य बातें

सूची समझने के तरीकों के साथ NaN को संभालना; ध्यान दें कि यह विधि (और अगले) केवल तब तक काम करेगी जब तक आपके डेटा में NaN नहीं है। NaNs को संभालते समय, आपको गैर-शून्य मानों के सूचकांकों को निर्धारित करना होगा और केवल उन को बदलना होगा। कुछ इस तरह की कोशिश करो:

df = pd.DataFrame({'text': [
    'a..b?!??', np.nan, '%hgh&12','abc123!!!', '$$$1234', np.nan]})

idx = np.flatnonzero(df['text'].notna())
col_idx = df.columns.get_loc('text')
df.iloc[idx,col_idx] = [
    p.sub('', x) for x in df.iloc[idx,col_idx].tolist()]

df
     text
0      ab
1     NaN
2   hgh12
3  abc123
4    1234
5     NaN

डेटाफ्रेम से निपटना; यदि आप DataFrames के साथ काम कर रहे हैं, जहां हर कॉलम को प्रतिस्थापन की आवश्यकता होती है, तो प्रक्रिया सरल है:

v = pd.Series(df.values.ravel())
df[:] = translate(v).values.reshape(df.shape)

या,

v = df.stack()
v[:] = translate(v)
df = v.unstack()

ध्यान दें कि translate फ़ंक्शन बेंचमार्किंग कोड के साथ नीचे परिभाषित किया गया है।

हर समाधान में ट्रेडऑफ़ होता है, इसलिए यह तय करना कि आपकी आवश्यकताओं में से कौन सा समाधान सबसे उपयुक्त है, इस बात पर निर्भर करेगा कि आप बलिदान करने के लिए क्या चाहते हैं दो बहुत ही सामान्य विचार प्रदर्शन हैं (जो हमने पहले ही देखे हैं), और स्मृति उपयोग। str.translate एक मेमोरी-भूखा समाधान है, इसलिए सावधानी के साथ उपयोग करें।

एक और विचार आपके रेगेक्स की जटिलता है। कभी-कभी, आप कुछ भी निकालना चाहते हैं जो अल्फ़ान्यूमेरिक या व्हॉट्सएप नहीं है। अन्य, आपको कुछ पात्रों, जैसे कि हाइफ़न, कॉलन और वाक्य टर्मिनेटर [.!?] रखना होगा [.!?] इन्हें स्पष्ट रूप से अपने रेगेक्स में जटिलता जोड़ें, जो इन समाधानों के प्रदर्शन को प्रभावित कर सकता है। सुनिश्चित करें कि आप क्या उपयोग करना है, यह तय करने से पहले अपने डेटा पर इन समाधानों का परीक्षण करें।

अंत में, इस समाधान के साथ यूनिकोड वर्णों को हटा दिया जाएगा। आप अपने रेगेक्स (यदि एक रेगेक्स-आधारित समाधान का उपयोग कर रहे हैं) को str.translate करना str.translate हैं, या बस str.translate साथ str.translate अन्यथा।

और भी अधिक प्रदर्शन के लिए (बड़े एन के लिए), पॉल पैंजर के इस जवाब पर एक नज़र डालें।

अनुबंध

कार्य

def pd_replace(df):
    return df.assign(text=df['text'].str.replace(r'[^\w\s]+', ''))


def re_sub(df):
    p = re.compile(r'[^\w\s]+')
    return df.assign(text=[p.sub('', x) for x in df['text'].tolist()])

def translate(df):
    punct = string.punctuation.replace('|', '')
    transtab = str.maketrans(dict.fromkeys(punct, ''))

    return df.assign(
        text='|'.join(df['text'].tolist()).translate(transtab).split('|')
    )

# MaxU's version (https://.com/a/50444659/4909087)
def pd_translate(df):
    punct = string.punctuation.replace('|', '')
    transtab = str.maketrans(dict.fromkeys(punct, ''))

    return df.assign(text=df['text'].str.translate(transtab))

प्रदर्शन बेंचमार्किंग कोड

from timeit import timeit

import pandas as pd
import matplotlib.pyplot as plt

res = pd.DataFrame(
       index=['pd_replace', 're_sub', 'translate', 'pd_translate'],
       columns=[10, 50, 100, 500, 1000, 5000, 10000, 50000],
       dtype=float
)

for f in res.index: 
    for c in res.columns:
        l = ['a..b?!??', '%hgh&12','abc123!!!', '$$$1234'] * c
        df = pd.DataFrame({'text' : l})
        stmt = '{}(df)'.format(f)
        setp = 'from __main__ import df, {}'.format(f)
        res.at[f, c] = timeit(stmt, setp, number=30)

ax = res.div(res.min()).T.plot(loglog=True) 
ax.set_xlabel("N"); 
ax.set_ylabel("time (relative)");

plt.show()

वेनिला पायथन str.translate() :

def pd_translate(df):
    return df.assign(text=df['text'].str.translate(transtab))





numpy