
本文介绍如何使用 pandas 高效统计每个用户在无序配对(如学生-评审员)中涉及的唯一联系人数量,自动将 (a,b) 和 (b,a) 视为同一关系,并汇总每人对应的去重联系人数。
在实际数据分析中,常遇到“对称关系”场景:例如师生互评、社交关注、协作配对等,其中 (student_login, reviewer_login) 与 (reviewer_login, student_login) 本质是同一组双向交互,不应重复计数。目标是为每个登录名(无论其角色)统计其唯一关联对象的数量——即该用户作为任一角色出现时,共与多少个不同用户组成过无序配对。
解决思路分为三步:
- 统一视角:将原始表中每行视为一条有向边(student → reviewer),再显式添加反向边(reviewer → student),使所有关系变为双向可访问;
- 标准化键名:将两列重命名为统一语义字段(如 'login' 和 'unique_contacts'),便于后续聚合;
- 分组去重统计:按用户登录名分组,对其关联对象列调用 .nunique(),确保每个联系人仅计一次。
以下是完整实现代码:
import pandas as pd
# 构造示例数据
data = {
'student_login': ['login_1', 'login_2', 'login_1', 'login_2'],
'reviewer_login': ['login_2', 'login_1', 'login_3', 'login_3']
}
df = pd.DataFrame(data)
# 核心处理:合并正向 + 反向记录,并重命名列以统一语义
cols = ['login', 'unique_contacts']
forward = df.set_axis(cols, axis=1) # student→reviewer 映射为 login→unique_contacts
backward = df.set_axis(cols[::-1], axis=1) # reviewer→student 映射为 unique_contacts→login(再经groupby翻转)
combined = pd.concat([forward, backward], ignore_index=True)
result = combined.groupby('login', as_index=False)['unique_contacts'].nunique()
print(result)输出:
login unique_contacts 0 login_1 2 1 login_2 2 2 login_3 2
✅ 关键说明:
- set_axis(cols[::-1], axis=1) 将原列顺序反转后重命名,使 reviewer_login 成为新 'login' 列,student_login 成为新 'unique_contacts' 列,从而补全反向关系;
- pd.concat(..., ignore_index=True) 确保索引连续,避免后续分组异常;
- .nunique() 在 groupby 后精确统计每个用户对应的不同联系人数量,天然支持重复对(如 login_1 → login_2 出现多次)的去重。
⚠️ 注意事项:
- 若原始数据含大小写不一致(如示例中 "Login_3"),建议先统一转小写:df = df.applymap(lambda x: x.lower() if isinstance(x, str) else x)(Pandas 2.1+ 推荐用 map() 或 select_dtypes 配合 str.lower());
- 此方法时间复杂度为 O(n),适用于百万级以内数据;超大规模可考虑用 numpy 向量化构造无序元组(如 frozenset([a,b]))后再聚合;
- 结果中每个用户均独立计数,即使某用户从未出现在 student_login 列(仅作 reviewer),也会被正确纳入统计——这正是双向建模的优势。
通过该方案,你无需手动遍历或使用低效循环,即可在一行核心逻辑内完成对称关系的去重聚合,兼顾可读性与执行效率。










