How about the following short "manual calculation"?

def weighted_avg_and_std(values, weights):
    """
    Return the weighted average and standard deviation.

    They weights are in effect first normalized so that they 
    sum to 1 (and so they must not all be 0).

    values, weights -- NumPy ndarrays with the same shape.
    """
    average = numpy.average(values, weights=weights)
    # Fast and numerically precise:
    variance = numpy.average((values-average)**2, weights=weights)
    return (average, math.sqrt(variance))

    
Answer from Eric O. Lebigot on Stack Overflow
Top answer
1 of 2
2

Interestingly, there is no single equation for a weighted standard error. Multiple versions have been proposed in the literature though. See for example:

Donald F. Gatz and Luther Smith (1995). "The Standard Error of a Weighted Mean Concentration - I: Bootstrapping Vs Other Methods". In: Atmospheric Environment 29.11, pp. 1185-1193

I implemented some of these method in R to use as an (unexported) function in the adjustedCurves R-package I developed, here is the code:

weighted.se <- function(x, w, se_method, na.rm=FALSE) {

  if (na.rm) {
    miss_ind <- !is.na(x)
    w <- w[miss_ind]
    x <- x[miss_ind]
  }

  n <- length(x)
  mean_Xw <- stats::weighted.mean(x=x, w=w, na.rm=na.rm)

  ## Miller (1977)
  if (se_method=="miller") {
    se <- 1/n * (1/sum(w)) * sum(w * (x - mean_Xw)^2)
  ## Galloway et al. (1984)
  } else if (se_method=="galloway") {
    se <- (n/(sum(w)^2)) * ((n*sum(w^2 * x^2) - sum(w*x)^2) / (n*(n-1)))
  ## Cochrane (1977)
  } else if (se_method=="cochrane") {
    mean_W <- mean(w)
    se <- (n/((n-1)*sum(w)^2))*(sum((w*x - mean_W*mean_Xw)^2)
                                - 2*mean_Xw*sum((w-mean_W)*(w*x-mean_W*mean_Xw))
                                + mean_Xw^2*sum((w-mean_W)^2))
  ## As implemented in Hmisc
  } else if (se_method=="Hmisc") {
    se <- (sum(w * (x - mean_Xw)^2) / (sum(w) - 1)) / n
  }
  return(sqrt(se))
}

where x is your vector of interest, w is a vector of weights with equal length, se_method specifies which method to use and na.rm specifies whether to remove missing values before performing calculations.

I understand that this doesn't fully answer your questions, but it might still be helpful to you.

2 of 2
1

I have kind of figured out a (almost) correct answer to my question so I will post it here and leave room for others to weigh in to improve it.

Answer to the first question

Apparently, there is no consensus as to the definition of the standard error of the weighted mean. Even different statistical softwares use different definitions. However, the most coherent answer that I keep seeing is this for an unbiased estimation of the standard error on a weighted mean:

where the is the unbiased estimator of the standard deviation of the random variable and is the sum of the individual weights that contribute to the unbiased estimation of . The following link is a statistical note that compares how it is computed in SPSS vs WinCross and SPSS uses the sum of weights as the denominator (which happens to be almost the same as the sample size in their example). So in the example I provided in my question, the sum of weights is .

Answer to the second question

I came up with the following formulas for recursive computation of the weighted mean, weighted standard deviation and the standard error on the weighted mean:

Given that the current known data points are and the next data point that triggers the update is denoted as , we can express the weighted stats like so:

Recursive weighted mean:

Recusrive weighted standard deviation

Standard error of the weighted mean

$$ se_w = \frac{s_{w,n+1}}{\sqrt{\sum_{i=1}^{n} w_i + w_{n+1}}} $$

Python's statsmodels implemented a class that computes all sorts of weighted statistics including the standard deviation and standard error (method under the name std_mean here in their source code. As we can see from their implementation, their unbiased estimator of the standard error with degres of freedom parameter set to is the formula that I wrote above. This answers my first question as to what should I take as a denominator when computing the unbiased estimation of the standard error on my weighted mean.

Using Python I was able to verify the implementation of the above estimators using recursive definitions vs statsmodels's weighted stats function knowing the full history of data like so:

import numpy as np
from statsmodels.stats.weightstats import DescrStatsW

