groupby在熊猫中最后的性能问题

chr*_*ock 6 python pandas

我有一个包含两列和一十多万个元素的DataFrame.

In [43]: df.head(10)
Out[43]:
                    localtime                 ref
4  2014-04-02 12:00:00.273537  139058754703810577
5  2014-04-02 12:00:02.223501  139058754703810576
6  2014-04-02 12:00:03.518817  139058754703810576
7  2014-04-02 12:00:03.572082  139058754703810576
8  2014-04-02 12:00:03.572444  139058754703810576
9  2014-04-02 12:00:03.572571  139058754703810576
10 2014-04-02 12:00:03.573320  139058754703810576
11 2014-04-02 12:00:09.278517  139058754703810576
14 2014-04-02 12:00:20.942802  139058754703810577
15 2014-04-02 12:01:13.410607  139058754703810576

[10 rows x 2 columns]

In [44]: df.dtypes
Out[44]:
localtime    datetime64[ns]
ref                   int64
dtype: object

In [45]: len(df)
Out[45]: 111743

In [46]: g = df.groupby('ref')
Run Code Online (Sandbox Code Playgroud)

如果我从我的组中请求最后一个元素,该函数就会挂起!

In [47]: %timeit g.last()
Run Code Online (Sandbox Code Playgroud)

我在6分钟后杀了它; top在整个时间内显示CPU为100%.

如果我localtime明确地请求列,这至少会返回,尽管对于有多少元素来说它仍然看起来很荒谬.

In [48]: %timeit g['localtime'].last()
1 loops, best of 3: 4.6 s per loop
Run Code Online (Sandbox Code Playgroud)

有什么我想念的吗?这是大熊猫0.13.1.


此问题与datetime64类型一起出现.假设我直接从文件中读取:

In [1]: import pandas as pd

In [2]: df = pd.read_csv('so.csv')

In [3]: df.dtypes
Out[3]:
localtime    object
ref           int64
dtype: object

In [4]: %timeit df.groupby('ref').last()
10 loops, best of 3: 28.1 ms per loop
Run Code Online (Sandbox Code Playgroud)

这种object类型工作得很好.然而,如果我施展时间戳,所有地狱都会破裂:

In [5]: df.localtime = pd.to_datetime(df.localtime)

In [6]: df.dtypes
Out[6]:
localtime    datetime64[ns]
ref                   int64
dtype: object

In [7]: %timeit df.groupby('ref').last()
Run Code Online (Sandbox Code Playgroud)

情节变粗.


使用Jeff的建议在没有数据文件的情况下重现:

In [70]: rng = pd.date_range('20130101',periods=20,freq='s')

In [71]: df = pd.DataFrame(dict(timestamp = rng.take(np.random.randint(0,20,size=100000)), value = np.random.randint(0,100,size=100000)*1000000))

In [72]: %timeit df.groupby('value').last()
1 loops, best of 3: 332 ms per loop
Run Code Online (Sandbox Code Playgroud)

但是,如果我改变随机整数的范围,那么问题又会出现!

In [73]: df = pd.DataFrame(dict(timestamp = rng.take(np.random.randint(0,20,size=100000)), value = np.random.randint(0,100000,size=100000)*1000))

In [74]: %timeit df.groupby('value').last()                                                           
Run Code Online (Sandbox Code Playgroud)

我只是增加了high第二个参数randint(),这意味着groupby()它将具有更大的长度.这会在没有数据文件的情况下重现我的错误.

请注意,如果我放弃datetime64类型,那么没有问题:

In [12]: df = pd.DataFrame(dict(timestamp = np.random.randint(0,20,size=100000), value = np.random.randint(0,100000,size=100000)*1000))

In [13]: %timeit df.groupby('value').last()
100 loops, best of 3: 14.4 ms per loop
Run Code Online (Sandbox Code Playgroud)

因此,罪魁祸首是在扩大last()datetime64.

Jef*_*eff 4

肯定发生了一些奇怪的事情......在 0.13.1(和 master)中看起来不错。发布您的文件的链接,我会看一下。

In [3]: rng = date_range('20130101',periods=20,freq='s')

In [4]: df = DataFrame(dict(timestamp = rng.take(np.random.randint(0,20,size=100000)), value = np.random.randint(0,100,size=100000)*1000000))

In [5]: df.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 100000 entries, 0 to 99999
Data columns (total 2 columns):
timestamp    100000 non-null datetime64[ns]
value        100000 non-null int64
dtypes: datetime64[ns](1), int64(1)
In [6]: %timeit df.groupby('value')['timestamp'].last()
100 loops, best of 3: 9.07 ms per loop

In [7]: %timeit df.groupby('value')['timestamp'].tail(1)
100 loops, best of 3: 16.3 ms per loop
Run Code Online (Sandbox Code Playgroud)

好的,这是解释:

使用np.random.randint(0,100,size=100000)for value 会创建 100 个组,同时np.random.randint(0,100000,size=100000)会创建更多组(在我的示例中为 63000 个)左右。

.last(< 0.14)隐式执行最后一个值non-nan。这个测试并不便宜,所以它的扩展性能很差(并且是在每个组的 python 空间中完成的)。

tail(1)另一方面(< 0.14)不检查这一点,因此 perf 更好(并使用 cython 路由来获取结果)。

在 0.14 中,这些将是相同的(即使你这样做:nth(-1,dropna='any')这将复制last这里所做的事情,这样做是为了获得更好的性能。(感谢@Andy Hayden)。

底线是使用tail(1)< 0.14。