Pandas应用函数于多列和多行

9
我有一个包含连续像素坐标(行和列)'xpos','ypos' 的 dataframe,我想要计算相邻像素之间路径的角度。目前,我有下面的解决方案,它能正常工作,并且对于我的数据大小来说速度足够快,但是遍历所有行似乎不是 pandas 应该处理的方式。我知道如何将函数应用到不同的列,以及如何将函数应用到不同行的列,但是无法理解如何结合两者。

这是我的代码:

fix_df = pd.read_csv('fixations_out.csv')

# wyliczanie kąta sakady
temp_list=[]
for count, row in df.iterrows():
    x1 = row['xpos']
    y1 = row['ypos']
    try:
        x2 = df['xpos'].ix[count-1]
        y2 = df['ypos'].ix[count-1]
        a = abs(180/math.pi * math.atan((y2-y1)/(x2-x1)))
        temp_list.append(a)
    except KeyError:
        temp_list.append(np.nan)

然后我将临时列表插入到df中。

编辑: 在实施评论中的提示后,我的代码如下:

df['diff_x'] = df['xpos'].shift() - df['xpos']
df['diff_y'] = df['ypos'].shift() - df['ypos']

def calc_angle(x):
    try:
        a = abs(180/math.pi * math.atan((x.diff_y)/(x.diff_x)))
        return a
    except ZeroDivisionError:
        return 0

df['angle_degrees'] = df.apply(calc_angle, axis=1)

我对我的df进行了三种解决方案的时间比较(df的大小约为6k行),迭代方式几乎比apply慢9倍,比不使用apply慢大约1500倍:

包括将新列插入到df中的迭代解决方案的执行时间:1.51秒

使用apply的无迭代解决方案的执行时间:0.17秒

使用diff()的EdChum的答案的执行时间,无迭代和无apply:0.001秒

建议:不要使用迭代或apply,始终尝试使用向量化计算;这不仅更快,而且更易读。


1
作为开始,您可以计算差异,如 df['xpos'].shift() - df['xpos'],而不是逐行执行此操作,然后您可以在整个列上使用您的函数计算角度。 - EdChum
我已更新我的答案,我获得了少于1毫秒的性能,这比许多数量级都要快。 - EdChum
1个回答

13

你可以通过以下方法实现,我将pandas的方式与你的方式进行比较,发现速度快了1000倍,而且这还没有将列表作为新列添加回去!这是在一个有10000行的数据帧上完成的。

In [108]:

%%timeit
import numpy as np
df['angle'] = np.abs(180/math.pi * np.arctan(df['xpos'].shift() - df['xpos']/df['ypos'].shift() - df['ypos']))

1000 loops, best of 3: 1.27 ms per loop

In [100]:

%%timeit
temp_list=[]
for count, row in df.iterrows():
    x1 = row['xpos']
    y1 = row['ypos']
    try:
        x2 = df['xpos'].ix[count-1]
        y2 = df['ypos'].ix[count-1]
        a = abs(180/math.pi * math.atan((y2-y1)/(x2-x1)))
        temp_list.append(a)
    except KeyError:
        temp_list.append(np.nan)
1 loops, best of 3: 1.29 s per loop

如果可能的话,请避免使用apply,因为它是逐行操作的。如果您能找到一个可以在整个系列或数据框上工作的向量化方法,那么请始终优先考虑这种方法。

更新

由于您只是对前一行进行减法操作,因此有内置方法可以完成此操作diff,这会得到更快的代码:

In [117]:

%%timeit
import numpy as np
df['angle'] = np.abs(180/math.pi * np.arctan(df['xpos'].diff(1)/df['ypos'].diff(1)))

1000 loops, best of 3: 1.01 ms per loop

另一个更新

对于系列和数据帧的除法,现在还有一种内置方法,这可以进一步缩短时间并实现低于1毫秒的时间:

In [9]:

%%timeit
import numpy as np
df['angle'] = np.abs(180/math.pi * np.arctan(df['xpos'].diff(1).div(df['ypos'].diff(1))))

1000 loops, best of 3: 951 µs per loop

第一个案例中,abs 应该改为 np.abs 吗? - joris
@joris,是的,为了保持一致性,但差别不大,1.27毫秒与1.29毫秒,我会更新答案的,谢谢。 - EdChum

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接