Estudiocasosycontroles

Author

Dr. Juan Fidel Osuna-Rams

Diseño de un Estudio de Casos y Controles

En un estudio de casos y controles, identificamos un grupo de personas con el desenlace de interés (casos) y otro grupo sin el desenlace (controles). Luego, evaluamos y comparamos las exposiciones previas entre ambos grupos para explorar asociaciones.

Ejemplo de Planteamiento

  • Pregunta de Investigación: ¿La hipertensión está asociada con un mayor riesgo de desarrollar diabetes?

  • Casos: Personas con diagnóstico de diabetes.

  • Controles: Personas sin diagnóstico de diabetes.

  • Exposición de interés: Hipertensión.

  • Covariables: Edad, IMC, actividad física, nivel educativo, hábito de fumar.

Planteamiento del Estudio

  1. Objetivo del Estudio:

    • Evaluar si la hipertensión aumenta el riesgo de diabetes.
  2. Pregunta de Investigación:

    • ¿Es más probable que las personas con hipertensión desarrollen diabetes que aquellas sin hipertensión?
  3. Variables:

    • Dependiente: Diabetes (Casos: Sí; Controles: No).

    • Independiente: Hipertensión (Sí/No).

    • Covariables: Edad, IMC, actividad física, nivel educativo, hábito de fumar.

Próximos Pasos

  • Limpieza de la base de datos simulada.

  • Exploración descriptiva (EDA) de las características de casos y controles.

  • Análisis bivariado (Odds Ratio para hipertensión).

  • Ajustes multivariados usando regresión logística.

Este documento aborda el análisis de un estudio de casos y controles para explorar la asociación entre hipertensión y diabetes. Iniciaremos con:

Descargamos el data frame: https://colab.research.google.com/drive/1BPtDSgzEfWKysUBdbb2UFubOwQecEb8X?usp=sharing#scrollTo=Y6l8GwkV2dss

estudio_casos_controles_errores.xlsx

lo guardamos como un objeto (datos)

1. Limpieza de una base de datos con errores intencionados. 2. Exploración descriptiva de las variables. 3. Análisis bivariado y multivariado mediante regresión logística.

# Instalar paquetes necesarios (si aún no están instalados)
# install.packages("readxl")
# install.packages("tidyverse")
# install.packages("janitor")
# install.packages("gtsummary")

# Cargar librerías
library(readxl)
library(tidyverse)
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.1.4     ✔ readr     2.1.5
✔ forcats   1.0.0     ✔ stringr   1.5.1
✔ ggplot2   3.5.1     ✔ tibble    3.2.1
✔ lubridate 1.9.3     ✔ tidyr     1.3.1
✔ purrr     1.0.2     
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(janitor)

Adjuntando el paquete: 'janitor'

The following objects are masked from 'package:stats':

    chisq.test, fisher.test
library(gtsummary)
library(dlookr)
Registered S3 methods overwritten by 'dlookr':
  method          from  
  plot.transform  scales
  print.transform scales

Adjuntando el paquete: 'dlookr'

The following object is masked from 'package:tidyr':

    extract

The following object is masked from 'package:base':

    transform
# Leer la base de datos desde Excel
#archivo <- "estudio_casos_controles_errores.xlsx"
library(readxl)
library(readxl)

estudio_casos_controles_errores <- read_excel("estudio_casos_controles_errores.xlsx")


#datos <- read_excel(archivo)

datos<-estudio_casos_controles_errores

# Visualizar las primeras filas
#head(datos)

Paso 2: Limpieza de la Base de Datos

En este paso, utilizaremos las funciones de janitor y dplyr para:

  1. Limpiar nombres de columnas.

  2. Corregir inconsistencias en valores categóricos.

# Limpiar nombres de columnas
datos <- datos %>% clean_names()

