library(ggplot2); library(readxl); library(moments)

R Studio Basics

R Studio fácil y sencillo para principiantes

Básicos de R

Todo lo de las ayudantías parte de vectores, factores y data frames; con esta tabla se resuelve casi cualquier ítem de la Ayudantía 1.

R como calculadora, objetos y ayuda

Qué quiero Código Resultado
Sumar, restar, multiplicar, dividir 8 - 2, (9 + 3) * 7, 4 / 25 6, 84, 0.16
Potencia (4 - 7)^3 -27
Raíz cuadrada sqrt(81) 9
Raíz cúbica (también de negativos) 27^(1/3), -8^(1/3) 3, -2
Raíz de un negativo sqrt(-8), (-8)^(1/3) NaN (no existe en los reales)
e y potencias de e exp(1), exp(2) 2.718, 7.389
Logaritmo natural (ln) log(3) 1.099
Logaritmo en base 10 log10(1000) o log(1000, base = 10) 3
Redondear a la décima round(5.67, 1) 5.7
Redondear hacia arriba / abajo ceiling(10.2), floor(10.8) 11, 10
Valor absoluto abs(-8) 8
Suma y producto de un vector sum(x), prod(x)
Guardar en un objeto x <- 1 + 1 (guarda lo de la derecha en lo de la izquierda)
Ver los objetos creados ls() u objects()
Borrar un objeto / todos rm(x) / rm(list = ls())
Ayuda de una función help(sum) o ?sum
Versión de R R.version.string
Carpeta de trabajo getwd(); cambiarla con setwd("C:/Users/.../ExploratorioII")
Paquetes install.packages("ggplot2") una vez; library(ggplot2) en cada sesión

Ojo con -8^(1/3): R calcula primero la potencia y luego el signo, por eso da -2. (-8)^(1/3) da NaN. R distingue mayúsculas: Edad y edad son objetos distintos.

Vectores e indexación

Qué quiero Código Ejemplo
Crear vector c() edades <- c(21, 19, 23, 20)
Vector con nombres c(nombre = valor) notas <- c(Ana = 5.2, Pedro = 6.1)
Cantidad de elementos length(x) length(edades)
Tipo del objeto class(x) "numeric", "character", "factor"
Elemento en posición i x[i] edades[1]
Varias posiciones x[c(...)] edades[c(1, 3, 5)]
Rango de posiciones x[a:b] edades[2:5]
Todo menos x[-i] edades[-c(1, 4)]
Por nombre x["nombre"] notas[c("Ana", "Diego")]
Nombres names(x) names(notas)
Ordenar valores sort(x) sort(x, decreasing = TRUE)
Posiciones para ordenar order(x) nombres[order(edad, decreasing = TRUE)]

Condiciones y filtros

  • Comparadores: >, >=, <, <=, ==, !=; devuelven TRUE/FALSE por elemento.
  • Combinar: & (y), | (o). Se repite la variable: notas[notas >= 4 & notas <= 6].
  • Filtrar otro vector con la condición: nombres[notas >= 5].
  • Contar cuántos cumplen: sum(notas >= 4) (cada TRUE vale 1).
  • Valores únicos: unique(x); cuántos distintos: length(unique(x)); repetidos: duplicated(x).

Estadísticos básicos

Función Calcula
mean(x) Promedio
median(x) Mediana
min(x), max(x), range(x) Mínimo, máximo, ambos
sd(x), var(x) Desviación estándar, varianza
sum(x), prod(x) Suma, producto
quantile(x, probs = c(0.1, 0.9)) Percentiles (probs entre 0 y 1)
IQR(x) Rango intercuartil Q3 − Q1
summary(x) Mín, Q1, mediana, media, Q3, máx
skewness(x), kurtosis(x) Asimetría y curtosis (paquete moments)

Con datos faltantes se agrega na.rm = TRUE: mean(x, na.rm = TRUE).

Moda: library(modeest); mfv(x) (datos sin agrupar o cualitativos; puede devolver varias modas). Sin paquete: names(which.max(table(x))). Con datos agrupados se informa la clase modal, el intervalo con mayor fᵢ: names(which.max(table(intervalos))). Se dice "el intervalo [32, 39) concentra más observaciones", nunca "la moda es 35".

Coeficiente de variación: 100 * sd(x) / mean(x) (no hay función propia). Varianza en unidades al cuadrado; la DE en la unidad de la variable.

Fórmulas: centro y dispersión

\[\bar{x} = \frac{x_1 + x_2 + \cdots + x_n}{n} = \frac{1}{n}\sum_{i=1}^{n} x_i\]
\bar{x} = \frac{x_1 + x_2 + \cdots + x_n}{n} = \frac{1}{n}\sum_{i=1}^{n} x_i
\[Me = x_{\left(\frac{n+1}{2}\right)} \;\; (n \text{ impar}) \qquad Me = \frac{x_{\left(\frac{n}{2}\right)} + x_{\left(\frac{n}{2}+1\right)}}{2} \;\; (n \text{ par, datos ordenados})\]
Me = x_{\left(\frac{n+1}{2}\right)} \;\; (n \text{ impar}) \qquad Me = \frac{x_{\left(\frac{n}{2}\right)} + x_{\left(\frac{n}{2}+1\right)}}{2} \;\; (n \text{ par, datos ordenados})
\[s^2 = \frac{\sum_{i=1}^{n} (x_i - \bar{x})^2}{n - 1} \qquad s = \sqrt{s^2}\]
s^2 = \frac{\sum_{i=1}^{n} (x_i - \bar{x})^2}{n - 1} \qquad s = \sqrt{s^2}
  • La media usa todas las observaciones, por eso un dato extremo la mueve: agregar un paciente con IMC 50 kg/m² la sube. La mediana es más robusta.
  • Mismo centro no es mismo comportamiento: A = {9, 10, 10, 10, 11} y B = {1, 5, 10, 15, 19} tienen x̄ = 10, pero B es mucho más disperso.
  • Se eleva al cuadrado porque las desviaciones se cancelan: (−2) + (−1) + 0 + 1 + 2 = 0, en cambio 4 + 1 + 0 + 1 + 4 = 10. var() divide por n − 1 (varianza muestral).
  • La DE no significa que todos los datos estén exactamente a ±s de la media: es una distancia promedio.
  • Moda: un conjunto puede ser unimodal, bimodal, multimodal o no tener moda. 5, 6, 7, 8, 8, 9, 10, 10, 11 tiene dos modas (8 y 10).
  • Clase modal: con intervalos de igual amplitud es la barra más alta del histograma. Si las amplitudes son distintas se compara la densidad de frecuencia (fᵢ / amplitud), no la altura.

Factores, matrices, data frames y listas

  • Factor nominal: factor(x, levels = c("Soltero", "Casado", "Viudo")).
  • Factor ordinal: factor(x, levels = c("basica", "media", "superior"), ordered = TRUE).
  • Categorías del factor: levels(f); cuántas: nlevels(f). c("Mujer", "Hombre") es texto (character); factor(c("Mujer", "Hombre")) es factor y tiene levels.
  • Matriz desde cero: matrix(1:6, nrow = 2) (se llena por columnas; byrow = TRUE para llenar por filas).
  • Matriz pegando vectores: cbind(edad, notas) (como columnas) o rbind(edad, notas) (como filas).
  • Dimensiones: dim(m), nrow(m), ncol(m); nombres: colnames(m) <- c("Edad", "Nota"), rownames(m) <- nombres.
  • Acceder: m[fila, columna], m[, "Edad"] (columna entera), m[c("Pedro", "Luis"), "Nota"].
  • Data frame: datos <- data.frame(ID, Edad, Sexo, Peso); columna con datos$Edad; fila (un sujeto) con datos[2, ]; celda con datos[3, 4]; variables con names(datos); estructura y tipo de cada columna con str(datos).
  • Filtrar filas: iris[iris$Species == "setosa", 1:4].
  • Lista: persona <- list(nombre = "Ana", edad = 25, notas = c(5.5, 6.0)); acceder con persona$notas o persona[["nombre"]].
Objeto Qué guarda Ejemplo
Vector Valores del mismo tipo c(10, 20, 30)
Factor Categorías (variable cualitativa) sexo, estado civil
Matriz Filas × columnas, todo del mismo tipo cbind(edad, notas)
data.frame Filas = sujetos, columnas = variables de distinto tipo una base de datos
Lista Objetos de distinto tipo y largo la información de una persona

Importar datos

