Many thanks to those who answered and commented! Your answers helped me with a problem where I wanted long-term retention of descriptive statistics, but was not able to retain the original data.
My solution was to create rollup records that would have one row for each combination of desired dimensions of the data (one of which might be time). Depending on the cardinality of the dimensions, this can be a huge space savings. In this example, we have 100 rollup records for 1 million measurements, since the cardinality of each dimension is 10.
I thought I'd demonstrate this method in SQL (PostgreSQL), and perhaps others might find this useful. This is my first answer, so do be kind. I'm not sampling the data, so I use the population formulas that use the divisor of N.
First, we create some measurement records, then roll them up into summary rollup records. All we store is sum, count, and population variance for each combination.
stats=> \timing
Timing is on.
stats=> SELECT setseed(.5);
setseed
---------
(1 row)
Time: 0.580 ms
stats=> drop table if exists measurements ;
NOTICE: table "measurements" does not exist, skipping
DROP TABLE
Time: 0.450 ms
stats=> SELECT setseed(.5);
setseed
---------
(1 row)
Time: 0.497 ms
stats=> create table measurements as
SELECT generate_series(1,1000000) AS id,
floor(random() *10) AS dimension1,
floor(random() *10) AS dimension2,
random() * 10 AS value;
SELECT 1000000
Time: 776.785 ms
stats=> drop table if exists rollups;
NOTICE: table "rollups" does not exist, skipping
DROP TABLE
Time: 0.839 ms
stats=> create table rollups as
select dimension1, dimension2, count(1) as measurements,
sum(value) as value_sum, var_pop(value) as value_var_pop
from measurements
group by dimension1, dimension2;
SELECT 100
Time: 880.328 ms
Then we calculate the mean and population standard deviation, first from the rollup records, then the complete dataset. This uses a "Subquery" to calculate the population mean used in the calculation. Note that the query times from summary records are considerably faster and should be acceptable for real-time display.
stats=> SELECT
SUM (value_sum) / SUM (measurements) mean,
SQRT (
(
SUM (
ru.value_var_pop * ru.measurements
) + SUM (
ru.measurements * (
(ru.value_sum / ru.measurements) /* row avg */
- ( /* grand avg subquery */
SELECT
SUM (value_sum) / SUM (measurements) grand_avg
FROM
rollups ga
)
) ^ 2
)
) / SUM (ru.measurements)
) as stddev_pop
FROM
rollups ru;
mean | stddev_pop
------------------+------------------
4.99954057566761 | 2.88618225050068
(1 row)
Time: 1.255 ms
stats=> select avg(value), stddev_pop(value) from measurements;
avg | stddev_pop
------------------+------------------
4.99954057566761 | 2.88618225050069
(1 row)
Time: 71.719 ms
As a bonus, we calculate mean and standard deviations by "dimension1," first from the rollup records, then the complete dataset. This uses a "Correlated Subquery" to calculate the means for each dimension1 value.
stats=> SELECT dimension1,
SUM (value_sum) / SUM (measurements) mean,
SQRT (
(
SUM (
ru.value_var_pop * ru.measurements
) + SUM (
ru.measurements * (
(ru.value_sum / ru.measurements) /* row avg */
- ( /* grand avg subquery */
SELECT
SUM (value_sum) / SUM (measurements) grand_avg
FROM
rollups ga
WHERE ga.dimension1 = ru.dimension1 /* same (correlated) group as outer query */
)
) ^ 2
)
) / SUM (ru.measurements)
) as stddev_pop
FROM
rollups ru
GROUP BY
dimension1
order by 1 ;
dimension1 | mean | stddev_pop
------------+------------------+------------------
0 | 5.00667407841466 | 2.89025062220051
1 | 4.98845700180817 | 2.88379096643363
2 | 4.99899669999969 | 2.88825163824503
3 | 4.98553153843827 | 2.88365611510306
4 | 4.99844263698516 | 2.8860230080168
5 | 5.0179497778027 | 2.88749365173117
6 | 5.00517112927829 | 2.88346174527641
7 | 5.00532821601654 | 2.89266358278946
8 | 4.98688468436205 | 2.88383465545076
9 | 5.00219626955073 | 2.88222690226276
(10 rows)
Time: 3.828 ms
stats=> select dimension1, avg(value), stddev_pop(value) from measurements group by dimension1 order by 1;
dimension1 | avg | stddev_pop
------------+------------------+------------------
0 | 5.00667407841466 | 2.89025062220053
1 | 4.98845700180817 | 2.88379096643364
2 | 4.99899669999969 | 2.88825163824505
3 | 4.98553153843827 | 2.88365611510306
4 | 4.99844263698516 | 2.88602300801678
5 | 5.0179497778027 | 2.88749365173116
6 | 5.00517112927829 | 2.8834617452764
7 | 5.00532821601654 | 2.89266358278946
8 | 4.98688468436205 | 2.88383465545078
9 | 5.00219626955073 | 2.88222690226277
(10 rows)
Time: 113.342 ms