C# LINQ聚合和分组(按时间段)
我试图理解如何使用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)) }); 我找到了很多明确日期范围的例子,我试着按
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))
});
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))
});