Pfeile der zugrunde liegenden Variablen im PCA-Biplot in R.


11

Auf die Gefahr hin, die Frage softwarespezifisch zu machen, und mit der Entschuldigung ihrer Allgegenwart und Eigenheiten möchte ich nach der Funktion biplot()in R und insbesondere nach der Berechnung und Darstellung der entsprechenden, überlagerten Standardpfeile fragen zu den zugrunde liegenden Variablen.


[Um einige der Kommentare zu verstehen, hatten die ursprünglich veröffentlichten Handlungen ein Problem von geringem Interesse und werden jetzt gelöscht.]


Ich kann nicht herausfinden, wie du deine grünen Pfeile bekommen hast. Sie sind nicht korrekt. Die Tatsache, dass das s.length Grün ca. ist. zweimal länger als die grüne Breite lässt vermuten, dass Sie die Vektoren für die nicht standardisierten Variablen gezeichnet haben. Dies kann bei einem auf Korrelationen basierenden Biplot von PCA nicht passieren.
ttnphns

Rote Pfeile scheinen korrekt zu sein. Siehe: Sie sind gleich lang und symmetrisch zu PC2. Dies ist die einzig mögliche Position, wenn Sie PCA mit nur 2 Variablen und basierend auf Korrelationen (dh standardisierten Variablen) durchführen. In PCA basierend auf Korrelationen sind Ladungen (die Koordinate der Pfeile) die Korrelationen zwischen den PCs und den Variablen. In Ihrem Beispiel sind die Ladungen (vars by PCs) : .74752, .66424; -.74752, .66424.
ttnphns

@ttnphns Ja, die roten Pfeile sind das, was ich zu reproduzieren versuche (sie sind korrekt), und sie werden mit dem biplot(name_of_the_PCA)Aufruf in R dargestellt , was in diesem Fall der Fall ist biplot(PCA). Ich hatte die Daten zentriert und skaliert.
Antoni Parellada

Also, was ist deine Frage? Wie berechnet man die Koordinaten für die roten Pfeile? Sie sollten die PCA- Ladungen sein . Manchmal werden Eigenvektoren aufgezeichnet (Ihr R-Befehl hat das wahrscheinlich getan?). Der einvernehmliche und sinnvolle Weg besteht jedoch darin, Ladungen zu zeichnen .
ttnphns

@ttnphns Das Zeichnen der Eigenvektoren (ich nehme an, es ist dasselbe wie die Ladungen) gibt mir die richtige Ausrichtung (danke), aber nicht die gleiche Größe wie die roten Pfeile (ich füge das Bild in das OP ein).
Antoni Parellada

Antworten:


19

Erwägen Sie, den Beitrag von @ amoeba und @ttnphns zu verbessern . Vielen Dank für Ihre Hilfe und Ihre Ideen.


Das Folgende stützt sich auf den Iris-Datensatz in R und insbesondere auf die ersten drei Variablen (Spalten) : Sepal.Length, Sepal.Width, Petal.Length.

Ein Biplot kombiniert ein Belastungsdiagramm (nicht standardisierte Eigenvektoren) - in Beton die ersten beiden Belastungen - und ein Bewertungsdiagramm (gedrehte und erweiterte Datenpunkte, dargestellt in Bezug auf Hauptkomponenten). Unter Verwendung des gleichen Datensatzes beschreibt @amoeba 9 mögliche Kombinationen von PCA-Biplots basierend auf 3 möglichen Normalisierungen des Score-Plots der ersten und zweiten Hauptkomponente und 3 Normalisierungen des Ladeplots (Pfeile) der Anfangsvariablen. Um zu sehen, wie R mit diesen möglichen Kombinationen umgeht, ist es interessant, die biplot()Methode zu betrachten:


Zuerst die lineare Algebra zum Kopieren und Einfügen bereit:

X = as.matrix(iris[,1:3])             # Three first variables of Iris dataset
CEN = scale(X, center = T, scale = T) # Centering and scaling the data
PCA = prcomp(CEN)

# EIGENVECTORS:
(evecs.ei = eigen(cor(CEN))$vectors)       # Using eigen() method
(evecs.svd = svd(CEN)$v)                   # PCA with SVD...
(evecs = prcomp(CEN)$rotation)             # Confirming with prcomp()

