Tema 5 Tipos de datos: cadenas de caracteres, vectores y factores
En este tema y el siguiente se estudian los principales tipos de datos de R. Todo dato tiene que tener asociado un tipo. Según el tipo del dato se utilizará una determinada representación para almacenar el dato en el ordenador. El tipo de un dato también determina qué operaciones es posible realizar con él. Por ejemplo, es posible sumar números o matrices numéricas, pero no secuencias de caracteres. En este tema vamos a estudiar los tipos de datos: cadena de caracteres, vector y factor.
5.1 Cadenas de caracteres
En temas previos ya hemos trabajado con tipos de datos básicos como números y valores lógicos. Las cadenas de caracteres también son un tipo de dato básico. Consisten en una secuencia de caracteres y sirven para representar información de tipo texto, como el nombre de una persona, el nombre de una calle, una matrícula de un coche, etcétera.
En R una cadena de caracteres se representa como una serie de caracteres encerradas entre comillas dobles o simples:
Independientemente de que usemos comillas dobles o simples, R nos mostrará la cadena en la consola con comillas dobles o sin ningún tipo de entrecomillado, pero nunca con comillas simples.
Si es necesario usar un tipo de comillas en una cadena de caracteres, podemos usar el otro tipo de comillas para delimitar la cadena:
Otra posibilidad es usar una secuencia de escape con el carácter de escape \
:
Otras secuencias de escape muy utilizas son: \\
(barra invertida), \n
(salto de línea) y \t
(tabulador).
Existen funciones como paste
, paste0
o substr
que permiten trabajar con cadenas de caracteres, aunque no las vamos a estudiar aquí. Estas funciones son muy versátiles y, dependiendo de cómo se invoquen, son capaces de realizar distintos procesamientos. Por ejemplo, con paste
se puede obtener una cadena de caracteres mezclando información de tipo texto con numérica, trabajando de una forma parecida a cat
.
5.2 Vectores
Un vector es una colección ordenada de datos del mismo tipo. Es posible acceder a los elementos de un vector individual o parcialmente. Los vectores son uno de los tipos de datos más importantes de R y de la mayoría de los lenguajes de programación.
Los vectores se implementan como ilustra la Figura 5.1. En la figura se observa cómo se almacena en la memoria del ordenador los elementos del vector \([6, 20, 15, \ldots]\), apareciendo las direcciones de memoria a la izquierda de los valores. Los elementos de un vector se almacenan en posiciones contiguas de memoria. Como todos los elementos de un vector tienen que ser del mismo tipo ocupan el mismo tamaño en la memoria y, por lo tanto, guardando la dirección de inicio del vector es fácil y eficiente acceder a cualquiera de sus elementos dado su índice. Por ejemplo, en la figura se ilustra cómo se accede al elemento de índice 5. En general para acceder a la dirección de memoria del elemento de índice i
habrá que realizar el cálculo: \(direccion\_inicio\_vector + (i-1)*tama\_direccion\).

