Análisis de Normalidad

library(shiny)
library(ggplot2)
library(dplyr)
library(readr)

Cargar datos y analizar normalidad

library(shiny)
library(tidyverse)
library(DT)
library(ggplot2)

# UI
ui <- fluidPage(
  # Titulo y estilo
  tags$head(
    tags$style(HTML("
      .content-wrapper { padding: 20px; }
      .box { border-radius: 5px; box-shadow: 0 0 10px rgba(0,0,0,0.1); }
      h2 { color: #2C3E50; }
    "))
  ),
  
  titlePanel("Análisis Exploratorio de Datos"),
  
  sidebarLayout(
    sidebarPanel(
      # Input para cargar archivo
      fileInput("file", "Sube un archivo CSV", 
                accept = c(".csv", ".tsv"), 
                buttonLabel = "Seleccionar...",
                placeholder = "Ningún archivo seleccionado"),
      
      # Opciones de lectura del archivo
      checkboxInput("header", "¿El archivo tiene encabezado?", TRUE),
      radioButtons("sep", "Separador:",
                   choices = c(Coma = ",", "Punto y coma" = ";", Tabulador = "\t"),
                   selected = ","),
      
      # Selector de variables (se actualizará dinámicamente)
      uiOutput("varselect"),
      
      # Ajustes del histograma
      sliderInput("bins", "Número de intervalos:", min = 5, max = 100, value = 30),
      
      # Selector para prueba de normalidad
      selectInput("normality_test", "Prueba de normalidad:",
                  choices = c("Shapiro-Wilk" = "shapiro", 
                              "Kolmogorov-Smirnov" = "ks"),
                  selected = "shapiro"),
                  
      # Botón para generar análisis
      actionButton("analyze", "Analizar Datos", 
                   class = "btn-primary btn-block",
                   icon = icon("chart-bar"))
    ),
    
    mainPanel(
      tabsetPanel(
        tabPanel("Resumen de datos", 
                 h3("Vista previa de los datos"),
                 DTOutput("data_preview"),
                 hr(),
                 h3("Estadísticas descriptivas"),
                 verbatimTextOutput("summary"),
                 hr(),
                 h3("Prueba de normalidad"),
                 verbatimTextOutput("normality_result")),
        
        tabPanel("Visualización", 
                 fluidRow(
                   column(6, plotOutput("histplot")),
                   column(6, plotOutput("boxplot"))
                 ),
                 hr(),
                 plotOutput("qq_plot")),
                 
        tabPanel("Información", 
                 h3("Datos cargados"),
                 verbatimTextOutput("data_info"),
                 hr(),
                 h3("Variables detectadas"),
                 verbatimTextOutput("vars_info"))
      )
    )
  )
)

# Server
server <- function(input, output, session) {
  
  # Función reactiva para leer los datos
  data <- reactive({
    req(input$file)
    
    # Leer el archivo según el separador seleccionado
    read_delim(input$file$datapath, 
              delim = input$sep,
              col_names = input$header,
              show_col_types = FALSE)
  })
  
  # Generar selector de variables
  output$varselect <- renderUI({
    req(data())
    num_vars <- names(select_if(data(), is.numeric))
    
    if(length(num_vars) == 0) {
      return(HTML("<div class='alert alert-warning'>No se detectaron variables numéricas</div>"))
    }
    
    selectInput("variable", "Selecciona una variable cuantitativa", 
                choices = num_vars,
                selected = num_vars[1])
  })
  
  # Información de los datos
  output$data_info <- renderPrint({
    req(data())
    cat("Nombre del archivo:", input$file$name, "\n")
    cat("Dimensiones:", dim(data())[1], "filas x", dim(data())[2], "columnas\n")
    cat("Tamaño del archivo:", round(input$file$size/1024, 2), "KB\n")
  })
  
  # Información de variables
  output$vars_info <- renderPrint({
    req(data())
    str(data())
  })
  
  # Vista previa de los datos
  output$data_preview <- renderDT({
    req(data())
    datatable(head(data(), 10), options = list(scrollX = TRUE))
  })
  
  # Todos los outputs se generan cuando se hace clic en "Analizar"
  observeEvent(input$analyze, {
    
    # Estadísticas descriptivas
    output$summary <- renderPrint({
      req(input$variable)
      summary_stats <- data() %>%
        summarise(
          n = n(),
          na = sum(is.na(.[[input$variable]])),
          min = min(.[[input$variable]], na.rm = TRUE),
          q1 = quantile(.[[input$variable]], 0.25, na.rm = TRUE),
          median = median(.[[input$variable]], na.rm = TRUE),
          mean = mean(.[[input$variable]], na.rm = TRUE),
          q3 = quantile(.[[input$variable]], 0.75, na.rm = TRUE),
          max = max(.[[input$variable]], na.rm = TRUE),
          sd = sd(.[[input$variable]], na.rm = TRUE),
          cv = sd(.[[input$variable]], na.rm = TRUE) / mean(.[[input$variable]], na.rm = TRUE) * 100,
          iqr = IQR(.[[input$variable]], na.rm = TRUE),
          skewness = moments::skewness(.[[input$variable]], na.rm = TRUE),
          kurtosis = moments::kurtosis(.[[input$variable]], na.rm = TRUE)
        )
      
      print(summary_stats)
    })
    
    # Histograma
    output$histplot <- renderPlot({
      req(input$variable)
      
      # Calcular densidad para superponer curva normal
      var_data <- na.omit(data()[[input$variable]])
      
      ggplot(data(), aes_string(x = input$variable)) +
        geom_histogram(aes(y = ..density..), bins = input$bins, 
                      fill = "#69b3a2", color = "black", alpha = 0.7) +
        geom_density(color = "#e63946", linewidth = 1.2) +
        stat_function(fun = dnorm, 
                     args = list(mean = mean(var_data, na.rm = TRUE), 
                                 sd = sd(var_data, na.rm = TRUE)),
                     color = "#1d3557", linewidth = 1, linetype = "dashed") +
        theme_minimal(base_size = 14) +
        labs(title = paste("Distribución de", input$variable),
             subtitle = "Con curva de densidad (rojo) y curva normal (azul)",
             x = input$variable,
             y = "Densidad") +
        theme(plot.title = element_text(face = "bold"),
              axis.title = element_text(face = "bold"))
    })
    
    # Boxplot
    output$boxplot <- renderPlot({
      req(input$variable)
      
      ggplot(data(), aes_string(y = input$variable)) +
        geom_boxplot(fill = "#457b9d", alpha = 0.7) +
        coord_flip() +
        theme_minimal(base_size = 14) +
        labs(title = paste("Diagrama de caja de", input$variable),
             subtitle = "Detección de valores atípicos",
             x = "",
             y = input$variable) +
        theme(plot.title = element_text(face = "bold"),
              axis.title = element_text(face = "bold"))
    })
    
    # QQ Plot
    output$qq_plot <- renderPlot({
      req(input$variable)
      var_data <- na.omit(data()[[input$variable]])
      
      ggplot(data.frame(x = var_data), aes(sample = x)) +
        stat_qq() +
        stat_qq_line(color = "#e63946") +
        theme_minimal(base_size = 14) +
        labs(title = paste("Gráfico Q-Q de", input$variable),
             subtitle = "Evaluación visual de normalidad",
             x = "Cuantiles teóricos",
             y = "Cuantiles observados") +
        theme(plot.title = element_text(face = "bold"),
              axis.title = element_text(face = "bold"))
    })
    
    # Prueba de normalidad
    output$normality_result <- renderPrint({
      req(input$variable)
      var_data <- na.omit(data()[[input$variable]])
      
      if (length(var_data) < 3) {
        cat("Se necesitan al menos 3 observaciones para realizar pruebas de normalidad.")
      } else if (input$normality_test == "shapiro" && length(var_data) > 5000) {
        cat("Shapiro-Wilk es apropiado para muestras de hasta 5000 observaciones.\n")
        cat("Tu muestra tiene", length(var_data), "observaciones. Considera usar Kolmogorov-Smirnov.")
      } else {
        if (input$normality_test == "shapiro") {
          resultado <- shapiro.test(var_data)
          cat("Prueba de Shapiro-Wilk para normalidad\n\n")
        } else {
          resultado <- suppressWarnings(ks.test(var_data, "pnorm", 
                                              mean = mean(var_data), 
                                              sd = sd(var_data)))
          cat("Prueba de Kolmogorov-Smirnov para normalidad\n\n")
        }
        
        print(resultado)
        
        cat("\nInterpretación:\n")
        if (resultado$p.value < 0.05) {
          cat("Con un p-valor de", round(resultado$p.value, 4), 
              "hay evidencia para rechazar la hipótesis nula de normalidad.")
        } else {
          cat("Con un p-valor de", round(resultado$p.value, 4), 
              "no hay evidencia suficiente para rechazar la hipótesis de normalidad.")
        }
      }
    })
  })
}

# Ejecutar la aplicación
shinyApp(ui = ui, server = server)

Resumen Estadístico

verbatimTextOutput("summary")

Histograma

plotOutput("histplot")

Prueba de Shapiro-Wilk

verbatimTextOutput("shapiro")