def update_weighted_mean_se(current_sum_weights, current_weighted_avg, current_weighted_std, new_weight, new_x):
    ''' 
    Update the weighted statistics (mean, weighted standard deviation and weighted standard error) given the previous 
    sum of weights, previous weighted mean, previous weighted standard deviation, new weight, and new x value. 
    '''
    # new weighted mean and weighted standard deviation recursively 
    new_sum_weights = current_sum_weights + new_weight
    new_weighted_avg = (current_sum_weights*current_weighted_avg + new_weight*new_x) / new_sum_weights
    new_weighted_std = np.sqrt((current_sum_weights*(current_weighted_std**2 + (current_weighted_avg-new_weighted_avg)**2) + new_weight*(new_x-new_weighted_avg)**2)/new_sum_weights)

    # new standard error on the weighted mean
    se_w = new_weighted_std / np.sqrt(new_sum_weights)
    return new_weighted_avg, new_weighted_std, se_w

# define the x measurements and their weights
x = np.array([10, 12, 15.2, 12.5, 11])
w = np.array([100, 120, 108, 80, 98])

# calculate the unbiased estimators of avg, std and se (with ddof=1)
sum_w = np.sum(w)
avg_w = np.sum(w * x) / sum_w
std_w = np.sqrt(np.sum(w*(x-avg_w)**2) / (sum_w-1))
se_w = std_w / np.sqrt(sum_w)

# add new values and compute weighted stats iteratively
new_x_array = np.array([20, 30])
new_weights_array = np.array([200, 150])
for new_x, new_w in zip(new_x_array, new_weights_array):
    avg_w, std_w, se_w = update_weighted_mean_se(sum_w, avg_w, std_w, new_w, new_x)
    sum_w+=new_w

# verify new weighted stats using the formula (with ddof=1) 
weighted_stats = DescrStatsW(np.concatenate([x, new_x_array]), weights=np.concatenate([w, new_weights_array]), ddof=1)

print('iterative weighted avg = %0.5f' %avg_w)
print('iterative weighted std = %0.5f' %std_w)
print('iterative weighted se = %0.5f' %se_w)
print('statsmodels weighted avg = %0.5f' %weighted_stats.mean)
print('statsmodels weighted std = %0.5f' %weighted_stats.std)
print('statsmodels weighted se = %0.5f' %weighted_stats.std_mean)

>>> OUTPUT:
iterative weighted avg = 17.12570
iterative weighted std = 6.88164
iterative weighted se = 0.23521
statsmodels weighted avg = 17.12570
statsmodels weighted std = 6.88539
statsmodels weighted se = 0.23534

My implementation yields the correct weighted average but the standard deviation (and by extention the standard error) are only accurate up to or decimal points. This means that my implementation of the standard deviation is not exactly the same as statsmodel's and there is room for improvement. I wonder if this is just a matter of numerical precision.

