An implementation of the “Simulating Variance Estimates” by ProfessorParris on youTube

Population variance is calculated:

\(\sigma = \frac{SS}{n}\)

But for sample standard deviations this formula systematically underestimates the variance and we use the sample standard deviation:

\(s = \frac{SS}{n - 1}\)

The following analysis will simulate repeated sampling from a normal distribution with a known mean (\(\mu\)) and standard deviation (\(\sigma\)) at different sample sizes.

We will then calculate the estimated variance and standard deviation using the population variance formula and the sample variance formula, and compare the results.

library(tidyr)
library(purrr)
library(dplyr)
library(knitr)
sample_data <- function(reps = 10000, mu = 100, sigma = 15, nsamples){
  df <- data.frame("rep" = seq(1:reps), "mu" = mu, "sigma"= sigma, "n" = nsamples) 
  df %>% 
    group_by(rep) %>% 
    do(data = data.frame(value= rnorm(nsamples, mu, sigma))) %>%
    unnest(data) %>% 
    mutate(nsamples = nsamples)
}
df <- bind_rows(sample_data(nsamples = 2),
                sample_data(nsamples = 5),
                sample_data(nsamples = 60),
                sample_data(nsamples = 100))

Then we calcuates the sample mean (\(\bar{X}\)), Sum of squares (SS), variance and standard deviation using the population estimates (n in denominator) and using the sample estimates (n-1 in denominator).

stats <- df %>%
  group_by(nsamples, rep) %>% 
  summarise(nreps = n(),
              xbar  = mean(value),
              SS    = sum((value - xbar)^2),
              popvariance = SS / nreps,
              samvariance = SS / (nreps - 1),
              sigma_est = sqrt(popvariance),
              s_est     = sqrt(samvariance))
stats

Reformat the variances to show the difference between the average variance calculated using the population and the sample formula.

vars <- stats %>%
  select(nsamples, rep, popvariance, samvariance) %>%
  rename(population = popvariance,
         sample     = samvariance) %>%
  gather("vartype", "variance", 3:4) 
meanvars <- vars %>% 
  group_by(nsamples, vartype) %>% 
  summarise(mean = mean(variance))
meanvars %>% 
  spread(vartype, mean) %>% 
  kable() 
nsamples population sample
2 112 223
5 180 225
60 222 225
100 223 225

Despite the small sample size the sample variance is on average a good estimate of the actual variance (\(\sigma^2 = 15^2 = 225\)).

When we plot the distribution it is clear that the individual estimates of the standard deviation have a large distribution which is highly skewed (especially as smalle sample sizes). The sample variance corrects well (on average) for the underestimation.

plot_variance_histogram <- function(n) {
  means <- meanvars %>% 
    filter(nsamples == n)
  
  vars %>%
    filter(nsamples == n) %>% 
    ggplot(aes(variance)) + 
      geom_histogram(binwidth = 25) +
      facet_grid(vartype~.) +
      scale_x_continuous(breaks = seq(-200, 1400, 200) ) +
      geom_vline(data = means %>% filter(nsamples == n), 
                 aes(xintercept = mean), color= "red", size = 1.1) +
      geom_vline(xintercept = 225, color= "blue") +
      labs(title = paste0("simulation of estimating variance (nsamples = ", n, ")"),
           subtitle= "comparing sample (n-1) and population (n) variance")
}
plot_variance_histogram(n= 5)

plot_variance_histogram(n= 60)

plot_variance_histogram(n= 2)

The sample standard deviation is however still systematially underestimated compared to the actual \(\sigma\) = 15.

stddevs <- stats %>%
  select(nsamples, rep, sigma_est, s_est) %>%
  rename(population = sigma_est,
         sample     = s_est) %>%
  gather("vartype", "stddev", 3:4) 
meansds <- stddevs %>% 
  group_by(nsamples, vartype) %>% 
  summarise(mean = mean(stddev))
meansds %>% 
  kable()