# EIGENVALUES:
(evals.ei = eigen(cor(CEN))$values)        # Using the eigen() method
(evals.svd = svd(CEN)$d^2/(nrow(X) - 1))   # and SVD: sing.values^2/n - 1
(evals = prcomp(CEN)$sdev^2)               # with prcomp() (needs squaring)

# SCORES:
scr.svd = svd(CEN)$u %*% diag(svd(CEN)$d)  # with SVD
scr = prcomp(CEN)$x                        # with prcomp()
scr.mm = CEN %*% prcomp(CEN)$rotation      # "Manually" [data] [eigvecs]

# LOADINGS:

loaded = evecs %*% diag(prcomp(CEN)$sdev)  # [E-vectors] [sqrt(E-values)]

1. Reproduzieren des Ladeplots (Pfeile):

Hier hilft die geometrische Interpretation dieses Beitrags von @ttnphns sehr. Die Notation des Diagramms im Beitrag wurde beibehalten: steht für die Variable im Betreffraum . ist der entsprechende Pfeil, der letztendlich aufgetragen ist; und die Koordinaten und sind die Komponenten, die eine Variable in Bezug auf und :h ' a 1 a 2 V PC 1 PC 2VSepal L.ha1a2VPC1PC2


Geben Sie hier die Bildbeschreibung ein


Die Komponente der Variablen Sepal L.in Bezug auf lautet dann:PC1

a1=hcos(ϕ)

welche, wenn die Bewertungen in Bezug auf - nennen wir sie - standardisiert sind, so dass ihreS 1PC1S1

S1=1nscores12=1 , die obige Gleichung entspricht dem Punktprodukt :VS1

a1=VS1=VS1cos(ϕ)(1)=h×1×cos(ϕ)

Da ,V=x2

Var(V)=x2n1=Vn1V=h=var(V)n1.

Gleichfalls,

S1=1=var(S1)n1.

Zurück zu Gl. ,(1)

a1=h×1×cos(ϕ)=var(V)var(S1)cos(θ)(n1)

cos(ϕ) kann daher ein betrachtet werden Pearson Korrelationskoeffizient , , mit dem Vorbehalt , dass ich die Falten des nicht verstehen - Faktors.rn1

Die roten Pfeile von duplizieren und blau überlappen biplot()

par(mfrow = c(1,2)); par(mar=c(1.2,1.2,1.2,1.2))

biplot(PCA, cex = 0.6, cex.axis = .6, ann = F, tck=-0.01) # R biplot
# R biplot with overlapping (reproduced) arrows in blue completely covering red arrows:
biplot(PCA, cex = 0.6, cex.axis = .6, ann = F, tck=-0.01) 
arrows(0, 0,
       cor(X[,1], scr[,1]) * 0.8 * sqrt(nrow(X) - 1), 
       cor(X[,1], scr[,2]) * 0.8 * sqrt(nrow(X) - 1), 
       lwd = 1, angle = 30, length = 0.1, col = 4)
arrows(0, 0,
       cor(X[,2], scr[,1]) * 0.8 * sqrt(nrow(X) - 1), 
       cor(X[,2], scr[,2]) * 0.8 * sqrt(nrow(X) - 1), 
       lwd = 1, angle = 30, length = 0.1, col = 4)
arrows(0, 0,
       cor(X[,3], scr[,1]) * 0.8 * sqrt(nrow(X) - 1), 
       cor(X[,3], scr[,2]) * 0.8 * sqrt(nrow(X) - 1), 
       lwd = 1, angle = 30, length = 0.1, col = 4)

Geben Sie hier die Bildbeschreibung ein

Sehenswürdigkeiten:

  • Die Pfeile können als Korrelation der ursprünglichen Variablen mit den von den ersten beiden Hauptkomponenten erzeugten Bewertungen reproduziert werden.
  • Alternativ kann dies wie im ersten Plot in der zweiten Zeile erreicht werden, der in @ amoebas Beitrag mit ist:VS

Geben Sie hier die Bildbeschreibung ein

