1. 概述
在Java中分析数据时,计算百分位数是理解数值数据集统计分布和特性的基础任务。
在这个教程中,我们将逐步讲解如何在Java中计算百分位数,提供代码示例和解释。
2. 百分位数的理解
在深入讨论实现细节之前,先来了解一下什么是百分位数以及它们在数据分析中的常见应用。
百分位数是统计学中的一种度量,表示给定百分比的数据观察值位于或低于该值。 例如,50th百分位数(也称为中位数)代表50%的数据点落在该值之下。
值得注意的是,百分位数的表达单位与输入数据集相同,而不是以百分比形式。例如,如果数据集指的是月工资,对应的百分位数将以美元、欧元或其他货币单位表示。
接下来,我们看几个具体的例子:
Input: A dataset with numbers 1-100 unsorted
-> sorted dataset: [1, 2, ... 49, (50), 51, 52, ..100]
-> The 50th percentile: 50
Input: [-1, 200, 30, 42, -5, 7, 8, 92]
-> sorted dataset: [-2, -1, 7, (8), 30, 42, 92, 200]
-> The 50th percentile: 8
百分位数常用于理解数据分布、识别异常值以及比较不同数据集。在处理大型数据集或简洁概括数据集特性时尤其有用。
现在,让我们看看如何在Java中计算百分位数。
3. 从Collection中计算百分位数
了解了百分位数后,我们来总结一个步骤来实现百分位数计算:
- 对给定的数据集按升序排序
- 计算所需百分位数的排名,即 percentile / 100 * dataset.size
- 取排名的上界值,因为排名可能是一个小数
- 最终结果是在排序后的数据集中索引为 ceiling(rank) - 1 的元素
接下来,我们创建一个泛型方法来实现上述逻辑:
static <T extends Comparable<T>> T getPercentile(Collection<T> input, double percentile) {
if (input == null || input.isEmpty()) {
throw new IllegalArgumentException("The input dataset cannot be null or empty.");
}
if (percentile < 0 || percentile > 100) {
throw new IllegalArgumentException("Percentile must be between 0 and 100 inclusive.");
}
List<T> sortedList = input.stream()
.sorted()
.collect(Collectors.toList());
int rank = percentile == 0 ? 1 : (int) Math.ceil(percentile / 100.0 * input.size());
return sortedList.get(rank - 1);
}
如您所见,上述实现相当直接。但值得一提的是:
- 需要验证
percentile
参数(0 <= percentile <= 100) - 我们使用Stream API对输入数据集进行排序,并将排序结果收集到新的列表中,以避免修改原始数据集
现在,让我们测试getPercentile()
方法。
4. 测试getPercentile()
方法
首先,方法应抛出IllegalArgumentException
,如果百分位数超出有效范围:
assertThrows(IllegalArgumentException.class, () -> getPercentile(List.of(1, 2, 3), -1));
assertThrows(IllegalArgumentException.class, () -> getPercentile(List.of(1, 2, 3), 101));
我们使用了**assertThrows()
方法来验证是否抛出了预期的异常**。
接下来,我们用一个1到100的列表作为输入,验证方法能否产生预期结果:
List<Integer> list100 = IntStream.rangeClosed(1, 100)
.boxed()
.collect(Collectors.toList());
Collections.shuffle(list100);
assertEquals(1, getPercentile(list100, 0));
assertEquals(10, getPercentile(list100, 10));
assertEquals(25, getPercentile(list100, 25));
assertEquals(50, getPercentile(list100, 50));
assertEquals(76, getPercentile(list100, 75.3));
assertEquals(100, getPercentile(list100, 100));
在这段代码中,我们通过IntStream准备了输入列表,并使用shuffle()
方法随机排序这100个数字。
此外,我们也测试了另一种数据集输入:
List<Integer> list8 = IntStream.of(-1, 200, 30, 42, -5, 7, 8, 92)
.boxed()
.collect(Collectors.toList());
assertEquals(-5, getPercentile(list8, 0));
assertEquals(-5, getPercentile(list8, 10));
assertEquals(-1, getPercentile(list8, 25));
assertEquals(8, getPercentile(list8, 50));
assertEquals(92, getPercentile(list8, 75.3));
assertEquals(200, getPercentile(list8, 100));
5. 从数组中计算百分位数
有时,给定的数据集输入是一个数组,而非Collection
。在这种情况下,我们可以**首先将输入数组转换为List
**,然后使用getPercentile()
方法计算所需的百分位数。
接下来,我们演示如何通过一个long
数组作为输入实现这一点:
long[] theArray = new long[] { -1, 200, 30, 42, -5, 7, 8, 92 };
//convert the long[] array to a List<Long>
List<Long> list8 = Arrays.stream(theArray)
.boxed()
.toList();
assertEquals(-5, getPercentile(list8, 0));
assertEquals(-5, getPercentile(list8, 10));
assertEquals(-1, getPercentile(list8, 25));
assertEquals(8, getPercentile(list8, 50));
assertEquals(92, getPercentile(list8, 75.3));
assertEquals(200, getPercentile(list8, 100));
代码显示,**由于我们的输入是基本类型的数组(long[]
),我们使用Arrays.stream()
将其转换为List<Long>
**。然后,我们可以将转换后的List
传递给getPercentile()
获取预期结果。
6. 总结
在这个文章中,我们首先讨论了百分位数的基本原理,然后探讨了如何在Java中为数据集计算百分位数。
如往常一样,所有示例的完整源代码可在GitHub上找到:https://github.com/eugenp/tutorials/tree/master/core-java-modules/core-java-lang-math-4。