5.2.1 Creación de vectores
En R es posible crear vectores de un gran número de formas. Una de las más utilizadas es usando la función c
, que toma como parámetros un número arbitrario de elementos. La función c
devuelve un vector con la concatenación de dichos elementos.
La función c
admite vectores como parámetros:
Los elementos de un vector pueden ser de cualquier tipo base:
dedos <- c('Pulgar', 'Índice', 'Corazón', 'Anular', 'Meñique')
dedos
## [1] "Pulgar" "Índice" "Corazón" "Anular" "Meñique"
(v <- c(TRUE, FALSE, FALSE)) # vector de valores lógicos
## [1] TRUE FALSE FALSE
5.2.1.1 Generación de secuencias regulares
El operador :
permite generar un vector que almacena una progresión aritmética con valores separados por una unidad. Por ejemplo 1:5
equivale a c(1, 2, 3, 4, 5)
. También es posible generar secuencias decrecientes:
El operador :
tiene una prioridad alta, mayor que la de algunos operadores aritméticos:
La función seq
también permite generar progresiones aritméticas. seq(1, 10)
equivale a 1:10
. Sin embargo, seq
tiene parámetros con nombre que permiten generar otro tipo de secuencias. Por ejemplo, by
especifica la distancia entre los elementos de la progresión aritmética:
seq(10, 12, by = .2)
## [1] 10.0 10.2 10.4 10.6 10.8 11.0 11.2 11.4 11.6 11.8 12.0
seq(from = 11, to = 9, by = -.5)
## [1] 11.0 10.5 10.0 9.5 9.0
El parámetro con nombre length.out
también es muy usado. Indica la longitud de la secuencia:
seq(0, pi, length.out = 10)
## [1] 0.0000000 0.3490659 0.6981317 1.0471976 1.3962634 1.7453293 2.0943951
## [8] 2.4434610 2.7925268 3.1415927
Esta última invocación genera 10 números equidistantes en el intervalo \([0, \pi]\) (los extremos del intervalo se incluyen en la secuencia). Otro uso interesante de seq
combina sus parámetros from
, by
y length.out
:
Otra función muy útil es rep
, que sirve para replicar valores de un vector. Veamos algunos ejemplos:
5.2.1.2 Vector vacío
A veces se necesita generar un vector vacío, es decir, sin datos. A continuación se ilustra una forma de hacerlo:
La función length
, aplicada a un vector, devuelve cuántos elementos tiene. El ejemplo anterior sirve para un vector de datos reales, para datos de distintos tipos básicos habría que cambiar numeric
por:
integer
(enteros)character
(cadenas de caracteres)logical
(valores lógicos)complex
(números complejos)
Otra posibilidad es usar la función vector
(consulta su ayuda si estás interesado en ver las distintas opciones):
5.2.1.3 Generación de números aleatorios
Existen muchas funciones que devuelven como resultado un vector. Entre ellas se encuentran las funciones generadoras de números aleatorios, como runif
o rnorm
. Por ejemplo:
runif(10)
## [1] 0.11659336 0.85240534 0.16414016 0.04945534 0.21357398 0.12361920
## [7] 0.98069999 0.25565891 0.97463474 0.24254497
rnorm(5)
## [1] -0.4635491 0.5278014 -0.6334026 0.5447587 -0.5546772
La primera genera una muestra aleatoria de tamaño 10 de variables independientes de una \(U(0, 1)\) y la segunda una muestra de tamaño 5 de una \(\mathcal{N}(0, 1)\). Por supuesto, se puede especificar los parámetros de la distribución (usa la ayuda de estas funciones para ver cómo se hace). Nota: una \(U(0, 1)\) es una distribución uniforme de extremos 0 y 1, una variable aleatoria perteneciente a dicha distribución puede tomar cualquier valor en el rango \((0, 1)\) con igual probabilidad.
La generación de números aleatorios juega un papel crucial en el desarrollo de simulaciones con ordenador. Sin embargo, la aleatoriedad de los números generados hace que distintas ejecuciones de una simulación produzcan distintos resultados. Aunque esto es lo deseable, algunas veces puede dificultar la realización de un programa que implementa una simulación. Puesto que los números aleatorios generados son realmente pseudoaleatorios (generan una secuencia determinista a partir de una semilla), en la fase de desarrollo de una simulación podemos establecer la semilla para obtener siempre los mismos resultados. La función set.seed
permite especificar la semilla. Veamos un ejemplo:
runif(5) # 5 números U(0, 1)
## [1] 0.22001495 0.96992986 0.08472204 0.60965870 0.65542190
runif(5) # 5 números U(0, 1)
## [1] 0.8678395 0.7507620 0.1298208 0.7140594 0.7063484
set.seed(8)
runif(5) # muestra de 5 U(0, 1) a partir de semilla 8
## [1] 0.4662952 0.2078233 0.7996580 0.6518713 0.3215092
set.seed(8)
runif(5) # La misma muestra que antes
## [1] 0.4662952 0.2078233 0.7996580 0.6518713 0.3215092
Ejemplo. Queremos realizar un experimento aleatorio que puede resultar con éxito con probabilidad \(p \in [0, 1]\). Por ejemplo, \(p = 0.7\), ¿cómo podríamos simular dicho experimento?
p <- 0.7
(experimento <- runif(1))
## [1] 0.7189275
experimento <= p # ¿Ha habido éxito?
## [1] FALSE
La solución sería el código previo. Se genera un valor aleatorio de una uniforme \((0, 1)\). La probabilidad de que el valor generado sea menor o igual que p
es p
.
5.2.1.4 Muestras aleatorias
Relacionada también con la aleatoriedad tenemos a la función sample
, que obtiene una muestra aleatoria de un vector de elementos. Por defecto, la muestra es sin reemplazo y todos los elementos tienen la misma probabilidad de salir, pero hay parámetros para modificar este comportamiento por defecto.
sample(1:10, 5) # muestra (sin reemplazo) de tamaño 5 de números del 1 al 10
## [1] 7 10 6 1 5
sample(c("a", "b", "c"), 5, replace = TRUE) # con reemplazo
## [1] "b" "c" "c" "b" "b"
sample(n)
genera una permutación aleatoria de los valores en el vector 1:n
.
sample(n)
equivale a sample(1:n, size = n)
. Puedes consultar la ayuda de la función para ver todas las posibilidades.
Veamos ahora un ejemplo de uso de sample
en el que los distintos elementos tienen distintas probabilidades de formar parte de la muestra:
sample(1:3, size = 10, replace = TRUE, prob = c(.1, .1, .8))
## [1] 2 3 3 1 3 3 3 1 1 3
sample(c("a", "b"), size = 10, replace = TRUE, prob = c(2, 4))
## [1] "a" "a" "b" "b" "a" "b" "b" "b" "b" "a"
En el primer ejemplo, el 1 y el 2 tienen probabilidad 0.1, mientras que el 3 tiene probabilidad 0.8. En el segundo ejemplo podemos ver que la suma del vector de probabilidades no tiene por qué ser 1. En este caso la "a"
tendrá una probabilidad de \(\frac{2}{6}\) y la "b"
de \(\frac{4}{6}\).
Ejercicio. Vamos a volver a realizar el experimento aleatorio que puede resultar con éxito con probabilidad \(p \in [0, 1]\). Por ejemplo, \(p = 0.7\). Esta vez lo solucionaremos usando sample
.
Para comprobar que los resultados son consistentes repetimos el experimento 1000 veces y vemos cuántas veces hay fracaso y éxito (la función table
se estudiará más adelante), se espera que el número de éxitos sea próximo a 700:
5.2.1.5 Lectura de datos desde el teclado
La función scan
permite leer una serie de valores almacenados en un archivo o, en su defecto, provenientes del teclado. scan
tiene muchas opciones para controlar la lectura. Usada sin parámetros lee del teclado números separados por espacios en blancos y/o saltos de línea. La lectura termina cuando se introduce una línea en blanco y los número leídos se devuelven en un vector:
5.2.2 Tipo de un vector
Se puede obtener el tipo de un vector (de sus elementos) con la función typeof
:
v1 <- c(1, 2.8, -3.5)
typeof(v1)
## [1] "double"
v2 <- c(2L, 20L)
typeof(v2)
## [1] "integer"
v3 <- c(TRUE, FALSE, FALSE)
typeof(v3)
## [1] "logical"
v4 <- c("1234 ABC", "2222 JKF")
typeof(v4)
## [1] "character"
Un vector puede almacenar elementos de tipo double (real), integer (entero), logical (lógico), character (cadena de caracteres), complex (complejo) y raw. Los dos últimos tipos no se usan demasiado, especialmente raw, y no los utilizaremos en estos apuntes.
Se puede comprobar si un vector es de un tipo concreto con las funciones is.double
, is.integer
, is.logical
, is.character
, is.complex
e is.raw
:
Todos los elementos de un vector deben ser del mismo tipo. Si se intenta crear un vector con elementos de distinto tipo, todos los elementos se convierten al tipo con mayor rango de representación. El rango de representación de menor a mayor es: lógico, entero, real, carácter. Veamos algún ejemplo:
c(TRUE, 9) # se convierten todos a real
## [1] 1 9
c(5.4, "diez") # se convierten todos a cadena
## [1] "5.4" "diez"
Cuando los valores lógicos se convierten a números FALSE
se convierte en 0 y TRUE
en 1. Se puede forzar la conversión de un vector de un tipo a otro con las funciones as.TIPO
:
v <- c(TRUE, FALSE, TRUE)
as.integer(v)
## [1] 1 0 1
as.integer(c("1", "1.5", "a"))
## Warning: NAs introducidos por coerción
## [1] 1 1 NA
En la última conversión as.integer
no sabe cómo convertir "a"
a entero, por lo que genera el valor NA
y muestra una advertencia.
Como ejercicio, intenta predecir qué producirán las siguientes expresiones: c(1, FALSE)
, c("a", 7)
y c(TRUE, 1L)
. Escríbelas en la consola y comprueba si has acertado.
5.2.3 Aritmética de vectores
Una de las ventajas de R frente a otros lenguajes de programación es que en R se puede aplicar los operadores aritméticos (+
, -
, *
, etcétera) a los vectores (los vectores deberán ser de algún tipo numérico o de tipo lógico):
En caso de que los vectores no tengan la misma longitud, el resultado será de la longitud del vector más largo y los vectores más pequeños se reciclan para que encajen con la longitud del vector más largo.
v1 <- c(-5, 2, 8.1)
v2 <- 1:2
v1 + v2 # v2 se recicla a c(1, 2, 1)
## Warning in v1 + v2: longitud de objeto mayor no es múltiplo de la longitud de
## uno menor
## [1] -4.0 4.0 9.1
Aunque el código previo es correcto, R nos avisa porque la longitud de v1
no es un múltiplo de la de v2
. En el siguiente ejemplo R no avisa:
Es interesante saber que en R cuando escribimos un valor literal, como 7, realmente se trata como un vector de un elemento. Es decir,7
es una forma abreviada de escribir c(7)
. Por lo tanto, lo siguiente es válido:
Realmente 2 es un vector de longitud uno que se recicla para que tenga la longitud de x
(es decir, se recicla a un vector con 10 doses), y lo mismo ocurre con 3. El efecto del código previo es equivalente a:
Es decir, se evalúa la función \(2x -3\) para los valores del 1 al 10.
Las funciones como sqrt
, log
, exp
, cos
, sin
, abs
, round
, … están escritas para trabajar con vectores:
produce el vector c(\(e^{1/2}\), \(e^{3/2}\), \(e^{5/2}\), \(e^{7/2}\)).
La combinación de aritmética de vectores con funciones gráficas, como plot
, que estudiaremos más adelante, permite visualizar de una forma sencilla funciones matemáticas:
5.2.4 Funciones aplicables a vectores
Aparte de las funciones vistas anteriormente, como exp
, que aplican una operación a los distintos elementos de un vector, hay otras funciones que realizan un procesamiento sobre los elementos de un vector en conjunto. La lista es muy grande, por lo que vamos a citar sólo algunas:
mean
,median
,var
ysd
: calculan la media, mediana, varianza muestral y desviación típica muestral respectivamente de una muestra de valores.max
ymin
: el máximo y mínimo repectivamente.range
: produce el vectorc(min(v), max(v))
, dondev
es el vector al que se aplica.sum
yprod
: calculan la sumatoria y el productorio de los elementos de un vector respectivamente.cumsum
ycumprod
: suma y producto acumulado respectivamente.sort
: ordena los elementos (por defecto, en orden creciente).rev
: devuelve el vector en orden inverso.
Veamos algunos ejemplos:
x <- c(2, 9, 5, 2.3)
median(x)
## [1] 3.65
max(x)
## [1] 9
sum(x)
## [1] 18.3
cumsum(x)
## [1] 2.0 11.0 16.0 18.3
sort(x)
## [1] 2.0 2.3 5.0 9.0
rev(x)
## [1] 2.3 5.0 9.0 2.0
cumprod(1:5) # factoriales de los números del 1 al 5
## [1] 1 2 6 24 120
La aritmética de vectores, junto con el hecho de que gran parte de las funciones se puedan aplicar a vectores permite expresar cálculos complejos de una forma sencilla. Por ejemplo, la función sd
calcula la desviación típica de una muestra x
formada por \(n\) números según la fórmula:
\[ \sigma = \sqrt{\frac{1}{n-1} \sum_{i=1}^n (x_{i}- \overline{x})^{2}} \]
En caso de que no existiera la función sd
, se podría escribir de una forma relativamente sencilla usando aritmética de vectores:
5.2.5 Vectores lógicos
Un vector lógico es aquel cuyos elementos son valores lógicos, por ejemplo:
Los operadores relacionales (<
, >
, <=
, >=
, ==
, !=
) se aplican de forma natural a los vectores, produciendo como resultado un vector lógico:
(v <- rnorm(5))
## [1] 0.8890187 0.9535617 0.3606410 0.4474049 2.7091723
v > 0
## [1] TRUE TRUE TRUE TRUE TRUE
"casa" == c("barco", "casa", "pez")
## [1] FALSE TRUE FALSE
Observa que en las expresiones previas 0
y casa
se reciclan para coincidir con las longitudes de los distintos vectores. En el siguiente caso, los dos vectores tienen la misma longitud:
(v1 <- runif(4))
## [1] 0.3948128 0.5136268 0.8479917 0.5274398
(v2 <- runif(4))
## [1] 0.002748603 0.058694025 0.309743033 0.107500788
v1 >= v2
## [1] TRUE TRUE TRUE TRUE
Cuando un vector lógico es aplicado en una operación aritmética, los valores FALSE
se convierten a 0 y los TRUE
a 1. Esto permite aplicar sum
y mean
a vectores lógicos para calcular cuántos elementos de un vector y qué proporción, respectivamente, verifican una condición:
x <- c(-2, 3, 4, -1, 4.2)
(condicion <- x > 0)
## [1] FALSE TRUE TRUE FALSE TRUE
sum(condicion) # ¿Cuántos elementos son mayores que cero?
## [1] 3
sum(x > 0) # En una única expresión
## [1] 3
mean(x > 0) # Proporción de elementos mayores que cero
## [1] 0.6
Ejercicio. Vamos a simular el lanzamiento de una moneda no cargada un millón de veces y a contar cuántas caras y cruces han salido:
experimento <- sample(c("cara", "cruz"), 1e6, replace = TRUE)
sum(experimento == "cara")
## [1] 499866
sum(experimento == "cruz")
## [1] 500134
Ejercicio: Simulación. A veces se desconoce la probabilidad de ocurrencia de un evento. Esta probabilidad equivale a la proporción de veces (frecuencia relativa) en las que, si se realiza infinitamente un experimento aleatorio, ocurre el evento. Por lo tanto, se puede usar el ordenador para simular múltiples veces un experimento aleatorio y calcular la proporción de las veces en las que ocurre el evento. Esta proporción es una estimación puntual de la probabilidad del evento. Cuántas más veces se simule el experimento mayor certeza se tiene de la estimación puntual obtenida está cerca de la probabilidad real de ocurrencia del evento. Como ejemplo supongamos que lanzamos un dado negro y otro verde, ¿cuál es la probabilidad de que el dado verde sea mayor que el negro? Esta probabilidad se puede calcular fácilmente como \(\frac{15}{36}\). Sin embargo, vamos a realizar una simulación para obtener una estimación puntual de esta probabilidad. Para ello lanzamos un millón de veces 2 dados y calculamos la proporción de veces en las que un dado es mayor que el otro:
15/36
## [1] 0.4166667
verde <- sample(6, size = 1e6, replace = TRUE)
negro <- sample(6, size = 1e6, replace = TRUE)
mean(verde > negro)
## [1] 0.417818
Al ser el número de experimentos grande (un millón) se tiene bastante certeza de que el estimador obtenido será próximo al valor real de la probabilidad.
5.2.5.1 Operadores lógicos
En un tema previo vimos los operadores lógicos: Y (&&
), O (||
) y NO (!
). Los operadores lógicos se pueden aplicar a vectores lógicos. Cuando se aplica a vectores lógicos, el operador lógico Y cambia de &&
a &
. El operador O cambia de ||
a |
. Estos operadores se aplican elemento a elemento. Por ejemplo:
v1 <- c(TRUE, FALSE, TRUE, FALSE)
v2 <- c(TRUE, TRUE, FALSE, FALSE)
v1 & v2
## [1] TRUE FALSE FALSE FALSE
v1 | v2
## [1] TRUE TRUE TRUE FALSE
!v1
## [1] FALSE TRUE FALSE TRUE
Como veremos más adelante, las expresiones lógicas combinadas con la indexación lógica, resultan muy útiles para trabajar con vectores. Veamos algún otro ejemplo de expresiones lógicas:
v <- c(2, 5, 9, 4, 6)
v >= 5 & v <= 10 # ¿v en el rango [5, 10]?
## [1] FALSE TRUE TRUE FALSE TRUE
sum(v >= 5 & v <= 10) # Cantidad de valores en [5, 10]
## [1] 3
mean(v >= 5 & v <= 10) # Proporción de valores en [5, 10]
## [1] 0.6
Existen dos funciones muy útiles para trabajar con vectores lógicos. Se trata de all
, que indica si todos los elementos de un vector lógico son verdaderos y any
que indica si al menos uno es verdadero.
v <- c(2, 4, 8, 10, 11)
v %% 2 == 0 # Calcula si los elementos de v son pares o no
## [1] TRUE TRUE TRUE TRUE FALSE
all(v %% 2 == 0) # ¿Todos los elementos de v son pares?
## [1] FALSE
any(v %% 2 == 0) # ¿Algún elemento de v es par?
## [1] TRUE
all(v > 0) # ¿Son todos positivos?
## [1] TRUE
Otra función relacionada con vectores lógicos es which
. Esta función devuelve los índices de un vector lógico con valores a verdadero:
(v <- sample(10))
## [1] 9 1 5 7 2 3 4 10 6 8
which(v > 5) # índices de v con valores mayores que 5
## [1] 1 4 8 9 10
which(v == max(v)) # índice del máximo en v
## [1] 8
n <- 20
which(n %% 1:n == 0) # divisores del número n
## [1] 1 2 4 5 10 20
Por último, la función xor
calcula la función O exclusiva. Esta función lógica indica si sólo uno de sus dos operandos es verdadero.
v1 <- c(TRUE, FALSE, TRUE, FALSE)
v2 <- c(TRUE, TRUE, FALSE, FALSE)
xor(v1, v2)
## [1] FALSE TRUE TRUE FALSE
No existe un operador O exclusivo, porque la operación xor
se puede obtener mediante los operadores básicos: O, Y, NO. Piensa un poco e intenta expresar xor
mediante estos operadores. La solución es la siguiente:
5.2.6 Indexación de un vector
A veces es útil trabajar con una parte de los elementos de un vector, ya sea para consultar sus valores o para modificarlos. La indexación permite seleccionar una parte de los elementos de un vector. Los vectores de R admiten cuatro tipos de indexación, que describimos en las siguientes subsecciones. El operador de indexación es []
. Para indexar hay que especificar un vector de índices encerrado entre []
.
5.2.6.1 Mediante un vector de enteros positivos
En este caso los valores del vector de índices deben pertenecer al conjunto \(\{1, 2, ..., length(v)\}\), donde \(v\) es el nombre del vector que se indexa. Por ejemplo:
v <- c(1, 4, 9, 16, 25)
v[c(2, 5)] # índices 2 y 5
## [1] 4 25
v[1:3] # 3 primeros elementos (índices del 1 al 3)
## [1] 1 4 9
v[c(1, 2, 2, 4)] # se pueden repetir valores
## [1] 1 4 4 16
Observa que el primer índice de un vector en R es 1, en otros lenguajes de programación es 0. Cuando sólo se quiere acceder a un elemento de un vector se puede usar la siguiente sintaxis:
v[3]
equivale a v[c(3)]
porque realmente 3
equivale a c(3)
.
Cuando solo se indexa un elemento se puede usar el operador [[]]
en lugar del operador []
:
En los ejemplos anteriores hemos utilizado la indexación para consultar los elementos de un vector, pero también puede usarse para modificar sus elementos:
v <- c(1, 4, 9, 16, 25)
v[3] <- 0
v
## [1] 1 4 0 16 25
v[1:2] <- 0 # aquí 0 se recicla a c(0, 0)
v
## [1] 0 0 0 16 25
v[1:3] <- c(4, 10, -1)
v
## [1] 4 10 -1 16 25
Ejercicio. Crea un vector aleatorio de 10 elementos y selecciona aquellos elementos del vector que ocupan índices impares. Usa seq
para expresar el vector de índices impares.
5.2.6.2 Mediante un vector de enteros negativos
Es similar a lo visto en el apartado anterior, pero los valores del vector de índices son negativos e indican los índices a excluir, en lugar de los índices a incluir:
5.2.6.3 Mediante un vector de valores lógicos
En este caso el vector contiene valores lógicos. Los índices correspondientes a valores verdaderos en el vector lógico son seleccionados, los que son falsos no se seleccionan.
v <- c("Juan", "Pascal", "Julio")
soltero <- c(FALSE, TRUE, TRUE)
v[soltero]
## [1] "Pascal" "Julio"
En el caso de que la longitud del vector lógico sea inferior a la del vector indexado, el vector lógico es reciclado:
v <- (1:7) ^ 2
v
## [1] 1 4 9 16 25 36 49
v[c(TRUE, FALSE)] # posiciones impares del vector
## [1] 1 9 25 49
El último ejemplo usa el reciclado para seleccionar los elementos del vector que ocupan índices impares.
Se puede usar el indexado lógico para seleccionar los elementos de un vector que verifican una condición. Esta operación es muy útil y habitual y a veces es conocida como filtrado:
v <- rnorm(10)
v
## [1] -0.69344101 0.80718587 -0.09931044 0.90467478 -0.64957217 0.42901828
## [7] 0.48631070 0.59375000 0.99638417 -1.75199061
positivos <- v[v > 0]
positivos
## [1] 0.8071859 0.9046748 0.4290183 0.4863107 0.5937500 0.9963842
v2 <- v[v >= -1.5 & v <= 1.5] # valores en el rango [-1.5, 1.5]
v2
## [1] -0.69344101 0.80718587 -0.09931044 0.90467478 -0.64957217 0.42901828
## [7] 0.48631070 0.59375000 0.99638417
v[v < 0] <- -v[v < 0] # convierte en positivos los negativos
v
## [1] 0.69344101 0.80718587 0.09931044 0.90467478 0.64957217 0.42901828
## [7] 0.48631070 0.59375000 0.99638417 1.75199061
El último procesamiento también se puede hacer con la instrucción v <- abs(v)
.
5.2.6.4 Mediante un vector de cadenas de caracteres
Para usar esta posibilidad el vector debe poseer un atributo names que permita identificar a sus componentes. Vamos a ver cómo se especifica este atributo:
edades <- c(18, 17, 18, 19)
names(edades) <- c("Ana", "Sara", "Juan", "Julia")
edades
## Ana Sara Juan Julia
## 18 17 18 19
El indexado con cadenas es similar al indexado con valores enteros positivos, pero usando un vector de cadenas de caracteres:
edades[c("Ana", "Julia")]
## Ana Julia
## 18 19
edades["Ana"] <- edades["Ana"] + 1 # incrementa la edad
Es posible crear un vector con nombres usando la función c
:
5.2.7 Valores perdidos
Un valor perdido (missing value) es un valor no disponible. Esto ocurre con frecuencia en estadística porque se desconoce el valor de una variable, por ejemplo, la edad de una persona en una encuesta (esto podría ocurrir porque el encuestado no la introdujo o porque no se entiende lo que escribió). En R se utiliza la palabra reservada NA
(Not available) para indicar un valor perdido. Hay que tener en cuenta que la mayoría de operaciones que implican un valor perdido producen un valor perdido. Por ejemplo:
pesos <- c(77, 68.2, 90.5, NA, 61.5) # pesos en kilogramos
pesos_en_libras <- pesos * 2.205
pesos_en_libras
## [1] 169.7850 150.3810 199.5525 NA 135.6075
mean(pesos)
## [1] NA
Esto es lógico, porque, por ejemplo, no podemos saber la media de un conjunto de valores si se desconoce el valor de algún elemento.
Si queremos eliminar los valores perdidos podemos usar la función is.na
, que aplicada a un vector produce un vector lógico indicando si los elementos del vector son valores perdidos:
Ahora la media de los pesos conocidos puede calcularse como:
Algunas funciones como mean
o sd
tienen un parámetro con nombre para eliminar los valores perdidos del cómputo:
Es tentador usar pesos == NA
en lugar de is.na(pesos)
, pero la expresión pesos == NA
produce c(NA, NA, NA, NA, NA)
. Esto es correcto, aunque sorprenda, porque no puedes saber si un valor no disponible coincide con otro valor (disponible o no).
Hay que tener en cuenta que hay un segundo tipo de valor que es considerado como un valor perdido por la función is.na
, se trata del valor NaN
(Not a Number). Este valor se genera al realizar ciertas operaciones aritméticas indefinidas:
Para distinguir entre NA
y NaN
ten en cuenta que is.na
es cierto para ambos valores, pero is.nan
es válido sólo para NaN
.
Ejercicio: Dado el vector especificado más abajo, calcula los índices de dicho vector con valores NA
.
5.2.8 Crecimiento dinámico de un vector
La mayoría de las veces un vector se creará con un tamaño y éste no se modificará. Sin embargo, un vector puede crecer o decrecer. Vamos a verlo con un ejemplo:
(v <- c(3, 2))
## [1] 3 2
v[5] <- 11 # v crece de tamaño 2 a 5
v # los valores indefinidos valen NA
## [1] 3 2 NA NA 11
length(v) <- 3 # ahora el vector decrece
v
## [1] 3 2 NA
En concreto, para añadir un elemento al final de un vector tenemos varias posibilidades:
5.2.9 Operaciones con conjuntos
Un vector sin repetidos permite representar el concepto matemático de un conjunto (una colección de elementos sin repetidos y sin un orden determinado). R tiene implementadas las operaciones básicas entre conjuntos, como unión, intersección, etcétera.
x <- c(1, 2, 5)
y <- c(5, 6, 1, 3)
union(x, y) # unión
## [1] 1 2 5 6 3
intersect(x, y) # intersección
## [1] 1 5
setdiff(x, y) # diferencia: elementos de x que no están en y
## [1] 2
setdiff(y, x)
## [1] 6 3
setequal(x, y) # igualdad
## [1] FALSE
setequal(x, c(5, 2, 1))
## [1] TRUE
Puedes observar que setequal
no tiene en cuenta el orden de los elementos, puesto que, por definición de conjunto, los elementos de un conjunto no tienen un orden definido.
Para comprobar si un conjunto contiene un elemento se puede utilizar el operador %in%
o la función is.element
:
2 %in% x
## [1] TRUE
c(2, 4) %in% x # comprobamos dos elementos
## [1] TRUE FALSE
is.element(2, x)
## [1] TRUE
is.element(c(2, 4), x)
## [1] TRUE FALSE
Ejercicio. Vamos a comprobar que el vector d
sólo contiene días de la semana.
d <- c("martes", "jueves")
dias <- c("lunes", "martes", "miércoles", "jueves", "viernes",
"sábado", "domingo")
all(d %in% dias)
## [1] TRUE
d <- c("lunes", "enero")
all(d %in% dias)
## [1] FALSE
length(setdiff(d, dias)) == 0 # otra alternativa
## [1] FALSE
Ejercicio. Dados dos vectores con los identificadores de los alumnos matriculados en las asignaturas de estadística e informática vamos a calcular cuántos alumnos están matriculadas en ambas asignaturas.
5.3 Factores
Un factor es un vector utilizado para especificar una clasificación discreta (agrupamiento) de los componentes de otros vectores de la misma longitud. Es decir, los factores sirven para representar datos categóricos. En principio, esta información se puede guardar con cadenas de caracteres, con enteros o valores lógicos (dependiento del tipo de la información), pero usar factores tiene alguna ventaja. Como ejemplo, vamos a suponer que tenemos un vector con las notas de un examen de prácticas. El grupo de prácticas de los alumnos se guarda en otro vector:
Es decir, el primer alumno sacó un 10 y pertenece al grupo 1, el segundo sacó un 7 y pertenece al grupo 2, etcétera. En este ejemplo el grupo de un alumno es un dato categórico. Un pequeño problema de la representación del dato categórico con un vector de cadenas de caracteres es que no sabemos cuántas categorías hay. Viendo el vector se observan dos grupos, pero ¿y si hay un grupo en el que nadie se ha presentado al examen? Los factores permiten especificar todos los valores posibles de la categoría:
notas <- c(10, 7, 6, 8)
grupo <- c("g1", "g2", "g2", "g2")
grupo2 <- factor(c("g1", "g2", "g2", "g2"), levels = c("g1", "g2", "g3"))
grupo2
## [1] g1 g2 g2 g2
## Levels: g1 g2 g3
Además, al visualizar el factor se puede ver todos los posibles valores o niveles de la categoría.
La función table
se aplica a un factor y cuenta ocurrencias de cada categoría (tabla de frecuencias):
Si no se usan factores no es posible especificar una cuenta de cero (como para el grupo 3), porque table
no puede deducir la existencia de niveles sin ocurrencias.
La función prop.table
produce las frecuencias relativas:
prop.table(table(grupo))
## grupo
## g1 g2
## 0.25 0.75
prop.table(table(grupo2))
## grupo2
## g1 g2 g3
## 0.25 0.75 0.00
En general, table
permite contar las ocurrencias de los distintos elementos de un vector:
(v <- (sample(6, size = 10, replace = TRUE))) # lanzamos un dado 10 veces
## [1] 3 2 2 2 5 6 6 3 6 2
table(v)
## v
## 2 3 5 6
## 4 2 1 3
Se puede consultar información relativa a los niveles de un factor:
Internamente los factores se almacenan como un vector de enteros para ahorrar espacio:
La función tapply
permite aplicar una función a un vector, agrupando sus datos por categorías. Por ejemplo, veamos la nota media del examen y la nota media por grupo:
mean(notas) # media de las notas
## [1] 7.75
tapply(notas, grupo2, mean) # media de las notas por grupo
## g1 g2 g3
## 10 7 NA
En esta invocación tapply
selecciona las notas del grupo g1
(el vector c(10)
) y pasa dicho vector a la función mean
, y lo mismo con las notas de los otros dos grupos (para el grupo g2
el vector es c(7, 6, 8)
).
Si las categorías tienen un orden natural se puede usar la función ordered
en lugar de factor
para crear el factor:
nombres <- c("Ana", "Simón", "Nuria")
notas <- ordered(c("Notable", "Aprobado", "Sobresaliente"),
levels = c("Suspenso", "Aprobado", "Notable",
"Sobresaliente"))
notas
## [1] Notable Aprobado Sobresaliente
## Levels: Suspenso < Aprobado < Notable < Sobresaliente
Por lo demás, un factor ordenado se comporta igual que uno en el que no existe un orden de las categorías.
Para poder introducir un valor en un factor, este debe pertenecer a los niveles del factor:
notas[1] <- "Sobresaliente" # se modifica la nota
notas
## [1] Sobresaliente Aprobado Sobresaliente
## Levels: Suspenso < Aprobado < Notable < Sobresaliente
notas[1] <- "Excelente"
## Warning in `[<-.factor`(`*tmp*`, 1, value = "Excelente"): invalid factor level,
## NA generated
notas
## [1] <NA> Aprobado Sobresaliente
## Levels: Suspenso < Aprobado < Notable < Sobresaliente
La función cut
sirve para discretizar una variable real y convertirla en un factor. Vamos a ver un ejemplo (usa ?cut
para ver más posibilidades):
(notas <- runif(9, min = 0, max = 10))
## [1] 3.5589774 5.3559704 0.9308813 1.6980304 8.9983245 4.2263761 7.4774647
## [8] 8.2265258 9.5465365
n2 <- cut(notas, c(0, 5, 7, 9, 10))
n2
## [1] (0,5] (5,7] (0,5] (0,5] (7,9] (0,5] (7,9] (7,9] (9,10]
## Levels: (0,5] (5,7] (7,9] (9,10]
table(n2)
## n2
## (0,5] (5,7] (7,9] (9,10]
## 4 1 3 1
n3 <- cut(notas,
c(0, 5, 7, 9, 10),
labels = c("Suspenso", "Aprobado", "Notable", "Sob"))
n3
## [1] Suspenso Aprobado Suspenso Suspenso Notable Suspenso Notable Notable
## [9] Sob
## Levels: Suspenso Aprobado Notable Sob
table(n3)
## n3
## Suspenso Aprobado Notable Sob
## 4 1 3 1
El primer parámetro de cut
es un vector con los valores reales y el segundo un vector que define los intervalos usados para discretizar. Por ejemplo, si el segundo vector se llama s
, por defecto, el primer intervalo será (s[0], s[1]], el segundo (s[1], s[2]] y así sucesivamente.
5.4 Números complejos
Aunque en estos apuntes no los vamos a utilizar, pues en estadística rara vez son necesarios, R permite trabajar con números complejos:
5.5 Ejercicios
Crea un vector formado por 15 ocurrencias del valor 6.
Supón que quieres jugar al bingo. Escribe una expresión que sirva para simular la extracción de las 90 bolas de la urna.
Escribe una expresión que simule el lanzamiento de una moneda 10 veces. Obtén como resultado de la expresión un vector de valores
"cara"
y"cruz"
. Cuenta las ocurrencias de cara y cruz, y la proporción de cada valor.Una urna tiene 6 bolas rojas y 5 verdes. Simula la extracción aleatoria de una muestra de tamaño 3.
La probabilidad de que una variable \(X \sim \mathcal{N}(0, 1)\) sea mayor que 3 es
pnorm(3, lower.tail = FALSE)
. Obtén una estimación de dicho valor generando un vector con un millón de instancias de una \(\mathcal{N}(0, 1)\) y calculando qué proproción de ellas son mayores que 3.Crea un vector aleatorio de 10 elementos y selecciona aquellos elementos del vector que ocupan índices impares. Usa
seq
para expresar el vector de índices impares.Existen muchos métodos numéricos capaces de proporcionar aproximaciones a \(\pi\). Usa la siguiente fórmula para calcular una aproximación (el número de términos de la sumatoria se leerá de teclado):
\[ \pi = \sqrt{\sum_{i=1}^{\infty} \frac{6}{i^{2}}} \]
Dadas dos muestras emparejadas:
calcula su coeficiente de correlación lineal muestral de Pearson, según la siguiente fórmula, donde \(n\) es el tamaño de ambas muestras (puedes usar
sd
):\[ \frac{\sum_{i=1}^n (x_{i}- \overline{x})(y_{i}- \overline{y})}{(n-1)\sigma_x\sigma_y} \]
Crea un vector aleatorio con
sample(100, size = 10)
. Extrae aquellos elementos del vector que elevados al cuadrado sean menores que 625.Escribe una expresión lógica que permita comprobar si todos los elementos de un vector son iguales. Sugerencia: puedes comprobar si todos los elementos son iguales que el primero.
Escribe una expresión lógica que permita comprobar si un vector está ordenado en orden creciente. Sugerencia: comprobar si todos los elementos coinciden uno a uno con la versión ordenada del vector (usar
sort
yall
)Escribe una expresión lógica que permita comprobar si un vector contiene exactamente los números del 1 al 20 sin repetidos (en cualquier orden).
Muestra un vector en orden inverso sin usar la función
rev
. Sugerencia: indexa con los índices en orden inverso.Ejecuta:
¿Tiene sentido la salida? Consulta la ayuda de
all
y escribe una expresión que compruebe si todos los elementos conocidos de un vector son mayores que cero.Casi todas las operaciones con
NA
producenNA
. Pero hay excepciones. Piensa en qué puede producir:NA ^ 0
,NA || TRUE
yNA && FALSE
. Ejecuta las expresiones y comprueba si has acertado.Compara la creación de las siguientes variables que almacena un valor categórico:
v1 <- c("Hombre", "Mujer", "Mujer", "hombre") v2 <- factor(c("Hombre", "Mujer", "Mujer", "hombre"), levels = c("Mujer", "Hombre"))
Ejecútalo y piensa por qué producen valores distintos.
El siguiente código guarda la longitud de pétalo y el tipo de 150 flores en los vectores
longitud
ytipo
:El tipo es un factor. Calcula cuántas flores de cada tipo hay. Calcula la media de la longitud de pétalo por tipo de flor y la máxima longitud de pétalo para cada tipo de flor.
5.6 Soluciones
# Crear un vector formado por 15 ocurrencias de 6
rep(6, times = 15)
## [1] 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6
# Bingo
sample(90)
## [1] 33 7 26 50 30 68 51 54 59 32 11 79 90 42 77 13 78 72 16 88 48 23 15 62 24
## [26] 61 4 35 14 74 63 10 82 39 73 85 31 58 37 84 52 45 47 57 25 18 3 22 76 53
## [51] 55 75 71 67 28 34 64 20 83 29 44 69 17 56 65 43 86 9 27 46 81 40 36 41 89
## [76] 38 8 19 60 66 80 49 12 6 2 1 70 5 21 87
# Lanzamiento de una moneda 10 veces
(experimento <- sample(c("cruz", "cara"), size = 10, replace = TRUE))
## [1] "cara" "cara" "cruz" "cara" "cruz" "cara" "cara" "cruz" "cara" "cruz"
table(experimento)
## experimento
## cara cruz
## 6 4
cat("Proporción caras:", mean(experimento == "cara"), "\n")
## Proporción caras: 0.6
cat("Proporción cruces:", mean(experimento == "cruz"))
## Proporción cruces: 0.4
# Extracción de 3 bolas de una urna con 6 bolas rojas y 5 verdes
sample(rep(c("roja", "verde"), times = c(6, 5)), size = 3)
## [1] "verde" "roja" "verde"
# Valores mayores que 3 de una N(0, 1)
pnorm(3, lower.tail = FALSE) # valor teórico
## [1] 0.001349898
v <- rnorm(1e6)
mean(v > 3)
## [1] 0.001308
# Elementos que ocupan un índice impar
v <- sample(1:10, 10)
v
## [1] 10 4 5 3 1 2 7 8 6 9
v[seq(1, length(v), by = 2)]
## [1] 10 5 1 7 6
# Aproximación a pi
n <- as.integer(readline("Número de términos: "))
print(sqrt(sum(6 / (1:n) ^ 2)))
# Coeficiente de correlación de Pearson
x <- iris$Petal.Length
y <- iris$Petal.Width
cor(x, y) # usando la función cor
## [1] 0.9628654
numerador <- sum((x - mean(x)) * (y - mean(y)))
denominador <- (length(x) - 1) * sd(x) * sd(y)
numerador / denominador
## [1] 0.9628654
# Elementos elevados al cuadrado menores que 625
(v <- sample(100, size = 10))
## [1] 59 18 57 94 52 90 29 71 47 51
v[v * v < 625]
## [1] 18
# Comprobar que todos los elementos de un vector son iguales
v1 <- rep(10, 5)
all(v1[1] == v1)
## [1] TRUE
v2 <- c(rep(10, 3), 4, rep(10, 3))
all(v2[1] == v2)
## [1] FALSE
length(unique(v1)) == 1 # menos eficiente
## [1] TRUE
# Comprobar que un vector está ordeando en orden creciente
v <- 1:10
all(v == sort(v))
## [1] TRUE
v <- c(1:10, 8)
all(v == sort(v))
## [1] FALSE
# Vector con valores del 1 al 20
sol = 1:20
v <- sample(1:20, size = 20)
length(v) == length(sol) && all(sort(v) == sol)
## [1] TRUE
v <- sample(1:20, size = 20, replace = TRUE)
length(v) == length(sol) && all(sort(v) == sol)
## [1] FALSE
v = 1:30
length(v) == length(sol) && all(sort(v) == sol)
## [1] FALSE
# Invertir un vector sin usar rev
(v <- c(1, 9, -2, 8))
## [1] 1 9 -2 8
v[length(v):1]
## [1] 8 -2 9 1
# Factor con valores Hombre y Mujer
(v1 <- c("Hombre", "Mujer", "Mujer", "hombre"))
## [1] "Hombre" "Mujer" "Mujer" "hombre"
(v2 <- factor(c("Hombre", "Mujer", "Mujer", "hombre"),
levels = c("Mujer", "Hombre")))
## [1] Hombre Mujer Mujer <NA>
## Levels: Mujer Hombre
# Lo que ocurre es que nos hemos equivocado al teclear hombre con una h inicial en minúscula. Usando factor te das cuenta antes, porque al no corresponderse con un nivel ha puesto el valor a NA
# Flores
longitud <- iris$Petal.Length
tipo <- iris$Species
table(tipo) # Cuenta ocurrencias de cada tipo de flor
## tipo
## setosa versicolor virginica
## 50 50 50
tapply(longitud, tipo, mean) # media de la longitud de pétalo por tipo
## setosa versicolor virginica
## 1.462 4.260 5.552
tapply(longitud, tipo, max) # máximo de la longitud de pétalo por tipo
## setosa versicolor virginica
## 1.9 5.1 6.9