Python 提高数据帧上文本清理的性能

Python 提高数据帧上文本清理的性能,python,performance,pandas,nltk,apply,Python,Performance,Pandas,Nltk,Apply,我有一个df: id text 1 This is a good sentence 2 This is a sentence with a number: 2015 3 This is a third sentence 我有一个文本清理功能: def clean(text): lettersOnly = re.sub('[^a-zA-Z]',' ', text) tokens = word_tokenize(lettersOnly.lower())

我有一个df:

id    text
1     This is a good sentence
2     This is a sentence with a number: 2015
3     This is a third sentence
我有一个文本清理功能:

def clean(text):
    lettersOnly = re.sub('[^a-zA-Z]',' ', text)
    tokens = word_tokenize(lettersOnly.lower())
    stops = set(stopwords.words('english'))
    tokens = [w for w in tokens if not w in stops]
    tokensPOS = pos_tag(tokens)
    tokensLemmatized = []
    for w in tokensPOS:
        tokensLemmatized.append(WordNetLemmatizer().lemmatize(w[0], get_wordnet_pos(w[1])))
    clean = " ".join(tokensLemmatized)
    return clean
get\u wordnet\u pos()
这是:

def get_wordnet_pos(treebank_tag):
    if treebank_tag.startswith('J'):
        return wordnet.ADJ
    elif treebank_tag.startswith('V'):
        return wordnet.VERB
    elif treebank_tag.startswith('N'):
        return wordnet.NOUN
    elif treebank_tag.startswith('R'):
        return wordnet.ADV
    else:
        return wordnet.NOUN
我正在将
extractFeatures()
应用于熊猫列,并创建一个新列,结果如下:

df['cleanText'] = df['text'].apply(clean)
结果df:

id    cleanText
1     good sentence
2     sentence number
3     third sentence
循环时间似乎呈指数增长。例如,使用
%%timeit
,将其应用于五行,每循环运行17毫秒。300行以每循环800毫秒的速度运行。500行以每循环1.26秒的速度运行

我通过在函数外部实例化
stops
WordNetLemmatizer()
对其进行了修改,因为它们只需要调用一次

stops = set(stopwords.words('english'))
lem = WordNetLemmatizer()
def clean(text):
    lettersOnly = re.sub('[^a-zA-Z]',' ', text)
    tokens = word_tokenize(lettersOnly.lower())
    tokens = [w for w in tokens if not w in stops]
    tokensPOS = pos_tag(tokens)
    tokensLemmatized = []
    for w in tokensPOS:
        tokensLemmatized.append(lem.lemmatize(w[0], get_wordnet_pos(w[1])))
    clean = " ".join(tokensLemmatized)
    return clean
应用
行上运行
%prun-l 10
,生成此表:

         672542 function calls (672538 primitive calls) in 2.798 seconds

   Ordered by: internal time
   List reduced from 211 to 10 due to restriction <10>

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
     4097    0.727    0.000    0.942    0.000 perceptron.py:48(predict)
     4500    0.584    0.000    0.584    0.000 {built-in method nt.stat}
     3500    0.243    0.000    0.243    0.000 {built-in method nt._isdir}
    14971    0.157    0.000    0.178    0.000 {method 'sub' of '_sre.SRE_Pattern' objects}
    57358    0.129    0.000    0.155    0.000 perceptron.py:250(add)
     4105    0.117    0.000    0.201    0.000 {built-in method builtins.max}
   184365    0.084    0.000    0.084    0.000 perceptron.py:58(<lambda>)
     4097    0.057    0.000    0.213    0.000 perceptron.py:245(_get_features)
      500    0.038    0.000    1.220    0.002 perceptron.py:143(tag)
     2000    0.034    0.000    0.068    0.000 ntpath.py:471(normpath)
672542个函数调用(672538个基元调用)只需2.798秒
订购人:内部时间
由于限制,名单从211个减少到10个
ncalls tottime percall cumtime percall文件名:lineno(函数)
4097 0.727 0.000 0.942 0.000感知器。py:48(预测)
45000.5840.000 0.5840.000{内置方法nt.stat}
3500 0.243 0.000 0.243 0.000{内置方法nt.\U isdir}
14971 0.157 0.000 0.178 0.000{方法'sub'的'u sre.sre_模式'objects}
57358 0.129 0.000 0.155 0.000感知器。py:250(添加)
4105 0.117 0.000 0.201 0.000{内置方法builtins.max}
184365 0.084 0.000 0.084 0.000感知器。py:58()
4097 0.057 0.000 0.213 0.000感知器。py:245(_get_特征)
500 0.038 0.000 1.220 0.002感知器。py:143(标签)
2000 0.034 0.000 0.068 0.000 ntpath.py:471(normpath)
可以预见,perceptron tagger似乎占用了大量资源,但我不确定如何简化它。此外,我不确定在哪里调用
nt.stat
nt.\u isdir


我应该如何更改函数或应用方法来提高性能?这个函数是Cython还是Numba的候选函数?

我在这里看到的第一个明显的改进点是,整个
get\u wordnet\u pos
函数应该简化为字典查找:

def get_wordnet_pos(treebank_tag):
    if treebank_tag.startswith('J'):
        return wordnet.ADJ
    elif treebank_tag.startswith('V'):
        return wordnet.VERB
    elif treebank_tag.startswith('N'):
        return wordnet.NOUN
    elif treebank_tag.startswith('R'):
        return wordnet.ADV
    else:
        return wordnet.NOUN
