真的有那么糟糕吗?
在下文中,我想向您展示如何使您的代码更快。但后来我意识到,这也取决于使用的数据集的大小。尽管如此,让我们先看看你的问题。我将在我的机器上运行相同的代码以进行比较。我将为一个大数据集(你的 100 倍)和一个小数据集(你的数据集)做所有事情。
Pandas 在一些数值计算上很慢。让我们看看与等效的 numpy 操作相比有多慢。
在 Linux 32 核上使用 pandas 0.23.4,在一个 jupyter 笔记本中(在 Windows 2 核上使用 pandas 1.0.4 以获得最后的结果,在一个 jupyter 笔记本中)
请注意,所有结果都在 jupyter 笔记本中找到。我没有更改任何设置。在现实世界的条件下,结果可能会有所不同。
测量
下面是我的测量。
大数据集
import pandas as pd
import numpy as np
a = np.random.randn(10000, 4000)
df0 = pd.DataFrame(a.copy())
df = df0.copy()
请注意,我使用了更多数据,100 倍以上。此外,我使用神奇的 %%time 命令而不是 %%timeit 进行测量。
df.shape
(10000, 4000)
我运行以下单元两次。第一次运行它时,内核可能仍会加载库或编译某些东西。它会显示不同的结果。但是您可以假设,在执行简单乘法时(就像执行 groupby 和聚合时一样),DataFrame 上没有任何内部状态更改或结果缓存。
此外,我不会像您那样在每个单元格中创建副本。尽管如此,以下创建一个新的 DataFrame 并保留旧的。它不仅是左侧数据框的视图。
%%time
_ = df * 1
CPU times: user 78 ms, sys: 90.6 ms, total: 169 ms
Wall time: 24.3 ms
如果我们将生成的 DataFrame 实例分配给df
指针,则单元格的执行需要更长的时间。也许是因为垃圾收集器从左侧释放了 DataFrame:笔记本中不再有对这个的引用。所以在你的性能测试中要小心,你正在测量什么!
%%time
df = df * 1
CPU times: user 84.4 ms, sys: 94.7 ms, total: 179 ms
Wall time: 31.7 ms
或使用就地乘法
%%time
df *= 1
CPU times: user 77.1 ms, sys: 97 ms, total: 174 ms
Wall time: 31 ms
对上述内容的观察:请注意,总时间高于挂钟时间(现在是你的挂钟或智能手机时钟)。这告诉我们,一些多处理或并发多线程在后台工作。
现在让我们继续讨论如何让事情变得更快。您基本上尝试了以下方法:
%%time
df[:] = df.values * 1.
CPU times: user 258 ms, sys: 234 ms, total: 492 ms
Wall time: 491 ms
这不是更快,因为在 s__setitem__
上非常复杂的pandas.Dataframe
s 很慢。你得到的一样loc
。
%%time
df.loc[:] = df.values * 1.
CPU times: user 260 ms, sys: 224 ms, total: 485 ms
Wall time: 483 ms
直接访问数据
您可以直接访问数据并设置值。这似乎更快。(但你可能会遇到问题,如果你有混合数据类型DataFrame
。)
%%time
df.values[...] = df.values * 1.
CPU times: user 95.7 ms, sys: 78.5 ms, total: 174 ms
Wall time: 173 ms
甚至更快,做所有的事情。(只要df.values[...]
返回对数据存储的引用。)
%%time
df.values[...] *= 1
CPU times: user 43.4 ms, sys: 0 ns, total: 43.4 ms
Wall time: 42.6 ms
能比这更快吗?让我们将其与以下乘法进行比较。首先通过乘以初始数据集,numpyarray a
...
%%time
_ = a * 1
CPU times: user 45.9 ms, sys: 82.7 ms, total: 129 ms
Wall time: 128 ms
...并通过执行相应的就地乘法。
%%time
a *= 1
CPU times: user 43.5 ms, sys: 0 ns, total: 43.5 ms
Wall time: 42.9 ms
它表明,不能预期少于大约 43 毫秒。因此,直接访问数据并对其进行操作与直接对 numpy 数组进行操作一样快。
但请注意,在我的示例中,即使是最初的问题也比这更快。表明,熊猫进行了一些优化,而 numpy 则没有。奇怪的!
小数据集
在这里,我做了和你一样的观察。直接访问数据的技巧再次发挥最佳效果(df.values[...] *= 1
)。
import numpy as np
import pandas as pd
a = np.random.randn(1000, 400)
df0 = pd.DataFrame(a.copy())
df = df0.copy()
df.shape
(1000, 400)
%%time
_ = df * 1
CPU times: user 4.23 ms, sys: 1.28 ms, total: 5.51 ms
Wall time: 2.83 ms
%%time
df = df * 1
CPU times: user 4.68 ms, sys: 188 µs, total: 4.87 ms
Wall time: 2.22 ms
%%time
df *= 1
CPU times: user 2.66 ms, sys: 1.76 ms, total: 4.42 ms
Wall time: 1.71 ms
%%time
df[:] = df.values * 1.
CPU times: user 4.28 ms, sys: 21 µs, total: 4.3 ms
Wall time: 3.51 ms
%%time
df.loc[:] = df.values * 1.
CPU times: user 3.77 ms, sys: 0 ns, total: 3.77 ms
Wall time: 3.13 ms
%%time
df.values[...] = df.values * 1.
CPU times: user 2.19 ms, sys: 0 ns, total: 2.19 ms
Wall time: 1.38 ms
%%time
df.values[...] *= 1
CPU times: user 211 µs, sys: 1.05 ms, total: 1.26 ms
Wall time: 681 µs
%%time
_ = a * 1
CPU times: user 1.61 ms, sys: 0 ns, total: 1.61 ms
Wall time: 818 µs
%%time
a *= 1
CPU times: user 379 µs, sys: 950 µs, total: 1.33 ms
Wall time: 671 µs
开放式问题
看起来使用 pandas 的简单乘法有时比使用 numpy 更快。这里是上面的大数据集。
%%timeit
_ = df * df
22.8 ms ± 590 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
%%timeit
_ = a * a
133 ms ± 4.85 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
我调用 timeit 或 time 都没有关系。结果是一样的。
%%time
_ = df * df
CPU times: user 62.3 ms, sys: 99.2 ms, total: 162 ms
Wall time: 23.8 ms
%%time
_ = a * a
CPU times: user 57.6 ms, sys: 82.3 ms, total: 140 ms
Wall time: 139 ms
我没想到会这样。你呢?
我在带有 pandas 1.0.4 的 Windows 10、2 核上对此进行了交叉检查。结果看起来基本相同。尽管相对差异不再那么大。
%%timeit
df * df
165 ms ± 5.96 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
%%timeit
a * a
251 ms ± 9.71 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)