Grouping by month with LINQ - linq

What's the most simple way to group data over time, by month?
In the example below, there's a list of games where each have the date played and the city the game was played in. If we want to get the number of games played per month, by grouping them by month, is it sensible to use a composite year-month key like I am? Any other approaches? Including those that don't require resorting to composite grouping key?
public static void GamesByMonth ()
{
var games = new List<Game>()
{
new Game() { Date = new DateTime( 2010, 11, 15 ), City = "Denver"},
new Game() { Date = new DateTime( 2011, 1, 11 ), City = "Chicago"},
new Game() { Date = new DateTime( 2011, 1, 10 ), City = "Houston"},
new Game() { Date = new DateTime( 2011, 3, 21 ), City = "Atlanta"},
new Game() { Date = new DateTime( 2011, 4, 18 ), City = "Denver"},
new Game() { Date = new DateTime( 2011, 4, 29 ), City = "Boston"}
};
var groupings =
from game in games
group game by new
{
game.Date.Year,
game.Date.Month
};
}
Note: I'm including year in the grouping key because, in this case, we want the trend over time as opposed to the absolute number of games played in a particular month, regardless of year.

var groupings =
from game in games
group game by new DateTime(game.Date.Year, game.Date.Month, 1)

Yes, that's sensible. Then you just use Count on each grouping to get the number of games per month.

Related

linq avg on grouped counts

Please consider this simple class
public class SEANCE
{
int ID_SEANCE {get;set;}
DateTime SEA_DATE {get;set;}
}
It's for a fitness management app, the SEANCE class is the session table for the members.
Here's my starting query to show the number of the sessions grouped by day Hour
var lst = ctx.SEANCES
.GroupBy(o => o.SEA_DATE.Hour)
.Select(g => new ChartData()
{
DT = new DateTime(2017, 01, 01, g.Key, 0, 0),
VALUE = g.Count()
});
return lst.OrderBy(a => a.DT).ToList();
as you can see, it returns the total number of sessions grouped by hour.
What I want instead is the average number of sessions by day and grouped by hour, and that's where I am a little lost :p
here what i mean
for example at 18, 1500 is corresponding to the total number of sessions at that time.
what i want instead is the average number by day
hope it make sense now :)
ctx.SEANCES.Count() / lst.Count(), but that seems like database source so better to use the result:
var groups = ctx.SEANCES
.GroupBy(s => s.SEA_DATE.Hour)
.OrderBy(g => g.Key)
.Select(g => new ChartData()
{
DT = g.First().SEA_DATE,
VALUE = g.Count()
})
.ToList();
var averageByHour = groups.Average(c => c.VALUE);
var averageByDay = groups.GroupBy(s => s.DT.Day).Average(g => g.Sum(c => c.VALUE));

entity framework grouping by datetime

Every 5 minutes a row in a sql server table is added. The fields are:
DateTime timeMark,Int total.
Using entity framework I want to populate a new list covering a whole week of five minute values using an average of the totals from the last three months.
How would I accomplish this with Entity Framework?
Assuming your log is really exact on the "five mintues", and that I understood well
, you want a list with 7 day * 24 hours * (60/5) minutes, so 2016 results ?
//define a startDate
var beginningDate = <the date 3 month ago to start with>;
//get the endDate
var endDate = beginningDate.AddMonths(3);
var list = myTable.Where(m => m.TimeMark >= beginningDate && m.TimeMark <=endDate)
//group by dayofWeek, hour and minute will give you data for each distinct day of week, hour and minutes
.GroupBy(m => new {
dayofWeek = SqlFunctions.DatePart("weekday", m.TimeMark),
hour = SqlFunction.DatePart("hour", m.TimeMark),
minute = SqlFunctions.DatePart("minute", m.TimeMark)
})
.Select(g => new {
g.Key.dayofWeek,
g.Key.hour,
g.Key.minute,
total = g.Average(x => x.Total)
});

Left join with grouping and ordering

