Plan:

1 - Empirical example

2 - PCA on correlated vs uncorrelated variables

3 - PCA on a correlation matrix (scaled) vs covariance matrix (non-scaled)

Empirical example

library(tidyverse)
library(DT)
library(corrplot)
library(psych)
library(plotly)
library(htmlwidgets)
library(ggbiplot)
library(ggfortify)
require(FactoMineR)
require(factoextra)

Times Higher Education 2021 ranking data

data_raw <- read.csv("THE2021.csv")
str(data_raw)
## 'data.frame':    1448 obs. of  11 variables:
##  $ name                        : chr  "University of Oxford" "Stanford University" "Harvard University" "California Institute of Technology" ...
##  $ location                    : chr  "United Kingdom" "United States" "United States" "United States" ...
##  $ scores_teaching             : num  91.3 92.2 94.4 92.5 90.7 90.3 85.8 91.9 88.8 88.9 ...
##  $ scores_research             : num  99.6 96.7 98.8 96.9 94.4 99.2 97.2 93.8 92.5 90.5 ...
##  $ scores_citations            : num  98 99.9 99.4 97 99.7 95.6 99.1 97.9 98.9 98.6 ...
##  $ scores_industry_income      : num  68.7 90.1 46.8 92.7 90.4 52.1 84.3 56.1 58 54.9 ...
##  $ scores_international_outlook: num  96.4 79.5 77.7 83.6 90 95.7 72.3 68.4 80.2 74 ...
##  $ stats_number_students       : int  20774 16223 21261 2238 11276 19370 39918 12910 8091 14292 ...
##  $ stats_student_staff_ratio   : num  11.1 7.4 9.3 6.3 8.4 11 19.8 6 8 5.9 ...
##  $ stats_pc_intl_students      : int  41 23 25 33 34 38 17 20 23 31 ...
##  $ stats_female_share          : int  46 44 49 36 39 47 51 50 46 46 ...
datatable(data_raw,
          options = list(pageLength = 5))
data_raw[,3:11] %>% 
  #na.omit() %>% 
  cor() %>% 
  corrplot() # six variables correlate, another pair of internationalisation, while female-male isn't related to the rest -- consider taking it out.

data_raw <- data_raw %>% 
            relocate(stats_pc_intl_students, .after = scores_international_outlook)
data_raw[,3:11] %>% 
  #na.omit() %>% 
  cor() %>% 
  corrplot()

# library(summarytools)
# dfSummary(data)
describe(data_raw)[11:12] 
##                               skew kurtosis
## name*                         0.00    -1.20
## location*                    -0.06    -1.46
## scores_teaching               2.08     5.35
## scores_research               1.95     4.15
## scores_citations              0.26    -1.15
## scores_industry_income        1.84     2.58
## scores_international_outlook  0.58    -0.74
## stats_pc_intl_students        1.81     4.47
## stats_number_students         3.81    24.69
## stats_student_staff_ratio     2.33    10.18
## stats_female_share           -0.73     0.75

Some variables are definitely non-normally distributed. How does this affect the results? The answer is not simple.

