【问题标题】:Pandas groupby and assign last of first group to the first of second groupPandas groupby 并将第一组的最后一个分配给第二组的第一个
【发布时间】:2021-06-17 13:02:47
【问题描述】:

我有一个如下的数据框:

def addJump(s):
    return s+'-'+s.shift(-1)

newDF = pd.DataFrame({
   'Group': ['A','A','B','C','B','B','A','C','C','A','D','D'],
   'Value': [1  , 3 , 5 , 10, 3 , 4 , 12, 11, 5 , 6 , 8 , 9 ],
   'Extra': [0  , 7 , 0 , 3 , 0 , 0 , 1 , 0 , 2 , 5 , 0 , 0 ]
}, index=   [ 0 , 0 , 0 , 1 , 1 , 1 , 2 , 2 , 3 , 3 , 3 , 4])
newDF['Jump'] = newDF.groupby(level=0)['Group'].transform(addJump)
newDF

  Group  Value  Extra  Jump
0   A      1    0      A-A
0   A      3    7      A-B
0   B      5    0      NaN
1   C     10    3      C-B
1   B      3    0      B-B
1   B      4    0      NaN
2   A     12    1      A-C
2   C     11    0      NaN
3   C      5    2      C-A
3   A      6    5      A-D
3   D      8    0      NaN
4   D      9    0      NaN

我需要在同一个index 中找到组更改的位置,并将下一个Group 第一次出现的Value 更新为前一个Group 的最后一个Value。例如,如果我们查看索引 0,则第 3 行中的 Value 应更新为 Value[row 2] + Extra[row 2]=10 ,更新将如下所示。

  Group  Value  Extra  Jump
0   A      1    0      A-A
0   A      3    7      A-B
0   B      10   0      NaN

最终结果如下:

  Group  Value  Extra  Jump
0   A      1    0      A-A
0   A      3    7      A-B
0   B     10    0      NaN
1   C     10    3      C-B
1   B     13    0      B-B
1   B      4    0      NaN
2   A     12    1      A-C
2   C     12    0      NaN
3   C      5    2      C-A
3   A      7    5      A-D
3   D      12   0      NaN
4   D      9    0      NaN

我可以使用groupby(level=0) 单独处理每个索引组,就像使用 addJump 函数一样。但是,我需要再次 groupby('Group') 并应用下一个函数,在该函数中我调用第一个 Group 中的最后一个并将其分配给第二个 Group 中的第一个。而这正是我挣扎的地方。

【问题讨论】:

  • 你的数据是有序的,所有相同的索引都在跟随吗?
  • 是的,它们已订购
  • for index =3 group D,上一行(index=3, group=A)的原始值给出6+5=11,但是你说的结果是12,是不是因为值在您编写 12 的操作之后,行的新值 (index=3, group=A) 变为 7?
  • 是的,正如您所说。新值为 7,因此更新后的值为 12

标签: python-3.x pandas dataframe pandas-groupby


【解决方案1】:

因为您需要更新以前的值以防它也发生变化,所以编写一个 for 循环更容易。为要更改的行创建一个带有 True 的掩码,然后在 for 循环中执行一个简单的 if else

# create a mask to get True for rows meeting the criteria to change
mask = (newDF['Group'].ne(newDF['Group'].shift()) 
        & (newDF.index.to_series().shift() == newDF.index))

# initialize values and return list
newVal = newDF['Value'].iloc[0]
l = []

# now loop keep the value from the previous loop and extra coumn shifted
for val, ext_sh, b in zip(newDF['Value'], newDF['Extra'].shift(fill_value=0), mask):
    if b: # you change these rows
        newVal = newVal + ext_sh
    else: # keep original value
        newVal = val
    l.append(newVal)
# assign the result to a column, can do it in Value directly
newDF['newVal'] = l

print(newDF)
  Group  Value  Extra  newVal
0     A      1      0       1
0     A      3      7       3
0     B      5      0      10
1     C     10      3      10
1     B      3      0      13
1     B      4      0       4
2     A     12      1      12
2     C     11      0      13
3     C      5      2       5
3     A      6      5       7
3     D      8      0      12
4     D      9      0       9

编辑:看了一下,你可以有一个 vectotize 版本,使用带有 True 的 mask_ 来改变行,然后传播以前的值一次 mask @987654324 @,并为想要的行添加移动的额外值和累积总和,当不在 mask_ 中时删除以重新启动累积总和。老实说,使用 for 循环的版本更难维护,但可能更快

mask_ = (newDF['Group'].ne(newDF['Group'].shift()) 
        & (newDF.index.to_series().shift() == newDF.index))

s_ = newDF['Extra'].shift().where(mask_).cumsum().ffill().fillna(0)

newDF['newVal2'] = (
    newDF['Value'].mask(mask_).ffill()
    + s_ 
    - s_.mask(mask_).ffill()
)

【讨论】:

  • 这适用于我分享的示例。但是有更快的方法吗?作为原始数据框,我包含 200k+ 行,这需要很长时间。
  • @AliSultan 查看编辑,我找到了一种矢量化方式,但更难理解:)
  • 您能评论一下这些台词以便我更好地理解吗?正如你所说,第二种解决方案有点棘手
  • @AliSultan 实际上很难在操作上加上有意义的词,我建议print(s_)print(s_.mask(mask_).ffill())print(s_- s_.mask(mask_).ffill()) 看看结果。真的是用来在后面几行要改变的时候得到extra的累积总和,看最后打印的最后不是0是7,也就是上一行的Extra(5)+之前的那一行(2)。因为最后,使用之前更新的值就像使用 (5) 之前的两行 + (2) 之前的两行中的额外 + (5) 之前的额外行
猜你喜欢
  • 1970-01-01
  • 2021-01-08
  • 1970-01-01
  • 1970-01-01
  • 2021-09-19
  • 1970-01-01
  • 2022-11-16
  • 1970-01-01
  • 2021-10-16
相关资源
最近更新 更多