【问题标题】:Moving average in SQLiteSQLite 中的移动平均线
【发布时间】:2018-01-28 15:53:29
【问题描述】:

我想计算 SQLite 表中数据的移动平均值。我在 MySQL 中找到了几种方法,但在 SQLite 中找不到有效的方法。

在 SQL 中,我认为应该这样做(但是,我无法尝试...):

SELECT date, value, 
avg(value) OVER (ORDER BY date ROWS BETWEEN 3 PRECEDING AND 3 FOLLOWING) as MovingAverageWindow7
FROM t ORDER BY date;

但是,我看到了两个缺点:

  • 这似乎不适用于 sqlite
  • 如果数据在前一行/后一行的几个日期内不连续,它会在一个比我实际想要的更宽的窗口上计算移动平均值,因为它仅基于周围的行数。因此,应添加日期条件

确实,我希望它计算每个日期的“价值”平均值,超过 +/-3 天(每周移动平均)或 +/-15 天(每月移动平均)

这是一个示例数据集:

CREATE TABLE t ( date DATE, value INTEGER );

INSERT INTO t (date, value) VALUES ('2018-02-01', 8);
INSERT INTO t (date, value) VALUES ('2018-02-02', 2);
INSERT INTO t (date, value) VALUES ('2018-02-05', 5);
INSERT INTO t (date, value) VALUES ('2018-02-06', 4);
INSERT INTO t (date, value) VALUES ('2018-02-07', 1);
INSERT INTO t (date, value) VALUES ('2018-02-10', 6);
INSERT INTO t (date, value) VALUES ('2018-02-11', 0);
INSERT INTO t (date, value) VALUES ('2018-02-12', 2);
INSERT INTO t (date, value) VALUES ('2018-02-13', 1);
INSERT INTO t (date, value) VALUES ('2018-02-14', 3);
INSERT INTO t (date, value) VALUES ('2018-02-15', 11);
INSERT INTO t (date, value) VALUES ('2018-02-18', 4);
INSERT INTO t (date, value) VALUES ('2018-02-20', 1);
INSERT INTO t (date, value) VALUES ('2018-02-21', 5);
INSERT INTO t (date, value) VALUES ('2018-02-28', 10);
INSERT INTO t (date, value) VALUES ('2018-03-02', 6);
INSERT INTO t (date, value) VALUES ('2018-03-03', 7);
INSERT INTO t (date, value) VALUES ('2018-03-04', 3);
INSERT INTO t (date, value) VALUES ('2018-03-08', 5);
INSERT INTO t (date, value) VALUES ('2018-03-09', 6);
INSERT INTO t (date, value) VALUES ('2018-03-15', 1);
INSERT INTO t (date, value) VALUES ('2018-03-16', 3);
INSERT INTO t (date, value) VALUES ('2018-03-25', 5);
INSERT INTO t (date, value) VALUES ('2018-03-31', 1);

【问题讨论】:

  • 这些 窗口函数 (... OVER (...)) 可能不起作用,因为它们是在 SQLite 3.25 中引入的。我的版本(Ubuntu 18.04 附带)是 3.22,所以它对我也不起作用。 :-(

标签: sqlite moving-average


【解决方案1】:

在版本 3.25.0 (2018-09-15) 中添加了窗口函数。在版本 3.28.0 (2019-04-16) 中添加 RANGE 帧类型后,您现在可以:

SELECT date, value, 
avg(value) OVER (
    ORDER BY CAST (strftime('%s', date) AS INT)
    RANGE BETWEEN 3 * 24 * 60 * 60 PRECEDING
        AND 3 * 24 * 60 * 60 FOLLOWING
) AS MovingAverageWindow7
FROM t ORDER BY date;

【讨论】:

    【解决方案2】:

    我想我确实找到了解决方案:

    SELECT date, value, 
      (SELECT AVG(value) FROM t t2 
       WHERE datetime(t1.date, '-3 days') <= datetime(t2.date) AND datetime(t1.date, '+3 days') >= datetime(t2.date)
       ) AS MAVG
    FROM t t1
    GROUP BY strftime('%Y-%m-%d', date); 
    

    我不知道这是否是最有效的方法,但它似乎有效

    编辑: 应用于包含 20 000 行的真实数据库,计算两个参数的每周移动平均值大约需要 1 分钟。

    我在那里看到两个选项:

    • 使用 SQLite 有一种更有效的计算方法
    • 从 SQLite 提取数据后,我在 Python 中计算移动平均值

    【讨论】:

      【解决方案3】:

      一种方法是创建一个中间表,将每个日期映射到它所属的组。

      CREATE TABLE groups (date DATE, daygroup DATE);
      INSERT INTO groups 
        SELECT date, strftime('%Y-%m-%d', datetime(date, '-1 days')) AS daygroup
        FROM t;  
      INSERT INTO groups 
        SELECT date, strftime('%Y-%m-%d', datetime(date, '-2 days')) AS daygroup
        FROM t;  
      INSERT INTO groups 
        SELECT date, strftime('%Y-%m-%d', datetime(date, '-3 days')) AS daygroup
        FROM t;  
      INSERT INTO groups 
        SELECT date, strftime('%Y-%m-%d', datetime(date, '+1 days')) AS daygroup
        FROM t;  
      INSERT INTO groups 
        SELECT date, strftime('%Y-%m-%d', datetime(date, '+2 days')) AS daygroup
        FROM t;  
      INSERT INTO groups 
        SELECT date, strftime('%Y-%m-%d', datetime(date, '+3 days')) AS daygroup
        FROM t;  
      INSERT INTO groups 
        SELECT date, date AS daygroup FROM t;
      

      例如,

      SELECT * FROM groups WHERE date = '2018-02-05'
      
          date        daygroup
          2018-02-05  2018-02-04
          2018-02-05  2018-02-03
          2018-02-05  2018-02-02
          2018-02-05  2018-02-06
          2018-02-05  2018-02-07
          2018-02-05  2018-02-08
          2018-02-05  2018-02-05
      

      表示“2018-02-05”属于“2018-02-02”到“2018-02-08”组。如果某个日期属于某个组,则该数据的值会加入该组的移动平均值的计算中。

      这样,计算移动平均线就变得简单了:

      SELECT
        d.date, d.value, c.ma
      FROM
        t AS d
      INNER JOIN 
        (SELECT 
          b.daygroup,
          avg(a.value) AS ma
        FROM 
          t AS a 
        INNER JOIN
          groups AS b
        ON a.date = b.date
        GROUP BY b.daygroup) AS c
      ON
        d.date = c.daygroup
      

      注意中间表的行数是原表的7倍,随着窗口的扩大,它按比例增长。除非您有更大的桌子,否则这应该是可以接受的。

      我还试验了 20 000 行。 在我的笔记本电脑上,插入查询耗时 1.5 秒,选择查询耗时 0.5 秒。

      添加,也许更好。

      不需要中间表的替代方案。 下面的查询将表与自身合并,允许滞后 3 天,然后取平均值。

      SELECT
        t1.date, avg(t2.value) AS MVG
      FROM 
        t AS t1
      INNER JOIN
        t AS t2
      ON
        datetime(t1.date, '-3 days') <= datetime(t2.date) 
        AND 
        datetime(t1.date, '+3 days') >= datetime(t2.date)
      GROUP BY
        t1.date
      ;
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-09-01
        • 2013-12-22
        • 2020-01-06
        • 2022-01-26
        • 2017-03-19
        • 2013-01-22
        • 1970-01-01
        • 2020-05-19
        相关资源
        最近更新 更多