set.seed(123)

n <- 2500  # más de 2000 observaciones

datos <- data.frame(
  ingreso_mensual = rnorm(n, mean = 1500, sd = 400),                  # numérica continua
  edad = sample(18:80, n, replace = TRUE),                            # numérica discreta
  horas_trabajo = rpois(n, lambda = 8),                               # numérica discreta
  gasto_alimentos = rnorm(n, mean = 300, sd = 50),                    # numérica continua
  gasto_transporte = rnorm(n, mean = 120, sd = 30),                   # numérica continua
  
  sexo = factor(sample(c("Hombre", "Mujer"), n, replace = TRUE)),     # categórica nominal
  estado_civil = factor(sample(c("Soltero", "Casado", "Divorciado", "Viudo"),
                               n, replace = TRUE)),                   # categórica nominal
  
  nivel_educativo = factor(sample(c("Primaria", "Secundaria", "Superior"),
                                  n, replace = TRUE),
                           ordered = TRUE),                           # categórica ordinal
  
  region = factor(sample(c("Costa", "Sierra", "Selva"), n, replace = TRUE)), # nominal
  acceso_internet = factor(sample(c("Sí", "No"), n, replace = TRUE)),        # nominal
  estrato = factor(sample(1:5, n, replace = TRUE), ordered = TRUE),          # ordinal
  
  num_hijos = rpois(n, lambda = 2),                                   # numérica discreta
  nivel_satisfaccion = factor(sample(1:10, n, replace = TRUE), 
                              ordered = TRUE),                        # ordinal
  
  ahorro_mensual = rnorm(n, mean = 200, sd = 100),                    # numérica continua
  consumo_energia = rnorm(n, mean = 50, sd = 12),                     # numérica continua
  
  tipo_vivienda = factor(sample(c("Propia", "Alquilada", "Familiar"), 
                                n, replace = TRUE))                  # nominal extra
)

head(datos)
##   ingreso_mensual edad horas_trabajo gasto_alimentos gasto_transporte   sexo
## 1        1275.810   56             8        270.2122        153.11752 Hombre
## 2        1407.929   31             8        243.7237        105.71713 Hombre
## 3        2123.483   39             7        290.6137        150.27149 Hombre
## 4        1528.203   41             3        248.0979        127.57367  Mujer
## 5        1551.715   39             9        367.1230        142.23150 Hombre
## 6        2186.026   74             8        306.1756         66.12078  Mujer
##   estado_civil nivel_educativo region acceso_internet estrato num_hijos
## 1        Viudo        Primaria  Selva              Sí       2         2
## 2       Casado        Primaria Sierra              Sí       2         3
## 3       Casado      Secundaria  Selva              No       1         0
## 4      Soltero        Superior Sierra              Sí       2         3
## 5      Soltero      Secundaria  Costa              Sí       2         3
## 6       Casado        Superior  Selva              Sí       2         3
##   nivel_satisfaccion ahorro_mensual consumo_energia tipo_vivienda
## 1                  4      106.46317        46.37502     Alquilada
## 2                  6      229.24309        27.05772     Alquilada
## 3                  8      325.56069        47.47791        Propia
## 4                  5       15.49504        57.11148        Propia
## 5                  8       23.37423        61.85887     Alquilada
## 6                 10      256.31744        52.03530      Familiar

análisis por grupos que reciba un dataframe, una

variable numérica y una variable categórica, y retorne un dataframe con

todas las medidas estadísticas calculadas para cada grupo. Esta función

utilizará bucles o funciones apply para iterar sobre grupos