相反,从
集合
包中初始化
defaultdict

import collections 
get_wordnet_pos = collections.defaultdict(lambda: wordnet.NOUN)
get_wordnet_pos.update({'J' : wordnet.ADJ,  
                        'V' : wordnet.VERB, 
                        'N' : wordnet.NOUN, 
                        'R' : wordnet.ADV })
然后,您将按如下方式访问查找:

get_wordnet_pos[w[1][0]]

下一步,如果要在多个地方使用,可以考虑预编译正则表达式。你得到的加速没有那么多,但这一切都很重要

pattern = re.compile('[^a-zA-Z]')
在函数内部,您可以调用:

pattern.sub(' ', text)
OTOH,如果您知道文本的来源,并且对可能看到的和可能看不到的内容有所了解,您可以预先编译一个字符列表,而使用
str.translate
,这比基于笨拙正则表达式的替换要快得多:

tab = str.maketrans(dict.fromkeys("1234567890!@#$%^&*()_+-={}[]|\'\":;,<.>/?\\~`", '')) # pre-compiled use once substitution table (keep this outside the function)

text = 'hello., hi! lol, what\'s up'
new_text = text.translate(tab) # this would run inside your function

print(new_text)

'hello hi lol whats up'
tab=str.maketrans(dict.fromkeys(“1234567890!@$%^&*()u+-={}[]\'\'\'\”:;,/?\\\\\`,'')\ 35;预编译的use once替换表(将其保留在函数之外)
text='你好,嗨!哈哈,怎么了'
new_text=text.translate(tab)#这将在函数内部运行
打印(新文本)
“你好,嗨,哈哈,怎么了”
此外,我要说的是,
word\u tokenize
太过分了——不管怎么说,你要做的就是去掉特殊字符,这样你就失去了
word\u tokenize
的所有好处,这与标点符号等有很大的不同。你可以选择使用
text.split()

最后,跳过
clean=“”.join(tokenslemmazized)
步骤。只需返回列表,然后在最后一步中调用
df.applymap(“.join)


我将基准测试留给您。

我在这里看到的第一个明显的改进点是,整个
get\u wordnet\u pos
功能应该简化为字典查找:

def get_wordnet_pos(treebank_tag):
    if treebank_tag.startswith('J'):
        return wordnet.ADJ
    elif treebank_tag.startswith('V'):
        return wordnet.VERB
    elif treebank_tag.startswith('N'):
        return wordnet.NOUN
    elif treebank_tag.startswith('R'):
        return wordnet.ADV
    else:
        return wordnet.NOUN
相反,从
集合
包中初始化
defaultdict

import collections 
get_wordnet_pos = collections.defaultdict(lambda: wordnet.NOUN)
get_wordnet_pos.update({'J' : wordnet.ADJ,  
                        'V' : wordnet.VERB, 
                        'N' : wordnet.NOUN, 
                        'R' : wordnet.ADV })
然后,您将按如下方式访问查找:

get_wordnet_pos[w[1][0]]

接下来,你可以考虑预编译你的正则表达式,如果它在多个地方被使用。你得到的加速不是那么多,但是它很重要。

pattern = re.compile('[^a-zA-Z]')
在函数内部,您可以调用:

pattern.sub(' ', text)
OTOH,如果您知道文本的来源,并且对可能看到的和可能看不到的内容有所了解,您可以预先编译一个字符列表,而使用
str.translate
,这比基于笨拙正则表达式的替换要快得多:

tab = str.maketrans(dict.fromkeys("1234567890!@#$%^&*()_+-={}[]|\'\":;,<.>/?\\~`", '')) # pre-compiled use once substitution table (keep this outside the function)

text = 'hello., hi! lol, what\'s up'
new_text = text.translate(tab) # this would run inside your function

print(new_text)

'hello hi lol whats up'
tab=str.maketrans(dict.fromkeys(“1234567890!@$%^&*()u+-={}[]\'\'\'\”:;,/?\\\\\`,'')\ 35;预编译的use once替换表(将其保留在函数之外)
text='你好,你好!哈哈,怎么了
new_text=text.translate(tab)#这将在函数内部运行
打印(新文本)
“你好,嗨,哈哈,怎么了”
此外,我要说的是,
word\u tokenize
太过分了——不管怎么说,你要做的就是去掉特殊字符,这样你就失去了
word\u tokenize
的所有好处,这对标点符号之类的东西来说真的很重要。您可以选择使用
text.split()

最后,跳过
clean=“.join(令牌已初始化)
步骤。只需返回列表,然后在最后一步中调用
df.applymap(“.join)


我将基准测试留给您。

如果没有您的数据和预期输出,就不能说了。添加了示例输入数据和清理功能的结果。我正在获得正确的输出-问题更多的是如何更快地获得正确的输出。有趣。单词的顺序重要吗?我猜是吗?是的,因为
cleanedText
随后被发送到矢量器中,以收集NGRAM、频率、tf idf权重,等等。我认为最明显的改进点是将
get\u wordnet\u pos
减少为
str
defaultdict
。如果没有您的数据和预期输出,就不能说了。添加了示例输入数据和清理功能的结果。我得到了p