基于列分布与条件的Python DataFrame随机数据采样方法
多列分布约束+高频城市优先的随机采样方案
核心逻辑:常规pandas单维度分层采样无法同时满足多列分布要求,需要先计算两列的采样配额,再结合City频次权重做约束采样,适配百万级数据规模的性能要求。
实现步骤
- 步骤1:统计全量数据中Id、Fruit列的分布占比,换算为指定采样量下各分组的目标采样条数,校验总条数匹配
- 步骤2:统计每个City的全量出现频次,作为该行的采样权重,频次越高权重越大
- 步骤3:采用约束采样逻辑,同时对齐Id、Fruit的采样配额,优先抽取权重更高的行
可运行代码实现
1. 基础数据准备
import pandas as pd import numpy as np # 示例输入数据,可替换为百万级全量数据 input_data = pd.DataFrame({ 'Id': ['A','A','A','A','A','A','B','B','B','C','C','C'], 'Fruit': ['Apple','Mango','Orange','Apple','Apple','Mango','Apple','Mango','Apple','Apple','Apple','Orange'], 'City':['California','California','Chicago','Michigan','New York','Ohio','Michigan','Michigan','Ohio','Florida','New York','Washington'] }) # 定义采样总条数 sample_n = 4
2. 计算各列采样配额
# 计算Id列各分组目标采样数 id_dist = input_data['Id'].value_counts(normalize=True).sort_index() id_target = (id_dist * sample_n).round().astype(int) # 校验总和匹配 if id_target.sum() != sample_n: diff = sample_n - id_target.sum() id_target[id_target.idxmax()] += diff # 计算Fruit列各分组目标采样数 fruit_dist = input_data['Fruit'].value_counts(normalize=True).sort_index() fruit_target = (fruit_dist * sample_n).round().astype(int) if fruit_target.sum() != sample_n: diff = sample_n - fruit_target.sum() fruit_target[fruit_target.idxmax()] += diff # 计算City权重:出现频次越高权重越大 city_freq = input_data['City'].value_counts().to_dict() input_data['weight'] = input_data['City'].map(city_freq)
3. 确定性优先采样(优先取最高频City的行)
适合需要固定优先级的场景,性能最高:
# 按权重倒序排序,优先取高频City行 input_data_sorted = input_data.sort_values('weight', ascending=False, ignore_index=True) id_count = {k:0 for k in id_target.index} fruit_count = {k:0 for k in fruit_target.index} result = [] for _, row in input_data_sorted.iterrows(): curr_id, curr_fruit = row['Id'], row['Fruit'] if id_count[curr_id] < id_target[curr_id] and fruit_count[curr_fruit] < fruit_target[curr_fruit]: result.append(row) id_count[curr_id] += 1 fruit_count[curr_fruit] += 1 if len(result) == sample_n: break output_data = pd.DataFrame(result).drop(columns='weight').reset_index(drop=True)
4. 随机加权采样(兼顾随机性+高频优先)
符合随机采样要求,高频City被抽中的概率更高:
id_count = {k:0 for k in id_target.index} fruit_count = {k:0 for k in fruit_target.index} remaining_data = input_data.copy() result = [] for _ in range(sample_n): # 过滤剩余满足配额的候选行 mask = (remaining_data['Id'].map(id_count) < id_target[remaining_data['Id']]) & \ (remaining_data['Fruit'].map(fruit_count) < fruit_target[remaining_data['Fruit']]) candidates = remaining_data[mask] # 按City权重加权随机采样1行 sampled_row = candidates.sample(n=1, weights=candidates['weight']).iloc[0] result.append(sampled_row) # 更新计数器和剩余数据集 id_count[sampled_row['Id']] += 1 fruit_count[sampled_row['Fruit']] += 1 remaining_data = remaining_data.drop(sampled_row.name) output_data = pd.DataFrame(result).drop(columns='weight').reset_index(drop=True)
性能说明
两种实现的时间复杂度均为O(nlogn),百万行数据可在秒级完成运算,无额外性能依赖。
内容的提问来源于stack exchange,提问作者Dr.Chuck
相关产品推荐
相关产品推荐