# Función de análisis por grupos
library(moments)
## Warning: package 'moments' was built under R version 4.5.2
analisis_por_grupos <- function(df, var_num, var_cat) {
  
  x <- df[[var_num]]
  g <- df[[var_cat]]
  
  # Filtrar NA
  completo <- complete.cases(x, g)
  x <- x[completo]
  g <- g[completo]
  
  grupos <- unique(g)
  
  resultados <- lapply(grupos, function(gr) {
    datos_g <- x[g == gr]
    
    data.frame(
      Grupo = as.character(gr),
      Media = mean(datos_g),
      Mediana = median(datos_g),
      Desviacion = sd(datos_g),
      Varianza = var(datos_g),
      Coef_Variacion = sd(datos_g) / mean(datos_g),
      Minimo = min(datos_g),
      Maximo = max(datos_g),
      Rango = max(datos_g) - min(datos_g),
      IQR = IQR(datos_g),
      Q1 = quantile(datos_g, 0.25),
      Q3 = quantile(datos_g, 0.75),
      Asimetria = skewness(datos_g),
      Curtosis = kurtosis(datos_g)
    )
  })
  
  final <- do.call(rbind, resultados)
  rownames(final) <- NULL
  return(final)
}
# ejemplo
resultado <- analisis_por_grupos(datos, "ingreso_mensual", "estado_civil")
resultado
##        Grupo    Media  Mediana Desviacion Varianza Coef_Variacion   Minimo
## 1      Viudo 1506.124 1496.350   406.9835 165635.6      0.2702191 280.8556
## 2     Casado 1510.585 1513.820   403.2035 162573.0      0.2669187 421.8683
## 3    Soltero 1503.643 1492.226   380.5357 144807.4      0.2530758 458.3847
## 4 Divorciado 1497.030 1499.846   391.7100 153436.7      0.2616580 360.5813
##     Maximo    Rango      IQR       Q1       Q3   Asimetria Curtosis
## 1 2796.416 2515.560 516.7646 1246.445 1763.210  0.09186004 3.117407
## 2 2816.207 2394.339 556.7529 1237.403 1794.156 -0.01520666 2.795052
## 3 2856.148 2397.764 532.4158 1241.518 1773.934  0.16520289 2.946306
## 4 2618.956 2258.375 513.0577 1239.038 1752.096 -0.12417518 2.950427

detección de outliers que identifique valores atípicos

mediante el método del rango intercuartílico y retorne los índices de las

observaciones atípicas con sus valores correspondientes.

detectar_outliers <- function(x) {
  
  # Eliminar NA pero conservar indices
  x_na <- is.na(x)
  x_clean <- x[!x_na]
  
  # Calcular cuartiles
  Q1 <- quantile(x_clean, 0.25)
  Q3 <- quantile(x_clean, 0.75)
  IQR_value <- Q3 - Q1
  
  # Límites para detectar outliers
  lim_inf <- Q1 - 1.5 * IQR_value
  lim_sup <- Q3 + 1.5 * IQR_value
  
  # Identificar outliers
  indices_outliers <- which(x < lim_inf | x > lim_sup)
  valores_outliers <- x[indices_outliers]
  
  # Retorno en lista
  return(list(
    limite_inferior = lim_inf,
    limite_superior = lim_sup,
    indices = indices_outliers,
    valores = valores_outliers
  ))
}
# ejemplo
outliers_ingreso <- detectar_outliers(datos$ingreso_mensual)
outliers_ingreso
## $limite_inferior
##      25% 
## 451.5254 
## 
## $limite_superior
##      75% 
## 2555.348 
## 
## $indices
##  [1]  164  416  456  591  747  842 1011 1012 1126 1324 1435 1622 1703 1851 1950
## [16] 2367 2383 2432
## 
## $valores
##  [1] 2796.4160  442.7404  435.6309  376.0901 2576.6856 2573.9436 2618.9565
##  [8] 2632.8904 2773.6178 2856.1483  421.8683 2816.2070  280.8556  448.2698
## [15] 2626.4337 2708.8417  360.5813 2594.0837

tabla de frecuencias que genere tablas completas con

frecuencias absolutas, relativas y acumuladas, funcionando para variables

categóricas y numéricas discretizadas automáticamente.

