LINQ group by hour and add missing integer values - c#

I have written a code like below:
var hourlyData = ctx.Transactions
.GroupBy(x => x.TransactionDate.Value.Hour)
.Select(pr => new HourlyGraph {
Hour = pr.Key,
Sales = pr.Sum(x => x.QuantitySoldTransaction)
}).OrderBy(x => x.Hour).ToList();
hourlyData.AddRange(Enumerable.Range(0, 23).Except(hourlyData.Select(m => m.Hour))
.Select(i => new HourlyGraph() { Hour = i, Sales =0 }));
Basically what I'm trying to do is to group all of my transactions from 0-23 (hours) and then add the missing integer values (hours). Those who miss I'd like to assign value - missing hour(22 lets say and sales 0).
The method above that I wrote gives me completely wrong values...
P.S. I get 22 value in hours multiple times, even though it shouldn't be there... 22 should only be repeated once (or any other hour value)...

Related

SelectMany c# linq

Sorry to open another post.
.SelectMany with C#
I asked in the previous post but I can't get the solution for my problem.
`
var departments = stops
.Where(stop => stop.InitDate != null)
.SelectMany(stop => new[] { Month = stop.InitDate.Month, Year = stop.InitDate.Year, Duration = stop.Duration })
.GroupBy(dt => new { dt.Month, dt.Year })
.OrderBy(g => g.Key.Month)
.ThenBy(g => g.Key.Year)
.Select(g => new
{
Key = g.Key.Month,
Año = g.Key.Year,
Duration = g.Sum(v => v.Duration),
Count = g.Count()
});
`
This is de final solution to my problem, but, when I use this in my code, I have some problems.
If I don't declare the variables "Month, Year, Duration", I get an error in:
.SelectMany(stop => new[] { Month = stop.InitDate.Month, Year = stop.InitDate.Year, Duration = stop.Duration })
But I do not know what kind of data they are month and year because if I declare it how integer, I get an error in .GroupBy(dt => new { dt.Month, dt.Year }), because the compiler recognizes dt as integer.
I tried to declare Month and Year as integer and put in the .GroupBy this:
.GroupBy(dt => new { Month, Year }) but it is not correct...
Thank you in advance
Raúl
Apparently you have a sequence named Stops which is a sequence of stop objects. Each stop object may or may have not an InitDate. If it has an InitDate, this InitDate has at least properties Month, Year and Duration, which are all int.
What you want is from your original Stops, only those stop objects that have an InitDate. From every stop object you select, you want to create a new object, with a Key property which contains the Month and the Year and a Duration property which contains the Duration.
You were almost there. Your problem was that you used SelectMany instead of a simple Select.
Normally you use SelectMany if you have a sequence of sequences that you want to concatenate into one sequence. However your Stops does not have a sequence of sequences. Every stop object should produce one "new object with Year, Month and Duration".
Or in simple words: whenever you have a collection of "thingies" and you want to convert every "thingy" into exactly one "other thingy", you should use Select, not SelectMany:
In your case the Select will be:
var stopData = stops
.Where(stop => stop.InitDate != null)
.Select(stop => new
{
GroupKey = new
{
Month = stop.InitDate.Month,
Year = stop.InitDate.Year,
},
Duration = stop.Duration
});
I put the Year and the Month alreay in a property GroupKey, because that makes the grouping easier:
var groupsOfSameMonth = stopData
.GroupBy( item => item.Key, item => item.Duration);
Now every group, contains a key, which is the {Month, Year}. The elements of the group are all Durations with this {month, year}. So now all you have to do is from every group, take all elements from the group and Sum() and Count() them:
var durationsOfSameMonth = groupsOfSameMonth
.Select(group => new
{
Month = group.Key.Month, // I assumed your Key was an error
Año = group.Key.Year,
Duration = group.Sum(),
Count = group.Count()
});
All you have to do is some ordering and you are finished.

.NET when grouping records by hour impossible to use datetime in the select

