如何在 pandas groupby 对象上应用函数并将结果保存回父数据框的新列?

Posted

技术标签:

【中文标题】如何在 pandas groupby 对象上应用函数并将结果保存回父数据框的新列?【英文标题】:How to apply a function on a pandas groupby object and save the results back into a new column of the parent dataframe? 【发布时间】:2019-09-06 13:14:12 【问题描述】:

我有一个看起来像这样的熊猫数据框:

In [5]: import pandas as pd                                                     

In [6]: df = pd.DataFrame('X': [0, 123, 342, 353, 467, 345, 789, 543, 3913], 
   ...:                    'Y': [0, 12, 23, 41, 23, 45, 23, 53, 23], 
   ...:                    'Group': [0, 1, 2, 0, 1, 2, 0, 1, 2])               

In [7]: df                                                                      
Out[7]: 
      X   Y  Group
0     0   0      0
1   123  12      1
2   342  23      2
3   353  41      0
4   467  23      1
5   345  45      2
6   789  23      0
7   543  53      1
8  3913  23      2

这三组代表测量系列,我想为系列的每次测量计算到前一个元素的欧几里得距离,并将每次测量加起来。 (第一次测量距离 = 0)。

我已阅读此处有关如何将 groupby 操作的结果重新分配回父数据框的所有论坛主题。但是在我基于组为数据框的每个条目(非聚合)计算一些东西的情况下,我找不到任何解决方案。

所以我想知道如何结合这些步骤:

from scipy.spatial.distance import euclidean

# 1. Group data
group = df.groupby('Group')
# 2. Calculate cumulative euclidean distance for each group
group['Distance'] = group.apply(lambda row: euclidean(row['X'], row['Y']).cumsum(), axis=1)
# 3. Assign back to original dataframe

第 1 步非常简单。对于第 2 步,我尝试了很多 df.groupby.applydf.groupby.apply.transform 的组合以及定义我自己的函数(不知道这是否适合单行)。但我无法真正让它按照我想要的方式行事。我假设groupby().transform() 是我想要的,但我无法让它按行操作。

还为了将结果重新分配给我的原始数据框而不是仅分配给 groupby 对象,我尝试了df.joinpd.mergepd.concat 等等,但我现在非常困惑什么区别是:D。

我想要的输出是:

Out[7]: 
      X   Y  Group  Distance  Cumulative Distance
0     0   0      0         0                    0
1   123  12      1         0                    0 
2   342  23      2         0                    0
3   353  41      0    355.37               355.37   
4   467  23      1    344.17               344.17     
5   345  45      2     22.20                22.20    
6   789  23      0    436.37               791.74     
7   543  53      1     81.71               425.88     
8  3913  23      2   3568.07              3590.44 

我只需要累积距离(再次按组计算)。但我将个人距离列为中间步骤。

【问题讨论】:

为什么在 idx 1 和 2 0 处会出现“距离”? @ChrisA 他们是小组的起点。在组内计算距离。 我认为您的预期结果是错误的。您假设使用 euclidean(point1, point2) euclidean([467, 23], [123, 12]) 应该产生 344.17 而不是 457.66 对不起,我在跑,想在离开前把问题推开,所以我在 excel 中快速完成了距离。我会适应的。 @ChrisA是的,我想按组计算到前一个条目的距离。所以对于每个组中的第一个条目,它应该是 0。 请将您的预期结果与我的回答进行比较。 【参考方案1】:

使用groupby applyshift 获取每一行的前一个点,然后使用bfill 自己填充第一个点。

之后,使用 zip XY 一起创建新列。

df.sort_values('Group', inplace=True)
df[['X_shift', 'Y_shift']] = df.groupby('Group')[['X', 'Y']].apply(lambda x: x.shift(1)).bfill()
df['point_1'] = tuple(zip(df.X, df.Y))
df['point_2'] = tuple(zip(df.X_shift, df.Y_shift))

df

      X   Y  Group  X_shift  Y_shift     point_1        point_2
