piR*_*red 21 python numpy pandas
我在这里和那里回答几个问题.我经常测试我和其他人的解决方案有多快.
是否有一个简单的框架来全面了解各种解决方案的速度有多快?
考虑数据帧 df
df = pd.DataFrame({
'Group': list('QLCKPXNLNTIXAWYMWACA'),
'Value': [29, 52, 71, 51, 45, 76, 68, 60, 92, 95,
99, 27, 77, 54, 39, 23, 84, 37, 99, 87]
})
Run Code Online (Sandbox Code Playgroud)
我想总结Value
按不同值分组的列Group
.我有三种方法可以做到这一点.
import pandas as pd
import numpy as np
from numba import njit
def sum_pd(df):
return df.groupby('Group').Value.sum()
def sum_fc(df):
f, u = pd.factorize(df.Group.values)
v = df.Value.values
return pd.Series(np.bincount(f, weights=v).astype(int), pd.Index(u, name='Group'), name='Value').sort_index()
@njit
def wbcnt(b, w, k):
bins = np.arange(k)
bins = bins * 0
for i in range(len(b)):
bins[b[i]] += w[i]
return bins
def sum_nb(df):
b, u = pd.factorize(df.Group.values)
w = df.Value.values
bins = wbcnt(b, w, u.size)
return pd.Series(bins, pd.Index(u, name='Group'), name='Value').sort_index()
Run Code Online (Sandbox Code Playgroud)
print(sum_pd(df).equals(sum_nb(df)))
print(sum_pd(df).equals(sum_fc(df)))
True
True
Run Code Online (Sandbox Code Playgroud)
%timeit sum_pd(df)
%timeit sum_fc(df)
%timeit sum_nb(df)
1000 loops, best of 3: 536 µs per loop
1000 loops, best of 3: 324 µs per loop
1000 loops, best of 3: 300 µs per loop
Run Code Online (Sandbox Code Playgroud)
MSe*_*ert 16
它们可能不会归类为“简单框架”,因为它们是需要安装的第三方模块,但是我经常使用两个框架:
simple_benchmark
(我是那个包裹的作者)perfplot
例如,该simple_benchmark
库允许装饰功能以进行基准测试:
from simple_benchmark import BenchmarkBuilder
b = BenchmarkBuilder()
import pandas as pd
import numpy as np
from numba import njit
@b.add_function()
def sum_pd(df):
return df.groupby('Group').Value.sum()
@b.add_function()
def sum_fc(df):
f, u = pd.factorize(df.Group.values)
v = df.Value.values
return pd.Series(np.bincount(f, weights=v).astype(int), pd.Index(u, name='Group'), name='Value').sort_index()
@njit
def wbcnt(b, w, k):
bins = np.arange(k)
bins = bins * 0
for i in range(len(b)):
bins[b[i]] += w[i]
return bins
@b.add_function()
def sum_nb(df):
b, u = pd.factorize(df.Group.values)
w = df.Value.values
bins = wbcnt(b, w, u.size)
return pd.Series(bins, pd.Index(u, name='Group'), name='Value').sort_index()
Run Code Online (Sandbox Code Playgroud)
还装饰一个产生基准值的函数:
from string import ascii_uppercase
def creator(n): # taken from another answer here
letters = list(ascii_uppercase)
np.random.seed([3,1415])
df = pd.DataFrame(dict(
Group=np.random.choice(letters, n),
Value=np.random.randint(100, size=n)
))
return df
@b.add_arguments('Rows in DataFrame')
def argument_provider():
for exponent in range(4, 22):
size = 2**exponent
yield size, creator(size)
Run Code Online (Sandbox Code Playgroud)
然后,运行基准测试所需要做的就是:
r = b.run()
Run Code Online (Sandbox Code Playgroud)
之后,您可以将结果检查为图表(matplotlib
为此需要库):
r.plot()
Run Code Online (Sandbox Code Playgroud)
如果功能在运行时非常相似,则百分比差异而不是绝对数字可能更为重要:
r.plot_difference_percentage(relative_to=sum_nb)
Run Code Online (Sandbox Code Playgroud)
或获取基准时间DataFrame
(需要pandas
)
r.to_pandas_dataframe()
Run Code Online (Sandbox Code Playgroud)
from simple_benchmark import BenchmarkBuilder
b = BenchmarkBuilder()
import pandas as pd
import numpy as np
from numba import njit
@b.add_function()
def sum_pd(df):
return df.groupby('Group').Value.sum()
@b.add_function()
def sum_fc(df):
f, u = pd.factorize(df.Group.values)
v = df.Value.values
return pd.Series(np.bincount(f, weights=v).astype(int), pd.Index(u, name='Group'), name='Value').sort_index()
@njit
def wbcnt(b, w, k):
bins = np.arange(k)
bins = bins * 0
for i in range(len(b)):
bins[b[i]] += w[i]
return bins
@b.add_function()
def sum_nb(df):
b, u = pd.factorize(df.Group.values)
w = df.Value.values
bins = wbcnt(b, w, u.size)
return pd.Series(bins, pd.Index(u, name='Group'), name='Value').sort_index()
Run Code Online (Sandbox Code Playgroud)
如果您不喜欢装饰器,也可以在一个调用中设置所有内容(在这种情况下,您不需要BenchmarkBuilder
和装饰器add_function
/ add_arguments
):
from simple_benchmark import benchmark
r = benchmark([sum_pd, sum_fc, sum_nb], {2**i: creator(2**i) for i in range(4, 22)}, "Rows in DataFrame")
Run Code Online (Sandbox Code Playgroud)
这里perfplot
提供了非常相似的界面(和结果):
import perfplot
r = perfplot.bench(
setup=creator,
kernels=[sum_pd, sum_fc, sum_nb],
n_range=[2**k for k in range(4, 22)],
xlabel='Rows in DataFrame',
)
import matplotlib.pyplot as plt
plt.loglog()
r.plot()
Run Code Online (Sandbox Code Playgroud)
MSe*_*ert 15
这个术语是"比较基准",与所有基准一样,重要的是指定(即使只是为了你自己)你想要的基准.一个糟糕的基准也比没有基准差.因此,任何框架都需要根据您的设置进行仔细调整.
通常,当您分析算法时,您会对"增长顺序"感兴趣.因此,通常您希望针对不同的输入长度对算法进行基准测试(但是在创建set
基准sort
算法时的其他指标可能很重要,例如"重复数" ).但不仅渐近性能很重要,常数因素(特别是如果它们是高阶项的常数因子)也很重要.
对于前言这么多,我经常发现自己使用某种"简单框架":
# Setup
import pandas as pd
import numpy as np
from numba import njit
@njit
def numba_sum(arr):
return np.sum(arr)
# Timing setup
timings = {sum: [], np.sum: [], numba_sum: []}
sizes = [2**i for i in range(1, 20, 2)]
# Timing
for size in sizes:
func_input = np.random.random(size=size)
for func in timings:
res = %timeit -o func(func_input) # if you use IPython, otherwise use the "timeit" module
timings[func].append(res)
Run Code Online (Sandbox Code Playgroud)
这就是制作一些基准测试所需的全部内容.更重要的问题是如何将它们可视化.我常用的一种方法是以对数方式绘制它们.这样你可以看到小数组的常数因子,但也可以看到渐近的执行方式:
%matplotlib notebook
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure(1)
ax = plt.subplot(111)
for func in timings:
ax.plot(sizes,
[time.best for time in timings[func]],
label=str(func)) # you could also use "func.__name__" here instead
ax.set_xscale('log')
ax.set_yscale('log')
ax.set_xlabel('size')
ax.set_ylabel('time [seconds]')
ax.grid(which='both')
ax.legend()
plt.tight_layout()
Run Code Online (Sandbox Code Playgroud)
但另一种方法是找到基线并绘制相对差异:
%matplotlib notebook
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure(1)
ax = plt.subplot(111)
baseline = sum_nb # choose one function as baseline
for func in timings:
ax.plot(sizes,
[time.best / ref.best for time, ref in zip(timings[func], timings[baseline])],
label=str(func)) # you could also use "func.__name__" here instead
ax.set_yscale('log')
ax.set_xscale('log')
ax.set_xlabel('size')
ax.set_ylabel('time relative to {}'.format(baseline)) # you could also use "func.__name__" here instead
ax.grid(which='both')
ax.legend()
plt.tight_layout()
Run Code Online (Sandbox Code Playgroud)
传说可能还需要更多的工作......现在已经很晚了......希望现在可以理解.
只是一些额外的随机评论:
该timeit.Timer.repeat
文档包含一个非常重要的说明:
从结果向量计算平均值和标准偏差并报告这些是很诱人的.但是,这不是很有用.在典型情况下,最低值给出了机器运行给定代码段的速度的下限; 结果向量中较高的值通常不是由Python的速度变化引起的,而是由于其他过程干扰您的计时准确性.因此结果的min()可能是您应该感兴趣的唯一数字.之后,您应该查看整个向量并应用常识而不是统计.
这意味着mean
可能有偏见,因此也是如此sum
.这就是为什么我用.best
了的%timeit
结果.这是"最小".当然,最低限度也不是完整的事实,只要确保min
和mean
(或sum
)不显示不同的趋势.
我使用上面的log-log图.这使得解释整体性能变得容易("当长于1000个元素时,x比y快"),但是它们很难量化(例如"x比y快3倍").因此,在某些情况下,其他类型的可视化可能更合适.
%timeit
很棒,因为它计算重复次数,因此每个基准测试需要大约1-3秒.但是在某些情况下,显式重复可能会更好.
始终确保时间实际上是正确的事情!在执行修改全局状态或修改输入的操作时要特别小心.例如,就地排序需要在每个基准测试之前进行设置步骤,否则您将对已经排序的事物进行排序(这是几种排序算法的最佳情况).
人们以前曾要求我这样做。因此,我只是将其发布为问答,希望其他人觉得它有用。
我欢迎所有反馈和建议。
我通常检查的事情的首要任务是解决输入数据大小变化时的解决方案速度。我们如何扩展数据的“大小”并不总是很明显。
我们使用称为的函数封装此概念,该函数creator
采用一个n
指定大小的参数。在这种情况下,creator
产生长度的数据帧n
具有两列Group
和Value
from string import ascii_uppercase
def creator(n):
letters = list(ascii_uppercase)
np.random.seed([3,1415])
df = pd.DataFrame(dict(
Group=np.random.choice(letters, n),
Value=np.random.randint(100, size=n)
))
return df
Run Code Online (Sandbox Code Playgroud)
我要测试列表中指定的各种尺寸
sizes = [1000, 3000, 10000, 30000, 100000]
Run Code Online (Sandbox Code Playgroud)
我需要测试功能列表。每个函数应采用一个输入,该输入是的输出creator
。
我们具有OP的功能
import pandas as pd
import numpy as np
from numba import njit
def sum_pd(df):
return df.groupby('Group').Value.sum()
def sum_fc(df):
f, u = pd.factorize(df.Group.values)
v = df.Value.values
return pd.Series(np.bincount(f, weights=v).astype(int), pd.Index(u, name='Group'), name='Value').sort_index()
@njit
def wbcnt(b, w, k):
bins = np.arange(k)
bins = bins * 0
for i in range(len(b)):
bins[b[i]] += w[i]
return bins
def sum_nb(df):
b, u = pd.factorize(df.Group.values)
w = df.Value.values
bins = wbcnt(b, w, u.size)
return pd.Series(bins, pd.Index(u, name='Group'), name='Value').sort_index()
methods = [sum_pd, sum_fc, sum_nb]
Run Code Online (Sandbox Code Playgroud)
最后,我们建立tester
功能
import pandas as pd
from timeit import timeit
def tester(sizes, methods, creator, k=100, v=False):
results = pd.DataFrame(
index=pd.Index(sizes, name='Size'),
columns=pd.Index([m.__name__ for m in methods], name='Method')
)
methods = {m.__name__: m for m in methods}
for n in sizes:
x = creator(n)
for m in methods.keys():
stmt = '%s(x)' % m
setp = 'from __main__ import %s, x' % m
if v:
print(stmt, setp, n)
t = timeit(stmt, setp, number=k)
results.set_value(n, m, t)
return results
Run Code Online (Sandbox Code Playgroud)
我们用
results = tester(sizes, methods, creator)
print(results)
Method sum_pd sum_fc sum_nb
Size
1000 0.0632993 0.0316809 0.0364261
3000 0.0596143 0.031896 0.0319997
10000 0.0609055 0.0324342 0.0363031
30000 0.0646989 0.03237 0.0376961
100000 0.0656784 0.0363296 0.0331994
Run Code Online (Sandbox Code Playgroud)
我们可以用
results.plot()
Run Code Online (Sandbox Code Playgroud)