I'm trying to group a list of records by hour and store the number of record for each hour. Here is my code :
DateTime firstTimeStamp = myRecords.DataBaseRecords.First().TimeStamp;
Statistics = myRecords.DataBaseRecords
.GroupBy(x => x.TimeStamp.Hour)
.Select(group => new GraphModel() { Date =firstTimeStamp.AddHours(group.Key), Value = group.Count() })
.ToList();
The problem is that when I'm on the select fuction, I cannot acces to the DateTime anymore so the field group.key contains a value between 0 and 24. I just need to group all the records by hour and foreach hour, I need to have the number of records in the Value parameter.
You have to group the data by absolute hours as of the first timestamp, i.e. the differences in hours calculated for each TimeStamp value:
Statistics = myRecords.DataBaseRecords
.GroupBy(x => DbFunctions.DiffHours(firstTimeStamp, x.TimeStamp) into g
.Select(g => new GraphModel
{
Date = g.FirstOrDefault().TimeStamp,
Value = g.Count()
};
If this is plain LINQ to objects (not Entity Framework) you can replace ...
DbFunctions.DiffHours(firstTimeStamp, x.TimeStamp)
... by
(x.TimeStamp - firstTimeStamp).TotalHours
If it's LINQ to SQL, use
SqlMethods.DateDiffHour(firstTimeStamp, x.TimeStamp)
Perhaps something like this may work out for you:
DateTime myDateTime = new DateTime(DateTime.Parse(firstTimeStamp).AddHours(group.Key).Ticks);
Question specific to answer above:
...Date = new DateTime(DateTime.Parse(firstTimeStamp).AddHours(group.Key))...

How to Filter Nested Collection within Dictionary using LINQ C#

Here is my entity structure
Transaction Entity
TransactionID, CompanyID TransactionItems
1 10 List<TransactionItems>
2 10 List<TransactionItems>
3 20 List<TransactionItems>
TransactionItems Entity
TransactionItemID TransactionID Value Postive Negative
1 2 10 yes
2 2 20 yes
3 2 30 yes
4 3 100 yes
5 3 200 yes
I need to sum the values of Value based on positive or negative flag and then compute total like this
CompanyID Postive Negative
10 30 30
20 200 100
The function in Business layer is returning Dictionary<int, List<Transaction>> which is basically like this
Key Value
CompanyID, List<Transaction>
So far i have only achieved this
_Transaction.GetCompanyTransactions().
Select(_Company => new
{
Company = _Company.Key,
Positive =
Negative =
});
can any one help please
You just have to sum the value field from the list :
_Transaction.GetCompanyTransactions()
.Select(g => new {
CompanyId = g.Key,
Positive = g.Value.SelectMany(t => t.TransactionItems).Where(t => t.Positive).Sum(t => t.Value),
Negative = g.Value.SelectMany(t => t.TransactionItems).Where(t => t.Negative).Sum(t => t.Value)})
});
You need to Sum the values based on the Positive/Negative flag.
It is done in two steps: you need to Sum the inner items first, then Sum the inner sums.
_Transaction.GetCompanyTransactions()
.Select(g => new
{
Company = g.Key,
Positive = g.Value.Sum(t => t.TransactionItems.Where(x => x.Positive).Sum(x => x.Value)),
Negative = g.Value.Sum(t => t.TransactionItems.Where(x => x.Negative).Sum(x => x.Value)),
});

How to group a list of data into year, season, month, week, day?

