按两列分组并根据其中一列计算累积值

Posted

技术标签:

【中文标题】按两列分组并根据其中一列计算累积值【英文标题】:Group by two columns and calculate cumulative value based on one of them 【发布时间】:2021-01-14 00:28:54 【问题描述】:

请考虑这个列表:

List<Data> lst = new List<Data>

    new Data()  Id = 1, Val1 = 100 ,
    new Data()  Id = 1, Val1 = 200 ,
    new Data()  Id = 1, Val1 = 300 ,
    new Data()  Id = 2, Val1 = 100 ,
    new Data()  Id = 2, Val1 = 200 ,
    new Data()  Id = 3, Val1 = 300 ,
    new Data()  Id = 3, Val1 = 300 ,
    new Data()  Id = 3, Val1 = 300 ,
    new Data()  Id = 1, Val1 = 200 ,
    new Data()  Id = 1, Val1 = 200 ,
    new Data()  Id = 1, Val1 = 200 ,
    new Data()  Id = 2, Val1 = 200 ,
    new Data()  Id = 3, Val1 = 100 ,
    new Data()  Id = 3, Val1 = 100 ,
;

然后是这段代码:

decimal Cumulative_Probability = 0;
var Result1 = (lst.OrderBy(o => o.Id).GroupBy(x => new  x.Val1 )
    .Select(y => new
    
        y.Key.Val1,
        Probability = (Convert.ToDecimal(y.Count()) / lst.Count),
        Cumulative_Probability = (Cumulative_Probability = 
            Cumulative_Probability + 
            (Convert.ToDecimal(y.Count()) / lst.Count))
    )).OrderBy(o => o.Val1).ToList();

此代码运行良好,Cumulative_Probability 计算正确。

现在请考虑以下代码:

decimal Cumulative_Probability2 = 0;
var Result2 = (lst.OrderBy(o => o.Id).GroupBy(x => new  x.Id, x.Val1 )
    .Select(y => new
    
        y.Key.Id,
        y.Key.Val1,
        Probability = (Convert.ToDecimal(y.Count()) 
            / lst.Where(o => o.Id == y.Key.Id).Count()),
        Cumulative_Probability = (Cumulative_Probability2 = 
            Cumulative_Probability2 + 
            (Convert.ToDecimal(y.Count()) / 
            lst.Where(o => o.Id == y.Key.Id).Count()))
    )).OrderBy(o => o.Id).ThenBy(o => o.Val1).ToList();

这段代码生成这个结果:

如您所见,Probability 在每个组中都正确计算,但不是Cumulative_Probability。我想在每个 Id 组中计算Cumulative_Probability(组记录首先根据Id 然后Val1)并且Cumulative_Probability2 不会在每个组中重置。我如何计算每个组中的Cumulative_Probability

谢谢


编辑 1)

我想要这个结果:

 Id             Val1             Probability       Cumulative_Probability 
 -------------------------------------------------------------------------
 1              100                0.16                 0.16
 1              200                0.66                 0.82
 1              300                0.16                 0.98
 2              100                0.33                 0.33
 2              200                0.66                 0.66
 ... 

【问题讨论】:

你能说明你的预期输出是什么吗? 我现在明白了,您只需要该 ID 的累积值 您想要每个 ID 的先前值的总和。所以你想要 Probability.Select((x, i) => x.Take(i + 1).Sum()) @jdweng 你认为Ids 是1,2,3,... 但在现实世界中我有Ids 就像1401, 2012, 3232, ... 一样 A GroupBy 创建一个二维数组 > : Probability.GroupBy(x => x.Id).Select(x => x.Select((y,i) => new id = x.Key, p = y, c= x.Take(i + 1).Sum())).ToList(); 【参考方案1】:

我借助一种累积累积概率的扩展方法以及一些嵌套的GroupBy 设法做到了这一点。我确信一定有更简单的方法,但我正在挠头试图找到它。

扩展名是:

public static class EnumerableExtensions

    public static IEnumerable<TResult> Accumulate<TSource, TAccumulate, TResult>(
        this IEnumerable<TSource> source, 
        TAccumulate seed, 
        Func<TAccumulate, TSource, (TAccumulate,TResult)> accumulator)
    
        var acc = seed;
        foreach(TSource value in source)
        
            var (newSeed, newSource) = accumulator.Invoke(acc, value);
            yield return newSource;
            acc = newSeed;
        
    

完成后的代码如下:

var result = lst.GroupBy( x => x.Id)
        .SelectMany( (grpId,i) =>  grpId.GroupBy(x => x.Val1)
                    .Accumulate(0M, (acc,grpVal) => (acc + (decimal)grpVal.Count()/grpId.Count(), new 
                        Id = grpId.Key,
                        Val1 = grpVal.Key,
                        Probability = (decimal)grpVal.Count()/grpId.Count(),
                        Cumulative_Probability = acc + ((decimal)grpVal.Count()/grpId.Count())
                    ))
                   )
        .OrderBy(x => x.Id);

现场示例:https://dotnetfiddle.net/dvW1qo

【讨论】:

太棒了!我看到这个问题有点晚了!我喜欢这样的任务:-) @DavidStania 我也是。我讨厌我的答案,因为重复计算概率 3 次。 感谢亲爱的朋友您的解决方案。我认为也必须有一个更简单的方法。我们必须希望@JonSkeet 看到这个问题:D。 @Jamiec 我添加了一个答案,它可以工作。感谢您的帮助【参考方案2】:

此代码有效:

var Result2 = (from a in lst.OrderBy(o => o.Id)
               group a by new  a.Id, a.Val1  into grp
               select new
               
                   grp.Key.Id,
                   grp.Key.Val1,
                   Probability = (Convert.ToDecimal(grp.Count()) / lst.Where(o => o.Id == grp.Key.Id).Count()),
                   Cumulative_Probability = (from b in lst.Where(o => o.Id == grp.Key.Id && o.Val1 <= grp.Key.Val1)
                                             group b by new  b.Val1  into grp2
                                             select new
                                             
                                                 Probability2 = (Convert.ToDecimal(grp2.Count()) / lst.Where(o => o.Id == grp.Key.Id).Count())
                                             ).Sum(o => o.Probability2)
                ).OrderBy(o => o.Id).ThenBy(o => o.Val1).ToList();

【讨论】:

以上是关于按两列分组并根据其中一列计算累积值的主要内容,如果未能解决你的问题,请参考以下文章

PostgreSQL - 按两列分组并使用一列作为结果列

Pandas 数据框:按两列分组,然后对另一列进行平均

按两列分组并计算 Pandas 中每个组合的出现次数

按两列排序,为啥不先分组呢?

在按两列分组时选择最大值,并在另一列上排序

Pandas:按两列分组,将第一列组中的第一个值相加