However, the good news is that PCA requires linearity of relationships between variables but not the normality of their distribution (See this comment https://datascience.stackexchange.com/questions/25789/why-does-pca-assume-gaussian-distribution).

PCA holds no strong distributional assumptions and ‘normality is important only to the extent that skewness or outliers affect the observed correlations’ https://tandfbis.s3.amazonaws.com/rt-media/pdf/9781848729995/IBM_SPSS_5e_Chapter_4.pdf

Read also this short paper to see how to test the robustness of PCs once you have them https://onlinelibrary.wiley.com/doi/epdf/10.1111/evo.13835

There are tests for multivariate normality, if you want to have them, but I do not require those. Here is one example (H1: lack of normality):

library(QuantPsyc)
mult.norm(data_raw[ , 3:11])$mult.test
##          Beta-hat       kappa p-val
## Skewness  47.2503 11403.07265     0
## Kurtosis 162.1288    85.35902     0

Look how different the variances are:

lapply(data_raw[ , 3:11], var)
## $scores_teaching
## [1] 179.3113
## 
## $scores_research
## [1] 293.1243
## 
## $scores_citations
## [1] 766.5359
## 
## $scores_industry_income
## [1] 275.3449
## 
## $scores_international_outlook
## [1] 540.7424
## 
## $stats_pc_intl_students
## [1] 144.5496
## 
## $stats_number_students
## [1] 424682073
## 
## $stats_student_staff_ratio
## [1] 105.9024
## 
## $stats_female_share
## [1] 147.2624

Standardize (=scale) only numeric variables

data_scaled <- data_raw
data_scaled[ , 3:11] <- data_raw[ , 3:11] %>%
  scale() %>% 
  as.data.frame()
str(data_scaled)
## 'data.frame':    1448 obs. of  11 variables:
##  $ name                        : chr  "University of Oxford" "Stanford University" "Harvard University" "California Institute of Technology" ...
##  $ location                    : chr  "United Kingdom" "United States" "United States" "United States" ...
##  $ scores_teaching             : num  4.76 4.83 4.99 4.85 4.72 ...
##  $ scores_research             : num  4.47 4.3 4.42 4.31 4.16 ...
##  $ scores_citations            : num  1.81 1.88 1.87 1.78 1.88 ...
##  $ scores_industry_income      : num  1.3969 2.6866 0.0771 2.8433 2.7047 ...
##  $ scores_international_outlook: num  2.13 1.4 1.32 1.58 1.85 ...
##  $ stats_pc_intl_students      : num  2.473 0.976 1.143 1.808 1.891 ...
##  $ stats_number_students       : num  -0.0712 -0.292 -0.0475 -0.9706 -0.5321 ...
##  $ stats_student_staff_ratio   : num  -0.72 -1.08 -0.895 -1.187 -0.983 ...
##  $ stats_female_share          : num  -0.3246 -0.4894 -0.0774 -1.1487 -0.9014 ...

Run PCA

pca_scores <- prcomp(data_scaled[,3:11])
str(pca_scores, give.attr = F)
## List of 5
##  $ sdev    : num [1:9] 1.867 1.21 1.127 0.948 0.816 ...
##  $ rotation: num [1:9, 1:9] -0.438 -0.483 -0.395 -0.278 -0.422 ...
##  $ center  : Named num [1:9] -7.78e-17 -8.45e-17 -6.91e-17 5.70e-17 7.90e-17 ...
##  $ scale   : logi FALSE
##  $ x       : num [1:1448, 1:9] -7.23 -6.65 -6.1 -7.06 -7.09 ...

How much variance do the PCs retain?

pca_scores$sdev ^ 2
## [1] 3.48591462 1.46337701 1.26901618 0.89840475 0.66615817 0.55930826 0.43691022
## [8] 0.14926140 0.07164939
ggbiplot::ggscreeplot(pca_scores)

ggbiplot::ggbiplot(pca_scores, var.axes = T, alpha = 0.1)

There is also another option that prevents variable names from overlapping:

ggplot2::autoplot(
  pca_scores,
  data = data_scaled,
  loadings = TRUE,
  loadings.colour = 'brown',
  loadings.label.colour = 'brown',
  loadings.label = TRUE,
  loadings.label.size = 4,
  loadings.label.repel = TRUE,
  alpha = 0.1
)

To get labels, add the new components to the data and plot them:

score_comps <- cbind(data_scaled, pca_scores$x[, 1:3]) %>% 
  as.data.frame
bi <- ggplot(score_comps, aes(PC1, PC2, text = name)) +
  geom_point(alpha = 0.1) +
  theme_bw()
ggplotly(bi, tooltip = "text")

Let’s select a group and map it on PCs:

score_components <- cbind(data_scaled, pca_scores$x[, 1:3]) %>% 
  as.data.frame
#table(score_components$location)
score_components$NAm <- if_else(score_components$location == "United States" | score_components$location == "Canada", "NAm", "not")

gr <- factor(score_components$NAm)
summary(gr)
##  NAm  not 
##  201 1247
gg1 <- ggplot(score_components, aes(PC1, PC2, text = name)) +
  geom_point(aes(color = NAm), alpha = 0.5) + 
  theme_bw()
ggplotly(gg1, tooltip = "text")

You can add formal tests to quantify the difference and see that the difference in PC1 is statistically significant, but in PC2 not.

t.test(score_components$PC1 ~ score_components$NAm)
## 
##  Welch Two Sample t-test
## 
## data:  score_components$PC1 by score_components$NAm
## t = -9.5491, df = 253.12, p-value < 2.2e-16
## alternative hypothesis: true difference in means between group NAm and group not is not equal to 0
## 95 percent confidence interval:
##  -1.721327 -1.132717
## sample estimates:
## mean in group NAm mean in group not 
##         -1.228934          0.198088
t.test(score_components$PC2 ~ score_components$NAm)
## 
##  Welch Two Sample t-test
## 
## data:  score_components$PC2 by score_components$NAm
## t = -0.766, df = 373.03, p-value = 0.4442
## alternative hypothesis: true difference in means between group NAm and group not is not equal to 0
## 95 percent confidence interval:
##  -0.18572089  0.08158888
## sample estimates:
## mean in group NAm mean in group not 
##      -0.044838609       0.007227394
t.test(score_components$PC3 ~ score_components$NAm)
## 
##  Welch Two Sample t-test
## 
## data:  score_components$PC3 by score_components$NAm
## t = 0.67417, df = 349.81, p-value = 0.5007
## alternative hypothesis: true difference in means between group NAm and group not is not equal to 0
## 95 percent confidence interval:
##  -0.08551853  0.17472418
## sample estimates:
## mean in group NAm mean in group not 
##       0.038411407      -0.006191414

What if we ran a PCA on the group of correlated variables only?

pca_scores_2 <- prcomp(data_scaled[,3:8])
summary(pca_scores_2)
## Importance of components:
##                           PC1    PC2    PC3     PC4     PC5     PC6
## Standard deviation     1.8662 1.0724 0.8143 0.68000 0.40236 0.28280
## Proportion of Variance 0.5804 0.1917 0.1105 0.07707 0.02698 0.01333
## Cumulative Proportion  0.5804 0.7721 0.8826 0.95969 0.98667 1.00000
ggbiplot::ggbiplot(pca_scores_2, var.axes = T, alpha = 0.1)

The retained variance is higher, and the 1st two PCs now explain more!

Let’s have another round of data preparation and PCA:

  • delete female-to-male ratio (it can be applied later to differentiate between universities) as it is uncorrelated with other variables

  • log-transform teaching, research, industry, students, and staff, to reduce skewness

  • remember to scale everything AFTER log-transforming and BEFORE running PCA (if you log-transform after scaling, you will lose all the data points scoring below the mean values!)

Note that we add a little bit here because log of 0 is not definable:

data_log <- data_raw # copy all raw variables
data_log$log_teaching <- log(data_log$scores_teaching)
data_log$log_research <- log(data_log$scores_research)
data_log$log_industry <- log(data_log$scores_industry_income)
data_log$log_stud <- log(data_log$stats_number_students)
data_log$log_staff <- log(data_log$stats_student_staff_ratio)
data_log$log_intl <- log(data_log$stats_pc_intl_students + 0.1) 

You may wonder, why log-transform? There are many correct answers. It is practiced here and there, and there are statistical reasons, too (see Andrew Gelman’s comment ‘Log transform, kids.’ https://statmodeling.stat.columbia.edu/2019/08/21/you-should-usually-log-transform-your-positive-data/). You may also think of these right-skewed variables as having a log-normal distribution which looks normal after this manipulation: https://upload.wikimedia.org/wikipedia/commons/thumb/4/4e/Lognormal_Distribution.svg/660px-Lognormal_Distribution.svg.png

data_short <- dplyr::select(data_log, 
                     name, 
                     location, 
                     log_teaching, 
                     log_research,
                     scores_citations,
                     log_industry,
                     scores_international_outlook,
                     log_intl, 
                     log_stud, 
                     log_staff)

Now the data are closer to normal distribution:

describe(data_short)[11:12]
##                               skew kurtosis
## name*                         0.00    -1.20
## location*                    -0.06    -1.46
## log_teaching                  0.84     0.38
## log_research                  0.57    -0.44
## scores_citations              0.26    -1.15
## log_industry                  1.38     0.88
## scores_international_outlook  0.58    -0.74
## log_intl                     -1.08     0.87
## log_stud                     -0.63     1.10
## log_staff                    -0.79     3.89

Note that here, we are using more variables but delete cases with NAs, which is why the sample is reduced and not representative anymore.

It depends on your goals how you would proceed with your own data.

data_short[ , 3:10] <- data_short[ , 3:10] %>%
  scale() %>% 
  as.data.frame()
summary(data_short)
##      name             location          log_teaching      log_research    
##  Length:1448        Length:1448        Min.   :-1.9208   Min.   :-1.5832  
##  Class :character   Class :character   1st Qu.:-0.7733   1st Qu.:-0.8134  
##  Mode  :character   Mode  :character   Median :-0.1848   Median :-0.1495  
##                                        Mean   : 0.0000   Mean   : 0.0000  
##                                        3rd Qu.: 0.5932   3rd Qu.: 0.6947  
##                                        Max.   : 3.3084   Max.   : 2.7104  
##  scores_citations   log_industry     scores_international_outlook
##  Min.   :-1.6563   Min.   :-0.8811   Min.   :-1.4391             
##  1st Qu.:-0.8888   1st Qu.:-0.7427   1st Qu.:-0.8371             
##  Median :-0.1068   Median :-0.4021   Median :-0.2028             
##  Mean   : 0.0000   Mean   : 0.0000   Mean   : 0.0000             
##  3rd Qu.: 0.8359   3rd Qu.: 0.3757   3rd Qu.: 0.6906             
##  Max.   : 1.8869   Max.   : 2.8148   Max.   : 2.2807             
##     log_intl          log_stud          log_staff        
##  Min.   :-2.5697   Min.   :-3.87434   Min.   :-5.385033  
##  1st Qu.:-0.6028   1st Qu.:-0.57091   1st Qu.:-0.528172  
##  Median : 0.1842   Median : 0.08956   Median : 0.000173  
##  Mean   : 0.0000   Mean   : 0.00000   Mean   : 0.000000  
##  3rd Qu.: 0.7521   3rd Qu.: 0.67778   3rd Qu.: 0.572468  
##  Max.   : 1.7813   Max.   : 3.03744   Max.   : 3.553679
data_short[,3:10] %>% 
  cor() %>% 
  corrplot()

Run PCA again, with a different function:

soc.pca1 <- PCA(data_short[, 3:10], graph = T)

summary.PCA(soc.pca1)
## 
## Call:
## PCA(X = data_short[, 3:10], graph = T) 
## 
## 
## Eigenvalues
##                        Dim.1   Dim.2   Dim.3   Dim.4   Dim.5   Dim.6   Dim.7
## Variance               3.510   1.481   1.020   0.712   0.567   0.362   0.239
## % of var.             43.874  18.515  12.745   8.897   7.087   4.522   2.992
## Cumulative % of var.  43.874  62.389  75.134  84.031  91.118  95.640  98.632
##                        Dim.8
## Variance               0.109
## % of var.              1.368
## Cumulative % of var. 100.000
## 
## Individuals (the 10 first)
##                                  Dist    Dim.1    ctr   cos2    Dim.2    ctr
## 1                            |  5.506 |  5.312  0.555  0.931 | -0.659  0.020
## 2                            |  5.681 |  5.150  0.522  0.822 | -1.581  0.117
## 3                            |  5.071 |  4.515  0.401  0.793 | -0.892  0.037
## 4                            |  6.291 |  5.229  0.538  0.691 | -3.268  0.498
## 5                            |  5.776 |  5.383  0.570  0.869 | -1.611  0.121
## 6                            |  5.244 |  4.945  0.481  0.889 | -0.642  0.019
## 7                            |  5.295 |  4.827  0.458  0.831 |  0.404  0.008
## 8                            |  5.144 |  4.369  0.376  0.721 | -1.951  0.177
## 9                            |  5.133 |  4.594  0.415  0.801 | -1.847  0.159
## 10                           |  5.171 |  4.507  0.400  0.760 | -1.826  0.156
##                                cos2    Dim.3    ctr   cos2  
## 1                             0.014 |  0.622  0.026  0.013 |
## 2                             0.077 |  1.552  0.163  0.075 |
## 3                             0.031 |  0.313  0.007  0.004 |
## 4                             0.270 |  0.908  0.056  0.021 |
## 5                             0.078 |  1.146  0.089  0.039 |
## 6                             0.015 |  0.058  0.000  0.000 |
## 7                             0.006 |  1.961  0.260  0.137 |
## 8                             0.144 |  0.725  0.036  0.020 |
## 9                             0.129 |  0.395  0.011  0.006 |
## 10                            0.125 |  0.437  0.013  0.007 |
## 
## Variables
##                                 Dim.1    ctr   cos2    Dim.2    ctr   cos2  
## log_teaching                 |  0.804 18.416  0.646 | -0.251  4.237  0.063 |
## log_research                 |  0.921 24.171  0.848 |  0.023  0.035  0.001 |
## scores_citations             |  0.742 15.688  0.551 |  0.037  0.094  0.001 |
## log_industry                 |  0.570  9.270  0.325 | -0.112  0.847  0.013 |
## scores_international_outlook |  0.767 16.758  0.588 |  0.155  1.616  0.024 |
## log_intl                     |  0.739 15.579  0.547 |  0.066  0.293  0.004 |
## log_stud                     |  0.064  0.118  0.004 |  0.796 42.811  0.634 |
## log_staff                    | -0.002  0.000  0.000 |  0.861 50.067  0.742 |
##                               Dim.3    ctr   cos2  
## log_teaching                  0.294  8.479  0.086 |
## log_research                  0.209  4.269  0.044 |
## scores_citations             -0.191  3.578  0.036 |
## log_industry                  0.635 39.547  0.403 |
## scores_international_outlook -0.478 22.386  0.228 |
## log_intl                     -0.402 15.876  0.162 |
## log_stud                      0.234  5.370  0.055 |
## log_staff                     0.071  0.495  0.005 |

Now, why is there a circle? It is called a ‘correlation circle’ of radius 1 and all the loadings of variables should be within it. (For non-standardized variables, the length of the loading arrows approximates the standard deviation of original variables.) Here is the best comment I have been able to find so far: https://stats.stackexchange.com/questions/141085/positioning-the-arrows-on-a-pca-biplot

fviz_screeplot(soc.pca1, addlabels = TRUE)

soc.pca1$var$contrib[ ,1:3]
##                                     Dim.1       Dim.2      Dim.3
## log_teaching                 1.841557e+01  4.23718422  8.4785459
## log_research                 2.417130e+01  0.03539823  4.2691499
## scores_citations             1.568818e+01  0.09366441  3.5784932
## log_industry                 9.270254e+00  0.84700351 39.5468495
## scores_international_outlook 1.675807e+01  1.61593886 22.3857099
## log_intl                     1.557860e+01  0.29307125 15.8756093
## log_stud                     1.178725e-01 42.81055926  5.3701857
## log_staff                    1.566083e-04 50.06718025  0.4954567

2 PCs retain 62% of the information - it is not the best solution

soc.pca1$eig
##        eigenvalue percentage of variance cumulative percentage of variance
## comp 1  3.5098819              43.873524                          43.87352
## comp 2  1.4812078              18.515098                          62.38862
## comp 3  1.0196111              12.745138                          75.13376
## comp 4  0.7117839               8.897299                          84.03106
## comp 5  0.5669757               7.087197                          91.11826
## comp 6  0.3617685               4.522107                          95.64036
## comp 7  0.2393296               2.991620                          98.63198
## comp 8  0.1094414               1.368018                         100.00000

Let’s look at a 2-PC solution:

pca_scores1 <- prcomp(data_short[ , 3:10])
score_components1 <- cbind(data_short, pca_scores1$x[, 1:3]) %>% 
  as.data.frame
gg1 <- ggplot(score_components1, aes(PC1, PC2, text = name)) +
  geom_point(alpha = 0.1) +
  theme_bw()
ggplotly(gg1, tooltip = "text")

Takeaways:

  • evaluate the quality of PCA and interpret the 1st and 2nd principal components based on the variables you had;

  • the more variance the first two components retain, the better;

  • make sense of the PCA solution by looking at specific data points in the new coordinates and telling a story.

PCA on correlated vs uncorrelated variables

1.Generate correlated variables:
https://www.r-bloggers.com/2021/05/how-to-generate-correlated-data-in-r/

set.seed(5)

library(MASS)
library(tidyverse)
library(GGally)
# create the variance covariance matrix
sigma<-rbind(c(1, 0.8, 0.7), c(0.8, 1, 0.5), c(0.7, 0.5, 1))
# create the mean vector
mu<-c(3, 3, 3) 
# generate the multivariate normal distribution
df<-as.data.frame(mvrnorm(n=1000, mu=mu, Sigma=sigma))
ggpairs(df)

head(df)
##         V1       V2       V3
## 1 2.783293 2.575782 1.306218
## 2 4.018948 3.802711 4.912126
## 3 1.872049 2.071065 1.715144
## 4 2.807426 3.239220 3.168219
## 5 4.214603 4.721166 4.641462
## 6 1.976361 2.990800 2.475940
# suitable for a correlation matrix
library(corrplot)
corrplot(sigma, is.corr = T)

  1. Run PCA on them, note the shared variance
ex.pca <- prcomp(df, center = TRUE,scale = TRUE)
summary(ex.pca)
## Importance of components:
##                           PC1    PC2     PC3
## Standard deviation     1.5334 0.7005 0.39746
## Proportion of Variance 0.7838 0.1636 0.05266
## Cumulative Proportion  0.7838 0.9473 1.00000
#install.packages("devtools")
#library(devtools)
#install_github("vqv/ggbiplot")
library(ggbiplot)
ggbiplot::ggbiplot(ex.pca, var.axes = T, alpha = 0.1)

  1. Generate uncorrelated data and run PCA on them:
# create the variance covariance matrix
sigma<-rbind(c(1, 0.01, -0.01), c(0.01, 1, 0.01), c(-0.01, 0.01, 1))
# create the mean vector
mu<-c(3, 3, 3) 
# generate the multivariate normal distribution
df<-as.data.frame(mvrnorm(n=1000, mu=mu, Sigma=sigma))
ggpairs(df)

head(df)
##         V1       V2       V3
## 1 2.711837 4.117121 2.557052
## 2 2.948858 3.226435 3.344459
## 3 3.005668 3.425484 1.416396
## 4 3.744557 3.766854 1.502967
## 5 2.307797 3.510017 2.293501
## 6 4.055856 3.680454 3.981674
ex.pca <- prcomp(df, center = TRUE,scale = TRUE)
summary(ex.pca)
## Importance of components:
##                          PC1    PC2    PC3
## Standard deviation     1.007 0.9997 0.9932
## Proportion of Variance 0.338 0.3331 0.3288
## Cumulative Proportion  0.338 0.6712 1.0000
ggbiplot::ggbiplot(ex.pca, var.axes = T, alpha = 0.1)

Takeaways:

  • the minimum shared variance in the first 2 PCs is 2/k (variables)

  • correlated data produce PCs with eigenvalues larger than 1; uncorrelated data give PCs with eigenvalues close to 1.

PCA on correlation matrix (scaled) vs covariation matrix (non-scaled)

  1. Generate correlated data with similar scales
sigma<-rbind(c(1, 0.9, 0.5), c(0.9, 1, 0.1), c(0.5, 0.1, 1))
mu<-c(3, 13, 23) 
df<-as.data.frame(mvrnorm(n=1000, mu=mu, Sigma=sigma))
head(df)
##         V1       V2       V3
## 1 3.769898 13.46396 24.18749
## 2 2.198070 11.83659 23.39284
## 3 2.276986 12.14388 22.78743
## 4 4.088129 13.82256 24.06543
## 5 4.390463 14.78453 23.06515
## 6 4.248605 13.78361 24.12097
cov(df)
##           V1         V2         V3
## V1 0.9488339 0.86953211 0.46052264
## V2 0.8695321 0.99686253 0.04521317
## V3 0.4605226 0.04521317 1.04003360
cor(df)
##           V1         V2         V3
## V1 1.0000000 0.89407280 0.46358801
## V2 0.8940728 1.00000000 0.04440416
## V3 0.4635880 0.04440416 1.00000000
ggpairs(df)

corrplot(sigma, is.corr = T)

Run a PCA with scaling (standardization):

ex.pca <- prcomp(df, center = TRUE,scale = TRUE)
summary(ex.pca)
## Importance of components:
##                           PC1    PC2     PC3
## Standard deviation     1.4233 0.9817 0.10262
## Proportion of Variance 0.6753 0.3212 0.00351
## Cumulative Proportion  0.6753 0.9965 1.00000
#ggbiplot(ex.pca, var.axes = T, alpha = 0.1)
f1 <- ggbiplot::ggbiplot(ex.pca, var.axes = T, alpha = 0.1)

Run a PCA withOUT scaling (standardization):

ex.pca2 <- prcomp(df, center = TRUE,scale = FALSE)
summary(ex.pca2)
## Importance of components:
##                          PC1    PC2     PC3
## Standard deviation     1.408 0.9965 0.10135
## Proportion of Variance 0.664 0.3326 0.00344
## Cumulative Proportion  0.664 0.9966 1.00000
#ggbiplot(ex.pca2, var.axes = T, alpha = 0.1)
f2 <- ggbiplot::ggbiplot(ex.pca2, var.axes = T, alpha = 0.1)

Run a PCA withOUT even centering the data:

ex.pca3 <- prcomp(df, center = FALSE,scale = FALSE)
summary(ex.pca3)
## Importance of components:
##                            PC1     PC2     PC3
## Standard deviation     26.6188 1.18305 0.57500
## Proportion of Variance  0.9976 0.00197 0.00047
## Cumulative Proportion   0.9976 0.99953 1.00000
#ggbiplot(ex.pca3, var.axes = T, alpha = 0.1)
f3 <- ggbiplot::ggbiplot(ex.pca3, var.axes = T, alpha = 0.1)
library(patchwork)
f1 + f2 + f3

  1. Now generate data with the same mean and similar but unequal variances:
sigma<-rbind(c(1, 0.9, 0.5), c(0.9, 2, 0.1), c(0.5, 0.1, 3))
mu<-c(3, 13, 23) 
df<-as.data.frame(mvrnorm(n=1000, mu=mu, Sigma=sigma))
head(df)
##         V1       V2       V3
## 1 3.944593 13.08659 22.49736
## 2 1.453579 12.92675 21.66832
## 3 4.551205 12.68153 24.12930
## 4 2.693779 11.79429 22.78315
## 5 2.222404 12.72665 20.78659
## 6 1.760597 10.75841 20.06451
cov(df)
##           V1        V2        V3
## V1 1.0349156 0.9163631 0.6749478
## V2 0.9163631 2.0549167 0.1845540
## V3 0.6749478 0.1845540 3.2077519
cor(df)
##           V1         V2         V3
## V1 1.0000000 0.62837374 0.37043952
## V2 0.6283737 1.00000000 0.07188297
## V3 0.3704395 0.07188297 1.00000000
ggpairs(df)

Run a PCA with scaling (standardization):

ex.pca <- prcomp(df, center = TRUE,scale = TRUE)
summary(ex.pca)
## Importance of components:
##                           PC1    PC2    PC3
## Standard deviation     1.3275 0.9681 0.5481
## Proportion of Variance 0.5874 0.3124 0.1001
## Cumulative Proportion  0.5874 0.8999 1.0000
#ggbiplot(ex.pca, var.axes = T, alpha = 0.1)
f1 <- ggbiplot::ggbiplot(ex.pca, var.axes = T, alpha = 0.1)

Run a PCA withOUT scaling (standardization):

ex.pca2 <- prcomp(df, center = TRUE,scale = FALSE)
summary(ex.pca2)
## Importance of components:
##                           PC1    PC2     PC3
## Standard deviation     1.8838 1.5302 0.63828
## Proportion of Variance 0.5635 0.3718 0.06469
## Cumulative Proportion  0.5635 0.9353 1.00000
#ggbiplot(ex.pca2, var.axes = T, alpha = 0.1)
f2 <- ggbiplot::ggbiplot(ex.pca2, var.axes = T, alpha = 0.1)

Run a PCA withOUT even centering the data:

ex.pca3 <- prcomp(df, center = FALSE,scale = FALSE)
summary(ex.pca3)
## Importance of components:
##                            PC1     PC2     PC3
## Standard deviation     26.6767 1.53166 0.81144
## Proportion of Variance  0.9958 0.00328 0.00092
## Cumulative Proportion   0.9958 0.99908 1.00000
#ggbiplot(ex.pca3, var.axes = T, alpha = 0.1)
f3 <- ggbiplot::ggbiplot(ex.pca3, var.axes = T, alpha = 0.1)
library(patchwork)
f1 + f2 + f3

Takeaways:

  • to give more variance to PC1, standardize solutions;
  • to level out variables’ impact on principal components, scale them.

Appendix

A variance-covariance matrix: has variances on the diagonal and covariances in other cells (symmetric) https://www.researchgate.net/publication/237014579/figure/fig1/AS:213963448033280@1428024269392/Variance-covariance-matrix-depicting-homogeneity-of-variance-and-compound-symmetry.png

Computer lab Jan 28 planned: master biplots and replicate this analysis on your data https://www.datacamp.com/tutorial/pca-analysis-r

LS0tDQp0aXRsZTogIlBDQSINCmF1dGhvcjogIkFubmEgU2hpcm9rYW5vdmEiDQpkYXRlOiAiYHIgU3lzLkRhdGUoKWAiDQpvdXRwdXQ6DQogIGh0bWxfZG9jdW1lbnQ6DQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQ0KICAgIHRvYzogdHJ1ZQ0KICAgIHRvY19kZXB0aDogMw0KLS0tDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLCB3YXJuaW5nID0gRiwgbWVzc2FnZSA9IEYpDQpgYGANCg0KIyBQbGFuOg0KDQoxIC0gRW1waXJpY2FsIGV4YW1wbGUNCg0KMiAtIFBDQSBvbiBjb3JyZWxhdGVkIHZzIHVuY29ycmVsYXRlZCB2YXJpYWJsZXMNCg0KMyAtIFBDQSBvbiBhIGNvcnJlbGF0aW9uIG1hdHJpeCAoc2NhbGVkKSB2cyBjb3ZhcmlhbmNlIG1hdHJpeCAobm9uLXNjYWxlZCkNCg0KIyMgRW1waXJpY2FsIGV4YW1wbGUNCg0KDQpgYGB7cn0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShEVCkNCmxpYnJhcnkoY29ycnBsb3QpDQpsaWJyYXJ5KHBzeWNoKQ0KbGlicmFyeShwbG90bHkpDQpsaWJyYXJ5KGh0bWx3aWRnZXRzKQ0KbGlicmFyeShnZ2JpcGxvdCkNCmxpYnJhcnkoZ2dmb3J0aWZ5KQ0KcmVxdWlyZShGYWN0b01pbmVSKQ0KcmVxdWlyZShmYWN0b2V4dHJhKQ0KDQpgYGANCg0KVGltZXMgSGlnaGVyIEVkdWNhdGlvbiAyMDIxIHJhbmtpbmcgZGF0YQ0KDQpgYGB7cn0NCmRhdGFfcmF3IDwtIHJlYWQuY3N2KCJUSEUyMDIxLmNzdiIpDQpzdHIoZGF0YV9yYXcpDQpgYGANCg0KDQpgYGB7cn0NCmRhdGF0YWJsZShkYXRhX3JhdywNCiAgICAgICAgICBvcHRpb25zID0gbGlzdChwYWdlTGVuZ3RoID0gNSkpDQpgYGANCg0KDQpgYGB7cn0NCmRhdGFfcmF3WywzOjExXSAlPiUgDQogICNuYS5vbWl0KCkgJT4lIA0KICBjb3IoKSAlPiUgDQogIGNvcnJwbG90KCkgIyBzaXggdmFyaWFibGVzIGNvcnJlbGF0ZSwgYW5vdGhlciBwYWlyIG9mIGludGVybmF0aW9uYWxpc2F0aW9uLCB3aGlsZSBmZW1hbGUtbWFsZSBpc24ndCByZWxhdGVkIHRvIHRoZSByZXN0IC0tIGNvbnNpZGVyIHRha2luZyBpdCBvdXQuDQpkYXRhX3JhdyA8LSBkYXRhX3JhdyAlPiUgDQogICAgICAgICAgICByZWxvY2F0ZShzdGF0c19wY19pbnRsX3N0dWRlbnRzLCAuYWZ0ZXIgPSBzY29yZXNfaW50ZXJuYXRpb25hbF9vdXRsb29rKQ0KZGF0YV9yYXdbLDM6MTFdICU+JSANCiAgI25hLm9taXQoKSAlPiUgDQogIGNvcigpICU+JSANCiAgY29ycnBsb3QoKQ0KYGBgDQoNCg0KYGBge3J9DQojIGxpYnJhcnkoc3VtbWFyeXRvb2xzKQ0KIyBkZlN1bW1hcnkoZGF0YSkNCmRlc2NyaWJlKGRhdGFfcmF3KVsxMToxMl0gDQpgYGANCg0KU29tZSB2YXJpYWJsZXMgYXJlIGRlZmluaXRlbHkgbm9uLW5vcm1hbGx5IGRpc3RyaWJ1dGVkLiBIb3cgZG9lcyB0aGlzIGFmZmVjdCB0aGUgcmVzdWx0cz8gVGhlIGFuc3dlciBpcyBub3Qgc2ltcGxlLg0KDQpIb3dldmVyLCB0aGUgZ29vZCBuZXdzIGlzIHRoYXQgUENBIHJlcXVpcmVzIF9fbGluZWFyaXR5X18gb2YgcmVsYXRpb25zaGlwcyBiZXR3ZWVuIHZhcmlhYmxlcyBidXQgbm90IHRoZSBub3JtYWxpdHkgb2YgdGhlaXIgZGlzdHJpYnV0aW9uIChTZWUgdGhpcyBjb21tZW50IDxodHRwczovL2RhdGFzY2llbmNlLnN0YWNrZXhjaGFuZ2UuY29tL3F1ZXN0aW9ucy8yNTc4OS93aHktZG9lcy1wY2EtYXNzdW1lLWdhdXNzaWFuLWRpc3RyaWJ1dGlvbj4pLg0KDQpQQ0EgaG9sZHMgbm8gc3Ryb25nIGRpc3RyaWJ1dGlvbmFsIGFzc3VtcHRpb25zIGFuZCAnbm9ybWFsaXR5IGlzIGltcG9ydGFudCBvbmx5IHRvIHRoZSBleHRlbnQgdGhhdCBza2V3bmVzcyBvciBvdXRsaWVycyBhZmZlY3QgdGhlIG9ic2VydmVkIGNvcnJlbGF0aW9ucycgPGh0dHBzOi8vdGFuZGZiaXMuczMuYW1hem9uYXdzLmNvbS9ydC1tZWRpYS9wZGYvOTc4MTg0ODcyOTk5NS9JQk1fU1BTU181ZV9DaGFwdGVyXzQucGRmPg0KDQpSZWFkIGFsc28gdGhpcyBzaG9ydCBwYXBlciB0byBzZWUgaG93IHRvIHRlc3QgdGhlIHJvYnVzdG5lc3Mgb2YgUENzIG9uY2UgeW91IGhhdmUgdGhlbSA8aHR0cHM6Ly9vbmxpbmVsaWJyYXJ5LndpbGV5LmNvbS9kb2kvZXBkZi8xMC4xMTExL2V2by4xMzgzNT4NCg0KDQoNClRoZXJlIGFyZSB0ZXN0cyBmb3IgbXVsdGl2YXJpYXRlIG5vcm1hbGl0eSwgaWYgeW91IHdhbnQgdG8gaGF2ZSB0aGVtLCBidXQgSSBkbyBub3QgcmVxdWlyZSB0aG9zZS4gSGVyZSBpcyBvbmUgZXhhbXBsZSAoSDE6IGxhY2sgb2Ygbm9ybWFsaXR5KToNCg0KYGBge3J9DQpsaWJyYXJ5KFF1YW50UHN5YykNCm11bHQubm9ybShkYXRhX3Jhd1sgLCAzOjExXSkkbXVsdC50ZXN0DQpgYGANCg0KDQpMb29rIGhvdyBkaWZmZXJlbnQgdGhlIHZhcmlhbmNlcyBhcmU6DQoNCmBgYHtyfQ0KbGFwcGx5KGRhdGFfcmF3WyAsIDM6MTFdLCB2YXIpDQpgYGANCg0KU3RhbmRhcmRpemUgKD1zY2FsZSkgb25seSBudW1lcmljIHZhcmlhYmxlcw0KDQpgYGB7cn0NCmRhdGFfc2NhbGVkIDwtIGRhdGFfcmF3DQpkYXRhX3NjYWxlZFsgLCAzOjExXSA8LSBkYXRhX3Jhd1sgLCAzOjExXSAlPiUNCiAgc2NhbGUoKSAlPiUgDQogIGFzLmRhdGEuZnJhbWUoKQ0Kc3RyKGRhdGFfc2NhbGVkKQ0KYGBgDQoNClJ1biBQQ0ENCg0KYGBge3J9DQpwY2Ffc2NvcmVzIDwtIHByY29tcChkYXRhX3NjYWxlZFssMzoxMV0pDQpzdHIocGNhX3Njb3JlcywgZ2l2ZS5hdHRyID0gRikNCmBgYA0KDQoNCkhvdyBtdWNoIHZhcmlhbmNlIGRvIHRoZSBQQ3MgcmV0YWluPw0KDQpgYGB7cn0NCnBjYV9zY29yZXMkc2RldiBeIDINCg0KYGBgDQoNCg0KYGBge3J9DQpnZ2JpcGxvdDo6Z2dzY3JlZXBsb3QocGNhX3Njb3JlcykNCmdnYmlwbG90OjpnZ2JpcGxvdChwY2Ffc2NvcmVzLCB2YXIuYXhlcyA9IFQsIGFscGhhID0gMC4xKQ0KYGBgDQoNClRoZXJlIGlzIGFsc28gYW5vdGhlciBvcHRpb24gdGhhdCBwcmV2ZW50cyB2YXJpYWJsZSBuYW1lcyBmcm9tIG92ZXJsYXBwaW5nOg0KDQpgYGB7cn0NCmdncGxvdDI6OmF1dG9wbG90KA0KICBwY2Ffc2NvcmVzLA0KICBkYXRhID0gZGF0YV9zY2FsZWQsDQogIGxvYWRpbmdzID0gVFJVRSwNCiAgbG9hZGluZ3MuY29sb3VyID0gJ2Jyb3duJywNCiAgbG9hZGluZ3MubGFiZWwuY29sb3VyID0gJ2Jyb3duJywNCiAgbG9hZGluZ3MubGFiZWwgPSBUUlVFLA0KICBsb2FkaW5ncy5sYWJlbC5zaXplID0gNCwNCiAgbG9hZGluZ3MubGFiZWwucmVwZWwgPSBUUlVFLA0KICBhbHBoYSA9IDAuMQ0KKQ0KYGBgDQoNCg0KVG8gZ2V0IGxhYmVscywgYWRkIHRoZSBuZXcgY29tcG9uZW50cyB0byB0aGUgZGF0YSBhbmQgcGxvdCB0aGVtOg0KDQpgYGB7cn0NCnNjb3JlX2NvbXBzIDwtIGNiaW5kKGRhdGFfc2NhbGVkLCBwY2Ffc2NvcmVzJHhbLCAxOjNdKSAlPiUgDQogIGFzLmRhdGEuZnJhbWUNCmJpIDwtIGdncGxvdChzY29yZV9jb21wcywgYWVzKFBDMSwgUEMyLCB0ZXh0ID0gbmFtZSkpICsNCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuMSkgKw0KICB0aGVtZV9idygpDQpnZ3Bsb3RseShiaSwgdG9vbHRpcCA9ICJ0ZXh0IikNCg0KYGBgDQoNCg0KTGV0J3Mgc2VsZWN0IGEgZ3JvdXAgYW5kIG1hcCBpdCBvbiBQQ3M6DQoNCmBgYHtyfQ0Kc2NvcmVfY29tcG9uZW50cyA8LSBjYmluZChkYXRhX3NjYWxlZCwgcGNhX3Njb3JlcyR4WywgMTozXSkgJT4lIA0KICBhcy5kYXRhLmZyYW1lDQojdGFibGUoc2NvcmVfY29tcG9uZW50cyRsb2NhdGlvbikNCnNjb3JlX2NvbXBvbmVudHMkTkFtIDwtIGlmX2Vsc2Uoc2NvcmVfY29tcG9uZW50cyRsb2NhdGlvbiA9PSAiVW5pdGVkIFN0YXRlcyIgfCBzY29yZV9jb21wb25lbnRzJGxvY2F0aW9uID09ICJDYW5hZGEiLCAiTkFtIiwgIm5vdCIpDQoNCmdyIDwtIGZhY3RvcihzY29yZV9jb21wb25lbnRzJE5BbSkNCnN1bW1hcnkoZ3IpDQpgYGANCg0KDQpgYGB7cn0NCmdnMSA8LSBnZ3Bsb3Qoc2NvcmVfY29tcG9uZW50cywgYWVzKFBDMSwgUEMyLCB0ZXh0ID0gbmFtZSkpICsNCiAgZ2VvbV9wb2ludChhZXMoY29sb3IgPSBOQW0pLCBhbHBoYSA9IDAuNSkgKyANCiAgdGhlbWVfYncoKQ0KZ2dwbG90bHkoZ2cxLCB0b29sdGlwID0gInRleHQiKQ0KYGBgDQoNCg0KDQpZb3UgY2FuIGFkZCBmb3JtYWwgdGVzdHMgdG8gcXVhbnRpZnkgdGhlIGRpZmZlcmVuY2UgYW5kIHNlZSB0aGF0IHRoZSBkaWZmZXJlbmNlIGluIFBDMSBpcyBzdGF0aXN0aWNhbGx5IHNpZ25pZmljYW50LCBidXQgaW4gUEMyIG5vdC4NCg0KYGBge3J9DQp0LnRlc3Qoc2NvcmVfY29tcG9uZW50cyRQQzEgfiBzY29yZV9jb21wb25lbnRzJE5BbSkNCnQudGVzdChzY29yZV9jb21wb25lbnRzJFBDMiB+IHNjb3JlX2NvbXBvbmVudHMkTkFtKQ0KdC50ZXN0KHNjb3JlX2NvbXBvbmVudHMkUEMzIH4gc2NvcmVfY29tcG9uZW50cyROQW0pDQpgYGANCg0KIyMjIFdoYXQgaWYgd2UgcmFuIGEgUENBIG9uIHRoZSBncm91cCBvZiBjb3JyZWxhdGVkIHZhcmlhYmxlcyBvbmx5Pw0KDQpgYGB7cn0NCnBjYV9zY29yZXNfMiA8LSBwcmNvbXAoZGF0YV9zY2FsZWRbLDM6OF0pDQpzdW1tYXJ5KHBjYV9zY29yZXNfMikNCmdnYmlwbG90OjpnZ2JpcGxvdChwY2Ffc2NvcmVzXzIsIHZhci5heGVzID0gVCwgYWxwaGEgPSAwLjEpDQpgYGANCg0KVGhlIHJldGFpbmVkIHZhcmlhbmNlIGlzIGhpZ2hlciwgYW5kIHRoZSAxc3QgdHdvIFBDcyBub3cgZXhwbGFpbiBtb3JlIQ0KDQoNCg0KIyMjIExldCdzIGhhdmUgYW5vdGhlciByb3VuZCBvZiBkYXRhIHByZXBhcmF0aW9uIGFuZCBQQ0E6DQoNCi0gZGVsZXRlIGZlbWFsZS10by1tYWxlIHJhdGlvIChpdCBjYW4gYmUgYXBwbGllZCBsYXRlciB0byBkaWZmZXJlbnRpYXRlIGJldHdlZW4gdW5pdmVyc2l0aWVzKSBhcyBpdCBpcyB1bmNvcnJlbGF0ZWQgd2l0aCBvdGhlciB2YXJpYWJsZXMNCg0KLSBsb2ctdHJhbnNmb3JtIHRlYWNoaW5nLCByZXNlYXJjaCwgaW5kdXN0cnksIHN0dWRlbnRzLCBhbmQgc3RhZmYsIHRvIHJlZHVjZSBza2V3bmVzcw0KDQotIHJlbWVtYmVyIHRvIHNjYWxlIGV2ZXJ5dGhpbmcgQUZURVIgbG9nLXRyYW5zZm9ybWluZyBhbmQgQkVGT1JFIHJ1bm5pbmcgUENBIChpZiB5b3UgbG9nLXRyYW5zZm9ybSBhZnRlciBzY2FsaW5nLCB5b3Ugd2lsbCBsb3NlIGFsbCB0aGUgZGF0YSBwb2ludHMgc2NvcmluZyBiZWxvdyB0aGUgbWVhbiB2YWx1ZXMhKQ0KDQpOb3RlIHRoYXQgd2UgYWRkIGEgbGl0dGxlIGJpdCBoZXJlIGJlY2F1c2UgbG9nIG9mIDAgaXMgbm90IGRlZmluYWJsZToNCg0KYGBge3J9DQpkYXRhX2xvZyA8LSBkYXRhX3JhdyAjIGNvcHkgYWxsIHJhdyB2YXJpYWJsZXMNCmRhdGFfbG9nJGxvZ190ZWFjaGluZyA8LSBsb2coZGF0YV9sb2ckc2NvcmVzX3RlYWNoaW5nKQ0KZGF0YV9sb2ckbG9nX3Jlc2VhcmNoIDwtIGxvZyhkYXRhX2xvZyRzY29yZXNfcmVzZWFyY2gpDQpkYXRhX2xvZyRsb2dfaW5kdXN0cnkgPC0gbG9nKGRhdGFfbG9nJHNjb3Jlc19pbmR1c3RyeV9pbmNvbWUpDQpkYXRhX2xvZyRsb2dfc3R1ZCA8LSBsb2coZGF0YV9sb2ckc3RhdHNfbnVtYmVyX3N0dWRlbnRzKQ0KZGF0YV9sb2ckbG9nX3N0YWZmIDwtIGxvZyhkYXRhX2xvZyRzdGF0c19zdHVkZW50X3N0YWZmX3JhdGlvKQ0KZGF0YV9sb2ckbG9nX2ludGwgPC0gbG9nKGRhdGFfbG9nJHN0YXRzX3BjX2ludGxfc3R1ZGVudHMgKyAwLjEpIA0KYGBgDQoNCllvdSBtYXkgd29uZGVyLCB3aHkgbG9nLXRyYW5zZm9ybT8gVGhlcmUgYXJlIG1hbnkgY29ycmVjdCBhbnN3ZXJzLiBJdCBpcyBwcmFjdGljZWQgaGVyZSBhbmQgdGhlcmUsIGFuZCB0aGVyZSBhcmUgc3RhdGlzdGljYWwgcmVhc29ucywgdG9vIChzZWUgQW5kcmV3IEdlbG1hbidzIGNvbW1lbnQgJ0xvZyB0cmFuc2Zvcm0sIGtpZHMuJyA8aHR0cHM6Ly9zdGF0bW9kZWxpbmcuc3RhdC5jb2x1bWJpYS5lZHUvMjAxOS8wOC8yMS95b3Utc2hvdWxkLXVzdWFsbHktbG9nLXRyYW5zZm9ybS15b3VyLXBvc2l0aXZlLWRhdGEvPikuIFlvdSBtYXkgYWxzbyB0aGluayBvZiB0aGVzZSByaWdodC1za2V3ZWQgdmFyaWFibGVzIGFzIGhhdmluZyBhIGxvZy1ub3JtYWwgZGlzdHJpYnV0aW9uIHdoaWNoIGxvb2tzIG5vcm1hbCBhZnRlciB0aGlzIG1hbmlwdWxhdGlvbjogPGh0dHBzOi8vdXBsb2FkLndpa2ltZWRpYS5vcmcvd2lraXBlZGlhL2NvbW1vbnMvdGh1bWIvNC80ZS9Mb2dub3JtYWxfRGlzdHJpYnV0aW9uLnN2Zy82NjBweC1Mb2dub3JtYWxfRGlzdHJpYnV0aW9uLnN2Zy5wbmc+DQoNCmBgYHtyfQ0KZGF0YV9zaG9ydCA8LSBkcGx5cjo6c2VsZWN0KGRhdGFfbG9nLCANCiAgICAgICAgICAgICAgICAgICAgIG5hbWUsIA0KICAgICAgICAgICAgICAgICAgICAgbG9jYXRpb24sIA0KICAgICAgICAgICAgICAgICAgICAgbG9nX3RlYWNoaW5nLCANCiAgICAgICAgICAgICAgICAgICAgIGxvZ19yZXNlYXJjaCwNCiAgICAgICAgICAgICAgICAgICAgIHNjb3Jlc19jaXRhdGlvbnMsDQogICAgICAgICAgICAgICAgICAgICBsb2dfaW5kdXN0cnksDQogICAgICAgICAgICAgICAgICAgICBzY29yZXNfaW50ZXJuYXRpb25hbF9vdXRsb29rLA0KICAgICAgICAgICAgICAgICAgICAgbG9nX2ludGwsIA0KICAgICAgICAgICAgICAgICAgICAgbG9nX3N0dWQsIA0KICAgICAgICAgICAgICAgICAgICAgbG9nX3N0YWZmKQ0KYGBgDQoNCk5vdyB0aGUgZGF0YSBhcmUgY2xvc2VyIHRvIG5vcm1hbCBkaXN0cmlidXRpb246DQoNCmBgYHtyfQ0KZGVzY3JpYmUoZGF0YV9zaG9ydClbMTE6MTJdDQpgYGANCg0KTm90ZSB0aGF0IGhlcmUsIHdlIGFyZSB1c2luZyBtb3JlIHZhcmlhYmxlcyBidXQgZGVsZXRlIGNhc2VzIHdpdGggTkFzLCB3aGljaCBpcyB3aHkgdGhlIHNhbXBsZSBpcyByZWR1Y2VkIGFuZCBub3QgcmVwcmVzZW50YXRpdmUgYW55bW9yZS4NCg0KSXQgZGVwZW5kcyBvbiB5b3VyIGdvYWxzIGhvdyB5b3Ugd291bGQgcHJvY2VlZCB3aXRoIHlvdXIgb3duIGRhdGEuDQoNCmBgYHtyfQ0KZGF0YV9zaG9ydFsgLCAzOjEwXSA8LSBkYXRhX3Nob3J0WyAsIDM6MTBdICU+JQ0KICBzY2FsZSgpICU+JSANCiAgYXMuZGF0YS5mcmFtZSgpDQpzdW1tYXJ5KGRhdGFfc2hvcnQpDQoNCmRhdGFfc2hvcnRbLDM6MTBdICU+JSANCiAgY29yKCkgJT4lIA0KICBjb3JycGxvdCgpDQpgYGANCg0KUnVuIFBDQSBhZ2Fpbiwgd2l0aCBhIGRpZmZlcmVudCBmdW5jdGlvbjoNCg0KYGBge3J9DQoNCnNvYy5wY2ExIDwtIFBDQShkYXRhX3Nob3J0WywgMzoxMF0sIGdyYXBoID0gVCkNCnN1bW1hcnkuUENBKHNvYy5wY2ExKQ0KYGBgDQoNCk5vdywgd2h5IGlzIHRoZXJlIGEgY2lyY2xlPyBJdCBpcyBjYWxsZWQgYSAnY29ycmVsYXRpb24gY2lyY2xlJyBvZiByYWRpdXMgMSBhbmQgYWxsIHRoZSBsb2FkaW5ncyBvZiB2YXJpYWJsZXMgc2hvdWxkIGJlIHdpdGhpbiBpdC4gKEZvciBub24tc3RhbmRhcmRpemVkIHZhcmlhYmxlcywgdGhlIGxlbmd0aCBvZiB0aGUgbG9hZGluZyBhcnJvd3MgYXBwcm94aW1hdGVzIHRoZSBzdGFuZGFyZCBkZXZpYXRpb24gb2Ygb3JpZ2luYWwgdmFyaWFibGVzLikgSGVyZSBpcyB0aGUgYmVzdCBjb21tZW50IEkgaGF2ZSBiZWVuIGFibGUgdG8gZmluZCBzbyBmYXI6IDxodHRwczovL3N0YXRzLnN0YWNrZXhjaGFuZ2UuY29tL3F1ZXN0aW9ucy8xNDEwODUvcG9zaXRpb25pbmctdGhlLWFycm93cy1vbi1hLXBjYS1iaXBsb3Q+DQoNCmBgYHtyfQ0KDQpmdml6X3NjcmVlcGxvdChzb2MucGNhMSwgYWRkbGFiZWxzID0gVFJVRSkNCnNvYy5wY2ExJHZhciRjb250cmliWyAsMTozXQ0KYGBgDQoNCjIgUENzIHJldGFpbiA2MiUgb2YgdGhlIGluZm9ybWF0aW9uIC0gaXQgaXMgbm90IHRoZSBiZXN0IHNvbHV0aW9uDQoNCmBgYHtyfQ0Kc29jLnBjYTEkZWlnDQpgYGANCg0KTGV0J3MgbG9vayBhdCBhIDItUEMgc29sdXRpb246DQoNCg0KYGBge3J9DQoNCnBjYV9zY29yZXMxIDwtIHByY29tcChkYXRhX3Nob3J0WyAsIDM6MTBdKQ0Kc2NvcmVfY29tcG9uZW50czEgPC0gY2JpbmQoZGF0YV9zaG9ydCwgcGNhX3Njb3JlczEkeFssIDE6M10pICU+JSANCiAgYXMuZGF0YS5mcmFtZQ0KZ2cxIDwtIGdncGxvdChzY29yZV9jb21wb25lbnRzMSwgYWVzKFBDMSwgUEMyLCB0ZXh0ID0gbmFtZSkpICsNCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuMSkgKw0KICB0aGVtZV9idygpDQpnZ3Bsb3RseShnZzEsIHRvb2x0aXAgPSAidGV4dCIpDQpgYGANCg0KIyMjIF9fVGFrZWF3YXlzOl9fDQoNCi0gZXZhbHVhdGUgdGhlIHF1YWxpdHkgb2YgUENBIGFuZCBpbnRlcnByZXQgdGhlIDFzdCBhbmQgMm5kIHByaW5jaXBhbCBjb21wb25lbnRzIGJhc2VkIG9uIHRoZSB2YXJpYWJsZXMgeW91IGhhZDsNCg0KLSB0aGUgbW9yZSB2YXJpYW5jZSB0aGUgZmlyc3QgdHdvIGNvbXBvbmVudHMgcmV0YWluLCB0aGUgYmV0dGVyOw0KDQotIG1ha2Ugc2Vuc2Ugb2YgdGhlIFBDQSBzb2x1dGlvbiBieSBsb29raW5nIGF0IHNwZWNpZmljIGRhdGEgcG9pbnRzIGluIHRoZSBuZXcgY29vcmRpbmF0ZXMgYW5kIHRlbGxpbmcgYSBzdG9yeS4NCg0KDQojIyBQQ0Egb24gY29ycmVsYXRlZCB2cyB1bmNvcnJlbGF0ZWQgdmFyaWFibGVzDQoNCjEuR2VuZXJhdGUgY29ycmVsYXRlZCB2YXJpYWJsZXM6ICANCjxodHRwczovL3d3dy5yLWJsb2dnZXJzLmNvbS8yMDIxLzA1L2hvdy10by1nZW5lcmF0ZS1jb3JyZWxhdGVkLWRhdGEtaW4tci8+DQogIA0KYGBge3J9DQpzZXQuc2VlZCg1KQ0KDQpsaWJyYXJ5KE1BU1MpDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkoR0dhbGx5KQ0KIyBjcmVhdGUgdGhlIHZhcmlhbmNlIGNvdmFyaWFuY2UgbWF0cml4DQpzaWdtYTwtcmJpbmQoYygxLCAwLjgsIDAuNyksIGMoMC44LCAxLCAwLjUpLCBjKDAuNywgMC41LCAxKSkNCiMgY3JlYXRlIHRoZSBtZWFuIHZlY3Rvcg0KbXU8LWMoMywgMywgMykgDQojIGdlbmVyYXRlIHRoZSBtdWx0aXZhcmlhdGUgbm9ybWFsIGRpc3RyaWJ1dGlvbg0KZGY8LWFzLmRhdGEuZnJhbWUobXZybm9ybShuPTEwMDAsIG11PW11LCBTaWdtYT1zaWdtYSkpDQpnZ3BhaXJzKGRmKQ0KaGVhZChkZikNCiMgc3VpdGFibGUgZm9yIGEgY29ycmVsYXRpb24gbWF0cml4DQpsaWJyYXJ5KGNvcnJwbG90KQ0KY29ycnBsb3Qoc2lnbWEsIGlzLmNvcnIgPSBUKQ0KYGBgDQoNCjIuIFJ1biBQQ0Egb24gdGhlbSwgbm90ZSB0aGUgc2hhcmVkIHZhcmlhbmNlDQoNCmBgYHtyfQ0KZXgucGNhIDwtIHByY29tcChkZiwgY2VudGVyID0gVFJVRSxzY2FsZSA9IFRSVUUpDQpzdW1tYXJ5KGV4LnBjYSkNCiNpbnN0YWxsLnBhY2thZ2VzKCJkZXZ0b29scyIpDQojbGlicmFyeShkZXZ0b29scykNCiNpbnN0YWxsX2dpdGh1YigidnF2L2dnYmlwbG90IikNCmxpYnJhcnkoZ2diaXBsb3QpDQpnZ2JpcGxvdDo6Z2diaXBsb3QoZXgucGNhLCB2YXIuYXhlcyA9IFQsIGFscGhhID0gMC4xKQ0KYGBgDQoNCjMuIEdlbmVyYXRlIHVuY29ycmVsYXRlZCBkYXRhIGFuZCBydW4gUENBIG9uIHRoZW06DQogIA0KYGBge3J9DQojIGNyZWF0ZSB0aGUgdmFyaWFuY2UgY292YXJpYW5jZSBtYXRyaXgNCnNpZ21hPC1yYmluZChjKDEsIDAuMDEsIC0wLjAxKSwgYygwLjAxLCAxLCAwLjAxKSwgYygtMC4wMSwgMC4wMSwgMSkpDQojIGNyZWF0ZSB0aGUgbWVhbiB2ZWN0b3INCm11PC1jKDMsIDMsIDMpIA0KIyBnZW5lcmF0ZSB0aGUgbXVsdGl2YXJpYXRlIG5vcm1hbCBkaXN0cmlidXRpb24NCmRmPC1hcy5kYXRhLmZyYW1lKG12cm5vcm0obj0xMDAwLCBtdT1tdSwgU2lnbWE9c2lnbWEpKQ0KZ2dwYWlycyhkZikNCmhlYWQoZGYpDQoNCg0KZXgucGNhIDwtIHByY29tcChkZiwgY2VudGVyID0gVFJVRSxzY2FsZSA9IFRSVUUpDQpzdW1tYXJ5KGV4LnBjYSkNCmdnYmlwbG90OjpnZ2JpcGxvdChleC5wY2EsIHZhci5heGVzID0gVCwgYWxwaGEgPSAwLjEpDQpgYGANCg0KIyMjIF9fVGFrZWF3YXlzOl9fDQoNCi0gdGhlIG1pbmltdW0gc2hhcmVkIHZhcmlhbmNlIGluIHRoZSBmaXJzdCAyIFBDcyBpcyAgMi9rICh2YXJpYWJsZXMpDQoNCi0gY29ycmVsYXRlZCBkYXRhIHByb2R1Y2UgUENzIHdpdGggZWlnZW52YWx1ZXMgbGFyZ2VyIHRoYW4gMTsgdW5jb3JyZWxhdGVkIGRhdGEgZ2l2ZSBQQ3Mgd2l0aCBlaWdlbnZhbHVlcyBjbG9zZSB0byAxLg0KDQoNCiMjIFBDQSBvbiBjb3JyZWxhdGlvbiBtYXRyaXggKHNjYWxlZCkgdnMgY292YXJpYXRpb24gbWF0cml4IChub24tc2NhbGVkKQ0KDQoxLiBHZW5lcmF0ZSBjb3JyZWxhdGVkIGRhdGEgd2l0aCBzaW1pbGFyIHNjYWxlcw0KDQpgYGB7cn0NCnNpZ21hPC1yYmluZChjKDEsIDAuOSwgMC41KSwgYygwLjksIDEsIDAuMSksIGMoMC41LCAwLjEsIDEpKQ0KbXU8LWMoMywgMTMsIDIzKSANCmRmPC1hcy5kYXRhLmZyYW1lKG12cm5vcm0obj0xMDAwLCBtdT1tdSwgU2lnbWE9c2lnbWEpKQ0KaGVhZChkZikNCmNvdihkZikNCmNvcihkZikNCmdncGFpcnMoZGYpDQpjb3JycGxvdChzaWdtYSwgaXMuY29yciA9IFQpDQpgYGANCg0KUnVuIGEgUENBIHdpdGggc2NhbGluZyAoc3RhbmRhcmRpemF0aW9uKToNCg0KYGBge3J9DQpleC5wY2EgPC0gcHJjb21wKGRmLCBjZW50ZXIgPSBUUlVFLHNjYWxlID0gVFJVRSkNCnN1bW1hcnkoZXgucGNhKQ0KI2dnYmlwbG90KGV4LnBjYSwgdmFyLmF4ZXMgPSBULCBhbHBoYSA9IDAuMSkNCmYxIDwtIGdnYmlwbG90OjpnZ2JpcGxvdChleC5wY2EsIHZhci5heGVzID0gVCwgYWxwaGEgPSAwLjEpDQpgYGANCg0KUnVuIGEgUENBIHdpdGhPVVQgc2NhbGluZyAoc3RhbmRhcmRpemF0aW9uKToNCg0KYGBge3J9DQpleC5wY2EyIDwtIHByY29tcChkZiwgY2VudGVyID0gVFJVRSxzY2FsZSA9IEZBTFNFKQ0Kc3VtbWFyeShleC5wY2EyKQ0KI2dnYmlwbG90KGV4LnBjYTIsIHZhci5heGVzID0gVCwgYWxwaGEgPSAwLjEpDQpmMiA8LSBnZ2JpcGxvdDo6Z2diaXBsb3QoZXgucGNhMiwgdmFyLmF4ZXMgPSBULCBhbHBoYSA9IDAuMSkNCmBgYA0KDQpSdW4gYSBQQ0Egd2l0aE9VVCBldmVuIGNlbnRlcmluZyB0aGUgZGF0YToNCg0KYGBge3J9DQpleC5wY2EzIDwtIHByY29tcChkZiwgY2VudGVyID0gRkFMU0Usc2NhbGUgPSBGQUxTRSkNCnN1bW1hcnkoZXgucGNhMykNCiNnZ2JpcGxvdChleC5wY2EzLCB2YXIuYXhlcyA9IFQsIGFscGhhID0gMC4xKQ0KZjMgPC0gZ2diaXBsb3Q6OmdnYmlwbG90KGV4LnBjYTMsIHZhci5heGVzID0gVCwgYWxwaGEgPSAwLjEpDQpgYGANCmBgYHtyfQ0KbGlicmFyeShwYXRjaHdvcmspDQpmMSArIGYyICsgZjMNCmBgYA0KDQoyLiBOb3cgZ2VuZXJhdGUgZGF0YSB3aXRoIHRoZSBzYW1lIG1lYW4gYW5kIHNpbWlsYXIgYnV0IHVuZXF1YWwgdmFyaWFuY2VzOg0KDQpgYGB7cn0NCnNpZ21hPC1yYmluZChjKDEsIDAuOSwgMC41KSwgYygwLjksIDIsIDAuMSksIGMoMC41LCAwLjEsIDMpKQ0KbXU8LWMoMywgMTMsIDIzKSANCmRmPC1hcy5kYXRhLmZyYW1lKG12cm5vcm0obj0xMDAwLCBtdT1tdSwgU2lnbWE9c2lnbWEpKQ0KaGVhZChkZikNCmNvdihkZikNCmNvcihkZikNCmdncGFpcnMoZGYpDQpgYGANCg0KUnVuIGEgUENBIHdpdGggc2NhbGluZyAoc3RhbmRhcmRpemF0aW9uKToNCg0KYGBge3J9DQpleC5wY2EgPC0gcHJjb21wKGRmLCBjZW50ZXIgPSBUUlVFLHNjYWxlID0gVFJVRSkNCnN1bW1hcnkoZXgucGNhKQ0KI2dnYmlwbG90KGV4LnBjYSwgdmFyLmF4ZXMgPSBULCBhbHBoYSA9IDAuMSkNCmYxIDwtIGdnYmlwbG90OjpnZ2JpcGxvdChleC5wY2EsIHZhci5heGVzID0gVCwgYWxwaGEgPSAwLjEpDQpgYGANCg0KUnVuIGEgUENBIHdpdGhPVVQgc2NhbGluZyAoc3RhbmRhcmRpemF0aW9uKToNCg0KYGBge3J9DQpleC5wY2EyIDwtIHByY29tcChkZiwgY2VudGVyID0gVFJVRSxzY2FsZSA9IEZBTFNFKQ0Kc3VtbWFyeShleC5wY2EyKQ0KI2dnYmlwbG90KGV4LnBjYTIsIHZhci5heGVzID0gVCwgYWxwaGEgPSAwLjEpDQpmMiA8LSBnZ2JpcGxvdDo6Z2diaXBsb3QoZXgucGNhMiwgdmFyLmF4ZXMgPSBULCBhbHBoYSA9IDAuMSkNCmBgYA0KDQpSdW4gYSBQQ0Egd2l0aE9VVCBldmVuIGNlbnRlcmluZyB0aGUgZGF0YToNCg0KYGBge3J9DQpleC5wY2EzIDwtIHByY29tcChkZiwgY2VudGVyID0gRkFMU0Usc2NhbGUgPSBGQUxTRSkNCnN1bW1hcnkoZXgucGNhMykNCiNnZ2JpcGxvdChleC5wY2EzLCB2YXIuYXhlcyA9IFQsIGFscGhhID0gMC4xKQ0KZjMgPC0gZ2diaXBsb3Q6OmdnYmlwbG90KGV4LnBjYTMsIHZhci5heGVzID0gVCwgYWxwaGEgPSAwLjEpDQpgYGANCmBgYHtyfQ0KbGlicmFyeShwYXRjaHdvcmspDQpmMSArIGYyICsgZjMNCmBgYA0KDQojIyMgX19UYWtlYXdheXM6X18NCg0KLSB0byBnaXZlIG1vcmUgdmFyaWFuY2UgdG8gUEMxLCBzdGFuZGFyZGl6ZSBzb2x1dGlvbnM7DQotIHRvIGxldmVsIG91dCB2YXJpYWJsZXMnIGltcGFjdCBvbiBwcmluY2lwYWwgY29tcG9uZW50cywgc2NhbGUgdGhlbS4NCg0KDQojIyMgX19BcHBlbmRpeF9fDQoNCkEgdmFyaWFuY2UtY292YXJpYW5jZSBtYXRyaXg6IGhhcyB2YXJpYW5jZXMgb24gdGhlIGRpYWdvbmFsIGFuZCBjb3ZhcmlhbmNlcyBpbiBvdGhlciBjZWxscyAoc3ltbWV0cmljKQ0KPGh0dHBzOi8vd3d3LnJlc2VhcmNoZ2F0ZS5uZXQvcHVibGljYXRpb24vMjM3MDE0NTc5L2ZpZ3VyZS9maWcxL0FTOjIxMzk2MzQ0ODAzMzI4MEAxNDI4MDI0MjY5MzkyL1ZhcmlhbmNlLWNvdmFyaWFuY2UtbWF0cml4LWRlcGljdGluZy1ob21vZ2VuZWl0eS1vZi12YXJpYW5jZS1hbmQtY29tcG91bmQtc3ltbWV0cnkucG5nPg0KDQoNCl9Db21wdXRlciBsYWIgSmFuIDI4IHBsYW5uZWQ6IG1hc3RlciBiaXBsb3RzIGFuZCByZXBsaWNhdGUgdGhpcyBhbmFseXNpcyBvbiB5b3VyIGRhdGENCjxodHRwczovL3d3dy5kYXRhY2FtcC5jb20vdHV0b3JpYWwvcGNhLWFuYWx5c2lzLXI+Xw0K