通过 fill-forward/LOCF 在 SQL 中的一系列连续行上估算一列?
Impute via fill-forward/LOCF a column over a range of sequential rows in SQL?
在为时间序列分析格式化数据时,一个常见的需求是通过随时间向前填充值(也称为 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 风格的 SQL 方言(例如 Netezza PureData)作为日期时间索引(假设过去的数据),则以下查询结构将实现前向填充。它也适用于多列 index/keys.
给定以下参数:
<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
这是上述查询的 SQL小提琴:http://sqlfiddle.com/#!15/d589b/1
对于某些数据库,例如 Postgres,您可以定义自己的聚合函数。
LOCF 只是一个 运行 合并。
CREATE OR REPLACE FUNCTION locf_state( FLOAT, FLOAT )
RETURNS FLOAT
LANGUAGE SQL
AS $f$
SELECT COALESCE(,)
$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
在为时间序列分析格式化数据时,一个常见的需求是通过随时间向前填充值(也称为 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 风格的 SQL 方言(例如 Netezza PureData)作为日期时间索引(假设过去的数据),则以下查询结构将实现前向填充。它也适用于多列 index/keys.
给定以下参数:
<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
这是上述查询的 SQL小提琴:http://sqlfiddle.com/#!15/d589b/1
对于某些数据库,例如 Postgres,您可以定义自己的聚合函数。 LOCF 只是一个 运行 合并。
CREATE OR REPLACE FUNCTION locf_state( FLOAT, FLOAT )
RETURNS FLOAT
LANGUAGE SQL
AS $f$
SELECT COALESCE(,)
$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