tabla_frecuencias <- function(x, bins = 5) {
  
  # Eliminar NAs
  x <- x[!is.na(x)]
  
  # Si es numérica, discretizamos automáticamente
  if (is.numeric(x)) {
    x_cat <- cut(x, breaks = bins, include.lowest = TRUE)
  } else {
    x_cat <- as.factor(x)
  }
  
  # Frecuencias absolutas
  frec_abs <- table(x_cat)
  
  # Frecuencias relativas
  frec_rel <- prop.table(frec_abs)
  
  # Acumuladas
  frec_abs_acum <- cumsum(frec_abs)
  frec_rel_acum <- cumsum(frec_rel)
  
  # Crear dataframe final
  tabla <- data.frame(
    Categoria = names(frec_abs),
    Frecuencia_Absoluta = as.numeric(frec_abs),
    Frecuencia_Relativa = round(as.numeric(frec_rel), 4),
    Frecuencia_Absoluta_Acumulada = as.numeric(frec_abs_acum),
    Frecuencia_Relativa_Acumulada = round(as.numeric(frec_rel_acum), 4)
  )
  
  rownames(tabla) <- NULL
  return(tabla)
}
# ejemplo 
tabla_ingreso <- tabla_frecuencias(datos$sexo, bins = 7)
tabla_ingreso
##   Categoria Frecuencia_Absoluta Frecuencia_Relativa
## 1    Hombre                1223              0.4892
## 2     Mujer                1277              0.5108
##   Frecuencia_Absoluta_Acumulada Frecuencia_Relativa_Acumulada
## 1                          1223                        0.4892
## 2                          2500                        1.0000

3era seccion

# 1. Importación del dataset y verificación de carga
datos <- datos   # aquí normalmente usarías read.csv(), readRDS(), etc.

# Verificación de carga exitosa
if (exists("datos")) {
  cat("✔ El dataset se cargó correctamente.\n")
  cat("Dimensiones:", dim(datos), "\n")
} else {
  stop("✘ Error: el dataset no se cargó.")
}
## ✔ El dataset se cargó correctamente.
## Dimensiones: 2500 16
# 2. Identificación de tipos de variables
# Tipos de variables
str(datos)
## 'data.frame':    2500 obs. of  16 variables:
##  $ ingreso_mensual   : num  1276 1408 2123 1528 1552 ...
##  $ edad              : int  56 31 39 41 39 74 68 23 42 41 ...
##  $ horas_trabajo     : int  8 8 7 3 9 8 5 7 9 9 ...
##  $ gasto_alimentos   : num  270 244 291 248 367 ...
##  $ gasto_transporte  : num  153 106 150 128 142 ...
##  $ sexo              : Factor w/ 2 levels "Hombre","Mujer": 1 1 1 2 1 2 2 1 2 2 ...
##  $ estado_civil      : Factor w/ 4 levels "Casado","Divorciado",..: 4 1 1 3 3 1 4 3 1 2 ...
##  $ nivel_educativo   : Ord.factor w/ 3 levels "Primaria"<"Secundaria"<..: 1 1 2 3 2 3 3 1 3 3 ...
##  $ region            : Factor w/ 3 levels "Costa","Selva",..: 2 3 2 3 1 2 3 1 3 2 ...
##  $ acceso_internet   : Factor w/ 2 levels "No","Sí": 2 2 1 2 2 2 1 2 1 1 ...
##  $ estrato           : Ord.factor w/ 5 levels "1"<"2"<"3"<"4"<..: 2 2 1 2 2 2 3 2 1 3 ...
##  $ num_hijos         : int  2 3 0 3 3 3 1 2 2 0 ...
##  $ nivel_satisfaccion: Ord.factor w/ 10 levels "1"<"2"<"3"<"4"<..: 4 6 8 5 8 10 10 9 2 2 ...
##  $ ahorro_mensual    : num  106.5 229.2 325.6 15.5 23.4 ...
##  $ consumo_energia   : num  46.4 27.1 47.5 57.1 61.9 ...
##  $ tipo_vivienda     : Factor w/ 3 levels "Alquilada","Familiar",..: 1 1 3 3 1 2 2 3 1 3 ...
# Tabla resumen de tipos
sapply(datos, class)
## $ingreso_mensual
## [1] "numeric"
## 
## $edad
## [1] "integer"
## 
## $horas_trabajo
## [1] "integer"
## 
## $gasto_alimentos
## [1] "numeric"
## 
## $gasto_transporte
## [1] "numeric"
## 
## $sexo
## [1] "factor"
## 
## $estado_civil
## [1] "factor"
## 
## $nivel_educativo
## [1] "ordered" "factor" 
## 
## $region
## [1] "factor"
## 
## $acceso_internet
## [1] "factor"
## 
## $estrato
## [1] "ordered" "factor" 
## 
## $num_hijos
## [1] "integer"
## 
## $nivel_satisfaccion
## [1] "ordered" "factor" 
## 
## $ahorro_mensual
## [1] "numeric"
## 
## $consumo_energia
## [1] "numeric"
## 
## $tipo_vivienda
## [1] "factor"
# 3.1 Detección de valores faltantes
# Conteo de NA por variable
naResumen <- data.frame(
  Variable = names(datos),
  NA_Conteo = sapply(datos, function(x) sum(is.na(x))),
  NA_Porcentaje = round(sapply(datos, function(x) mean(is.na(x)) * 100), 3)
)

