【问题标题】:How do I use LINQ to get the Average Value over a Date Range如何使用 LINQ 获取日期范围内的平均值
【发布时间】:2013-10-29 08:04:33
【问题描述】:

我正在尝试确定是否可以在 LINQ to Objects 语句中完成以下操作。

我有一个字典,键为 DateTime(键是多天的值)和双精度值。我有太多数据要绘制在图表上,所以想要每 5 分钟的平均值。

示例输入

01/01/2012 23:53    5
01/01/2012 23:54    2
01/01/2012 23:55    1
01/01/2012 23:56    3
01/01/2012 23:57    4
01/01/2012 23:58    5
01/01/2012 23:59    6
02/01/2012 00:00    2
02/01/2012 00:01    4
02/01/2012 00:02    5

预期输出

01/01/2012 23:55    3
02/01/2012 00:00    4.4

【问题讨论】:

  • 您使用的是哪种LINQ
  • key as a DateTime (spanning multiple days) 嗯? DateTime 如何跨越多天?
  • 对不起,每个值都只是一个日期时间,但关键不是一天的一组时间。
  • 使用 LINQ to Objects - 我已经更新了问题
  • 试着在镜子前说三遍 Jon Skeet,也许他会出现并给出答案。

标签: c# linq


【解决方案1】:

使用这个helper 方法:

static DateTime RoundToNearestInterval(DateTime dt, TimeSpan d)
{
   int f=0;
   double m = (double)(dt.Ticks % d.Ticks) / d.Ticks;
   if (m >= 0.5)
       f=1;            
   return new DateTime(((dt.Ticks/ d.Ticks)+f) * d.Ticks);
}

就这么简单

var result = from kvp in data
             let key = RoundToNearestInterval(kvp.Key, TimeSpan.FromMinutes(5))
             group kvp by key into g
             select new { g.Key, Value = g.Average(x => x.Value) };

var result = data.GroupBy(kvp => RoundToNearestInterval(kvp.Key, TimeSpan.FromMinutes(5)), kvp => kvp.Value)
                 .Select(g => new { g.Key, Value = g.Average() });

LINQPad 示例:

void Main()
{
    var tmp = new Dictionary<string, int>
    {
        {"01/01/2012 23:53", 5},
        {"01/01/2012 23:54", 2},
        {"01/01/2012 23:55", 1},
        {"01/01/2012 23:56", 3},
        {"01/01/2012 23:57", 4},
        {"01/01/2012 23:58", 5},
        {"01/01/2012 23:59", 6},
        {"02/01/2012 00:00", 2},
        {"02/01/2012 00:01", 4},
        {"02/01/2012 00:02", 5}
    };
    var data = tmp.ToDictionary(d => DateTime.Parse(d.Key), d=>d.Value);

    var result = from kvp in data
                 let key = RoundToNearestInterval(kvp.Key, TimeSpan.FromMinutes(5))
                 group kvp by key into g
                 select new {g.Key, Value = g.Average (x => x.Value) };

    result.ToDictionary(r => r.Key, v => v.Value).Dump();
}

