我正在寻找一种有效的方法来处理熊猫中的以下数据。
我有一个包含数十万个开始和结束时间戳记的数据框:
data_df
start_ts end_ts
0 2019-06-10 12:00:00+00:00 2019-06-10 22:30:00+00:00
1 2019-06-11 12:00:00+00:00 2019-06-11 13:30:00+00:00
2 2019-06-11 14:00:00+00:00 2019-06-11 19:00:00+00:00
3 2019-06-14 12:00:00+00:00 2019-06-14 18:30:00+00:00
4 2019-06-10 12:00:00+00:00 2019-06-10 21:30:00+00:00
5 2019-06-11 12:00:00+00:00 2019-06-11 18:30:00+00:00
...
我还有一组标记时间箱(tp1
-tp10
)。每天有10个垃圾箱,但是这些垃圾箱的时间可以每天更改(例如-tp1
可能是一天的00:00到01:30,然后是00:00到01:45在另一天)。每个要处理的数据集都有7天,每天有10个时间段,因此范围集的大小为70,如下所示:
labeled_bins_df
start_range end_range label
0 2019-06-10 00:00:00+00:00 2019-06-10 04:30:00+00:00 tp1
1 2019-06-10 04:30:00+00:00 2019-06-10 09:45:00+00:00 tp2
2 2019-06-10 09:45:00+00:00 2019-06-10 12:30:00+00:00 tp3
...
我想要的是一个具有原始data_df
数据,但又有tp1
至tp10
列以及每行分钟数的表:
timed_bins
start_ts end_ts tp1 tp2 tp3 tp4 ...
0 2019-06-10 12:00:00+00:00 2019-06-10 22:30:00+00:00 0 0 30 120 ...
1 2019-06-11 12:00:00+00:00 2019-06-11 13:30:00+00:00 0 45 45 0 ...
我目前正在天真地执行此操作,遍历我的行,并搜索每个数据行所在的bin,正如您可以想象的那样,这相当慢。是否可以执行任何pandas-fu来对日期时间范围进行这种装箱?
编辑:一种思想,可能有助于思考新的方向。如果我要将所有时间戳(包括数据和标记的bin)都转换为unix时间戳(自1970年1月1日以来的秒数),则将基于整数范围而不是日期进行合并/求和。然后,这将得出每个槽中的秒数,简单地除以60,就可以得到每个槽中的分钟数。这消除了对日期边界等的所有担忧。
编辑2:根据要求,这是一组简化的示例数据,使用了三个不同的时间仓。我专门将其中一个数据样本(第二行)设置为2天。此外,还有一个result_df
显示了预期的输出。
data_samples = [
{'start_ts': '2019-06-10T12:00:00+0000', 'end_ts': '2019-06-10T22:30:00+0000'},
{'start_ts': '2019-06-10T22:00:00+0000', 'end_ts': '2019-06-11T05:30:00+0000'},
{'start_ts': '2019-06-10T10:00:00+0000', 'end_ts': '2019-06-10T14:15:00+0000'},
{'start_ts': '2019-06-12T08:07:00+0000', 'end_ts': '2019-06-12T18:22:00+0000'},
{'start_ts': '2019-06-11T14:03:00+0000', 'end_ts': '2019-06-11T15:30:00+0000'},
{'start_ts': '2019-06-11T02:33:00+0000', 'end_ts': '2019-06-11T10:31:00+0000'}
]
data_set = [{
'start_ts': datetime.datetime.strptime(x['start_ts'], '%Y-%m-%dT%H:%M:%S%z'),
'end_ts': datetime.datetime.strptime(x['end_ts'], '%Y-%m-%dT%H:%M:%S%z')} for x in data_samples]
data_df = pd.DataFrame(data_set)[['start_ts', 'end_ts']]
time_bin_samples = [
{'start_ts': '2019-06-10T00:00:00+0000', 'end_ts': '2019-06-10T08:15:00+0000', 'label': 't1'},
{'start_ts': '2019-06-10T08:15:00+0000', 'end_ts': '2019-06-10T18:00:00+0000', 'label': 't2'},
{'start_ts': '2019-06-10T18:00:00+0000', 'end_ts': '2019-06-11T00:00:00+0000', 'label': 't3'},
{'start_ts': '2019-06-11T00:00:00+0000', 'end_ts': '2019-06-11T09:00:00+0000', 'label': 't1'},
{'start_ts': '2019-06-11T09:00:00+0000', 'end_ts': '2019-06-11T19:15:00+0000', 'label': 't2'},
{'start_ts': '2019-06-11T19:15:00+0000', 'end_ts': '2019-06-12T00:00:00+0000', 'label': 't3'},
{'start_ts': '2019-06-12T00:00:00+0000', 'end_ts': '2019-06-12T10:30:00+0000', 'label': 't1'},
{'start_ts': '2019-06-12T10:30:00+0000', 'end_ts': '2019-06-12T12:00:00+0000', 'label': 't2'},
{'start_ts': '2019-06-12T12:00:00+0000', 'end_ts': '2019-06-13T00:00:00+0000', 'label': 't3'},
]
time_bin_set = [{
'start_ts': datetime.datetime.strptime(x['start_ts'], '%Y-%m-%dT%H:%M:%S%z'),
'end_ts': datetime.datetime.strptime(x['end_ts'], '%Y-%m-%dT%H:%M:%S%z'),
'label': x['label']} for x in time_bin_samples
]
time_bin_df = pd.DataFrame(time_bin_set)[['start_ts', 'end_ts', 'label']]
result_set = [
{'t1': 0, 't2': 360, 't3': 270},
{'t1': 330, 't2': 0, 't3': 120},
{'t1': 0, 't2': 255, 't3': 0},
{'t1': 143, 't2': 90, 't3': 382},
{'t1': 0, 't2': 87, 't3': 0},
{'t1': 387, 't2': 91, 't3': 0}
]
result_df = pd.DataFrame(result_set)
答案 0 :(得分:0)
我知道迭代数据帧的行效率不高。
在这里,我将尝试使用data_df
来标识merge_asof
中每行的第一个和最后一个bin。
然后,我将通过迭代一次数据帧值来构建子数据帧列表,以便添加与一行相对应的所有bin,并合并该列表。
从那里开始计算每个bin的时间间隔并使用pivot_table
即可获得预期的结果。
代码可能是:
# store the index as a column to make sure to keep it
data_df = data_df.rename_axis('ix').reset_index().sort_values(
['end_ts', 'start_ts'])
time_bin_df = time_bin_df.rename_axis('ix').reset_index().sort_values(
['end_ts', 'start_ts'])
# identify first and last bin per row
first = pd.merge_asof(data_df, time_bin_df, left_on='start_ts',
right_on='end_ts', suffixes=('', '_first'),
direction='forward').values
last = pd.merge_asof(data_df, time_bin_df, left_on='end_ts', right_on='start_ts',
suffixes=('', '_ bin')).values
# build a list of bin dataframes (one per row in data_df)
data = []
for i, val in enumerate(first):
elt = time_bin_df[(time_bin_df['ix']>=val[3])
&(time_bin_df['ix']<=last[i][3])].copy()
# compute the begin and end of the intersection of the period and the bin
elt.loc[elt['start_ts']<val[1], 'start_ts'] = val[1]
elt.loc[elt['end_ts']>val[2], 'end_ts'] = val[2]
elt['ix_data'] = val[0]
data.append(elt)
# concat everything
tmp = pd.concat(data)
# compute durations in minutes
tmp['duration'] = (tmp['end_ts'] - tmp['start_ts']).dt.total_seconds() / 60
# pivot to get the expected result
result_df = tmp.pivot_table('duration', 'ix_data', 'label', 'sum', fill_value=0
).rename_axis(None).rename_axis(None, axis=1)
这可能会花费一些时间,因为仍然需要进行冗长的操作来构建数据帧列表,但其他操作应进行向量化。