R
library(readxl)
getwd()                                  # ¿en qué carpeta está trabajando R?
data <- read_excel("colesterol.xlsx")    # Excel (.xls o .xlsx)
data <- read.csv("Libro5.csv")           # CSV
View(data); head(data); str(data)        # ver la base y el tipo de cada variable
dim(data); nrow(data); ncol(data)        # sujetos y variables
names(data); summary(data)               # nombres y resumen inicial de cada variable
data("iris")                             # base que viene con R
data$Unit_price <- as.numeric(data$Unit_price)  # si quedó como texto
data$sexo <- factor(data$sexo)                  # texto -> factor

Tabular: frecuencias y tablas de contingencia

Quiero Código
Frecuencia absoluta fᵢ table(data$sexo)
Frecuencia relativa rᵢ = fᵢ / n prop.table(table(data$sexo))
Porcentaje con 1 decimal round(100 * prop.table(table(data$sexo)), 1)
Frecuencia acumulada Fᵢ (ordinales o intervalos) cumsum(table(data$actividad))
Categoría más frecuente names(which.max(table(data$sexo)))
Tabla de contingencia (dos variables) t1 <- table(data$sexo, data$actividad)
Agregar totales de fila y columna addmargins(t1)
% dentro de cada fila (filas suman 100) round(100 * prop.table(t1, margin = 1), 1)
% dentro de cada columna round(100 * prop.table(t1, margin = 2), 1)
% sobre el total round(100 * prop.table(t1), 1)
Tabla como data frame (para ggplot o torta) tabla <- as.data.frame(table(data$sexo)) (columnas Var1, Freq)

table() sobre una variable cuantitativa con muchos valores da una tabla inútil: hay que agruparla en intervalos con cut() (ver Histogramas). Para que una ordinal salga en su orden, convertirla antes: factor(data$actividad, levels = c("Baja", "Moderada", "Alta"), ordered = TRUE).

Medidas por variable y por grupo (sapply y familia)

Para sacar medidas de muchas variables o de muchos grupos sin repetir código: sapply recorre columnas, tapply/aggregate recorren grupos, y una función propia medidas() junta todo en una tabla.

Quiero Función Ejemplo
Una medida para cada columna sapply(df, f) sapply(setosa, mean)
Solo columnas numéricas df[sapply(df, is.numeric)] sapply(iris[sapply(iris, is.numeric)], mean)
Por columnas (2) o filas (1) de una matriz apply(m, 2, f) apply(iris[, 1:4], 2, max)
Una variable según grupo tapply(x, grupo, f) tapply(iris$Petal.Length, iris$Species, mean)
Igual, pero como data frame aggregate(y ~ g, data, FUN) aggregate(Petal.Length ~ Species, data = iris, FUN = mean)
Todas las variables según grupo aggregate(. ~ g, data, FUN) aggregate(. ~ Species, data = iris, FUN = median)
Separar una variable por grupo split(x, grupo) split(iris$Petal.Length, iris$Species)
Con datos faltantes argumento extra sapply(df, mean, na.rm = TRUE)

La función medidas(): todas las medidas de una vez

Se define una vez y se usa con cualquier variable, base o grupo.

R
library(moments)
medidas <- function(x) c(
  Media = mean(x), Mediana = median(x), Min = min(x), Max = max(x),
  DE = sd(x), CV = 100 * sd(x) / mean(x),
  Asimetria = skewness(x), Curtosis = kurtosis(x))

medidas(iris$Petal.Length)                       # una variable

Caso 1 — varias variables (como la A6): cada fila es una variable.

R
setosa <- iris[iris$Species == "setosa", 1:4]
res <- round(t(sapply(setosa, medidas)), 2)   # t() pone variables en filas
res
#              Media Mediana Min Max   DE    CV Asimetria Curtosis
# Sepal.Length  5.01     5.0 4.3 5.8 0.35  7.04      0.12     2.65
# Petal.Length  1.46     1.5 1.0 1.9 0.17 11.88      0.10     3.80 ...

Caso 2 — una variable según grupo (como la Tarea 1: cuantitativa vs cualitativa): cada fila es un grupo.

R
res <- round(t(sapply(split(iris$Petal.Length, iris$Species), medidas)), 2)
res
#            Media Mediana Min Max   DE    CV Asimetria Curtosis
# setosa      1.46    1.50 1.0 1.9 0.17 11.88      0.10     3.80
# versicolor  4.26    4.35 3.0 5.1 0.47 11.03     -0.59     2.93
# virginica   5.55    5.55 4.5 6.9 0.55  9.94      0.53     2.74

# Tarea 1
t(sapply(split(BASE_TAREA_1$horas_estudio, BASE_TAREA_1$carrera), medidas))

Caso 3 — una sola medida por grupo: tapply(x, grupo, sd) o aggregate(x ~ grupo, data, FUN = sd).

Para llevar cualquiera de estas tablas al informe: xtable::xtable(res) (LaTeX) o knitr::kable(res) (R Markdown).

Cómo leer las medidas

  • CV < 15 % datos homogéneos; > 30 % muy dispersos (Tarea 1: horas de estudio CV = 31.4 %).
  • Asimetría ≈ 0 simétrica; > 0 cola a la derecha (media > mediana); < 0 cola a la izquierda.
  • Curtosis (moments) ≈ 3 mesocúrtica; > 3 leptocúrtica (colas más pesadas que la normal); < 3 platicúrtica (colas menos pesadas). No se interpreta como "qué tan puntiaguda".

Soluciones

Cada ayudantía agrega una pieza: vectores (A1), barras (A2–A3), tabla de frecuencias + histograma (A4), interpretación de medidas con airquality (A5) y boxplot + medidas + reporte en LaTeX (A6).

Ayudantía Fecha Base Tema Funciones clave
A1 19 ago vectores a mano Vectores, filtros, matrices c, [ ], sort, order, cbind, rownames
A2 26 ago penguins.xlsx Frecuencias y gráfico de barras table, barplot, geom_bar, labs
A3 2 sep SuperMarket.xlsx Barras, %, torta, dos variables prop.table, after_stat, coord_polar, position = "fill"
A4 9 sep SuperMarket.xlsx Tabla de frecuencias e histograma cut, table, cumsum, hist, geom_histogram
A5 23 sep airquality Interpretar medidas y gráficos na.omit, quantile, sd, boxplot.stats
A6 30 sep iris Descriptivo, boxplot, medidas por grupo summary, geom_boxplot, sapply, skewness

A1 — Vectores y matrices (19 ago)

Ejercicio 1 — edades <- c(21, 19, 23, 20, 22, 18, 25, 21)

R
length(edades)                     # a) 8 estudiantes
class(edades)                      # b) "numeric"
edades[1]                          # c) primera edad: 21
edades[c(1, 3, 5)]                 #    posiciones 1, 3, 5: 21 23 22
edades[2:5]                        #    de la 2 a la 5: 19 23 20 22
edades[-2]                         # d) todas menos la segunda
edades[-c(1, 4)]                   #    todas menos la primera y la cuarta
sort(edades)                       # e) creciente: 18 19 20 21 21 22 23 25
sort(edades, decreasing = TRUE)    #    decreciente: 25 23 22 21 21 20 19 18

Ejercicio 2 — notas <- c(Ana = 5.2, Pedro = 6.1, Camila = 4.8, Diego = 5.7)

R
names(notas)                       # a) "Ana" "Pedro" "Camila" "Diego"
class(notas)                       # b) "numeric" (los nombres no cambian la clase)
notas[2]                           # c) por posición: Pedro 6.1
notas["Camila"]                    #    por nombre: 4.8
notas[c("Ana", "Diego")]           #    dos a la vez: 5.2 5.7
sort(notas)                        # d) menor a mayor: Camila 4.8, Ana 5.2, Diego 5.7, Pedro 6.1
order(notas)                       # e) 3 1 4 2
notas[order(notas, decreasing = TRUE)]   # f) mayor a menor: Pedro, Diego, Ana, Camila

e) order() no devuelve notas, devuelve posiciones: "la menor está en la posición 3 (Camila), luego la 1 (Ana), la 4 (Diego) y la 2 (Pedro)".

Ejercicio 3

R
nombres <- c("Ana", "Pedro", "Camila", "Diego", "Sofía", "Esteban", "Luis")
edad  <- c(23, 19, 25, 21, 22, 24, 17)
notas <- c(5.5, 4.2, 6.1, 3.8, 5.9, 6.5, 4.2)

length(nombres)                    # a) 7 estudiantes
nombres[order(edad)]               # b) por edad, menor a mayor:
                                   #    Luis Pedro Diego Sofía Ana Esteban Camila
