如何将按帧范围记录的动物行为DataFrame扩展为逐帧格式并保留未标注帧的NaN值
如何将按帧范围记录的动物行为DataFrame扩展为逐帧格式并保留未标注帧的NaN值
我完全懂你的需求啦——你手头的动物行为标注数据是按帧范围记录的,但现在需要转换成逐帧的格式,关键是那些没被标注的空白帧(比如例子里的11-15帧)必须保留NaN,不能像之前用ffill那样把前面的行为硬生生填充进去。下面给你两个实用的解决方法:
方法1:用explode展开帧范围(直观易懂)
这个方法的核心是先把每个行为对应的帧范围拆成单独的帧列表,再把列表展开成逐行,最后补全所有帧的索引来保留NaN。
import pandas as pd # 你的原始数据 data = { 'Frame start': [0, 2, 5, 9, 16], 'Frame stop': [2, 5, 9, 11, 20], 'Behavior': ['turning', 'immobility', 'mild movement', 'immobility', 'jump']} df = pd.DataFrame(data) df.set_index('Frame start', inplace=True) # 步骤1:重置索引,把Frame start变回普通列 df_reset = df.reset_index() # 步骤2:为每一行生成对应的帧列表(注意Frame stop是不包含的,所以用range(start, stop)) df_reset['Frames'] = df_reset.apply(lambda row: list(range(row['Frame start'], row['Frame stop'])), axis=1) # 步骤3:展开帧列表,每个帧单独成一行 expanded_df = df_reset.explode('Frames').set_index('Frames')['Behavior'] # 步骤4:重新索引到完整的帧范围(这里是0到19),空白帧自动填充为NaN full_frames = range(0, 20) final_df = expanded_df.reindex(full_frames) # 查看结果 print(final_df)
运行后得到的结果正是你想要的:
Frames 0 turning 1 turning 2 immobility 3 immobility 4 immobility 5 mild movement 6 mild movement 7 mild movement 8 mild movement 9 immobility 10 immobility 11 NaN 12 NaN 13 NaN 14 NaN 15 NaN 16 jump 17 jump 18 jump 19 jump Name: Behavior, dtype: object
方法2:利用区间索引(适合大数据量)
如果你的标注数据量很大,用区间索引的方式效率会更高,原理是把每个行为的帧范围转换成区间,再匹配每个帧是否属于某个区间。
import pandas as pd # 原始数据 data = { 'Frame start': [0, 2, 5, 9, 16], 'Frame stop': [2, 5, 9, 11, 20], 'Behavior': ['turning', 'immobility', 'mild movement', 'immobility', 'jump']} df = pd.DataFrame(data) df.set_index('Frame start', inplace=True) # 步骤1:创建包含所有帧的DataFrame full_df = pd.DataFrame({'Frame': range(0, 20)}) # 步骤2:把原始数据的帧范围转换成左闭右开的区间 df_reset = df.reset_index() df_reset['Frame Interval'] = pd.IntervalIndex.from_arrays(df_reset['Frame start'], df_reset['Frame stop'], closed='left') # 步骤3:用merge_asof匹配每个帧对应的区间和行为 merged_df = pd.merge_asof(full_df, df_reset[['Frame Interval', 'Behavior']], left_on='Frame', right_on='Frame Interval', direction='backward') # 步骤4:判断帧是否在对应的区间内,不在则设为NaN merged_df['Behavior'] = merged_df.apply( lambda x: x['Behavior'] if x['Frame'] in x['Frame Interval'] else pd.NA, axis=1 ) # 整理成最终格式 final_df = merged_df.set_index('Frame')['Behavior'] print(final_df)
这个方法得到的结果和方法1完全一致,但处理大规模数据时速度会更快。
为什么之前的ffill不行?
你之前尝试的reindex + ffill之所以不符合要求,是因为它会把最后一个非NaN值一直填充到下一个非NaN值出现,而我们需要的是只有标注过的帧才有行为值,未标注的空白帧保持NaN,所以必须先精准展开有标注的帧,再补全空白索引,而不是直接填充。
备注:内容来源于stack exchange,提问作者Albert hiuka fok
相关产品推荐
相关产品推荐