nsamples vartype mean
2 population 8.47
2 sample 11.98
5 population 12.60
5 sample 14.09
60 population 14.83
60 sample 14.95
100 population 14.89
100 sample 14.96
n = 100
means <- means %>%
  filter(nsamples == n)
stddevs %>%
    filter(nsamples == n) %>% 
    ggplot(aes(stddev)) + 
      geom_histogram(binwidth = 0.5) +
      facet_grid(vartype~.) +
      scale_x_continuous(breaks = seq(10, 20, 1) ) +
      geom_vline(data = means %>% filter(nsamples == n), 
                 aes(xintercept = mean), color= "red", size = 1.1) +
      geom_vline(xintercept = 15, color= "blue") +
      labs(title = paste0("simulation of estimating std. dev (nsamples = ", n, ")"),
           subtitle= "comparing sample (n-1) and population (n) sd")

n = 100
stddevs %>%
  filter(nsamples == n) %>% 
  ggplot(aes(stddev)) + 
    geom_histogram(binwidth = 0.05) +
    facet_grid(vartype~.) +
    scale_x_continuous(breaks = seq(10, 20, 1) ) +
    coord_cartesian(xlim = c(14, 16))+
    geom_vline(data = means %>% filter(nsamples == n), 
               aes(xintercept = mean), color= "red", size = 1.1) +
    geom_vline(xintercept = 15, color= "blue") +
    labs(title = paste0("simulation of estimating std. dev (nsamples = ", n, ")"),
         subtitle= "comparing sample (n-1) and population (n) sd")

This demonstrates that while the estimate of the sample mean and sample variance is an unbiased estimator of the population mean (\(\mu\)) population variance (\(\sigma\)). However, sample standard deviation is not an unbiased estimator of the population standard deviation and systematically underestimates the standard deviation.

How well does the standard error estimate the error in the population mean?

As I had all this simulation data I thought that I would have a look at the difference between the errors in the xbar confidence interval (based on the SE mean), using the z distribution and the t-distribution at different sample sizes.

stats <- stats %>%
  mutate(SEmean = s_est / sqrt(nreps),
         ZciLL   = (xbar - 1.96 * SEmean),
         ZciUL   = (xbar + 1.96 * SEmean),
         ZmeanOK = ifelse( (100 > ZciLL) & (100 < ZciUL), 
                          yes = TRUE, no = FALSE ),
         tciLL   = (xbar - qt(p = 0.975, df= (nreps - 1)) * SEmean),
         tciUL   = (xbar + qt(p = 0.975, df= (nreps - 1)) * SEmean),
         tmeanOK = ifelse( (100 > tciLL) & (100 < tciUL), 
                          yes = TRUE, no = FALSE )  )
stats %>%
  group_by(nsamples) %>% 
  summarise(zmean = sum(ZmeanOK) / 10000,
            avgZciRange = mean(ZciUL - ZciLL), 
            tmean = sum(tmeanOK) / 10000,
            avgtciRange = mean(tciUL - tciLL) ) %>% 
  kable()
nsamples zmean avgZciRange tmean avgtciRange
2 0.702 33.20 0.948 215.21
5 0.872 24.69 0.949 34.98
60 0.944 7.57 0.949 7.72
100 0.948 5.87 0.951 5.94
stats %>%   
  ggplot(aes(x= xbar)) + 
    geom_histogram(bins = 100) +
    geom_vline(xintercept = 100, color= "red") +
    facet_grid(nsamples~., scales = "free") +
    labs(title= "Distribution of sample means",
         subtitle= "with different sample sizes")