Musicians write songs. Songs are played on the air.
I have database tables Musicians, Songs and AirTimes. The AirTimes table entries hold information on which song was played on which date and for how many minutes.
I have classes Musician, Song, AirTime that correspond to the tables. The classes have navigational properties that point to the other entity. Arrows below represent navigation.
Musician <--> Song <--> AirTime
From the database, I have to retrieve all the Musicians and dates on which his/her song got AirTime. Plus I want to show the number of Songs played on a particular date and the number of minutes played on that date.
In Microsoft SQL, I would do it as follows:
select
dbo.Musicians.LastName
, dbo.AirTimes.PlayDate
, count(dbo.AirTimes.PlayDate) as 'No. of entries'
, sum(dbo.AirTimes.Duration) as 'No. of minutes'
from dbo.Musicians
left outer join dbo.Songs
on dbo.Musicinas.MusicianId = dbo.Songs.MusicianId
left outer join dbo.AirTimes
on dbo.Songs.SongId = dbo.AirTimes.SongId
and '2014-07-01T00:00:00' <= dbo.AirTimes.PlayDate
and dbo.AirTimes.PlayDate <= '2014-07-31T00:00:00'
group by
dbo.Musicians.LastName
, dbo.AirTimes.PlayDate
order by
dbo.Musicians.LastName
, dbo.AirTimes.PlayDate
Can anybody “translate” this into linq-to-entitese?
Update Aug. 9, 2012
I'm unable to confirm grudolf's schemes do what I wanted. I accomplished things with a different technique. Nonetheless, I accept his/her answer.
As you have the navigational properties in both directions you can start either from AirTimes:
var grpTime = (
from a in AirTimes
where a.Date >= firstDate && a.Date < lastDate
group a by new {a.Song.Musician.LastName, a.Song.Title, a.Date} into grp
select new {
grp.Key.LastName,
grp.Key.Title,
grp.Key.Date,
Plays = grp.Count(),
Seconds = grp.Sum(x => x.Duration)
}
);
or from Musicians:
var grpMus = (
from m in Musicians
from s in m.Songs
from p in s.Plays
where p.Date >= firstDate && p.Date < lastDate
group p by new {m.LastName, s.Title, p.Date} into grp
select new {
grp.Key.LastName,
grp.Key.Title,
grp.Key.Date,
Plays = grp.Count(),
Seconds= grp.Sum(x => x.Duration)
}
);
EDIT:
To display all musicians, including those without airtime you can use another level of grouping - in first step you calculate totals per song+day and then group them with song's author. It could probably work directly with database but I didn't manage to find an efficient way to do it. Yet. ;) With code, the original AirTimes result is changed to return Musician instead of his lastname and then joined to list of all musicians:
//Airtimes for musicians
var grpAir = (
from a in AirTimes
where a.Date >= firstDate && a.Date < lastDate
group a by new {a.Song.Musician, a.Date} into grp
select new {
//Musician instead of his LastName for joining. Id would work too
grp.Key.Musician,
//grp.Key.Musician.LastName,
Date=grp.Key.Date,
Plays = grp.Count(),
Secs = grp.Sum(x => x.Duration)
}
);
var res = (
from m in Musicians
join g in grpAir on m equals g.Musician into g2
from g in g2.DefaultIfEmpty()
orderby m.LastName
select new {
m.LastName,
Date = (g==null ? null : g.Date),
Plays = (g==null ? 0 : g.Plays),
Secs = (g==null ? 0 : g.Secs)
}
);
You can find a more complete LINQPad sample at https://gist.github.com/3236238

Group By Dates using Linq