sort(edad)                         # c) 17 19 21 22 23 24 25
notas > 4                          # d) TRUE/FALSE por cada nota
notas >= 5
notas == 6.1
notas != 4.2
notas[notas >= 5]                  # e) 5.5 6.1 5.9 6.5
notas[notas < 4]                   #    3.8
notas[notas >= 4 & notas <= 6]     #    entre 4 y 6 incluidos: 5.5 4.2 5.9 4.2
sum(notas >= 4)                    # f) 6 estudiantes con nota >= 4
sum(notas < 4)                     #    1 estudiante con nota < 4
nombres[notas >= 5]                # g) Ana Camila Sofía Esteban
nombres[notas < 4]                 # h) Diego
summary(notas); sd(notas)          # i) ver abajo
unique(notas)                      # j) 5.5 4.2 6.1 3.8 5.9 6.5
length(unique(notas))              # k) 6 notas distintas
duplicated(notas)                  # l) TRUE solo en la posición 7 (el segundo 4.2)
nombres[order(notas, decreasing = TRUE)]  # m) Esteban Camila Sofía Ana Pedro Luis Diego

i) Respuesta modelo: "Las 7 notas van de 3,8 a 6,5. El promedio es 5,17 y la mediana 5,5: la mitad del grupo tiene 5,5 o más. La desviación estándar es 1,08 puntos (CV ≈ 21 %, variabilidad moderada). Solo Diego reprueba (3,8)."

Ejercicio 4

R
Informacion <- cbind(edad, notas)            # a) columnas = variables
dim(Informacion); nrow(Informacion); ncol(Informacion)   # b) 7 filas, 2 columnas
colnames(Informacion) <- c("Edad", "Nota")   # c)
rownames(Informacion) <- nombres             # d)
Informacion["Sofía", "Edad"]                 # e) 22
Informacion["Sofía", "Nota"]                 #    5.9
Informacion["Diego", ]                       #    toda la fila: Edad 21, Nota 3.8
Informacion[, "Edad"]                        #    todas las edades
Informacion[, "Nota"]                        #    todas las notas
Informacion[c("Pedro", "Camila", "Luis"), "Edad"]   # 19 25 17
Informacion[c("Pedro", "Camila", "Luis"), "Nota"]   # 4.2 6.1 4.2

Regla de la matriz: [fila, columna]; dejar un lado vacío significa "todas". rbind() pondría las variables como filas.

A2 — Pingüinos: frecuencias y barras (26 ago)

1. Importar

R
library(readxl); library(ggplot2)
penguins <- read_excel("penguins.xlsx")   # 333 filas, 4 columnas
str(penguins); head(penguins)

2. Clasificar variables

Variable Tipo Nota
Especies Cualitativa nominal Adelie, Chinstrap, Gentoo
Isla Cualitativa nominal Biscoe, Dream, Torgersen
Sexo Cualitativa nominal (dicotómica) female, male
Crias Cuantitativa discreta 0 a 3; "No_aplica" en machos, por eso R la lee como texto
R
penguins$Crias_num <- as.numeric(ifelse(penguins$Crias == "No_aplica", NA, penguins$Crias))
summary(penguins$Crias_num)      # solo hembras: media 1,35 crías

3a. Pingüinos por especie

R
table(penguins$Especies)                       # Adelie 146, Chinstrap 68, Gentoo 119
round(100 * prop.table(table(penguins$Especies)), 1)   # 43.8 % 20.4 % 35.7 %
barplot(table(penguins$Especies), main = "Pingüinos por especie",
        xlab = "Especie", ylab = "Frecuencia", col = c("orange", "purple", "cyan4"))

Respuesta: Adelie es la especie con más observaciones (146, 43,8 %); Chinstrap la con menos (68). Gráfico adecuado: barras, porque es cualitativa.

3b. Pingüinos por isla

R
table(penguins$Isla)                    # Biscoe 163, Dream 123, Torgersen 47
names(which.max(table(penguins$Isla)))  # "Biscoe"
names(which.min(table(penguins$Isla)))  # "Torgersen"

Respuesta: la mayor cantidad se registró en Biscoe (163) y la menor en Torgersen (47).

3c. Sexo

R
table(penguins$Sexo)                    # female 165, male 168
prop.table(table(penguins$Sexo))        # 49.5 % y 50.5 %
barplot(table(penguins$Sexo), col = c("pink", "skyblue"), main = "Sexo")

Respuesta: sí, es prácticamente igual (165 hembras y 168 machos, ≈ 50 % cada uno).

3d. ggplot2 paso a paso

R
ggplot(penguins, aes(x = Especies))              # 1) base + eje X (lienzo vacío)
ggplot(penguins, aes(x = Especies)) +
  geom_bar()                                     # 2) barras
ggplot(penguins, aes(x = Especies)) +
  geom_bar(fill = "steelblue") +                 # 5) relleno de un color
  labs(title = "Cantidad de pingüinos por especie",   # 3) título
       x = "Especie", y = "N° de pingüinos")    # 4) ejes

# relleno distinto por especie
ggplot(penguins, aes(x = Especies, fill = Especies)) + geom_bar() +
  scale_fill_manual(values = c("darkorange", "purple", "cyan4"))

La altura de cada barra es la frecuencia absoluta: el número de pingüinos de esa especie.

Extra (barras con dos variables): ggplot(penguins, aes(x = Isla, fill = Especies)) + geom_bar() muestra que Gentoo solo vive en Biscoe y Chinstrap solo en Dream.

A3 — SuperMarket: variables cualitativas (2 sep)

R
library(readxl); library(ggplot2); library(paletteer)
SuperMarket <- read_excel("SuperMarket.xlsx")    # 1000 transacciones
paleta <- paletteer_d("dichromat::Categorical_12")

Regla para dos variables: la de x es el grupo; la de fill es la que se distribuye dentro de cada grupo.

1. Transacciones por tipo de cliente

R
table(SuperMarket$Customer_type)              # Member 565, Normal 435
ggplot(SuperMarket, aes(x = Customer_type, fill = Customer_type)) +
  geom_bar() + scale_fill_manual(values = paleta) +
  geom_text(stat = "count", aes(label = after_stat(count)), vjust = -0.5) +
  labs(x = "Tipo de cliente", y = "N° de transacciones",
       title = "Transacciones por tipo de cliente") +
  theme(legend.position = "none")

Respuesta: 565 transacciones de clientes Member y 435 de Normal.

2. Porcentaje por tipo de cliente

R
round(100 * prop.table(table(SuperMarket$Customer_type)), 1)   # 56.5 % y 43.5 %
ggplot(SuperMarket, aes(x = Customer_type, fill = Customer_type)) +
  geom_bar(aes(y = after_stat(count / sum(count)))) +
  geom_text(stat = "count",
            aes(y = after_stat(count / sum(count)),
                label = scales::percent(after_stat(count / sum(count)), accuracy = 0.1)),
            vjust = -0.5) +
  scale_fill_manual(values = paleta) +
  scale_y_continuous(labels = scales::percent, expand = expansion(mult = c(0, 0.1))) +
  labs(x = "Tipo de cliente", y = "Porcentaje de transacciones")

Respuesta: el 56,5 % de las transacciones son de clientes Member y el 43,5 % de Normal.

3. Medios de pago (torta)

R
table(SuperMarket$Payment)       # Cash 344, Credit card 311, Ewallet 345
tabla1 <- as.data.frame(table(SuperMarket$Payment))
tabla1$porcentaje <- tabla1$Freq / sum(tabla1$Freq)
ggplot(tabla1, aes(x = "", y = Freq, fill = Var1)) +
  geom_bar(stat = "identity") + coord_polar(theta = "y") +
  geom_text(aes(label = scales::percent(porcentaje, accuracy = 0.1)),
            position = position_stack(vjust = 0.5)) +
  scale_fill_manual(values = paleta) +
  labs(title = "Transacciones por medio de pago", fill = "Medio de pago") +
  theme_void()

Respuesta: se usan 3 medios. Distribución pareja: Ewallet 34,5 %, Cash 34,4 %, Credit card 31,1 %.

4. Combinación género × medio de pago

R
tabla2 <- table(SuperMarket$Gender, SuperMarket$Payment)
addmargins(tabla2)
#         Cash Credit card Ewallet  Sum
# Female   205         183     183  571
# Male     139         128     162  429

5. Comparar directamente hombres y mujeres: barras lado a lado

R
ggplot(SuperMarket, aes(x = Payment, fill = Gender)) +
  geom_bar(position = "dodge") +
  geom_text(stat = "count", aes(label = after_stat(count)),
            position = position_dodge(width = 0.9), vjust = -0.5) +
  labs(x = "Medio de pago", y = "N° de transacciones", fill = "Género")

6. Barras apiladas (medio de pago dentro de cada género)

R
ggplot(SuperMarket, aes(x = Gender, fill = Payment)) +
  geom_bar() + scale_fill_manual(values = paleta) +
  geom_text(stat = "count", aes(label = after_stat(count)),
            position = position_stack(vjust = 0.5)) +
  labs(x = "Género", y = "N° de transacciones", fill = "Medio de pago")

