Рассчитайте продолжительность перекрывающихся диапазонов времени, используя панд
У меня есть большие CSV-файлы данных о трафике, как в примере ниже, для которого мне нужно рассчитать общее количество байтов и продолжительность каждой передачи данных. Временные диапазоны перекрываются, но их необходимо объединить:
first_packet_ts last_packet_ts bytes_uplink bytes_downlink service user_id
1441901695012 1441901696009 165 1212 facebook 3
1441901695500 1441901696212 23 4321 facebook 3
1441901698000 1441901698010 242 3423 youtube 4
1441901698400 1441901698500 423 2344 youtube 4
Желаемый результат:
duration bytes_uplink bytes_downlink service user_id
1200 188 5533 facebook 3
110 665 5767 youtube 4
В настоящее время я использую что-то вроде следующих строк:
df = pd.read_csv(input_file_path)
df = df.groupby(['service', 'user_id'])
durations = df.apply(calculate_duration)
df = df[['bytes_uplink', 'bytes_downlink']].sum()
df = df.reset_index()
Функция calc_duration (ниже) выполняет итерацию содержимого каждой группы, объединяет перекрывающиеся временные интервалы и затем возвращает кадр данных, который затем объединяется с суммированным кадром данных df.
def calculate_duration(group):
ranges = group[['first_packet_ts', 'last_packet_ts']].itertuples()
duration = 0
for i,current_start, current_stop in ranges:
for i, start, stop in ranges:
if start > current_stop:
duration += current_stop - current_start
current_start, current_stop = start, stop
else:
current_stop = max(current_stop, stop)
duration += current_stop - current_start
return duration
Этот подход очень медленный, поскольку включает в себя итерацию и вызов метода apply для каждой группы.
Есть ли более эффективный способ вычислить продолжительность передачи данных, объединяя перекрывающиеся интервалы, используя панд (как-нибудь избежать итераций?), Предпочтительно, не прибегая к использованию Cython?
2 ответа
Как насчет этого? (рассчитав время, может немного помедленнее...)
pd.pivot_table(df, columns='user_id', index='service',
values=['bytes_uplink', 'bytes_downlink'], aggfunc=sum)
Редактировать: я не думаю, что это более справедливо, чем у вас, но вы можете попробовать что-то вроде этого:
# create dummy start/end dataframe
df = pd.DataFrame({'end':pd.Series([50, 100, 120, 150]), 'start':pd.Series([30, 0, 40, 130])})
df = df[['start', 'end']]
df = df.sort('start')
df['roll_end'] = df.end.cummax()
df.roll_end = df.roll_end.shift()
df['new_start'] = df.start
overlap = df.start - df.roll_end < 0
# if start is before rolling max end time then reset start to rolling max end time
df.new_start[overlap] = df.roll_end[overlap]
# if the new start is after end, then completely overlapping
print np.sum([x for x in df.end - df.new_start if x > 0])
Приведенный ниже код воспроизводит ваш вывод с учетом данных примера. Это то, что вы ищете?
>>> df.groupby(['service', 'user_id'])['bytes_uplink', 'bytes_downlink'].sum().reset_index()
service user_id bytes_uplink bytes_downlink
0 facebook 3 188 5533
1 youtube 4 665 5767