加快熊猫的double iterrows()

时间:2019-09-10 16:02:00

标签: python pandas numpy dataframe

我有几个需要处理的大数据集(约3000行,100列)。每行代表地图上的一个点,并且有一堆与该点关联的数据。我正在进行空间计算(将来可能会引入更多变量),因此对于每一行,我仅使用1-4列中的数据。问题是我必须将每一行与其他每一行进行比较-本质上,我正在尝试找出每个点之间的空间关系。在项目的此阶段,我正在计算以确定表中每个点在给定半径内有多少个点。我必须这样做5到6次(即针对多个半径尺寸运行距离计算功能。)这意味着我最终只能进行大约10到5000万次计算。太慢了非常慢(例如超过9个小时的计算时间。)

运行所有这些计算之后,我需要将它们作为新列追加到原始(很大)数据框中。目前,我一直在将整个数据帧传递给函数,这可能会进一步降低速度。

我知道很多人都在超级计算机或专用多核单元上运行这种大小的计算,但是我想尽我所能来优化我的代码以尽可能高效地运行,而不管硬件如何。

我当前正在使用带有.iterrows()的double for循环。我已经消除了尽可能多的不必要的步骤。我可能能够将数据框配对为一个子集,然后将其传递给函数,然后在另一步骤中将计算结果附加到原始数据,如果这样做将有助于加快处理速度。我还考虑过使用.apply()消除外部循环(例如,将.apply()内部循环应用于数据帧中的所有行...?)

下面,我展示了我正在使用的功能。这可能是我在该项目中拥有的最简单的应用程序……还有其他一些应用程序根据特定的空间标准进行更多的计算/返回对或成组的点,但这是显示我的基本概念的最佳示例做。

# specify file to be read into pandas
df = pd.read_csv('input_file.csv', low_memory = False)

# function to return distance between two points w/ (x,y) coordinates
def xy_distance_calc(x1, x2, y1, y2):
    return math.sqrt((x1 - x2)**2 + (y1-y2)**2)

# function to calculate number of points inside a given radius for each point
def spacing_calc(data, rad_crit, col_x, col_y):
    count_list = list()
    df_list = pd.DataFrame()

    for index, row in data.iterrows():
        x_row_current = row[col_x]
        y_row_current = row[col_y]
        count = 0
        # dist_list = list()

        for index1, row1 in data.iterrows():
            x1 = row1[col_x]
            y1 = row1[col_y]
            dist = xy_distance_calc(x_row_current, x1, y_row_current, y1)

            if dist < rad_crit: 
                count += 1

            else:
                continue

        count_list.append(count)

    df_list = pd.DataFrame(data=count_list, columns = [str(rad_crit) + ' radius'])

    return df_list

# call the function for each radius in question, append new data

df_2640 = spacing_calc(df, 2640.0, 'MID_X', 'MID_Y')

df = df.join(df_2640)

df_1320 = spacing_calc(df, 1320.0, 'MID_X', 'MID_Y')
df = df.join(df_1320)

df_1155 = spacing_calc(df, 1155.0, 'MID_X', 'MID_Y')
df = df.join(df_1155)

df_990 = spacing_calc(df, 990.0, 'MID_X', 'MID_Y')
df = df.join(df_990)

df_660 = spacing_calc(df, 660.0, 'MID_X', 'MID_Y')
df = df.join(df_660)

df_330 = spacing_calc(df, 330.0, 'MID_X', 'MID_Y')
df = df.join(df_330)


df.to_csv('spacing_calc_all.csv', index=None)

没有错误,一切正常,我只是认为它没有效率那么高。

1 个答案:

答案 0 :(得分:0)

您的问题是循环次数过多。至少,您应该计算距离矩阵,并对从该矩阵到半径内的点数进行计数。但是,最快的解决方案是使用numpy的矢量化函数,它们是高度优化的C代码。

与大多数学习经历一样,最好从一个小问题入手:

>>> import numpy as np
>>> import pandas as pd
>>> from scipy.spatial import distance_matrix

# Create a dataframe with columns two MID_X and MID_Y assigned at random
>>> np.random.seed(42)
>>> df = pd.DataFrame(np.random.uniform(1, 10, size=(5, 2)), columns=['MID_X', 'MID_Y'])
>>> df.index.name = 'PointID'

            MID_X     MID_Y
PointID                    
0        4.370861  9.556429
1        7.587945  6.387926
2        2.404168  2.403951
3        1.522753  8.795585
4        6.410035  7.372653

# Calculate the distance matrix
>>> cols = ['MID_X', 'MID_Y']
>>> d = distance_matrix(df[cols].values, df[cols].values)

array([[0.        , 4.51542241, 7.41793942, 2.94798323, 2.98782637],
        [4.51542241, 0.        , 6.53786001, 6.52559479, 1.53530446],
        [7.41793942, 6.53786001, 0.        , 6.4521226 , 6.38239593],
        [2.94798323, 6.52559479, 6.4521226 , 0.        , 5.09021286],
        [2.98782637, 1.53530446, 6.38239593, 5.09021286, 0.        ]])

# The radii for which you want to measure. They need to be raised 
# up 2 extra dimensions to prepare for array broadcasting later
>>> radii = np.array([3,6,9])[:, None, None]

array([[[3]],
       [[6]],
       [[9]]])

# Count how many points fall within a certain radius from another
# point using numpy's array broadcasting. `d < radii` will return
# an array of `True/False` and we can count the number of `True`
# by `sum` over the last axis.
#
# The distance between a point to itself is 0 and we don't want
# to count that hence the -1.
>>> count = (d < radii).sum(axis=-1) - 1

array([[2, 1, 0, 1, 2],
       [3, 2, 0, 2, 3],
       [4, 4, 4, 4, 4]])

# Putting everything together for export
>>> result = pd.DataFrame(count, index=radii.flatten()).stack().to_frame('Count')
>>> result.index.names = ['Radius', 'PointID']

                Count
Radius PointID       
3      0            2
       1            1
       2            0
       3            1
       4            2
6      0            3
       1            2
       2            0
       3            2
       4            3
9      0            4
       1            4
       2            4
       3            4
       4            4

最终结果意味着在半径3内,点#0具有2个邻居,点#1具有1个邻居,点#2具有0个邻居,依此类推。根据自己的喜好调整框架的格式并对其进行格式化。

将其扩展到数千个点应该没有问题。