names(datos)
[1] "id"                       "diabetes"                
[3] "edad"                     "sexo"                    
[5] "imc"                      "hipertension"            
[7] "actividad_fisica_min_sem" "fumador"                 
[9] "nivel_educativo"         
# Corregir inconsistencias en valores categóricos
datos <- datos %>%
  mutate(
    sexo = str_to_title(trimws(sexo)),  # Título (primera letra en mayúscula)
    fumador = str_to_lower(trimws(fumador)),  # Minúscula
    hipertension = str_to_lower(trimws(hipertension)),  # Minúscula
    nivel_educativo = str_to_title(trimws(nivel_educativo))  # Título
  )

# Visualizar los datos limpios
head(datos)
# A tibble: 6 × 9
     id diabetes  edad sexo      imc hipertension actividad_fisica_min…¹ fumador
  <dbl> <chr>    <dbl> <chr>   <dbl> <chr>                         <dbl> <chr>  
1     1 Sí          75 Mascul…  25.8 no                              207 no     
2     2 Sí          32 Femeni…  30.4 no                              295 no     
3     3 Sí          58 Femeni…  30.9 sí                              126 sí     
4     4 Sí          64 Mascul…  29.7 no                              264 no     
5     5 Sí          68 Mascul…  18.6 no                              208 sí     
6     6 Sí          47 Femeni…  39.7 sí                              258 sí     
# ℹ abbreviated name: ¹​actividad_fisica_min_sem
# ℹ 1 more variable: nivel_educativo <chr>
sapply(datos, unique)
$id
  [1]   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17  18
 [19]  19  20  21  22  23  24  25  26  27  28  29  30  31  32  33  34  35  36
 [37]  37  38  39  40  41  42  43  44  45  46  47  48  49  50  51  52  53  54
 [55]  55  56  57  58  59  60  61  62  63  64  65  66  67  68  69  70  71  72
 [73]  73  74  75  76  77  78  79  80  81  82  83  84  85  86  87  88  89  90
 [91]  91  92  93  94  95  96  97  98  99 100

$diabetes
[1] "Sí" "No"

$edad
 [1] 75 32 58 64 68 47 49 72 52 63 62 79 77 39 76 55 44 66 46 34 33 50 69 78 37
[26] 71 65 51 60 57 42 70 35 30 41 40 43 48 45 73 74 36 56 31 67 53 59 54 38 61

$sexo
[1] "Masculino" "Femenino" 

$imc
  [1] 25.8 30.4 30.9 29.7 18.6 39.7 38.0 23.0 24.8 37.9 39.6 24.0 30.6 35.8 27.0
 [16] 34.2 22.0 31.4 37.1 20.2 27.7 22.9 28.2 30.3 20.5 24.9 38.4 30.7 28.3 34.7
 [31] 34.5 19.5 33.7 36.5 22.1 35.3 24.7 25.1 32.8 20.9 37.6 33.5 28.0 27.9 34.9
 [46] 20.3 31.0 36.0 25.7 34.6 30.8 37.0 36.2 38.1 21.3 21.5 27.1 27.6 21.1 22.8
 [61] 28.6 35.9 18.7 38.5 33.9 26.6 32.9 19.1 32.2 19.2 28.7 30.2 19.9 32.5 39.9
 [76] 35.0 20.7 32.7 19.6 35.5 35.4 23.9 25.5 35.6 21.4 26.5 33.0 32.0 23.1 38.8
 [91] 33.6 30.0 23.6 34.0 24.1 35.7 39.2 19.7 28.4 24.3 30.1 25.4 37.4 22.2 30.5
[106] 36.6 19.4 18.8 28.1 33.1 32.1 22.7 29.6 21.7 24.4 26.0 23.5 31.6 37.2 31.1
[121] 39.8 32.4 19.3 29.9 21.8 39.3 34.8 34.1 38.7 21.0 31.3 26.1 39.0 31.5 36.7

$hipertension
[1] "no" "sí" "si"

