Como ya debes saber (y si no te lo cuento) en esta casa llevamos unas semanas enzarzados con mostrar como se puede empezar con R en RStudio. Hace dos semanas hablamos de objetos pero dejamos pendientes algunos tipos de datos especiales en los que hoy me gustaría profundizar.
¿Qué pasa cuando lo que tengo son variables cualitativas? ¿Qué son los valores lógicos? ¿Cómo marco si tengo un dato faltante? ¿Me dejas que te cuente?
Los factores y el orden
Cuando hace unas semanas hablábamos de como guardar variables en R, surgió la idea de que para poder guardar variables categóricas hacia falta un formato especial, y así es.
Ese formato especial permite que identifiquemos las categorías con su etiqueta. Una etiqueta que, llegado el momento, podriamos cambiar.
Por ejemplo, a veces puede ser útil guardar las posibles categorías como números pero sin perder de vista que esos “números” representan cualidades, no cantidades… Vamos, que el número es solo una etiqueta.
Y es que, piénsalo. Si tengo que guardar una variable donde las categorías son azul, verde, negro y marrón, puede darse el caso de equivocarme en la forma de guardar el nombre y tener varios azules, otros tantos Azules, algunos azule, o… vete tu a saber. Sin embargo, si los identificamos con un 1, 2, 3 y 4, es mucho más difícil equivocarse.
Pero bueno, que esto es solo una razón de la necesidad de poner etiquetas, que también puede ser que lo que quiera es cambiar las etiquetas existentes para cambiar el trabajo de idioma, sin tener que modificar dato a dato.
Vistas las razones para hacerlo, la pregunta ahora es ¿cómo lo hacemos?
Cómo crear un factor
Partiendo de un vector, o una columna dentro de un banco de datos que ya contiene las observaciones de esa variable factor, con las etiquetas que queremos usar, lo más práctico es tirar de la función de coerción: as.factor.
Por ejemplo, imaginemos que tenemos una variable que guarda el nivel de satisfacción de ciertos clientes con un producto, resumido como “alto”, “medio” y “bajo.
> satisfaccion_chr <- c("alto", "medio", "bajo", "bajo", "alto", "alto", "bajo", "bajo",
+ "alto", "alto", "bajo", "bajo", "alto", "bajo", "alto", "medio",
+ "medio", "alto", "bajo", "medio")
Si miráis que tipo tiene esta variable (lo puedes ver en el environment o usando la función str), veréis que es de tipo caracter
> str(satisfaccion_chr)
## chr [1:20] "alto" "medio" "bajo" "bajo" "alto" "alto" "bajo" "bajo" "alto" ...
Forzaremos entonces que sea de tipo factor usando as.factor:
> satisfaccion <- as.factor(satisfaccion_chr)
Y podemos ver como ahora su tipo ha cambiado
> str(satisfaccion)
## Factor w/ 3 levels "alto","bajo",..: 1 3 2 2 1 1 2 2 1 1 ...
Podemos acceder y cambiar las etiquetas de sus niveles con
> levels(satisfaccion)
## [1] "alto" "bajo" "medio"
Pero ¡ojo! fíjate bien. Los niveles se ordenan por orden alfabético y esto tiene cierto impacto a la hora de resumir la variable o dibujarla en un gráfico (cosas que veremos en próximas entradas). La pregunta entonces es ¿podemos cambiar el orden de las etiquetas?
El orden de las etiquetas
Si quieres cambiar el orden de los niveles, lo primero que debes tener en cuenta es: no trates de hacerlo directamente sobrescribiendo los “levels” que veiamos antes porque cambiaras las etiquetas de los datos, no solo el orden en el que se muestran.
Para que te hagas una idea del problema, si resumimos la variable con la función table vemos que tenemos 8 de nivel alto, 8 de bajo y 4 de medio:
> table(satisfaccion)
## satisfaccion
## alto bajo medio
## 8 8 4
Ahora vamos a copiar la variable en una nueva y vamos a forzar el orden, alto-medio-bajo mediante la función levels:
> satisfaccion2 <- satisfaccion
> levels(satisfaccion2) <- c("alto", "medio", "bajo")
Si ahora volvemos a usar la función table, lo que vemos es que tenemos 4 de nivel bajo y 8 de nivel medio, porque, en realidad, hemos cambiado las etiquetas pero no el orden:
> table(satisfaccion2)
## satisfaccion2
## alto medio bajo
## 8 8 4
Por tanto, si quieres cambiar las etiquetas de esta forma, recuerda mantener el orden que R asigna por defecto: alfabético en el caso de partir de un vector de caracteres y numérico en el caso de que las etiquetas originales fuesen números.
Si lo que queremos es forzar el orden, o forzar el orden y cambiar las etiquetas a la vez, lo mejor es usar la función factor.
La función factor
La función factor tiene dos argumentos, levels y labels. El primero hace referencia los valores/etiquetas que vamos a encontrar en los datos originales y, este sí, nos servirá para especificar el orden. El segundo, sirve para cambiar las etiquetas, por ejemplo, al inglés. Mira:
> satisfaccion3 <- factor(satisfaccion_chr, levels = c("alto", "medio", "bajo"), labels = c("high", "mid", "low"))
Y ahora sí, mantenemos la configuración de los datos, con las nuevas etiquetas y en el orden que queremos:
> table(satisfaccion3)
## satisfaccion3
## high mid low
## 8 4 8
Un argumento más de la función factor que aun no he mencionado, es el argumento ordered. Este es especialmente útil cuando nuestra variable es de tipo ordenado, es decir, cuando el orden de las categorías importa, como es el caso de satisfacción.
Para poder especificarlo debemos especificar ordered = TRUE y procurar que en el argumento levels especifiquemos correctamente el orden que deseamos que se conserve.
Pero… ojo, ese TRUE ya ha aparecido varias veces cuando estamos usando argumentos de funciones. TRUE significa verdadero en inglés… ¿verdadero?
Hablemos de valores lógicos.
Valores lógicos
Los valores lógicos en R son aquellos que indican la presencia ausencia de una característica, un sí o un no, un verdadero o falso, etc. y lo hacen mediante las etiquetas TRUE o FALSE o sus abreviaturas T y F.
Se trata de valores reservados de R, lo que quiere decir que no puedes usar como nombre para otra cosa, solo para lo que sirven, para ser valores lógicos.
Uno de sus principales usos es dentro de las funciones para activar o desactivar algunos de sus argumentos, como ya hemos visto en el caso de ordered de la función factor o el byrow de la función matrix que vimos en la entrada anterior, pero no es el único.
Los vectores lógicos también nos pueden servir para indicar que posiciones de un vector cumplen una determinada característica. Para ello nos apoyaremos en los conocidos como operadores lógicos que son:
Operador | Función |
---|---|
< | para ver si un valor es menor que otro |
<= | para ver si un valor es menor o igual que otro |
> | para ver si un valor es mayor que otro |
>= | para ver si un valor es mayor o igual que otro |
== | para ver si un valor es exactamente igual a otro |
!= | para ver si un valor es distinto de otro |
Mira un ejemplo:
> x <- 3
> y <- x > 12
> y
## [1] FALSE
Hemos creado un objeto que valía 3 y hemos preguntado si es mayor que 12 creando otro objeto con la respuesta que, en este caso es FALSE.
Podríamos haber hecho lo mismo con un vector obteniendo un vector de valores lógicos donde el TRUE nos indicará que la condición se cumple:
> x <- 1:20
> y <- x > 12
> y
## [1] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## [13] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
Una cosa curiosa, además, es que si ahora usamos la función sum para sumar todos los valores del vector, lo que obtendremos es el número de valores TRUE:
> sum(y)
## [1] 8
Las consultas con resultado lógico se pueden además encadenar preguntando por varias a la vez. Pero para saber como hacerlo necesitamos hablar de la aritmética de los valores lógicos. Vamos a ello.
Aritmética de valores lógicos
Existen otros operadores lógicos, además de los ya mencionados, que nos permiten trabajar con varios objetos de tipo lógico (como cuando sumamos o restamos datos numéricos). La siguiente tabla resume un poco cuales son y como funcionan. Teniendo en cuenta que x e y sean vectores de uno o más valores lógicos
operador | Operación |
---|---|
!x | Negación de x: Los TRUE los convierte en FALSE y viceversa. |
x&y | Intersección, operador lógico “y” TRUE “y” TRUE da TRUE, otra comparación da FALSE |
x|y | Unión, operador lógico “o” FALSE “o” FALSE da FALSE, otra comparación da TRUE |
xor(x,y) | “o” exclusivo, solo da TRUE si exclusivamente uno de los dos es TRUE |
all() | dada una serie de valores lógicos, dará TRUE si todos lo son y FALSE en otro caso |
any() | Para una serie de argumentos lógicos devuelve TRUE si al menos uno lo es |
Pero para ver mejor como funcionan, vamos con algunos ejemplos.
Empecemos por los operadores &, | y xor que tienen siempre dos argumentos, en este caso dos vectores lógicos que van a comparar posición a posición.
> x <- c(TRUE, TRUE, FALSE, FALSE)
> y <- c(TRUE, FALSE, TRUE, FALSE)
> x & y
## [1] TRUE FALSE FALSE FALSE
> x | y
## [1] TRUE TRUE TRUE FALSE
> xor(x, y)
## [1] FALSE TRUE TRUE FALSE
Podemos ver que & da TRUE solo cuando la posición correspondiente de x y la de y lo son. Por otro lado, | dará TRUE si al menos uno de los dos valores lo es o los dos lo son. Por último, xor dará TRUE exclusivamente cuando lo sea uno y no el otro.
Después tenemos las ordenes any y all que nos sirven estudiar un vector de valores lógicos en si mismo.
> any(x)
## [1] TRUE
> all(x)
## [1] FALSE
Como vemos, la orden any da TRUE si alguno de los valores de x lo es, mientras que all solo daría TRUE en el caso de que todos sus valores lo fuesen.
Como decíamos hace unos párrafos, utilizando algunas de estas ordenes podemos ir encadenando comprobaciones lógicas para llegar finalmente a obtener un vector indicando la posición en la que se cumplen esas condiciones. De ese modo, un vector lógico acabará siendo el aliado perfecto para seleccionar dentro de un banco de datos. Veámolos.
Vectores índice lógicos.
Cuando estuvimos hablando de objetos hace unas semanas, vimos que para acceder a los valores dentro de un vector, una matriz, un array o un banco de datos, podíamos hacer uso de los corchetes e indicar las posiciones a las que queríamos acceder. Pues bien, otra de las formas de realizar esta labor es poniendo, entre esos mismos corchetes, un vector de valores lógicos que indique con un TRUE los valores a seleccionar. Pongámonos en situación.
A la opinión de los clientes que ya teníamos, les hemos añadido la edad de cada uno de ellos y creado un banco de datos con ambas variables
> edad <- c(37, 22, 36, 30, 24, 30, 24, 35, 30, 31, 34, 35,
+ 28, 22, 39, 22, 27, 34, 23, 33)
>
> df <- data.frame(satis = satisfaccion, edad = edad)
Queremos conocer ahora la edad de aquellos que han dado una puntuación alta al producto.
> indices <- df$satis == "alto"
> indices
## [1] TRUE FALSE FALSE FALSE TRUE TRUE FALSE FALSE TRUE TRUE FALSE FALSE
## [13] TRUE FALSE TRUE FALSE FALSE TRUE FALSE FALSE
> df$edad[indices]
## [1] 37 24 30 30 31 28 39 34
También podríamos hacerlo seleccionando la fila completa (todas las columnas) del banco de datos que cumpla esa condición:
> df[indices,]
## satis edad
## 1 alto 37
## 5 alto 24
## 6 alto 30
## 9 alto 30
## 10 alto 31
## 13 alto 28
## 15 alto 39
## 18 alto 34
Podríamos encadenar ordenes para que se cumpliesen condiciones en más de una variable. Añadamos el año en el que se compró el producto:
> df$year <- c(2022, 2022, 2022, 2022, 2022, 2022, 2022, 2022, 2021, 2022, 2022, 2022, 2021, 2021, 2021, 2022, 2021, 2022, 2022, 2022)
Y seleccionemos las observaciones con edad mayor de 35 y año de compra 2022:
> indices <- (df$edad >= 35 & df$year == 2022)
> indices
## [1] TRUE FALSE TRUE FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE TRUE
## [13] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
> df[indices , ]
## satis edad year
## 1 alto 37 2022
## 3 bajo 36 2022
## 8 bajo 35 2022
## 12 bajo 35 2022
Hasta aquí, todo estupendo pero… ¿Qué pasaría si perdiéramos un valor del banco de dato? ¿Si para un cliente, por ejemplo, no tuviésemos la edad?
La primera pregunta que cabe hacerse en este caso es que valor poner en su lugar ¿Un 0? ¿Se puede dejar en blanco?
Hablemos de valores especiales.
Datos Especiales NA, Inf y NaNs
En R encontramos algunas expresiones que permiten identificar situaciones especiales dentro de los datos o al ejecutar alguna orden. La primera y en la que más nos centraremos hoy, es el valor que indica que tenemos datos faltantes, NA: “not available”.
Es muy importante tener en cuenta que no podríamos dejar el valor en blanco como sí hacemos (y es conveniente) en una tabla de excel. Además, poner un 0 u otro valor, es un error que puede tener consecuencias realmente graves, puesto que R lo vería como una observación numérica más o como otro nivel de la variable cualitativa en caso de tener un factor.
Sin embargo, pese a que su uso es fundamental, los valores NA presentan algunos “problemillas” que no podemos perder de vista. En primer lugar, cualquier operación (y de estas hablaremos más en la próxima entrega) con elementos NA va a dar como resultado un NA, a no ser que se trate de una función y en ella podamos incluir explícitamente el argumento na.rm=TRUE, que obviará el dato a la hora de hacer el cálculo.
Para verlo, vamos a eliminar uno de los datos de edad:
> df$edad[3] <- NA
>
> df$edad
## [1] 37 22 NA 30 24 30 24 35 30 31 34 35 28 22 39 22 27 34 23 33
Si quisiéramos calcular la media de edad podríamos usar la función mean… pero, mira lo que pasa:
> mean(df$edad)
## [1] NA
Dentro de la función mean y muchas otras, esto es salvable, solo hay que hacer lo que hemos indicado un poco más arriba, activar el argumento na.rm:
> mean(df$edad, na.rm = TRUE)
## [1] 29.47368
El problema viene cuando queremos usar ese vector para seleccionar observaciones dentro del banco de datos mediante el uso de vectores índices. Fíjate en lo que pasa al intentar seleccionar los valores de edad mayor de 35 como habíamos hecho antes:
> indices <- (df$edad >= 35 & df$year == 2022)
> indices
## [1] TRUE FALSE NA FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE TRUE
## [13] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
> df[indices , ]
## satis edad year
## 1 alto 37 2022
## NA <NA> NA NA
## 8 bajo 35 2022
## 12 bajo 35 2022
El vector índice contiene un NA y eso produce, en el resultado final, una fila completa de NAs que no nos da ninguna información y solo entorpece el trabajo.
En este caso, el argumento na.rm = TRUE ni siquiera es una opción y tendremos que recurrir a otras soluciones como la identificación de valores faltantes. Para ello contamos con la función is.na que dará TRUE cuando el valor lo sea. Y si queremos ver cuales NO son NA pues bastará con negar la orden, esto es !is.na:
> is.na(df$edad)
## [1] FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## [13] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
> !is.na(df$edad)
## [1] TRUE TRUE FALSE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
## [13] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
Así, añadiendo esta condicion:
> indices <- (df$edad >= 35 & df$year == 2022 & !is.na(df$edad))
> indices
## [1] TRUE FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE TRUE
## [13] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
> df[indices , ]
## satis edad year
## 1 alto 37 2022
## 8 bajo 35 2022
## 12 bajo 35 2022
Y ¡listo!
Pero ¡espera! no te vayas aún, que me queda dar un par de pinceladas sobre otros valores especiales.
Hasta el infinito y más allá.
A veces cuando hacemos cálculos en R podriamos llegar a superar la precisión del ordenador y tener entonces un infinito. El infinito en R se representa con la expresión Inf y podemos identificarlo usando la orden is.infinite.
Pero los cálculos no solo se estropean por llegar a números muy muy grandes (o muy muy pequeños, que también existe en -Inf). A veces el problema es cuando tenemos algo como una indeterminación. Puede ser por dividir 0 entre 0 o Inf entre Inf. En esos casos el resultado es la expresión NaN, del inglés `not a number’. Quizás tienes dudas de cómo puede ser que cometamos ese error, pero veremos en próximas entradas como al hacer cálculos en R, lo hacemos a lo grande, con muchos datos, y no es posible estar al tanto de todo todo todo.
Y, bueno, pues esto ha sido todo por hoy. Espero que os haya resultado útil y…
¡Seguimos aprendiendo!
One Reply to “Empezando en R (III). Datos especiales”