C# LINQ聚合和分组(按时间段)

C# LINQ聚合和分组(按时间段),c#,linq,aggregate-functions,C#,Linq,Aggregate Functions,我试图理解如何使用LINQ按时间间隔对数据进行分组;然后理想地将每组进行聚合 var versionsGroupedByRoundedTimeAndAuthor = db.Versions.GroupBy(g => new { UserID = g.Author.ID, Time = RoundUp(g.Timestamp, TimeSpan.FromMinutes(2)) }); 我找到了很多明确日期范围的例子,我试着按

我试图理解如何使用LINQ按时间间隔对数据进行分组;然后理想地将每组进行聚合

var versionsGroupedByRoundedTimeAndAuthor = db.Versions.GroupBy(g => 
new
{
                UserID = g.Author.ID,
                Time = RoundUp(g.Timestamp, TimeSpan.FromMinutes(2))
});
我找到了很多明确日期范围的例子,我试着按时间段分组,比如5分钟、1小时、1天

var versionsGroupedByRoundedTimeAndAuthor = db.Versions.GroupBy(g => 
new
{
                UserID = g.Author.ID,
                Time = RoundUp(g.Timestamp, TimeSpan.FromMinutes(2))
});
例如,我有一个类,它用一个值包装DateTime:

public class Sample
{
     public DateTime timestamp;
     public double value;
}
var versionsGroupedByRoundedTimeAndAuthor = db.Versions.GroupBy(g => 
new
{
                UserID = g.Author.ID,
                Time = RoundUp(g.Timestamp, TimeSpan.FromMinutes(2))
});
这些观察结果作为一个系列包含在列表集合中:

List<Sample> series;
var versionsGroupedByRoundedTimeAndAuthor = db.Versions.GroupBy(g => 
new
{
                UserID = g.Author.ID,
                Time = RoundUp(g.Timestamp, TimeSpan.FromMinutes(2))
});

这从根本上说是有缺陷的,因为它将时间跨度本身分组。我不明白如何在查询中使用时间跨度(或表示间隔的任何数据类型)。

您可以将时间戳舍入到下一个边界(即,向下舍入到过去最接近的5分钟边界),并将其用作分组:

var groups = series.GroupBy(x =>
{
    var stamp = x.timestamp;
    stamp = stamp.AddMinutes(-(stamp.Minute % 5));
    stamp = stamp.AddMilliseconds(-stamp.Millisecond - 1000 * stamp.Second);
    return stamp;
})
.Select(g => new { TimeStamp = g.Key, Value = g.Average(s => s.value) })
.ToList();
var versionsGroupedByRoundedTimeAndAuthor = db.Versions.GroupBy(g => 
new
{
                UserID = g.Author.ID,
                Time = RoundUp(g.Timestamp, TimeSpan.FromMinutes(2))
});
通过在分组中使用修改的时间戳,将分钟设置为前一个5分钟边界,并删除秒和毫秒,可以实现这一点。当然,同样的方法也可用于其他时间段,即小时和天

var versionsGroupedByRoundedTimeAndAuthor = db.Versions.GroupBy(g => 
new
{
                UserID = g.Author.ID,
                Time = RoundUp(g.Timestamp, TimeSpan.FromMinutes(2))
});
编辑:

var versionsGroupedByRoundedTimeAndAuthor = db.Versions.GroupBy(g => 
new
{
                UserID = g.Author.ID,
                Time = RoundUp(g.Timestamp, TimeSpan.FromMinutes(2))
});
基于此合成样本输入:

var series = new List<Sample>();
series.Add(new Sample() { timestamp = DateTime.Now.AddMinutes(3) });
series.Add(new Sample() { timestamp = DateTime.Now.AddMinutes(4) });
series.Add(new Sample() { timestamp = DateTime.Now.AddMinutes(5) });
series.Add(new Sample() { timestamp = DateTime.Now.AddMinutes(6) });
series.Add(new Sample() { timestamp = DateTime.Now.AddMinutes(7) });
series.Add(new Sample() { timestamp = DateTime.Now.AddMinutes(15) });
var versionsGroupedByRoundedTimeAndAuthor = db.Versions.GroupBy(g => 
new
{
                UserID = g.Author.ID,
                Time = RoundUp(g.Timestamp, TimeSpan.FromMinutes(2))
});
var系列=新列表();
Add(newsample(){timestamp=DateTime.Now.AddMinutes(3)});
Add(newsample(){timestamp=DateTime.Now.AddMinutes(4)});
Add(newsample(){timestamp=DateTime.Now.AddMinutes(5)});
Add(newsample(){timestamp=DateTime.Now.AddMinutes(6)});
Add(newsample(){timestamp=DateTime.Now.AddMinutes(7)});
Add(newsample(){timestamp=DateTime.Now.AddMinutes(15)});

为我制作了3个分组,一个分组时间戳为3:05,一个分组时间戳为3:10,另一个分组时间戳为3:20 pm(您的结果可能因当前时间而异)。

对于按小时分组,您需要按时间戳的小时部分分组,可以这样做:

var groups = from s in series
  let groupKey = new DateTime(s.timestamp.Year, s.timestamp.Month, s.timestamp.Day, s.timestamp.Hour, 0, 0)
  group s by groupKey into g select new
                                      {
                                        TimeStamp = g.Key,
                                        Value = g.Average(a=>a.value)
                                      };
var versionsGroupedByRoundedTimeAndAuthor = db.Versions.GroupBy(g => 
new
{
                UserID = g.Author.ID,
                Time = RoundUp(g.Timestamp, TimeSpan.FromMinutes(2))
});

我在这场比赛中已经很晚了,但我在寻找其他东西时遇到了这个问题,我想我有更好的办法

series.GroupBy (s => s.timestamp.Ticks / TimeSpan.FromHours(1).Ticks)
        .Select (s => new {
            series = s
            ,timestamp = s.First ().timestamp
            ,average = s.Average (x => x.value )
        }).Dump();
var versionsGroupedByRoundedTimeAndAuthor = db.Versions.GroupBy(g => 
new
{
                UserID = g.Author.ID,
                Time = RoundUp(g.Timestamp, TimeSpan.FromMinutes(2))
});
这是一个linqpad程序示例,您可以验证和测试它

void Main()
{
    List<Sample> series = new List<Sample>();

    Random random = new Random(DateTime.Now.Millisecond);
    for (DateTime i = DateTime.Now.AddDays(-5); i < DateTime.Now; i += TimeSpan.FromMinutes(1))
    {
        series.Add(new UserQuery.Sample(){ timestamp = i, value = random.NextDouble() * 100 });
    }
    //series.Dump();
    series.GroupBy (s => s.timestamp.Ticks / TimeSpan.FromHours(1).Ticks)
        .Select (s => new {
            series = s
            ,timestamp = s.First ().timestamp
            ,average = s.Average (x => x.value )
        }).Dump();
}

