Visualisierung konfirmatorische Faktorenanalyse

Shiny App

Author

Dominik Vollbracht

Shiny App

Generell

Diese App illustriert die Komponenten einer konfirmatorischen Faktorenanalyse (CFA). Du kannst die Parameter der CFA über die Schieberegler auf der linken Seite anpassen. Über die beiden Tabs kannst Du entscheiden, ob beobachtete Werte im Streudiagramm angezeigt werden sollen oder nicht.

Parameter

Die Parameter der CFA sind wie folgt definiert:

\(\alpha\): Der Intercept des manifesten Indikators. Der Intercept gibt den erwarteten Wert des manifesten Indikators an, wenn die latente Variable den Wert Null annimmt. Dies ist nur bei Modellen mit Mittelwertsstruktur relevant.

\(\lambda\): Die Ladung des manifesten Indikators auf die latente Variable. Die Ladung gibt an, wie stark der manifeste Indikator mit der latenten Variable zusammenhängt. Wenn die latente Variable um eine Einheit steigt, ändert sich der manifeste Indikator um den Wert der Ladung. Eine Ladung von Null bedeutet, dass der manifeste Indikator nicht mit der latenten Variable zusammenhängt.

\(\epsilon\): Die Residualvarianz des manifesten Indikators. Die Residualvarianz gibt an, wie viel Varianz im manifesten Indikator nicht durch die latente Variable erklärt wird. Eine hohe Residualvarianz bedeutet, dass der manifeste Indikator viele andere Einflüsse hat, die nicht durch die latente Variable erklärt werden.

App

Es kann etwas dauern, bis die App geladen ist (ca. 10s-30s), bitte habe kurz Geduld.

#| '!! shinylive warning !!': |
#|   shinylive does not work in self-contained HTML documents.
#|   Please set `embed-resources: false` in your metadata.
#| standalone: true
#| viewerHeight: 1000
#| viewerWidth: 3000

library(shiny)
library(bslib)
library(shinylive)
library(shinythemes)

# helper function
cfa_plot <- function(int, sl, res, p = TRUE) {
  intercept <- int
  slope <- sl
  residsd <- sqrt(res)
  
  x <- rnorm(n = 150, mean = 0, sd = 1)
  y <- intercept + slope*x + rnorm(150, mean = 0, sd = residsd)
  d <- data.frame(x, y)
  
  plot(
    d, type = "n",
    xlab = "Eta (latente Variable)",
    ylab = "manifester Indikator",
    cex.lab = 1.2,
    font.lab = 2,
    xlim = c(-2, 2),
    ylim = c(0, 7)
  )
  
  if (p) points(d, pch = 16, cex = 0.6, col = "gray70")
  abline(v = 0, col = "gray50", lty = "dashed")
  
  lines(c(-3, 3), c(intercept - 3*slope, intercept + 3*slope))
  lines(c(-1.5, -0.5), rep(intercept-1.5*slope, 2), col = "dodgerblue4")
  lines(c(-0.5, -0.5), c(intercept-1.5*slope, intercept-0.5*slope), col = "steelblue4")
  
  points(0, intercept, lwd = 4, cex = 1.2, col = "darkred")  
  text(-0.35, intercept-slope, expression(lambda), cex = 1.3, font = 2, col = "steelblue4")
  
  text(-1, intercept-1.5*slope - 0.3, "1", cex = 1, col = "steelblue4", font = 2) 
  text(0.2, intercept, expression(alpha), cex = 1.3, col = "darkred", font = 2)
}

# UI
ui <- fluidPage(

  # -> siehe Abschnitt zum Theme unten
  theme = bslib::bs_theme(bootswatch = "cerulean"),

  tags$head(
    # Seite insgesamt schmaler machen
    tags$style(HTML("
      .container-fluid {
        max-width: 1000px;
        margin: 0 auto;
      }
    ")),
    tags$style(HTML("
      .js-irs-0 .irs-single, .js-irs-0 .irs-bar-edge, .js-irs-0 .irs-bar {background: darkred}
      .js-irs-1 .irs-single, .js-irs-1 .irs-bar-edge, .js-irs-1 .irs-bar {background: steelblue4}
      .js-irs-2 .irs-single, .js-irs-2 .irs-bar-edge, .js-irs-2 .irs-bar {background: gray}
    "))
  ),

  # titlePanel("Beta Item Response Theory With Response Styles: Illustration"),

  sidebarLayout(
    sidebarPanel(
      width = 4,
      sliderInput("interceptA", 
        withMathJax(HTML(
            "\\(\\alpha\\): Intercept"
          )),
        min = 2, max = 5, value = 3.5, step = 0.1),
      sliderInput("slopeB",
        withMathJax(HTML(
            "\\(\\lambda\\): Ladung"
          )),
        min = -2, max = 2, value = 1, step = 0.1),
      sliderInput("residD",
        withMathJax(HTML(
            "\\(\\epsilon\\): Residualvarianz"
          )),
        min = 0, max = 3, value = 0.5, step = 0.1)
    ),

    mainPanel(
      width = 8,
      tabsetPanel(
        tabPanel("ohne beobachtete Werte",
          plotOutput("loading", height = "400px"),
          textOutput("instruction")
        ),
        tabPanel("mit beobachteten Werten",
          plotOutput("loadingRes", height = "400px"),
          textOutput("instruction2")
        )
      )
    )
  )
)

# Server
server <- function(input, output) {

  output$loading <- renderPlot({
    cfa_plot(
      int = input$interceptA,
      sl  = input$slopeB,
      res = input$residD,
      p   = FALSE
    )
  })

  output$loadingRes <- renderPlot({
    cfa_plot(
      int = input$interceptA,
      sl  = input$slopeB,
      res = input$residD,
      p   = TRUE
    )
  })
}

shinyApp(ui = ui, server = server)