codexpast-exam-of-the-mathematics-course-of-the-university-of-cambridge2005iiipaper-46-factor-rotation.py
past-exam-of-the-mathematics-course-of-the-university-of-cambridge/2005/iii/paper-46-factor-rotation.py
"""Original two-factor sketch; Python 3.14.4 / NumPy 2.3.5 / Matplotlib 3.10.7.
Output the basename PNG to CWD; respect the caller's MPLCONFIGDIR.
"""
from pathlib import Path
import numpy as np
import matplotlib.pyplot as plt
def main():
loadings = np.array([[.85, .10], [.80, .15], [.10, .85], [.15, .80]])
specific = 1 - np.sum(loadings**2, axis=1)
covariance = loadings @ loadings.T + np.diag(specific)
angle = np.pi/4
rotation = np.array([[np.cos(angle), -np.sin(angle)], [np.sin(angle), np.cos(angle)]])
rotated = loadings @ rotation
assert np.allclose(rotated @ rotated.T, loadings @ loadings.T)
colors = ["#176aa0", "#599bbd", "#b85518", "#df9661"]
fig, axes = plt.subplots(1, 3, figsize=(12, 4.5), dpi=100, facecolor="white", constrained_layout=True)
for ax, matrix, title in zip(axes[:2], [loadings, rotated], ["An interpretable factor orientation", "Equivalent loadings after rotation"]):
theta = np.linspace(0, 2*np.pi, 300)
ax.plot(np.cos(theta), np.sin(theta), color="0.75", lw=1)
for i, ((x, y), color) in enumerate(zip(matrix, colors)):
ax.annotate("", xy=(x,y), xytext=(0,0), arrowprops={"arrowstyle": "->", "color": color, "lw": 2})
offset = [(7, 7), (-10, -18), (5, 8), (9, -17)][i]
ax.annotate(f"X{i+1}", xy=(x,y), xytext=offset, textcoords="offset points", color=color, fontsize=11)
ax.axhline(0, color="0.75", lw=.7)
ax.axvline(0, color="0.75", lw=.7)
ax.set(xlim=(-1.1, 1.1), ylim=(-1.1, 1.1), xlabel="Factor 1 loading", ylabel="Factor 2 loading", title=title)
ax.set_aspect("equal")
values = np.linalg.eigvalsh(covariance)[::-1]
axes[2].plot(range(1,5), values, "o-", color="#4d5763", lw=2)
axes[2].set(xticks=range(1,5), xlabel="Component number", ylabel="Eigenvalue", title="Scree plot of the same covariance", ylim=(0, max(values)*1.2))
axes[2].spines[["top", "right"]].set_visible(False)
fig.suptitle("Factor rotation preserves covariance and communalities", fontsize=15)
fig.savefig(Path.cwd() / "paper-46-factor-rotation.png", facecolor="white", transparent=False)
plt.close(fig)
if __name__ == "__main__":
main()