I am dealing with big data of years.
The data model is quite simple:
public class ValueData
{
public DateTime TimeRecorded {get; set;}
public double ValueRecorded {get; set;}
}
After having a list of ValueData: List<ValueData> for years of data, I need to group the data based on: Year ==> contains data of 4 seasons: Season ==> A season contains 4 months ==> A month contains data of 4 weeks ==> A week contains data of 7 days based on the week calendar numbers of a year. Because I need to make a sum of data per year, per season, per month, per week and per day
How can I achieve this data classification? should I use LinQ?
I guess you are looking for something like conditional groups
but 2gbs of data will take a while to process.
I guess it's ok for a one time parse and save the results but if you need to run this often you'll need a more appropriate solution.
I believe you want something along the lines of the following query:
var groups = data.GroupBy(v => new {Year = v.TimeRecorded.Year,
Season = SeasonFromMonth(v.TimeRecorded.Month),
Month = v.TimeRecorded.Month,
Week = System.Globalization.CultureInfo.InvariantCulture.Calendar.GetWeekOfYear(v.TimeRecorded, System.Globalization.CalendarWeekRule.FirstDay, System.DayOfWeek.Monday),
Day = v.TimeRecorded.Day});
With a helper function SeasonFromMonth that accepts the integer month and returns some value (probably an enum) indicating the season.
You can then select relevant subgroups with queries like:
var fallValues = groups.Where(g => g.Key.Season == Seasons.Fall);
var decemberValues = groups.Where(g => g.Key.Month == 12);
var firstOfMonth = groups.Where(g => g.Key.Day == 1);
and so on, or flatten the groups into a single list by adding a SelectMany clause (although the SelectMany will throw away the key information):
groups.Where(g => g.Key.Season == Seasons.Fall).SelectMany(g => g);
groups.Where(g => g.Key.Month == 12).SelectMany(g => g);
groups.Where(g => g.Key.Day == 1).SelectMany(g => g);

LINQ lambda OrderByDescending on group by key.count() returning list of string of group by key for chart legend sorting

Trying to sort the legend of a pie chart in MVC 4 C#. The chart shows number of products sold by their item numbers. In the legend I want to display the item number, then number sold, then the percent sold and have this ordered by number sold descending.
I've gotten the "Y" values (# sold) to order correctly but in trying to order the "X" values they end up jumbled and don't match their proper "Y" values.
It appears to me like I should just have to group the data by item number, then select the data by anonymous type of item number and count of item number, then order by descending based on the item number count, then select again this time only taking a list string of the item numbers now in the correct order.
There is a reasonable chance the above is working fine and the problem more lies with my near complete lack of understanding of how the DataBindXY method works in charting.
Code:
var data = srv.GetTSMMetricData(Mode, Territories, selectedTerritory).GroupBy(x => x.ItemNumber).ToList();
var xValues = data.Select(x => x.Key).OrderByDescending(x => x.Count()).ToList();
mySeries.Points.DataBindXY(xValues, data.Select(x => x.Count()).OrderByDescending(x => x).ToList());
Any help or nudge in the right direction would be awesome. Let me know if there is anything else I can post up that might be missing.
Thanks.
Edit: Forgot I was trying to use anonymous type in previous version of the above code..
var xValues = data.Select(x => new { Item = x.Key, Count = x.Key.Count() }).OrderByDescending(x => x.Count).ToList();
mySeries.Points.DataBindXY(xValues.Select(x => x.Item).ToList(), data.Select(x => x.Count()).OrderByDescending(x => x).ToList());
Edit 2: Example of current result (top 5 legend entries)..
ItemNumber - # Sold - % Sold ----- Correct ItemNumber for # Sold
-------------------------------------------------------------
8500 42 (20.79)% PRS
JOL 19 (9.41)% JOL
280 18 (8.91)% ENV
171 13 (6.44)% AMR
172 12 (5.94)% GRD
Note: 38 rows of total results
The results have all the correct ItemNumbers for the query and all the # Sold but is not matching the ItemNumbers to the correct # Sold. I can re-write the code so that the ItemNumbers and # Sold match up but only order it by ItemNumber and I really want to order it by # Sold. Hopefully this makes it more clear what the problem is and what I'd like to see happen.
I think that by sorting twice you mix up the order of the keys and the values.
Make sure that you first create a list of pairs that is sorted correctly:
var data = srv.GetTSMMetricData(Mode, Territories, selectedTerritory)
.GroupBy(x => x.ItemNumber)
.Select( x => new
{
ItemNumber = x.Key,
Count = x.Count()
} )
.OrderByDescending(x => x.Count)
.ToList();
Then do the binding:
mySeries.Points.DataBindXY(data.Select(x => x.ItemNumber)
,data.Select(x => x.Count));
GroupBy returns an IEnumerable<IGrouping<TKey,TEntity>
To count the number of objects in each grouping:
srv.GetTSMMetricData(Mode, Territories, selectedTerritory)
.GroupBy(x => x.ItemNumber)
.Select( x => new
{
ItemNumber = x.Key,
Count = x.Count()
} );

Categories