oder im R-Code:

    biplot(PCA, cex = 0.6, cex.axis = .6, ann = F, tck=-0.01) # R biplot
    # R biplot with overlapping arrows in blue completely covering red arrows:
    biplot(PCA, cex = 0.6, cex.axis = .6, ann = F, tck=-0.01) 
    arrows(0, 0,
       (svd(CEN)$v %*% diag(svd(CEN)$d))[1,1] * 0.8, 
       (svd(CEN)$v %*% diag(svd(CEN)$d))[1,2] * 0.8, 
       lwd = 1, angle = 30, length = 0.1, col = 4)
    arrows(0, 0,
       (svd(CEN)$v %*% diag(svd(CEN)$d))[2,1] * 0.8, 
       (svd(CEN)$v %*% diag(svd(CEN)$d))[2,2] * 0.8, 
       lwd = 1, angle = 30, length = 0.1, col = 4)
    arrows(0, 0,
       (svd(CEN)$v %*% diag(svd(CEN)$d))[3,1] * 0.8, 
       (svd(CEN)$v %*% diag(svd(CEN)$d))[3,2] * 0.8, 
       lwd = 1, angle = 30, length = 0.1, col = 4)

oder sogar noch ...

    biplot(PCA, cex = 0.6, cex.axis = .6, ann = F, tck=-0.01) # R biplot
    # R biplot with overlapping (reproduced) arrows in blue completely covering red arrows:
    biplot(PCA, cex = 0.6, cex.axis = .6, ann = F, tck=-0.01) 
    arrows(0, 0,
       (loaded)[1,1] * 0.8 * sqrt(nrow(X) - 1), 
       (loaded)[1,2] * 0.8 * sqrt(nrow(X) - 1), 
       lwd = 1, angle = 30, length = 0.1, col = 4)
    arrows(0, 0,
       (loaded)[2,1] * 0.8 * sqrt(nrow(X) - 1), 
       (loaded)[2,2] * 0.8 * sqrt(nrow(X) - 1), 
       lwd = 1, angle = 30, length = 0.1, col = 4)
    arrows(0, 0,
       (loaded)[3,1] * 0.8 * sqrt(nrow(X) - 1), 
       (loaded)[3,2] * 0.8 * sqrt(nrow(X) - 1), 
       lwd = 1, angle = 30, length = 0.1, col = 4)

Verbindung mit der geometrischen Erklärung von Ladungen durch @ttnphns oder diesem anderen informativen Beitrag auch durch @ttnphns .

  • Es gibt einen Skalierungsfaktor : sqrt(nrow(X) - 1), der ein bisschen rätselhaft bleibt.

  • 0.8 hat mit der Schaffung von Platz für das Etikett zu tun - siehe diesen Kommentar hier :

Außerdem sollte man sagen, dass die Pfeile so gezeichnet sind, dass die Mitte der Textbeschriftung dort ist, wo sie sein sollte! Die Pfeile werden dann vor dem Plotten mit 0,80,8 multipliziert, dh alle Pfeile sind kürzer als sie sein sollten, vermutlich um eine Überlappung mit der Textbeschriftung zu vermeiden (siehe Code für biplot.default). Ich finde das äußerst verwirrend. - Amöbe 19. März 15 um 10:06 Uhr


2. Zeichnen des biplot()Punktediagramms (und der Pfeile gleichzeitig):

Die Achsen werden auf die Einheitssumme der Quadrate skaliert, die dem ersten Diagramm der ersten Zeile auf @ amöbens Post entspricht , das reproduziert werden kann, indem die Matrix der svd-Zerlegung (dazu später mehr) - " Spalten von : Dies sind Hauptkomponenten, die auf die Einheitssumme der Quadrate skaliert sind. "UU

Geben Sie hier die Bildbeschreibung ein

Bei der Biplot-Konstruktion spielen auf der unteren und oberen horizontalen Achse zwei verschiedene Skalen eine Rolle:

Geben Sie hier die Bildbeschreibung ein

Die relative Skalierung ist jedoch nicht sofort ersichtlich und erfordert eine Untersuchung der Funktionen und Methoden:

biplot()Zeichnet Scores als Spalten von in SVD, die orthogonale Einheitsvektoren sind:U

> scr.svd = svd(CEN)$u %*% diag(svd(CEN)$d) 
> U = svd(CEN)$u
> apply(U, 2, function(x) sum(x^2))
[1] 1 1 1

Während die prcomp()Funktion in R die auf ihre Eigenwerte skalierten Scores zurückgibt:

