Esta guía acompaña la primera jornada del taller. Sirve para dos cosas: seguir la clase en vivo y repasar después por tu cuenta. No necesitas saber programar.
Qué vas a lograr hoy
Cómo está escrita
Cada tema sigue el mismo orden: primero una explicación corta, luego un bloque de código para ejecutar y, al final, qué deberías observar en el resultado. Las cajas de colores significan siempre lo mismo:
Verde · Control. Algo que debes comprobar antes de seguir.
Naranja · Atención. Un error frecuente o un detalle que suele confundir.
Azul · IA. Cómo usar un asistente de IA en ese punto.
Rojo · Privacidad. Reglas sobre los datos de salud.
Morado · Tu turno. Un ejercicio corto, con la solución oculta debajo.
Cómo ejecutar el código
Este archivo es un R Markdown (.Rmd): un
documento que mezcla texto y código. En RStudio, cada bloque gris de
código tiene un botón verde ▶ arriba a la derecha que ejecuta ese
bloque. También puedes poner el cursor en una línea y pulsar
Ctrl + Enter (en Mac, Cmd +
Enter) para ejecutar solo esa línea.
Avanza en orden, de arriba hacia abajo: los bloques posteriores usan lo que crean los anteriores. Si es tu primera vez, empieza por la Parte 0.
Se instalan los dos, y en ese orden: primero R, después RStudio. Ambos son gratuitos.
.pkg que corresponda a tu equipo (Apple Silicon
M1/M2/M3 o Intel).¿No puedes instalar programas en tu computadora? Usa Posit Cloud (https://posit.cloud): es RStudio dentro del navegador. Crea una cuenta gratuita, abre un proyecto nuevo y sube los archivos de datos con el botón Upload del panel Files.
Al abrir RStudio verás la ventana dividida en paneles:
| Panel | Dónde está | Para qué sirve |
|---|---|---|
| Editor (Source) | Arriba a la izquierda | Aquí se escriben y guardan los scripts y documentos como este |
| Consola (Console) | Abajo a la izquierda | Aquí R responde: resultados, avisos y errores |
| Environment | Arriba a la derecha | Lista de los objetos que has creado (tus bases, tablas, valores) |
| Files / Plots / Help | Abajo a la derecha | Archivos de tu carpeta, gráficos y páginas de ayuda |
Si no ves el editor, crea un archivo nuevo con File › New File › R Script.
Atajos de teclado que más usarás
| Atajo (Windows / Linux) | En Mac | Qué hace |
|---|---|---|
| Ctrl + Enter | Cmd + Enter | Ejecuta la línea donde está el cursor |
| Alt + - | Option + - | Escribe la flecha de asignación <- |
| Ctrl + Shift + M | Cmd + Shift + M | Escribe el pipe %>% |
| Ctrl + S | Cmd + S | Guarda el archivo |
| Tab | Tab | Autocompleta nombres |
| F1 | F1 | Abre la ayuda de la función bajo el cursor |
R necesita saber en qué carpeta están tus archivos. La forma más ordenada es crear un proyecto:
telesalud_taller..Rmd) dentro de esa misma
carpeta..csv y esta guía. Ábrela desde ahí.Un proyecto hace que R trabaje siempre dentro de esa carpeta, sin
tener que escribir rutas largas como C:/Users/....
Ejecuta este bloque. Es tu primera instrucción en R.
#> [1] "R version 4.5.3 (2026-03-11 ucrt)"
Deberías ver un texto con la versión de R instalada. Cualquier versión 4.x sirve para este taller.
Ahora preguntemos a R en qué carpeta está trabajando y qué archivos ve ahí:
#> [1] "C:/Users/USER/Downloads/carpeta de trabajo taller de R/Bases_R_Telesalud"
#> [1] "00_importar_y_explorar.R"
#> [2] "02_laboratorio_guiado_dia1.html"
#> [3] "atenciones_anonimizadas_completas.csv"
#> [4] "atenciones_muestra_docente_10000.csv"
#> [5] "control_calidad.csv"
#> [6] "Correo_para_docentes.txt"
#> [7] "diccionario_variables.csv"
#> [8] "Guia_Dia1_R_IA_Telesalud.Rmd"
#> [9] "Guia_y_diccionarios_R_2026.xlsx"
#> [10] "LEEME_ANTES_DE_USAR.txt"
#> [11] "resultados"
#> [12] "teleeducacion_anual.csv"
#> [13] "teleeducacion_mensual.csv"
getwd() devuelve la ruta de la carpeta.
list.files() muestra su contenido. En esa lista deben
aparecer los archivos del taller.
file.exists(c("atenciones_muestra_docente_10000.csv",
"teleeducacion_mensual.csv",
"diccionario_variables.csv"))#> [1] TRUE TRUE TRUE
Control. Los tres valores deben ser
TRUE. Si alguno es FALSE, R está mirando otra
carpeta: vuelve al punto 0.5. Solución rápida: Session › Set Working
Directory › To Source File Location.
En esta parte no usamos todavía la base de datos completa. Aprendemos las piezas básicas del lenguaje con ejemplos pequeños, casi todos con cifras reales de Telesalud.
Lo más simple que hace R es calcular. Ejecuta cada línea y mira la consola.
#> [1] 4
#> [1] 62425
#> [1] 0.6727219
El [1] que aparece antes de cada resultado solo indica
que es el primer elemento de la respuesta. Puedes ignorarlo.
Todo lo que va después de un # es un
comentario: R no lo ejecuta. Sirve para explicar qué
hace el código. Comentar bien es parte de escribir buen código.
Calcular y perder el resultado no sirve de mucho. En R guardamos los
valores en objetos, que son nombres que apuntan a un
valor. Se crean con la flecha <-, que se lee
“recibe”.
Estas líneas no muestran nada en la consola: solo guardan. Mira el panel Environment: ahí aparecieron los dos objetos. Para ver su valor, escribe su nombre:
#> [1] 29548
Y lo más útil: los objetos se pueden usar en cálculos y guardar el resultado en otro objeto.
#> [1] 62425
Reglas para poner nombres
atenciones_junio (este estilo se llama
snake_case).total y
Total son objetos distintos.Veamos esto último. El siguiente bloque produce un error a propósito:
#> Error:
#> ! objeto 'Total_jun_jul' no encontrado
Leer errores es una habilidad.
object 'Total_jun_jul' not found significa “no existe
ningún objeto con ese nombre”. Casi siempre es un error de tipeo o una
mayúscula. Un error no rompe nada: corriges y vuelves a ejecutar.
Tu turno. En agosto hubo 51753 atenciones y en
septiembre 43868. Crea dos objetos con esos valores y un tercero llamado
total_ago_sep con la suma.
No todos los datos son números. R distingue varios tipos, y saber cuál tienes importa porque cada tipo permite operaciones distintas.
mes <- 8 # numérico (numeric)
departamento <- "COCHABAMBA" # texto (character): siempre entre comillas
usa_glucometro <- TRUE # lógico (logical): TRUE o FALSE
class(mes)#> [1] "numeric"
#> [1] "character"
#> [1] "logical"
La función class() te dice de qué tipo es un objeto.
| Tipo | Ejemplo | En la base de Telesalud |
|---|---|---|
Numérico (numeric, integer) |
8, 29548 |
mes, n_diagnosticos |
Texto (character) |
"COCHABAMBA" |
departamento, proceso,
sexo |
Lógico (logical) |
TRUE, FALSE |
resultado de preguntas como “¿es mujer?” |
Factor (factor) |
texto con categorías fijas | lo usaremos mañana para grupo_edad |
Comillas. "8" con comillas es un texto,
no un número: no se puede sumar. Y un texto sin comillas, como
COCHABAMBA, R lo interpreta como el nombre de un objeto y
dará error si no existe.
NA (Not Available) significa “no
sabemos”. Es uno de los conceptos más importantes al trabajar
con datos de salud, porque un dato faltante no es un
cero.
#> [1] NA
El resultado es NA. Tiene sentido: si no conocemos un
valor, no conocemos la suma. R nos obliga a decidir qué hacer:
#> [1] 137
#> [1] 68.5
#> [1] FALSE TRUE FALSE
#> [1] 1
na.rm = TRUE significa “quita los NA antes de calcular”
(NA remove). is.na() pregunta, valor por valor, si
es faltante.
Regla de oro. Nunca reemplaces un NA
por 0 sin una razón explícita. “No se registró el peso” y “pesa cero
kilos” son afirmaciones muy distintas.
Un vector es una serie de valores del mismo tipo. Se
crea con la función c() (de combinar). Ya usaste
uno en el bloque anterior.
meses <- c(6, 7, 8, 9)
atenciones_mes <- c(29548, 32877, 51753, 43868) # atenciones por mes, base completa
nombres_mes <- c("Junio", "Julio", "Agosto", "Septiembre")Con un vector puedes hacer resúmenes:
#> [1] 4
#> [1] 158046
#> [1] 39511.5
#> [1] 51753
Y también operar con todos sus elementos a la vez. Aquí calculamos qué porcentaje del total representa cada mes:
#> [1] 18.7 20.8 32.7 27.8
R dividió cada mes por el total, multiplicó por 100 y redondeó a un decimal. Agosto concentra casi un tercio de las atenciones.
Para sacar un elemento concreto se usan corchetes con su posición:
#> [1] 51753
#> [1] "Agosto"
Ya usaste varias funciones: sum(),
mean(), round(), class(). Una
función es una instrucción con nombre que recibe datos entre paréntesis
y devuelve un resultado.
Lo que va dentro del paréntesis son los argumentos, separados por comas.
#> [1] 33
#> [1] 32.7
#> [1] 32.72
¿Cómo saber qué argumentos acepta una función? Con la ayuda:
También puedes poner el cursor sobre el nombre de la función y pulsar F1.
Un data frame es una tabla: filas y columnas, como una hoja de cálculo, pero con reglas.
NA).Podemos construir uno pequeño uniendo los vectores de antes:
resumen_mensual <- data.frame(
mes = meses,
nombre_mes = nombres_mes,
atenciones = atenciones_mes,
porcentaje = porcentaje_mes
)
resumen_mensualCada vector se convirtió en una columna. Para explorar una tabla hay unas pocas funciones que usarás siempre:
#> [1] 4
#> [1] 4
#> [1] "mes" "nombre_mes" "atenciones" "porcentaje"
Para extraer una columna se usa el signo $:
#> [1] 29548 32877 51753 43868
#> [1] 158046
La base de Telesalud que abriremos en la Parte 2 es exactamente esto: un data frame, solo que con miles de filas y 33 columnas.
R trae funciones básicas. Los paquetes agregan funciones nuevas hechas por la comunidad. Hoy usamos tres:
| Paquete | Para qué sirve |
|---|---|
readr |
Importar archivos (CSV) |
dplyr |
Transformar tablas: contar, filtrar, ordenar, resumir |
ggplot2 |
Hacer gráficos |
Con los paquetes hay dos pasos, y conviene no confundirlos:
install.packages()): se hace
una sola vez por computadora. Necesita internet.library()): se hace
cada vez que abres R.Una analogía: instalar es comprar un libro; cargar es sacarlo del estante para usarlo.
El siguiente bloque instala solo los paquetes que te falten. La primera vez puede tardar unos minutos.
paquetes <- c("readr", "dplyr", "ggplot2")
faltan <- paquetes[!paquetes %in% rownames(installed.packages())]
if (length(faltan) > 0) install.packages(faltan)Y este los carga:
Error típico.
there is no package called 'dplyr' significa que falta
instalarlo. could not find function "%>%" significa que
está instalado pero no lo cargaste con library(dplyr) en
esta sesión.
%>%: encadenar pasosCasi todo análisis es una secuencia: toma la tabla, luego filtra,
luego cuenta, luego ordena. El pipe
%>% permite escribirlo en ese mismo orden. Se lee
“y luego”.
Sin pipe, las funciones se anidan y hay que leer de adentro hacia afuera:
Con pipe, se lee de arriba hacia abajo, como una receta:
resumen_mensual %>% # toma la tabla resumen_mensual
arrange(desc(atenciones)) # y luego ordénala de mayor a menorEl resultado es el mismo. arrange() ordena filas y
desc() indica orden descendente. El pipe se escribe con
Ctrl + Shift + M.
Versiones recientes de R también aceptan el pipe nativo
|>. Para lo que haremos funcionan igual; en esta guía
usamos %>%.
Tu turno. Usa el pipe para ordenar
resumen_mensual de menor a mayor número de
atenciones. Pista: arrange() ordena de menor a mayor si no
usas desc().
Trabajaremos toda la semana con datos de la Plataforma de Telesalud, preparados para docencia:
| Archivo | Contenido |
|---|---|
atenciones_anonimizadas_completas.csv |
158.046 registros de atención, 33 variables (junio a septiembre de 2026) |
atenciones_muestra_docente_10000.csv |
Muestra de 10.000 registros para practicar con rapidez |
teleeducacion_mensual.csv |
1.404 filas: actividades educativas por mes y departamento, 2020–2026 |
teleeducacion_anual.csv |
117 filas: lo mismo, resumido por año |
diccionario_variables.csv |
Qué significa cada variable y cuántos faltantes tiene |
control_calidad.csv |
Problemas detectados en los datos, sin corregir |
Reglas de uso de estos datos.
Importar es leer un archivo y convertirlo en un data frame dentro de
R. Usamos read_csv() del paquete readr.
Primero elegimos con qué archivo trabajar. usar_muestra
funciona como un interruptor: con TRUE usamos la muestra de
10.000 registros; con FALSE, la base completa.
usar_muestra <- TRUE
archivo <- if (usar_muestra) {
"atenciones_muestra_docente_10000.csv"
} else {
"atenciones_anonimizadas_completas.csv"
}
archivo#> [1] "atenciones_muestra_docente_10000.csv"
La estructura if (...) {...} else {...} significa:
si la condición es verdadera, usa lo primero; si no, lo
segundo. El objeto archivo ahora contiene el nombre
del archivo elegido.
Ahora sí, importamos:
atenciones <- read_csv(archivo,
na = c("", "NA"),
show_col_types = FALSE,
locale = locale(encoding = "UTF-8"))Qué hace cada argumento:
| Argumento | Significado |
|---|---|
archivo |
El nombre del archivo a leer |
na = c("", "NA") |
Las celdas vacías se leen como dato faltante (NA) |
show_col_types = FALSE |
Evita un mensaje largo al importar |
locale(encoding = "UTF-8") |
Lee correctamente tildes y caracteres especiales |
Mira el panel Environment: apareció
atenciones, con su número de filas (obs.) y
columnas (variables).
Importamos también la base de teleeducación y el diccionario, que usaremos más adelante:
Antes de analizar, se mira. Las tres preguntas iniciales son: ¿cuántas filas?, ¿cuántas columnas?, ¿cómo se llaman?
#> [1] 10000
#> [1] 33
#> [1] "registro_id" "anio" "mes"
#> [4] "departamento" "proceso" "sexo"
#> [7] "grupo_etario_fuente" "grupo_edad" "tipo_captacion"
#> [10] "origen_atencion" "referido_por" "especialidad"
#> [13] "tipo_atencion" "destino_interconsulta" "cobertura"
#> [16] "tipo_consulta" "modalidad_tiempo" "capitulo_cie10"
#> [19] "n_diagnosticos" "imc_categoria" "n_dispositivos"
#> [22] "usa_electrocardiografo" "usa_otoscopio" "usa_monitor_sv"
#> [25] "usa_glucometro" "usa_ecografo" "eje_ent"
#> [28] "eje_et" "eje_salud_infantil" "eje_salud_materna"
#> [31] "eje_discapacidad" "eje_otros" "telemetria_n"
Control. Con la muestra deben salir 10.000 filas; con la base completa, 158.046. En ambos casos, 33 columnas.
glimpse() da la vista más completa: una línea por
variable, con su tipo y sus primeros valores.
#> Rows: 10,000
#> Columns: 33
#> $ registro_id <chr> "Re173e3e454bf61bd", "R925359eb7f124b01", "Rbb9…
#> $ anio <dbl> 2026, 2026, 2026, 2026, 2026, 2026, 2026, 2026,…
#> $ mes <dbl> 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,…
#> $ departamento <chr> "ORURO", "CHUQUISACA", "CHUQUISACA", "CHUQUISAC…
#> $ proceso <chr> "TLM", "TLM", "TLC", "TLC", "TLM", "TLC", "TLC"…
#> $ sexo <chr> "M", "F", "F", "F", "F", "M", "F", "M", "F", "M…
#> $ grupo_etario_fuente <chr> "60_mas", "20-39", "50-59", "50-59", "20-39", "…
#> $ grupo_edad <chr> "60_mas", "20-39", "40-59", "40-59", "20-39", "…
#> $ tipo_captacion <chr> NA, NA, "CONSULTA MEDICINA GENERAL", "CONSULTA …
#> $ origen_atencion <chr> "INSTITUCIONAL", "CAMPANA", "INSTITUCIONAL", "I…
#> $ referido_por <chr> "TELESALUD", "TELESALUD", "TELESALUD", "TELESAL…
#> $ especialidad <chr> NA, NA, "MEDICINA GENERAL", "MEDICINA GENERAL",…
#> $ tipo_atencion <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
#> $ destino_interconsulta <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
#> $ cobertura <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
#> $ tipo_consulta <chr> "SEGUIMIENTO", "NUEVO", "SEGUIMIENTO", "SEGUIMI…
#> $ modalidad_tiempo <chr> NA, NA, "REAL", "REAL", NA, "REAL", "REAL", NA,…
#> $ capitulo_cie10 <chr> "VII_VIII_OJO_OIDO", "XXI_CONTACTO_SERVICIOS", …
#> $ n_diagnosticos <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2,…
#> $ imc_categoria <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
#> $ n_dispositivos <dbl> 2, 2, 0, 0, 2, 0, 0, 2, 2, 0, 2, 0, 2, 0, 1, 1,…
#> $ usa_electrocardiografo <dbl> 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1,…
#> $ usa_otoscopio <dbl> 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,…
#> $ usa_monitor_sv <dbl> 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0,…
#> $ usa_glucometro <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,…
#> $ usa_ecografo <dbl> 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,…
#> $ eje_ent <dbl> 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1,…
#> $ eje_et <dbl> 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1,…
#> $ eje_salud_infantil <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,…
#> $ eje_salud_materna <dbl> 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,…
#> $ eje_discapacidad <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,…
#> $ eje_otros <dbl> 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0,…
#> $ telemetria_n <dbl> 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1,…
Cómo leer esta salida: <chr> es texto,
<dbl> es número. Fíjate que mes es
numérico y departamento es texto, y que en varias columnas
ya se ven NA.
Para ver las primeras filas como tabla:
Si quieres ver toda la tabla como hoja de cálculo, ejecuta
View(atenciones) en la consola (con V mayúscula) o haz clic
en su nombre en el panel Environment.
Ningún análisis serio empieza sin saber qué significa cada columna. El diccionario es otra tabla, y la consultamos con las mismas herramientas:
Aquí aparecen dos funciones nuevas de dplyr:
filter() se queda con las filas que
cumplen una condición. conjunto == "atenciones" significa
“donde la columna conjunto sea igual a atenciones”. Atención al
doble signo igual ==: se usa para
comparar. Un solo = sirve para asignar argumentos.select() se queda con las columnas que
nombres.Las 33 variables se agrupan así:
| Grupo | Variables |
|---|---|
| Tiempo y lugar | anio, mes, departamento |
| Persona | sexo, grupo_etario_fuente,
grupo_edad, cobertura |
| Proceso y servicio | proceso, tipo_captacion,
origen_atencion, referido_por,
especialidad, tipo_atencion,
destino_interconsulta, tipo_consulta,
modalidad_tiempo |
| Clínico | capitulo_cie10, n_diagnosticos,
imc_categoria |
| Dispositivos | n_dispositivos, usa_electrocardiografo,
usa_otoscopio, usa_monitor_sv,
usa_glucometro, usa_ecografo |
| Ejes programáticos | eje_ent, eje_et,
eje_salud_infantil, eje_salud_materna,
eje_discapacidad, eje_otros |
| Otros | telemetria_n, registro_id |
Pregunta clave: ¿qué representa una fila? En esta base, cada fila es un registro de atención, no un paciente. Una misma persona puede aparecer varias veces.
#> [1] 10000
#> [1] 10000
Los dos números coinciden: hay un registro_id por fila.
Ese identificador es un código aleatorio creado para proteger los datos;
no permite saber qué registros pertenecen a la misma persona.
Consecuencia para interpretar. Decimos “10.000 atenciones”, nunca “10.000 pacientes”. Lo mismo en teleeducación: los beneficiarios son conteos de asistencia, no necesariamente personas distintas.
También conviene confirmar el periodo que cubren los datos:
Solo hay meses 6 a 9 de 2026. Son cuatro meses, no un año: cualquier conclusión “anual” sería una extrapolación.
count() responde “¿cuántos hay de cada uno?”. Es la
función que más usarás.
La tabla resultante tiene una fila por departamento y una columna
n con el número de atenciones. Con sort = TRUE
sale ordenada de mayor a menor:
Un conteo solo dice poco; casi siempre queremos también el
porcentaje. Para crear columnas nuevas se usa mutate():
Léelo como una receta: toma atenciones, y luego cuenta por departamento, y luego agrega una columna con el porcentaje. La Paz, Cochabamba y Santa Cruz concentran cerca del 60% de los registros.
Se puede contar por dos variables a la vez. Veamos la variable
proceso, que tiene cuatro códigos operacionales de la
fuente (HCD, HIC, TLC, TLM), cruzada con el mes:
Observa las filas de TLM: en el mes 8 hay un salto muy grande respecto de los otros meses. Lo llamaremos el misterio de agosto y lo investigarás en el desafío final.
Control de consistencia. En cualquier conteo, la
suma de n debe ser igual al total de filas. Si no cuadra,
algo se perdió en el camino. Acostúmbrate a verificarlo.
summarise() reduce una tabla a un resumen; aquí, la suma
de la columna n. El total coincide con
nrow(atenciones).
Tu turno. ¿Cuántas atenciones hay por
sexo, con su porcentaje? ¿Y por
grupo_edad?
atenciones %>%
count(sexo) %>%
mutate(porcentaje = round(100 * n / sum(n), 1))
atenciones %>%
count(grupo_edad) %>%
mutate(porcentaje = round(100 * n / sum(n), 1))Aproximadamente dos tercios de las atenciones corresponden a mujeres, y cerca de una de cada cuatro a personas de 60 años o más.
filter() selecciona filas según una condición. Por
ejemplo, solo las atenciones de Cochabamba:
Las condiciones se pueden combinar. Con coma (o &)
deben cumplirse todas:
atenciones %>%
filter(departamento == "COCHABAMBA", mes == 8) %>%
count(capitulo_cie10, sort = TRUE) %>%
head(5)Este es el perfil clínico (por capítulo CIE-10) de Cochabamba en
agosto. head(5) muestra solo las cinco primeras filas.
Operadores de comparación más usados:
| Operador | Significado | Ejemplo |
|---|---|---|
== |
igual a | sexo == "F" |
!= |
distinto de | proceso != "TLM" |
> >= <
<= |
mayor, mayor o igual, menor, menor o igual | mes >= 8 |
%in% |
está en una lista | departamento %in% c("ORURO", "POTOSI") |
is.na() |
es faltante | is.na(especialidad) |
! |
negación | !is.na(especialidad) |
El texto debe coincidir exactamente. En esta base
los departamentos están en mayúsculas y sin tildes:
"POTOSI", no "Potosí". Si un filtro devuelve
cero filas, revisa primero cómo está escrito el valor con
count().
Antes de sacar conclusiones hay que saber cuánto falta y dónde. Empecemos por una variable:
#> [1] 3630
#> [1] 36.3
Un truco útil: mean(is.na(x)) da directamente la
proporción de faltantes, porque R trata TRUE como 1 y
FALSE como 0.
Ahora lo mismo para todas las variables a la vez. Este bloque es más avanzado; léelo con calma:
faltantes <- tibble(
variable = names(atenciones),
n_faltantes = sapply(atenciones, function(x) sum(is.na(x))),
porcentaje = round(100 * n_faltantes / nrow(atenciones), 1)
) %>%
arrange(desc(porcentaje))
faltantes %>% filter(n_faltantes > 0)Qué hace: tibble() crea una tabla nueva con tres
columnas; sapply() aplica a cada columna de
atenciones la operación “contar los NA”; después se calcula
el porcentaje y se ordena.
Buen momento para usar la IA. Si una línea como la
de sapply() no se entiende, pídele una explicación:
“Explícame línea por línea qué hace este código de R, en lenguaje
simple: [pega el código]”. Pegar código es seguro; lo que no se
pega son los datos.
El resultado muestra variables con muchísimos faltantes:
imc_categoria supera el 95%, cobertura ronda
el 88%. Con ese nivel de ausencia, no sirven para conclusiones
generales.
Pero no todos los faltantes son iguales. Veamos si la falta de
especialidad depende del proceso:
atenciones %>%
group_by(proceso) %>%
summarise(
registros = n(),
pct_sin_especialidad = round(mean(is.na(especialidad)) * 100, 1),
pct_sin_modalidad = round(mean(is.na(modalidad_tiempo)) * 100, 1)
)Dos funciones nuevas que trabajan juntas: group_by()
divide la tabla en grupos (aquí, uno por proceso) y
summarise() calcula un resumen para cada
grupo. n() cuenta las filas del grupo.
Hallazgo. especialidad y
modalidad_tiempo faltan en el 100% de los registros
TLM y en el 0% de los demás procesos. No es un
descuido de registro: el formulario de TLM no pide esos campos. Es un
faltante por diseño. No se rellena; se explica.
Otra trampa de esta base. En las variables de
dispositivos (usa_glucometro, usa_otoscopio,
etc.), el valor 0 significa “sin registro del dispositivo”, no
necesariamente “no se usó”. Redacta siempre “figura registrado en X% de
las atenciones”.
ggplot2 construye gráficos por capas. Todo gráfico
necesita tres cosas:
aes(): qué variable va en cada eje, y
cuál define el color.geom_...(): la forma: barras
(geom_col), líneas (geom_line), puntos
(geom_point).Empecemos con lo mínimo: atenciones por mes.
Primero contamos por mes; esa tabla pasa a ggplot(). En
aes() decimos: el mes en el eje X, el conteo n
en el eje Y. factor(mes) le indica a R que trate el mes
como categoría y no como número continuo.
El error más común del primer día. Dentro de un
gráfico, las capas se unen con +, no con
%>%. El pipe lleva los datos hasta
ggplot(); desde ahí, todo se suma con +.
Ahora lo mejoramos: color por proceso, títulos claros y un estilo más limpio.
atenciones %>%
count(mes, proceso) %>%
ggplot(aes(x = factor(mes), y = n, fill = proceso)) +
geom_col(position = "dodge") +
scale_fill_manual(values = c(HCD = "#8C95A6", HIC = "#75AADB",
TLC = "#276DC3", TLM = "#E4032E")) +
labs(title = "Atenciones por mes y proceso",
subtitle = "Plataforma de Telesalud, junio a septiembre de 2026",
x = "Mes", y = "Número de atenciones", fill = "Proceso") +
theme_minimal(base_size = 13)Qué agregó cada capa:
| Capa | Qué hace |
|---|---|
fill = proceso |
Colorea las barras según el proceso |
position = "dodge" |
Pone las barras lado a lado en vez de apiladas |
scale_fill_manual() |
Asigna un color concreto a cada proceso |
labs() |
Título, subtítulo y nombres de ejes |
theme_minimal() |
Un estilo visual sobrio |
El gráfico hace evidente lo que la tabla insinuaba: el salto de TLM en agosto.
Un segundo ejemplo, con barras horizontales ordenadas, útil cuando las etiquetas son largas:
atenciones %>%
count(departamento) %>%
ggplot(aes(x = reorder(departamento, n), y = n)) +
geom_col(fill = "#276DC3") +
coord_flip() +
labs(title = "Atenciones por departamento",
x = NULL, y = "Número de atenciones") +
theme_minimal(base_size = 13)reorder(departamento, n) ordena los departamentos según
su número de atenciones y coord_flip() gira el gráfico.
Leer con cuidado. Más atenciones en un departamento puede significar más población, más necesidad o simplemente mejor registro. Sin un denominador (población, establecimientos), el gráfico muestra volumen, no desempeño. Lo trabajaremos en el Día 3.
Para guardar el último gráfico como imagen:
dir.create("resultados", showWarnings = FALSE)
ggsave("resultados/atenciones_por_departamento.png", width = 9, height = 5, dpi = 150)dir.create() crea la carpeta resultados si
no existe, y ggsave() guarda ahí el gráfico.
La base de teleeducación tiene otra unidad de análisis: cada fila es una combinación de tipo de actividad × año × mes × departamento.
#> Rows: 1,404
#> Columns: 11
#> $ tipo_actividad <chr> "telecapacitacion", "telecapacitacion", "tel…
#> $ anio <dbl> 2026, 2026, 2026, 2026, 2026, 2026, 2026, 20…
#> $ mes <dbl> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 1, 2,…
#> $ departamento <chr> "BENI", "BENI", "BENI", "BENI", "BENI", "BEN…
#> $ actividades <dbl> 2, 3, 3, 3, 3, 2, 4, 5, 2, NA, NA, NA, 0, 18…
#> $ beneficiarios_hombres <dbl> 48, 47, 93, 64, 83, 132, 159, 144, 74, NA, N…
#> $ beneficiarios_mujeres <dbl> 62, 86, 173, 121, 155, 181, 336, 339, 129, N…
#> $ beneficiarios_total <dbl> 110, 133, 266, 185, 238, 313, 495, 483, 203,…
#> $ estado_reporte <chr> "reportado", "reportado", "reportado", "repo…
#> $ anio_inferido <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,…
#> $ inconsistencia_sexo_total <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,…
Antes de sumar beneficiarios, hay que mirar la columna
estado_reporte:
Hay meses marcados como sin_reporte. No son meses con
cero actividades: son meses de los que no hay
información. Por eso los excluimos explícitamente antes de
graficar:
tele_mensual %>%
filter(estado_reporte == "reportado",
!is.na(beneficiarios_total),
tipo_actividad == "telecapacitacion") %>%
group_by(anio) %>%
summarise(beneficiarios = sum(beneficiarios_total)) %>%
ggplot(aes(x = factor(anio), y = beneficiarios)) +
geom_col(fill = "#E4032E") +
labs(title = "Beneficiarios reportados de telecapacitación por año",
subtitle = "2026 incompleto: los datos llegan hasta septiembre",
x = "Año", y = "Beneficiarios (conteos de asistencia)") +
theme_minimal(base_size = 13)Tres advertencias de esta base. (1) Los
beneficiarios son conteos de asistencia, no personas únicas. (2) El año
2026 está incompleto: no lo compares como año entero. (3) En
telepromoción, el bloque de 2025 tiene el año inferido
(anio_inferido = 1) y conviene validarlo con la fuente.
La IA puede escribir, explicar y corregir código R con mucha rapidez. Eso no elimina tu trabajo: lo cambia. La pregunta, los datos, la validación y la interpretación siguen siendo tu responsabilidad.
Un prompt que produce código útil tiene seis piezas:
| Pieza | Ejemplo |
|---|---|
| Contexto | Soy analista de Telesalud y trabajo en R con dplyr y ggplot2. |
| Objetivo | Quiero contar las atenciones por departamento y mes. |
| Datos | Tengo un data frame atenciones con las columnas
mes (numérico, 6 a 9) y departamento (texto en
mayúsculas). |
| Restricciones | Usa solo dplyr y ggplot2. No reemplaces NA por 0. |
| Formato de salida | Comenta cada línea del código. |
| Pruebas | Agrega un control: la suma debe ser igual a
nrow(atenciones). |
Compara. Prompt vago: “hazme un gráfico de los datos de telesalud”. La IA tendrá que inventar nombres de columnas y elegir el gráfico por ti.
Plantilla para copiar y completar
Soy analista de Telesalud y trabajo en R con dplyr y ggplot2. Tengo un data frame llamado
atencionescon estas columnas:[nombres y tipos de las columnas que necesitas]. Quiero[tu pregunta]. No reemplaces NA por 0. Comenta cada línea del código y agrega un control que verifique que la suma de los grupos es igual anrow(atenciones).
Para obtener rápidamente los nombres y tipos de las columnas que vas a describir:
#> mes departamento proceso origen_atencion
#> "numeric" "character" "character" "character"
Eso es lo que se comparte con la IA: nombres y tipos. Nunca las filas.
| Qué | Ejemplos | |
|---|---|---|
| 🟢 Sí | Estructura y código | Nombres y tipos de columnas, tu código, mensajes de error |
| 🟡 Con cuidado | Resultados agregados | Tablas con conteos muy pequeños, datos institucionales no publicados |
| 🔴 Nunca | Microdatos | Filas de la base, archivos CSV, cualquier dato identificable |
Usa siempre las herramientas de IA autorizadas por tu institución.
| Mensaje | Causa más probable |
|---|---|
could not find function "%>%" |
Falta ejecutar library(dplyr) |
object 'atenciones' not found |
No ejecutaste el bloque de importación, o hay un error de tipeo |
... does not exist in current working directory |
El archivo no está en la carpeta de trabajo: revisa
getwd() |
unexpected symbol / unexpected ')' |
Falta o sobra una coma, un paréntesis o unas comillas |
there is no package called '...' |
Falta instalar el paquete |
Prompt de depuración
Estoy aprendiendo R. Ejecuté este código:
[tu código]. Obtuve este error:[mensaje completo]. Explícame en lenguaje simple qué significa, cuál es la causa más probable y cómo lo corrijo.
Ahora te toca a ti recorrer el ciclo completo.
Protocolo de 25 minutos
Elige una de estas preguntas o trae la tuya. Cada una incluye un punto de partida que puedes ejecutar y luego ampliar.
¿Qué explica el salto de TLM en agosto? Pista: cruza
el proceso con el mes y con origen_atencion.
atenciones %>%
filter(proceso == "TLM") %>%
count(mes, origen_atencion) %>%
arrange(origen_atencion, mes)Preguntas para tu interpretación: ¿qué origen cambia más entre julio y agosto? Eso que observas, ¿es un hallazgo o una hipótesis? ¿Con quién habría que hablar para confirmarlo?
¿Qué tres especialidades concentran más atenciones en tu departamento?
mi_departamento <- "COCHABAMBA" # cámbialo por el tuyo, en mayúsculas y sin tildes
atenciones %>%
filter(departamento == mi_departamento, !is.na(especialidad)) %>%
count(especialidad, sort = TRUE) %>%
head(3)Cuidado: al excluir los NA de especialidad
dejas fuera todo el proceso TLM. Dilo en tu interpretación.
¿La proporción de consultas de seguimiento cambia según el grupo de edad?
¿Qué porcentaje de atenciones tiene marcado el eje ENT en cada departamento?
atenciones %>%
group_by(departamento) %>%
summarise(pct_eje_ent = round(100 * mean(eje_ent == 1), 1)) %>%
arrange(desc(pct_eje_ent))Recuerda: 0 significa “sin marcación”, no “no corresponde a ENT”.
¿Cómo evolucionan por mes los beneficiarios de telecapacitación en tu departamento?
tele_mensual %>%
filter(departamento == "COCHABAMBA",
tipo_actividad == "telecapacitacion",
estado_reporte == "reportado",
!is.na(beneficiarios_total)) %>%
ggplot(aes(x = mes, y = beneficiarios_total)) +
geom_line(color = "#276DC3") +
geom_point(color = "#276DC3") +
facet_wrap(~ anio) +
scale_x_continuous(breaks = 1:12) +
labs(title = "Beneficiarios mensuales de telecapacitación",
x = "Mes", y = "Beneficiarios") +
theme_minimal()facet_wrap(~ anio) dibuja un panel pequeño por cada
año.
¿Hay especialidades que parecen la misma con distinto nombre?
atenciones %>%
filter(!is.na(especialidad)) %>%
count(especialidad) %>%
arrange(especialidad) %>%
print(n = Inf)Recorre la lista y anota los pares sospechosos. No los corrijas todavía: mañana decidiremos juntos qué hacer con ellos y cómo documentarlo.
Los datos originales no se modifican nunca. Los resultados se guardan
aparte, en la carpeta resultados:
dir.create("resultados", showWarnings = FALSE)
frecuencia_departamento <- atenciones %>%
count(departamento, sort = TRUE) %>%
mutate(porcentaje = round(100 * n / sum(n), 1))
write_csv(frecuencia_departamento, "resultados/frecuencia_departamento_dia1.csv")
write_csv(faltantes, "resultados/faltantes_por_variable_dia1.csv")write_csv() es el inverso de read_csv():
guarda una tabla como archivo CSV. Puedes abrirlo luego en Excel.
#> R version 4.5.3 (2026-03-11 ucrt)
#> Platform: x86_64-w64-mingw32/x64
#> Running under: Windows 11 x64 (build 26200)
#>
#> Matrix products: default
#> LAPACK version 3.12.1
#>
#> locale:
#> [1] LC_COLLATE=Spanish_Bolivia.utf8 LC_CTYPE=Spanish_Bolivia.utf8
#> [3] LC_MONETARY=Spanish_Bolivia.utf8 LC_NUMERIC=C
#> [5] LC_TIME=Spanish_Bolivia.utf8
#>
#> time zone: America/La_Paz
#> tzcode source: internal
#>
#> attached base packages:
#> [1] stats graphics grDevices utils datasets methods base
#>
#> other attached packages:
#> [1] ggplot2_4.0.3 dplyr_1.1.4 readr_2.2.0
#>
#> loaded via a namespace (and not attached):
#> [1] bit_4.6.0 gtable_0.3.6 jsonlite_2.0.0 crayon_1.5.3
#> [5] compiler_4.5.3 tidyselect_1.2.1 parallel_4.5.3 jquerylib_0.1.4
#> [9] textshaping_1.0.1 systemfonts_1.3.1 scales_1.4.0 yaml_2.3.10
#> [13] fastmap_1.2.0 R6_2.6.1 labeling_0.4.3 generics_0.1.4
#> [17] httr2_1.2.1 knitr_1.51 tibble_3.3.0 ellmer_0.3.2
#> [21] bslib_0.9.0 pillar_1.11.1 RColorBrewer_1.1-3 tzdb_0.5.0
#> [25] rlang_1.1.6 cachem_1.1.0 xfun_0.52 sass_0.4.10
#> [29] S7_0.2.0 bit64_4.6.0-1 otel_0.2.0 cli_3.6.5
#> [33] withr_3.0.2 magrittr_2.0.3 digest_0.6.37 grid_4.5.3
#> [37] vroom_1.7.0 rstudioapi_0.17.1 rappdirs_0.3.3 hms_1.1.4
#> [41] lifecycle_1.0.4 coro_1.1.0 vctrs_0.6.5 evaluate_1.0.5
#> [45] glue_1.8.0 farver_2.1.2 ragg_1.4.0 rmarkdown_2.31
#> [49] tools_4.5.3 pkgconfig_2.0.3 htmltools_0.5.8.1
sessionInfo() muestra la versión de R y de los paquetes
cargados. Es el registro que permite a otra persona, o a ti dentro de
unos meses, reproducir el análisis en las mismas condiciones.
usar_muestra <- TRUE por FALSE
en el punto 2.2, vuelve a ejecutar todo y compara los porcentajes por
departamento y por proceso. ¿La muestra representa bien a la base
completa?mean(atenciones$usa_glucometro) * 100.| Función | Qué hace | Ejemplo |
|---|---|---|
<- |
Guarda un valor en un objeto | x <- 5 |
c() |
Combina valores en un vector | c(6, 7, 8, 9) |
class() |
Tipo de un objeto | class(x) |
sum(), mean(), max(),
round() |
Resúmenes numéricos | mean(x, na.rm = TRUE) |
is.na() |
¿Es faltante? | sum(is.na(x)) |
getwd(), list.files() |
Carpeta de trabajo y su contenido | getwd() |
install.packages() |
Instala un paquete (una vez) | install.packages("dplyr") |
library() |
Carga un paquete (cada sesión) | library(dplyr) |
read_csv(), write_csv() |
Importar y exportar CSV | read_csv("archivo.csv") |
nrow(), ncol(), names() |
Tamaño y nombres de una tabla | nrow(atenciones) |
glimpse(), head(),
View() |
Mirar una tabla | glimpse(atenciones) |
%>% |
“Y luego” | datos %>% count(x) |
count() |
Cuenta filas por categoría | count(departamento, sort = TRUE) |
filter() |
Selecciona filas | filter(mes == 8) |
select() |
Selecciona columnas | select(mes, proceso) |
arrange() |
Ordena filas | arrange(desc(n)) |
mutate() |
Crea o modifica columnas | mutate(pct = 100 * n / sum(n)) |
group_by() + summarise() |
Resumen por grupo | group_by(proceso) %>% summarise(n = n()) |
n_distinct() |
Cuenta valores distintos | n_distinct(registro_id) |
ggplot() + aes() +
geom_col() |
Gráfico de barras | ver punto 2.9 |
labs(), theme_minimal() |
Títulos y estilo del gráfico | ver punto 2.9 |
ggsave() |
Guarda el último gráfico | ggsave("grafico.png") |
| Término | Significado |
|---|---|
| Script | Archivo de texto con instrucciones de R, guardado para repetirlas |
| Objeto | Un nombre que guarda un valor, un vector o una tabla |
| Vector | Serie de valores del mismo tipo |
| Data frame | Tabla de datos: filas son observaciones, columnas son variables |
| Función | Instrucción con nombre que recibe argumentos y devuelve un resultado |
| Argumento | Dato u opción que se entrega a una función, dentro del paréntesis |
| Paquete | Conjunto de funciones adicionales |
| NA | Dato faltante: “no sabemos” |
| Pipe | El operador %>%, que encadena pasos |
| Directorio de trabajo | Carpeta donde R busca y guarda archivos |
| Unidad de análisis | Lo que representa cada fila de una tabla |
| Reproducible | Que otra persona puede repetir el análisis y obtener lo mismo |
Día 2 · Calidad, limpieza y transformación. Aprenderás a evaluar la calidad de los datos, limpiar categorías inconsistentes y dejar una base lista para construir indicadores.