0     0   0      0      0.0      0.0      (0, 0)     (0.0, 0.0)
3   353  41      0      0.0      0.0   (353, 41)     (0.0, 0.0)
6   789  23      0    353.0     41.0   (789, 23)  (353.0, 41.0)
1   123  12      1    123.0     12.0   (123, 12)  (123.0, 12.0)
4   467  23      1    123.0     12.0   (467, 23)  (123.0, 12.0)
7   543  53      1    467.0     23.0   (543, 53)  (467.0, 23.0)
2   342  23      2    342.0     23.0   (342, 23)  (342.0, 23.0)
5   345  45      2    342.0     23.0   (345, 45)  (342.0, 23.0)
8  3913  23      2    345.0     45.0  (3913, 23)  (345.0, 45.0)

并使用apply计算每个点的欧式距离,然后使用groupbycumsum得到最终结果。

df['Distance'] = df.apply(lambda row: euclidean(row.point_1, row.point_2), axis=1)

df

      X   Y  Group  X_shift  Y_shift     point_1        point_2     Distance
0     0   0      0      0.0      0.0      (0, 0)     (0.0, 0.0)     0.000000
3   353  41      0      0.0      0.0   (353, 41)     (0.0, 0.0)   355.373043
6   789  23      0    353.0     41.0   (789, 23)  (353.0, 41.0)   436.371401
1   123  12      1    123.0     12.0   (123, 12)  (123.0, 12.0)     0.000000
4   467  23      1    123.0     12.0   (467, 23)  (123.0, 12.0)   344.175827
7   543  53      1    467.0     23.0   (543, 53)  (467.0, 23.0)    81.706793
2   342  23      2    342.0     23.0   (342, 23)  (342.0, 23.0)     0.000000
5   345  45      2    342.0     23.0   (345, 45)  (342.0, 23.0)    22.203603
8  3913  23      2    345.0     45.0  (3913, 23)  (345.0, 45.0)  3568.067824

df['Cumulative Distance'] = df.groupby('Group').Distance.cumsum()

# Drop unuse columns
df.drop(columns=['X_shift', 'Y_shift', 'point_1', 'point_2'], inplace=True)
df.sort_index(inplace=True)
df

      X   Y  Group     Distance  Cumulative Distance
0     0   0      0     0.000000             0.000000
1   123  12      1     0.000000             0.000000
2   342  23      2     0.000000             0.000000
3   353  41      0   355.373043           355.373043
4   467  23      1   344.175827           344.175827
5   345  45      2    22.203603            22.203603
6   789  23      0   436.371401           791.744445
7   543  53      1    81.706793           425.882620
8  3913  23      2  3568.067824          3590.271428

【讨论】:

现在测试您的解决方案。到目前为止我发现了两个问题:首先,df.sort_values('Group', inplace=True) 没有为我排序索引。我的真实数据有 120 行,当按组排序时,我得到组 0 的索引未排序,如[0, 69, 108, 30, 96...]。我不知道这是为什么。第二:在计算累积距离之前,我必须使用df.sort_index(inplace=True)。这可能与第一个问题有关。我把你的代码风格稍微改了一下,放到了一个函数中,所以我会进一步检查错误是否在我这边。 事实证明您的解决方案在我提供的测试数据上完美运行。该错误必须隐藏在我的真实世界数据的应用程序中。非常感谢您的帮助,我会从这里解决。 @cripcate 你可以尝试评论sort_values并在第2行替换.apply(lambda x: x.shift(1).bfill())吗? 我的数据在开始之前没有排序。它的工作原理是这样的:df[['X_shift', 'Y_shift']] = df.groupby('Group')[['ROI_X_µm', 'ROI_Y_µm']].apply(lambda x: x.shift(1).bfill())。然后df['P1'] = tuple(zip(df['ROI_X_µm'], df['ROI_Y_µm']))df['P2'] = tuple(zip(df['X_shift'], df['Y_shift'])),排序:df.sort_values('Group', inplace=True)计算距离:df['Dist'] = df.apply(lambda row: euclidean(row['P1'], row['P2']), axis=1)df.sort_index(inplace=True)df['Cum_Dist'] = df.groupby('Group')['Dist'].cumsum()

以上是关于如何在 pandas groupby 对象上应用函数并将结果保存回父数据框的新列?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 pandas groupby 对象上调用不同的聚合操作

Pandas | 18 GroupBy 分组

在 groupby 熊猫对象上应用 rolling() 时,多索引重复

如何让 pandas groupby 不偷懒?

如何对不同长度的 Python Pandas groupby 对象进行切片?

Python pandas:替换 groupby 对象中的选择值