naResumen
##                              Variable NA_Conteo NA_Porcentaje
## ingreso_mensual       ingreso_mensual         0             0
## edad                             edad         0             0
## horas_trabajo           horas_trabajo         0             0
## gasto_alimentos       gasto_alimentos         0             0
## gasto_transporte     gasto_transporte         0             0
## sexo                             sexo         0             0
## estado_civil             estado_civil         0             0
## nivel_educativo       nivel_educativo         0             0
## region                         region         0             0
## acceso_internet       acceso_internet         0             0
## estrato                       estrato         0             0
## num_hijos                   num_hijos         0             0
## nivel_satisfaccion nivel_satisfaccion         0             0
## ahorro_mensual         ahorro_mensual         0             0
## consumo_energia       consumo_energia         0             0
## tipo_vivienda           tipo_vivienda         0             0
#📌 Decisión de tratamiento (documentada):
#Variables numéricas:
#Se reemplazarán NA con la mediana, para no distorsionar con valores extremos.
#Se marcará un indicador binario (_na_flag) para preservar la información faltante original.
#Variables categóricas:
#Se reemplazarán NA con la categoría "Desconocido".
#También se crea un marcador (_na_flag).

seccion 6

#1. Histogramas con Curvas de Densidad Ingreso mensual
library(ggplot2)

ggplot(datos, aes(x = ingreso_mensual)) +
  geom_histogram(aes(y = ..density..), bins = 35, fill = "skyblue", alpha = 0.7) +
  geom_density(color = "red", size = 1.2) +
  labs(
    title = "Distribución del Ingreso Mensual",
    x = "Ingreso mensual (S/.)",
    y = "Densidad"
  ) +
  theme_minimal()
## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
## ℹ Please use `linewidth` instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
## Warning: The dot-dot notation (`..density..`) was deprecated in ggplot2 3.4.0.
## ℹ Please use `after_stat(density)` instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.

#2. Boxplots Comparativos Entre Grupos Ingreso mensual por sexo
ggplot(datos, aes(x = sexo, y = ingreso_mensual, fill = sexo)) +
  geom_boxplot(alpha = 0.8) +
  labs(
    title = "Comparación del Ingreso Mensual por Sexo",
    x = "Sexo",
    y = "Ingreso mensual (S/.)"
  ) +
  theme_minimal() +
  scale_fill_brewer(palette = "Set2")

#3. Gráficos de Dispersión (Scatterplot) Ingreso vs. Ahorro mensual
ggplot(datos, aes(x = ingreso_mensual, y = ahorro_mensual)) +
  geom_point(alpha = 0.35, color = "blue") +
  geom_smooth(method = "lm", color = "red") +
  labs(
    title = "Relación entre Ingreso Mensual y Ahorro",
    x = "Ingreso mensual (S/.)",
    y = "Ahorro mensual (S/.)"
  ) +
  theme_minimal()
## `geom_smooth()` using formula = 'y ~ x'

#4. Gráficos de Barras para Variables Categóricas Distribución del sexo
ggplot(datos, aes(x = sexo, fill = sexo)) +
  geom_bar() +
  labs(
    title = "Distribución por Sexo",
    x = "Sexo",
    y = "Frecuencia"
  ) +
  theme_minimal() +
  scale_fill_brewer(palette = "Set2")

#5. Visualizaciones que Comunican Hallazgos Relevantes Boxplot del nivel de satisfacción por estrato socioeconómico
ggplot(datos, aes(x = estrato, y = as.numeric(nivel_satisfaccion), fill = estrato)) +
  geom_boxplot() +
  labs(
    title = "Nivel de Satisfacción según Estrato Socioeconómico",
    x = "Estrato",
    y = "Nivel de satisfacción"
  ) +
  theme_minimal()