【讨论】:

    【解决方案2】:

    这是一个 LINQ 查询,可以满足您的需求,您可以在 LINQPad 中进行测试:

    void Main()
    {
        var points = new[]
        {
            new { dt = new DateTime(2012, 1, 1, 23, 53, 00), value = 5 },
            new { dt = new DateTime(2012, 1, 1, 23, 54, 00), value = 2 },
            new { dt = new DateTime(2012, 1, 1, 23, 55, 00), value = 1 },
            new { dt = new DateTime(2012, 1, 1, 23, 56, 00), value = 3 },
            new { dt = new DateTime(2012, 1, 1, 23, 57, 00), value = 4 },
            new { dt = new DateTime(2012, 1, 1, 23, 58, 00), value = 5 },
            new { dt = new DateTime(2012, 1, 1, 23, 59, 00), value = 6 },
            new { dt = new DateTime(2012, 1, 2, 00, 00, 00), value = 2 },
            new { dt = new DateTime(2012, 1, 2, 00, 01, 00), value = 4 },
            new { dt = new DateTime(2012, 1, 2, 00, 01, 00), value = 5 }
        };
    
        var interval = TimeSpan.FromMinutes(5);
        var averageByInterval =
            from point in points
            let intervalStart = new DateTime(((int)((point.dt.Ticks + interval.Ticks / 2) / interval.Ticks)) * interval.Ticks)
            group point.value by intervalStart into g
            select new { g.Key, average = g.Average() };
        averageByInterval.Dump();
    }
    

    输出:

    【讨论】:

      【解决方案3】:

      看起来您的 dictionary 包含 ordered 元素,因此我们可以执行以下操作:

      var firstDate = yourDict.First().Key;
      var output = yourDict.GroupBy(e=> (int)(e.Key - firstDate).TotalMinutes / 5)
                           .ToDictionary(g => g.First().Key
                                             .AddMinutes(g.Average(e=>(e.Key - g.First().Key).TotalMinutes)),
                                         g => g.Average(e=>e.Value));
      

      注意:OP的输入数据使用与en-US不同的cutlure,月份在一天之后。这是进行一些测试的值得注意的一点。否则测试将不正确。

      【讨论】:

        【解决方案4】:

        试试这个:

        var results = 
            data
                .GroupBy(
                    x => (x.Key.Ticks / TimeSpan.TicksPerMinute + 2) / 5,
                    x => x.Value)
                .Select(x => new
                {
                    Key = new DateTime(x.Key * TimeSpan.TicksPerMinute * 5),
                    Value = x.Average()
                });
        

        【讨论】:

        • 与预期输出不匹配。输出应该是{{01/01/2012 23:55, 3}, {02/01/2012 00:00, 4.4}}
        • 四舍五入到最接近的 5 分钟让我震惊。我已经确定了答案。
        【解决方案5】:
          var data = new Dictionary<DateTime, double>();
        
          data.Add(new DateTime(2012, 1, 1, 23, 53, 0), 5);
          data.Add(new DateTime(2012, 1, 1, 23, 54, 0), 2);
          data.Add(new DateTime(2012, 1, 1, 23, 55, 0), 1);
          data.Add(new DateTime(2012, 1, 1, 23, 56, 0), 3);
          data.Add(new DateTime(2012, 1, 1, 23, 57, 0), 4);
          data.Add(new DateTime(2012, 1, 1, 23, 58, 0), 5);
          data.Add(new DateTime(2012, 1, 1, 23, 59, 0), 6);
          data.Add(new DateTime(2012, 1, 2, 0, 0, 0), 2);
          data.Add(new DateTime(2012, 1, 2, 0, 1, 0), 4);
          data.Add(new DateTime(2012, 1, 2, 0, 2, 0), 5);
        
          var result = data.GroupBy(kvp =>
          {
            var dt = kvp.Key;
            var nearest5 = (int)Math.Round(dt.Minute / 5.0) * 5;
            //Add the minutes after inital date creation to deal with minutes=60
            return new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, 0, 0).AddMinutes(nearest5);
          })
          .Select(g =>
          {
            return new KeyValuePair<DateTime, double>(g.Key, g.Average(row => row.Value));
          });
        
          foreach (var r in result)
          {
            Console.WriteLine(r.Key + " " + r.Value);
            //  1/01/2012 11:55:00 PM 3
            //  2/01/2012 12:00:00 AM 4.4
        
          }
        

        【讨论】:

        • 这可行,但为什么不直接简化为data.GroupBy(dt =&gt; Math.Round(new TimeSpan(dt.Key.Ticks).TotalMinutes / 5.0) * 5) .Select(g =&gt; new { Key = new DateTime().AddMinutes(g.Key), Value = g.Average(row =&gt; row.Value)})
        • 反正之前的工作方式有问题,所以还是稍微改了一下
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-06-03
        相关资源
        最近更新 更多