【问题标题】:Moving Average based on Timestamps in PostgreSQLPostgreSQL 中基于时间戳的移动平均线
【发布时间】:2012-11-28 21:41:20
【问题描述】:

我想通过时间戳执行移动平均。 我有两列:温度和时间戳(时间-日期),我想根据每 15 分钟的连续温度观测值执行移动平均值。换句话说,选择数据以基于 15 分钟的时间间隔执行平均。此外,对于不同的时间序列,可以有不同数量的观察。我的意思是所有窗口大小都相等(15 分钟),但每个窗口中可能有不同数量的观察值。 例如: 对于第一个窗口,我们必须计算 n 次观察的平均值,对于第二个窗口,我们必须计算 n+5 次观察的平均值。

数据样本:

ID时间戳温度 1 2007-09-14 22:56:12 5.39 2 2007-09-14 22:58:12 5.34 3 2007-09-14 23:00:12 5.16 4 2007-09-14 23:02:12 5.54 5 2007-09-14 23:04:12 5.30 6 2007-09-14 23:06:12 5.20 7 2007-09-14 23:10:12 5.39 8 2007-09-14 23:12:12 5.34 9 2007-09-14 23:20:12 5.16 10 2007-09-14 23:24:12 5.54 11 2007-09-14 23:30:12 5.30 12 2007-09-14 23:33:12 5.20 13 2007-09-14 23:40:12 5.39 14 2007-09-14 23:42:12 5.34 15 2007-09-14 23:44:12 5.16 16 2007-09-14 23:50:12 5.54 17 2007-09-14 23:52:12 5.30 18 2007-09-14 23:57:12 5.20

主要挑战:

我如何学习代码以每 15 分钟进行一次区分,而由于采样频率不同,没有准确的 15 分钟时间间隔。

【问题讨论】:

  • 如果新的 15 分钟窗口开始,滚动平均值是否会“重新开始”?还是应该计算“最后”15 分钟的平均值?
  • @a_horse_with_no_name,实际上,数据集包括 4 周的历史数据,我需要移动平均结果作为新数据集。
  • 这不能回答我的问题。

标签: sql postgresql timestamp moving-average


【解决方案1】:

你可以自己加入你的桌子:

select l1.id, avg( l2.Temperature )
from l l1
inner join l l2 
   on l2.id <= l1.id and
      l2.Timestamps + interval '15 minutes' > l1.Timestamps
group by l1.id
order by id
;

Results:

| ID |            AVG |
-----------------------
|  1 |           5.39 |
|  2 |          5.365 |
|  3 | 5.296666666667 |
|  4 |         5.3575 |
|  5 |          5.346 |
|  6 | 5.321666666667 |
|  7 | 5.331428571429 |

注意:只有“努力工作”。您应该将结果与原始表连接或附加新列进行查询。我不知道您需要的最终查询。调整此解决方案或寻求更多帮助。

【讨论】:

    【解决方案2】:

    假设您想在每 15 分钟间隔后重新开始滚动平均值:

    select id, 
           temp,
           avg(temp) over (partition by group_nr order by time_read) as rolling_avg
    from (       
      select id, 
             temp,
             time_read, 
             interval_group,
             id - row_number() over (partition by interval_group order by time_read) as group_nr
      from (
        select id, 
               time_read, 
               'epoch'::timestamp + '900 seconds'::interval * (extract(epoch from time_read)::int4 / 900) as interval_group,
               temp
        from readings
      ) t1
    ) t2
    order by time_read;
    

    它基于Depesz's solution按“时间范围”分组:

    这是一个 SQLFiddle 示例:http://sqlfiddle.com/#!1/0f3f0/2

    【讨论】:

    • 由于id - row_number() 被用作group_nr,我认为这仅在IDs 连续时才有效(即IDs 中必须没有间隙)
    【解决方案3】:

    这是一种利用该工具将聚合函数用作窗口函数的方法。聚合函数将最后 15 分钟的观察值与当前运行总数一起保存在一个数组中。状态转换函数将落后于 15 分钟窗口的元素从数组中移出,并推送最新的观察结果。最后一个函数简单地计算数组中的平均温度。

    现在,至于这是否是一个好处......这取决于。它侧重于postgresql的plgpsql-execution部分而不是database-access部分,我自己的经验是plpgsql并不快。如果您可以轻松地对表进行查找以查找每个观察的前 15 分钟的行,那么自联接(如@danihp 答案)会做得很好。然而,这种方法可以处理来自一些更复杂的来源的观察,这些查找是不切实际的。与以往一样,在您自己的系统上试用和比较。

    -- based on using this table definition
    create table observation(id int primary key, timestamps timestamp not null unique,
                             temperature numeric(5,2) not null);
    
    -- note that I'm reusing the table structure as a type for the state here
    create type rollavg_state as (memory observation[], total numeric(5,2));
    
    create function rollavg_func(state rollavg_state, next_in observation) returns rollavg_state immutable language plpgsql as $$
    declare
      cutoff timestamp;
      i int;
      updated_memory observation[];
    begin
      raise debug 'rollavg_func: state=%, next_in=%', state, next_in;
      cutoff := next_in.timestamps - '15 minutes'::interval;
      i := array_lower(state.memory, 1);
      raise debug 'cutoff is %', cutoff;
      while i <= array_upper(state.memory, 1) and state.memory[i].timestamps < cutoff loop
        raise debug 'shifting %', state.memory[i].timestamps;
        i := i + 1;
        state.total := state.total - state.memory[i].temperature;
      end loop;
      state.memory := array_append(state.memory[i:array_upper(state.memory, 1)], next_in);
      state.total := coalesce(state.total, 0) + next_in.temperature;
      return state;
    end
    $$;
    
    create function rollavg_output(state rollavg_state) returns float8 immutable language plpgsql as $$
    begin
      raise debug 'rollavg_output: state=% len=%', state, array_length(state.memory, 1);
      if array_length(state.memory, 1) > 0 then
        return state.total / array_length(state.memory, 1);
      else
        return null;
      end if;
    end
    $$;
    
    create aggregate rollavg(observation) (sfunc = rollavg_func, finalfunc = rollavg_output, stype = rollavg_state);
    
    -- referring to just a table name means a tuple value of the row as a whole, whose type is the table type
    -- the aggregate relies on inputs arriving in ascending timestamp order
    select rollavg(observation) over (order by timestamps) from observation;
    

    【讨论】:

      猜你喜欢
      • 2023-03-23
      • 1970-01-01
      • 2013-01-22
      • 2017-09-01
      • 1970-01-01
      • 2013-12-22
      • 1970-01-01
      • 1970-01-01
      • 2018-10-21
      相关资源
      最近更新 更多