如何基于Pandas实现日志文件中最大并发唯一用户数统计?
我明白你的问题了——你现在需要统计的是唯一并发用户数,而不是并发会话数:同一用户不管开多少个重叠会话,在同一个时间点都只能被计数一次。原来的代码统计的是符合条件的会话总数,所以才会把同一用户的多个会话重复计算,我们来调整逻辑解决这个问题。
解决方案:统计唯一并发用户数
快速修正:修改现有代码逻辑
最简单的方法就是把原来统计会话数量的len()替换成统计唯一用户数的nunique(),直接修改你现有代码的核心统计部分就行:
import pandas as pd import sys filename = sys.argv[1] # Read the csv into the pandas dataframe df = pd.read_csv(filename) # Convert start time and end time columns to datetime format df["START_TIME"] = pd.to_datetime(df["START_TIME"]) df["END_TIME"] = pd.to_datetime(df["END_TIME"]) active_users = {} # 统计满足时间条件的唯一用户数,而非会话数 for i in df.index: current_time = df.loc[i, "START_TIME"] # 筛选出当前时间点仍活跃的会话 active_sessions = df[(df["START_TIME"] <= current_time) & (df["END_TIME"] > current_time)] # 统计唯一用户数量(关键修改:用nunique()替代len()) active_users[i] = active_sessions["USER_NAME"].nunique() # 将结果合并到原数据框,添加index=False避免生成多余索引列 total = df.join(pd.DataFrame({'CONCURRENT_EVENTS': pd.Series(active_users)})) total.to_csv("Output - " + filename, index=False)
为什么这样改?
原来的len(active_sessions)统计的是当前活跃的会话条数,而active_sessions["USER_NAME"].nunique()会自动对用户去重,返回当前时间点真正活跃的不同用户数量。用你的示例数据测试的话:
- 第3行(index=2)的
CONCURRENT_EVENTS会从3变成2(jkk + ady,排除了ady的重复会话) - 第5行(index=4)的
CONCURRENT_EVENTS会从3变成2(jkk的会话还在,ady的新会话开始,去重后是2个用户)
完全符合你的期望输出。
性能优化:针对大数据集的高效写法
如果你的日志文件特别大(比如十万行以上),上面的循环方法效率会很低——因为每次循环都要遍历全表筛选数据。这种情况下可以用事件点法优化,把复杂度从O(N²)降到O(N log N):
import pandas as pd import sys filename = sys.argv[1] df = pd.read_csv(filename) df["START_TIME"] = pd.to_datetime(df["START_TIME"]) df["END_TIME"] = pd.to_datetime(df["END_TIME"]) # 把每个会话拆成「用户上线」和「用户下线」两个事件 events = [] for _, row in df.iterrows(): # 上线事件:时间、用户名、操作类型(+1表示上线) events.append((row["START_TIME"], row["USER_NAME"], 1)) # 下线事件:时间、用户名、操作类型(-1表示下线) events.append((row["END_TIME"], row["USER_NAME"], -1)) # 按时间排序事件,注意:如果时间相同,先处理下线事件(避免同一时间点重复计数) events.sort(key=lambda x: (x[0], x[2])) # 遍历事件,维护活跃用户集合,记录每个时间点的用户数 active_users = set() event_log = [] current_count = 0 for time, user, typ in events: if typ == 1: if user not in active_users: active_users.add(user) current_count += 1 else: if user in active_users: active_users.remove(user) current_count -= 1 event_log.append((time, current_count)) # 把事件日志转成DataFrame,方便后续匹配原数据的时间点 event_df = pd.DataFrame(event_log, columns=["TIME", "CONCURRENT_USERS"]) event_df = event_df.sort_values("TIME").reset_index(drop=True) # 给原数据的每个START_TIME匹配对应的活跃用户数 df["CONCURRENT_EVENTS"] = df["START_TIME"].apply( lambda x: event_df[event_df["TIME"] <= x]["CONCURRENT_USERS"].iloc[-1] ) # 保存结果 df.to_csv("Output - " + filename, index=False)
优化思路解释
这个方法的核心是把所有会话的开始和结束都变成事件点,然后按时间顺序遍历事件,维护一个实时的活跃用户集合:
- 用户上线时,如果不在集合里就添加,计数+1
- 用户下线时,如果在集合里就移除,计数-1
最后再把原数据的每个时间点对应到最近的活跃用户数,这样比循环遍历全表高效得多。
最终验证结果
不管用哪种方法,测试你的示例输入后,都会得到你期望的输出:
| SESSION_ID | START_TIME | END_TIME | USER_NAME | CONCURRENT_EVENTS |
|---|---|---|---|---|
| 45030 | 2020-03-29 14:37:00 | 2020-03-29 19:01:00 | jkk | 1 |
| 45033 | 2020-03-29 14:46:00 | 2020-03-29 16:23:00 | ady | 2 |
| 45035 | 2020-03-29 14:54:00 | 2020-03-29 18:27:00 | ady | 2 |
| 45036 | 2020-03-29 15:51:00 | 2020-03-29 17:34:00 | drm | 3 |
| 45040 | 2020-03-29 17:38:00 | 2020-03-29 22:07:00 | ady | 2 |
| 45042 | 2020-03-29 18:58:00 | 2020-03-29 20:25:00 | djx | 3 |
内容的提问来源于stack exchange,提问作者snowdog
相关产品推荐
相关产品推荐