🌐
YouTube
youtube.com › how to fix your computer
PYTHON : Weighted standard deviation in NumPy - YouTube
PYTHON : Weighted standard deviation in NumPy [ Gift : Animated Search Engine : https://www.hows.tech/p/recommended.html ] PYTHON : Weighted standard deviat...
Published: December 7, 2021
Views: 97
🌐
Statology
statology.org › home › how to calculate weighted standard deviation in python
How to Calculate Weighted Standard Deviation in Python
November 29, 2021 - The easiest way to calculate a weighted standard deviation in Python is to use the DescrStatsW() function from the statsmodels package:
Top answer
1 of 5
128
How about the following short "manual calculation"? · def weighted_avg_and_std(values, weights): · """ · Return the weighted average and standard deviation. · values, weights -- Numpy ndarrays with the same shape. · """ · average = numpy.average(values, weights=weights) · # Fast and numerically precise: · variance = numpy.average((values-average)**2, weights=weights) · return (average, math.sqrt(variance))
2 of 5
40
There is a class in statsmodels that makes it easy to calculate weighted statistics: statsmodels.stats.weightstats.DescrStatsW. · Assuming this dataset and weights: · import numpy as np · from statsmodels.stats.weightstats import DescrStatsW · array = np.array([1,2,1,2,1,2,1,3]) · weights = np.ones_like(array) · weights[3] = 100 · You initialize the class (note that you have to pass in the correction factor, the delta degrees of freedom at this point): · weighted_stats = DescrStatsW(array, weights=weights, ddof=0) · Then you can calculate: · .mean the weighted mean: · >>> weighted_stats.mean 1.97196261682243 · .std the weighted standard deviation: · >>> weighted_stats.std 0.21434289609681711 · .var the weighted variance: · >>> weighted_stats.var 0.045942877107170932 · .std_mean the standard error of weighted mean: · >>> weighted_stats.std_mean 0.020818822467555047 · Just in case you're interested in the relation between the standard error and the standard deviation: The standard error is (for ddof == 0) calculated as the weighted standard deviation divided by the square root of the sum of the weights minus 1 (corresponding source for statsmodels version 0.9 on GitHub): · standard_error = standard_deviation / sqrt(sum(weights) - 1)
🌐
Statology
statology.org › home › how to compute standard deviation and variance with numpy
How to Compute Standard Deviation and Variance with NumPy
November 16, 2024 - When calculating standard deviation and variance, we need to specify whether we’re working with a population or a sample. Here’s why it matters: Population: We have data for every possible observation · Sample: We only have data for a subset of possible observations · The ddof parameter (delta degrees of freedom) helps account for this difference: ... import numpy as np # Sample data data = np.array([10, 12, 14, 15, 16, 16, 18, 20]) # Population calculations (ddof=0) pop_std = np.std(data, ddof=0) pop_var = np.var(data, ddof=0) # Sample calculations (ddof=1) sample_std = np.std(data, ddof=1) sample_var = np.var(data, ddof=1) print("Population Statistics:") print(f"Standard Deviation: {pop_std:.2f}") print(f"Variance: {pop_var:.2f}\n") print("Sample Statistics:") print(f"Standard Deviation: {sample_std:.2f}") print(f"Variance: {sample_var:.2f}")
🌐
statsmodels
statsmodels.org › dev › generated › statsmodels.stats.weightstats.DescrStatsW.html
statsmodels.stats.weightstats.DescrStatsW - statsmodels 0.15.0 (+989)
This is essentially the same as replicating each observations by its weight, if the weights are integers, often called case or frequency weights. ... default ddof=0, degrees of freedom correction used for second moments, var, std, cov, corrcoef. However, statistical tests are independent of ddof, based on the standard formulas. ... >>> import numpy as np >>> np.random.seed(0) >>> x1_2d = 1.0 + np.random.randn(20, 3) >>> w1 = np.random.randint(1, 4, 20) >>> d1 = DescrStatsW(x1_2d, weights=w1) >>> d1.mean array([ 1.42739844, 1.23174284, 1.083753 ]) >>> d1.var array([ 0.94855633, 0.52074626, 1.12309325]) >>> d1.std_mean array([ 0.14682676, 0.10878944, 0.15976497])
🌐
Franksaundersjr
franksaundersjr.com › 2023 › 01 › 06 › how-to-calculate-weighted-mean-and-weighted-standard-deviation-with-python
How to Calculate Weighted Mean and Weighted Standard Deviation with Python – Analyses by Frank Saunders Jr
January 6, 2023 - The function takes the items in ... deviation equation, which we then divide by the ((length of weights minus 1) times the sum of the weights) divided by the length of the weights....
Top answer
1 of 1
1

You have multiple recursive (in the mathematical relation sense, not the computer science sense) expressions, notably on momentum, mean and variance. There is some minor vectorisation that can be done. I have tested this suggested code for correctness according to your provided examples. I've shown it to pull apart and illustrate what can be vectorised and what can't. I've also found accumulate to be a pain, because it seems to ignore the identity and dtype parameters.

Lo, there is hope: despite this looking as ugly as heck, in my testing it offers a ~180% speedup.

Suggested code

Docstrings omitted for brevity.

from timeit import timeit

import pandas as pd
import seaborn as sns
import numpy as np
from matplotlib import pyplot as plt
from numpy.random import default_rng


def ewm_old(values, time_steps, weights, decay_rate=0.01, variance_epsilon=1e-6):
    momentum = 0.0
    mean = 0.0
    variance = 0.0

    means = np.empty_like(values)
    stds = np.empty_like(values)
    for i in range(values.shape[0]):
        retention = (1.0 - decay_rate) ** time_steps[i]

        new_momentum = momentum * retention + weights[i]

        means[i] = mean = \
            mean * (momentum / new_momentum) * retention + \
            values[i] * weights[i] / new_momentum

        deviation = values[i] - mean

        variance = \
            variance * (momentum / new_momentum) * retention + \
            np.square(deviation) * weights[i] / new_momentum

        momentum = new_momentum

        stds[i] = np.sqrt(variance + variance_epsilon)

    return means, stds


def ewm_new(
    values: np.ndarray,
    time_steps: np.ndarray,
    weights: np.ndarray,
    decay_rate: float = 0.01,
    variance_epsilon: float = 1e-6,
) -> tuple[
    np.ndarray,
    np.ndarray,
]:
    def make_momentum(momentum: float, i: int) -> float:
        return momentum * retention[i-1] + weights[i-1]
    def make_means(mean: float, i: int) -> float:
        return mean*coefficients[i-1] + values[i-1]*offsets[i-1]
    def make_variances(variance: float, i: int) -> float:
        return variance*coefficients[i-1] + deviations[i-1]*offsets[i-1]

    retention = (1 - decay_rate)**time_steps
    momenta = np.frompyfunc(
        make_momentum, nin=2, nout=1, identity=0,
    ).accumulate(np.arange(len(values))).astype(np.float64)

    factors = momenta * retention / weights
    coefficients = factors / (factors + 1)
    offsets = 1 / (1 + factors)
    means = np.frompyfunc(
        make_means, nin=2, nout=1, identity=0,
    ).accumulate(np.arange(1+len(values)))[1:].astype(np.float64)

    deviations = (values - means)**2
    variances = np.frompyfunc(
        make_variances, nin=2, nout=1, identity=0,
    ).accumulate(np.arange(1+len(values)))[1:].astype(np.float64)

    stds = np.sqrt(variances + variance_epsilon)
    return means, stds


def test() -> None:
    rand = default_rng(seed=0)
    values, time_steps, weights = rand.random((3, 10))
    means, stds = ewm_new(values, time_steps, weights)

    assert np.allclose(
        means,
        np.array((
            0.63696169, 0.33792459, 0.09563421, 0.06077860, 0.28410923,
            0.38241663, 0.44761338, 0.51056811, 0.51504314, 0.56303342,
        )),
        rtol=0, atol=1e-6,
    )

    assert np.allclose(
        stds,
        np.array((
            0.00100000, 0.06149956, 0.05598960, 0.05115476, 0.29145246,
            0.34006516, 0.29895069, 0.28304818, 0.26340344, 0.27797243,
        )),
        rtol=0, atol=1e-6,
    )

    values     = np.array((1, 2, 1, 2, 1, 2, 1, 2, 1, 2), dtype=np.float64)
    time_steps = np.array((1, 1, 2, 2, 1, 1, 2, 2, 1, 1), dtype=np.float64)
    weights    = np.array((1, 1, 1, 1, 5, 5, 1, 1, 1, 1), dtype=np.float64)
    means, stds = ewm_new(values, time_steps, weights)

    assert np.allclose(
        means,
        (
            1.0000000,  1.50251256, 1.33219236, 1.50379090, 1.21925230,
            1.5028668,  1.46816320, 1.50314792, 1.47179905, 1.50307306,
        ),
        rtol=0, atol=1e-6,
    )

    assert np.allclose(
        stds,
        (
            0.00100000, 0.35266091, 0.34585958, 0.39006561, 0.30556194,
            0.38630063, 0.39249905, 0.40020053, 0.40503148, 0.41104515,
        ),
        rtol=0, atol=1e-6,
    )


def profile() -> None:
    rand = default_rng(seed=0)

    scale = np.round(10**np.linspace(0, 4, 150)).astype(int)
    times = []

    for n in scale:
        values, time_steps, weights = rand.random((3, n))

        for method in (ewm_old, ewm_new):
            t = timeit(lambda: method(values, time_steps, weights), number=1)
            times.append((method.__name__, n, t))

    df = pd.DataFrame(times, columns=('method', 'n', 'time'))
    sns.lineplot(data=df, x='n', y='time', hue='method')
    plt.show()


if __name__ == '__main__':
    test()
    profile()

Find elsewhere
🌐
Codegive
codegive.com › blog › numpy_weighted_standard_deviation.php
Numpy weighted standard deviation
To calculate the numpy weighted standard deviation, you typically compute the weighted mean using np.average and then derive the weighted variance by summing the squared differences from the weighted mean, each multiplied by its corresponding weight, before taking the square root.
🌐
NumPy
numpy.org › doc › 2.1 › reference › generated › numpy.std.html
numpy.std — NumPy v2.1 Manual
The standard deviation is computed for the flattened array by default, otherwise over the specified axis.
🌐
NumPy
numpy.org › doc › stable › reference › generated › numpy.std.html
numpy.std — NumPy v2.5 Manual
The standard deviation is computed for the flattened array by default, otherwise over the specified axis.
🌐
statsmodels
statsmodels.org › stable › generated › statsmodels.stats.weightstats.DescrStatsW.html
statsmodels.stats.weightstats.DescrStatsW - statsmodels 0.14.6
This is essentially the same as replicating each observations by its weight, if the weights are integers, often called case or frequency weights. ... default ddof=0, degrees of freedom correction used for second moments, var, std, cov, corrcoef. However, statistical tests are independent of ddof, based on the standard formulas. ... >>> import numpy as np >>> np.random.seed(0) >>> x1_2d = 1.0 + np.random.randn(20, 3) >>> w1 = np.random.randint(1, 4, 20) >>> d1 = DescrStatsW(x1_2d, weights=w1) >>> d1.mean array([ 1.42739844, 1.23174284, 1.083753 ]) >>> d1.var array([ 0.94855633, 0.52074626, 1.12309325]) >>> d1.std_mean array([ 0.14682676, 0.10878944, 0.15976497])
🌐
Python
mail.python.org › pipermail › numpy-discussion › 2010-September › 052651.html
[Numpy-discussion] weighted mean; weighted standard error of the mean (sem)
September 9, 2010 - By default the error is calculated as 1/sqrt( weights.sum() ). If calcerr=True it is calculated as sqrt( (w**2 * (arr-mean)**2).sum() )/weights.sum() sdev=False: If True, also return the weighted standard deviation as a third element in the tuple. OUTPUTS: wmean, werr: A tuple of the weighted mean and error. If sdev=True the tuple will also contain sdev: wmean,werr,wsdev REVISION HISTORY: Converted from IDL: 2006-10-23. Erin Sheldon, NYU """ # no copy made if they are already arrays arr = numpy.array(arrin, ndmin=1, copy=False) # Weights is forced to be type double.
🌐
Arab Psychology
scales.arabpsychology.com › home › how to calculate weighted standard deviation in python using the statistics package
How To Calculate Weighted Standard Deviation In Python Using The Statistics Package
December 2, 2025 - Determining the Weighted Standard Deviation (WSD) in Python is a common requirement in advanced statistical analysis, particularly when dealing with non-uniform datasets. While standard deviation treats all data points equally, WSD accounts for the varying importance or reliability of observations through the use of assigned weights.
🌐
naaness
naaness.weebly.com › blog › numpy-weighted-standard-deviation
Numpy weighted standard deviation - naaness
January 8, 2023 - out: It is an optional parameter and it is used to store the result of dian() function and by default it takes none value.If the axis is 0 then the direction down the rows and if the axis is 1 then...
🌐
GeeksforGeeks
geeksforgeeks.org › compute-the-weighted-average-of-a-given-numpy-array
Compute the weighted average of a given NumPy array | GeeksforGeeks
August 29, 2020 - In NumPy, we can compute the mean, standard deviation, and variance of a given array along the second axis by two approaches first is by using inbuilt functions and second is by the formulas of the mean, standard deviation, and variance. Method 1: Using numpy.mean(), numpy.std(), numpy.var() Python · 2 min read · How to Calculate Weighted Average in Pandas?
🌐
Statsmodels
statsmodels.org › devel › generated › statsmodels.stats.weightstats.DescrStatsW.html
statsmodels.stats.weightstats.DescrStatsW — statsmodels 0.15.0 (+1618)
This is essentially the same as replicating each observations by its weight, if the weights are integers, often called case or frequency weights. ... default ddof=0, degrees of freedom correction used for second moments, var, std, cov, corrcoef. However, statistical tests are independent of ddof, based on the standard formulas. ... >>> import numpy as np >>> np.random.seed(0) >>> x1_2d = 1.0 + np.random.randn(20, 3) >>> w1 = np.random.randint(1, 4, 20) >>> d1 = DescrStatsW(x1_2d, weights=w1) >>> d1.mean array([ 1.42739844, 1.23174284, 1.083753 ]) >>> d1.var array([ 0.94855633, 0.52074626, 1.12309325]) >>> d1.std_mean array([ 0.14682676, 0.10878944, 0.15976497])
🌐
ExceptionsHub
exceptionshub.com › weighted-standard-deviation-in-numpy.html
Weighted standard deviation in NumPy? | ExceptionsHub
December 9, 2017 - Included there you will find Statistics.py which implements weighted standard deviations. ... import pandas as pd import numpy as np # X is the dataset, as a Pandas' DataFrame mean = mean = np.ma.average(X, axis=0, weights=weights) # Computing the weighted sample mean (fast, efficient and precise) # Convert to a Pandas' Series (it's just aesthetic and more # ergonomic; no difference in computed values) mean = pd.Series(mean, index=list(X.keys())) xm = X-mean # xm = X diff to mean xm = xm.fillna(0) # fill NaN with 0 (because anyway a variance of 0 is just void, but at least it keeps the other covariance's values computed correctly)) sigma2 = 1./(w.sum()-1) * xm.mul(w, axis=0).T.dot(xm); # Compute the unbiased weighted sample covariance
🌐
Solved
code.i-harness.com › en › q › 24d3d2
python - nist - weighted standard deviation excel - Solved
There doesn't appear to be such a function in numpy/scipy yet, but there is a ticket proposing this added functionality. Included there you will find Statistics.py which implements weighted standard deviations.