$actividad_fisica_min_sem
  [1] 207 295 126 264 208 258 180 259 179 205 199  29  24  38 152  32 115 111
 [19] 201 113  72   0 225  30 276  23  18  60 123  19 116 153   6  71  93  10
 [37] 248 174 191  42 234  15 118  49  33 253 231 290  63 268  26 297 143  45
 [55] 292 142 289 213   8 245 256 278 288  94 214 282 228 101 141 262 155 242
 [73]  84 190 284  66 212  47 296 122 136  68 286  91   2 162  59   9 250 154
 [91] 139  85  86  98  43  37   7  97 216  74  58  16  56 137 121 246 272 124
[109] 105 188 267 170 151 100 275 172 133  96 112 217 215  11 178   3 132  52
[127]  75 138 125  20  27  80 127  67 108 119 281 202   5  51 240 144

$fumador
[1] "no" "sí"

$nivel_educativo
[1] "Primaria"      "Secundaria"    "Universitario"

limpieza para evitar que se repitan elementos u homogenizarlos:

# Limpieza y homogenización de datos categóricos
datos <- datos %>%
  mutate(
    # Homogeneizar valores en `fumador`
    hipertension = case_when(
      hipertension %in% c("sí", "Sí", "SÍ", "si") ~ "Sí",
      hipertension %in% c("no", "No", "NO") ~ "No",
      TRUE ~ NA_character_
    ))

sapply(datos, unique)
$id
  [1]   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17  18
 [19]  19  20  21  22  23  24  25  26  27  28  29  30  31  32  33  34  35  36
 [37]  37  38  39  40  41  42  43  44  45  46  47  48  49  50  51  52  53  54
 [55]  55  56  57  58  59  60  61  62  63  64  65  66  67  68  69  70  71  72
 [73]  73  74  75  76  77  78  79  80  81  82  83  84  85  86  87  88  89  90
 [91]  91  92  93  94  95  96  97  98  99 100

$diabetes
[1] "Sí" "No"

$edad
 [1] 75 32 58 64 68 47 49 72 52 63 62 79 77 39 76 55 44 66 46 34 33 50 69 78 37
[26] 71 65 51 60 57 42 70 35 30 41 40 43 48 45 73 74 36 56 31 67 53 59 54 38 61

$sexo
[1] "Masculino" "Femenino" 

$imc
  [1] 25.8 30.4 30.9 29.7 18.6 39.7 38.0 23.0 24.8 37.9 39.6 24.0 30.6 35.8 27.0
 [16] 34.2 22.0 31.4 37.1 20.2 27.7 22.9 28.2 30.3 20.5 24.9 38.4 30.7 28.3 34.7
 [31] 34.5 19.5 33.7 36.5 22.1 35.3 24.7 25.1 32.8 20.9 37.6 33.5 28.0 27.9 34.9
 [46] 20.3 31.0 36.0 25.7 34.6 30.8 37.0 36.2 38.1 21.3 21.5 27.1 27.6 21.1 22.8
 [61] 28.6 35.9 18.7 38.5 33.9 26.6 32.9 19.1 32.2 19.2 28.7 30.2 19.9 32.5 39.9
 [76] 35.0 20.7 32.7 19.6 35.5 35.4 23.9 25.5 35.6 21.4 26.5 33.0 32.0 23.1 38.8
 [91] 33.6 30.0 23.6 34.0 24.1 35.7 39.2 19.7 28.4 24.3 30.1 25.4 37.4 22.2 30.5
[106] 36.6 19.4 18.8 28.1 33.1 32.1 22.7 29.6 21.7 24.4 26.0 23.5 31.6 37.2 31.1
[121] 39.8 32.4 19.3 29.9 21.8 39.3 34.8 34.1 38.7 21.0 31.3 26.1 39.0 31.5 36.7

$hipertension
[1] "No" "Sí"

$actividad_fisica_min_sem
  [1] 207 295 126 264 208 258 180 259 179 205 199  29  24  38 152  32 115 111
 [19] 201 113  72   0 225  30 276  23  18  60 123  19 116 153   6  71  93  10
 [37] 248 174 191  42 234  15 118  49  33 253 231 290  63 268  26 297 143  45
 [55] 292 142 289 213   8 245 256 278 288  94 214 282 228 101 141 262 155 242
 [73]  84 190 284  66 212  47 296 122 136  68 286  91   2 162  59   9 250 154
 [91] 139  85  86  98  43  37   7  97 216  74  58  16  56 137 121 246 272 124
