如何更快从DataFrame生成指定结构的联系人统计字典?
高效将包含嵌套参会者的DataFrame转换为联系人统计字典
问题背景
我有如下结构的DataFrame:
| event | attendees | duration |
|---|---|---|
| meeting | [{"id":1, "email": "email1"}, {"id":2, "email": "email2"}] | 3600 |
| lunch | [{"id":2, "email": "email2"}, {"id":3, "email": "email3"}] | 7200 |
需要转换为以下结构的字典:
{ "email1": { 'num_events_with': 1, 'duration_of_events': 3600, }, "email2": { 'num_events_with': 2, 'duration_of_events': 10800, }, "email3": { 'num_events_with': 1, 'duration_of_events': 7200, }, }
实际场景中DataFrame有数千行,当前实现速度过慢,代码如下:
# need to sort because I use diff() later df.sort_values(by='startTime', ascending=True, inplace=True) # a list of all contacts (emails) that have been in events with user contacts = contacts_in_events contact_info_dict = {} df['attendees_str'] = df['attendees'].astype(str) for contact in contacts: temp_df = df[df['attendees_str'].str.contains(contact)] duration_of_events = temp_df['duration'].sum() num_events_with = len(temp_df.index) contact_info_dict[contact] = { 'duration_of_events': duration_of_events, 'num_events_with': num_events_with }
实际DataFrame的单条记录样例(to_dict('records')输出):
{ 'creator': { 'displayName': None, 'email': 'creator of event', 'id': None, 'self': None }, 'start': { 'date': None, 'dateTime': '2022-09-13T12:30:00-04:00', 'timeZone': 'America/Toronto' }, 'end': { 'date': None, 'dateTime': '2022-09-13T13:00:00-04:00', 'timeZone': 'America/Toronto' }, 'attendees': [ { 'comment': None, 'displayName': None, 'email': 'email1@email.com', 'responseStatus': 'accepted' }, { 'comment': None, 'displayName': None, 'email': 'email2@email.com', 'responseStatus': 'accepted' } ], 'summary': 'One on One Meeting', 'description': '...', 'calendarType': 'work', 'startTime': Timestamp('2022-09-13 16:30:00+0000', tz='UTC'), 'endTime': Timestamp('2022-09-13 17:00:00+0000', tz='UTC'), 'eventDuration': 1800.0, 'dowStart': 1.0, 'endStart': 1.0, 'weekday': True, 'startTOD': 59400, 'endTOD': 61200, 'day': Period('2022-09-13', 'D') }
高效解决方案
当前方法速度慢的核心原因是循环遍历每个联系人并做字符串匹配,数据量大时会产生O(N*M)的时间复杂度(N为联系人数量,M为DataFrame行数)。以下是两种更高效的实现方式:
方法1:利用explode展开参会者列表
通过展开嵌套的参会者列表,结合Pandas分组聚合完成统计,全程用向量化操作替代循环:
# 1. 从参会者列表中提取邮箱,生成新列 df['emails'] = df['attendees'].apply(lambda x: [attendee['email'] for attendee in x]) # 2. 将邮箱列表展开为单行对应单个参会者的格式 exploded_df = df.explode('emails') # 3. 按邮箱分组,统计事件数和总时长 aggregated = exploded_df.groupby('emails').agg( num_events_with=('emails', 'count'), duration_of_events=('eventDuration', 'sum') # 对应实际数据中的时长列名 ).reset_index() # 4. 转换为目标字典格式 contact_info_dict = { row['emails']: { 'num_events_with': row['num_events_with'], 'duration_of_events': row['duration_of_events'] } for _, row in aggregated.iterrows() }
方法2:单次遍历字典累加(内存友好)
如果DataFrame过大,explode会占用过多内存,可采用一次遍历完成统计,时间复杂度仅O(M):
contact_info_dict = {} for _, row in df.iterrows(): current_duration = row['eventDuration'] for attendee in row['attendees']: email = attendee['email'] if email not in contact_info_dict: contact_info_dict[email] = { 'num_events_with': 0, 'duration_of_events': 0 } contact_info_dict[email]['num_events_with'] += 1 contact_info_dict[email]['duration_of_events'] += current_duration
关键优化点
- 跳过字符串匹配:直接从嵌套字典中提取邮箱,精准且避免不必要的字符串操作开销。
- 用向量化操作或单次遍历替代多轮循环,将时间复杂度从O(N*M)降低到O(M)。
- 若需保留排序逻辑,提前对DataFrame排序即可,不影响统计效率。
内容的提问来源于stack exchange,提问作者CofeeBean42
相关产品推荐
相关产品推荐