7. % de cada medio de pago dentro de cada género (filas suman 100)

R
round(100 * prop.table(tabla2, margin = 1), 1)
#         Cash Credit card Ewallet
# Female  35.9        32.0    32.0
# Male    32.4        29.8    37.8
ggplot(SuperMarket, aes(x = Gender, fill = Payment)) +
  geom_bar(position = "fill") + scale_y_continuous(labels = scales::percent) +
  labs(x = "Género", y = "Porcentaje", fill = "Medio de pago")

Respuesta: las mujeres usan más efectivo (35,9 %); los hombres más Ewallet (37,8 %).

8. Distribución del género dentro de cada medio de pago (columnas suman 100)

R
round(100 * prop.table(tabla2, margin = 2), 1)
#         Cash Credit card Ewallet
# Female  59.6        58.8    53.0
# Male    40.4        41.2    47.0
ggplot(SuperMarket, aes(x = Payment, fill = Gender)) +
  geom_bar(position = "fill") + scale_y_continuous(labels = scales::percent)

Respuesta: en los 3 medios predominan las mujeres; Ewallet es el más equilibrado (53 % vs 47 %).

9. (Tarea) Categoría de producto según tipo de cliente

R
round(100 * prop.table(table(SuperMarket$Customer_type, SuperMarket$Product_line), 1), 1)
ggplot(SuperMarket, aes(x = Customer_type, fill = Product_line)) +
  geom_bar(position = "fill") + scale_y_continuous(labels = scales::percent) +
  scale_fill_manual(values = paleta) +
  labs(x = "Tipo de cliente", y = "Porcentaje", fill = "Categoría de producto")

Respuesta: se distribuye Product_line respecto del grupo Customer_type. Las 6 categorías están entre 15 % y 19 % en ambos tipos; la distribución es similar. Diferencia mayor: Food and beverages (Member 18,4 % vs Normal 16,1 %).

Pregunta tipo Gráfico position
Cuántos de cada combinación Apiladas "stack" (por defecto)
Comparar grupos directamente Lado a lado "dodge"
Qué % dentro de cada grupo Apiladas al 100 % "fill"

A4 — Tabla de frecuencias e histograma (9 sep)

Ojo: en el Excel Unit_price viene como texto (chr). Hay que convertirla antes de cualquier cálculo.

R
library(readxl); library(ggplot2)
SuperMarket <- read_excel("SuperMarket.xlsx")
SuperMarket$Unit_price <- as.numeric(SuperMarket$Unit_price)
x <- SuperMarket$Unit_price

1. Descripción inicial del precio unitario

R
n <- length(x)                     # 1000 observaciones
sort(x)                            # ordenar
minimo <- min(x); maximo <- max(x) # 10.08 y 99.96
rango <- maximo - minimo           # 89.88
mean(x)                            # 55.67
median(x)                          # 55.23
names(which.max(table(x)))         # moda: "83.77" (aparece 3 veces)

Respuesta: media 55,67 ≈ mediana 55,23 → distribución aproximadamente simétrica. La moda (83,77) aparece solo 3 veces en 1000 datos: en una variable continua la moda casi no informa.

2. Número de intervalos (k)

R
k <- round(1 + 3.22 * log10(n))    # Sturges: 1 + 3.22·3 = 10.66 → k = 11
sqrt(n)                            # alternativa raíz: 31.6 → 32 (demasiados)

k es la cantidad de intervalos (clases) en que se agrupan los datos para la tabla y el histograma.

3. Amplitud

R
A <- round(rango / k, 2) + 0.01    # 89.88 / 11 = 8.17 → A = 8.18

Se suma 0.01 para que el último intervalo alcance a cubrir el máximo.

4. Límites de los intervalos

R
limites <- round(minimo + (0:k) * A, 2)
# 10.08 18.26 26.44 34.62 42.80 50.98 59.16 67.34 75.52 83.70 91.88 100.06

A mano: el primer límite es el mínimo y se suma A cada vez: 10,08 → 18,26 → 26,44 → …

5. Etiquetas

R
etiquetas <- paste0(format(limites[-length(limites)], nsmall = 2), " - ",
                    format(limites[-1], nsmall = 2))
# "10.08 - 18.26" "18.26 - 26.44" ...

limites[-length(limites)] = todos menos el último (límites inferiores); limites[-1] = todos menos el primero (superiores).

6. Clasificar y tabla de frecuencias

R
intervalos <- cut(x, breaks = limites, labels = etiquetas,
                  right = FALSE, include.lowest = TRUE)
f  <- table(intervalos); fi <- as.vector(f)
ri <- fi / n; Fi <- cumsum(fi); Ri <- cumsum(ri)
tabla_frecuencia <- data.frame(intervalo = names(f), fi, ri = round(ri, 3),
                               Fi, Ri = round(Ri, 3))
sum(fi)                            # 1000 = n ✓
Intervalo fi Intervalo fi
[10.08, 18.26) 90 [59.16, 67.34) 79
[18.26, 26.44) 101 [67.34, 75.52) 98
[26.44, 34.62) 82 [75.52, 83.70) 89
[34.62, 42.80) 83 [83.70, 91.88) 81
[42.80, 50.98) 89 [91.88, 100.06] 110
[50.98, 59.16) 98 Total 1000

7. Histograma con hist() y con ggplot2

R
hist(x, breaks = limites, right = FALSE,
     main = "Distribución del precio unitario",
     xlab = "Precio unitario", ylab = "Frecuencia", col = "gray")

ggplot(SuperMarket, aes(x = Unit_price)) +
  geom_histogram(breaks = limites, closed = "left", fill = "gray", color = "black") +
  labs(title = "Distribución del precio unitario", x = "Precio unitario", y = "Frecuencia") +
  theme_minimal()

Respuesta: todas las barras tienen alturas parecidas (entre 79 y 110): la distribución es aproximadamente uniforme, los precios están repartidos de forma pareja entre 10 y 100, sin una zona que concentre los datos.

8. Repetir con Quantity

R
x <- SuperMarket$Quantity          # ya es numérica
# mismo código: n = 1000, min 1, max 10, rango 9, k = 11, A = 0.83
mean(x); median(x)                 # 5.51 y 5
names(which.max(table(x)))         # moda: 10 (119 veces)

Cuidado: Quantity es discreta (solo enteros 1 a 10). Con A = 0,83 queda un intervalo vacío ([5.15, 5.98) no contiene ningún entero) y el histograma muestra un hueco falso. Mejor usar un intervalo por valor:

R
table(x)                                   # frecuencia de cada cantidad
ggplot(SuperMarket, aes(x = Quantity)) +
  geom_histogram(breaks = seq(0.5, 10.5, 1), fill = "gray", color = "black") +
  scale_x_continuous(breaks = 1:10)

Respuesta: cada cantidad de 1 a 10 aparece entre 85 y 119 veces; también es aproximadamente uniforme. Media 5,51 ≈ mediana 5.

A5 — airquality: interpretar medidas y gráficos (23 sep)

Nueva York, mayo a septiembre de 1973. Los cuadros del PDF salen de na.omit(airquality): 111 días sin datos faltantes. Con la base completa los números no coinciden. Las respuestas siguen el orden del Word de preguntas.

Variable Descripción Unidad Tipo
Ozone Concentración de ozono ppb Cuantitativa continua
Solar.R Radiación solar Langley (Ly) Cuantitativa continua
Wind Velocidad promedio del viento mph Cuantitativa continua
Temp Temperatura máxima diaria °F Cuantitativa continua
Month Mes (5 a 9) — Cualitativa ordinal (como grupo)
Day Día del mes — Cuantitativa discreta
R
library(ggplot2); library(moments)
data("airquality")
aq <- na.omit(airquality)                 # n = 111
aq$Mes <- factor(aq$Month, levels = 5:9,
                 labels = c("Mayo", "Junio", "Julio", "Agosto", "Septiembre"))

# Cuadros 2, 3 y 5: cambiar x por aq$Temp, aq$Wind o aq$Ozone
x <- aq$Temp
round(c(Media = mean(x), Mediana = median(x),
        Moda = as.numeric(names(which.max(table(x)))),
        Min = min(x), Max = max(x), Rango = max(x) - min(x),
        Varianza = var(x), DE = sd(x), CV = 100 * sd(x) / mean(x),
        Q1 = quantile(x, 0.25, names = FALSE), Q3 = quantile(x, 0.75, names = FALSE),
        RIC = IQR(x), Asimetria = skewness(x), Curtosis = kurtosis(x)), 2)

