LINQ aggregate and group by periods of time
You could round the time stamp to the next boundary (i.e. down to the closest 5 minute boundary in the past) and use that as your grouping:
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();
Above achieves that by using a modified time stamp in the grouping, which sets the minutes to the previous 5 minute boundary and removes the seconds and milliseconds. The same approach of course can be used for other time periods, i.e. hours and days.
Edit:
Based on this made up sample input:
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) });
3 groups were produced for me, one with grouping timestamp 3:05, one with 3:10 and one with 3:20 pm (your results may vary based on current time).
Aggregate values at hourly interval using LINQ C#
You can shift your Timestamp
to get the effect. Instead of
items.GroupBy(u => new { u.ID, u.TimeStamp.Date, u.TimeStamp.Hour })
You shift the time that used for grouping by to be 15 minutes in the past, so everything at the first 15 minutes of the hour go to the earlier hour.
items.GroupBy(u => new { u.ID, u.TimeStamp.AddMinutes(-15).Date, u.TimeStamp.AddMinutes(-15).Hour })
LINQ: Group by Date Range within 5 minutes
You can try something like this:
var groupedItems = items
.GroupBy(i => i.ItemId, (k, g) => g
.GroupBy(i => (long)new TimeSpan(i.Date.Ticks - g.Min(e => e.Date).Ticks).TotalMinutes / 5))
.SelectMany(g => g);
Group by date range , count and sort within each group LINQ
Introduce array, which contains ranges you want to group by. Here is two ranges - today (zero days) and 14 days (two weeks):
var today = DateTime.Today;
var ranges = new List<int?> { 0, 14 };
Now group your items by range it falls into. If there is no appropriate range (all dates more than two weeks) then default null
range value will be used:
var defaultGroups =
from h in ListHeader
let daysFromToday = (int)(today - h.EntryDate).TotalDays
group h by ranges.FirstOrDefault(range => daysFromToday <= range) into g
orderby g.Min(x => x.EntryDate)
select g;
UPDATE: Adding custom ranges for grouping:
var ranges = new List<int?>();
ranges.Add(0); // today
ranges.Add(7*2); // two weeks
ranges.Add(DateTime.Today.Day); // within current month
ranges.Add(DateTime.Today.DayOfYear); // within current year
ranges.Sort();
Linq: Group by date intervals (5, 10, 15, etc.. years)
Review the following Unit tests used to demonstrate how to group by periods/intervals.
[TestClass]
public class GroupByDateIntervalsTests {
[TestMethod]
public void Group_By_5_year_Intervals_Max_30() {
var employees = GenerateRandomDates(DateTime.Now, 5, 40, 50).Select((d, i) => new { id = i, StartDate = d });
var now = DateTime.Today;
var period = 5;
var maxPeriod = 30;
var groups = from employee in employees
let interval = DateTime.MinValue.AddDays((now - employee.StartDate).TotalDays).Year / period
group employee by Math.Min(interval * period, maxPeriod) into g
orderby g.Key
select new {
period = g.Key,
employees = g.Select(e => e.id).ToArray()
};
var result = groups.ToList();
}
[TestMethod]
public void Group_By_Random_Interval_Max_30() {
var employees = GenerateRandomDates(DateTime.Now, 5, 40, 50).Select((d, i) => new { id = i, StartDate = d });
var now = DateTime.Today;
var periods = new[] { 5, 10, 20, 30 };
var groups = employees
.GroupBy(employee => {
var period = DateTime.MinValue.AddDays((now - employee.StartDate).TotalDays).Year;
var interval = periods.Where(p => (period / p) > 0).Max();
return Math.Min(interval, periods.Max());
})
.Select(g => new {
period = g.Key,
employees = g.Select(e => e.id).ToArray()
});
var result = groups.ToList();
}
public List<DateTime> GenerateRandomDates(DateTime rootDate, int minAgeAtRootDate, int maxAgeAtRootDate, int count) {
Contract.Assert(minAgeAtRootDate <= maxAgeAtRootDate, "invalid age range. Minimum age cannot be higher than maximum age");
var minDate = rootDate.Date.AddYears(-maxAgeAtRootDate);
var maxDate = rootDate.Date.AddYears(-minAgeAtRootDate);
var range = (maxDate - minDate).Days;
if (range == 0) {
range = 364;
}
var random = new Random();
var dates = Enumerable
.Range(1, count)
.Select(i => minDate.AddDays(random.Next(range)))
.ToList();
return dates;
}
}
Group datetime by an interval of minutes
I think you are looking for SelectMany
which will unwind one level of grouping:
var ans = myList
.GroupBy(c => c.caller, (caller, cg) => new { Key = caller, MinDateTime = cg.Min(c => c.date), Calls = cg })
.SelectMany(cg => cg.Calls.GroupBy(c => (int)(c.date - cg.MinDateTime).TotalMinutes / 30))
.OrderBy(cg => cg.Min(c => c.date))
.ToList();
Note: The GroupBy
return selects the Min
as a minor efficiency improvement so you don't constantly re-find the minimum DateTime
for each group per call.
Note 2: The (int)
conversion creates the buckets - otherwise, .TotalMinutes
returns a double
and the division by 30 just gives you a (unique) fractional answer and you get no grouping into buckets.
By modifying the initial code (again for minor efficiency), you can reformat the answer to match your textual result:
var ans = myList
.GroupBy(c => c.caller, (caller, cg) => new { Key = caller, MinDateTime = cg.Min(c => c.date), Calls = cg })
.SelectMany(cg => cg.Calls.GroupBy(c => (int)(c.date - cg.MinDateTime).TotalMinutes / 30), (bucket, cg) => new { FirstCall = cg.MinBy(c => c.date), Calls = cg })
.OrderBy(fcc => fcc.FirstCall.date)
.ToList();
var ans2 = ans.Select(fcc => new { Caller = fcc.FirstCall.caller, FirstCallDateTime = fcc.FirstCall.date, LastCallDateTime = fcc.Calls.Max(c => c.date), Count = fcc.Calls.Count() })
.ToList();
linq group by timespan
This is what I ended up with:
var result = (from logs in db.logs
where logs.AccountNumber == 1450
where logs.StartDateTime > new DateTime(2013,1,1) && logs.StartDateTime < new DateTime(2013,1,31)
group logs by logs.AccountNumber into g
select new {
AvgTime = (g.Sum(h=>h.ElapsedHours) * 60) + g.Sum(h=>h.ElapsedMinutes) / g.Count(),
TotalTime = (g.Sum(h=>h.ElapsedHours) * 60) + g.Sum(h=>h.ElapsedMinutes)
}).ToList()
.Select(x=> new {
AvgTime = TimeSpan.FromMinutes(x.AvgTime),
TotalTime = TimeSpan.FromMinutes(x.TotalTime)
});
Grouping by Time ranges in Linq
I've done this myself for exactly the same situation you describe.
For speed, modified the database's datapoints table to include an integer-based time column, SecondsSince2000
, and then worked with that value in my LINQ to SQL query. SecondsSince2000
is a computed column defined as:
datediff(second, dateadd(month,1200,0), DataPointTimeColumn) PERSISTED
Where DataPointTimeColumn
is the name of the column that stores the datapoint's time. The magic function call dateadd(month,1200,0)
returns 2000-01-01 at midnight, so the column stores the number of seconds since that time.
The LINQ to SQL query is then made much simpler, and faster:
int timeSlotInSeconds = 60;
var wattList =
(from t in _table
where t.Id == id
&& t.Time >= startTime
&& t.Time <= endTime
group t by t.SecondsSince2000 - (t.SecondsSince2000 % timeSlotInSeconds)
into g
orderby g.Key
select g.Average(a => a.Value))).ToList();
If you can't modify your database, you can still do this:
var baseTime = new DateTime(2000, 1, 1);
var wattList =
(from t in _table
where t.Id == id
&& t.Time >= startTime
&& t.Time <= endTime
let secondsSince2000 = (int)(t.Time- baseTime).TotalSeconds
group t by secondsSince2000 - (secondsSince2000 % timeSlotInSeconds)
into g
orderby g.Key
select g.Average(a => a.Value))).ToList();
The query will be quite a bit slower.
Related Topics
How to Find the Fully Qualified Name of an Assembly
Differencebetween Streamwriter.Flush() and Streamwriter.Close()
How to Get Status Code from Webclient
Do Interfaces Derive from System.Object? C# Spec Says Yes, Eric Says No, Reality Says No
ASP.NET MVC Razor Render Without Encoding
ASP.NET MVC: Calling a Method from a View
C#: How to Get First Char of a String
Connecting to SQL Server with Visual Studio Express Editions
How to Make Observablecollection Thread-Safe
The Source File Is Different from When the Module Was Built
Datetime2' Error When Using Entity Framework in VS 2010 .Net 4.0
Programmatically Get Summary Comments at Runtime
Jobject.Parse VS JSONconvert.Deserializeobject
Wait for an Ajax Call to Complete with Selenium 2 Webdriver