> apply(scr, 2, function(x) var(x))         # pr.comp() scores scaled to evals
       PC1        PC2        PC3 
2.02142986 0.90743458 0.07113557 
> evals                                     #... here is the proof:
[1] 2.02142986 0.90743458 0.07113557

Daher können wir die Varianz durch Teilen durch die Eigenwerte auf skalieren :1

> scr_var_one = scr/sqrt(evals)[col(scr)]  # to scale to var = 1
> apply(scr_var_one, 2, function(x) var(x)) # proved!
[1] 1 1 1

Da die Summe der Quadrate jedoch , müssen wir durch dividieren, weil:1n1

var(scr_var_one)=1=1nscr_var_onen1
> scr_sum_sqrs_one = scr_var_one / sqrt(nrow(scr) - 1) # We / by sqrt n - 1.
> apply(scr_sum_sqrs_one, 2, function(x) sum(x^2))     #... proving it...
PC1 PC2 PC3 
  1   1   1

Zu beachten ist, dass die Verwendung des Skalierungsfaktors später in geändert wird, wenn die Definition der Erklärung in der Tatsache zu liegen scheint, dassn1nlan

prcompverwendet : "Im Gegensatz zu Princomp werden Varianzen mit dem üblichen Divisor berechnet ".n1n1


Nachdem Sie alle ifAussagen und andere Reinigungsmittel entfernt haben, gehen Sie biplot()wie folgt vor:

X   = as.matrix(iris[,1:3])                    # The original dataset
CEN = scale(X, center = T, scale = T)          # Centered and scaled
PCA = prcomp(CEN)                              # PCA analysis

par(mfrow = c(1,2))                            # Splitting the plot in 2.
biplot(PCA)                                    # In-built biplot() R func.

# Following getAnywhere(biplot.prcomp):

choices = 1:2                                  # Selecting first two PC's
scale = 1                                      # Default
scores= PCA$x                                  # The scores
lam = PCA$sdev[choices]                        # Sqrt e-vals (lambda) 2 PC's
n = nrow(scores)                               # no. rows scores
lam = lam * sqrt(n)                            # See below.

# at this point the following is called...
# biplot.default(t(t(scores[,choices])      /  lam), 
#                t(t(x$rotation[,choices]) *   lam))

# Following from now on getAnywhere(biplot.default):

x = t(t(scores[,choices])       / lam)         # scaled scores
# "Scores that you get out of prcomp are scaled to have variance equal to      
#  the eigenvalue. So dividing by the sq root of the eigenvalue (lam in 
#  biplot) will scale them to unit variance. But if you want unit sum of 
#  squares, instead of unit variance, you need to scale by sqrt(n)" (see comments).
# > colSums(x^2)
# PC1       PC2 
# 0.9933333 0.9933333    # It turns out that the it's scaled to sqrt(n/(n-1)), 
# ...rather than 1 (?) - 0.9933333=149/150

y = t(t(PCA$rotation[,choices]) * lam)         # scaled eigenvecs (loadings)


n = nrow(x)                                    # Same as dataset (150)
p = nrow(y)                                    # Three var -> 3 rows

# Names for the plotting:

xlabs = 1L:n
xlabs = as.character(xlabs)                    # no. from 1 to 150 
dimnames(x) = list(xlabs, dimnames(x)[[2L]])   # no's and PC1 / PC2

ylabs = dimnames(y)[[1L]]                      # Iris species
ylabs = as.character(ylabs)
dimnames(y) <- list(ylabs, dimnames(y)[[2L]])  # Species and PC1/PC2

# Function to get the range:
unsigned.range = function(x) c(-abs(min(x, na.rm = TRUE)), 
                                abs(max(x, na.rm = TRUE)))
rangx1 = unsigned.range(x[, 1L])               # Range first col x
# -0.1418269  0.1731236
rangx2 = unsigned.range(x[, 2L])               # Range second col x
# -0.2330564  0.2255037
rangy1 = unsigned.range(y[, 1L])               # Range 1st scaled evec
# -6.288626   11.986589
rangy2 = unsigned.range(y[, 2L])               # Range 2nd scaled evec
# -10.4776155   0.8761695

(xlim = ylim = rangx1 = rangx2 = range(rangx1, rangx2))
# range(rangx1, rangx2) = -0.2330564  0.2255037