# Cuadro 4: medidas por mes
t(sapply(split(aq$Wind, aq$Mes), function(x) round(c(
  n = length(x), Media = mean(x), Mediana = median(x), DE = sd(x), Var = var(x),
  Min = min(x), Q1 = quantile(x, 0.25, names = FALSE),
  Q3 = quantile(x, 0.75, names = FALSE), Max = max(x)), 2)))

# Figuras
ggplot(aq, aes(x = Temp)) + geom_histogram(binwidth = 5, fill = "gray", color = "black")
ggplot(aq, aes(x = Mes, y = Temp, fill = Mes)) + geom_boxplot() +
  theme(legend.position = "none")
ggplot(aq, aes(x = Ozone, y = "")) + geom_boxplot() +
  stat_summary(fun = mean, geom = "point", shape = 8)
boxplot.stats(aq$Ozone)$out              # atípicos

Situación 1 — Temperatura máxima diaria (°F)

Datos: media 77,79 · mediana 79 · moda 81 · mín 57 · máx 97 · rango 40 · varianza 90,82 · DE 9,53 · CV 12,3 % · Q1 71 · Q3 84,5 · asimetría −0,23.

  1. Promedio: la temperatura máxima diaria promedio fue 77,79 °F.
  2. Posición central: la mediana es 79 °F; la mitad de los días tuvo máximas de 79 °F o menos y la otra mitad de 79 °F o más.
  3. Media, mediana y moda: media 77,79 < mediana 79 < moda 81. Son cercanas, así que la distribución es casi simétrica, con leve asimetría negativa (asimetría −0,23): algunos días fríos, sobre todo en mayo, bajan la media.
  4. Dispersión: DE 9,53 °F; en promedio las máximas se alejan unos 9,5 °F de la media. CV 12,3 % < 15 %: temperaturas homogéneas, la media es representativa.
  5. Rango: 97 − 57 = 40 °F. Es la diferencia entre el día más caluroso y el más frío del período. Solo depende de dos días extremos.
  6. 50 % central: entre Q1 = 71 °F y Q3 = 84,5 °F (RIC 13,5 °F).
  7. Histograma: unimodal, con la mayor concentración entre 80 y 85 °F y la mayoría de los días entre 70 y 90 °F. La cola izquierda (días fríos de 57–65 °F) es algo más larga, lo que confirma la leve asimetría negativa. No hay atípicos.
  8. Entre meses: mayo es el más frío (media 66,5 °F); julio (83,9) y agosto (83,7) son los más calurosos; junio (78,2) y septiembre (76,9) son intermedios. La temperatura sube de mayo a julio–agosto y baja en septiembre. Julio es el mes menos variable (DE 4,4) y septiembre el más variable (DE 8,5). Julio tiene un atípico bajo (73 °F).

Situación 2 — Velocidad promedio del viento (mph)

Datos: media 9,94 · mediana 9,70 · mín 2,3 · máx 20,7 · rango 18,4 · varianza 12,66 · DE 3,56 · CV 35,79 % · Q1 7,4 · Q3 11,5.

Mes n Media Mediana DE
Mayo 24 11,50 11,50 3,46
Junio 9 12,20 11,50 3,87
Julio 26 8,52 7,70 3,00
Agosto 23 8,86 9,70 3,37
Septiembre 29 10,10 10,30 3,47
  1. Promedio: la velocidad promedio del viento fue 9,94 mph.
  2. Media y mediana: 9,94 vs 9,70, casi iguales. La distribución es aproximadamente simétrica, con leve asimetría positiva: algunos días de viento fuerte suben un poco la media.
  3. DE = 3,56 mph: en promedio, la velocidad diaria del viento se aleja unas 3,56 mph de la media de 9,94 mph.
  4. CV = 35,79 %: la DE equivale a casi el 36 % de la media. Es una variabilidad relativa alta (> 30 %): las velocidades son heterogéneas y la media no representa bien a todos los días.
  5. 50 % central: entre 7,4 y 11,5 mph (RIC 4,1 mph).
  6. Mes con mayor promedio: junio (12,20 mph), seguido de mayo (11,50). Junio tiene solo 9 días, así que su resultado es menos confiable.
  7. Mes más variable según DE: junio (3,87 mph). El menos variable es julio (3,00).
  8. Boxplots: mayo y junio tienen las cajas más altas (más viento); julio y agosto las más bajas (meses de verano con menos viento); septiembre es intermedio. Las cajas tienen alturas parecidas, así que la dispersión es similar entre meses. Hay atípicos altos en mayo (18,4 y 20,1 mph) y junio (20,7 mph).

Situación 3 — Concentración de ozono (ppb)

Datos: media 42,10 · mediana 31 · mín 1 · máx 168 · rango 167 · varianza 1107,29 ppb² · DE 33,28 · CV 79,04 % · Q1 18 · Q3 62 · asimetría 1,25.

  1. Promedio: la concentración promedio de ozono fue 42,10 ppb.
  2. Posición central: la mediana es 31 ppb; la mitad de los días tuvo 31 ppb o menos.
  3. Media y mediana: la media (42,10) supera claramente a la mediana (31). Hay asimetría positiva: pocos días con ozono muy alto inflan la media. La mediana representa mejor el día típico.
  4. 50 % central: entre 18 y 62 ppb (RIC 44 ppb).
  5. Dispersión: muy alta. La DE es 33,28 ppb (varianza 1107,29 ppb²) y el rango 167 ppb, de 1 a 168.
  6. Coeficiente de variación: 79,04 %, muy superior al 30 %. Los datos son muy heterogéneos y la media es poco representativa.
  7. Histograma: sesgado a la derecha. La mayoría de los días se concentra bajo 40 ppb (68 de 111) y las barras bajan a medida que sube el ozono, con una cola larga hasta 168 ppb.
  8. Valores extremos: sí. LS = Q3 + 1,5·RIC = 62 + 1,5·44 = 128 ppb, y los días con 135 y 168 ppb lo superan. Aparecen como puntos sobre el bigote superior.
  9. Descripción general: distribución asimétrica positiva, muy dispersa (CV 79 %) y con atípicos altos. El día típico tiene alrededor de 31 ppb, pero hay episodios puntuales de contaminación elevada. Conviene resumirla con mediana y RIC, no con media y DE.

Comparar dispersión entre variables con distinta unidad: por CV. Temperatura (12,3 %) < viento (35,8 %) < ozono (79,0 %).

A6 — iris: descriptivo, boxplot y medidas de Setosa (30 sep)

1. Cargar la base (viene con R, no se importa)

R
data("iris")
head(iris); str(iris)        # 150 flores × 5 variables, 50 por especie

2. Análisis descriptivo

R
summary(iris)
round(sapply(iris[, 1:4], sd), 2)                        # DE de cada variable
round(sapply(iris[, 1:4], function(x) 100 * sd(x) / mean(x)), 1)   # CV
Variable Mín Q1 Mediana Media Q3 Máx DE CV
Sepal.Length (cm) 4,3 5,1 5,80 5,84 6,4 7,9 0,83 14,2 %
Sepal.Width (cm) 2,0 2,8 3,00 3,06 3,3 4,4 0,44 14,3 %
Petal.Length (cm) 1,0 1,6 4,35 3,76 5,1 6,9 1,77 47,0 %
Petal.Width (cm) 0,1 0,3 1,30 1,20 1,8 2,5 0,76 63,6 %

Respuesta: hay 4 variables cuantitativas continuas (cm) y una cualitativa nominal (Species, 50 flores por especie). Las medidas del sépalo son homogéneas (CV ≈ 14 %). Las del pétalo son muy heterogéneas (CV 47 % y 64 %) y su media es menor que la mediana. El salto entre Q1 (1,6) y la mediana (4,35) en Petal.Length sugiere grupos de flores muy distintos: las especies.

3. Boxplot de la longitud del pétalo según especie

R
library(ggplot2)
ggplot(iris, aes(x = Species, y = Petal.Length, fill = Species)) +
  geom_boxplot() +
  labs(title = "Longitud del pétalo según especie", x = "Especie", y = "Longitud (cm)") +
  theme(legend.position = "none")
ggsave("boxplot_petalo.png", width = 7, height = 5)
aggregate(Petal.Length ~ Species, data = iris, FUN = median)

Respuesta: las tres cajas no se solapan, así que la longitud del pétalo separa claramente las especies. Setosa tiene los pétalos más cortos (mediana 1,5 cm) y menos variables (DE 0,17). Versicolor es intermedia (mediana 4,35 cm) y Virginica tiene los más largos (mediana 5,55 cm) y es la más dispersa (DE 0,55). Hay atípicos en Setosa (1,0; 1,1; 1,9 cm) y en Versicolor (3,0 cm).

4. Medidas para Setosa

