分组内生成无重复用户对及DataFrame存储的技术实现问题
分组内生成不重复用户配对并存储到DataFrame的问题
需求说明
有包含group_id和user_id的数据集,存在多个用户组(组内人数可为奇数)。需要在每个组内生成用户对,要求每一轮配对中用户不重复,直至组内所有用户完成相互配对;现有代码存在跨组配对问题,且无法将配对结果正确存储到pandas DataFrame,运行时出现报错。
报错信息
AssertionError Traceback (most recent call last) C:\ProgramData\Anaconda3\lib\site-packages\pandas\core\internals\construction.py in _finalize_columns_and_data(content, columns, dtype) 981 try: ---> 982 columns = _validate_or_indexify_columns(contents, columns) 983 except AssertionError as err: C:\ProgramData\Anaconda3\lib\site-packages\pandas\core\internals\construction.py in _validate_or_indexify_columns(content, columns) 1029 # caller's responsibility to check for this... ---> 1030 raise AssertionError( 1031 f"{len(columns)} columns passed, passed data had " AssertionError: 1 columns passed, passed data had 6 columns The above exception was the direct cause of the following exception: ValueError Traceback (most recent call last) ~\AppData\Local\Temp\ipykernel_14172\369883545.py in <module> 24 return stages 25 ---> 26 out = (df.groupby(['group_id'], as_index=False)['user_id'].apply(combine).explode('user_id')) 27 print(out.head()) C:\ProgramData\Anaconda3\lib\site-packages\pandas\core\groupby\groupby.py in apply(self, func, *args, **kwargs) 1421 with option_context("mode.chained_assignment", None): 1422 try: ---> 1423 result = self._python_apply_general(f, self._selected_obj) 1424 except TypeError: 1425 # gh-20949 C:\ProgramData\Anaconda3\lib\site-packages\pandas\core\groupby\groupby.py in _python_apply_general(self, f, data, not_indexed_same) 1467 not_indexed_same = mutated or self.mutated 1468 ---> 1469 return self._wrap_applied_output( 1470 data, values, not_indexed_same=not_indexed_same 1471 ) C:\ProgramData\Anaconda3\lib\site-packages\pandas\core\groupby\generic.py in _wrap_applied_output(self, data, values, not_indexed_same) 1025 return self.obj._constructor_sliced(values, index=key_index) 1026 else: ---> 1027 result = self.obj._constructor(values, columns=[self._selection]) 1028 self._insert_inaxis_grouper_inplace(result) 1029 return result C:\ProgramData\Anaconda3\lib\site-packages\pandas\core\frame.py in __init__(self, data, index, columns, dtype, copy) 719 # ndarray], Index, Series], Sequence[Any]]" 720 columns = ensure_index(columns) # type: ignore[arg-type] ---> 721 arrays, columns, index = nested_data_to_arrays( 722 # error: Argument 3 to "nested_data_to_arrays" has incompatible 723 # type "Optional[Collection[Any]]"; expected "Optional[Index]" C:\ProgramData\Anaconda3\lib\site-packages\pandas\core\internals\construction.py in nested_data_to_arrays(data, columns, index, dtype) 517 columns = ensure_index(data[0]._fields) 518 ---> 519 arrays, columns = to_arrays(data, columns, dtype=dtype) 520 columns = ensure_index(columns) 521 C:\ProgramData\Anaconda3\lib\site-packages\pandas\core\internals\construction.py in to_arrays(data, columns, dtype) 881 arr = _list_to_arrays(data) 882 ---> 883 content, columns = _finalize_columns_and_data(arr, columns, dtype) 884 return content, columns 885 C:\ProgramData\Anaconda3\lib\site-packages\pandas\core\internals\construction.py in _finalize_columns_and_data(content, columns, dtype) 983 except AssertionError as err: 984 # GH#26429 do not raise user-facing AssertionError ---> 985 raise ValueError(err) from err 986 987 if len(contents) and contents[0].dtype == np.object_: ValueError: 1 columns passed, passed data had 6 columns
示例数据集
| group_id | user_id |
|---|---|
| 1 | a1 |
| 1 | b1 |
| 1 | c1 |
| 1 | d1 |
| 2 | x1 |
| 2 | y1 |
| 2 | z1 |
现有初始化代码
import pandas as pd import numpy as np df = [[1, 'a1'], [1, 'b1'], [1, 'c1'], [1, 'd1'], [2, 'x1'], [2, 'y1'], [2, 'z1']] df = pd.DataFrame(df, columns=['group_id', 'user_id']) df.head()
存在问题的解决方案(跨组配对)
以下代码会忽略分组约束,直接对所有用户生成配对,不符合需求:
from itertools import combinations # Even Number of users Required users = df['user_id'].to_list() n = int(len(users) / 2) stages = [] for i in range(len(users) - 1): t = users[:1] + users[-i:] + users[1:-i] if i else users stages.append(list(zip(t[:n], reversed(t[n:])))) print(stages)
期望输出
| group_id | combinations |
|---|---|
| 1 | a1-d1 |
| 1 | b1-c1 |
| 1 | a1-c1 |
| 1 | d1-b1 |
| 1 | a1-b1 |
| 1 | d1-c1 |
| 2 | x1-z1 |
| 2 | y1-y1 |
| 2 | x1-y1 |
| 2 | z1-y1 |
| 2 | x1-x1 |
| 2 | y1-z1 |
正确解决方案
以下代码实现分组内生成不重复配对,并正确存储到DataFrame:
import pandas as pd def generate_group_pairings(users): pairings = [] original_len = len(users) n = original_len # 处理人数为1的情况 if n == 1: pairings.append(f"{users[0]}-{users[0]}") return pairings # 奇数人数时添加虚拟用户(用最后一个用户代替,实现轮空逻辑) if n % 2 != 0: users = users.copy() users.append(users[-1]) n = len(users) # 循环赛配对算法:固定第一个用户,旋转其他用户生成每轮配对 fixed_user = users[0] rotating_users = users[1:] for _ in range(n - 1): current_round = [] # 固定用户与旋转列表末尾用户配对 current_round.append(f"{fixed_user}-{rotating_users[-1]}") # 中间用户两两配对 half = len(rotating_users) // 2 for j in range(half): current_round.append(f"{rotating_users[j]}-{rotating_users[-(j+2)]}") pairings.extend(current_round) # 旋转用户列表 rotating_users = [rotating_users[-1]] + rotating_users[:-1] # 剔除奇数人数时的虚拟配对(用户自配对的条目) if original_len % 2 != 0: pairings = [p for p in pairings if not (p.startswith(f"{users[-1]}-") and p.endswith(f"-{users[-1]}"))] return pairings # 分组处理并生成结果 result = df.groupby('group_id')['user_id'].apply(lambda x: generate_group_pairings(list(x))).explode().reset_index() result.columns = ['group_id', 'combinations'] print(result)
代码说明
generate_group_pairings函数:针对单个组的用户列表生成符合要求的配对:- 处理组内人数为1的情况,直接生成自配对
- 奇数人数时临时添加虚拟用户,确保每轮配对无重复,后续剔除虚拟配对条目
- 使用循环赛算法,固定一个用户,旋转其他用户生成每轮不重复的配对,保证每个用户与组内其他用户都完成配对
- 分组处理:通过
groupby对每个组应用配对生成函数,再用explode展开配对结果,最后重置索引得到目标格式的DataFrame。
内容的提问来源于stack exchange,提问作者n3a5p7s9t1e3r
相关产品推荐
相关产品推荐