# And the critical value is the maximum of the ratios of ranges of 
# scaled e-vectors / scaled scores:

(ratio = max(rangy1/rangx1, rangy2/rangx2)) 
# rangy1/rangx1   =   26.98328    53.15472
# rangy2/rangx2   =   44.957418   3.885388
# ratio           =   53.15472

par(pty = "s")                                 # Calling a square plot

# Plotting a box with x and y limits -0.2330564  0.2255037
# for the scaled scores:

plot(x, type = "n", xlim = xlim, ylim = ylim)  # No points
# Filling in the points as no's and the PC1 and PC2 labels:
text(x, xlabs) 
par(new = TRUE)                                # Avoids plotting what follows separately

# Setting now x and y limits for the arrows:

(xlim = xlim * ratio)  # We multiply the original limits x ratio
# -16.13617  15.61324
(ylim = ylim * ratio)  # ... for both the x and y axis
# -16.13617  15.61324

# The following doesn't change the plot intially...
plot(y, axes = FALSE, type = "n", 
     xlim = xlim, 
     ylim = ylim, xlab = "", ylab = "")

# ... but it does now by plotting the ticks and new limits...
# ... along the top margin (3) and the right margin (4)
axis(3); axis(4)
text(y, labels = ylabs, col = 2)  # This just prints the species

arrow.len = 0.1                   # Length of the arrows about to plot.

# The scaled e-vecs are further reduced to 80% of their value
arrows(0, 0, y[, 1L] * 0.8, y[, 2L] * 0.8, 
       length = arrow.len, col = 2)

die wie erwartet die biplot()Ausgabe, wie sie direkt aufgerufen wird (rechtes Bild biplot(PCA)unten), in all ihren unberührten ästhetischen Mängeln reproduziert (rechtes Bild unten) :

Geben Sie hier die Bildbeschreibung ein

Sehenswürdigkeiten:

  • Die Pfeile sind auf einer Skala aufgetragen, die sich auf das maximale Verhältnis zwischen dem skalierten Eigenvektor jeder der beiden Hauptkomponenten und ihren jeweiligen skalierten Bewertungen (der ratio) bezieht . AS @amoeba Kommentare:

Das Streudiagramm und das "Pfeildiagramm" sind so skaliert, dass die größte (im absoluten Wert) x- oder y-Pfeilkoordinate der Pfeile genau gleich der größten (im absoluten Wert) x- oder y-Koordinate der gestreuten Datenpunkte war

  • Wie oben erwartet, können die Punkte direkt als Punktzahlen in der Matrix der SVD dargestellt werden:U


1
+1, gute Studie. Ich Rhabe Ihrer Frage ein Tag hinzugefügt , weil sich die verwirrende Angelegenheit (nämlich der Skalierungskoeffizient) als teilweise R-spezifisch erwiesen hat. Im Allgemeinen konnten Sie sich selbst davon überzeugen, dass das PCA-Biplot ein Overlay-Streudiagramm von Komponentenbewertungen (Zeilenkoordinaten) und Komponentenrichtungskoeffizienten (Spaltenkoordinaten) ist, und da verschiedene Beträge von Standardisierungen durch "Trägheit" (Varianz) auf jedes angewendet werden können von dem auch, so können verschiedene Blicke des Biplots entstehen. Hinzufügen: In der Regel (sinnvoller) werden Ladungen als Spaltenkoordinaten (Pfeile) angezeigt.
ttnphns

1
(Forts.) Siehe meine Übersicht über Biplot , in der mit anderen Worten erklärt wird, was Sie in Ihrer netten Antwort gezeigt haben.
ttnphns

2
+ 1 danke für das Schreiben von Tutorials und mit reproduzierbarem Code für uns!
Haitao Du

Antony, hast du (wie von Hand) gezeichnet oder hast du dein Bild gezeichnet (Daten eingegeben)? Welche Software haben Sie verwendet? Es sieht gut aus.
ttnphns

@ttnphns Danke! Hier ist der Link dazu . Ich habe mich gefragt, ob Sie es verbessern und die Ladungen und PCs besser und didaktischer darstellen könnten. Fühlen Sie sich frei zu ändern (es ist ein wunderbar benutzerfreundliches Programm), und wenn Sie dies tun, teilen Sie es bitte.
Antoni Parellada
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.