When is Inverse-Variance Weighting Useful?

A situation where inverse-variance weighting is useless

Let's import our libraries first.

In [1]:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

Imagine a temperature sensor that only works occasionally and sends you the data when it's active. We can simulate this situation by having a fake data and only working with it's samples.

In [2]:
data = np.random.normal(50, 100, 1000)
data[:5]
Out[2]:
array([200.3380287 ,  50.72392934, 110.00960272, 197.51782181,
       229.28075779])

Let's also see how the actual data looks like in a KDE plot.

In [3]:
fig, ax = plt.subplots()
sns.kdeplot(data, ax=ax)
plt.show()
No description has been provided for this image
In [4]:
samples = []
for _ in range(20):
    samples.append(np.random.choice(data, 20, replace=False))

samples[:5]
Out[4]:
[array([ -53.86495898,  123.96695913,  203.05929478,  257.323909  ,
          72.87784569,  171.1608002 ,   34.19326637, -200.64198281,
         279.87513181,  218.33415968,  105.3152633 ,    7.59252663,
         255.14683517,  135.47130899,   88.10760032,  195.71800641,
         -19.23492977,  245.3333219 ,  216.47417236,  130.43433655]),
 array([-49.72043292, 317.87093612, 195.71800641, 187.04803056,
        175.8548227 , 178.42996563,  60.45064636, 166.11189634,
        181.25384828,  41.33311655, 200.48427728, 219.90060174,
         38.74957079,  14.0110272 , -48.05059471,  88.10760032,
        -88.63823663,  63.01048882, -17.58882917, 203.05929478]),
 array([  26.64587704,  208.47203036,   11.43495354,   50.58801693,
          72.47731429,   28.53010453, -200.64198281,  179.85611669,
         146.46643564,  112.8446782 ,  -46.8486496 ,   50.59751164,
          79.077875  ,  -25.51074279,  -57.87682771, -100.55399379,
        -101.22093867,   35.73412385,   11.57043332,   -3.43765166]),
 array([ -72.91374912,   94.45132783,    5.61356701,  127.09621087,
          67.50732558,  -30.49796172,  -31.80528773,   22.10778949,
          87.70586451,   51.37116411,  271.54547685,  -86.29849976,
         127.20491815,   97.90941509,  -32.83063211, -107.06240512,
         185.03330714,  -42.84205437, -144.97388664,  107.18922085]),
 array([-145.9708673 ,  104.30672542,   20.4465604 ,  219.1590372 ,
         116.1833204 ,   11.60492817,  186.75549621,   -4.78499968,
          99.62303421,  175.8548227 ,   93.52878864,   81.73806433,
        -128.76463054,  183.31795543,  -10.46295423,   17.72363145,
          32.48825545,  184.92460038, -255.53048583,   91.88725305])]

And here we have 20 samples from the population data.

In [5]:
for i, s in enumerate(samples, 1):
    print(f"Var of {i}:", np.var(s), f"| Mean of {i}:", np.mean(s))
    print()
Var of 1: 14431.873305823494 | Mean of 1: 123.332143336563

Var of 2: 11638.8115219395 | Mean of 2: 106.36980182238474

Var of 3: 9253.667383067768 | Mean of 3: 23.910234199975633

Var of 4: 10581.315320774995 | Mean of 4: 34.7755555450357

Var of 5: 14368.626253325583 | Mean of 5: 53.70142679325518

Var of 6: 10566.403254657851 | Mean of 6: 57.56693203488313

Var of 7: 6251.859223130192 | Mean of 7: 98.01160365825675

Var of 8: 9403.322796531937 | Mean of 8: 101.26162169680872

Var of 9: 11012.5488888822 | Mean of 9: 39.683199866626616

Var of 10: 8237.084462351013 | Mean of 10: 56.26695056203117

Var of 11: 2497.857114834854 | Mean of 11: 76.07232637577418

Var of 12: 10078.862193127687 | Mean of 12: 31.964438226592954

Var of 13: 9534.620240504239 | Mean of 13: 34.99865550100945

Var of 14: 9474.892541468722 | Mean of 14: 40.43874937083173

Var of 15: 10953.436007067963 | Mean of 15: 62.69158820564147

Var of 16: 7087.663226446561 | Mean of 16: 66.47452569785503

Var of 17: 10732.097942658786 | Mean of 17: 24.35723262092491

Var of 18: 12299.26307800468 | Mean of 18: 48.9704483641026

Var of 19: 5831.593493234256 | Mean of 19: 49.81440028144634