[109] 105 188 267 170 151 100 275 172 133  96 112 217 215  11 178   3 132  52
[127]  75 138 125  20  27  80 127  67 108 119 281 202   5  51 240 144

$fumador
[1] "no" "sí"

$nivel_educativo
[1] "Primaria"      "Secundaria"    "Universitario"

Paso 3: Exploración de Datos (EDA)

Resumen Estadístico de Variables Cuantitativas

podemos usar los paquetes, dlookr, gtsummary o simbplemete con base R:

# Resumen estadístico
datos %>%
  select_if(is.numeric) %>%
  summary()
       id              edad            imc        actividad_fisica_min_sem
 Min.   :  1.00   Min.   :30.00   Min.   :18.60   Min.   :  0.0           
 1st Qu.: 25.75   1st Qu.:45.00   1st Qu.:24.38   1st Qu.: 71.0           
 Median : 50.50   Median :56.50   Median :30.30   Median :140.0           
 Mean   : 50.50   Mean   :55.81   Mean   :29.53   Mean   :145.5           
 3rd Qu.: 75.25   3rd Qu.:68.00   3rd Qu.:34.70   3rd Qu.:214.2           
 Max.   :100.00   Max.   :79.00   Max.   :39.90   Max.   :297.0           

como lo harías con dlookr?

datos %>% describe()
# A tibble: 4 × 26
  described_variables        n    na  mean    sd se_mean   IQR skewness kurtosis
  <chr>                  <int> <int> <dbl> <dbl>   <dbl> <dbl>    <dbl>    <dbl>
1 id                       200     0  50.5 28.9    2.05   49.5   0         -1.20
2 edad                     200     0  55.8 14.2    1.00   23    -0.136     -1.07
3 imc                      200     0  29.5  6.18   0.437  10.3  -0.135     -1.13
4 actividad_fisica_min_…   200     0 146.  87.4    6.18  143.    0.0855    -1.16
# ℹ 17 more variables: p00 <dbl>, p01 <dbl>, p05 <dbl>, p10 <dbl>, p20 <dbl>,
#   p25 <dbl>, p30 <dbl>, p40 <dbl>, p50 <dbl>, p60 <dbl>, p70 <dbl>,
#   p75 <dbl>, p80 <dbl>, p90 <dbl>, p95 <dbl>, p99 <dbl>, p100 <dbl>
datos %>% normality()
# A tibble: 4 × 4
  vars                     statistic    p_value sample
  <chr>                        <dbl>      <dbl>  <dbl>
1 id                           0.954 0.00000513    200
2 edad                         0.959 0.0000151     200
3 imc                          0.955 0.00000565    200
4 actividad_fisica_min_sem     0.954 0.00000429    200
datos %>% plot_normality()

Distribución de Casos y Controles

# Frecuencia de diabetes
datos %>%
  count(diabetes) %>%
  mutate(porcentaje = n / sum(n) * 100)
# A tibble: 2 × 3
  diabetes     n porcentaje
  <chr>    <int>      <dbl>
1 No         100         50
2 Sí         100         50
# Gráfico de barras
datos %>%
  ggplot(aes(x = diabetes, fill = diabetes)) +
  geom_bar() +
  labs(title = "Distribución de Casos y Controles", x = "Diabetes", y = "Frecuencia") +
  theme_minimal()

Paso 4: Análisis Bivariado (Odds Ratios)

Usaremos tablas de contingencia para calcular las Odds Ratios de hipertensión.

# Tabla de contingencia

datos2 <- datos %>%
  mutate(
    diabetes = factor(diabetes, levels = c("Sí", "No")),  # Diabetes: "si" primero
    hipertension = factor(hipertension, levels = c("Sí", "No"))  # Hipertensión: "si" primero
  )

