Pandas:根据下一行的值替换组中的列值
我正在处理这个数据框,它必须按
DocumentId
和
PersonId
分组。在该组中,如果
End Date
列为空,则用 DocCode 为 RT 的行填充它。
DocumentID,PersonID,DocCode,StartDate,Amount,EndDate
120303,110001,FB,5/18/21,245,
120303,110001,TW,5/25/21,460,
120303,110001,RT,6/1/21,,6/6/21
120303,110011,GK,4/1/21,0,
120303,110011,AK,4/8/21,128,
120303,110011,PL,4/12/21,128,
120303,110011,FB,4/16/21,256,
120303,110011,RT,4/28/21,,5/4/21
它工作正常,但还有另一个问题。在
DocumentId & PersonID
组中,如果金额发生变化,下一个金额的
StartDate
将是上一个金额的
EndDate
。因此中间数据​​框将如下所示:
然后,该组内所有具有重复金额和空金额的行将折叠为 1 行。
这是我用来填充
DocCode
所在行中所有空的
EndDate
列的代码
RT
:
df = pd.read_csv(path).sort_values(by=["StartDate"])
df.groupby(["DocumentId", "PersonId"]).apply(fill_end_date).reset_index(drop=True)
def fill_end_date(df):
rt_doc = df[df["DocumentCode"] == "RT"]
# if there is row in this group by with DocumentCode RT
if not rt_doc.empty:
end_date = rt_doc.iloc[0]["EndDate"]
# and EndDate not empty
if pd.notnull(end_date):
# all other rows need to be filled by that end date
df = df.fillna({"EndDate": end_date})
return df
首先正向填充
Amount
列值以填充所有空白。这样做是为了查找 Amount 何时发生变化。将
StartDate
值向后移动
1
,这将用于在检测到 Amount 变化时填充
EndDate
列:
df.Amount.ffill(inplace=True)
df['StartDateShift'] = df['StartDate'].shift(-1)
>>> df
DocumentID PersonID DocCode StartDate Amount EndDate StartDateShift
0 120303 110001 FB 5/18/21 245.0 NaN 5/25/21
1 120303 110001 TW 5/25/21 460.0 NaN 6/1/21
2 120303 110001 RT 6/1/21 460.0 6/6/21 4/1/21
3 120303 110011 GK 4/1/21 0.0 NaN 4/8/21
4 120303 110011 AK 4/8/21 128.0 NaN 4/12/21
5 120303 110011 PL 4/12/21 128.0 NaN 4/16/21
6 120303 110011 FB 4/16/21 256.0 NaN 4/28/21
7 120303 110011 RT 4/28/21 256.0 5/4/21 NaN
现在获取所有
Amount
发生变化的行,并从中创建
掩码
。使用此掩码将
EndDate
中的
NaNs
填充到
StartDateShift
中的值中:
mask = (df['Amount'] != df['Amount'].shift(-1))
df['EndDate'].fillna(df[mask]['StartDateShift'], inplace=True)
>>> df
DocumentID PersonID DocCode StartDate Amount EndDate StartDateShift
0 120303 110001 FB 5/18/21 245.0 5/25/21 5/25/21
1 120303 110001 TW 5/25/21 460.0 NaN 6/1/21
2 120303 110001 RT 6/1/21 460.0 6/6/21 4/1/21
3 120303 110011 GK 4/1/21 0.0 4/8/21 4/8/21
4 120303 110011 AK 4/8/21 128.0 NaN 4/12/21
5 120303 110011 PL 4/12/21 128.0 4/16/21 4/16/21
6 120303 110011 FB 4/16/21 256.0 NaN 4/28/21
7 120303 110011 RT 4/28/21 256.0 5/4/21 NaN
回填
EndDate
值,然后
删除 Amount 列中的重复项
(
将空值折叠到一行
)。现在再删除
StartDateShift
列,您将获得最终的数据框:
df['EndDate'].bfill(inplace=True)
df = df.drop_duplicates('Amount', keep='first').drop('StartDateShift', axis=1)
>>> df
DocumentID PersonID DocCode StartDate Amount EndDate
0 120303 110001 FB 5/18/21 245.0 5/25/21
1 120303 110001 TW 5/25/21 460.0 6/6/21
3 120303 110011 GK 4/1/21 0.0 4/8/21
4 120303 110011 AK 4/8/21 128.0 4/16/21
6 120303 110011 FB 4/16/21 256.0 5/4/21
在上述步骤中,我没有
groupby
DocumentId
和
PersonId
列,因为没有它,逻辑在您的示例 df 上运行良好。但对于您的实际用例,您可以在
DocumentId
和
PersonId
列上应用
groupby
,并对每个
group
执行上述所有步骤,然后在最后执行
concat
:
dfs = []
for i,dfg in df.groupby(['DocumentID','PersonID']):
dfg.Amount.ffill(inplace=True)
dfg['StartDateShift'] = dfg['StartDate'].shift(-1)
dfg['EndDate'].fillna(dfg[~(dfg['Amount']==dfg['Amount'].shift(-1))]['StartDateShift'], inplace=True)
dfg['EndDate'].bfill(inplace=True)
dfg = dfg.drop_duplicates('Amount', keep='first').drop('StartDateShift', axis=1)
dfs.append(dfg)
final_df = pd.concat(dfs)
>>> final_df
DocumentID PersonID DocCode StartDate Amount EndDate
0 120303 110001 FB 5/18/21 245.0 5/25/21
1 120303 110001 TW 5/25/21 460.0 6/6/21
3 120303 110011 GK 4/1/21 0.0 4/8/21
4 120303 110011 AK 4/8/21 128.0 4/16/21
6 120303 110011 FB 4/16/21 256.0 5/4/21