LS0tCnRpdGxlOiAiU2ltdWxhdGluZyBWYXJpYW5jZSBFc3RpbWF0ZXMiCmF1dGhvcjogIkFhcm9uIE0uIFNhdW5kZXJzIgpkYXRlOiAiRGVjZW1iZXIgMjEsIDIwMTYiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpCmBgYAoKCkFuIGltcGxlbWVudGF0aW9uIG9mIHRoZSAiU2ltdWxhdGluZyBWYXJpYW5jZSBFc3RpbWF0ZXMiIGJ5IFByb2Zlc3NvclBhcnJpcyBvbiBbeW91VHViZV0oaHR0cHM6Ly95b3V0dS5iZS9KZ2R0NnFLQjVDdykKCgpQb3B1bGF0aW9uIHZhcmlhbmNlIGlzIGNhbGN1bGF0ZWQ6CgokXHNpZ21hID0gXGZyYWN7U1N9e259JAogCkJ1dCBmb3Igc2FtcGxlIHN0YW5kYXJkIGRldmlhdGlvbnMgdGhpcyBmb3JtdWxhIHN5c3RlbWF0aWNhbGx5IHVuZGVyZXN0aW1hdGVzIHRoZSB2YXJpYW5jZSBhbmQgd2UgdXNlIHRoZSBzYW1wbGUgc3RhbmRhcmQgZGV2aWF0aW9uOgoKJHMgPSBcZnJhY3tTU317biAtIDF9JAoKVGhlIGZvbGxvd2luZyBhbmFseXNpcyB3aWxsIHNpbXVsYXRlIHJlcGVhdGVkIHNhbXBsaW5nIGZyb20gYSBub3JtYWwgZGlzdHJpYnV0aW9uIHdpdGggYSBrbm93biBtZWFuICgkXG11JCkgYW5kIHN0YW5kYXJkIGRldmlhdGlvbiAoJFxzaWdtYSQpIGF0IGRpZmZlcmVudCBzYW1wbGUgc2l6ZXMuIAoKV2Ugd2lsbCB0aGVuIGNhbGN1bGF0ZSB0aGUgZXN0aW1hdGVkIHZhcmlhbmNlIGFuZCBzdGFuZGFyZCBkZXZpYXRpb24gdXNpbmcgdGhlIHBvcHVsYXRpb24gdmFyaWFuY2UgZm9ybXVsYSBhbmQgdGhlIHNhbXBsZSB2YXJpYW5jZSBmb3JtdWxhLCBhbmQgY29tcGFyZSB0aGUgcmVzdWx0cy4KCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCByZXN1bHRzPSdoaWRlJ30KbGlicmFyeSh0aWR5cikKbGlicmFyeShwdXJycikKbGlicmFyeShkcGx5cikKbGlicmFyeShrbml0cikKCnNhbXBsZV9kYXRhIDwtIGZ1bmN0aW9uKHJlcHMgPSAxMDAwMCwgbXUgPSAxMDAsIHNpZ21hID0gMTUsIG5zYW1wbGVzKXsKICBkZiA8LSBkYXRhLmZyYW1lKCJyZXAiID0gc2VxKDE6cmVwcyksICJtdSIgPSBtdSwgInNpZ21hIj0gc2lnbWEsICJuIiA9IG5zYW1wbGVzKSAKICBkZiAlPiUgCiAgICBncm91cF9ieShyZXApICU+JSAKICAgIGRvKGRhdGEgPSBkYXRhLmZyYW1lKHZhbHVlPSBybm9ybShuc2FtcGxlcywgbXUsIHNpZ21hKSkpICU+JQogICAgdW5uZXN0KGRhdGEpICU+JSAKICAgIG11dGF0ZShuc2FtcGxlcyA9IG5zYW1wbGVzKQp9CgpkZiA8LSBiaW5kX3Jvd3Moc2FtcGxlX2RhdGEobnNhbXBsZXMgPSAyKSwKICAgICAgICAgICAgICAgIHNhbXBsZV9kYXRhKG5zYW1wbGVzID0gNSksCiAgICAgICAgICAgICAgICBzYW1wbGVfZGF0YShuc2FtcGxlcyA9IDYwKSwKICAgICAgICAgICAgICAgIHNhbXBsZV9kYXRhKG5zYW1wbGVzID0gMTAwKSkKYGBgCgoKVGhlbiB3ZSBjYWxjdWF0ZXMgdGhlIHNhbXBsZSBtZWFuICgkXGJhcntYfSQpLCBTdW0gb2Ygc3F1YXJlcyAoU1MpLCB2YXJpYW5jZSBhbmQgc3RhbmRhcmQgZGV2aWF0aW9uIHVzaW5nIHRoZSBwb3B1bGF0aW9uIGVzdGltYXRlcyAobiBpbiBkZW5vbWluYXRvcikgYW5kIHVzaW5nIHRoZSBzYW1wbGUgZXN0aW1hdGVzIChuLTEgaW4gZGVub21pbmF0b3IpLgoKYGBge3J9CnN0YXRzIDwtIGRmICU+JQogIGdyb3VwX2J5KG5zYW1wbGVzLCByZXApICU+JSAKICBzdW1tYXJpc2UobnJlcHMgPSBuKCksCiAgICAgICAgICAgICAgeGJhciAgPSBtZWFuKHZhbHVlKSwKICAgICAgICAgICAgICBTUyAgICA9IHN1bSgodmFsdWUgLSB4YmFyKV4yKSwKICAgICAgICAgICAgICBwb3B2YXJpYW5jZSA9IFNTIC8gbnJlcHMsCiAgICAgICAgICAgICAgc2FtdmFyaWFuY2UgPSBTUyAvIChucmVwcyAtIDEpLAogICAgICAgICAgICAgIHNpZ21hX2VzdCA9IHNxcnQocG9wdmFyaWFuY2UpLAogICAgICAgICAgICAgIHNfZXN0ICAgICA9IHNxcnQoc2FtdmFyaWFuY2UpKQpzdGF0cwoKYGBgCgpSZWZvcm1hdCB0aGUgdmFyaWFuY2VzIHRvIHNob3cgdGhlIGRpZmZlcmVuY2UgYmV0d2VlbiB0aGUgYXZlcmFnZSB2YXJpYW5jZSBjYWxjdWxhdGVkIHVzaW5nIHRoZSBwb3B1bGF0aW9uIGFuZCB0aGUgc2FtcGxlIGZvcm11bGEuCgpgYGB7cn0KdmFycyA8LSBzdGF0cyAlPiUKICBzZWxlY3QobnNhbXBsZXMsIHJlcCwgcG9wdmFyaWFuY2UsIHNhbXZhcmlhbmNlKSAlPiUKICByZW5hbWUocG9wdWxhdGlvbiA9IHBvcHZhcmlhbmNlLAogICAgICAgICBzYW1wbGUgICAgID0gc2FtdmFyaWFuY2UpICU+JQogIGdhdGhlcigidmFydHlwZSIsICJ2YXJpYW5jZSIsIDM6NCkgCgptZWFudmFycyA8LSB2YXJzICU+JSAKICBncm91cF9ieShuc2FtcGxlcywgdmFydHlwZSkgJT4lIAogIHN1bW1hcmlzZShtZWFuID0gbWVhbih2YXJpYW5jZSkpCgptZWFudmFycyAlPiUgCiAgc3ByZWFkKHZhcnR5cGUsIG1lYW4pICU+JSAKICBrYWJsZSgpIAoKYGBgCgpEZXNwaXRlIHRoZSBzbWFsbCBzYW1wbGUgc2l6ZSB0aGUgc2FtcGxlIHZhcmlhbmNlIGlzIG9uIGF2ZXJhZ2UgYSBnb29kIGVzdGltYXRlIG9mIHRoZSBhY3R1YWwgdmFyaWFuY2UgKCRcc2lnbWFeMiA9IDE1XjIgPSAyMjUkKS4KCldoZW4gd2UgcGxvdCB0aGUgZGlzdHJpYnV0aW9uIGl0IGlzIGNsZWFyIHRoYXQgdGhlIGluZGl2aWR1YWwgZXN0aW1hdGVzIG9mIHRoZSBzdGFuZGFyZCBkZXZpYXRpb24gaGF2ZSBhIGxhcmdlIGRpc3RyaWJ1dGlvbiB3aGljaCBpcyBoaWdobHkgc2tld2VkIChlc3BlY2lhbGx5IGFzIHNtYWxsZSBzYW1wbGUgc2l6ZXMpLiBUaGUgc2FtcGxlIHZhcmlhbmNlIGNvcnJlY3RzIHdlbGwgKG9uIGF2ZXJhZ2UpIGZvciB0aGUgdW5kZXJlc3RpbWF0aW9uLgoKCmBgYHtyfQpwbG90X3ZhcmlhbmNlX2hpc3RvZ3JhbSA8LSBmdW5jdGlvbihuKSB7CiAgbWVhbnMgPC0gbWVhbnZhcnMgJT4lIAogICAgZmlsdGVyKG5zYW1wbGVzID09IG4pCiAgCiAgdmFycyAlPiUKICAgIGZpbHRlcihuc2FtcGxlcyA9PSBuKSAlPiUgCiAgICBnZ3Bsb3QoYWVzKHZhcmlhbmNlKSkgKyAKICAgICAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAyNSkgKwogICAgICBmYWNldF9ncmlkKHZhcnR5cGV+LikgKwogICAgICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKC0yMDAsIDE0MDAsIDIwMCkgKSArCiAgICAgIGdlb21fdmxpbmUoZGF0YSA9IG1lYW5zICU+JSBmaWx0ZXIobnNhbXBsZXMgPT0gbiksIAogICAgICAgICAgICAgICAgIGFlcyh4aW50ZXJjZXB0ID0gbWVhbiksIGNvbG9yPSAicmVkIiwgc2l6ZSA9IDEuMSkgKwogICAgICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSAyMjUsIGNvbG9yPSAiYmx1ZSIpICsKICAgICAgbGFicyh0aXRsZSA9IHBhc3RlMCgic2ltdWxhdGlvbiBvZiBlc3RpbWF0aW5nIHZhcmlhbmNlIChuc2FtcGxlcyA9ICIsIG4sICIpIiksCiAgICAgICAgICAgc3VidGl0bGU9ICJjb21wYXJpbmcgc2FtcGxlIChuLTEpIGFuZCBwb3B1bGF0aW9uIChuKSB2YXJpYW5jZSIpCn0KCnBsb3RfdmFyaWFuY2VfaGlzdG9ncmFtKG49IDUpCmBgYAoKYGBge3J9CnBsb3RfdmFyaWFuY2VfaGlzdG9ncmFtKG49IDYwKQpgYGAKCmBgYHtyfQpwbG90X3ZhcmlhbmNlX2hpc3RvZ3JhbShuPSAyKQpgYGAKClRoZSBzYW1wbGUgc3RhbmRhcmQgZGV2aWF0aW9uIGlzIGhvd2V2ZXIgc3RpbGwgc3lzdGVtYXRpYWxseSB1bmRlcmVzdGltYXRlZCBjb21wYXJlZCB0byB0aGUgYWN0dWFsICRcc2lnbWEkID0gMTUuIAoKCmBgYHtyfQpzdGRkZXZzIDwtIHN0YXRzICU+JQogIHNlbGVjdChuc2FtcGxlcywgcmVwLCBzaWdtYV9lc3QsIHNfZXN0KSAlPiUKICByZW5hbWUocG9wdWxhdGlvbiA9IHNpZ21hX2VzdCwKICAgICAgICAgc2FtcGxlICAgICA9IHNfZXN0KSAlPiUKICBnYXRoZXIoInZhcnR5cGUiLCAic3RkZGV2IiwgMzo0KSAKCm1lYW5zZHMgPC0gc3RkZGV2cyAlPiUgCiAgZ3JvdXBfYnkobnNhbXBsZXMsIHZhcnR5cGUpICU+JSAKICBzdW1tYXJpc2UobWVhbiA9IG1lYW4oc3RkZGV2KSkKCm1lYW5zZHMgJT4lIAogIGthYmxlKCkKYGBgCgoKYGBge3J9Cm4gPSAxMDAKbWVhbnMgPC0gbWVhbnMgJT4lCiAgZmlsdGVyKG5zYW1wbGVzID09IG4pCgpzdGRkZXZzICU+JQogICAgZmlsdGVyKG5zYW1wbGVzID09IG4pICU+JSAKICAgIGdncGxvdChhZXMoc3RkZGV2KSkgKyAKICAgICAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAwLjUpICsKICAgICAgZmFjZXRfZ3JpZCh2YXJ0eXBlfi4pICsKICAgICAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IHNlcSgxMCwgMjAsIDEpICkgKwogICAgICBnZW9tX3ZsaW5lKGRhdGEgPSBtZWFucyAlPiUgZmlsdGVyKG5zYW1wbGVzID09IG4pLCAKICAgICAgICAgICAgICAgICBhZXMoeGludGVyY2VwdCA9IG1lYW4pLCBjb2xvcj0gInJlZCIsIHNpemUgPSAxLjEpICsKICAgICAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gMTUsIGNvbG9yPSAiYmx1ZSIpICsKICAgICAgbGFicyh0aXRsZSA9IHBhc3RlMCgic2ltdWxhdGlvbiBvZiBlc3RpbWF0aW5nIHN0ZC4gZGV2IChuc2FtcGxlcyA9ICIsIG4sICIpIiksCiAgICAgICAgICAgc3VidGl0bGU9ICJjb21wYXJpbmcgc2FtcGxlIChuLTEpIGFuZCBwb3B1bGF0aW9uIChuKSBzZCIpCmBgYAoKYGBge3J9Cm4gPSAxMDAKc3RkZGV2cyAlPiUKICBmaWx0ZXIobnNhbXBsZXMgPT0gbikgJT4lIAogIGdncGxvdChhZXMoc3RkZGV2KSkgKyAKICAgIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gMC4wNSkgKwogICAgZmFjZXRfZ3JpZCh2YXJ0eXBlfi4pICsKICAgIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBzZXEoMTAsIDIwLCAxKSApICsKICAgIGNvb3JkX2NhcnRlc2lhbih4bGltID0gYygxNCwgMTYpKSsKICAgIGdlb21fdmxpbmUoZGF0YSA9IG1lYW5zICU+JSBmaWx0ZXIobnNhbXBsZXMgPT0gbiksIAogICAgICAgICAgICAgICBhZXMoeGludGVyY2VwdCA9IG1lYW4pLCBjb2xvcj0gInJlZCIsIHNpemUgPSAxLjEpICsKICAgIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IDE1LCBjb2xvcj0gImJsdWUiKSArCiAgICBsYWJzKHRpdGxlID0gcGFzdGUwKCJzaW11bGF0aW9uIG9mIGVzdGltYXRpbmcgc3RkLiBkZXYgKG5zYW1wbGVzID0gIiwgbiwgIikiKSwKICAgICAgICAgc3VidGl0bGU9ICJjb21wYXJpbmcgc2FtcGxlIChuLTEpIGFuZCBwb3B1bGF0aW9uIChuKSBzZCIpCmBgYAoKVGhpcyBkZW1vbnN0cmF0ZXMgdGhhdCB3aGlsZSB0aGUgZXN0aW1hdGUgb2YgdGhlIHNhbXBsZSBtZWFuIGFuZCBzYW1wbGUgdmFyaWFuY2UgaXMgYW4gdW5iaWFzZWQgZXN0aW1hdG9yIG9mIHRoZSBwb3B1bGF0aW9uIG1lYW4gKCRcbXUkKSBwb3B1bGF0aW9uIHZhcmlhbmNlICgkXHNpZ21hJCkuIEhvd2V2ZXIsIHNhbXBsZSBzdGFuZGFyZCBkZXZpYXRpb24gaXMgbm90IGFuIHVuYmlhc2VkIGVzdGltYXRvciBvZiB0aGUgcG9wdWxhdGlvbiBzdGFuZGFyZCBkZXZpYXRpb24gYW5kIHN5c3RlbWF0aWNhbGx5IHVuZGVyZXN0aW1hdGVzIHRoZSBzdGFuZGFyZCBkZXZpYXRpb24uCgojIyBIb3cgd2VsbCBkb2VzIHRoZSBzdGFuZGFyZCBlcnJvciBlc3RpbWF0ZSB0aGUgZXJyb3IgaW4gdGhlIHBvcHVsYXRpb24gbWVhbj8KCkFzIEkgaGFkIGFsbCB0aGlzIHNpbXVsYXRpb24gZGF0YSBJIHRob3VnaHQgdGhhdCBJIHdvdWxkIGhhdmUgYSBsb29rIGF0IHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gdGhlIGVycm9ycyBpbiB0aGUgeGJhciBjb25maWRlbmNlIGludGVydmFsIChiYXNlZCBvbiB0aGUgU0UgbWVhbiksIHVzaW5nIHRoZSB6IGRpc3RyaWJ1dGlvbiBhbmQgdGhlIHQtZGlzdHJpYnV0aW9uIGF0IGRpZmZlcmVudCBzYW1wbGUgc2l6ZXMuCgpgYGB7cn0Kc3RhdHMgPC0gc3RhdHMgJT4lCiAgbXV0YXRlKFNFbWVhbiA9IHNfZXN0IC8gc3FydChucmVwcyksCiAgICAgICAgIFpjaUxMICAgPSAoeGJhciAtIDEuOTYgKiBTRW1lYW4pLAogICAgICAgICBaY2lVTCAgID0gKHhiYXIgKyAxLjk2ICogU0VtZWFuKSwKICAgICAgICAgWm1lYW5PSyA9IGlmZWxzZSggKDEwMCA+IFpjaUxMKSAmICgxMDAgPCBaY2lVTCksIAogICAgICAgICAgICAgICAgICAgICAgICAgIHllcyA9IFRSVUUsIG5vID0gRkFMU0UgKSwKICAgICAgICAgdGNpTEwgICA9ICh4YmFyIC0gcXQocCA9IDAuOTc1LCBkZj0gKG5yZXBzIC0gMSkpICogU0VtZWFuKSwKICAgICAgICAgdGNpVUwgICA9ICh4YmFyICsgcXQocCA9IDAuOTc1LCBkZj0gKG5yZXBzIC0gMSkpICogU0VtZWFuKSwKICAgICAgICAgdG1lYW5PSyA9IGlmZWxzZSggKDEwMCA+IHRjaUxMKSAmICgxMDAgPCB0Y2lVTCksIAogICAgICAgICAgICAgICAgICAgICAgICAgIHllcyA9IFRSVUUsIG5vID0gRkFMU0UgKSAgKQoKc3RhdHMgJT4lCiAgZ3JvdXBfYnkobnNhbXBsZXMpICU+JSAKICBzdW1tYXJpc2Uoem1lYW4gPSBzdW0oWm1lYW5PSykgLyAxMDAwMCwKICAgICAgICAgICAgYXZnWmNpUmFuZ2UgPSBtZWFuKFpjaVVMIC0gWmNpTEwpLCAKICAgICAgICAgICAgdG1lYW4gPSBzdW0odG1lYW5PSykgLyAxMDAwMCwKICAgICAgICAgICAgYXZndGNpUmFuZ2UgPSBtZWFuKHRjaVVMIC0gdGNpTEwpICkgJT4lIAogIGthYmxlKCkKCmBgYAoKCmBgYHtyfQpzdGF0cyAlPiUgICAKICBnZ3Bsb3QoYWVzKHg9IHhiYXIpKSArIAogICAgZ2VvbV9oaXN0b2dyYW0oYmlucyA9IDEwMCkgKwogICAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gMTAwLCBjb2xvcj0gInJlZCIpICsKICAgIGZhY2V0X2dyaWQobnNhbXBsZXN+Liwgc2NhbGVzID0gImZyZWUiKSArCiAgICBsYWJzKHRpdGxlPSAiRGlzdHJpYnV0aW9uIG9mIHNhbXBsZSBtZWFucyIsCiAgICAgICAgIHN1YnRpdGxlPSAid2l0aCBkaWZmZXJlbnQgc2FtcGxlIHNpemVzIikKYGBgCgo=