假设我有两个表A
和B
。
表A
具有多级索引(a, b)
和一列(ts)。
b
明确地判断。
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排序。
此外,{em>始终 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包含表B
指定的“ts之后出现的最小值”。
所以,我的目标是:
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
个,假设每个b的平均b数(可能是100-200),并且认为每个a的观测数量可能大约为300。在制作中,我将再增加1000个a
。
1,000,000 x 200 x 300 = 60,000,000,000
行
保留在RAM中可能有点太多了,特别是考虑到我需要的数据完全由C语言描述,就像我上面讨论的那样。
我如何改善表现?
答案 0 :(得分:3)
感谢您提供样本数据。我已经更新了这个答案 建议给出100万分之一的预期数组大小。
线路资料
对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
考虑内置数据类型和原始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
用循环替换函数调用
我能想到的最后一个主要改进是用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
答案 1 :(得分: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