如何生成3D百分位数?能否从数据云质心构建递增范围?
数据生成代码
set.seed(123) n <- 10000 age <- rnorm(n, 40, 10) weight <- rnorm(n, 70, 15) height <- rnorm(n, 170, 10)
需求
生成3D百分位数,例如:X%的样本处于(Age1-Age2, Weight1-Weight2, Height1-Height2)区间内,(X+Y)%的样本处于(Age1-Age3, Weight1-Weight3, Height1-Height3)区间内等,且范围需单调递增。
现有尝试
通过单变量分位数划分箱体,代码如下:
age_cuts <- quantile(age, probs = seq(0, 1, 0.1)) weight_cuts <- quantile(weight, probs = seq(0, 1, 0.1)) height_cuts <- quantile(height, probs = seq(0, 1, 0.1)) results <- data.frame() for(i in 2:length(age_cuts)) { age_range <- c(age_cuts[1], age_cuts[i]) weight_range <- c(weight_cuts[1], weight_cuts[i]) height_range <- c(height_cuts[1], height_cuts[i]) in_box <- age >= age_range[1] & age <= age_range[2] & weight >= weight_range[1] & weight <= weight_range[2] & height >= height_range[1] & height <= height_range[2] percent <- sum(in_box) / n * 100 results <- rbind(results, data.frame( decile = (i-1) * 10, age_min = age_range[1], age_max = age_range[2], weight_min = weight_range[1], weight_max = weight_range[2], height_min = height_range[1], height_max = height_range[2], percent_contained = percent, n_points = sum(in_box) )) }
运行结果
decile age_min age_max weight_min weight_max height_min height_max percent_contained n_points 10 1.546798 27.20561 17.669 50.64000 134.5412 157.0580 0.06 6 20 1.546798 31.60029 17.669 57.34884 134.5412 161.4875 0.75 75 30 1.546798 34.81272 17.669 62.03452 134.5412 164.6067 2.72 272 40 1.546798 37.51687 17.669 65.96050 134.5412 167.2295 6.56 656 50 1.546798 39.88911 17.669 69.73178 134.5412 169.8810 12.65 1265 60 1.546798 42.47684 17.669 73.59989 134.5412 172.4757 22.12 2212 70 1.546798 45.18560 17.669 77.53439 134.5412 175.3048 34.58 3458 80 1.546798 48.42140 17.669 82.53608 134.5412 178.4548 51.38 5138 90 1.546798 52.62804 17.669 89.25561 134.5412 182.8159 72.73 7273 100 1.546798 78.47768 17.669 126.38053 134.5412 213.2282 100.00 10000

疑问
能否从数据云的质心起始构建百分位数范围?这样是否能保证单调递增的范围覆盖的样本占比持续增大?
完全可以从数据质心出发构建这类3D百分位数范围,而且只要方法得当,绝对能保证覆盖的样本占比单调递增。
核心思路
从质心(也就是三个变量的均值点:(mean(age), mean(weight), mean(height)))向外扩展,本质是按样本到质心的距离排序,然后按分位数截取距离阈值,对应生成包含质心的嵌套区间(或超球体/超椭球)。因为每次扩展都是把更远的样本纳入,所以覆盖比例必然是逐步增加的,天然满足单调递增的要求。
具体实现步骤
- 计算质心:先算出三个变量的均值,作为中心原点。
- 标准化变量(可选但推荐):因为年龄、体重、身高的量纲不同,直接计算欧氏距离会被方差大的变量主导,所以先标准化每个变量:
age_std <- scale(age) weight_std <- scale(weight) height_std <- scale(height) - 计算每个样本到质心的距离:用标准化后的数据计算欧氏距离:
distances <- sqrt(age_std^2 + weight_std^2 + height_std^2) - 按距离分位数划分阈值:生成距离的分位数,作为扩展的边界:
dist_cuts <- quantile(distances, probs = seq(0, 1, 0.1)) - 映射回原始变量的区间:对每个距离阈值,找到所有距离小于等于该阈值的样本,然后提取这些样本在原始变量上的最小和最大值,得到对应的3D区间:
results_centroid <- data.frame() centroid_age <- mean(age) centroid_weight <- mean(weight) centroid_height <- mean(height) for(i in 1:length(dist_cuts)) { if(i == 1) next # 跳过0分位数,对应质心单点 current_dist <- dist_cuts[i] in_range <- distances <= current_dist # 提取原始变量的区间 age_min <- min(age[in_range]) age_max <- max(age[in_range]) weight_min <- min(weight[in_range]) weight_max <- max(weight[in_range]) height_min <- min(height[in_range]) height_max <- max(height[in_range]) percent <- sum(in_range)/n *100 results_centroid <- rbind(results_centroid, data.frame( decile = (i-1)*10, centroid_age = centroid_age, centroid_weight = centroid_weight, centroid_height = centroid_height, age_min = age_min, age_max = age_max, weight_min = weight_min, weight_max = weight_max, height_min = height_min, height_max = height_max, percent_contained = percent, n_points = sum(in_range) )) }
为什么能保证单调递增?
因为每次循环的距离阈值是逐步增大的,dist_cuts是从0到最大距离的分位数,所以in_range的样本集合是嵌套包含的:第k个分位数的样本集合完全包含第k-1个分位数的样本集合。因此覆盖的样本占比必然是严格递增的,不会出现波动。
和现有方法的区别
之前的方法是从每个变量的最小值开始扩展,相当于从数据的“角落”往外扩,样本覆盖比例的增长非线性且可能不符合直觉;而从质心扩展是从数据最密集的区域向外扩散,更符合“百分位数范围”的直观理解——比如10%的样本是离中心最近的10%,90%是包含所有离中心较近的90%样本。
内容的提问来源于stack exchange,提问作者stats_noob