Var of 20: 14896.518573462987 | Mean of 20: 22.291276868206275

We can show the mean and the variance of these samples in a beautiful pandas data frame. It is also going to make it easier for us to work with parameters of the samples.

In [6]:
data_sum = pd.DataFrame(
    {
        "mean": [np.mean(s) for s in samples],
        "var": [np.var(s) for s in samples],
        "weights": [1 / np.var(s) for s in samples],
    }
)
data_sum
Out[6]:
mean var weights
0 123.332143 14431.873306 0.000069
1 106.369802 11638.811522 0.000086
2 23.910234 9253.667383 0.000108
3 34.775556 10581.315321 0.000095
4 53.701427 14368.626253 0.000070
5 57.566932 10566.403255 0.000095
6 98.011604 6251.859223 0.000160
7 101.261622 9403.322797 0.000106
8 39.683200 11012.548889 0.000091
9 56.266951 8237.084462 0.000121
10 76.072326 2497.857115 0.000400
11 31.964438 10078.862193 0.000099
12 34.998656 9534.620241 0.000105
13 40.438749 9474.892541 0.000106
14 62.691588 10953.436007 0.000091
15 66.474526 7087.663226 0.000141
16 24.357233 10732.097943 0.000093
17 48.970448 12299.263078 0.000081
18 49.814400 5831.593493 0.000171
19 22.291277 14896.518573 0.000067

Here we can also see a column called weights, it is calculated by taking the reciprocal of variance ($\frac{1}{\sigma^2}$). We are going to use this weights to calculate the weighted mean, which we expect to yield better results.

In [7]:
weighted_mean = np.sum(data_sum["weights"] * data_sum["mean"]) / np.sum(
    data_sum["weights"]
)
In [8]:
actual_mean = np.mean(data)
print("Actual mean:", actual_mean)
print("Weighted mean:", weighted_mean)
print("Grand mean:", np.mean(data_sum["mean"]))
Actual mean: 57.347211431459236
Weighted mean: 60.51752403446366
Grand mean: 57.647655551410274

As you can see in the results, grand mean is giving us a better result compared to weighted mean. This is because we are using the same distribution, or same sensor, for sampling. When we draw samples from the same distribution, since the “uncertainity” of the samples remains the same, there is no point in using the weighted mean.

A situation where inverse-variance weighting is useful

You can think of this actual mean as the actual temperature of a room.

In [9]:
actual_mean = 30

Below, we simulated different sensors with different variances (or in this context, different quailities). But because the temperature of the room cannot change from sensor to sensor, it remains the same (actual_mean).

In [10]:
samples_from_different_dists = []

samples_from_different_dists.append(np.random.normal(actual_mean, 10, 10))
samples_from_different_dists.append(np.random.normal(actual_mean, 40, 10))

# Adding a lot of bad sensors to prove a point :D
samples_from_different_dists.append(np.random.normal(actual_mean, 150, 10))
samples_from_different_dists.append(np.random.normal(actual_mean, 150, 10))

We created the same pandas data frame the see the results clearly. But here, difference between weights are more noticeable. Because we rely more on the low-variance sensor, while also taking the high-variance sensor's data into account.

In [11]:
different_data_sum = pd.DataFrame(
    {
        "mean": [np.mean(s) for s in samples_from_different_dists],
        "var": [np.var(s) for s in samples_from_different_dists],
        "weights": [1 / np.var(s) for s in samples_from_different_dists],
    }
)
different_data_sum
Out[11]:
mean var weights
0 34.088838 99.718519 0.010028
1 27.583243 2078.257111 0.000481
2 36.685331 42926.619918 0.000023
3 61.612300 41310.398984 0.000024
In [12]:
weighted_mean_diff = np.sum(
    different_data_sum["mean"] * different_data_sum["weights"]
) / np.sum(different_data_sum["weights"])
weighted_mean_diff
Out[12]:
np.float64(33.86116079389045)

Here, the weighted mean calculated with IVW method is much closer to the actual mean than the grand mean because we are using different distributions (sensors) with different variances.

In [13]:
print("Actual mean:", actual_mean)
print("Weighted mean:", weighted_mean_diff)
print("Grand mean:", np.mean(different_data_sum["mean"]))
Actual mean: 30
Weighted mean: 33.86116079389045
Grand mean: 39.9924280099536

You may also ask why should we bother with high-variance data if we already have a decent low-variance data. The answer to this question is: Data is data, no matter what. In most scenarios, using high-variance data and giving it less weight is much more appropriate methodology than not using it at all.

>