如果你在Python中处理数据,Pandas必然是你最常使用的库之一,因为它具有方便和强大的数据处理功能。
如果我们想要将相同的函数应用于Pandas数据帧中整个列的值,我们可以简单地使用 .apply()。Pandas数据帧和Pandas系列(数据帧中的一列)都可以与 .apply() 一起使用。
但是,你是否注意到当我们有一个超大数据集时,.apply() 可能会非常慢?
在本文中,我们将讨论一些加速数据操作的技巧,当你想要将某个函数应用于列时。
例如,这是我们的示例数据集。
import pandas as pd
import numpy as np
import timeit
d = {'category': ['apple', 'pear', 'peach'], 'radius': [3, 4, 2], 'sweetness': [1, 2, 3]}
df = pd.DataFrame(data=d)
df
如果我们想要在数据帧中添加一个名为'diameter'的列,基于半径列中的值,基本上是直径 = 半径 * 2,我们可以使用 .apply()。
df['diameter'] = df['radius'].apply(lambda x: x*2)
df
我们来计算一下执行时间,
# Timing
setup_code = """
import pandas as pd
d = {'category': ['apple', 'pear', 'peach'], 'radius': [3, 4, 2], 'sweetness': [1, 2, 3]}
df = pd.DataFrame(data=d)
"""
mycode = '''
df['radius'].apply(lambda x: max(x,3))
'''
# timeit statement
t1 = timeit.timeit(setup=setup_code,
stmt = mycode,
number = 10000)
print(f"10000 runs of mycode is {t1}")
这给了我们 0.56 秒。但如果数据有数百万行,需要多长时间?我这里没有展示,但是需要几十分钟。这么简单的操纵是不可接受的,对吧?
我们应该如何加快速度呢?
这是使用 NumPy 而不是 .apply() 函数的技巧。
df['radius_or_3'] = np.maximum(df['radius'],3)
与 .apply() 相比,这里的 NumPy 函数 max 是一种更好的矢量化函数。我们来计算一下时间。
# Timing
setup_code = """
import pandas as pd
import numpy as np
d = {'category': ['apple', 'pear', 'peach'], 'radius': [3, 4, 2], 'sweetness': [1, 2, 3]}
df = pd.DataFrame(data=d)
"""
mycode = '''
np.maximum(df['radius'],3)
'''
# timeit statement
t1 = timeit.timeit(setup=setup_code,
stmt = mycode,
number = 10000)
print(f"10000 runs of mycode is {t1}")
0.31 秒,比 .apply() 函数快 2 倍,对吗?
因此,要点是,在简单地使用 .apply() 函数处理所有内容之前,首先尝试为您的任务找到相应的 NumPy 函数。
有时我们需要使用数据中的多列作为函数的输入。例如,我们想要创建一列列表来记录“radius_or_3”和“diameter”之间可能的大小。
我们可以对整个数据框使用 .apply(),
df['sizes'] = df.apply(lambda x: list(range(x.radius_or_3,x.diameter)), axis=1)
df
| | category | radius | sweetness | diameter | radius_or_3 | sizes | | :------- | :----- | :-------- | :------- | :---------- | :---- | ---- | ------------ | | 0 | apple | 3 | 1 | 6 | 3 | [3, 4, 5] | | 1 | pear | 4 | 2 | 8 | 4 | [4, 5, 6, 7] | | 2 | peach | 2 | 3 | 4 | 3 | [3] |
这一步实际上非常耗时,因为我们实际上在 .apply() 函数中传递了很多不必要的东西。执行时间是,
# Timing
setup_code = """
import pandas as pd
import numpy as np
d = {'category': ['apple', 'pear', 'peach'], 'radius': [3, 4, 2], 'sweetness': [1, 2, 3]}
df = pd.DataFrame(data=d)
df['diameter'] = df['radius']*2
df['radius_or_3'] = np.maximum(df['radius'],3)
"""
mycode = '''
df.apply(lambda x: list(range(x.radius_or_3,x.diameter)), axis=1)
'''
# timeit statement
t1 = timeit.timeit(setup=setup_code,
stmt = mycode,
number = 10000)
print(f"10000 runs of mycode is {t1}")
这给了我们 1.84 秒。我告诉你,对于一个数百万行的数据框,需要 20 多分钟。
我们是否能够找到更高效的方法来执行这项任务呢?
答案是肯定的。唯一需要做的是创建一个接受所需的数量的NumPy数组(Pandas系列)作为输入的函数。
def create_range(a,b):
range_l = np.empty((len(a),1),object)
for i,val in enumerate(a):
range_l[i,0] = list(range(val,b[i]))
return range_l
df['sizes'] = create_range(df['radius_or_3'].values,df['diameter'].values)
df
这一部分的代码包含一个名为create_range的函数,它接受两个NumPy数组,并通过简单的for循环返回一个NumPy数组。返回的NumPy数组可以自动转换为Pandas Series。
让我们看看我们节省了多少时间。
# Timing
setup_code = """
import pandas as pd
import numpy as np
d = {'category': ['apple', 'pear', 'peach'], 'radius': [3, 4, 2], 'sweetness': [1, 2, 3]}
df = pd.DataFrame(data=d)
df['diameter'] = df['radius']*2
df['radius_or_3'] = np.maximum(df['radius'],3)
"""
mycode = '''
def create_range(a,b):
range_l = np.empty((len(a),1),object)
for i,val in enumerate(a):
range_l[i,0] = list(range(val,b[i]))
return range_l
create_range(df['radius_or_3'].values,df['diameter'].values)
'''
# timeit statement
t1 = timeit.timeit(setup=setup_code,
stmt = mycode,
number = 10000)
print(f"10000 runs of mycode is {t1}")
这将得到0.07秒的时间!!!
看到了吗?这比对整个数据帧使用的 .apply() 函数快26倍!!