// Define other methods and classes here
public class Sample
{
     public DateTime timestamp;
     public double value;
}
var versionsGroupedByRoundedTimeAndAuthor = db.Versions.GroupBy(g => 
new
{
                UserID = g.Author.ID,
                Time = RoundUp(g.Timestamp, TimeSpan.FromMinutes(2))
});
void Main()
{
列表系列=新列表();
Random Random=新随机数(DateTime.Now.毫秒);
for(DateTime i=DateTime.Now.AddDays(-5);is.timestamp.Ticks/TimeSpan.FromHours(1.Ticks)
。选择(s=>new{
系列=s
,timestamp=s.First().timestamp
,平均值=s.平均值(x=>x.值)
}).Dump();
}
//在此处定义其他方法和类
公共类样本
{
公共日期时间戳;
公共双重价值;
}

我建议使用new DateTime()避免任何小于毫秒的问题

var versionsGroupedByRoundedTimeAndAuthor = db.Versions.GroupBy(g => 
new
{
                UserID = g.Author.ID,
                Time = RoundUp(g.Timestamp, TimeSpan.FromMinutes(2))
});

var versionsGroupedByRoundedTimeAndAuthor = db.Versions.GroupBy(g => 
new
{
                UserID = g.Author.ID,
                Time = RoundUp(g.Timestamp, TimeSpan.FromMinutes(2))
});
注意,我在这里按Author.ID和四舍五入的时间戳分组

var versionsGroupedByRoundedTimeAndAuthor = db.Versions.GroupBy(g => 
new
{
                UserID = g.Author.ID,
                Time = RoundUp(g.Timestamp, TimeSpan.FromMinutes(2))
});
取材自@dtb answer的汇总函数

var versionsGroupedByRoundedTimeAndAuthor = db.Versions.GroupBy(g => 
new
{
                UserID = g.Author.ID,
                Time = RoundUp(g.Timestamp, TimeSpan.FromMinutes(2))
});

请阅读以下内容:即使我真的迟到了,平等到毫秒并不总是意味着平等

,以下是我的2美分:

var versionsGroupedByRoundedTimeAndAuthor = db.Versions.GroupBy(g => 
new
{
                UserID = g.Author.ID,
                Time = RoundUp(g.Timestamp, TimeSpan.FromMinutes(2))
});
我想在5分钟的时间间隔内对时间值进行上下取整:

var versionsGroupedByRoundedTimeAndAuthor = db.Versions.GroupBy(g => 
new
{
                UserID = g.Author.ID,
                Time = RoundUp(g.Timestamp, TimeSpan.FromMinutes(2))
});
10:31 --> 10:30
10:33 --> 10:35
10:36 --> 10:35
这可以通过转换为TimeSpan.Tick并转换回DateTime并使用Math.Round()来实现:

var versionsGroupedByRoundedTimeAndAuthor = db.Versions.GroupBy(g => 
new
{
                UserID = g.Author.ID,
                Time = RoundUp(g.Timestamp, TimeSpan.FromMinutes(2))
});

移位的时间戳可以在上面所示的linq分组中使用。

我改进了BrokenGlass的答案,使其更通用并增加了保护措施。根据他目前的回答,如果你选择9的间隔,它将不会达到你预期的效果。同样的道理,任何数字60都不能被整除。对于这个例子,我使用9,从午夜(0:00)开始

var versionsGroupedByRoundedTimeAndAuthor = db.Versions.GroupBy(g => 
new
{
                UserID = g.Author.ID,
                Time = RoundUp(g.Timestamp, TimeSpan.FromMinutes(2))
});
  • 从0:00到0:08.999的所有内容都将按照您的预期放入0:00的组中。它将一直这样做,直到您到达0:54开始的分组
  • 在0:54时,它将只对0:54到0:59.999之间的内容进行分组,而不会向上分组到01:03.999
对我来说,这是一个巨大的问题

var versionsGroupedByRoundedTimeAndAuthor = db.Versions.GroupBy(g => 
new
{
                UserID = g.Author.ID,
                Time = RoundUp(g.Timestamp, TimeSpan.FromMinutes(2))
});
我不知道如何解决这个问题,但您可以添加保护措施。
变化:

var versionsGroupedByRoundedTimeAndAuthor = db.Versions.GroupBy(g => 
new
{
                UserID = g.Author.ID,
                Time = RoundUp(g.Timestamp, TimeSpan.FromMinutes(2))
});
  • 60%[间隔]等于0的任何分钟都是可接受的间隔。下面的if声明对此进行了保障
  • 小时间隔也可以

    var versionsGroupedByRoundedTimeAndAuthor = db.Versions.GroupBy(g => 
    new
    {
                    UserID = g.Author.ID,
                    Time = RoundUp(g.Timestamp, TimeSpan.FromMinutes(2))
    });
    
            double minIntervalAsDouble = Convert.ToDouble(minInterval);
            if (minIntervalAsDouble <= 0)
            {
                string message = "minInterval must be a positive number, exiting";
                Log.getInstance().Info(message);
                throw new Exception(message);
            }
            else if (minIntervalAsDouble < 60.0 && 60.0 % minIntervalAsDouble != 0)
            {
                string message = "60 must be divisible by minInterval...exiting";
                Log.getInstance().Info(message);
                throw new Exception(message);
            }
            else if (minIntervalAsDouble >= 60.0 && (24.0 % (minIntervalAsDouble / 60.0)) != 0 && (24.0 % (minIntervalAsDouble / 60.0) != 24.0))
            {
                //hour part must be divisible...
                string message = "If minInterval is greater than 60, 24 must be divisible by minInterval/60 (hour value)...exiting";
                Log.getInstance().Info(message);
                throw new Exception(message);
            }
            var groups = datas.GroupBy(x =>
            {
                if (minInterval < 60)
                {
                    var stamp = x.Created;
                    stamp = stamp.AddMinutes(-(stamp.Minute % minInterval));
                    stamp = stamp.AddMilliseconds(-stamp.Millisecond);
                    stamp = stamp.AddSeconds(-stamp.Second);
                    return stamp;
                }
                else
                {
                    var stamp = x.Created;
                    int hourValue = minInterval / 60;
                    stamp = stamp.AddHours(-(stamp.Hour % hourValue));
                    stamp = stamp.AddMilliseconds(-stamp.Millisecond);
                    stamp = stamp.AddSeconds(-stamp.Second);
                    stamp = stamp.AddMinutes(-stamp.Minute);
                    return stamp;
                }
            }).Select(o => new
            {
                o.Key,
                min = o.Min(f=>f.Created),
                max = o.Max(f=>f.Created),
                o
            }).ToList();
    
    double minInterval asdouble=Convert.ToDouble(minInterval);
    如果(MiniIntervalasDouble=60.0&&(24.0%(MiniIntervalasDouble/60.0))!=0&(24.0%(MiniIntervalasDouble/60.0)!=24.0))
    {
    //小时部分必须是可分的。。。
    string message=“如果minInterval大于60,则24必须可被minInterval整除/60(小时值)…正在退出”;
    Log.getInstance().Info(消息);
    抛出新异常(消息);
    }
    var groups=datas.GroupBy(x=>
    {
    如果(最小间隔<60)
    {
    var stamp=x.已创建;
    stamp=stamp.AddMinutes(-(stamp.Minute%minInterval));
    stamp=stamp.add毫秒(-stamp.millis秒);
    stamp=stamp.AddSeconds(-stamp.Second);
    返回印章;
    }
    其他的
    {
    var stamp=x.已创建;
    int hourValue=最小间隔/60;
    stamp=stamp.AddHours(-(stamp.Hour%hourValue));
    stamp=stamp.add毫秒(-stamp.millis秒);
    stamp=stamp.AddSeconds(-stamp.Second);
    stamp=stamp.AddMinutes(-stamp.minutes);
    返回印章;
    }
    }).选择(o=>new
    {
    o、 钥匙,
    min=o.min(f=>f.Created),
    max=o.max(f=>f.Created),
    o
    }).ToList();
    

  • 在select语句中输入您想要的内容!我输入min/max是因为它更容易测试。

    我知道这并不能直接回答问题,但我在谷歌上搜索了一个与聚合can非常相似的解决方案
    var versionsGroupedByRoundedTimeAndAuthor = db.Versions.GroupBy(g => 
    new
    {
                    UserID = g.Author.ID,
                    Time = RoundUp(g.Timestamp, TimeSpan.FromMinutes(2))
    });