R
library(moments)
setosa <- iris[iris$Species == "setosa", 1:4]
resultados <- data.frame(
  Media = sapply(setosa, mean),     Mediana = sapply(setosa, median),
  Minimo = sapply(setosa, min),     Maximo = sapply(setosa, max),
  DE = sapply(setosa, sd),
  Asimetria = sapply(setosa, skewness), Curtosis = sapply(setosa, kurtosis))
round(resultados, 2)
xtable::xtable(resultados, digits = 2)   # misma tabla en formato LaTeX
Variable (cm) Media Mediana Mín Máx DE Asimetría Curtosis
Sepal.Length 5,01 5,0 4,3 5,8 0,35 0,12 2,65
Sepal.Width 3,43 3,4 2,3 4,4 0,38 0,04 3,74
Petal.Length 1,46 1,5 1,0 1,9 0,17 0,10 3,80
Petal.Width 0,25 0,2 0,1 0,6 0,11 1,22 4,43

Respuesta: en Setosa media ≈ mediana en las 4 variables. Sepal.Length, Sepal.Width y Petal.Length son casi simétricas (asimetría cercana a 0). Petal.Width tiene asimetría positiva (1,22): la mayoría de los pétalos mide 0,2 cm y unos pocos llegan a 0,6 cm. Sepal.Length es platicúrtica (2,65 < 3). Las otras tres son leptocúrticas (> 3): datos concentrados cerca del centro con colas más pesadas; Petal.Width es la más marcada (4,43).

Tarea 1 — Cuantitativa vs cualitativa

R
# Clasificar y convertir a factor
BASE_TAREA_1$carrera <- factor(BASE_TAREA_1$carrera,
  levels = c("Ciencias", "Ciencias Sociales", "Ingeniería", "Salud"))
BASE_TAREA_1$carga_academica <- factor(BASE_TAREA_1$carga_academica,
  levels = c("Baja", "Moderada", "Alta"), ordered = TRUE)
summary(BASE_TAREA_1[, c("carrera", "carga_academica", "horas_estudio")])

round(100 * prop.table(table(BASE_TAREA_1$carrera)), 1)   # % por carrera

# Boxplot horizontal por grupo con la media marcada
ggplot(BASE_TAREA_1, aes(x = horas_estudio, y = carrera, fill = carrera)) +
  geom_boxplot() +
  stat_summary(fun = mean, geom = "point", shape = 16, size = 2) +
  theme(legend.position = "none")

# Medidas por carrera: ver "Medidas por variable y por grupo", caso 2

Ojo: en tu script se creó carga_academic (sin la "a" final), así que quedó una columna nueva en vez de convertir la original.

Gráficos: R base y ggplot2

Antes de graficar, pregúntate qué variable se distribuye y respecto de qué grupo. R base sirve para mirar rápido; ggplot2 queda mejor para el informe.

R base

R
t <- table(data$sexo)
barplot(t, main = "Pacientes según sexo", xlab = "Sexo", ylab = "N° de pacientes",
        col = c("pink", "skyblue"), ylim = c(0, 150))   # ylim cambia la escala del eje Y
barplot(t, horiz = TRUE, xlim = c(0, 150))              # barras horizontales
barplot(table(data$sexo, data$actividad), beside = TRUE, legend.text = TRUE)  # agrupadas
pie(t)                                                  # torta
pie(t, labels = paste0(names(t), " ", round(100 * prop.table(t), 1), " %"))
hist(data$imc, breaks = limites, right = FALSE)         # ver Histogramas
boxplot(data$edad, horizontal = TRUE)                   # boxplot
boxplot(edad ~ actividad, data = data)                  # boxplot por grupo

ggplot2: la estructura

Todo gráfico es ggplot(datos, aes(...)) + geom_...() + labs() + scale_...() + theme_...(). El + va al final de cada línea.

Capa Qué hace Ejemplo
Datos La base que se usa ggplot(data, ...)
aes() Qué variable va en x, y, fill aes(x = sexo, fill = actividad)
Geometría Tipo de gráfico geom_bar(), geom_col(), geom_histogram(), geom_boxplot()
after_stat() Usa lo que calculó el geom after_stat(count), after_stat(count / sum(count))
Escalas Ejes y colores scale_y_continuous(labels = scales::percent), scale_fill_manual()
Etiquetas Título y nombres labs(title = "...", x = "...", y = "...", fill = "...")
Coordenadas Girar o hacer torta coord_flip(), coord_polar(theta = "y")
Tema Apariencia theme_minimal(), theme_classic(), theme_void()

Recetario ggplot2

Quiero Código
Barras de frecuencias ggplot(data, aes(x = sexo)) + geom_bar()
Frecuencia sobre cada barra + geom_text(stat = "count", aes(label = after_stat(count)), vjust = -0.5)
Barras de proporciones geom_bar(aes(y = after_stat(count / sum(count))))
Eje Y en porcentaje + scale_y_continuous(labels = scales::percent)
Todas las barras de un color geom_bar(fill = "steelblue") (fuera de aes)
Un color por categoría aes(x = sexo, fill = sexo) (dentro de aes)
Elegir los colores + scale_fill_manual(values = c("Mujer" = "#E78AC3", "Hombre" = "#66C2A5"))
Sacar la leyenda + theme(legend.position = "none")
Barras horizontales + coord_flip()
Dos variables, apiladas (conteos) ggplot(data, aes(x = sexo, fill = actividad)) + geom_bar()
Dos variables, lado a lado geom_bar(position = "dodge")
Dos variables, apiladas al 100 % geom_bar(position = "fill") + eje en %
Torta ggplot(data, aes(x = "", fill = sexo)) + geom_bar() + coord_polar(theta = "y") + theme_void()
Histograma con tus límites geom_histogram(breaks = limites, closed = "left", color = "black")
Boxplot ggplot(data, aes(x = edad)) + geom_boxplot()
Boxplot por grupo ggplot(data, aes(x = actividad, y = edad)) + geom_boxplot()
Marcar la media en el boxplot + stat_summary(fun = mean, geom = "point", shape = 16, size = 3)
Guardar ggsave("grafico.png", plot = g, width = 10, height = 6, units = "in", dpi = 300) (también .pdf)

Con position = "fill", la variable en x es el grupo. x = sexo, fill = actividad muestra la actividad según sexo; al revés, el sexo según actividad.

Torta con porcentajes

R
tabla <- as.data.frame(table(data$sexo))            # columnas Var1 y Freq
tabla$porcentaje <- tabla$Freq / sum(tabla$Freq) * 100
g <- ggplot(tabla, aes(x = "", y = Freq, fill = Var1)) +
  geom_bar(stat = "identity") +                     # la altura es Freq, no un conteo
  coord_polar(theta = "y") +
  geom_text(aes(label = paste0(round(porcentaje, 1), " %")),
            position = position_stack(vjust = 0.5)) +  # etiqueta al centro del sector
  scale_fill_manual(values = c("Mujer" = "#E78AC3", "Hombre" = "#66C2A5")) +
  labs(x = NULL, y = NULL, fill = "Sexo", title = "Distribución por sexo") +
  theme_void()
ggsave("grafico_sexo.png", plot = g, width = 10, height = 6, units = "in", dpi = 300)

Usa la torta con pocas categorías y proporciones claramente distintas. Para comparar categorías con precisión son mejores las barras.

  • Evitar la torta cuando hay muchas categorías, cuando las proporciones son muy parecidas o cuando hay que comparar con precisión.
  • Las categorías deben ser mutuamente excluyentes y sumar el total.
  • coord_polar(theta = "y") transforma la altura de cada barra en un ángulo; el total de las frecuencias es el círculo completo.
  • scale_y_continuous(labels = scales::percent) solo cambia cómo se muestra el eje; la proporción la calcula after_stat(). Con library(scales) se puede escribir percent sin el prefijo.

Boxplot

R
ggplot(data, aes(x = edad, y = "")) +
  geom_boxplot() +
  stat_summary(fun = mean, geom = "point", shape = 16, size = 3) +   # media
  labs(title = "Distribución de la edad", x = "Edad (años)", y = NULL) +
  theme_classic() +
  theme(axis.text.y = element_blank(), axis.ticks.y = element_blank())  # sin eje Y

ggplot(data, aes(x = actividad, y = edad, fill = actividad)) +        # por grupo
  geom_boxplot() + theme(legend.position = "none")

boxplot.stats(data$edad)$out                                          # atípicos

Cómo leerlo: ver "Leer un boxplot" en Interpretación de resultados.

Histogramas

Un histograma se arma en 6 pasos: n, rango, k (Sturges), amplitud, límites e intervalos con cut(); después se grafica con hist() o geom_histogram() usando esos mismos límites.

Fórmulas

