Pandas DataFrame重复行求和去重问题排查(Python)
Pandas DataFrame重复行求和保留最后一行异常排查
需求描述
对DataFrame中ID、RecordingType、Date完全相同的行,将Sleep字段求和后保留组内最后一行,删除其余行。当前代码运行后,17/01/2022对应的Sleep值仅为220(预期250),且当重复行超过3行时功能失效。
原始DataFrame
ID RecordingType Date Sleep 1 MinsAsleep 17/01/2022 30 1 MinsAsleep 17/01/2022 40 1 MinsAsleep 17/01/2022 50 1 MinsAsleep 17/01/2022 60 1 MinsAsleep 17/01/2022 70 1 MinsAsleep 19/01/2022 100
预期结果
ID RecordingType Date Sleep 1 MinsAsleep 17/01/2022 250 1 MinsAsleep 19/01/2022 100
现有代码
temp_physdata = # dataframe I inserted above temp_physdata = temp_physdata.sort_values(by=['ID', 'RecordingType', 'Date','Sleep'], ascending=True).reset_index().drop(columns=["index"]) temp_physdata["ToRemove"] = False dict_temp_physdata = temp_physdata.to_dict("records") outputcolnames = {'MinsAsleep':'Sleep'} for i in range(temp_physdata.shape[0]-1): curr_recording_type = dict_temp_physdata[i]["RecordingType"] next_recording_type = dict_temp_physdata[i+1]["RecordingType"] # Check if current and next row have the same ID and RecordingType and Date if (dict_temp_physdata[i]["ID"] == dict_temp_physdata[i+1]["ID"] and curr_recording_type == next_recording_type and dict_temp_physdata[i]["Date"] == dict_temp_physdata[i+1]["Date"]): # Check if current row is MinsAsleep recording if curr_recording_type == 'MinsAsleep': # Check if the next row is the last row with the same ID, RecordingType, and Date values if i+1 == temp_physdata.shape[0]-1 or (dict_temp_physdata[i+1]["ID"] != dict_temp_physdata[i+2]["ID"] or next_recording_type != dict_temp_physdata[i+2]["RecordingType"] or dict_temp_physdata[i+1]["Date"] != dict_temp_physdata[i+2]["Date"]): temp_physdata.at[i,"ToRemove"] = True # Update the last row with the sum of all the MinsAsleep Recording values temp_physdata.at[i+1, outputcolnames[next_recording_type]] += dict_temp_physdata[i][outputcolnames[curr_recording_type]] # Mark all the other rows to be removed for j in range(i, temp_physdata.shape[0]-1): if dict_temp_physdata[j]["ID"] == dict_temp_physdata[i]["ID"] and curr_recording_type == dict_temp_physdata[j]["RecordingType"] and dict_temp_physdata[i]["Date"] == dict_temp_physdata[j]["Date"]: temp_physdata.at[j, "ToRemove"] = True else: temp_physdata.at[i,"ToRemove"] = True temp_physdata.at[i+1,outputcolnames[next_recording_type]] = max(dict_temp_physdata[i][outputcolnames[curr_recording_type]], dict_temp_physdata[i+1][outputcolnames[next_recording_type]]) temp_physdata = temp_physdata[temp_physdata["ToRemove"] == False].reset_index().drop(columns=["index"])
实际结果
ID RecordingType Date Sleep 1 MinsAsleep 17/01/2022 220 1 MinsAsleep 19/01/2022 100
问题原因分析
- 求和逻辑不完整:现有代码仅当
i+1是同组最后一行时,才将当前行Sleep值累加到下一行,导致组内前面的多行(如示例中前3行的30、40、50)未被纳入求和,最终仅累加了40+50+60+70=220,遗漏了首行的30。 - 字典数据未同步更新:
dict_temp_physdata是基于初始DataFrame转换的字典,后续修改temp_physdata的Sleep值不会同步到字典中,后续循环判断和计算依赖的仍是原始数据,逻辑易出错。 - 删除标记逻辑混乱:内层循环标记删除时,错误将同组最后一行也标记为待删除(如示例中第4行的
ToRemove会被设为True),仅因循环范围未覆盖最后一行才侥幸保留,但逻辑存在严重漏洞。 - 循环条件限制:仅处理相邻行的判断,无法应对超过3行的重复组,导致前面的行完全未被处理。
修复方案
直接使用Pandas内置的groupby功能实现需求,代码简洁且可靠:
方案1:仅保留求和后的Sleep值
temp_physdata = temp_physdata.groupby( ['ID', 'RecordingType', 'Date'], as_index=False ).agg(Sleep=('Sleep', 'sum'))
方案2:保留组内最后一行的其他字段(若存在)
如果需要保留组内最后一行的其他列信息,同时更新Sleep为求和值:
temp_physdata = temp_physdata.groupby( ['ID', 'RecordingType', 'Date'], as_index=False ).apply(lambda x: x.assign(Sleep=x['Sleep'].sum()).tail(1)).reset_index(drop=True)
以上两种方案均能正确计算出17/01/2022的Sleep值为250,且无论重复行数量多少都能稳定运行。
内容的提问来源于stack exchange,提问作者mariant
相关产品推荐
相关产品推荐

