Apuntes Métodos y Técnicas de Investigación Cuantitativa
Apuntes Métodos y Técnicas de Investigación Cuantitativa
Índice
1 Tema 1. Introducción al software estadístico R 2
1.1 Instalación del software estadístico R . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.2 Uso del software estadístico R . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.3 Tipos de variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.4 Estructuras básicas en R: Vectores y data frames/tibbles . . . . . . . . . . . . . . . . . . . . . 3
1.5 Importar conjuntos de datos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.6 Manejo básico de conjuntos de datos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
En muchas ocasiones las variables cualitativas son representadas con números por comodidad en la
codificación, sin embargo es importante ser consciente de que dicho número no tiene significado más
allá de la propia codificación de la respuesta.
• Cuantitativas (o numéricas): variables que toman valores numéricos. Las variables cuantitativas
además pueden ser:
– Discretas: toman un número finito o numerable de valores, como por ejemplo, el número de hijos
(0, 1, 2, 3, 4, 5, etc.).
– Continuas: toman un número infinito no numerable de valores. Si la granularidad de una variable
discreta es muy grande (por ejemplo, el salario anual) se puede tratar como una variable continua.
## [1] 1 2 3 4 5
vectorcaracter <- c("A","B","C","D","E")
vectorcaracter
## [1] "numeric"
class(vectorcaracter)
## [1] "character"
class(vectorlogico)
## [1] "logical"
# Comprobación de la clase de la variable vectornumerico
is.numeric(vectornumerico)
## [1] TRUE
is.numeric(vectorcaracter)
## [1] FALSE
En muchas ocasiones es útil hacer uso de operaciones relacionales para construir vectores lógicos. A
continuación se muestran algunos ejemplos.
# Construcción de un vector lógico que indique si los valores de la variable
# vectornumerico son o no iguales a 2 (se utiliza == para representar la igualdad)
vectornumerico==2
## [1] "A"
# Obtención de los primeros tres elementos de la variable vectorcaracter
vectorcaracter[1:3]
## # A tibble: 5 x 3
## vectornumerico vectorcaracter vectorlogico
## <dbl> <chr> <lgl>
## 1 1 A TRUE
## 2 2 B TRUE
## 3 3 C FALSE
## 4 4 D FALSE
## 5 5 E TRUE
Obsérvese la ligera diferencia en la impresión por pantalla entre el data.frame y el tibble creados. Cuando
el conjunto de datos tiene muchos individuos (filas), es más últil el uso de un tibble, ya que solamente las
primeras filas se imprimen por pantalla.
Se puede hacer referencia a una variable en las columnas de un data.frame o un tibble utilizando el símbolo $.
conjunto$vectorcaracter # Referencia a la variable vectorcaracter dentro del tibble conjunto
La función dim() nos devuelve el número de filas y columnas de un tibble o de un data.frame, mientras que
la función length() nos devuelve la longitud de un vector.
dim(conjunto)
## [1] 5 3
length(conjunto$vectorcaracter)
## [1] 5
Se observa que conjunto es un tibble con 5 filas y 3 columnas, así como conjunto$vectorcaracter es un
vector de longitud 5.
Como en muchos lenguajes de programación, en R se puede hacer uso de estructuras lógicas. Un ejemplo de
esto es la estructura if...else donde podemos elegir realizar una acción única y exclusivamente si se cumple (o
no) una variable lógica.
# Ejemplo en el que se entra al if
if(1 > 0) {
TRUE
} else {
FALSE
}
## [1] TRUE
# Ejemplo en el que se entra al else
if(1 < 0) {
TRUE
} else {
FALSE
}
## [1] FALSE
R permite crear funciones personalizadas haciendo uso de la sintaxis funcion <- function(argumentos){},
donde funcion pasará a ser el nombre con el que podremos llamar a la función creada y argumentos serán
los argumentos de entrada para la función. A continuación, vemos un ejemplo para crear una función que nos
compruebe si el vector introducido contiene algún 1.
contieneuno <- function(vector) {
if(sum(vector==1)>0) {
TRUE
} else {
FALSE
}
}
contieneuno(vectornumerico)
## [1] TRUE
contieneuno(vectorcaracter)
## [1] FALSE
En ocasiones estamos interesados en aplicar una función a todas las variables (columnas) de un tibble. Para
ello tenemos que hacer uso de alguna de las funciones de la familia apply(). En esta familia, una de las
opciones más sencillas de utilizar es la función sapply(). Por ejemplo, podemos aplicar la función que
creamos con anterioridad a todas las variables (columnas) del tibble conjunto. Se observa como la primera
variable muestra alguna aparición del valor 1 (tal y como era de esperar), así como la tercera variable (pues
R identifica el valor lógico FALSE como un 0 y el valor lógico TRUE como un 1).
sapply(conjunto,contieneuno)
Recuérdese que el símbolo <- significa asignación, es decir, guardar el conjunto de datos obtenido por el
comando read_sas("cy07_msu_sch_qqq.sas7bdat", NULL) bajo el nombre cy07_msu_sch_qqq.
Otros formatos para importar/exportar datos también están disponibles en R. Por ejemplo, las funciones
read_table(), read_csv() y read_tsv() son utilizadas con una gran frecuencia al trabajar con ficheros .txt,
.csv o .tsv. El paquete readxl sirve para importar/exportar archivos en formato .xsl o .xslx, característicos del
programa Microsoft Excel. De manera análoga, el ya mencionado paquete haven sirve para importar/exportar
archivos de otros software estadísticos como, por ejemplo, SAS o SPSS.
19. SC017Q02NA. Variable de tipo cualitativo. Representa la medida en la que el equipo docente está poco
cualificado para impartir docencia. Niveles: 1 (En absoluto), 2 (Muy poco), 3 (En cierta medida) y 4
(Mucho).
175. PRIVATESCH. Variable de tipo cualitativo. Representa si la escuela es pública o privada. Esta variable
debería ser de tipo dicotómico, pero debido a diversos errores de codificación se consideran más niveles
en la base de datos.
177. STRATIO. Variable de tipo cuantitativo continuo. Representa la ratio alumno-profesor.
178. SCHSIZE. Variable de tipo cuantitativo discreto (con gran granularidad). Representa el tamaño (número
de alumnos) de la escuela.
195. SENWT. Variable de tipo cuantitativo continuo. Es una variable de ponderación normalizada construida
artificialmente para el análisis del rendimiento del alumnado en un grupo de países en los que se desea
que las contribuciones de cada país sea igual, independientemente de su población o del tamaño de la
muestra.
Para seleccionar solamente estas columnas del conjunto de datos utilizaremos la función select(), asignándolas
a un tibble llamado datos.
datos <- select(cy07_msu_sch_qqq,c(1,2,9,19,175,177,178,195))
Nótese que se podría llegar al mismo resultado utilizando el nombre de las variables en lugar del número de
la columna.
datos <- select(cy07_msu_sch_qqq,c("CNTRYID","CNT","OECD","SC017Q02NA","PRIVATESCH",
"STRATIO","SCHSIZE","SENWT"))
La función print() nos permite obtener una descripción de las primeras filas de un tibble. Por defecto solo
se muestran las 10 primeras filas, pero se puede pedir un número distinto de filas por medio del argumento n.
print(datos)
## # A tibble: 21,903 x 8
## CNTRYID CNT OECD SC017Q02NA PRIVATESCH STRATIO SCHSIZE SENWT
## <dbl> <chr> <dbl> <dbl> <chr> <dbl> <dbl> <dbl>
## 1 8 ALB 0 1 public NA NA 24.8
## 2 8 ALB 0 1 public NA NA 20.2
## 3 8 ALB 0 1 public 9.76 205 20.6
## 4 8 ALB 0 3 public NA NA 18.4
## 5 8 ALB 0 1 public 18 315 45.4
## 6 8 ALB 0 1 public 4.74 45 20.2
## 7 8 ALB 0 1 private 13.8 221 3.11
## 8 8 ALB 0 1 public 17.0 823 3.11
## 9 8 ALB 0 1 private 16.3 408 3.11
## 10 8 ALB 0 1 private NA NA 21.0
## # i 21,893 more rows
print(datos, n=5)
## # A tibble: 21,903 x 8
## CNTRYID CNT OECD SC017Q02NA PRIVATESCH STRATIO SCHSIZE SENWT
## <dbl> <chr> <dbl> <dbl> <chr> <dbl> <dbl> <dbl>
## 1 8 ALB 0 1 public NA NA 24.8
## 2 8 ALB 0 1 public NA NA 20.2
## 3 8 ALB 0 1 public 9.76 205 20.6
## 4 8 ALB 0 3 public NA NA 18.4
## 5 8 ALB 0 1 public 18 315 45.4
La función glimpse() también nos permite visualizar el conjunto de datos de una manera resumida.
glimpse(datos)
## Rows: 21,903
## Columns: 8
## $ CNTRYID <dbl> 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,~
## $ CNT <chr> "ALB", "ALB", "ALB", "ALB", "ALB", "ALB", "ALB", "ALB", "AL~
## $ OECD <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,~
## $ SC017Q02NA <dbl> 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 3, 1, 1, 1, 1, 3,~
## $ PRIVATESCH <chr> "public", "public", "public", "public", "public", "public",~
## $ STRATIO <dbl> NA, NA, 9.7619, NA, 18.0000, 4.7368, 13.8125, 16.9691, 16.3~
## $ SCHSIZE <dbl> NA, NA, 205, NA, 315, 45, 221, 823, 408, NA, 275, NA, NA, 3~
## $ SENWT <dbl> 24.81933, 20.20900, 20.63480, 18.44654, 45.40500, 20.20900,~
La función names() nos devuelve los nombres de todas las variables en las columnas del tibble. Al hacer esto,
obtenemos un vector, que es la estructura más básica de R.
names(datos)
##
## 1 2 3 4 <NA>
## 10077 7031 3082 564 1149
Se observa como de las 21903 observaciones de las que disponemos, 10077 respuestas son 1 (En absoluto),
7031 son 2 (Muy poco), 3082 son 3 (En cierta medida), 564 son 4 (Mucho), habiendo 1149 valores faltantes.
Es posible obtener la tabla de frecuencias relativas a partir de la función prop.table() aplicada sobre la
misma tabla de frecuencias. Esto nos devolverá las proporciones correspondientes a cada uno de los valores.
Por ejemplo, se observa que en torno a un 46% de las observaciones disponibles son 1 (En absoluto).
prop.table(tabla)
##
## 1 2 3 4 <NA>
## 0.46007396 0.32100625 0.14071132 0.02574990 0.05245857
Una medida resumen muy utilizada para describir una muestra es la moda, que es el valor de la variable
que aparece más frecuentemente en la muestra. Esta medida descriptiva es la única opción cuando nos
encontramos ante variables categóricas (donde no se debe utilizar ni la media, ni la mediana), pero no es muy
recomendable si tenemos variables numéricas que tomen un gran número de valores distintos. Aunque la
moda no viene implementada específicamente como función en R, es muy sencillo obtenerla visualmente a
partir de la tabla de frecuencias o analíticamente haciendo uso de la función which.max(). Esta función nos
devuelve la posición del valor con la máxima frecuencia, por lo que podemos obtener la moda obteniendo el
correspondiente elemento de la tabla de frecuencias.
names(tabla)[which.max(tabla)]
## [1] "1"
La medida de posición más habitual para variables cuantitativas es la media, que se obtiene como la suma de
todos los valores de la muestra dividida entre el tamaño de la muestra. En R se calcula usando la función
mean().
mean(datos$SENWT)
## [1] 18.26234
Se concluye que el valor medio de la variable SENWT es 18.2623384.
En presencia de valores faltantes, la media será por defecto identificada como NA. El uso del parámetro
na.rm=TRUE nos permite calcular la media de la variable sin tener en cuenta estos valores. Por ejemplo, la
variable SCHSIZE contiene valores faltantes por lo que es necesario hacer uso del parámetro na.rm=TRUE para
obtener la media de esta variable.
mean(datos$SCHSIZE)
## [1] NA
mean(datos$SCHSIZE, na.rm=TRUE)
## [1] 772.5211
Se concluye que el número de alumnos medio de las escuelas para las que se dispone de información es
772.521096.
Otra opción para resumir un conjunto de datos es a través de los cuantiles. Formalmente, se define el cuantil
de orden p (0 < p < 1) como el valor que marca un corte de modo que una proporción p de valores sea menor
o igual que él y una proporción 1 − p de valores sea mayor o igual que él. Los cuantiles están implementados
en la función quantile(), donde el orden del cuantil se introduce a través del parámetro probs. El caso
particular de la mediana (que es el cuantil de orden 0.5, llamado también segundo cuartil) tiene implementada
su propia función (median()). También son populares los cuantiles de orden 0.25 (llamado también primer
cuartil) y de orden 0.75 (llamado también tercer cuartil). Por último, íntimamente relacionados con los
cuantiles, tenemos las nociones de mínimo y máximo, que vienen implementados a través de las funciones
min() y max(), respectivamente. Al igual que pasaba con la media, en todas estas funciones el parámetro
na.rm nos permite decidir si se tienen en cuenta los valores faltantes o no para calcular el valor de la medida
de posición correspondiente.
quantile(datos$SENWT, probs=0.25, names=FALSE)
## [1] 5.50544
median(datos$SENWT)
## [1] 11.39931
min(datos$SENWT)
## [1] 0.15084
max(datos$SENWT)
## [1] 328.7267
Hasta ahora, solamente hemos hablado de medidas de posición, pero también existen otras medidas resumen
que nos sirven para medir cuánto de dispersos están nuestros datos. Las medidas de dispersión más habituales
para variables cuantitativas son el recorrido (diferencia entre el máximo y el mínimo de la muestra), el
recorrido intercuartílico (diferencia entre el tercer cuartil y el primer cuartil de la muestra), la varianza
(promedio de las distancias de todos los valores de la muestra a la media de la muestra) y la desviación típica
(raíz cuadrada de la varianza). Estas tres últimas medidas de dispersión tienen una función propia en R
(IQR(), var() y sd(), respectivamente).
IQR(datos$SENWT)
## [1] 17.90313
var(datos$SENWT)
## [1] 490.2352
sd(datos$SENWT)
## [1] 22.14126
Sin embargo, para obtener el recorrido es necesario acudir a la definición y restar al máximo de la muestra el
mínimo.
max(datos$SENWT)-min(datos$SENWT)
## [1] 328.5759
Es importante mencionar que el denominador utilizado en las funciones sd() y var() es n − 1 en lugar de n.
En ocasiones, se utiliza el nombre cuasidesviación típica y cuasivarianza para referirse a estas medidas.
Por último, es posible agrupar estos resúmenes por medio de la función summarise(). Esta función también
permite realizar resúmenes por grupos, indicando la agrupación requerida por medio de group_by().
Resumen <- summarise(datos,
MaxSENWT=max(SENWT),
MeanSENWT=mean(SENWT),
DistintosSENWT=length(unique(SENWT)))
Resumen
## # A tibble: 1 x 3
## MaxSENWT MeanSENWT DistintosSENWT
## <dbl> <dbl> <int>
## 1 329. 18.3 13044
ResumenGrupos <- summarise(group_by(datos,CNT),
MaxSENWT=max(SENWT),
MeanSENWT=mean(SENWT),
LengthSENWT=length(unique(SENWT)))
ResumenGrupos
## # A tibble: 80 x 4
## CNT MaxSENWT MeanSENWT LengthSENWT
## <chr> <dbl> <dbl> <int>
## 1 ALB 46.8 15.3 97
## 2 ARE 82.0 6.62 78
## 3 ARG 121. 11.0 370
## 4 AUS 72.9 6.55 591
## 5 AUT 94.4 17.2 184
## 6 BEL 153. 17.4 256
## 7 BGR 69.1 25.4 147
## 8 BIH 82.6 23.5 128
## 9 BLR 68.2 21.4 123
## 10 BRA 59.4 8.38 489
## # i 70 more rows
A partir de los resultados anteriores, se observa, por ejemplo, que la media de la variable SENWT es del orden
de 18.3, sin embargo en Albania (ALB) la media de esta variable es del orden de 15.3.
En ocasiones estamos interesados en aplicar una función a todas las variables de un tibble. Para ello tenemos
que hacer uso de alguna de las funciones de la familia apply(). En esta familia, una de las opciones más
sencillas de utilizar es la función sapply().
sapply(datos,class)
##
## 0 1
## 1 5051 5026
## 2 3015 4016
## 3 1559 1523
## 4 423 141
## <NA> 528 621
Por ejemplo, el valor de la primera fila y primera columna nos indica que 5051 observaciones se corresponden
con individuos que indicaron el valor 1 (En absoluto) para la variable SC017Q02NA y pertenecen a países que
no son miembros de la OECD (0 en la variable OECD).
Para obtener la tabla de frecuencias marginales, se puede recurrir a la función table(), llamando a una de
las variables solamente. También es posible obtener la tabla de frecuencias marginales por medio de una
suma por filas/columnas utilizando la función apply().
table(datos$SC017Q02NA, useNA="ifany")
##
## 1 2 3 4 <NA>
## 10077 7031 3082 564 1149
apply(Tabla,1,sum)
## 1 2 3 4 <NA>
## 10077 7031 3082 564 1149
table(datos$OECD, useNA="ifany")
##
## 0 1
## 10576 11327
apply(Tabla,2,sum)
## 0 1
## 10576 11327
La función prop.table() permite calcular tablas de frecuencias relativas.
prop.table(Tabla)
##
## 0 1
## 1 0.230607679 0.229466283
## 2 0.137652376 0.183353878
## 3 0.071177464 0.069533854
## 4 0.019312423 0.006437474
## <NA> 0.024106287 0.028352281
En este caso el valor de la primera fila y primera columna indica que en torno a un 23.06% de la observaciones se
corresponden con individuos que indicaron el valor 1 (En absoluto) para la variable SC017Q02NA y pertenecen
##
## 0 1
## 1 0.5012404 0.4987596
## 2 0.4288152 0.5711848
## 3 0.5058404 0.4941596
## 4 0.7500000 0.2500000
## <NA> 0.4595300 0.5404700
En este caso el valor de la primera fila y primera columna indica que en torno a un 50.12% de los individuos
que indicaron el valor 1 (En absoluto) para la variable SC017Q02NA pertenecen a países que no son miembros
de la OECD (0 en la variable OECD).
prop.table(Tabla, margin=2)
##
## 0 1
## 1 0.47759077 0.44371855
## 2 0.28507943 0.35455107
## 3 0.14740923 0.13445749
## 4 0.03999622 0.01244813
## <NA> 0.04992436 0.05482476
En este caso el valor de la primera fila y primera columna indica que en torno a un 47.76% de los individuos
que pertenecen a países que no son miembros de la OECD (0 en la variable OECD) indicaron el valor 1 (En
absoluto) para la variable SC017Q02NA.
10000
7500
5000
2500
1 2 3 4 NA
datos$SC017Q02NA
En este gráfico se observa claramente como de los individuos que respondieron a la variable SC017Q02NA,
la mayor parte respondieron 1 (En absoluto), siendo 2 (Muy poco) la segunda respuesta más frecuente y 4
(Mucho) la respuesta menos frecuente.
A pesar de la sencillez de uso de la función qplot(), esta opción se nos puede quedar limitada para la
realización de gráficos más avanzados. Es por esto que vamos a trabajar con el comando ggplot(), que
nos permite una mayor cantidad de opciones de personalización mediante el uso de capas superpuestas.
Como primera capa, debemos realizar una capa inicial construida a partir del conjunto de datos sobre el que
queremos realizar el diagrama de barras. El comando aes() nos permitirá ligar distintas cuestiones estéticas
al gráfico (como, por ejemplo, la variable en el eje x o el color). A continuación, deberemos indicar el tipo de
gráfico que queremos realizar añadiendo una nueva capa con el símbolo +. En este caso, queremos añadir una
nueva capa que sea un diagrama de barras, utilizando el comando geom_bar().
ggplot(data = datos, aes(x = SC017Q02NA)) + geom_bar()
10000
7500
count
5000
2500
1 2 3 4 NA
SC017Q02NA
Las funciones ggtitle(), xlab() y ylab() nos permiten cambiar el título y los nombres de los ejes.
ggplot(data = datos, aes(x = SC017Q02NA)) + geom_bar() +
ggtitle("Estudio PISA") + xlab("Variable SC017Q02NA") + ylab("Frecuencia")
Estudio PISA
10000
7500
Frecuencia
5000
2500
1 2 3 4 NA
Variable SC017Q02NA
El comando factor() nos permite reordenar los niveles de la variable a representar. Esto es de gran utilidad
cuando los valores de la variable son, por ejemplo, Bajo, Medio y Alto, que R nos ordenará por orden
alfabético. La función factor() también nos permite convertir a factor una variable numérica discreta de la
que queramos realizar un diagrama de barras. Por último, esta función también es de utilidad si queremos
que los valores faltantes se incluyan explícitamente en el diagrama de barras.
datos$Factor <- factor(datos$SC017Q02NA,
levels=c(1,2,3,4,NA), # Orden de los niveles del factor
exclude=NULL) # Por defecto se excluyen los valores faltantes
ggplot(data = datos, aes(x = Factor)) + geom_bar()
10000
7500
count
5000
2500
1 2 3 4 NA
Factor
Se pueden especificar manualmente los colores del interior (fill) y del contorno (color) para cada una de
las barras.
ggplot(data = datos, aes(x = Factor)) +
geom_bar(color="black",fill=c("red","blue","green","orange","pink"))
10000
7500
count
5000
2500
1 2 3 4 NA
Factor
También podemos ligar algunas de las propiedades estéticas del gráfico a otra variable haciendo uso de aes().
Por ejemplo, podemos ligar el color del interior de las barras a otra variable. Si la variable asociada a las
barras coincide con la variable asociada al color, simplemente se obtendrán barras de distintos colores.
ggplot(data = datos, aes(x = Factor)) +
geom_bar(aes(fill=Factor))
10000
7500
Factor
1
count
2
5000
3
4
NA
2500
1 2 3 4 NA
Factor
El uso de scale_fill_manual() nos permite introducir a mano los colores del gráfico, siendo necesario
introducir el color para los NAs en un argumento aparte. Por defecto, R tiene implementados los colores más
usuales por medio de sus nombres en inglés, pero admite cualquier otro colo introduciendo su representación
hexadecimal. Nótese que existen numerosas páginas web que generan combinaciones de colores visualmente
llamativas para realizar gráficos. Un ejemplo de página web de este tipo sería: https://coolors.co/.
ggplot(data = datos, aes(x = Factor)) +
geom_bar(aes(fill=Factor)) +
scale_fill_manual(values = c("#1F0322","#8A1C7C","#DA4167","#F0BCD4"), na.value="grey")
10000
7500
Factor
1
count
2
5000
3
4
NA
2500
1 2 3 4 NA
Factor
De manera similar, scale_fill_brewer() nos permite utilizar una de las numerosas paletas de colores
disponibles en R.
ggplot(data = datos, aes(x = Factor)) +
geom_bar(aes(fill=Factor)) +
scale_fill_brewer(palette="Reds", na.value="grey")
10000
7500
Factor
1
count
2
5000
3
4
NA
2500
1 2 3 4 NA
Factor
Se puede utilizar una segunda variable para estratificar la variable representada en el diagrama de barras.
ggplot(data = datos, aes(x = Factor)) +
geom_bar(aes(fill=PRIVATESCH))
10000
7500 PRIVATESCH
invalid
count
missing
5000
private
PRIVATE
public
PUBLIC
2500
1 2 3 4 NA
Factor
Nótese que la estratificación de la variable se puede mostrar desplazada en lugar de superpuesta dentro del
diagrama de barras.
ggplot(data = datos, aes(x = Factor)) +
geom_bar(aes(fill=PRIVATESCH),position = "dodge")
5000
4000
PRIVATESCH
3000 invalid
count
missing
private
2000 PRIVATE
public
PUBLIC
1000
1 2 3 4 NA
Factor
Nótese por último que geom_bar() toma como entrada un vector de valores y representa las frecuencias de
cada valor en el vector. Si ya tenemos los datos agrupados en frecuencias, dichas frecuencias se deberían
pasar como la variable estética ligada al eje y, siendo necesario forzar la opción stat="identity".
DatosDiagrama <- tibble(valores=1:5, frecuencias=c(5,3,5,7,1))
ggplot(data = DatosDiagrama, aes(x = valores, y=frecuencias)) +
geom_bar(stat="identity")
6
frecuencias
1 2 3 4 5
valores
20000
15000 Factor
1
count
10000 3
4
NA
5000
20000
Factor
1
5000
2
x
3
4
NA
15000
10000
count
3.3 Histograma
El histograma es una representación gráfica que nos permite visualizar una variable cuantitativa continua.
Podemos realizar un histograma por medio de la función qplot().
qplot(datos$SENWT, geom="histogram")
8000
6000
4000
2000
Se observa como la mayor parte de los individuos toman valores menores de 50 para la variable SENWT, siendo
cada vez menos frecuente la aparición de valores cada vez más grandes.
La función geom_histogram() tiene distintos parámetros que nos permiten implementar diversas opciones
estéticas. Por ejemplo, el parámetro binwidth nos permite variar el ancho de las celdas del histograma,
mientras que mediante el parámetro boundary podemos indicar el punto desde el que queremos que se
construya la primera barra. Al igual que ocurría con los diagramas de barras, color indica el color del borde
de las barras del histograma y fill indica el color de su interior.
ggplot(data = datos, aes(x=SENWT)) +
geom_histogram(binwidth=10, color="black", fill="red", boundary=0)
10000
7500
count
5000
2500
Si ligamos el relleno del gráfico a una variable del conjunto de datos, obtenemos distintos histogramas
superpuestos, uno por valor que tome la variable ligada al relleno del gráfico. En este caso es útil utilizar el
parámetro alpha para reducir la opacidad de las barras, así como forzar la opción position="identity"
para que R no apile las barras de los histogramas. Si el número de elementos en cada valor de la variable ligada
al relleno del gráfico es diferente, puede ser recomendable realizar un histograma de densidades, indicando
y=..density...
ggplot(data = datos, aes(x=SENWT, y=..density.., fill=factor(OECD))) +
geom_histogram(color="black", alpha=0.5, position="identity")
0.03
factor(OECD)
density
0.02
0
1
0.01
0.00
En este gráfico podemos comprobar que no se observan grandes diferencias en la variable SENWT entre países
miembros de la OECD y países no miembros.
8000
6000
4000
2000
Las conclusiones extraídas de una poligonal de frecuencias son siempre parecidas a las extraídas de un
histograma, por lo que la elección entre estos dos tipos de gráficos es típicamente una cuestión de gusto
personal.
Por medio de capas, la poligonal de frecuencias se obtiene con la función geom_freqpoly(). Muchos de
los parámetros de esta función coinciden con los del histograma. Entre los parámetros más importantes
encontramos binwidth, que nos permite variar el ancho de las celdas; color, que nos permite asignarle un
color; y size, que nos permite variar el ancho del trazo. Como en el caso del histograma, se puede asignar
por medio de aes una segunda variable para estratificar la poligonal de frecuencias.
ggplot(data = datos, aes(x=SENWT, color=factor(OECD))) +
geom_freqpoly(binwidth=5, size=1)
2000
factor(OECD)
count
0
1
1000
300
datos$SENWT
200
100
0 1
factor(datos$OECD)
En este gráfico no se observan grandes diferencias entre los países miembros de la OECD y los países no
miembros para la variable SENWT, ni en términos de posición (localización de la caja) ni de dispersión (altura
de la caja).
También podemos hacer uso de la función geom_boxplot(), indicando como variable y aquella que queremos
resumir por medio del diagrama de caja, mientras que la variable x se reserva para separar los datos por
grupos. Es posible girar el diagrama de caja, invirtiendo los roles de las variables x e y.
ggplot(data = datos, aes(x = factor(OECD), y = SENWT)) +
geom_boxplot()
300
200
SENWT
100
0 1
factor(OECD)
Como ocurrió con los gráficos vistos con anterioridad, es posible modificar distintas opciones de la función
geom_boxplot(). Por ejemplo, el parámetro fill nos permite rellenar la caja del color deseado. Nótese
que el parámetro fill se puede ligar a la variable de agrupación, apareciendo así una leyenda asociada. En
el caso de los diagramas de caja, dicha leyenda es en ocasiones innecesaria, por lo que se puede eliminar
indicando theme(legend.position="none").
ggplot(data = datos, aes(x=factor(OECD), y=SENWT, fill=factor(OECD))) +
geom_boxplot() +
theme(legend.position="none")
300
200
SENWT
100
0 1
factor(OECD)
Se pueden señalizar distintas medidas resumen, como por ejemplo la media, haciendo uso de stat_summary().
ggplot(data = datos, aes(x=factor(OECD), y=SENWT, fill=factor(OECD))) +
geom_boxplot() +
stat_summary(fun=mean, geom="point", size=2, shape=22, fill="white")
300
200
factor(OECD)
SENWT
0
1
100
0 1
factor(OECD)
Otras variaciones en el diseño incluyen la inclusión de los bigotes con stat_boxplot(geom = "errorbar")
(opción puramente estética).
ggplot(data = datos, aes(x=factor(OECD), y=SENWT, fill=factor(OECD))) +
stat_boxplot(geom = "errorbar", width = 0.5) +
geom_boxplot()
300
200
factor(OECD)
SENWT
0
1
100
0 1
factor(OECD)
300
200
factor(OECD)
SENWT
0
1
100
0 1
factor(OECD)
count
5000
factor(OECD)
4000
3000
2000
1000
1 2 3 4 NA
Factor
n
factor(OECD)
1000
2000
3000
4000
5000
1 2 3 4 NA
Factor
Ambos diagramas sirven para el mismo propósito y, en este caso, nos permiten concluir que si nos fijamos en
las celdas de color más claro (primer gráfico) o en los puntos de mayor tamaño (segundo gráfico), la variable
Factor (versión factorizada de la variable SC017Q02NA) distribuye sus valores más frecuentemente en los
valores 1 (En absoluto) y 2 (Muy poco). Por otro lado, la variable OECD no parece tener gran influencia sobre
la frecuencia de las observaciones, ni parece que exista una interacción directa entre las dos variables de
estudio.
300
200
SENWT
100
0 5000 10000
SCHSIZE
A primera vista, no se observa ninguna relación entre las dos variables de estudio.
Es posible modificar la forma de los puntos del diagrama de dispersión por medio de la opción shape. Las
formas disponibles se pueden ver en la siguiente tabla:
También es posible utilizar las funciones geom_hline() y geom_vline() para realizar líneas horizontales y
verticales, así como utilizar la función geom_text() para añadir texto al gráfico.
ggplot(data = datos, aes(x = SCHSIZE, y = SENWT)) +
geom_point(data = filter(datos, SCHSIZE<=10000), colour = "black") +
geom_point(data = filter(datos, SCHSIZE>10000), colour = "red", shape=15,size=2) +
geom_vline(xintercept = c(10000), colour = "red", size = 1, linetype = "dashed") +
geom_text(aes(label=ifelse(SCHSIZE>10000,as.character(CNT),"")),size=3, nudge_y = 0.8)
300
200
SENWT
100
ARE
PHL TAPPHL PHL QCI
0
0 5000 10000
SCHSIZE
El paquete ggrepel implementa mejoras a las funciones base de que evitan el solapamiento de componentes
del gráfico. Por ejemplo, geom_text_repel() es la correspondiente versión mejorada de geom_text().
#install.packages("ggrepel") # Ejecutar si el paquete no ha sido instalado
library(ggrepel)
ggplot(data = datos, aes(x = SCHSIZE, y = SENWT)) +
geom_point(data = filter(datos, SCHSIZE<=10000), colour = "black") +
geom_point(data = filter(datos, SCHSIZE>10000), colour = "red", shape=15,size=2) +
geom_vline(xintercept = c(10000), colour = "red", size = 1, linetype = "dashed") +
geom_text_repel(aes(label=ifelse(SCHSIZE>10000,as.character(CNT),"")),size=3, nudge_y = 0.8)
300
200
SENWT
100
3.9 Subfiguras
En ocasiones queremos agrupar los datos en subfiguras. La función facet_grid(variable1 ~ .) nos creará
subfiguras agrupadas en filas a partir de variable1, mientras que la función facet_grid(. ~ variable2)
nos creará subfiguras agrupadas en columnas a partir de variable2. Se puede crear una matriz de subfiguras
en función de variable1 y variable2 haciendo uso de facet_grid(variable1 ~ variable2). Si el número
de subfiguras es muy grande, en ocasiones es más visual hacer uso de la función facet_wrap(~ variable).
Es importante mencionar que para la comparación de histogramas a veces es útil representar las densidades
en lugar de las frecuancias absolutas por medio del comando y=..density...
ggplot(data = datos, aes(x = Factor)) +
geom_bar() +
facet_grid(. ~ PRIVATESCH)
5000
4000
3000
count
2000
1000
1 2 3 4 NA 1 2 3 4 NA 1 2 3 4 NA 1 2 3 4 NA 1 2 3 4 NA 1 2 3 4 NA 1 2 3 4 NA
Factor
5000
4000
3000
0
2000
1000
count
5000
4000
3000
1
2000
1000
0
1 2 3 4 NA
Factor
2000
0
1000
count
2000
1
1000
0
1 2 3 4 NA 1 2 3 4 NA 1 2 3 4 NA 1 2 3 4 NA 1 2 3 4 NA 1 2 3 4 NA 1 2 3 4 NA
Factor
invalid missing
5000
4000
3000
2000
1000
0
private PRIVATE public
5000
4000
count
3000
2000
1000
0
1 2 3 4 NA 1 2 3 4 NA
PUBLIC
5000
4000
3000
2000
1000
0
1 2 3 4 NA
Factor
P (RC|D ∈ H0 ) .
P (RA|D ∈ H1 ) = 1 − P (RC|D ∈ H1 ) .
PD (RC) := P (RC|D)
Para construir la Región Crítica se suele recurrir a un estadístico del contraste T que mida las discrepancias
de las muestras con la hipótesis nula. Típicamente se darán tres casos:
• RC = {T > c} si los valores grandes de T representan poca compatibilidad con H0 ;
• RC = {T < c} si los valores pequeños de T representan poca compatibilidad con H0 ;
• RC = {T < c} ∪ {T > c0 } si tanto valores grandes como pequeños de T representan poca compatibilidad
con H0 .
El cálculo de c (y c0 ) se hará a partir de la distribución de T , que debe ser conocida bajo la hipótesis nula.
Se buscarán valores de c que maximicen P (RC|D ∈ H0 ) manteniendo el nivel de significación (i.e., bajo
la restricción P (RC|D ∈ H0 ) ≤ α). Si se conoce la forma analítica de la distribución de T , las constantes
de construcción de la RC se obtienen fácilmente a través de los cuantiles correspondientes; de lo contrario,
podemos acudir a la simulación.
Los pasos a seguir a la hora de realizar un test de hipótesis son los siguientes:
1. Formular las hipótesis de estudio en lenguaje estadístico, identificando la hipótesis nula H0 y la
alternativa H1 ;
2. Fijar el nivel de significación α;
3. Construir la Región Crítica, típicamente a partir de un estadístico del contraste que mida las discrepancias
de las muestras con la hipótesis nula;
4. Obtener una muestra;
5. Tomar la decisión correspondiente en base a la pertenencia o no de la muestra a la Región Crítica.
En la forma clásica de realizar un test de hipótesis, se fijan en primer lugar el nivel de significación y la
Región Crítica, permitiéndonos realizar el mismo test para distintas muestras obtenidas. Existe una forma
análoga de realizar un test de hipótesis, fijando en primer lugar la muestra y calculando el menor nivel de
significación α para el que nos encontramos en la Región Crítica. A dicho valor se le conoce como p-valor
asociado a la muestra. Ambas formas de realizar un test de hipótesis son equivalentes, puesto que:
• p-valor ≤ α es equivalente a estar en la RC;
• p-valor > α es equivalente a estar en la RA.
En general, es más común realizar contrastes de hipótesis analizando el p-valor obtenido puesto que los
softwares estadísticos (como por ejemplo R) devuelven directamente el p-valor para cada muestra dada.
A continuación, veremos distintos tipos de contrastes de hipótesis que suelen aparecer en problemas en el
ámbito de la educación: tests de normalidad, tests de comparación de medias (tanto para una muestra,
como para dos), tests de proporciones (tanto para una muestra, como para dos) y tests de independencia
chi-cuadrado.
0.08
0.06
density
0.04
0.02
0.00
0 25 50 75 100
STRATIO
En este caso, se observa como la variable STRATIO no parece comportarse como una distribución normal.
Si se quiere realizar este estudio de una manera más formal, se puede acudir a algún test de normalidad, en
donde se tienen las siguientes hipótesis nula y alternativa:
H0 : X N (µ, σ) ,
H1 : X 6 N (µ, σ) ,
##
## Shapiro-Wilk normality test
##
## data: datos$STRATIO[1:5000]
## W = 0.71414, p-value < 2.2e-16
Puesto que se obtiene un p-valor más pequeño que cualquier nivel de significación habitual (e.g., α = 0.05),
se rechaza la hipótesis de que la variable STRATIO sigue una distribución normal.
Otra alternativa bastante popular es el test de Anderson-Darling, cuya implementación en R del paquete
nortest a través de la función ad.test() sí permite trabajar con muestras de gran tamaño, como es el caso
de nuestro conjunto de datos.
#install.packages("nortest") #Ejecutar si el paquete no ha sido instalado
library(nortest)
ad.test(datos$STRATIO)
##
## Anderson-Darling normality test
##
## data: datos$STRATIO
## A = 1009.5, p-value < 2.2e-16
Concluimos de nuevo que hay evidencias suficientes para rechazar a un nivel de significación de α = 0.05
la normalidad de la variable. Eso sí, es sabido que en conjuntos de datos de tamaño muy grande no es
recomendable el uso de test de normalidad, pues tenderán a rechazar la hipótesis de normalidad en la mayoría
de los casos al detectar ligeras desviaciones. Estas ligeras desviaciones pueden ni siquiera ser inherentes a la
propia variable, sino ser debidas a errores de medición, redondeo de decimales, etc.
H0 : µ = µ0 , H00 : µ ≤ µ0 , H000 : µ ≥ µ0 ,
H1 : µ 6= µ0 , H10 : µ > µ0 , H100 : µ < µ0 .
Existen numerosos tests para esta tarea, pero nos centraremos en los tres más populares. La opción más
prominente es sin duda el test t de Student (función t.test()), sin embargo es importante mencionar que
este test no es adecuado si no nos encontramos ante una variable con distribución normal, especialmente si el
tamaño muestral es pequeño. La segunda opción más popular es el test de Wilcoxon (función wilcox.test()),
la alternativa no paramétrica al test t de Student que no requiere normalidad sino una condición más débil
como es la simetría (así como que la variable sea continua o, al menos, tome un número moderadamente
grande de valores). Por último, nos encontramos antes la opción menos potente de las tres, el test de los
signos (función SignTest(), del paquete DescTools), pero que puede ser utilizada siempre, incluso para
variables cualitativas ordinales.
A continuación, aplicamos los tres tests a la variable STRATIO de nuestro conjunto de datos para estudiar si
la media de la población es igual a 14, teniendo en cuenta que deberíamos atender solamente al test de los
signos al no parecer nuestra variable ni normal, ni simétrica.
t.test(datos$STRATIO, mu=14)
##
## One Sample t-test
##
## data: datos$STRATIO
## t = -6.1446, df = 18041, p-value = 8.186e-10
## alternative hypothesis: true mean is not equal to 14
## 95 percent confidence interval:
## 13.43592 13.70876
## sample estimates:
## mean of x
## 13.57234
wilcox.test(datos$STRATIO, mu=14)
##
## Wilcoxon signed rank test with continuity correction
##
## data: datos$STRATIO
## V = 56589436, p-value < 2.2e-16
## alternative hypothesis: true location is not equal to 14
##
## One-sample Sign-Test
##
## data: datos$STRATIO
## S = 6364, number of differences = 17997, p-value < 2.2e-16
## alternative hypothesis: true median is not equal to 14
## 95.1 percent confidence interval:
## 12.0000 12.1786
## sample estimates:
## median of the differences
## 12.08495
Puesto que se obtienen en todos los casos p-valores mucho más pequeños que α = 0.05, todos los tests
concluyen que hay evidencias suficientes para rechazar a un nivel de significación de α = 0.05 que la media
sea igual a 14 (aunque, dada la ausencia de normalidad/simetría, solamente se debería utilizar el tercero en
este caso).
De una manera similar, se podría estudiar si la media es mayor o igual que 14 indicando el argumento
alternative="less" (puesto que la hipótesis alternativa contiene el símbolo <).
t.test(datos$STRATIO, mu=14, alternative="less")
##
## One Sample t-test
##
## data: datos$STRATIO
## t = -6.1446, df = 18041, p-value = 4.093e-10
## alternative hypothesis: true mean is less than 14
## 95 percent confidence interval:
## -Inf 13.68683
## sample estimates:
## mean of x
## 13.57234
wilcox.test(datos$STRATIO, mu=14, alternative="less")
##
## Wilcoxon signed rank test with continuity correction
##
## data: datos$STRATIO
## V = 56589436, p-value < 2.2e-16
## alternative hypothesis: true location is less than 14
SignTest(datos$STRATIO, mu=14, alternative="less")
##
## One-sample Sign-Test
##
## data: datos$STRATIO
## S = 6364, number of differences = 17997, p-value < 2.2e-16
## alternative hypothesis: true median is less than 14
## 95 percent confidence interval:
## -Inf 12.1638
## sample estimates:
## median of the differences
## 12.08495
De manera análoga, se concluye por medio de los tres tests que hay evidencias suficientes para rechazar a
un nivel de significación de α = 0.05 que la media de la variable sea mayor o igual que 14 (aunque, dada la
ausencia de normalidad/simetría, solamente se debería utilizar el tercero en este caso). El caso en el que
se quisiera estudiar si la media es menor o igual que 14 indicando el argumento alternative="greater"
(puesto que la hipótesis alternativa contiene el símbolo >).
H0 : µA − µB = µ0 , H00 : µA − µB ≤ µ0 , H000 : µA − µB ≥ µ0 ,
H1 : µA − µB 6= µ0 , H10 : µA − µB > µ0 , H100 : µA − µB < µ0 .
donde µA y µB son las medias de las dos poblaciones. El caso más común es aquel en el que se puede asumir
la normalidad de las dos poblaciones (es decir, que la variable sigue una distribución normal en cada uno de
los dos grupos por separado), pues se puede acudir al test t de Student para la comparación de dos medias.
Este test está implementado también en la función t.test(), pero es necesario en esta ocasión introducir
como variable y la variable dicotómica que divide la población en dos grupos.
t.test(datos$STRATIO, datos$OECD, mu=0, alternative="two.sided")
##
## Welch Two Sample t-test
##
## data: datos$STRATIO and datos$OECD
## t = 187.36, df = 18126, p-value < 2.2e-16
## alternative hypothesis: true difference in means is not equal to 0
## 95 percent confidence interval:
## 12.91861 13.19178
## sample estimates:
## mean of x mean of y
## 13.5723391 0.5171438
#t.test(datos$STRATIO, datos$OECD, mu=0, alternative="greater")
#t.test(datos$STRATIO, datos$OECD, mu=0, alternative="less")
A partir del p-valor obtenido, se concluye que hay evidencias suficientes para rechazar a un nivel de significación
de α = 0.05 que la media en los dos grupos sea la misma.
En caso, de no poder garantizar la normalidad de las dos poblaciones, se puede acudir al test de Mann-Whitney,
implementado en R también bajo la función wilcox.test() siendo necesario indicar también como variable
y la variable que divide la población en dos grupos. En este caso sería necesario también que la variable sea
continua o, al menos, tome un número moderadamente grande de valores.
wilcox.test(datos$STRATIO, datos$OECD, mu=0, alternative="two.sided")
##
## Wilcoxon rank sum test with continuity correction
##
## data: datos$STRATIO and datos$OECD
## W = 394296084, p-value < 2.2e-16
Se llegaría a la misma conclusión que con el test t para comparación de medias, sin necesidad de garantizar
la normalidad en ambos grupos.
H0 : p = p0 , H00 : p ≤ p0 , H000 : p ≥ p0 ,
H1 : p 6= p0 , H10 : p > p0 , H100 : p < p0 .
Este tipo de test está implementado en R en la función prop.test() y requiere que se introduzca el
número de éxitos (argumento x) y el tamaño muestral (argumento n), así como la proporción p0 (argumento
p) que se quiere contrastar. De igual manera que ocurría con la comparación para un promedio, se
indicaría mediante el argumento alternative si se quiere hacer el contraste bilateral (opción por defecto,
alternative=two.sided), el contraste unilateral con ≤ en la hipótesis nula (alternative=greater) o el
contraste unilateral con ≥ en la hipótesis nula (alternative=less).
Por ejemplo, podemos estudiar si la proporción de individuos en los que la variable SENWT toma un valor
menor que 12 es igual a 0.5.
Exitos <- sum(datos$SENWT<12)
Exitos
## [1] 11277
Totales <- length(datos$SENWT<12)
Totales
## [1] 21903
prop.test(x=Exitos,n=Totales,p=0.5,alternative="two.sided")
##
## 1-sample proportions test with continuity correction
##
## data: Exitos out of Totales, null probability 0.5
## X-squared = 19.29, df = 1, p-value = 1.123e-05
## alternative hypothesis: true p is not equal to 0.5
## 95 percent confidence interval:
## 0.5082174 0.5214993
## sample estimates:
## p
## 0.514861
Dado el p-valor obtenido, se concluye que hay evidencias suficientes para rechazar a un nivel de significación
de α = 0.05 que la proporción de individuos en los que la variable SENWT toma un valor menor que 12 sea
igual a 0.5.
H0 : pA = pB , H00 : pA ≤ pB , H000 : pA ≥ pB ,
H1 : pA 6= pB , H10 : pA > pB , H100 : pA < pB .
Este tipo de tests también está implementado en R en la función prop.test() y tiene una sintáxis de uso
similar al test para una proporción. En esta ocasión se debe introducir un vector conteniendo el número de
éxitos en los dos grupos (argumento x) y otro vector conteniendo los tamaños muestrales de los dos grupos
(argumento n). El argumento alternative funciona de la misma forma que en casos anteriores.
Procedemos a continuación a estudiar si la proporción de individuos en los que la variable SENWT toma un
valor menor que 12 en los dos grupos indicados por la variable OECD. Por cuestiones de espacio, calculamos el
número de éxitos en los dos grupos y los tamaños muestrales de los dos grupos con anterioridad.
x0 <- sum(datos$SENWT[datos$OECD==0]<12) #Número de individuos con SENWT<12 en OECD=0
x1 <- sum(datos$SENWT[datos$OECD==1]<12) #Número de individuos con SENWT<12 en OECD=1
n0 <- length(datos$SENWT[datos$OECD==0]) #Número de individuos en OECD=0
n1 <- length(datos$SENWT[datos$OECD==1]) #Número de individuos en OECD=1
prop.test(x=c(x0,x1),n=c(n0,n1),alternative="two.sided")
##
## 2-sample test for equality of proportions with continuity correction
##
## data: c(x0, x1) out of c(n0, n1)
## X-squared = 154, df = 1, p-value < 2.2e-16
## alternative hypothesis: two.sided
## 95 percent confidence interval:
## -0.09724579 -0.07066186
## sample estimates:
## prop 1 prop 2
## 0.4714448 0.5553986
A partir del p-valor obtenido, se concluye que hay evidencias suficientes para rechazar a un nivel de significación
de α = 0.05 que la proporción de individuos que la variable SENWT toma un valor menor que 12 sea igual en
los dos grupos.
##
## 0 1
## 1 0.5012404 0.4987596
## 2 0.4288152 0.5711848
## 3 0.5058404 0.4941596
## 4 0.7500000 0.2500000
## <NA> 0.4595300 0.5404700
Para un estudio de la independencia de las dos variables más formal, podemos realizar el test chi-cuadrado.
La hipótesis nula y alternativa son las siguientes:
##
## Pearson's Chi-squared test
##
## data: Tabla
## X-squared = 266.08, df = 4, p-value < 2.2e-16
Concluimos por lo tanto que hay evidencias suficientes para rechazar a un nivel de significación de α = 0.05
que ambas variables sean independientes. Es recomendable que la mayoría de las frecuencias esperadas (al
menos el 80%) sean mayores que 5 y, de lo contrario, se suele recomendar agrupar algunas clases de las
variables de estudio. Por defecto R devuelve un aviso si alguna frecuencia esperada es menor que 5. Para
consultar las frecuencias esperadas en nuestra tabla de contingencia podemos estudiar la variable expected
de la salida del test. En nuestro caso, todas las frecuencias esperadas son mayores que 5.