\[R = x_{\max} - x_{\min}\]
R = x_{\max} - x_{\min}
\[k = 1 + 3.22 \log_{10}(n) \quad \text{(Sturges)} \qquad k \approx \sqrt{n} \quad \text{(raíz)}\]
k = 1 + 3.22 \log_{10}(n) \quad \text{(Sturges)} \qquad k \approx \sqrt{n} \quad \text{(raíz)}
\[A = \frac{R}{k} \quad (\text{redondear y sumar } 0.01 \text{ para cubrir el máximo})\]
A = \frac{R}{k} \quad (\text{redondear y sumar } 0.01 \text{ para cubrir el máximo})
\[f_i = \text{frecuencia}, \quad r_i = \frac{f_i}{n}, \quad F_i = \sum_{j \le i} f_j, \quad R_i = \sum_{j \le i} r_j\]
f_i = \text{frecuencia}, \quad r_i = \frac{f_i}{n}, \quad F_i = \sum_{j \le i} f_j, \quad R_i = \sum_{j \le i} r_j

Pasos en R

R
x <- colesterol$imc          # o SuperMarket$Unit_price
n <- length(x)
minimo <- min(x); maximo <- max(x)
R <- maximo - minimo

k <- round(1 + 3.22 * log10(n))    # Sturges
# k <- ceiling(sqrt(n))            # alternativa raíz de n

A <- round(R / k, 2) + 0.01
limites <- round(minimo + (0:k) * A, 2)

etiquetas <- paste0(format(limites[-length(limites)], nsmall = 2), " - ",
                    format(limites[-1], nsmall = 2))

intervalos <- cut(x, breaks = limites, labels = etiquetas,
                  right = FALSE, include.lowest = TRUE)   # [a, b)

Al redondear A, revisar que el último límite supere el máximo y que el primer y el último intervalo tengan fᵢ ≠ 0. Si no, sumar una unidad del último decimal (0.01 con dos decimales). Ejemplo IMC (n = 250, k = 9): con A = 2.48 el último límite es 36.79 y deja fuera el máximo 36.83; con A = 2.49 llega a 36.88. Sturges y √n no coinciden: para n = 250 dan k ≈ 9 y k ≈ 16.

Tabla de frecuencias

R
f  <- table(intervalos)
fi <- as.vector(f)
ri <- fi / n
Fi <- cumsum(fi)
Ri <- cumsum(ri)
tabla_frecuencia <- data.frame(intervalo = names(f), fi = fi,
                               ri = round(ri, 3), Fi = Fi, Ri = round(Ri, 3))
sum(fi) == n            # chequeo: debe dar TRUE
xtable::xtable(tabla_frecuencia)   # versión LaTeX

Atajos: prop.table(tabla) * 100 da porcentajes; cumsum(tabla) da la acumulada.

Chequeos de la tabla: Σfᵢ = n, Σrᵢ = 1 y la última acumulada Fₖ = n (sum(fi), sum(ri), Fi[k]).

Graficar

R
# R base
hist(x, breaks = limites, right = FALSE,
     main = "Distribución del IMC", xlab = "IMC (kg/m^2)", ylab = "N° de pacientes")

# ggplot2
ggplot(colesterol, aes(x = imc)) +
  geom_histogram(breaks = limites, closed = "left",
                 fill = "steelblue", color = "black") +
  labs(title = "Distribución del IMC", x = "IMC (kg/m^2)", y = "N° de pacientes") +
  theme_minimal()

# Desde la tabla, con colores por intervalo
ggplot(tabla_frecuencia, aes(x = intervalo, y = fi, fill = intervalo)) +
  geom_col(width = 1, color = "black") +
  theme_minimal() + theme(legend.position = "none")
Argumento Para qué
breaks = limites Usa los límites calculados en vez de los automáticos
right = FALSE / closed = "left" Intervalos [a, b): incluye el límite izquierdo
include.lowest = TRUE Incluye el valor extremo en el primer/último intervalo
fill, color Relleno y borde de las barras
width = 1 en geom_col Barras pegadas, como histograma

hist(x) sin breaks deja que R elija los intervalos; para usar los de Sturges hay que pasarle breaks = limites. En el gráfico desde la tabla se ajustan los ejes con scale_x_discrete(labels = etiquetas) y scale_y_continuous(breaks = seq(0, 50, by = 5)).

Si el histograma tiene cola larga a la derecha, la media queda sobre la mediana (asimetría positiva); compararlas con mean() y median().

Histograma vs gráfico de barras

Histograma Gráfico de barras
Variable Cuantitativa Cualitativa, o cuantitativa con pocas categorías
Eje X Valores agrupados en intervalos Categorías
Barras Adyacentes (pegadas) Separadas
Orden Importa Puede no importar (nominales)

La tabla de frecuencias y el histograma muestran la misma información; el número de clases cambia la forma que se ve.

Interpretación de resultados

Una buena respuesta tiene 3 partes: el valor con su unidad, qué significa en el contexto de la variable, y qué se concluye (comparando con otra medida o con el gráfico). Nunca solo el número.

Checklist para describir una variable cuantitativa

  1. Explorar los datos: str(), head(), length(), NA.
  2. Mirar un gráfico: histograma y boxplot.
  3. Centro: mean(), median(), moda o clase modal.
  4. Dispersión: rango, var(), sd(), CV.
  5. Posición: quantile(x, probs = c(.10, .25, .50, .75, .90)), IQR().
  6. Valores extremos: regla 1,5·RIC, boxplot.stats(x)$out.
  7. Forma: media vs mediana, skewness(), kurtosis().
  8. Interpretar todo en el contexto de la variable, con su unidad.

Percentil en palabras: "P90 = 20 min: aproximadamente el 90 % de los tiempos es menor o igual a 20 minutos y el 10 % restante lo supera". El sesgo depende del estimador y de n: se informa junto con el gráfico y la comparación media vs mediana, nunca solo el número.

Qué dice cada medida

Medida Qué significa Frase modelo
Media Valor promedio; sensible a extremos "En promedio, la temperatura máxima fue 77,79 °F."
Mediana (Q2) Valor central: 50 % bajo y 50 % sobre; resistente a extremos "La mitad de los días tuvo a lo más 79 °F."
Moda Valor más frecuente "La temperatura más repetida fue 81 °F."
Rango Máx − mín; amplitud total, depende solo de 2 datos "Las temperaturas abarcan 40 °F, de 57 a 97 °F."
Varianza Promedio de desviaciones al cuadrado; unidad al cuadrado, difícil de interpretar "Varianza 90,82 °F²" (mejor interpretar la DE)
Desviación estándar Cuánto se alejan los datos de la media, en la misma unidad "En promedio, los valores se alejan 9,53 °F de la media."
CV DE como % de la media; compara dispersión entre variables con distinta unidad "CV 35,8 %: variabilidad relativa alta."
Q1 / Q3 25 % de los datos bajo Q1; 75 % bajo Q3 "El 25 % de los días tuvo menos de 71 °F."
RIC = Q3 − Q1 Ancho del 50 % central; resistente a extremos "El 50 % central está entre 71 y 84,5 °F (RIC 13,5 °F)."
Percentil p p % de los datos bajo ese valor "El 90 % de los días tuvo menos de X."
Asimetría Hacia dónde va la cola "Asimetría 1,25 > 0: cola hacia valores altos."
Curtosis Peso de las colas comparado con la normal (3); no es "qué tan puntiaguda" "Curtosis 4,2 > 3: leptocúrtica, valores concentrados con colas pesadas."

Cuartiles y percentiles: Q1 = P25, Q2 = P50 = mediana, Q3 = P75. quantile(x) sin probs entrega mín, Q1, Q2, Q3 y máx; summary(x) agrega la media.

Mapa: centro, dispersión, posición y forma

Pregunta Medidas En R
¿Dónde está el centro? Media, mediana, moda mean(), median(), mfv()
¿Cuánto varían? Rango, varianza, desviación estándar, CV max(x) - min(x), var(), sd(), 100 * sd(x) / mean(x)
¿Dónde se ubican? Cuantiles, RIC quantile(), IQR()
¿Qué forma tienen? Sesgo, curtosis skewness(), kurtosis()

CV: cómo clasificar

CV Interpretación
< 15 % Datos homogéneos, poca variabilidad; la media es representativa
15 % – 30 % Variabilidad moderada
> 30 % Datos heterogéneos, alta variabilidad; la media es poco representativa (usar mediana)
\[CV = \frac{s}{\bar{x}} \times 100\%\]
CV = \frac{s}{\bar{x}} \times 100\%

Forma: media vs mediana vs moda

Relación Forma Histograma Boxplot
Media ≈ mediana ≈ moda Simétrica Campana centrada Mediana al centro de la caja, bigotes parecidos
Media > mediana (> moda) Asimetría positiva (sesgo a la derecha) Cola larga hacia la derecha Mediana cerca de Q1, bigote superior más largo, atípicos arriba
Media < mediana (< moda) Asimetría negativa (sesgo a la izquierda) Cola larga hacia la izquierda Mediana cerca de Q3, bigote inferior más largo

