Theory of PCA - Answer Key

Authors

Noor Sohail

Will Gammerdinger

Published

September 5, 2025

Exercise 1

There are a couple properties of variance and co-variance that we can verify:

cov(X,X) is equal to var(X). We can observe this is mathematically below:

\[ \operatorname{cov}(X, X) = \frac{1}{n-1} \sum_{i=1}^{n} (x_i - \bar{x})(x_i - \bar{x}) = \frac{1}{n-1} \sum_{i=1}^{n} (x_i - \bar{x})^{2} = \operatorname{var}(X) \]

As a result, you will sometimes see covariance matrices written as:

\[ \begin{bmatrix} \operatorname{var}(X) & \operatorname{cov}(X, Y) & \dots & \operatorname{cov}(X, Z) \\ \operatorname{cov}(Y, X) & \operatorname{var}(Y) & \dots & \operatorname{cov}(Y, Z) \\ \dots & \dots & \dots & \dots \\ \operatorname{cov}(Z, X) & \operatorname{cov}(Z, Y) & \dots & \operatorname{var}(Z) \end{bmatrix} \]

  1. Confirm this property by estimating the variance for Gene A.
# Estimate the variance for Gene A
var(recentered_expression_matrix[, "Gene_A"])
[1] 799.3333
# Estimate the variance for Gene A
print(recentered_expression_df["Gene_A"].var())
799.3333333333334
  1. Now estimate the covariance for Gene A and Gene A
# Estimate the covariance for Gene A and Gene A
cov(recentered_expression_matrix[, "Gene_A"], recentered_expression_matrix[, "Gene_A"])
[1] 799.3333
# Estimate the covariance for Gene A and Gene A
print(recentered_expression_df["Gene_A"].cov(recentered_expression_df["Gene_A"]))
799.3333333333333
  1. Is the value the same? Does it match the value in the covariance matrix for Gene A and Gene A?
# Extract the covariance estimate of Gene A and Gene A from the covariance matrix
cov_matrix["Gene_A", "Gene_A"]
[1] 799.3333
# Extract the covariance estimate of Gene A and Gene A from the covariance matrix
print(cov_matrix.loc["Gene_A", "Gene_A"])
799.3333333333333
  1. cov(X,Y) is equal to cov(Y,X). We can observe this is mathematically below:

\[ \operatorname{cov}(X, Y) = \frac{1}{n-1} \sum_{i=1}^{n} (x_i - \bar{x})(y_i - \bar{y}) = \frac{1}{n-1} \sum_{i=1}^{n} (y_i - \bar{y})(x_i - \bar{x}) = \operatorname{cov}(Y, X) \]

Estimate the covariance of Gene B and Gene A.

# Estimate the covariance of Gene B and Gene A
cov(recentered_expression_tibble[, "Gene_B"], recentered_expression_tibble[, "Gene_A"])
         Gene_A
Gene_B 584.6667
# Estimate the covariance of Gene B and Gene A
print(recentered_expression_df["Gene_B"].cov(recentered_expression_df["Gene_A"]))
584.6666666666666
  1. How does this compare to the covariance that we estimated by hand?

It is the same.

Exercise 2

When looking at the percent explained by each principal component, the first principal component should explain the most and each of the following principal components should explain less than the previous principal component. Let’s have a look at our pct_var_explained object, are our results congruent with this expectation?

# Print the percent variance explained
pct_var_explained
    PC_1     PC_2 
96.16723  3.83277 
# Print the percent variance explained
print(pct_var_explained)
[96.16723045  3.83276955]

Yes, PC_1 explains python pct_var_explained[0] and PC_2 explains python pct_var_explained[1]

Exercise 3

Create a plot of the Principal Components Analysis derived from prcomp()/PCA(). Is it the same as the plot we derived except only reflected over the x-axis (R)/rotated 180°(Python)?

# Create a tibble to hold the PC scores prcomp() found and also make the Cell IDs into a column
prcomp_pc_scores_tibble <- prcomp_PCA$x %>% 
  as.data.frame() %>% 
  rownames_to_column("cells") %>% 
  as_tibble()

# Plot the PC scores found by prcomp()
ggplot(prcomp_pc_scores_tibble, aes(x = PC1, y = PC2, label = cells)) +
  geom_point( color = "cornflowerblue") +
  geom_text(hjust = 0, vjust = -1) +
  theme_bw() +
  xlim(-50, 50) +
  ylim(-12, 12) +
  xlab(paste0("PC 1 (Variance Explained ", round(prcomp_eigenvalues["PC_1"]/sum(prcomp_eigenvalues) * 100, digits = 2),"%)")) +
  ylab(paste0("PC 2 (Variance Explained ", round(prcomp_eigenvalues["PC_2"]/sum(prcomp_eigenvalues) * 100, digits = 2),"%)")) +
  ggtitle("PCA of Expression Values from Four Cells") +
  theme(plot.title = element_text(hjust = 0.5))
Figure 1: Scatterplot of PC_1 vs. PC_2 from prcomp().

Yes, it is the same plot just reflected over the x-axis.

sns.set(style="whitegrid")

# Initialize a plot with a specific size
plt.figure(figsize = (8, 6))

# Add a scatterplot layer to the plot, coloring points by genotype
PCA_sklearn_plot = sns.scatterplot(data = pc_scores_sklearn_df,
                x = "PC_1", 
                y = "PC_2")

# Set x-axis limits
plt.xlim(left = -55,
         right = 55)

# Set y-axis limits
plt.ylim(bottom = -15,
         top = 15)

# Add cell labels at PC coordinates
for cell_id, row in pc_scores_sklearn_df.iterrows():
    PCA_sklearn_plot.text(
        x = row["PC_1"] + 1,
        y = row["PC_2"] + 1,
        s = cell_id,
        fontsize = 9,
        ha = "left",
        va = "bottom"
    )

# Change the text of the x-axis label
plt.xlabel(xlabel = f"PC 1 (Variance Explained {pca_sklearn.explained_variance_ratio_[0].round(4) * 100}%)")

# Change the text of the y-axis label
plt.ylabel(ylabel = f"PC 2 (Variance Explained {pca_sklearn.explained_variance_ratio_[1].round(4) * 100}%)")

# Add plot title
plt.title(label = "PCA of Expression Values from Four Cells")

# Render the plot
plt.show()
Figure 2: Scatterplot of PC_1 vs. PC_2 from scikit-learn.

Yes, it is the same plot just rotated 180°.