我对数据清理项目的一个工作,我必须清洗大熊猫数据帧的一部分的多个领域。 大多是我写的正则表达式和简单的功能。 下面的例子,
def func1(s):
s = str(s)
s = s.replace(' ', '')
if len(s) > 0 and s != '0':
if s.isalpha() and len(s) < 2:
return s
def func2(s):
s = str(s)
s = s.replace(' ', '')
s = s.strip(whitespace+','+'-'+'/'+'\\')
if s != '0':
if s.isalnum() or s.isdigit():
return s
def func3(s):
s = str(s)
if s.isdigit() and s != '0':
return s
else:
return None
def func4(s):
if str(s['j']).isalpha() and str(s['k']).isdigit() and s['l'] is none:
return s['k']
并呼吁他们这样。
x['a'] = x['b'].apply(lambda x: func1(x) if pd.notnull(x) else x)
x['c'] = x['d'].apply(lambda x: func2(x) if pd.notnull(x) else x)
x['e'] = x['f'].apply(lambda x: func3(x) if pd.notnull(x) else x)
x['g'] = x.apply(lambda x: func4(x), axis = 1)
一切都在这里很好,但我已经写了近50个这样的功能,这样,我的数据集有10多万条记录。 脚本运行几个小时,如果我的理解是正确的,该功能被称为排明智的,所以每个函数被调用的次数为行,其需要长时间来处理此。 有没有办法来优化呢? 我如何以更好的方式处理这个? 可通过不适用的功能? 谢谢。
样本数据集: -
Name f j b
339043 Moir Point RD 3 0
21880 Fisher-Point Drive Freemans Ba 6 0
457170 Whakamoenga Point 29 0
318399 Motukaraka Point RD 0 0
274047 Apirana Avenue Point England 360 0 366
207588 Hobsonville Point RD 127 0
747136 Dog Point RD 130 0
325704 Aroha Road Te Arai Point 36 0
291888 One Tree Point RD 960 0
207954 Hobsonville Point RD 160 0 205D
248410 Huia Road Point Chevalier 106 0
在一般情况下,你应该避免调用.apply
于DataFrame
。 这是真的什么是让你。 引擎盖下,它正在创造一个新的Series
中的每一行DataFrame
和发送传递给函数来.apply
。 不用说,这是相当多的,每行的开销,因此.apply
是在一个完整的DataFrame
是缓慢的。
在下面的例子中,我已经改名了一些列的函数调用,因为示例数据是有限的。
import sys
import time
import contextlib
import pandas as pd
@contextlib.contextmanager
def timethis(label):
'''A context manager to time a bit of code.'''
print('Timing', label, end=': ')
sys.stdout.flush()
start = time.time()
yield
print('{:.4g} seconds'.format(time.time() - start))
... func1, func2, and func3 definitions...
def func4(s):
if str(s['j']).isalpha() and str(s['f']).isdigit() and s['b'] is none:
return s['f']
x = pd.DataFrame({'f': [3, 6, 29, 0, 360, 127, 130, 36, 960, 160, 106],
'j': 0,
'b': [None, None, None, None, 366, None, None, None, None, '205D', None]})
x = pd.concat(x for _ in range(100000))
y = x.copy()
x['a'] = x['b'].apply(lambda x: func1(x) if pd.notnull(x) else x)
x['c'] = x['j'].apply(lambda x: func2(x) if pd.notnull(x) else x)
x['e'] = x['f'].apply(lambda x: func3(x) if pd.notnull(x) else x)
with timethis('func4'):
x['g'] = x.apply(func4, axis = 1) # The lambda in your example was not needed
...
def vectorized_func4(df):
'''Accept the whole DataFrame and not just a single row.'''
j_isalpha = df['j'].astype(str).str.isalpha()
f_isdigit = df['f'].astype(str).str.isdigit()
b_None = df['b'].isnull()
ret_col = df['f'].copy()
keep_rows = j_isalpha & f_isdigit & b_None
ret_col[~keep_rows] = None
return ret_col
y['a'] = vectorized_func1(y['b'])
y['c'] = vectorized_func2(y['j'])
y['e'] = vectorized_func3(y['f'])
with timethis('vectorized_func4'):
y['g'] = vectorized_func4(y)
输出:
Timing func4: 115.9 seconds
Timing vectorized_func4: 25.09 seconds
事实证明,对于func1
, func2
和func3
相比,量化方法时,它是一洗就表现。 .apply
(和.map
为此事)对Series
因为每个单元没有额外的开销也不是那么慢。 然而,这并不意味着你应该只使用.apply
,当你有一个Series
,而不是调查的矢量内置方法Series
-往往不是你可能能够做的比更好的apply
。
这里是你会如何改写func3
要矢量(我添加定时语句,所以我们可以看到花费最多的时间)。
def vectorized_func3(col):
with timethis('fillna'):
col = col.fillna('')
with timethis('astype'):
col = col.astype(str)
with timethis('rest'):
is_digit_string = col.str.isdigit()
not_0_string = col != '0'
keep_rows = is_digit_string & not_0_string
col[~keep_rows] = None
return col
这里是相对于定时func3
:
Timing func3: 8.302 seconds
Timing fillna: 0.006584 seconds
Timing astype: 9.445 seconds
Timing rest: 1.65 seconds
这需要很长的时间来只是改变了dtype
A的Series
,因为一个新的Series
,必须创建,然后每个元素被强制转换。 其它所有东西都炽烈 。 如果你可以改变你的算法不要求改变数据类型为str
,或者可以简单地存储为str
摆在首位,然后向量化的方法是要快得多(尤其是vectorized_func4
)。
带走
- 不要使用
.apply
于一个完整的DataFrame
,除非你绝对必须。 如果你觉得你一定要,去把喝咖啡的时候,想想这十分钟,尽量想办法做到这一点没有.apply
。 - 尽量不要使用
.apply
于一个Series
,你也许可以做的更好,但它不会像上一个完整的坏DataFrame
。 - 试着拿出不需要不断转换的算法
dtype
。
而不是多种功能,你可以在所有condtions一个功能使用if..elif。 只是一个想法!