
实际上,中有一些隐藏的开销
zip(df.A.values,df.B.values)。关键在于numpy数组以与Python对象根本不同的方式存储在内存中。
numpy数组(例如
np.arange(10))本质上存储为连续的内存块,而不是单独的Python对象。相反,Python列表(例如
list(range(10)),)作为指向各个Python对象(即,整数0-9)的指针存储在内存中。这种差异是为什么内存中的numpy数组比Python等效列表更小以及为什么可以对numpy数组执行更快的计算的基础。
因此,就像
Counter消耗一样
zip,需要将关联的元组创建为Python对象。这意味着Python需要从numpy数据中提取元组值,并在内存中创建相应的Python对象。这有明显的开销,这就是为什么在将纯Python函数与numpy数据结合使用时要非常小心。您可能会经常看到的这种陷阱的一个基本示例是在
sumnumpy数组上使用内置的Python
:
sum(np.arange(10**5))实际上比纯Python
sum(range(10**5))慢一点,当然两者都比慢得多
np.sum(np.arange(10**5))
作为特定于此问题的示例,请观察以下时序,比较
Counter压缩的numpy数组与相应的压缩的Python列表的性能。
In [2]: a = np.random.randint(10**4, size=10**6) ...: b = np.random.randint(10**4, size=10**6) ...: a_list = a.tolist() ...: b_list = b.tolist()In [3]: %timeit Counter(zip(a, b))455 ms ± 4.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)In [4]: %timeit Counter(zip(a_list, b_list))334 ms ± 4.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
这两个时间之间的差异使您可以合理地估算前面讨论的开销。
但这还不是故事的结局。
groupby在熊猫中构造一个对象也涉及一些开销,至少与这个问题有关,因为有些
groupby元数据并不是严格地仅仅为了获取
size而必需的,而
Counter您所关心的是一件奇异的事情。通常,此开销远小于与关联的开销
Counter,但是通过一些快速实验,我发现
Counter当大多数组仅由单个元素组成时,您实际上可以获得略微更好的性能。
考虑以下几个时间安排(使用@BallpointBen的
sort=False建议),这些时间安排在少数几个大型团体<->许多小型团体的范围内:
def grouper(df): return df.groupby(['A', 'B'], sort=False).size()def count(df): return Counter(zip(df.A.values, df.B.values))for m, n in [(10, 10**6), (10**3, 10**6), (10**7, 10**6)]: df = pd.Dataframe({'A': np.random.randint(0, m, n), 'B': np.random.randint(0, m, n)}) print(m, n) %timeit grouper(df) %timeit count(df)这给了我下表:
m grouper counter10 62.9 ms 315 ms10**3 191 ms 535 ms10**7 514 ms 459 ms
当然,如果您要作为最终对象,则
Counter可以通过转换回a来抵消的任何收益
Series。
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)