如何在Python中按指定价值比例从带值项目列表抽取24个唯一样本?
指定比例匹配的抽样解决方案
你需要从40799个带数值的项目中抽取24个唯一项目,使得每个选中项目的数值占选中总和的比例尽可能匹配给定的24个目标比例(总和为1)。以下是两种可在Python中实现的实用方法:
方法一:贪心迭代调整法
这是一种启发式优化方法,通过迭代替换样本中的项目来逐步缩小实际比例与目标比例的误差。
实现步骤
- 定义误差函数:用实际比例与目标比例的平方误差和来衡量匹配程度,误差越小匹配度越高。
- 生成初始样本:随机挑选24个唯一项目作为初始集合。
- 迭代优化:遍历未选中的项目,尝试替换当前样本中的每个项目,保留能让误差减小的替换操作,重复直到误差不再明显下降或达到迭代上限。
Python代码示例
import pandas as pd import numpy as np # 替换为你的实际数据 np.random.seed(42) data = pd.DataFrame({ 'item_id': [f'id{i}' for i in range(1, 40800)], 'value': np.random.randint(50000, 100000, size=40799) }) # 替换为你的目标比例列表(总和需为1) p_list = np.random.dirichlet(np.ones(24)) def calculate_error(selected_values, target_probs): sum_v = selected_values.sum() if sum_v == 0: return float('inf') actual_ratios = selected_values / sum_v return np.sum((actual_ratios - target_probs) ** 2) # 初始随机抽样 initial_idx = np.random.choice(data.index, size=24, replace=False) current_selected = data.loc[initial_idx].copy() current_error = calculate_error(current_selected['value'], p_list) best_error = current_error best_selected = current_selected.copy() max_iter = 1000 tolerance = 1e-8 iter_count = 0 while iter_count < max_iter: improved = False # 逐个尝试替换样本中的项目 for i in range(24): unselected_idx = data.index.difference(current_selected.index) # 随机采样候选项目(减少计算量) candidate_idx = np.random.choice(unselected_idx, size=100, replace=False) for idx in candidate_idx: temp_selected = current_selected.copy() temp_selected.iloc[i] = data.loc[idx] temp_error = calculate_error(temp_selected['value'], p_list) if temp_error < current_error - tolerance: current_selected = temp_selected.copy() current_error = temp_error improved = True if current_error < best_error: best_error = current_error best_selected = current_selected.copy() break if improved: break if not improved: break iter_count += 1 # 输出结果 print(f"最优匹配误差: {best_error:.6f}") print("选中项目详情:") print(best_selected[['item_id', 'value']]) # 验证比例匹配情况 sum_selected = best_selected['value'].sum() actual_ratios = best_selected['value'] / sum_selected ratio_compare = pd.DataFrame({ '目标比例': p_list, '实际比例': actual_ratios }) print("\n比例对比:") print(ratio_compare.round(4))
方法二:目标权重匹配抽样
通过预估选中项目的总和,为每个目标比例匹配最接近的数值项目,再迭代调整总和重新匹配,快速缩小误差。
实现步骤
- 预估初始总和:通过多次随机抽样,计算24个项目总和的平均值作为初始预估总和。
- 匹配目标数值:根据每个目标比例和预估总和,计算对应目标数值,在未选中项目中挑选数值最接近的项目。
- 迭代调整:用选中项目的实际总和重新计算目标数值,再次匹配项目,重复几次优化结果。
Python代码示例
import pandas as pd import numpy as np # 替换为你的实际数据 np.random.seed(42) data = pd.DataFrame({ 'item_id': [f'id{i}' for i in range(1, 40800)], 'value': np.random.randint(50000, 100000, size=40799) }) # 替换为你的目标比例列表(总和需为1) p_list = np.random.dirichlet(np.ones(24)) # 预估初始选中总和 sample_sums = [] for _ in range(1000): sample_sum = data.sample(24)['value'].sum() sample_sums.append(sample_sum) initial_S = np.mean(sample_sums) # 初始匹配项目 selected = pd.DataFrame(columns=data.columns) for p in p_list: target_v = p * initial_S candidates = data[~data.index.isin(selected.index)] candidates['diff'] = abs(candidates['value'] - target_v) best_item = candidates.sort_values('diff').iloc[0] selected = pd.concat([selected, best_item.to_frame().T]) # 迭代调整总和与匹配 for _ in range(5): current_S = selected['value'].sum() new_selected = pd.DataFrame(columns=data.columns) for p in p_list: target_v = p * current_S candidates = data[~data.index.isin(new_selected.index)] candidates['diff'] = abs(candidates['value'] - target_v) best_item = candidates.sort_values('diff').iloc[0] new_selected = pd.concat([new_selected, best_item.to_frame().T]) selected = new_selected # 计算误差与输出结果 sum_selected = selected['value'].sum() actual_ratios = selected['value'] / sum_selected error = np.sum((actual_ratios - p_list) ** 2) print(f"匹配误差: {error:.6f}") print("选中项目详情:") print(selected[['item_id', 'value']]) print("\n比例对比:") ratio_compare = pd.DataFrame({ '目标比例': p_list, '实际比例': actual_ratios }) print(ratio_compare.round(4))
注意事项
- 两种方法均为启发式算法,无法保证全局最优,但在实际场景中能满足比例匹配需求。
- 若数据量极大,可减少方法一中的候选项目采样数量,或减少方法二中的迭代次数以提升效率。
- 可根据需求替换误差函数(比如用绝对误差代替平方误差)。
- 可多次尝试不同初始样本,选择误差最小的结果。
内容的提问来源于stack exchange,提问作者YZW
相关产品推荐
相关产品推荐