tabla_contingencia <- datos2 %>%
  count(diabetes, hipertension) %>%
  pivot_wider(names_from = hipertension, values_from = n, values_fill = 0)

tabla_contingencia
# A tibble: 2 × 3
  diabetes    Sí    No
  <fct>    <int> <int>
1 Sí          48    52
2 No          41    59
# Calcular Odds Ratio manualmente
odds_ratio <- (tabla_contingencia$Sí[1] * tabla_contingencia$No[2]) /
              (tabla_contingencia$Sí[2] * tabla_contingencia$No[1])


# Extraer valores de la tabla
a <- tabla_contingencia$Sí[1]  # Diabetes "Sí" e Hipertensión "Sí"
b <- tabla_contingencia$No[1]  # Diabetes "Sí" e Hipertensión "No"
c <- tabla_contingencia$Sí[2]  # Diabetes "No" e Hipertensión "Sí"
d <- tabla_contingencia$No[2]  # Diabetes "No" e Hipertensión "No"

# Calcular Odds Ratio
odds_ratio <- (a * d) / (b * c)

cat("Odds Ratio de hipertensión asociada a diabetes:", round(odds_ratio, 2))
Odds Ratio de hipertensión asociada a diabetes: 1.33

Determinar IC 95% a a los OR

# Logaritmo natural del OR
ln_or <- log(odds_ratio)

# Varianza del log(OR)
var_ln_or <- (1 / a) + (1 / b) + (1 / c) + (1 / d)

# Intervalo de confianza del 95%
z <- 1.96  # Valor crítico para 95%
se_ln_or <- sqrt(var_ln_or)  # Error estándar del log(OR)

# Límites inferior y superior del IC
ic_lower <- exp(ln_or - z * se_ln_or)
ic_upper <- exp(ln_or + z * se_ln_or)

# Imprimir resultados
cat("OR =", round(odds_ratio, 2), "\n","IC95%:", round(ic_lower, 2), "-", round(ic_upper, 2), "\n")
OR = 1.33 
 IC95%: 0.76 - 2.32 

Interpretación del Odds Ratio

  • OR>1: La hipertensión es más probable en personas con diabetes.

  • OR=1 : No hay asociación entre diabetes e hipertensión.

  • OR<1: La hipertensión es menos probable en personas con diabetes.

  • Frecuencias (a,b,c,d):

    • Se extraen directamente de la tabla de contingencia calculada con pivot_wider.
  • Odds Ratio (odds_ratio):

    • El cálculo del OR ya está realizado.
  • Logaritmo Natural del OR (ln_or):

    • Utilizamos log(odds_ratio) para calcular el logaritmo natural del OR.
  • Varianza del log(OR) (var_ln_or):

    • Fórmula:

  • Intervalo de Confianza:

    • Límite inferior:

    • Límite superior:

Interpretación del Resultado

  • El resultado de R=1.33 con un intervalo de confianza al 95% (C95%: 0.76 - 2.32) puede interpretarse de la siguiente manera:

    1. Valor del Odds Ratio (OR)

    • OR=1.33 (OR=>1)

      • Indica que las personas con diabetes tienen 1.33 veces más probabilidades de tener hipertensión en comparación con las personas sin diabetes.

      • Sin embargo, este valor debe evaluarse junto con el intervalo de confianza para determinar si la asociación es estadísticamente significativa.

    2. Intervalo de Confianza al 95% (IC95%)

    • IC95%=[0.76, 2.32]:

      • Este intervalo significa que, con un 95% de confianza, el verdadero Odds Ratio podría estar entre 0.76 y 2.32.

      • Incluye el valor 1, lo que implica que la asociación no es estadísticamente significativa (es posible que no haya diferencia real en las probabilidades de hipertensión entre personas con y sin diabetes).

Interpretación Práctica:

  • Existe una posible asociación positiva entre diabetes e hipertensión (OR=1.33), pero esta asociación no es estadísticamente significativa según el IC95%=[0.76, 2.32].

  • Más datos o estudios adicionales serían necesarios para confirmar esta relación.