What I have is a set of users with join dates and I want to use GoogleChartSharp to create a chart showing how many users have joined each month.
So I have this set of dates (US format) in a list:
01/01/2009
02/01/2009
02/12/2009
03/02/2009
03/12/2009
03/22/2009
Googlechartsharp requires me to do something like this:
string[] monthYears = new string[] { "01/2009", "02/2009", "03/2009"};
int[] number= new int[] {1,2,3};
LineChart chart = new LineChart(150, 150);
chart.SetData(data);
chart.SetLegend(monthYears);
string url = chart.GetUrl();
How do I utilize linq to get the list of dates into arrays required by google? Or is Linq even the right tool?
Sure, Linq is an ideal tool. The code below is an example. (I have specified the month in the dates so I don't have to use DateTime.ParseExact when setting up the input array).
DateTime[] dates =
{
DateTime.Parse("jan/01/2009")
,DateTime.Parse("feb/01/2009")
,DateTime.Parse("feb/12/2009")
,DateTime.Parse("mar/02/2009")
,DateTime.Parse("mar/12/2009")
,DateTime.Parse("mar/22/2009")
};
var datesGroupedByMonthYear = from date in dates
group date by date.ToString("MM/yyyy") into groupedDates
orderby groupedDates.First() ascending
select new { MonthYear = groupedDates.Key, Dates = groupedDates };
string[] monthYears = (from d in datesGroupedByMonthYear select d.MonthYear).ToArray();
int[] number = (from d in datesGroupedByMonthYear select d.Dates.Count()).ToArray();
Asusming those numvers are the number of times each month occurs. Then you could do something like this:
var numbers = from d in monthYears .Select(x => DateTime.Parse(x))
group d by d.Month into dg
select dg.Count();
That will give you an IEnumerable of the count for each. If you need it as an array it is not too hard to convert that to an array.

TimeSpan to friendly string library (C#)

Does anyone know of a good library (or code snippet) for converting a TimeSpan object to a "friendly" string such as:
Two years, three months and four days
One week and two days
(It's for a document expiry system, where the expiry could be anything from a few days to several decades)
Just to clarify, say I had a TimeSpan with 7 days, that should print "1 week", 14 days "2 weeks", 366 days "1 year and 1 day", etc etc.
I just stumbled upon this question because I wanted to do a similar thing. After some googling I still didn't find what I wanted: display a timespan in a sort of "rounded" fashion. I mean: when some event took several days, it doesn't always make sense to display the milliseconds. However, when it took minutes, it probably does. And in that case, I don't want 0 days and 0 hours to be displayed. So, I want to parametrize the number of relevant timespan parts to be displayed. This resulted in this bit of code:
public static class TimeSpanExtensions
{
private enum TimeSpanElement
{
Millisecond,
Second,
Minute,
Hour,
Day
}
public static string ToFriendlyDisplay(this TimeSpan timeSpan, int maxNrOfElements)
{
maxNrOfElements = Math.Max(Math.Min(maxNrOfElements, 5), 1);
var parts = new[]
{
Tuple.Create(TimeSpanElement.Day, timeSpan.Days),
Tuple.Create(TimeSpanElement.Hour, timeSpan.Hours),
Tuple.Create(TimeSpanElement.Minute, timeSpan.Minutes),
Tuple.Create(TimeSpanElement.Second, timeSpan.Seconds),
Tuple.Create(TimeSpanElement.Millisecond, timeSpan.Milliseconds)
}
.SkipWhile(i => i.Item2 <= 0)
.Take(maxNrOfElements);
return string.Join(", ", parts.Select(p => string.Format("{0} {1}{2}", p.Item2, p.Item1, p.Item2 > 1 ? "s" : string.Empty)));
}
}
Example (LinqPad):
new TimeSpan(1,2,3,4,5).ToFriendlyDisplay(3).Dump();
new TimeSpan(0,5,3,4,5).ToFriendlyDisplay(3).Dump();
Displays:
1 Day, 2 Hours, 3 Minutes
5 Hours, 3 Minutes, 4 Seconds
Suits me, see if it suits you.
Not a fully featured implementation, but it should get you close enough.
DateTime dtNow = DateTime.Now;
DateTime dtYesterday = DateTime.Now.AddDays(-435.0);
TimeSpan ts = dtNow.Subtract(dtYesterday);
int years = ts.Days / 365; //no leap year accounting
int months = (ts.Days % 365) / 30; //naive guess at month size
int weeks = ((ts.Days % 365) % 30) / 7;
int days = (((ts.Days % 365) % 30) % 7);
StringBuilder sb = new StringBuilder();
if(years > 0)
{
sb.Append(years.ToString() + " years, ");
}
if(months > 0)
{
sb.Append(months.ToString() + " months, ");
}
if(weeks > 0)
{
sb.Append(weeks.ToString() + " weeks, ");
}
if(days > 0)
{
sb.Append(days.ToString() + " days.");
}
string FormattedTimeSpan = sb.ToString();
In the end, do you really need to let someone know a document is going to expire exactly 1 year, 5 months, 2 weeks, and 3 days from now? Can't you get by with telling them the document will expire over 1 year from now, or over 5 months from now? Just take the largest unit and say over n of that unit.
There is now also the Humanizer project that looks very interesting that can do this and way more.
Here is my solution to this. It is based on other answers in this thread, with added support for year and month as that was requested in the original question (and was what I needed).
As for the discussion whether or not this makes sense I would say that there are cases where it does so. In my case we wanted to show the duration of agreements that in some cases are just a few days, and in other cases several years.
Tests;
[Test]
public void ToFriendlyDuration_produces_expected_result()
{
new DateTime(2019, 5, 28).ToFriendlyDuration(null).Should().Be("Until further notice");
new DateTime(2019, 5, 28).ToFriendlyDuration(new DateTime(2020, 5, 28)).Should().Be("1 year");
new DateTime(2019, 5, 28).ToFriendlyDuration(new DateTime(2021, 5, 28)).Should().Be("2 years");
new DateTime(2019, 5, 28).ToFriendlyDuration(new DateTime(2021, 8, 28)).Should().Be("2 years, 3 months");
new DateTime(2019, 5, 28).ToFriendlyDuration(new DateTime(2019, 8, 28)).Should().Be("3 months");
new DateTime(2019, 5, 28).ToFriendlyDuration(new DateTime(2019, 8, 31)).Should().Be("3 months, 3 days");
new DateTime(2019, 5, 1).ToFriendlyDuration(new DateTime(2019, 5, 31)).Should().Be("30 days");
new DateTime(2010, 5, 28).ToFriendlyDuration(new DateTime(2020, 8, 28)).Should().Be("10 years, 3 months");
new DateTime(2010, 5, 28).ToFriendlyDuration(new DateTime(2020, 5, 29)).Should().Be("10 years, 1 day");
}
Implementation;
private class TermAndValue
{
public TermAndValue(string singular, string plural, int value)
{
Singular = singular;
Plural = plural;
Value = value;
}
public string Singular { get; }
public string Plural { get; }
public int Value { get; }
public string Term => Value > 1 ? Plural : Singular;
}
public static string ToFriendlyDuration(this DateTime value, DateTime? endDate, int maxNrOfElements = 2)
{
if (!endDate.HasValue)
return "Until further notice";
var extendedTimeSpan = new TimeSpanWithYearAndMonth(value, endDate.Value);
maxNrOfElements = Math.Max(Math.Min(maxNrOfElements, 5), 1);
var termsAndValues = new[]
{
new TermAndValue("year", "years", extendedTimeSpan.Years),
new TermAndValue("month", "months", extendedTimeSpan.Months),
new TermAndValue("day", "days", extendedTimeSpan.Days),
new TermAndValue("hour", "hours", extendedTimeSpan.Hours),
new TermAndValue("minute", "minutes", extendedTimeSpan.Minutes)
};
var parts = termsAndValues.Where(i => i.Value != 0).Take(maxNrOfElements);
return string.Join(", ", parts.Select(p => $"{p.Value} {p.Term}"));
}
internal class TimeSpanWithYearAndMonth
{
internal TimeSpanWithYearAndMonth(DateTime startDate, DateTime endDate)
{
var span = endDate - startDate;
Months = 12 * (endDate.Year - startDate.Year) + (endDate.Month - startDate.Month);
Years = Months / 12;
Months -= Years * 12;
if (Months == 0 && Years == 0)
{
Days = span.Days;
}
else
{
var startDateExceptYearsAndMonths = startDate.AddYears(Years);
startDateExceptYearsAndMonths = startDateExceptYearsAndMonths.AddMonths(Months);
Days = (endDate - startDateExceptYearsAndMonths).Days;
}
Hours = span.Hours;
Minutes = span.Minutes;
}
public int Minutes { get; }
public int Hours { get; }
public int Days { get; }
public int Years { get; }
public int Months { get; }
}
I know this is old, but I wanted to answer with a great nuget package.
Install-Package Humanizer
https://www.nuget.org/packages/Humanizer
https://github.com/MehdiK/Humanizer
Example from their readme.md
TimeSpan.FromMilliseconds(1299630020).Humanize(4) => "2 weeks, 1 day, 1 hour, 30 seconds"
#ian-becker Needs the credit
The TimeSpan object has Days, Hours, Minutes, and Seconds properties on it, so it wouldn't be too hard to make a snippet that formats those values to a friendly string.
Unfortunately Days is the largest value. Anything longer than that and you'll have to start worrying about days in a month for every year...etc. You're better off stopping at days in my opinion (the added effort doesn't seem worth the gain).
UPDATE
...I figured I'd bring this up from my own comment:
Understandable, but is "This document expires in 10 years, 3 months, 21 days, 2 hours, and 30 minutes" really any more helpful or less silly? If it were up to me, since neither representation seems very useful...I'd leave off the timespan for expiry until the date got reasonably close (30 or 60 days maybe if you're worried about getting the document updated). Seems a much better UX choice to me.
It probably won't do everything you are looking for, but in v4 Microsoft will be implementing IFormattable on TimeSpan.
To format any period longer than 1 day (i.e. month/year/decade etc.) a Timespan object is not enough.
Suppose your timespan is 35 days, then from Apr 1 you would get one month and five days, whereas from Dec 1 you would get one month and four days.
Yes! I needed the same so many times then now I create my onw package and published it in Nuget. You are welcome to use it.
The package name is EstecheAssemblies
It's easy to implement:
using EstecheAssemblies;
var date = new DateTime("2019-08-08 01:03:21");
var text = date.ToFriendlyTimeSpan();
See how-do-i-calculate-relative-time, asked (by user number 1) a year ago when SO was young and not public.
It was (probably) the basis for the current age display for SO questions and answers.

Resources