通过填充前向/LOCF 在 SQL 中的一系列连续行上计算一列?

Posted

技术标签:

【中文标题】通过填充前向/LOCF 在 SQL 中的一系列连续行上计算一列?【英文标题】:Impute via fill-forward/LOCF a column over a range of sequential rows in SQL? 【发布时间】:2015-06-19 04:24:17 【问题描述】:

在为时间序列分析格式化数据时,一个常见的需求是通过随时间推移向前填充值来估算缺失值(也称为 Last-Observation-Carried-Forward / LOCF)。

虽然数据分析环境通常提供该功能(例如 Pandas fillna()),但对于较大的数据集,在 SQL 中计算它可能更有效(例如利用数据并行数据仓库设备)。

例如,考虑:

    | UNIT | TIME | VALUE |
    |------|------|-------|
    | 1    | 1    | NULL  |
    | 1    | 2    | .5    |
    | 1    | 3    | NULL  |
    | 1    | 4    | NULL  |
    | 1    | 5    | .2    |
    | 1    | 6    | NULL  |
    | 2    | 1    | .6    |
    | 2    | 2    | NULL  |

在 TIME 前填充 VALUE 列后(独立于每个 UNIT)产生:

    | UNIT | TIME | VALUE |
    |------|------|-------|
    | 1    | 1    | NULL  |
    | 1    | 2    | .5    |
    | 1    | 3    | .5    |
    | 1    | 4    | .5    |
    | 1    | 5    | .2    |
    | 1    | 6    | .2    |
    | 2    | 1    | .6    |
    | 2    | 2    | .6    |

(请注意,UNIT 1 的初始 NULL 无法估算,因为没有先验值)

时间也可以是时间戳或日期时间类型的列。

【问题讨论】:

关于如何在 PostgreSQL 上线性插值/插补数值数据:joyofdata.de/blog/locf-linear-imputation-postgresql-tutorial 【参考方案1】:

如果使用 PostgreSQL 风格的 SQL 方言(例如 Netezza PureData)作为日期时间索引(假设过去的数据),则以下查询结构将实现前向填充。它也适用于多列索引/键。

给定以下参数:

<key_cols> - 唯一标识每个时间序列样本的列列表(例如 UNIT, TIME<impute_col> - 需要在其中估算值的列(例如 VALUE<impute_over_range_col> - 时间序列的连续范围列(例如TIME

和推导:

<keys_no_range> - 除了<impute_over_range_col> 之外的键列
SELECT DISTINCT T1.<key_cols>, 
                COALESCE(T1.<impute_col>, T2.<impute_col>) AS <impute_col>
FROM table T1
LEFT OUTER JOIN (SELECT T1.<key_cols>,
                     T1.<impute_col>,
                     LEAD(T1.<impute_over_range_col>,1) 
                         OVER (PARTITION BY T1.<keys_no_range> 
                               ORDER BY T1.<key_cols>)
                         AS NEXT_RANGE
                     FROM table T1
                     WHERE T1.<impute_col> IS NOT NULL
                     ORDER BY T1.<key_cols>
                ) T2
              ON (T1.<impute_over_range_col> BETWEEN T2.<impute_over_range_col> 
                                             AND COALESCE(NEXT_RANGE, CURRENT_DATE)) 
              AND T1.<keys_no_range>[0] = T2.<keys_no_range>[0]
              AND T1.<keys_no_range>[1] = T2.<keys_no_range>[1]
              -- ... for each col in <keys_no_range>

具体来说,问题中的例子:

SELECT DISTINCT T1.UNIT, T1.TIME, 
                COALESCE(T1.VALUE, T2.VALUE) AS VALUE
FROM table T1
LEFT OUTER JOIN (SELECT T1.UNIT, T1.TIME,
                     T1.VALUE,
                     LEAD(T1.TIME,1) 
                         OVER (PARTITION BY T1.UNIT 
                               ORDER BY T1.UNIT, T1.TIME)
                         AS NEXT_RANGE
                     FROM table T1
                     WHERE T1.VALUE IS NOT NULL
                     ORDER BY T1.UNIT, T1.TIME
                ) T2
              ON (T1.TIME BETWEEN T2.TIME
                           AND COALESCE(NEXT_RANGE, CURRENT_DATE)) 
              AND T1.UNIT = T2.UNIT

这是上述查询的 SQLFiddle:http://sqlfiddle.com/#!15/d589b/1

【讨论】:

【参考方案2】:

对于某些数据库,例如 Postgres,您可以定义自己的聚合函数。 LOCF 只是一个运行中的 COALESCE。

CREATE OR REPLACE FUNCTION locf_state( FLOAT, FLOAT )
RETURNS FLOAT
LANGUAGE SQL
AS $f$
  SELECT COALESCE($2,$1)
$f$;

CREATE AGGREGATE locf(FLOAT) (
  SFUNC = locf_state,
  STYPE = FLOAT
);

这样查询就更易读了:

SELECT unit, time, 
       locf(value) OVER( PARTITION BY unit ORDER BY time )
FROM   mytable;

SQLFiddle:http://sqlfiddle.com/#!15/2c73b/1/0

【讨论】:

我还有其他问题。如何在 mssql 上“创建聚合 locf”

以上是关于通过填充前向/LOCF 在 SQL 中的一系列连续行上计算一列?的主要内容,如果未能解决你的问题,请参考以下文章

当窗口/分区使用前向填充时,向 pyspark sql 中的 last() 函数添加条件

网格中的填充点 - 前向欧拉算法 - 输出错误

SQL 更新语句为每个不同的表记录子集填充数字系列

使用非连续范围的值通过RowSource属性填充UserForm Combobox

如何用特定值填充列中的一系列单元格?

如何在 pandas DataFrame 中以衰减速率前向填充 NaN 值