3

假设我有两张桌子AB.

A具有多级索引(a, b)和一列 (ts)。 b明确确定 ts。

A = pd.DataFrame(
     [('a', 'x', 4), 
      ('a', 'y', 6), 
      ('a', 'z', 5), 
      ('b', 'x', 4), 
      ('b', 'z', 5), 
      ('c', 'y', 6)], 
     columns=['a', 'b', 'ts']).set_index(['a', 'b'])
AA = A.reset_index()

B是另一个具有非唯一索引 ( a) 的单列 (ts) 表。ts 在每个组的“内部”排序,即B.ix[x]针对每个 x 排序。此外,总是有一个值B.ix[x]大于或等于 中的值A

B = pd.DataFrame(
    dict(a=list('aaaaabbcccccc'), 
         ts=[1, 2, 4, 5, 7, 7, 8, 1, 2, 4, 5, 8, 9])).set_index('a')

其中的语义是B包含对索引指示的类型事件发生的观察。

我想从B每个事件类型的第一次出现的时间戳中A找到每个b. 换句话说,我想得到一个具有相同形状的表格A,而不是 ts 包含由 table 指定的“ts 之后出现的最小值” B

所以,我的目标是:

C: 
('a', 'x') 4
('a', 'y') 7
('a', 'z') 5
('b', 'x') 7
('b', 'z') 7
('c', 'y') 8

我有一些工作代码,但速度非常慢。

C = AA.apply(lambda row: (
    row[0], 
    row[1], 
    B.ix[row[0]].irow(np.searchsorted(B.ts[row[0]], row[2]))), axis=1).set_index(['a', 'b'])

剖析显示罪魁祸首很明显B.ix[row[0]].irow(np.searchsorted(B.ts[row[0]], row[2])))。但是,从长远来看,使用合并/连接的标准解决方案会占用过多的 RAM。

考虑到现在我有 1000 个a,假设每个 a 的平均 b 数是恒定的(可能是 100-200),并考虑每个 a 的观察数可能是 300 左右。在生产中,我将再有 1000a个s。

1,000,000 x 200 x 300 = 60,000,000,000

保存在 RAM 中可能有点太多了,尤其是考虑到我需要的数据完全可以用 C 来描述,就像我上面讨论的那样。

我将如何提高性能?

4

2 回答 2

3

感谢您提供示例数据。鉴于预期的数组大小为 100 万,我已经用一般建议更新了这个答案。

  1. 线型材

    对 lambda 函数的内容进行行分析表明,大部分时间都花在 B.ix[] 上(此处已重构为仅调用一次)。

    In [91]: lprun -f stack.foo1 AA.apply(stack.foo1, B=B, axis=1)
    Timer unit: 1e-06 s
    
    File: stack.py
    Function: foo1 at line 4
    Total time: 0.006651 s
    
    Line #      Hits         Time  Per Hit   % Time  Line Contents
    ==============================================================
         4                                           def foo1(row, B):
         5         6         6158   1026.3     92.6      subset = B.ix[row[0]].ts
         6         6          418     69.7      6.3      idx = np.searchsorted(subset, row[2])
         7         6           56      9.3      0.8      val = subset.irow(idx)
         8         6           19      3.2      0.3      return val
    
  2. 考虑内置数据类型和原始 numpy 数组而不是更高级别的构造。

    由于 B 在此处的行为类似于 dict 并且多次访问相同的键,因此让我们将 df.ix 与普通的 Python 字典(在其他地方预先计算)进行比较。具有 1M 键(唯一 A 值)的字典应该只需要 ~34MB(33% 容量:3 * 1e6 * 12 字节)。

    In [102]: timeit B.ix['a']
    10000 loops, best of 3: 122 us per loop
    
    In [103]: timeit dct['a']
    10000000 loops, best of 3: 53.2 ns per loop
    
  3. 用循环替换函数调用

    我能想到的最后一个重大改进是用 for 循环替换 df.apply() 以避免调用任何函数 200M 次(或者不管 A 是多大)。

希望这些想法有所帮助。


原始的、富有表现力的解决方案,但内存效率不高:

In [5]: CC = AA.merge(B, left_on='a', right_index=True)

In [6]: CC[CC.ts_x <= CC.ts_y].groupby(['a', 'b']).first()
Out[6]: 
     ts_x  ts_y
a b            
a x     4     4
  y     6     7
  z     5     5
b x     4     7
  z     5     7
c y     6     8
于 2012-12-17T22:29:51.467 回答
2

另一个使用 numpy 的布尔数组表示法的选项,它似乎比原始的快一个数量级(在这个小例子中,我怀疑它在更大的数据集上会更好......):
我怀疑这主要是因为选择最小值是比排序快得多的任务。

In [11]: AA.apply(lambda row: (B.ts.values[(B.ts.values >= row['ts']) &
                                           (B.index == row['a'])].min()),
                          axis=1)
Out[11]: 
0    4
1    7
2    5
3    7
4    7
5    8

In [12]: %timeit AA.apply(lambda row: (B.ts.values[(B.ts.values >= row['ts']) &(B.index == row['a'])].min()), axis=1)
1000 loops, best of 3: 1.46 ms per loop

如果您只是将其作为列添加到AA.

如果您在示例中创建一个新的数据框 - 尝试“公平地”测试这个 - 它会更慢(但仍然是原始数据框的两倍):

In [13]: %timeit C = AA.apply(lambda row: (row[0], row[1], B.ix[row[0]].irow(np.searchsorted(B.ts[row[0]], row[2]))), axis=1).set_index(['a', 'b'])
100 loops, best of 3: 10.3 ms per loop

In [14]: %timeit C = AA.apply(lambda row: (row[0], x[1], B.ts.values[(B.ts.values >= row['ts']) & (B.index == row['a'])].min()), axis=1)
100 loops, best of 3: 4.32 ms per loop
于 2012-12-17T22:38:11.493 回答