Con asimetría o atípicos se describe con mediana y RIC; si es simétrica, con media y DE.

Ejemplos típicos de sesgo positivo: ingresos, tiempos de espera, precios de viviendas.

Valores atípicos (regla 1,5·RIC)

\[LI = Q_1 - 1.5 \cdot RIC \qquad LS = Q_3 + 1.5 \cdot RIC\]
LI = Q_1 - 1.5 \cdot RIC \qquad LS = Q_3 + 1.5 \cdot RIC

Un dato fuera de [LI, LS] es atípico y aparece como punto en el boxplot. En R: boxplot.stats(x)$out.

Ejemplo ozono: RIC = 62 − 18 = 44; LS = 62 + 66 = 128 → 135 y 168 ppb son atípicos.

Leer un boxplot

  1. Línea dentro de la caja = mediana.
  2. Bordes de la caja = Q1 y Q3; altura de la caja = RIC (50 % central).
  3. Bigotes = hasta el dato más extremo que no es atípico.
  4. Puntos sueltos = atípicos.
  5. Caja larga = más dispersión; mediana descentrada = asimetría.

Comparar grupos (boxplots o tabla por grupo)

  • Centro: ¿qué grupo tiene mayor mediana/media? ("Julio y agosto son los meses más calurosos").
  • Dispersión: ¿qué caja es más larga o qué DE es mayor? ("Septiembre es el más variable").
  • Forma y atípicos: ¿algún grupo asimétrico o con puntos extremos?
  • Tamaño: mencionar si un grupo tiene n chico ("junio con solo 9 días, resultado menos confiable").
  • Superposición: si las cajas se solapan mucho, la diferencia entre grupos es débil.

Leer un histograma

  • Centro: dónde está la barra más alta (clase modal).
  • Forma: simétrico, sesgado a la derecha o a la izquierda; unimodal o bimodal.
  • Dispersión: qué tan ancho es el rango de barras.
  • Atípicos: barras aisladas lejos del resto.

Qué NO concluir

  • Descriptivo ≠ causal: "en julio hay menos viento" no dice que el calor cause menos viento.
  • Los resultados valen para esta muestra (NY, mayo–sep 1973), no para cualquier lugar o año.
  • Si se eliminaron NA (na.omit), mencionarlo: cambia n y puede sesgar.

R Markdown

Un .Rmd tiene tres partes: encabezado YAML, texto en Markdown y chunks de código; el botón Knit (Ctrl+Shift+K) lo convierte en HTML, PDF o Word.

Plantilla base

R Markdown (.Rmd)
---
title: "Análisis Descriptivo"
author: "Nombre Autor"
date: "29 de septiembre de 2026"
output: html_document      # pdf_document, word_document
---

```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE, message = FALSE, warning = FALSE)
library(readxl)
library(ggplot2)
```

# Introducción
En este informe se analiza la variable ...

# Datos
```{r datos}
carga2 <- read_excel("BASE_TAREA_1.xlsx")
names(carga2)
```

# Medidas
```{r}
media <- mean(carga2$horas_estudio, na.rm = TRUE)
mediana <- median(carga2$horas_estudio, na.rm = TRUE)
```
El promedio fue de `r round(media, 2)` horas, con mediana `r round(mediana, 2)`.

Opciones de chunk

Opción Efecto
echo = FALSE Muestra el resultado pero no el código
include = FALSE Ejecuta pero no muestra nada (ideal para setup)
eval = FALSE Muestra el código sin ejecutarlo
message = FALSE, warning = FALSE Oculta mensajes y advertencias de paquetes
results = "hide" Oculta la salida impresa
fig.width = 6, fig.height = 4 Tamaño del gráfico en pulgadas
fig.align = "center" Centra el gráfico
fig.cap = "Título" Pie de figura

Las opciones van en la llave del chunk: ```{r grafico, echo=FALSE, fig.align="center"}. Con knitr::opts_chunk$set() quedan por defecto para todo el documento.

Sintaxis Markdown

Escribo Obtengo
# Título, ## Subtítulo Encabezados nivel 1 y 2
**negrita**, *cursiva* negrita, cursiva
`codigo` Código en línea
- item / 1. item Lista con viñetas / numerada
`r expresion` Resultado de R dentro del texto
$\bar{x}$ / $$ ... $$ Fórmula en línea / centrada
![pie](imagen.png) Imagen
[texto](url) Enlace

Tablas bonitas

R
knitr::kable(tabla_frecuencia, caption = "Tabla de frecuencias del IMC")

Errores típicos

  • Cada chunk corre en un entorno limpio al hacer Knit: cargar paquetes y datos dentro del .Rmd, no basta con tenerlos en la consola.
  • View() e install.packages() dentro del .Rmd rompen o enlentecen el Knit; déjalos en la consola.
  • La ruta del Excel es relativa a la carpeta del .Rmd; si el archivo está al lado, basta read_excel("BASE_TAREA_1.xlsx").
  • Los chunks se escriben en el .Rmd, no en un script .R.

Errores encontrados en tus scripts

Archivo Dice Debe decir
t.Rmd nar.rm=TRUE na.rm = TRUE
Ayudantia_1.R notas[notas >=4 & <=6] notas[notas >= 4 & notas <= 6]
Ayudantia_1.R length(unique(notas) length(unique(notas))
Ayudantia_1.R nombres[order(edad)] para "mayor a menor" nombres[order(edad, decreasing = TRUE)]
Ayudantia_3.R data <- read_excel(...) y luego usa SuperMarket SuperMarket <- read_excel("SuperMarket.xlsx")
Introduccion.R Data.frame(...) data.frame(...) (minúscula)

Qué gráfico y qué medidas según el tipo de variable

Primero clasificar la variable; eso decide la tabla, el gráfico y las medidas.

Tipo Ejemplos Tabla / medidas Gráfico En R
Cualitativa nominal sexo, carrera, medio de pago Frecuencia y %, moda Barras, torta table, prop.table, geom_bar
Cualitativa ordinal carga académica (Baja < Moderada < Alta) Frecuencia, %, acumulada, moda, mediana Barras en orden factor(..., ordered = TRUE)
Cuantitativa discreta n° de crías, Quantity Todas las medidas Barras o histograma table, summary
Cuantitativa continua temperatura, IMC, precio Todas las medidas, tabla por intervalos Histograma, boxplot cut, geom_histogram, geom_boxplot
Cuali vs cuali género × medio de pago Tabla de contingencia, % por fila/columna Barras apiladas, lado a lado, 100 % table(a, b), position = "fill"
Cuanti vs cuali horas estudio por carrera Medidas por grupo Boxplots por grupo tapply, aggregate, geom_boxplot

Recordatorio: class(x) dice cómo lo guardó R (numeric, character, factor); no siempre coincide con el tipo estadístico. Un código numérico (1 = Ciencias) es cualitativo aunque R diga numeric.

Errores de R frecuentes

Casi todos los errores son de nombre, de paquete sin cargar, de tipo de dato o de NA.

Mensaje Causa Solución
object 'X' not found Variable mal escrita o no creada/cargada Revisar mayúsculas; names(df); volver a correr read_excel
could not find function "ggplot" Paquete sin cargar library(ggplot2) (y install.packages("ggplot2") si no está)
there is no package called 'X' Paquete no instalado install.packages("X")
non-numeric argument to binary operator / argument is not numeric or logical La columna es texto df$x <- as.numeric(df$x)
Resultado NA en mean, sd, max... Hay datos faltantes mean(x, na.rm = TRUE) o na.omit(df)
unexpected symbol / unexpected ')' Falta coma, paréntesis o comilla Contar paréntesis; mirar la línea anterior
$ operator is invalid for atomic vectors Se usó $ en un vector Usar x["nombre"] o verificar que sea data frame
undefined columns selected Nombre de columna mal escrito names(df); recordar la coma: df[filas, ]
cannot open file / path does not exist Ruta mal o archivo en otra carpeta getwd(), setwd() o Session → Set Working Directory
Removed N rows containing non-finite values (warning) ggplot ignoró NA Normal; o filtrar con na.omit
+ al inicio de la consola Comando incompleto Terminar el comando o apretar Esc
ggplot no muestra nada + al inicio de la línea en vez del final El + va al final de cada línea
factor con NA tras convertir levels no coinciden con los datos (tildes, mayúsculas) unique(df$x) y copiar los niveles exactos

Atajos de RStudio: Ctrl+Enter corre la línea; Ctrl+Shift+M escribe %>%; Alt+− escribe <-; ?funcion abre la ayuda.