加快熊猫过滤

时间:2014-08-06 22:38:13

标签: python performance optimization numpy pandas

我有一个37456153行x 3列Pandas数据帧,包含以下列:[Timestamp, Span, Elevation]。每个Timestamp值都有大约62000行SpanElevation数据,看起来像(例如,在Timestamp = 17210过滤时):

        Timestamp       Span  Elevation
94614       17210  -0.019766     36.571
94615       17210  -0.019656     36.453
94616       17210  -0.019447     36.506
94617       17210  -0.018810     36.507
94618       17210  -0.017883     36.502

...           ...        ...        ...
157188      17210  91.004000     33.493
157189      17210  91.005000     33.501
157190      17210  91.010000     33.497
157191      17210  91.012000     33.500
157192      17210  91.013000     33.503

如上所示,Span数据的间隔不等,我实际需要它。所以我想出了以下代码将其转换为等间距格式。我知道我想分析的startend位置。然后我将delta参数定义为我的增量。我创建了一个名为mesh的numpy数组,它保存了我想要最终的等间距Span数据。最后,我决定对给定TimeStamp(代码中的17300)的数据帧进行迭代,以测试它的工作速度。代码中的for循环计算每个增量的+/- Elevation范围的平均0.5delta值。

我的问题是:过滤数据帧需要603毫秒,并在单次次迭代时计算平均值Elevation。对于给定的参数,我必须经历9101次迭代,从而导致该循环结束的大约1.5小时的计算时间。此外,这是一个Timestamp值,我有600个(900小时做所有?!)。

有什么方法可以加快这个循环吗?非常感谢任何输入!

# MESH GENERATION
start = 0
end = 91
delta = 0.01

mesh = np.linspace(start,end, num=(end/delta + 1))
elevation_list =[]

#Loop below will take forever to run, any idea about how to optimize it?!

for current_loc in mesh:
    average_elevation = np.average(df[(df.Timestamp == 17300) & 
                                      (df.Span > current_loc - delta/2) & 
                                      (df.Span < current_loc + delta/2)].Span)
     elevation_list.append(average_elevation)

2 个答案:

答案 0 :(得分:6)

您可以使用np.searchsorted对整个事物进行矢量化。我不是一个大熊猫用户,但这样的东西应该工作,并在我的系统上运行得相当快。使用chrisb的虚拟数据:

In [8]: %%timeit
   ...: mesh = np.linspace(start, end, num=(end/delta + 1))
   ...: midpoints = (mesh[:-1] + mesh[1:]) / 2
   ...: idx = np.searchsorted(midpoints, df.Span)
   ...: averages = np.bincount(idx, weights=df.Elevation, minlength=len(mesh))
   ...: averages /= np.bincount(idx, minlength=len(mesh))
   ...: 
100 loops, best of 3: 5.62 ms per loop  

这比你的代码快3500倍:

In [12]: %%timeit
    ...: mesh = np.linspace(start, end, num=(end/delta + 1))
    ...: elevation_list =[]
    ...: for current_loc in mesh:
    ...:     average_elevation = np.average(df[(df.Span > current_loc - delta/2) & 
    ...:                                       (df.Span < current_loc + delta/2)].Span)
    ...:     elevation_list.append(average_elevation)
    ...: 
1 loops, best of 3: 19.1 s per loop

编辑那么这是如何运作的?在midpoints中,我们存储了桶之间边界的排序列表。然后,我们在此排序列表上使用searchsorted进行二进制搜索,并获取idx,它基本上告诉我们每个数据点属于哪个存储桶。剩下的就是对每个桶中的所有值进行分组。这是bincount的用途。给定一系列整数,它计算每个数字出现的次数。给定一组int,以及相应的weights数组,而不是为存储桶的计数器添加1,它会在weights中添加相应的值。通过两次调用bincount,您可以得到每个桶的总和和项目数:除以它们,您就得到了桶的平均值。

答案 1 :(得分:1)

这是一个想法 - 可能仍然太慢,但我想我会分享。首先,一些虚拟数据。

df = pd.DataFrame(data={'Timestamp': 17210, 
                        'Span': np.linspace(-1, 92, num=60000), 
                        'Elevation': np.linspace(33., 37., num=60000)})

然后,获取您创建的网格数组,将其转换为数据框,并添加移位条目,以便数据框中的每个条目代表新偶数跨度的一步。

mesh_df = pd.DataFrame(mesh, columns=['Equal_Span'])
mesh_df['Equal_Span_Prev'] = mesh_df['Equal_Span'].shift(1)
mesh_df = mesh_df.dropna()

接下来,我想根据两个Equal_Span列之间的条目将此数据框与更大的数据集连接起来。在pandas中可能有一种方法,但笛卡尔式连接似乎更容易在SQL中表达,所以首先,我将所有数据传送到内存中的sqlite数据库。如果遇到内存问题,请将其作为基于文件的数据库。

import sqlite3
con = sqlite3.connect(':memory:')
df.to_sql('df', con, index=False)
mesh_df.to_sql('mesh_df', con, index=False)

这是主查询。在我的测试数据上花了大约1分30秒,所以这可能仍需要很长时间才能完成整个数据集。

join_df = pd.read_sql("""SELECT a.Timestamp, a.Span, a.Elevation, b.Equal_Span
                         FROM df a, mesh_df b
                         WHERE a.Span BETWEEN b.Equal_Span_Prev AND b.Equal_Span""", con)

但是一旦数据处于这种形式,就可以轻松/快速地获得所需的平均值。

join_df.groupby(['Timestamp','Equal_Span'])['Elevation'].mean()
相关问题