使用LINQ计算百分位数

5

大家好,

经过查看StackOverflow和更广泛的互联网,我仍然无法有效地使用LINQ计算百分位数。

其中percentile是统计学中使用的一种指标,表示在一组观察值中,有多少比例的观察值低于该值。下面的示例尝试将值列表转换为数组,其中每个(唯一)值都用其关联的百分位数表示。列表的min()和max()必然是返回的数组百分位数的0%和100%。

使用LINQPad,以下代码生成所需的输出VP[]:

enter image description here

这可以被解释为: - 在0%时,最小值为1 - 在100%时,最大值为3 - 在最小值和最大值之间的50%处,该值为2
void Main()
{
    var list = new List<double> {1,2,3};
    double denominator = list.Count - 1;   
    var answer = list.Select(x => new VP
        {
            Value = x,
            Percentile = list.Count(y => x > y) / denominator
        })
        //.GroupBy(grp => grp.Value) --> commented out until attempted duplicate solution 
        .ToArray();
    answer.Dump();
}

public struct VP
{
    public double Value;
    public double Percentile;
}

然而,当“列表”包含重复条目时(例如1,2,**2,**3),这将返回一个不正确的VP[]:

enter image description here

我的尝试按照列表中的唯一值进行分组(包括“.GroupBy(grp => grp.Value)”)未能产生期望的结果(Value =2,& Percentile = 0.666):

enter image description here

欢迎提出所有建议,包括是否考虑到了使用“list.Count(y => x > y)”进行重复迭代的效率问题。

感谢您一如既往的支持! Shannon


我不确定你究竟要计算什么,也许我的数学有些生疏... 你能否告诉我“比例/百分位”的确切含义以及它与分母的关系。谢谢。 - TheCatWhisperer
为什么要使用list.Count()来计算分母?你想要实现什么并不是很清楚。 - Rufus L
@TheCatWhisperer 对于原始列表中的每个元素(即1、2、3),我需要该值和百分位数(例如,value = 2 表示分布的50%点)。我正在组装一种类似于概率密度函数的东西。其中 VP[] 可以快速引用,以确定50%的元素小于或等于“2”。 - shansen
百分位数是如何分布的?通常是正态分布、学生分布等? - TheCatWhisperer
如果您重新措辞问题并包含列表代表的定义以及价值和比例是什么,那将非常有帮助。 - Rufus L
显示剩余8条评论
3个回答

1

我不确定我理解这个问题的要求。当我运行接受答案的代码时,我得到了这个结果:

original result

但是,如果我将输入更改为以下内容:
var dataSet = new List<double> { 1, 1, 1, 1, 2, 3, 3, 3, 2 };

...然后我得到了这个结果:

updated result

使用“列表的min()和max()必须是返回的数组百分位数的0%和100%”这一行,我觉得OP要求的值应该从0到1,但更新后的结果超过了1。
对我来说,第一个值应该是0%似乎也是错误的,因为我不确定在数据上下文中它的意义是什么。
阅读链接的维基百科页面后,似乎OP实际上正在尝试计算百分位值的反向计算。事实上,文章说0的百分位数是未定义的。这很有道理,因为0的百分位数将是空值集-空值集的最大值是多少呢?
OP似乎正在从值中计算百分位数。因此,在这个意义上,并知道0是未定义的,似乎最合适的计算值是等于或低于集合中每个不同值的值的百分比。
现在,如果我使用Microsoft的Reactive Framework团队的交互扩展(NuGet“Ix-Main”),那么我可以运行以下代码:
var dataSet = new List<double> { 1, 1, 1, 1, 2, 3, 3, 3, 2 };

var result =
    dataSet
        .GroupBy(x => x)
        .Scan(
            new VP()
            {
                Value = double.MinValue, Proportion = 0.0
            },
            (a, x) =>
                new VP()
                {
                    Value = x.Key,
                    Proportion = a.Proportion + (double)x.Count() / dataSet.Count
                });

我得到了这个结果:

result

这告诉我大约44%的数值是1;大约67%的数值是1或2;而所有数值都是1、2或3。这对于要求来说似乎是最合理的计算方式。

0
void Main()
{
    var list = new List<double> {1,2,3};
    double denominator = list.Count - 1;   
    var answer = list.OrderBy(x => x).Select(x => new VP
        {
            Value = x,
            Proportion = list.IndexOf(x) / denominator
        })
        .ToArray();
    answer.Dump();
}

public struct VP
{
    public double Value;
    public double Proportion;
}

谢谢回复。你的方法更有效是有道理的。然而,当存在重复条目(例如1,2,2,3)时,在返回的VP[]中,问题仍然存在,其中值“2”被复制,每个百分位为33%,而不是具有唯一的“2”,其百分位为67%。 - shansen
我不明白在列表中只有3个项目时,为什么会出现两次2的可能性。也许这是linqpad中的一个错误? - TheCatWhisperer

0

这是我的做法。我改了一些变量名以使上下文更清晰。

var dataSet = new List<double> { 1, 2, 3, 2 };
double denominator = dataSet.Count - 1;
var uniqueValues = dataSet.Distinct();
var vp = dataSet.Select(value => new VP
{
    Value = value,
    Proportion = dataSet.Count(datum => value > datum) / denominator
});

var answer = uniqueValues.Select(u => new VP{
    Value = u,
    Proportion = vp.Where(v => v.Value == u).Select(x => x.Proportion).Sum()
});

Ed,谢谢你,这正是我想要的。对于我在定义问题时缺乏清晰度,我表示歉意。我会改进的。 - shansen
2
它看起来非常低效。Count(datum => value > datum) 会一遍又一遍地对整个集合进行迭代。 - MarcinJuraszek

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接