Visualización de Datos con R
Introducción a R
Instalación e Interfaz
Lo primero que debe hacer para empezar a utilizar R es instalarlo en su ordenador. R funciona en casi todas las plataformas disponibles, incluidos los sistemas Windows, Mac OS X y Linux. Links de descarga para R y RStudio.
RStudioes un entorno de desarrollo integrado (IDE) disponible para R, el cual tiene un buen editor con resaltado de sintaxis, un visor de objetos de R y un gran número de características agradables que están integradas.
Gestión de Data Frames con el paquete paquete dplyr
Data Frames
- Un data frame es una estructura de datos clave en estadística y en R. La estructura básica de un data frame es que hay una observación por fila y cada columna representa una variable, medida, rasgo o característica de esa observación. R tiene una implementación interna de los data frame que es probablemente la que se utiliza con más frecuencia.
El paquete dplyr
- El paquete dplyr fue desarrollado por Hadley
Wickham de RStudio y es una versión optimizada y destilada de su
paquete plyr. Una importante contribución del paquete
dplyr es que proporciona una “gramática” (en
particular, verbos) para la manipulación de datos y para operar con
data frames. Con esta gramática, se puede comunicar de forma
comprensible lo que se está haciendo a un data frame. Esto es
útil porque proporciona una abstracción para la manipulación de datos
que antes no existía. Otra contribución útil es que las funciones de
dplyr son muy rápidas, ya que muchas operaciones
clave están codificadas en
C++.
Gramática dplyr
Algunos de los “verbos” clave proporcionados por el paquete dplyr son
select: devuelve un subconjunto de las columnas de un dataframe, utilizando una notación flexiblefilter: extraer un subconjunto de filas de un dataframe basándose en condiciones lógicasarrange: reordenar las filas de un dataframerename: renombrar las variables de un dataframemutate: añadir nuevas variables/columnas o transformar variables existentessummarise: generar estadísticas de resumen de diferentes variables en eldataframe, posiblemente dentro de los estratos%>%: el operador “pipe” se utiliza para conectar varias acciones verbales en una pipeline
Propiedades comunes de la función dplyr
Todas las funciones que trataremos en este capítulo tendrán algunas características comunes. En particular,
- El primer argumento es un dataframe.
- Los argumentos siguientes describen qué hacer con el dataframe especificado en el primer argumento, y puede referirse a las columnas del dataframe directamente sin usar el operador $ (sólo use los nombres de las columnas).
- El resultado de retorno de una función es un nuevo dataframe
- Los dataframe deben estar correctamente formateados y anotados para que todo esto sea útil. En resumen, debe haber una observación por fila y cada columna debe representar un rasgo o característica de esa observación.
Instalación del paquete dplyr
El paquete dplyr se puede instalar desde CRAN o desde GitHub utilizando el paquete devtools y la función install_github(). El repositorio de GitHub suele contener las últimas actualizaciones del paquete y la versión de desarrollo.
Para instalarlo desde CRAN, basta con ejecutar
- Para instalar desde GitHub puedes ejecutar
- Después de instalar el paquete es importante que lo cargues en su
sesión de R con la función
library()
- Si la instalación de dplyr no fue realizada
correctamente, un mensaje de instalación requerida aparecerá
resaltado en amarillo en la parte superior de
RStudio. Haga click en Install y espere un momento mientras se lleva a cabo la instalación. Puede usar también esta estrategia de instalación en lo que sigue
- NOTA: Si alguna vez se encuentra con un problema en
el que R confunde la función que quiere llamar, puede
especificar el nombre completo de una función utilizando el
operador ::. Por ejemplo, la función de
filter del paquete dplyr tiene el
nombre completo
dplyr::filter. Llamar a las funciones con su nombre completo resolverá cualquier confusión sobre la función a la que se quiere llamar.
select()
Para los ejemplos de esta sección utilizaremos un dataset que contiene información sobre la contaminación del aire y la temperatura de la ciudad de Chicago (ver Air pollution dataset). Todos los datos usados en este curso los podrás descargar en: RDataSets
- Después de descomprimir el archivo, puede cargar los datos en
Rutilizando la funciónreadRDS(). Esta función proporciona los medios para guardar y restaurar un único objetoRen una conexión. Esta difiere desaveyload, que guardan y restauran uno o más objetos con nombre en un entorno. Son ampliamente utilizadas por el propioR, por ejemplo para almacenar los metadatos de un paquete y para almacenar las bases de datos dehelp.search: la extensión de archivo".rds"es la más utilizada.
- Puede ver algunas características básicas del dataset con las funciones dim() y str().
## [1] 6940 8
## 'data.frame': 6940 obs. of 8 variables:
## $ city : chr "chic" "chic" "chic" "chic" ...
## $ tmpd : num 31.5 33 33 29 32 40 34.5 29 26.5 32.5 ...
## $ dptp : num 31.5 29.9 27.4 28.6 28.9 ...
## $ date : Date, format: "1987-01-01" "1987-01-02" ...
## $ pm25tmean2: num NA NA NA NA NA NA NA NA NA NA ...
## $ pm10tmean2: num 34 NA 34.2 47 NA ...
## $ o3tmean2 : num 4.25 3.3 3.33 4.38 4.75 ...
## $ no2tmean2 : num 20 23.2 23.8 30.4 30.3 ...
## city tmpd dptp date pm25tmean2 pm10tmean2 o3tmean2 no2tmean2
## 1 chic 31.5 31.500 1987-01-01 NA 34.00000 4.250000 19.98810
## 2 chic 33.0 29.875 1987-01-02 NA NA 3.304348 23.19099
## 3 chic 33.0 27.375 1987-01-03 NA 34.16667 3.333333 23.81548
## 4 chic 29.0 28.625 1987-01-04 NA 47.00000 4.375000 30.43452
## 5 chic 32.0 28.875 1987-01-05 NA NA 4.750000 30.33333
## 6 chic 40.0 35.125 1987-01-06 NA 48.00000 5.833333 25.77233
La función
select()puede utilizarse para seleccionar las columnas de un dataframe en las que desee centrarse. A menudo se tiene un dataframe grande que contiene “todos” los datos, pero un análisis determinado puede utilizar solo un subconjunto de variables u observaciones. La funciónselect()le permite obtener las pocas columnas que pueda necesitar.Supongamos que queremos tomar sólo las 3 primeras columnas. Hay varias formas de hacerlo. Podríamos, por ejemplo, utilizar índices numéricos. Pero también podemos utilizar los nombres directamente.
## [1] "city" "tmpd" "dptp"
## city tmpd dptp
## 1 chic 31.5 31.500
## 2 chic 33.0 29.875
## 3 chic 33.0 27.375
## 4 chic 29.0 28.625
## 5 chic 32.0 28.875
## 6 chic 40.0 35.125
## city tmpd dptp
## 1 chic 31.5 31.500
## 2 chic 33.0 29.875
## 3 chic 33.0 27.375
## 4 chic 29.0 28.625
## 5 chic 32.0 28.875
## 6 chic 40.0 35.125
- Tenga en cuenta que el signo : normalmente no se puede utilizar con nombres o cadenas, pero dentro de la función select() puede utilizarlo para especificar un rango de nombres de variables. También puede omitir variables con la función select() utilizando el signo negativo
## date pm25tmean2 pm10tmean2 o3tmean2 no2tmean2
## 1 1987-01-01 NA 34.00000 4.250000 19.98810
## 2 1987-01-02 NA NA 3.304348 23.19099
## 3 1987-01-03 NA 34.16667 3.333333 23.81548
## 4 1987-01-04 NA 47.00000 4.375000 30.43452
## 5 1987-01-05 NA NA 4.750000 30.33333
## 6 1987-01-06 NA 48.00000 5.833333 25.77233
- El código equivalente en
Rbase sería el siguiente, el cual es menos intuitivo
## date pm25tmean2 pm10tmean2 o3tmean2 no2tmean2
## 1 1987-01-01 NA 34.00000 4.250000 19.98810
## 2 1987-01-02 NA NA 3.304348 23.19099
## 3 1987-01-03 NA 34.16667 3.333333 23.81548
## 4 1987-01-04 NA 47.00000 4.375000 30.43452
## 5 1987-01-05 NA NA 4.750000 30.33333
## 6 1987-01-06 NA 48.00000 5.833333 25.77233
- La función
select()también permite una sintaxis especial que permite especificar nombres de variables basados en patrones. Así, por ejemplo, si quisiéramos conservar todas las variables que terminan con un “2”, podríamos hacer
## 'data.frame': 6940 obs. of 4 variables:
## $ pm25tmean2: num NA NA NA NA NA NA NA NA NA NA ...
## $ pm10tmean2: num 34 NA 34.2 47 NA ...
## $ o3tmean2 : num 4.25 3.3 3.33 4.38 4.75 ...
## $ no2tmean2 : num 20 23.2 23.8 30.4 30.3 ...
- O si quisiéramos mantener todas las variables que empiezan por “d”, podríamos hacer
## 'data.frame': 6940 obs. of 2 variables:
## $ dptp: num 31.5 29.9 27.4 28.6 28.9 ...
## $ date: Date, format: "1987-01-01" "1987-01-02" ...
- También puede utilizar expresiones regulares más generales si es
necesario. Consulte la página de ayuda escribiendo en consola
(??select)para más detalles.
filter()
- La función filter() se utiliza para extraer
subconjuntos de filas de un dataframe. Esta función es similar a la
función subset() existente en R, pero
es bastante más rápida. Supongamos que queremos extraer las filas del
dataframe
PM2.5(la materia particulada 2.5, incluye sustancias químicas orgánicas, polvo, hollín y metales.) que sean superiores a 30 (que es un nivel razonablemente alto), podríamos hacer
## 'data.frame': 194 obs. of 8 variables:
## $ city : chr "chic" "chic" "chic" "chic" ...
## $ tmpd : num 23 28 55 59 57 57 75 61 73 78 ...
## $ dptp : num 21.9 25.8 51.3 53.7 52 56 65.8 59 60.3 67.1 ...
## $ date : Date, format: "1998-01-17" "1998-01-23" ...
## $ pm25tmean2: num 38.1 34 39.4 35.4 33.3 ...
## $ pm10tmean2: num 32.5 38.7 34 28.5 35 ...
## $ o3tmean2 : num 3.18 1.75 10.79 14.3 20.66 ...
## $ no2tmean2 : num 25.3 29.4 25.3 31.4 26.8 ...
- Puede ver que ahora sólo hay 194 filas en el dataframe y la
distribución de los valores de
pm25tmean2es
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 30.05 32.12 35.04 36.63 39.53 61.50
- Podemos colocar una secuencia lógica arbitrariamente
compleja dentro de filter(), por lo que podríamos,
por ejemplo, extraer las filas en las que
PM2.5es mayor que 30 y la temperatura,tmpdes mayor que 80 grados Fahrenheit
## date tmpd pm25tmean2
## 1 1998-08-23 81 39.60000
## 2 1998-09-06 81 31.50000
## 3 2001-07-20 82 32.30000
## 4 2001-08-01 84 43.70000
## 5 2001-08-08 85 38.83750
## 6 2001-08-09 84 38.20000
## 7 2002-06-20 82 33.00000
## 8 2002-06-23 82 42.50000
## 9 2002-07-08 81 33.10000
## 10 2002-07-18 82 38.85000
## 11 2003-06-25 82 33.90000
## 12 2003-07-04 84 32.90000
## 13 2005-06-24 86 31.85714
## 14 2005-06-27 82 51.53750
## 15 2005-06-28 85 31.20000
## 16 2005-07-17 84 32.70000
## 17 2005-08-03 84 37.90000
arrange()
- La función arrange() se utiliza para reordenar
las filas de un dataframe según una de las variables/columnas.
Reordenar las filas de un dataframe (conservando el orden
correspondiente de otras columnas) suele ser una tarea ardua en
R. La función arrange() simplifica la
tarea. Aquí podemos ordenar las filas del dataframe
chicagopor fecha, de modo que la primera fila sea la observación más antigua y la última la más reciente
- Ahora podemos visualizar las primeras y últimas filas de las siguiente manera
## date pm25tmean2
## 1 1987-01-01 NA
## 2 1987-01-02 NA
## 3 1987-01-03 NA
## date pm25tmean2
## 6938 2005-12-29 7.45000
## 6939 2005-12-30 15.05714
## 6940 2005-12-31 15.00000
- Las columnas también se pueden ordenar de forma descendente
utilizando el operador especial
desc()
## city tmpd dptp date pm25tmean2 pm10tmean2 o3tmean2 no2tmean2
## 1 chic 35 30.1 2005-12-31 15.00000 23.5 2.531250 13.25000
## 2 chic 36 31.0 2005-12-30 15.05714 19.2 3.034420 22.80556
## 3 chic 35 29.4 2005-12-29 7.45000 23.5 6.794837 19.97222
## 4 chic 37 34.5 2005-12-28 17.75000 27.5 3.260417 19.28563
## 5 chic 40 33.6 2005-12-27 23.56000 27.0 4.468750 23.50000
## 6 chic 35 29.6 2005-12-26 8.40000 8.5 14.041667 16.81944
- Si se observan las tres primeras y las tres últimas filas, las fechas aparecen en orden descendente
## date pm25tmean2
## 1 2005-12-31 15.00000
## 2 2005-12-30 15.05714
## 3 2005-12-29 7.45000
## date pm25tmean2
## 6938 1987-01-03 NA
## 6939 1987-01-02 NA
## 6940 1987-01-01 NA
rename()
- Renombrar una variable en un dataframe en
R es sorprendentemente difícil de hacer. La función
rename()está diseñada para facilitar este proceso. Aquí puedes ver los nombres de las cinco primeras variables del dataframechicago.
## city tmpd dptp date pm25tmean2
## 1 chic 35 30.1 2005-12-31 15.00000
## 2 chic 36 31.0 2005-12-30 15.05714
## 3 chic 35 29.4 2005-12-29 7.45000
- La columna
dptpse supone que representa la temperatura del punto de rocío y la columna pm25tmean2 proporciona los datos PM2.5. Sin embargo, estos nombres son incómodos y probablemente deban ser renombrados a algo más sencillos.Dew point: temperatura más alta a la que empieza a condensarse el vapor de agua contenido en el aire, produciendo rocío, neblina, cualquier tipo de nube o, en caso de que la temperatura sea lo suficientemente baja, escarcha.
## city tmpd dewpoint date pm25
## 1 chic 35 30.1 2005-12-31 15.00000
## 2 chic 36 31.0 2005-12-30 15.05714
## 3 chic 35 29.4 2005-12-29 7.45000
- La sintaxis dentro de la función rename() es
tener el nuevo nombre en el lado izquierdo del signo
=y el nombre antiguo en el lado derecho.
mutate()
La función
mutate()existe para calcular las transformaciones de las variables de un dataframe. A menudo, se desea crear nuevas variables derivadas de variables existentes ymutate()proporciona una interfaz limpia para hacerlo. Por ejemplo, con los datos de contaminación atmosférica, a menudo queremos eliminar la tendencia de los datos restando la media de los mismosDe este modo, podemos ver si el nivel de contaminación atmosférica de un día determinado es superior o inferior a la media (en lugar de observar su nivel absoluto). Aquí creamos una variable
pm25detrendque resta la media de la variablepm25.
## city tmpd dewpoint date pm25 pm10tmean2 o3tmean2 no2tmean2
## 1 chic 35 30.1 2005-12-31 15.00000 23.5 2.531250 13.25000
## 2 chic 36 31.0 2005-12-30 15.05714 19.2 3.034420 22.80556
## 3 chic 35 29.4 2005-12-29 7.45000 23.5 6.794837 19.97222
## 4 chic 37 34.5 2005-12-28 17.75000 27.5 3.260417 19.28563
## 5 chic 40 33.6 2005-12-27 23.56000 27.0 4.468750 23.50000
## 6 chic 35 29.6 2005-12-26 8.40000 8.5 14.041667 16.81944
## pm25detrend
## 1 -1.230958
## 2 -1.173815
## 3 -8.780958
## 4 1.519042
## 5 7.329042
## 6 -7.830958
- También existe la función relacionada llamada
transmute(), que hace lo mismo quemutate()pero luego elimina todas las variables no transformadas. En este caso, se remueve la tendencia de las variables PM10 y ozono (O3)
head(transmute(chicago,
pm10detrend = pm10tmean2 - mean(pm10tmean2, na.rm = TRUE),
o3detrend = o3tmean2 - mean(o3tmean2, na.rm = TRUE)))## pm10detrend o3detrend
## 1 -10.395206 -16.904263
## 2 -14.695206 -16.401093
## 3 -10.395206 -12.640676
## 4 -6.395206 -16.175096
## 5 -6.895206 -14.966763
## 6 -25.395206 -5.393846
- Observe que sólo hay dos columnas en el dataframe transmutado.
group_by()
La función
group_by()se utiliza para generar estadísticas de resumen del dataframe dentro de los estatus definidos por una variable. Por ejemplo, en este dataset de contaminación atmosférica, podría querer saber cuál es el nivel medio anual de PM2,5. Entonces el estatus es el año, y eso es algo que podemos derivar de la variable fecha. Junto con la funcióngroup_by()se suele utilizar la funciónsummarize()(o summarise()` para algunas partes del mundo).La operación general aquí es una combinación de dividir un dataframe en piezas separadas definidas por una variable o grupo de variables (
group_by()), y luego aplicar una función de resumen en esos subconjuntos (summarize()). En primer lugar, podemos crear una variable de año utilizandoas.POSIXlt(). Las opciones para días y meses respectivamente, están disponibles usando$mdayymon+1(ver as.POSIXlt). Los valores anuales se almacenan utilizando un valor de índice base de 1900. Así,2015se almacena como 115 ($year= 115).
## city tmpd dewpoint date pm25 pm10tmean2 o3tmean2 no2tmean2
## 1 chic 35 30.1 2005-12-31 15.00000 23.5 2.531250 13.25000
## 2 chic 36 31.0 2005-12-30 15.05714 19.2 3.034420 22.80556
## 3 chic 35 29.4 2005-12-29 7.45000 23.5 6.794837 19.97222
## 4 chic 37 34.5 2005-12-28 17.75000 27.5 3.260417 19.28563
## 5 chic 40 33.6 2005-12-27 23.56000 27.0 4.468750 23.50000
## 6 chic 35 29.6 2005-12-26 8.40000 8.5 14.041667 16.81944
## pm25detrend year
## 1 -1.230958 2005
## 2 -1.173815 2005
## 3 -8.780958 2005
## 4 1.519042 2005
## 5 7.329042 2005
## 6 -7.830958 2005
- Ahora podemos crear un dataframe separado que divida el dataset original por año
## # A tibble: 6,940 × 10
## # Groups: year [19]
## city tmpd dewpoint date pm25 pm10tmean2 o3tmean2 no2tmean2
## <chr> <dbl> <dbl> <date> <dbl> <dbl> <dbl> <dbl>
## 1 chic 35 30.1 2005-12-31 15 23.5 2.53 13.2
## 2 chic 36 31 2005-12-30 15.1 19.2 3.03 22.8
## 3 chic 35 29.4 2005-12-29 7.45 23.5 6.79 20.0
## 4 chic 37 34.5 2005-12-28 17.8 27.5 3.26 19.3
## 5 chic 40 33.6 2005-12-27 23.6 27 4.47 23.5
## 6 chic 35 29.6 2005-12-26 8.4 8.5 14.0 16.8
## 7 chic 35 32.1 2005-12-25 6.7 8 14.4 13.8
## 8 chic 37 35.2 2005-12-24 30.8 25.2 1.77 32.0
## 9 chic 41 32.6 2005-12-23 32.9 34.5 6.91 29.1
## 10 chic 22 23.3 2005-12-22 36.6 42.5 5.39 33.7
## # ℹ 6,930 more rows
## # ℹ 2 more variables: pm25detrend <dbl>, year <dbl>
- Por último, calculamos los estadísticos de resumen para cada año
del dataframe con la función
summarize(). La funciónsummarize()devuelve undataframeconyearcomo primera columna, y luego los promedios anuales de pm25, o3 y no2.
summarize(years, pm25 = mean(pm25, na.rm = TRUE),
o3 = max(o3tmean2, na.rm = TRUE),
no2 = median(no2tmean2, na.rm = TRUE))## # A tibble: 19 × 4
## year pm25 o3 no2
## <dbl> <dbl> <dbl> <dbl>
## 1 1987 NaN 63.0 23.5
## 2 1988 NaN 61.7 24.5
## 3 1989 NaN 59.7 26.1
## 4 1990 NaN 52.2 22.6
## 5 1991 NaN 63.1 21.4
## 6 1992 NaN 50.8 24.8
## 7 1993 NaN 44.3 25.8
## 8 1994 NaN 52.2 28.5
## 9 1995 NaN 66.6 27.3
## 10 1996 NaN 58.4 26.4
## 11 1997 NaN 56.5 25.5
## 12 1998 18.3 50.7 24.6
## 13 1999 18.5 57.5 24.7
## 14 2000 16.9 55.8 23.5
## 15 2001 16.9 51.8 25.1
## 16 2002 15.3 54.9 22.7
## 17 2003 15.2 56.2 24.6
## 18 2004 14.6 44.5 23.4
## 19 2005 16.2 58.8 22.6
En un ejemplo algo más trabajado, podríamos querer saber cuáles son los niveles medios de de ozono (
o3) y dióxido de nitrógeno (no2) dentro de los quintiles depm25. Una forma más sencilla de de hacerlo sería a través de un modelo de regresión, pero en realidad podemos hacerlo rápidamente congroup_by()ysummarize().Primero, podemos crear una variable categórica de pm25 dividida en quintiles La función
cutdivide el rango en intervalos y codifica los valores según el intervalo al que pertenecen.
## 0% 20% 40% 60% 80% 100%
## 1.700 8.700 12.375 16.700 22.610 61.500
## city tmpd dewpoint date pm25 pm10tmean2 o3tmean2 no2tmean2
## 1 chic 35 30.1 2005-12-31 15.00000 23.5 2.531250 13.25000
## 2 chic 36 31.0 2005-12-30 15.05714 19.2 3.034420 22.80556
## 3 chic 35 29.4 2005-12-29 7.45000 23.5 6.794837 19.97222
## 4 chic 37 34.5 2005-12-28 17.75000 27.5 3.260417 19.28563
## 5 chic 40 33.6 2005-12-27 23.56000 27.0 4.468750 23.50000
## 6 chic 35 29.6 2005-12-26 8.40000 8.5 14.041667 16.81944
## pm25detrend year pm25.quint
## 1 -1.230958 2005 (12.4,16.7]
## 2 -1.173815 2005 (12.4,16.7]
## 3 -8.780958 2005 (1.7,8.7]
## 4 1.519042 2005 (16.7,22.6]
## 5 7.329042 2005 (22.6,61.5]
## 6 -7.830958 2005 (1.7,8.7]
- Ahora podemos agrupar el dataframe por la variable pm25.quint.
## # A tibble: 6,940 × 11
## # Groups: pm25.quint [6]
## city tmpd dewpoint date pm25 pm10tmean2 o3tmean2 no2tmean2
## <chr> <dbl> <dbl> <date> <dbl> <dbl> <dbl> <dbl>
## 1 chic 35 30.1 2005-12-31 15 23.5 2.53 13.2
## 2 chic 36 31 2005-12-30 15.1 19.2 3.03 22.8
## 3 chic 35 29.4 2005-12-29 7.45 23.5 6.79 20.0
## 4 chic 37 34.5 2005-12-28 17.8 27.5 3.26 19.3
## 5 chic 40 33.6 2005-12-27 23.6 27 4.47 23.5
## 6 chic 35 29.6 2005-12-26 8.4 8.5 14.0 16.8
## 7 chic 35 32.1 2005-12-25 6.7 8 14.4 13.8
## 8 chic 37 35.2 2005-12-24 30.8 25.2 1.77 32.0
## 9 chic 41 32.6 2005-12-23 32.9 34.5 6.91 29.1
## 10 chic 22 23.3 2005-12-22 36.6 42.5 5.39 33.7
## # ℹ 6,930 more rows
## # ℹ 3 more variables: pm25detrend <dbl>, year <dbl>, pm25.quint <fct>
- Por último, podemos calcular la media de
o3yno2dentro de los quintiles de pm25
## # A tibble: 5 × 3
## pm25.quint o3 no2
## <fct> <dbl> <dbl>
## 1 (1.7,8.7] 21.7 18.0
## 2 (8.7,12.4] 20.4 22.1
## 3 (12.4,16.7] 20.7 24.4
## 4 (16.7,22.6] 19.9 27.3
## 5 (22.6,61.5] 20.3 29.6
Con base en la tabla, no hay una relación fuerte entre
pm25yo3, pero parece haber una correlación positiva entrepm25yno2. Un modelo estadístico más sofisticado puede ayudar a dar respuestas precisas a estas preguntas, pero una simple aplicación de las funcionesdplyrpuede llevarle a la mayoría de los caminos.Nótese además que si omitimos el uso de la función
na.omit, podremos observar una fila cuya clase corresponde a<NA>. Esto está asociado a observaciones para las cuales existen valores deo3yno2, pero,pm25es faltante. Estudiaremos mas adelante como realizar imputación de este tipo de datos.
## # A tibble: 6 × 3
## pm25.quint o3 no2
## <fct> <dbl> <dbl>
## 1 (1.7,8.7] 21.7 18.0
## 2 (8.7,12.4] 20.4 22.1
## 3 (12.4,16.7] 20.7 24.4
## 4 (16.7,22.6] 19.9 27.3
## 5 (22.6,61.5] 20.3 29.6
## 6 <NA> 18.8 25.8
%>%
- El operador
pipeline%>% es muy útil para encadenar múltiples funcionesdplyren una secuencia de operaciones. Nótese que hasta ahora, cada vez que se desea aplicar más de una función, la secuencia quedaba dentro de una secuencia de llamadas a funciones anidadas que es difícil de leer, por ejemplo
- Este anidamiento no es una forma natural de pensar en una secuencia de operaciones. El operador %>% permite encadenar las operaciones de izquierda a derecha, es decir
Tomemos el ejemplo que acabamos de hacer en la última sección, en el que hemos calculado la media de o3 y no2 dentro de los quintiles de pm25. Allí tuvimos que
- Crear una nueva variable pm25.quint
- Agrupar el dataframe por esa nueva variable
- Calcular la media de o3 y no2 en los subgrupos definidos por pm25.quint
Eso se puede hacer con la siguiente secuencia en una sola expresión de R
library(tidyr)
mutate(chicago, pm25.quint = cut(pm25, qq)) %>%
group_by(pm25.quint) %>%
summarize(o3 = mean(o3tmean2, na.rm = TRUE), no2 = mean(no2tmean2, na.rm = TRUE)) %>%
drop_na()## # A tibble: 5 × 3
## pm25.quint o3 no2
## <fct> <dbl> <dbl>
## 1 (1.7,8.7] 21.7 18.0
## 2 (8.7,12.4] 20.4 22.1
## 3 (12.4,16.7] 20.7 24.4
## 4 (16.7,22.6] 19.9 27.3
## 5 (22.6,61.5] 20.3 29.6
De esta manera no tenemos que crear un conjunto de variables temporales en el camino o crear una secuencia masiva anidada de llamadas a funciones. Observe en el código anterior que el dataframe
chicagopasa a la primera llamada amutate(), pero después no tiene que pasar el primer argumento agroup_by()o asummarize(). Una vez que se recorre el pipeline con %>%, el primer argumento se toma como la salida del elemento anterior en la pipeline.Otro ejemplo podría ser el cálculo del nivel medio de contaminantes
pm25, o3, no2por mes. Esto podría ser útil para ver si hay alguna tendencia estacional en los datos.
mutate(chicago, month = as.POSIXlt(date)$mon + 1) %>%
group_by(month) %>%
summarize(pm25 = mean(pm25, na.rm = TRUE),
o3 = max(o3tmean2, na.rm = TRUE),
no2 = median(no2tmean2, na.rm = TRUE))## # A tibble: 12 × 4
## month pm25 o3 no2
## <dbl> <dbl> <dbl> <dbl>
## 1 1 17.8 28.2 25.4
## 2 2 20.4 37.4 26.8
## 3 3 17.4 39.0 26.8
## 4 4 13.9 47.9 25.0
## 5 5 14.1 52.8 24.2
## 6 6 15.9 66.6 25.0
## 7 7 16.6 59.5 22.4
## 8 8 16.9 54.0 23.0
## 9 9 15.9 57.5 24.5
## 10 10 14.2 47.1 24.2
## 11 11 15.2 29.5 23.6
## 12 12 17.5 27.7 24.5
- Aquí podemos ver que el o3 tiende a ser bajo en los meses de invierno (11, 12, 1, 2, 3) y alto en el verano (4,5,…,10) mientras que no2 es más alto en invierno y más bajo en verano.
Graficar un dataset
- El paquete
ggplot2puede utilizarse para crear varios tipos de gráficos y diagramas a partir de un dataframe. La funciónggplot()se utiliza para definir gráficos, y se le pueden pasar una serie de parámetros y unirse a otras funciones para producir finalmente un gráfico de salida. El primer parámetro que se pasa aggplot()es el dataframe que se quiere representar. Normalmente será un objeto data.frame, pero también puede ser un subconjunto de un dataframe definido con la funciónsubset().
- Puede obtener un recuento del número de registros con la función
nrow(). La funciónView()se puede utilizar para ver los datos en un formato tabular.
## [1] 481376
## # A tibble: 6 × 11
## `Report Number` `Occurred Date` `Occurred Time` `Reported Date`
## <dbl> <chr> <dbl> <chr>
## 1 2.01e13 12/13/1908 2114 12/13/2008
## 2 2.01e13 06/15/1964 0 06/15/2010
## 3 2.01e12 01/01/1973 0 01/25/2012
## 4 2.01e13 06/01/1974 0 09/09/2013
## 5 2.02e13 01/01/1975 0 08/11/2016
## 6 1.98e12 12/16/1975 900 12/16/1975
## # ℹ 7 more variables: `Reported Time` <dbl>, `Crime Subcategory` <chr>,
## # `Primary Offense Description` <chr>, Precinct <chr>, Sector <chr>,
## # Beat <chr>, Neighborhood <chr>
Un
tibble, otbl_df, es una reimaginación moderna deldata.frame, manteniendo lo que el tiempo ha demostrado que es eficaz, y desechando lo que no lo es. En este caso limitaremos las columnas a lo siguiente: Reported Date, Crime Subcategory, Primary Offense Description, Precinct, Sector, Beat y Neighborhoodtibbletiene un método de imprimir refinado que muestra sólo las 10 primeras filas y todas las columnas que caben en la pantalla. Esto hace que sea mucho más fácil trabajar con datos de gran tamaño. A diferencia de losdata.frame, lostibbleno admiten operaciones aritméticas en todas las columnas, mientras que con undata.frame.
dfCrime = select(dfCrime,
"Reported Date",
"Crime Subcategory",
"Primary Offense Description",
"Precinct",
"Sector",
"Beat",
"Neighborhood")- También es posible que quiera cambiar el nombre de las columnas
para que sean más fáciles de leer o simplificar los nombres. La
función
select()se puede utilizar para hacer esto también.
dfCrime = select(dfCrime,
"CrimeDate" = "Reported Date",
"Category" = "Crime Subcategory",
"Description" = "Primary Offense Description",
"Precinct",
"Sector",
"Beat",
"Neighborhood")
head(dfCrime)## # A tibble: 6 × 7
## CrimeDate Category Description Precinct Sector Beat Neighborhood
## <chr> <chr> <chr> <chr> <chr> <chr> <chr>
## 1 12/13/2008 DUI DUI-LIQUOR EAST G G2 CENTRAL ARE…
## 2 06/15/2010 FAMILY OFFENSE-NONV… CHILD-OTHER WEST Q Q2 QUEEN ANNE
## 3 01/25/2012 SEX OFFENSE-OTHER SEXOFF-OTH… NORTH N N2 NORTHGATE
## 4 09/09/2013 SEX OFFENSE-OTHER SEXOFF-OTH… UNKNOWN <NA> <NA> UNKNOWN
## 5 08/11/2016 SEX OFFENSE-OTHER SEXOFF-OTH… UNKNOWN <NA> <NA> UNKNOWN
## 6 12/16/1975 BURGLARY-RESIDENTIAL BURGLARY-F… SOUTH R R3 LAKEWOOD/SE…
- El filtrado del dataset le permite centrarse en un subconjunto de filas en lugar de en todo el dataset. El paquete dplyr incluye una función filter() que soporta esta capacidad
## [1] 25172
- También puede incluir varias expresiones en una función
filter(), como sigue, vecindario QUEEN ANNE y categoría BURGLARY-RESIDENTIAL. Examinaremos expresiones de filtro más complejas en una sección posterior
dfCrime3 = filter(dfCrime,
Neighborhood == "QUEEN ANNE",
Category == "BURGLARY-RESIDENTIAL")
head(dfCrime3)## # A tibble: 6 × 7
## CrimeDate Category Description Precinct Sector Beat Neighborhood
## <chr> <chr> <chr> <chr> <chr> <chr> <chr>
## 1 04/22/2008 BURGLARY-RESIDENTIAL BURGLARY-N… WEST Q Q3 QUEEN ANNE
## 2 01/02/2008 BURGLARY-RESIDENTIAL BURGLARY-F… WEST Q Q3 QUEEN ANNE
## 3 01/02/2008 BURGLARY-RESIDENTIAL BURGLARY-F… WEST D D2 QUEEN ANNE
## 4 01/07/2008 BURGLARY-RESIDENTIAL BURGLARY-F… WEST Q Q3 QUEEN ANNE
## 5 01/09/2008 BURGLARY-RESIDENTIAL BURGLARY-N… WEST Q Q2 QUEEN ANNE
## 6 01/10/2008 BURGLARY-RESIDENTIAL BURGLARY-F… WEST Q Q3 QUEEN ANNE
- En la ventana de la consola de
RStudioañada el código que ve a continuación para agrupar los delitos por ronda policial
- La función
n()se utiliza para obtener un recuento del número de registros de cada grupo. Añada a el código que se ve a continuación. La columnaBeatcorresponde a el límite del sector policial designado donde se produjeron las infracciones (ver Seattle Police Department)
## # A tibble: 4 × 2
## Beat n
## <chr> <int>
## 1 D2 4373
## 2 Q1 88
## 3 Q2 10851
## 4 Q3 9860
Gráficos con ggplot()
- En la consola de RStudio añada el código que ve a
continuación. La función ggplot() en este caso recibe
el dataframe dfCrime. La función
geom_col()se utiliza para definir la geometría del gráfico (diagrama de barras) y se le pasa un parámetro de asignación que se define llamando a la funciónaes()y pasando las columnas para el ejex(Beat), y el ejey(n = count)
Gráficos de robos por mes y año
En este ejercicio crearemos un par de gráficos de barras que muestren el número de robos por año y por mes para el barrio de Queen Anne. Además de los paquetes
dplyryggplot2que que usamos anteriormente, también usaremos el paquetelubridatepara manipular la información de las fechas.En el panel de paquetes de
RStudio, cargue el paquetelubridate. El paquetelubridateforma parte detidyversey se utiliza para trabajar con fechas y horas. Además, asegúrese de que los paquetesreadr, dplyryggplot2están cargados
- Especifique las columnas y los nombres de las columnas
dfCrime = select(dfCrime,
"CrimeDate" = "Reported Date",
"Category" = "Crime Subcategory",
"Description" = "Primary Offense Description",
"Precinct",
"Sector",
"Beat",
"Neighborhood")- Filtrar los registros para que sólo se conserven los robos residenciales en el barrio de Queen Anne
El paquete
dplyrincluye la capacidad de crear dinámicamente nuevas columnas en un dataframe mediante la manipulación de los datos de las columnas existentes en el dataframe. La funciónmutate()se utiliza para crear las nuevas columnas. Aquí la funciónmutate()se utilizará para extraer el año de la columna CrimeDate.Agregue el siguiente código para ver esto en acción. El segundo parámetro crea una nueva columna llamada YEAR y la rellena utilizando la función
year()del paquetelubridate. Dentro de la funciónyear()la columna CrimeDate, que es una columna de caracteres, se convierte en una fecha con base en el formatoformat="%m/%d/%Y".
library(lubridate)
dfCrime3 = mutate(dfCrime2, YEAR = year(as.Date(dfCrime2$CrimeDate,format="%m/%d/%Y")))- Observe la columna YEAR al final del dataframe. La
función
mutate()siempre añade nuevas columnas al final del dataframe
## # A tibble: 6 × 8
## CrimeDate Category Description Precinct Sector Beat Neighborhood YEAR
## <chr> <chr> <chr> <chr> <chr> <chr> <chr> <dbl>
## 1 04/22/2008 BURGLARY-RESI… BURGLARY-N… WEST Q Q3 QUEEN ANNE 2008
## 2 01/02/2008 BURGLARY-RESI… BURGLARY-F… WEST Q Q3 QUEEN ANNE 2008
## 3 01/02/2008 BURGLARY-RESI… BURGLARY-F… WEST D D2 QUEEN ANNE 2008
## 4 01/07/2008 BURGLARY-RESI… BURGLARY-F… WEST Q Q3 QUEEN ANNE 2008
## 5 01/09/2008 BURGLARY-RESI… BURGLARY-N… WEST Q Q2 QUEEN ANNE 2008
## 6 01/10/2008 BURGLARY-RESI… BURGLARY-F… WEST Q Q3 QUEEN ANNE 2008
- Ahora agruparemos los datos por año y resumiremos obteniendo un recuento del número de delitos por año
## # A tibble: 6 × 2
## YEAR n
## <dbl> <int>
## 1 2008 205
## 2 2009 194
## 3 2010 181
## 4 2011 203
## 5 2012 170
## 6 2013 209
- Cree un gráfico de barras llamando a las funciones
ggplot()ygeom_col()como se ve abajo. Defina YEAR como columna para el ejexy el número de delitos para el eje el ejey. Esto debería producir el gráfico que se ve a continuación
- Ahora crearemos otro gráfico de barras que muestre el número de
delitos por por mes en lugar de por año. En primer lugar, cree una
columna MONTH utilizando la función
mutate()
- Agrupamos y resumimos los datos por mes
## # A tibble: 12 × 2
## MONTH n
## <dbl> <int>
## 1 1 211
## 2 2 162
## 3 3 173
## 4 4 188
## 5 5 206
## 6 6 203
## 7 7 196
## 8 8 144
## 9 9 187
## 10 10 154
## 11 11 168
## 12 12 201
- Para obtener una mejor visualización, podemos por ejemplo utilizar
la función
month.abb[]para cambiar losidde los meses a las iniciales correspondientes
## # A tibble: 12 × 3
## MONTH n MONTH_S
## <dbl> <int> <chr>
## 1 1 211 Jan
## 2 2 162 Feb
## 3 3 173 Mar
## 4 4 188 Apr
## 5 5 206 May
## 6 6 203 Jun
## 7 7 196 Jul
## 8 8 144 Aug
## 9 9 187 Sep
## 10 10 154 Oct
## 11 11 168 Nov
## 12 12 201 Dec
- A continuación, se crea el gráfico de barras
library(scales)
dfCrime4$MONTH_S <- factor(dfCrime4$MONTH_S, levels=unique(dfCrime4$MONTH_S))
ggplot(data=dfCrime4) + geom_col(mapping = aes(x=MONTH_S, y=n), fill="blue") +
xlab("Month") + ylab("Frequency") +
ggtitle("Number of crimes per month")Conclusiones
- En este capítulo se han abordado algunas técnicas básicas para
la exploración y visualización utilizando el paquete
tidyversey su ecosistema de subpaquetes. Después de instalar y cargar el paquete mediante RStudio, se realizaron una serie de tareas usando el lenguaje de programación R con una serie de subpaquetes tidyverse. Se cargó un dataset desde un archivo CSV utilizando readr. - Después, se manipuló los datos de varias maneras utilizando el
paquete dplyr. Se utilizó la función
select() para incluir y renombrar columnas. El
contenido del dataframe se filtró con la función
filter(). A continuación, los datos se agruparon y
finalmente se produjeron varios gráficos utilizando
ggplot2. En la siguiente sección se estudiará más sobre
cómo usar el paquete
readrpara cargar datos de fuentes de datos externas.
Lectura de datos en R
Los objetos de datos grandes, normalmente almacenados como dataframe en R, suelen leerse de archivos externos. En esta sección, examinaremos una serie de funciones que se pueden utilizar para leer datos. Hay un gran número de formatos de datos comunes que pueden ser leídos dentro y fuera de R. Esto incluye archivos de texto en formatos como csv, txt, html y json. También incluye archivos de salida de aplicaciones estadísticas como SAS y SPSS. Los recursos en línea Los recursos en línea, incluidos los servicios web y las páginas HTML, también pueden leerse en R. Por último, también se pueden leer tablas de bases de datos relacionales y no relacionales. Hay una serie de funciones proporcionadas por R y Tidyverse que le permitirán leer estas diversas fuentes.
En este capítulo cubriremos los siguientes temas:
- Carga de un archivo csv con read.table()
- Carga de un archivo csv con read.csv()
- Carga de un archivo delimitado por tabulaciones con read.table()
- Uso de readr para cargar datos
Lectura de archivos csv con
read.table()
La primera función que examinaremos es read.table(). La función read.table() es una función función integrada en R que puede utilizarse para leer varios formatos de archivo en un dataframe, esta es probablemente la función interna más común utilizada para leer archivos simples en R. Sin embargo, como veremos más adelante en esta sección, tidyverse incluye funciones similares que son más eficientes en la lectura de datos externos en R. La sintaxis de read.table() es aceptar un nombre de archivo, que será la ruta junto con un indicador TRUE|FALSE para la cabecera. Si se establece como TRUE, se asume que los nombres de las columnas están en la línea de cabecera del archivo. La ruta no es no es necesaria si ya se ha establecido el directorio de trabajo. La salida de la función read.table() es un objeto dataframe. La función file.choose() es una función práctica que puede utilizar para seleccionar interactivamente el archivo que desea importar en lugar de tener que codificar la ruta del dataset. En este ejercicio aprenderá a utilizar la función read.table() para cargar un archivo con formato csv
Abra RStudio y busque el panel de la consola. Si es necesario, establezca el directorio de trabajo escribiendo el código que ve a continuación en el panel de la consola o desde las opcciones Sesión | Establecer directorio de trabajo | Elegir Directorio en el menú de RStudio.
La carpeta RDataSets contiene un archivo llamado StudyArea.csv, que es un archivo separado por comas que contiene datos de incendios forestales de los años 1980-2016 para los estados de California, Oregón, Washington, Idaho, Montana, Wyoming, Colorado, Utah, Nevada, Arizona y Nuevo México. Hay un poco más de 439.000 registros en este archivo y hay 37 columnas de información que describen cada incendio durante este periodo.
Utilice la función
read.table()para cargar estos datos en un nuevo objeto dataframe
- Recibirá un mensaje de error cuando intente ejecutar esta línea de código. El mensaje de error mensaje de error debería aparecer como se ve a continuación
La razón por la que se generó un mensaje de error en este caso es que la función read.table() utiliza espacios como delimitador entre registros y nuestro archivo utiliza comas como delimitador
Actualice su llamada a read.table() como se ve a continuación para incluir el argumento sep, que debería ser una coma
- Cuando ejecute esta línea de código verá un nuevo error
- La función read.table() NO rellenará automáticamente los valores que falten con un valor por defecto como NA, así que como algunas de las columnas están vacías en nuestras filas obtenemos un mensaje de error que indica que una línea en particular no tiene las 14 columnas de información. Podemos arreglar esto añadiendo el parámetro fill y poniéndolo igual a TRUE. Actualice su código como se ve a continuación para añadir el parámetro de fill
## [1] 153095
Cuando ejecute esta línea de código, importará el contenido del archivo a un objeto objeto dataframe. Sin embargo, si mira la pestaña Global Enviroment en RStudio verá que sólo se han cargado 153.095 registros y sin embargo sabemos que hay más de 400.000.
Las comillas (simples o dobles) en un archivo csv pueden hacer que no se carguen los registros. Agreguemos un parámetro más para manejar los registros que fueron descartados debido a comillas.
## [1] 439362
Al ejecutar esta línea de código, se deberían importar 440.476 registros. Los datos se cargan en un objeto
Rdata.frameque es una estructura que se asemeja a una tabla. Por ahora, puede pensar en ellos como tablas que contienen columnas y filas.La función
read.table()se utiliza normalmente para cargar archivos de texto delimitados por tabulaciones, pero muchos intentarán utilizar la función read.table() con archivos de formato csv sin entender todos los parámetros que pueden necesitar ser incluidos. En su lugar, también puede utilizarread.csv()como haremos en el siguiente paso.
Lectura de archivos csv con read.csv()
La función
read.csv()también es una función incorporada en R que es casi idéntica aread.table(), con la excepción de que los argumentos de cabecera y relleno se establecen en TRUE por defecto. En este paso se observa que es mucho más fácil cargar un archivo csv usandoread.csv().La función
read.csv()maneja automáticamente la mayoría de las situaciones que deben identificarse cuando se utilizaread.table()para cargar un archivo csv. Introduzca y ejecute el código que se ve a continuación para ver cuánto más fácil es esto conread.csv(). Esto cargará correctamente los más de 400.000 registros del archivocsv. En general esta función es mucho más fácil de usar queread.table().
## [1] 439362
Lectura de archivo delimitado por tabulaciones con read.table()
La función
read.table()se utiliza más a menudo para leer el contenido de un archivo delimitado por tabulaciones. En este paso aprenderás a hacerloLa carpeta RDataSets incluye un archivo llamado all_genes_pombase.txt, que está delimitado por texto. Abra este archivo con Excel o alguna otra aplicación para ver la estructura de los campos y los delimitadores.
En la ventana de la consola de R introduzca y ejecute el código que ve a continuación para importar el archivo.
## [1] 7019
## ensembl_id name chromosome
## 1 SPAC1002.01 SPAC1002.01 I
## 2 SPAC1002.02 pom34 I
## 3 SPAC1002.03c gls2 I
## 4 SPAC1002.04c taf11 I
## 5 SPAC1002.05c jmj2 I
## 6 SPAC1002.06c bqt2 I
## description feature_type
## 1 conserved fungal protein protein_coding
## 2 nucleoporin Pom34 protein_coding
## 3 glucosidase II alpha subunit Gls2 protein_coding
## 4 transcription factor TFIID complex subunit Taf11 (predicted) protein_coding
## 5 histone demethylase Jmj2 protein_coding
## 6 bouquet formation protein Bqt2 protein_coding
## strand start end
## 1 1 1798347 1799015
## 2 1 1799061 1800053
## 3 -1 1799915 1803141
## 4 -1 1803624 1804491
## 5 -1 1804548 1806797
## 6 -1 1807270 1807781
- Esto debería cargar 7019 registros en el data.frame. Notará que muchos de los parámetros todavía tienen que ser utilizados cuando se carga el dataset, por lo que no es tan fácil de como se podría esperar incluso en este caso.
Uso de readr para cargar datos
Hasta ahora en este capítulo hemos visto varias funciones incorporadas en R para leer archivos externos como dataframe. El paquete tidyverse incluye un subpaquete llamado readr que también puede utilizarse para cargar datos externos. El paquete readr incluye una función read_csv() que carga los datos mucho más rápido que la función interna read.csv().
En general, la función read_csv() del paquete readr es preferible a las funciones que se encuentran en la instalación básica de R. El paquete readr también incluye algunas otras funciones para cargar varios formatos de archivo, incluyendo read_delim(), read_csv2(), y read_tsv(). Cada una estas funciones aceptan los mismos parámetros, por lo que una vez que haya aprendido a utilizar cualquiera de las funciones de R para cargar datos, puede fácilmente utilizar cualquiera de las otras.
En este paso vas a utilizar la función read_csv() que se encuentra en el paquete readr para cargar los datos en un dataframe.
- Cargar la librería readr
- La función read_csv() del paquete readr puede utilizarse para cargar archivos csv. En comparación con las funciones de carga base que vimos anteriormente, las funciones readr son significativamente más rápidas (10 veces), incluyen una útil barra de progreso para proporcionar información sobre el progreso de la carga de archivos grandes, y todas las funciones trabajan exactamente de la misma manera.
- Añada y ejecute el código a continuación. Observe que los datos se
cargan mucho más rápido en el objeto dataframe. El argumento
col_typesfue utilizado en este caso para cargar todas las columnas como un tipo de datos de caracteres para simplificar. De lo contrario, tendríamos que hacer un preprocesamiento adicional de los datos para tener en cuenta los distintos tipos de datos de las columnas.
- Ahora vamos a ejecutar esta función de nuevo, pero esta vez quitando el argumento col_types para poder ver un ejemplo de algunos de los posibles errores de carga que pueden ocurrir. Lo primero que verá es una lista de las columnas que se importarán junto con el tipo de datos de la columna. A continuación aparecerá un mensaje de advertencia indicando que hubo errores de análisis en la carga.
- Puede utilizar la función
problems()para obtener una lista de los errores de análisis. Añada y ejecute el código que ves a continuación.
## # A tibble: 0 × 5
## # ℹ 5 variables: row <int>, col <int>, expected <chr>, actual <chr>, file <chr>
- Por el aspecto de los mensajes de error parece que hay un problema con la columna UNIT. Si vuelve a mirar la lista de columnas y tipos de datos, observará que la columna UNIT fue creada como un tipo de datos entero. Sin embargo, si abre el archivo StudyArea.csv en Excel o en otra aplicación, verá rápidamente verá que no todos los valores son numéricos, algunos incluyen letras. Esto explica los errores de análisis en el dataset.
- Actualice su código como se ve a continuación y ejecútelo de nuevo. Esto establece la columna UNIT a un tipo de datos de carácter (texto).
dfReadr = read_csv("C:/Data/StudyArea.csv", col_types = list(UNIT = col_character()),
col_names = TRUE)- Esta vez debería obtener una carga limpia del dataset. Eso no significa que los datos no necesiten alguna preparación y limpieza adicional. Por ejemplo, hay algunos campos de fecha, como STARTDATED, que se cargaron como caracteres pero es mejor que se carguen como campos de fecha. Podemos dejar este trabajo de preparación adicional para un ejercicio posterior.
- Puede examinar las primeras líneas del dataframe introduciendo la función head() como se ve a continuación.
## # A tibble: 6 × 14
## FID ORGANIZATI UNIT SUBUNIT SUBUNIT2 FIRENAME CAUSE YEAR_ STARTDATED
## <dbl> <chr> <chr> <chr> <chr> <chr> <chr> <dbl> <chr>
## 1 0 FWS 81682 USCADBR San Diego Bay … PUMP HO… Human 2001 1/1/01 0:…
## 2 1 FWS 81682 USCADBR San Diego Bay … I5 Human 2002 5/3/02 0:…
## 3 2 FWS 81682 USCADBR San Diego Bay … SOUTHBAY Human 2002 6/1/02 0:…
## 4 3 FWS 81682 USCADBR San Diego Bay … MARINA Human 2001 7/12/01 0…
## 5 4 FWS 81682 USCADBR San Diego Bay … HILL Human 1994 9/13/94 0…
## 6 5 FWS 81682 USCADBR San Diego Bay … IRRIGAT… Human 1994 4/22/94 0…
## # ℹ 5 more variables: CONTRDATED <chr>, OUTDATED <chr>, STATE <chr>,
## # STATE_FIPS <dbl>, TOTALACRES <dbl>
- En esta sección se estudiaron varias funciones para cargar un archivo de datos externo incluyendo las funciones incorporadas en R read.table() y read.csv(). Aunque estas funciones funciones pueden hacer el trabajo, la función read_csv() que se encuentra en el paquete readr es una función mucho más eficiente para cargar datos externos. En la siguiente sección se aprenderá a transformar datasets utilizando el paquete dplyr. Se estudiarán técnicas para filtrar el contenido de un dataframe, seleccionar las columnas específicas que se utilizarán, organizar las filas en orden ascendente o descendente y resumir y agrupar un dataset.
Transformación de datos
Antes de poder analizar un conjunto de datos en R, a menudo es necesario manipularlo o transformarlo de diversas maneras. El paquete dplyr, que forma parte del paquete más amplio tidyverse, proporciona un conjunto de funciones que permiten transformar un conjunto de datos de diversas maneras.
En esta sección trataremos los siguientes temas:
- Filtrar registros para crear un subconjunto
- Limitar la lista de columnas
- Organizar las filas en orden ascendente o descendente
- Añadir filas
- Resumir y agrupar
- Canalización para la eficiencia del código
Filtrar registros para crear un subconjunto
La primera función de dplyr que examinaremos es filter(). La función filter() se utiliza para crear un subconjunto de registros basado en algún valor. Por ejemplo, puede querer crear un dataframe de incendios forestales que contenga incidentes que hayan quemado más de 25.000 acres. Siempre que tenga un dataframe existente que incluya una columna que mida el número de acres quemadas, puede crear este subconjunto utilizando la función filter().
Como será el caso de todas las funciones de dplyr que examinemos, el primer argumento que se pasa a la función filter() es un objeto de dataframe. Cada parámetro adicional pasado a la función es una expresión condicional utilizada para filtrar el dataframe.
Por ejemplo, observe la siguiente línea de código. Esta declaración llama a la función filter() para crear una nueva variable llamada df25k, que contendrá sólo las filas en las que la columna ACRES contenga un valor superior a 25000.
- En el siguiente ejemplo de código, se pasan dos expresiones condicionales. La primera se utiliza para filtrar los registros de manera que el número de ACRES sea mayor mayor o igual a 25.000, y la segunda filtra los registros para los que la columna YEAR contenga un valor de 2016.
- En este caso, la variable df25k incluirá los registros en los que coincidan ambas condiciones: la superficie quemada es superior a 25000 y el año del incendio fue 2016. Este también puede reescribirse como un único parámetro que utiliza el operador & para combinar como se ve a continuación.
En este ejercicio aprenderá a utilizar la función filter() para crear un subconjunto de registros basado en algún valor. Los ejercicios de este capítulo requieren los siguientes paquetes: readr, dplyr, ggplot2. Pueden cargarse desde el panel de paquetes, el panel de la consola o un script. Si es necesario, establezca el directorio de trabajo escribiendo el código: setwd(
) .El repositorio RDataSets contiene un archivo llamado StudyArea.csv, que es un archivo separado por comas que contiene datos de incendios forestales de los años 1980-2016 para los estados de California, Oregón, Washington, Idaho, Montana, Wyoming, Colorado, Utah, Nevada, Arizona y Nuevo México. Hay algo más de 439.000 registros en este archivo y hay 37 columnas de información que describen cada incendio durante este periodo.
Utilice la función read_csv() para cargar el conjunto de datos en un dataframe.
dfFires = read_csv("C:/Data/StudyArea.csv", col_types = list(UNIT = col_character()), col_names = TRUE)- Utilice la función nrow() para asegurarse de que los aproximadamente 439.000 registros se hayan cargado.
## [1] 439362
- Inicialmente utilizaremos una única expresión condicional con la función filter() para crear un subconjunto de registros que contenga sólo los incendios forestales de más de 25.000 acres.
- Obtenga un recuento del número de registros que coinciden con el filtro. Debería haber 655 filas. También puede utilizar la función View(df25k) para ver los datos en un formato tabular.
## [1] 655
## # A tibble: 6 × 14
## FID ORGANIZATI UNIT SUBUNIT SUBUNIT2 FIRENAME CAUSE YEAR_ STARTDATED
## <dbl> <chr> <chr> <chr> <chr> <chr> <chr> <dbl> <chr>
## 1 1875 FWS 22570 USAZKGR Kofa National … KING VA… Human 2005 10/1/05 0…
## 2 1968 FWS 81674 USCAHPR Hopper Mountai… PIRU Human 2003 10/23/03 …
## 3 2476 FWS 81720 USCATNR San Diego Nati… CEDAR Human 2003 10/25/03 …
## 4 2477 FWS 81720 USCATNR San Diego Nati… MINE Human 2003 10/26/03 …
## 5 6929 FWS 61520 USMTCMR Charles M. Rus… SODA CR… Natu… 2006 7/16/06 0…
## 6 9161 FWS 14621 USORSLR Sheldon Nation… BADGER … Natu… 1999 8/24/99 0…
## # ℹ 5 more variables: CONTRDATED <chr>, OUTDATED <chr>, STATE <chr>,
## # STATE_FIPS <dbl>, TOTALACRES <dbl>
- También puede incluir múltiples expresiones condicionales como parte del filtro.
## [1] 152
- También puede combinar las expresiones en una sola expresión con varias condiciones, como se ve a continuación. El carácter & es el operador “y”. Tendrías que usar el carácter | para incluir un operador “o”.
- Por último, cuando se tiene una lista de valores potenciales que se desea incluir por el filtro se puede utilizar la sentencia %in%. Esta línea de código en particular crearía un dataframe que contiene los incendios ocurridos en los años 2010, 2011 o 2012.
## # A tibble: 6 × 14
## FID ORGANIZATI UNIT SUBUNIT SUBUNIT2 FIRENAME CAUSE YEAR_ STARTDATED
## <dbl> <chr> <chr> <chr> <chr> <chr> <chr> <dbl> <chr>
## 1 12831 FWS 81627 USCASRR Sacramento Riv… SWR-SR-… Human 2010 5/10/10 0…
## 2 12839 FWS 84555 USNVDSR Desert Nationa… MORMON … Human 2010 6/28/10 0…
## 3 12840 FWS 13561 USWALPR Little Pend Or… WINSLOW… Natu… 2010 7/31/10 0…
## 4 12841 FWS 13700 USWAHFR Hanford Reach … SADDLE … Natu… 2010 7/31/10 0…
## 5 12847 FWS 84555 USNVDSR Desert Nationa… DEAD HO… Natu… 2010 7/25/10 0…
## 6 12921 FWS 22530 USAZBAR Buenos Aires N… M M 14.… Unde… 2010 11/2/10 0…
## # ℹ 5 more variables: CONTRDATED <chr>, OUTDATED <chr>, STATE <chr>,
## # STATE_FIPS <dbl>, TOTALACRES <dbl>
Acotar la lista de columnas con select()
El archivo StudyArea.csv con el que ha estado trabajando en los ejercicios incluye 37 columnas de información. En la mayoría de los casos no necesitará todas las columnas. La función select() puede utilizarse para reducir la lista de columnas para incluir sólo las necesarias para una tarea.
Utilice la función read_csv() para cargar el conjunto de datos en un dataframe. Nota: En aras de la exhaustividad, se cargarán los datos externos del archivo StudyArea.csv en el dataframe dfFires, pero este paso no es absolutamente necesario si está haciendo los ejercicios en secuencia en la misma sesión de R Studio.
dfFires = read_csv("C:/Data/StudyArea.csv", col_types = list(UNIT = col_character()), col_names = TRUE)- En una nueva línea, añada una llamada a la función select() como se ve a continuación para limitar las columnas que se devuelven.
- Visualice las primeras filas y observe que ahora sólo tenemos tres columnas.
## # A tibble: 6 × 3
## FIRENAME TOTALACRES YEAR_
## <chr> <dbl> <dbl>
## 1 PUMP HOUSE 0.1 2001
## 2 I5 3 2002
## 3 SOUTHBAY 0.5 2002
## 4 MARINA 0.1 2001
## 5 HILL 1 1994
## 6 IRRIGATION 0.1 1994
- Muchos de los nombres de las columnas que importes no serán muy fáciles de leer, por lo que no es raro que quiera renombrar las columnas también. Esto puede lograrse usando la función select().
dfFires2 = select(dfFires, "FIRE" = "FIRENAME", "ACRES" ="TOTALACRES", "YR" = "YEAR_")
head(dfFires2)## # A tibble: 6 × 3
## FIRE ACRES YR
## <chr> <dbl> <dbl>
## 1 PUMP HOUSE 0.1 2001
## 2 I5 3 2002
## 3 SOUTHBAY 0.5 2002
## 4 MARINA 0.1 2001
## 5 HILL 1 1994
## 6 IRRIGATION 0.1 1994
- También hay una serie de útiles funciones de ayuda que puede utilizar con la función select() para filtrar las columnas devueltas. Estas incluyen starts_with(), ends_with(), contains(), matches() y num_range().
## # A tibble: 6 × 3
## STARTDATED CONTRDATED OUTDATED
## <chr> <chr> <chr>
## 1 1/1/01 0:00 1/1/01 0:00 <NA>
## 2 5/3/02 0:00 5/3/02 0:00 <NA>
## 3 6/1/02 0:00 6/1/02 0:00 <NA>
## 4 7/12/01 0:00 7/12/01 0:00 <NA>
## 5 9/13/94 0:00 9/13/94 0:00 <NA>
## 6 4/22/94 0:00 4/22/94 0:00 <NA>
- También puede hacer varias llamadas a estas funciones de ayuda
## # A tibble: 6 × 4
## STARTDATED CONTRDATED OUTDATED TOTALACRES
## <chr> <chr> <chr> <dbl>
## 1 1/1/01 0:00 1/1/01 0:00 <NA> 0.1
## 2 5/3/02 0:00 5/3/02 0:00 <NA> 3
## 3 6/1/02 0:00 6/1/02 0:00 <NA> 0.5
## 4 7/12/01 0:00 7/12/01 0:00 <NA> 0.1
## 5 9/13/94 0:00 9/13/94 0:00 <NA> 1
## 6 4/22/94 0:00 4/22/94 0:00 <NA> 0.1
Organizar las filas
La función arrange() del paquete dplyr puede utilizarse para ordenar las filas de un dataframe. Esta función acepta un conjunto de columnas para ordenarlas, siendo el orden por defecto ordenación de las filas en orden ascendente. Sin embargo, puede pasar la función desc() helper para ordenar las filas en orden descendente.
Utilice la función read_csv() para cargar el conjunto de datos en un dataframe.
dfFires = read_csv("C:/Data/StudyArea.csv", col_types = list(UNIT = col_character()), col_names = TRUE)- Filtrar el conjunto de datos para que contenga solo los incendios de más de 1.000 acres quemados del año 2016.
- Añade y ejecuta el código que ves a continuación para crear un subconjunto de columnas y renombrarlas
- Ordena las filas para que estén en orden ascendente
## # A tibble: 152 × 3
## NAME ACRES YR
## <chr> <dbl> <dbl>
## 1 Crackerbox 1000 2016
## 2 Lakes 1000 2016
## 3 Choulic 2 1008 2016
## 4 Amigo Wash 1020. 2016
## 5 Granite 1030 2016
## 6 Tie 1031 2016
## 7 Black 1040 2016
## 8 Bybee Creek 1072 2016
## 9 MARSHES 1080 2016
## 10 Bug Creek 1089 2016
## # ℹ 142 more rows
- Utilice la función de ayuda
desc()para ordenar las filas en orden descendente.
## # A tibble: 152 × 3
## NAME ACRES YR
## <chr> <dbl> <dbl>
## 1 PIONEER 188404 2016
## 2 Junkins 181320 2016
## 3 Range 12 171915 2016
## 4 Erskine 48007 2016
## 5 Cedar 45977 2016
## 6 Maple 45425 2016
## 7 Rail 43799 2016
## 8 North Fire 42102 2016
## 9 Laidlaw 39813 2016
## 10 BLUE CUT 36274 2016
## # ℹ 142 more rows
Añadir filas con mutate()
La función mutate() se utiliza para añadir nuevas columnas a un dataframe que son resultado de operaciones entre otras columnas del dataframe. Las nuevas columnas creadas con la función mutate() se añadirán al final del dataframe. Para este ejercicio usará el paquete lubridate. El paquete lubridate forma parte de tidyverse y se utiliza para trabajar con fechas y horas.
Utilice la función read_csv() para cargar el conjunto de datos en un dataframe
dfFires = read_csv("C:/Data/StudyArea.csv",
col_types = list(UNIT = col_character()),
col_names = TRUE)- Utilice la función select() para definir un conjunto de columnas para el dataframe.
- Realice un filtrado básico de los datos para que sólo se incluyan los incendios de más de 1.000 acres quemados y que tengan una causa humana o natural
- Utilice la función mutate() para crear una nueva columna DOY que contenga el día del año en que se inició el incendio. La función yday() del paquete lubridate se utiliza para devolver el día del año utilizando una fecha de entrada formateada de la columna STARTDATED.
| ORGANIZATI | STATE | YEAR_ | TOTALACRES | CAUSE | STARTDATED | DOY |
|---|---|---|---|---|---|---|
| FWS | Arizona | 1988 | 1500 | Human | 3/26/88 0:00 | 86 |
| FWS | Arizona | 1986 | 10390 | Human | 5/15/86 0:00 | 135 |
| FWS | Montana | 1986 | 1400 | Human | 6/27/86 0:00 | 178 |
| FWS | Arizona | 2002 | 1035 | Human | 2/28/02 0:00 | 59 |
| FWS | Arizona | 2000 | 5700 | Human | 4/9/00 0:00 | 100 |
| FWS | Arizona | 2000 | 2750 | Human | 5/14/00 0:00 | 135 |
Resumir y agrupar
Las estadísticas de resumen de un dataframe pueden producirse con la función summarize(). La agrupación de datos en un dataframe facilita el paradigma de dividir-aplicar-combinar. Este paradigma primero divide los datos en grupos, utilizando la función group_by() en dplyr, luego aplica el análisis al grupo y, finalmente, combina los resultados.
En este ejercicio utilizará las funciones mutate(), summarize() y group_by() para agrupar los incendios forestales por década y producir un resumen del tamaño medio de los incendios forestales para cada década.
Utilice la función read_csv() para cargar el conjunto de datos en un dataframe.
dfFires = read_csv("C:/Data/StudyArea.csv",
col_types = list(UNIT = col_character()),
col_names = TRUE)- Seleccione las columnas que se utilizarán en el ejercicio.
- Filtrar los registros
- Utilice la función mutate() para crear una nueva columna llamada DECADE que define la década en la que se produjo cada incendio. En este caso se llama a la función ifelse() para producir los valores de cada década.
df <- mutate(df, DECADE = ifelse(YEAR_ %in% 1980:1989, "1980-1989",
ifelse(YEAR_ %in% 1990:1999, "1990-1999",
ifelse(YEAR_ %in% 2000:2009, "2000-2009",
ifelse(YEAR_ %in% 2010:2016, "2010-2016", "-99")))))
knitr::kable(head(df))| ORGANIZATI | STATE | YEAR_ | TOTALACRES | CAUSE | DECADE |
|---|---|---|---|---|---|
| FWS | Arizona | 1988 | 1500 | Human | 1980-1989 |
| FWS | Arizona | 1986 | 10390 | Human | 1980-1989 |
| FWS | Montana | 1986 | 1400 | Human | 1980-1989 |
| FWS | Arizona | 2002 | 1035 | Human | 2000-2009 |
| FWS | Arizona | 2000 | 5700 | Human | 2000-2009 |
| FWS | Arizona | 2000 | 2750 | Human | 2000-2009 |
- Utilice la función group_by() para agrupar el dataframe por década.
- Resuma el tamaño medio de los incendios forestales por década utilizando la función summarize()
| DECADE | mean(TOTALACRES) |
|---|---|
| 1980-1989 | 8128.645 |
| 1990-1999 | 8333.036 |
| 2000-2009 | 12329.181 |
| 2010-2016 | 14443.197 |
- Pongamos en orden las cosas cambiando el nombre de la nueva columna producida por la función summarize()
- Por último, vamos a crear un gráfico de barras con los resultados. Discutiremos la creación de diferentes tipos de tablas y gráficos a medida que avancemos en los capítulos posteriores
library(ggplot2)
ggplot(data=sm) +
geom_col(mapping = aes(x=DECADE, y=MEAN_ACRES_BURNED), fill="red")Tuberías
- La canalización es una forma eficiente de enviar la salida de una función a otra función sin crear un conjunto de datos intermedio y es más útil cuando se tiene una serie de funciones para ejecutar. La sintaxis para la canalización consiste en utilizar los caracteres %>% al final de cada sentencia que desee canalizar. En este ejercicio aprenderá a utilizar la canalización para encadenar dataframe de entrada y salida. Resolveremos el ejercicio anterior usando las tuberias.
library(lubridate)
df = read_csv("C:/Data/StudyArea.csv", col_types = list(UNIT = col_character()), col_names = TRUE) %>%
select(ORGANIZATI, STATE, YEAR_, TOTALACRES, CAUSE, STARTDATED) %>%
filter(TOTALACRES >= 1000 & CAUSE %in% c("Human", "Natural")) %>%
mutate(DOY = yday(as.Date(STARTDATED, format="%m/%d/%y %H:%M")))
knitr::kable(head(df))| ORGANIZATI | STATE | YEAR_ | TOTALACRES | CAUSE | STARTDATED | DOY |
|---|---|---|---|---|---|---|
| FWS | Arizona | 1988 | 1500 | Human | 3/26/88 0:00 | 86 |
| FWS | Arizona | 1986 | 10390 | Human | 5/15/86 0:00 | 135 |
| FWS | Montana | 1986 | 1400 | Human | 6/27/86 0:00 | 178 |
| FWS | Arizona | 2002 | 1035 | Human | 2/28/02 0:00 | 59 |
| FWS | Arizona | 2000 | 5700 | Human | 4/9/00 0:00 | 100 |
| FWS | Arizona | 2000 | 2750 | Human | 5/14/00 0:00 | 135 |
La primera línea de código lee el contenido del archivo externo StudyArea.csv en una variable de dataframe df como hemos hecho en todos los demás ejercicios de este capítulo. Sin embargo, observará la inclusión de la sentencia de canalización (%>%>) al final de la línea. Esto asegura que el contenido de la variable df será automáticamente automáticamente a la función select().
Observe que la función select() no crea una variable como hemos hecho en ejercicios anteriores, y que hemos omitido el primer parámetro, que normalmente habría sido la variable del dataframe. Se da a entender que la variable df será pasada a la función select().
Este mismo proceso de incluir la sentencia piping al final de cada línea y dejando fuera el primer parámetro se repite para todas las líneas de código adicionales en las que queramos pasar automáticamente la variable df a la siguiente función de dplyr.
En esta sección se utilizó el paquete dplyr para realizar varias funciones de transformación de datos. Se aprendió a limitar columnas con la función
select()filtrar un marco de datos basado en una o más expresiones, añadir columnas conmutate(), y a resumir y agrupar datos. Por último, ha aprendido a utilizar la canalización para hacer su código más eficiente. En el próximo capítulo se aprenderá a crear conjuntos de datos ordenados con el paquetetidyr.
Ejercicio para entregar
Crear un nuevo dataframe que sea un subconjunto del dataframe original de dfFires. El subconjunto debe contener todos los incendios del Estado de Idaho y las columnas deben ser limitadas para que sólo estén presentes las columnas YEAR_, CAUSE y TOTALACRES. Cambie el nombre de las columnas. Agrupe los datos por CAUSE y YEAR_ y luego resuma por el total de acres quemados. Trazar los resultados.
Resuelva la Tarea 1.1 de la Sección 1 de
PythonutilizandoR.
Creación de datos ordenados
La ordenación de datos es una forma coherente de organizar los datos en R y puede facilitarse a través del paquete
tidyrque se encuentra en el ecosistema ecosistematidyverse. Hay tres reglas que podemos seguir para hacer un conjunto de datos ordenado.En primer lugar, cada variable debe tener su propia columna. En segundo lugar, cada observación debe tener su propia fila y, por último, cada valor debe tener su propia celda. Esto se ilustrado por el diagrama siguiente.
En primer lugar, tener una estructura de datos consistente es muy importante. Los paquetes que forman parte de
tidyverseincluyendodplyryggplot2están diseñados para trabajar con datos ordenados, así que asegurar que que tus datos sean uniformes facilita el procesamiento eficiente de tus datos. Además, colocar las variables en columnas permite facilitar la vectorización en R.
Muchos de los conjuntos de datos que encuentre no estarán ordenados y requerirán algo de trabajo por su parte. Puede haber muchas razones por las que un conjunto de datos no esté ordenado. A menudo, las personas que crearon el conjunto de datos no están familiarizadas con los principios de los datos ordenados.
Otra razón común por la que los conjuntos de datos no están ordenados es que los datos se organizan a menudo para facilitar algo más que el análisis. Para que la introducción de datos sea lo más fácil posible en ocasiones se suelen organizar los datos de forma poco ordenada. Así, muchos conjuntos de datos requieren algún tipo de ordenación antes de poder empezar el análisis.
El primer paso es averiguar cuáles son las variables y observaciones del conjunto de datos. Esto le facilitará la comprensión de lo que deben ser las columnas y las. Además, también tendrá que resolver uno o dos problemas comunes. Tendrá que averiguar si una variable está repartida en varias columnas, y si una observación está dispersa en varias filas. Estos conceptos se conocen como reunión y dispersión. Examinaremos examinaremos más a fondo estos conceptos en los ejercicios de este capítulo.
En este capítulo trataremos los siguientes temas:
- Recopilación
- Distribución
- Separación
- Unión
Recopilación
- Un problema común en muchos conjuntos de datos es que los
nombres de las columnas no son variables sino valores de una
variable. En la figura siguiente, las columnas 1999 y 2000
son en realidad valores de la variable
YEAR. Cada fila de la tabla existente representa en realidad dos observaciones. El paquetetidyrpuede utilizarse para reunir estas columnas existentes en una nueva variable. En este caso, tenemos que crear una nueva columna columna llamadaYEARy luego reunir en esta los valores existentes en las columnas 1999 y 2000.
- La función
gather()del paquetetidyrpuede utilizarse para realizar la recopilación de datos.
- Hay tres parámetros de la función
gather(). El primero es el conjunto de columnas que representan lo que deberían ser valores y no variables. Estas serían las columnas 1999 y 2000 en el ejemplo que hemos seguido. - A continuación, tendrá que nombrar la variable de la
nueva columna. Esto también se llama la clave
key, y en este caso será la variable del año. Por último, tendrás que proporcionar el valorvalue, que es el nombre de la variable cuyos valores se reparten por las celdas.
- En este ejercicio aprenderás a utilizar la función
gather()para resolver este tipo de problemas:
- Descargue el archivo CountryPopulation.csv localizado en RDataSets. Abra este archivo, por ejemplo en Microsoft Excel, o algún otro tipo de software de hoja de cálculo. El archivo debería tener un aspecto similar al de la pantalla que se muestra a continuación. Esta hoja de cálculo incluye el año 2017.
- Las columnas de cada año representan valores, no
variables. Estas columnas deben reunirse en un
nuevo par de variables que representen el año y la
población. En este ejercicio utilizarás la función
gather()para realizar esta tarea de ordenación de datos.
- Si es necesario, cargue los paquetes
readrytidyrhaciendo clic en las casillas de verificación en el panel de paquetes o incluyendo la siguiente línea de código.
- Cargue el archivo CountryPopulation.csv en RStudio escribiendo el código que ve abajo en el panel de la consola
## spc_tbl_ [264 × 10] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
## $ Country Name: chr [1:264] "Aruba" "Afghanistan" "Angola" "Albania" ...
## $ Country Code: chr [1:264] "ABW" "AFG" "AGO" "ALB" ...
## $ 2010 : num [1:264] 101669 28803167 23369131 2913021 84449 ...
## $ 2011 : num [1:264] 102053 29708599 24218565 2905195 83751 ...
## $ 2012 : num [1:264] 102577 30696958 25096150 2900401 82431 ...
## $ 2013 : num [1:264] 103187 31731688 25998340 2895092 80788 ...
## $ 2014 : num [1:264] 103795 32758020 26920466 2889104 79223 ...
## $ 2015 : num [1:264] 104341 33736494 27859305 2880703 78014 ...
## $ 2016 : num [1:264] 104822 34656032 28813463 2876101 77281 ...
## $ 2017 : num [1:264] 105264 35530081 29784193 2873457 76965 ...
## - attr(*, "spec")=
## .. cols(
## .. `Country Name` = col_character(),
## .. `Country Code` = col_character(),
## .. `2010` = col_double(),
## .. `2011` = col_double(),
## .. `2012` = col_double(),
## .. `2013` = col_double(),
## .. `2014` = col_double(),
## .. `2015` = col_double(),
## .. `2016` = col_double(),
## .. `2017` = col_double()
## .. )
## - attr(*, "problems")=<externalptr>
- Utilice la función
View()para mostrar los datos en una estructura tabular.
- Utilice la función
gather()como se ve a continuación
dfPop2 = gather(dfPop,
"2010", "2011", "2012", "2013", "2014", "2015", "2016", "2017",
key = "YEAR",
value = "POPULATION")
knitr::kable(head(dfPop2, 10))| Country Name | Country Code | YEAR | POPULATION |
|---|---|---|---|
| Aruba | ABW | 2010 | 101669 |
| Afghanistan | AFG | 2010 | 28803167 |
| Angola | AGO | 2010 | 23369131 |
| Albania | ALB | 2010 | 2913021 |
| Andorra | AND | 2010 | 84449 |
| Arab World | ARB | 2010 | 356508908 |
| United Arab Emirates | ARE | 2010 | 8270684 |
| Argentina | ARG | 2010 | 41223889 |
| Armenia | ARM | 2010 | 2877311 |
| American Samoa | ASM | 2010 | 55637 |
- Otra opción también sería la siguiente, cuando contamos con un gran número de columnas de este tipo
years <- colnames(dfPop)[grep("^\\d{4}$", colnames(dfPop))]
dfPop2 <- dfPop %>%
gather(key = "YEAR", value = "POPULATION", all_of(years))
knitr::kable(head(dfPop2, 10))| Country Name | Country Code | YEAR | POPULATION |
|---|---|---|---|
| Aruba | ABW | 2010 | 101669 |
| Afghanistan | AFG | 2010 | 28803167 |
| Angola | AGO | 2010 | 23369131 |
| Albania | ALB | 2010 | 2913021 |
| Andorra | AND | 2010 | 84449 |
| Arab World | ARB | 2010 | 356508908 |
| United Arab Emirates | ARE | 2010 | 8270684 |
| Argentina | ARG | 2010 | 41223889 |
| Armenia | ARM | 2010 | 2877311 |
| American Samoa | ASM | 2010 | 55637 |
Donde, en expresiones regulares,
"^\\d{4}$"tiene el siguiente significado:^: Representa el inicio de una cadena.\\d: Representa un dígito.{4}: Indica que el elemento anterior (\\d, en este caso) debe aparecer exactamente 4 veces.$: Representa el final de una cadena.
En resumen,
"^\\d{4}$"coincide con cualquier cadena que contenga exactamente 4 dígitos y no contenga ningún otro carácter adicional antes o después de los dígitos.
Distribución
La distribución es lo contrario de la reunión y se utiliza cuando una observación se distribuye en varias filas. En el diagrama siguiente, la tabla debe definir una observación de un país por año. Sin embargo, observará que está repartida en dos filas. Una fila para
casesy otra parapopulation.Podemos utilizar la función
spread()para solucionar este problema. La funciónspread()toma dos parámetros: la columna que contiene los nombres de las variables repetidas, conocida comokeyy una columna que contiene valores de múltiples variables, que llamamosvalue
- Instale el paquete
devtoolsy los conjuntos de datos DSR utilizando el código que ve a continuación escribiendo en el panel de la consola. Alternativamente, puede utilizar el panel de paquetes para instalar los librerías
| country | year | type | count |
|---|---|---|---|
| Afghanistan | 1999 | cases | 745 |
| Afghanistan | 1999 | population | 19987071 |
| Afghanistan | 2000 | cases | 2666 |
| Afghanistan | 2000 | population | 20595360 |
| Brazil | 1999 | cases | 37737 |
| Brazil | 1999 | population | 172006362 |
- Utilice la función
spread()para corregir este problema.
| country | year | cases | population |
|---|---|---|---|
| Afghanistan | 1999 | 745 | 19987071 |
| Afghanistan | 2000 | 2666 | 20595360 |
| Brazil | 1999 | 37737 | 172006362 |
| Brazil | 2000 | 80488 | 174504898 |
| China | 1999 | 212258 | 1272915272 |
| China | 2000 | 213766 | 1280428583 |
Separación
- Otro caso común es el de dos variables que se colocan en la misma columna. Por ejemplo, la hoja de cálculo siguiente tiene una columna State-County Name que en realidad contiene dos variables separadas por una barra.
- La función
separate()puede utilizarse para dividir una columna en varias columnas dividiendo por un separador. Por defecto, la funciónseparate()buscará automáticamente cualquier carácter no alfanumérico o se puede definir un carácter específico (ver separate()).
- En la carpeta de RDataSets hay un archivo llamado usco2005.csv. Abra este archivo, por ejemplo con Microsoft Excel, o algún otro tipo de software de hoja de cálculo. El archivo debería tener un aspecto similar al de la captura de pantalla de abajo.
- Cargue el archivo usco2005.csv en RStudio escribiendo el código que ve a continuación en el panel de la consola
| STATE | STATEFIPS | COUNTYFIPS | FIPS | State-County Name | TP-TotPop |
|---|---|---|---|---|---|
| AL | 1 | 1 | 1001 | Alabama-Autauga County | 48.612 |
| AL | 1 | 3 | 1003 | Alabama-Baldwin County | 162.586 |
| AL | 1 | 5 | 1005 | Alabama-Barbour County | 28.414 |
| AL | 1 | 7 | 1007 | Alabama-Bibb County | 21.516 |
| AL | 1 | 9 | 1009 | Alabama-Blount County | 55.725 |
| AL | 1 | 11 | 1011 | Alabama-Bullock County | 11.055 |
| AL | 1 | 13 | 1013 | Alabama-Butler County | 20.766 |
| AL | 1 | 15 | 1015 | Alabama-Calhoun County | 112.141 |
| AL | 1 | 17 | 1017 | Alabama-Chambers County | 35.460 |
| AL | 1 | 19 | 1019 | Alabama-Cherokee County | 24.522 |
- Utilice la función
separate()para separar el contenido de la columna State-County Name en las columnas StateAbbrev y CountyName
df2 = separate(df, "State-County Name",into = c("StateAbbrev", "CountyName"))
knitr::kable(head(df2, 10))| STATE | STATEFIPS | COUNTYFIPS | FIPS | StateAbbrev | CountyName | TP-TotPop |
|---|---|---|---|---|---|---|
| AL | 1 | 1 | 1001 | Alabama | Autauga | 48.612 |
| AL | 1 | 3 | 1003 | Alabama | Baldwin | 162.586 |
| AL | 1 | 5 | 1005 | Alabama | Barbour | 28.414 |
| AL | 1 | 7 | 1007 | Alabama | Bibb | 21.516 |
| AL | 1 | 9 | 1009 | Alabama | Blount | 55.725 |
| AL | 1 | 11 | 1011 | Alabama | Bullock | 11.055 |
| AL | 1 | 13 | 1013 | Alabama | Butler | 20.766 |
| AL | 1 | 15 | 1015 | Alabama | Calhoun | 112.141 |
| AL | 1 | 17 | 1017 | Alabama | Chambers | 35.460 |
| AL | 1 | 19 | 1019 | Alabama | Cherokee | 24.522 |
Unión
La función
unite()es exactamente lo contrario deseparate(), ya que combina varias columnas en una sola. Aunque no se utiliza tan a menudo comoseparate(), puede haber ocasiones en las que necesite la funcionalidad proporcionada porunite(). En este ejercicio unirás el marco de datos que fue separado en el último ejercicio.En el panel de la consola, añada el código que ve a continuación para unir las columnas StateAbbrev y CountyName en una sola columna. Por defecto
unite()realiza unión con base en la expresión regular"_","unite(data, col, ..., sep = "_", remove = TRUE)"
| STATE | STATEFIPS | COUNTYFIPS | FIPS | State_County_Name | TP-TotPop |
|---|---|---|---|---|---|
| AL | 1 | 1 | 1001 | Alabama_Autauga | 48.612 |
| AL | 1 | 3 | 1003 | Alabama_Baldwin | 162.586 |
| AL | 1 | 5 | 1005 | Alabama_Barbour | 28.414 |
| AL | 1 | 7 | 1007 | Alabama_Bibb | 21.516 |
| AL | 1 | 9 | 1009 | Alabama_Blount | 55.725 |
| AL | 1 | 11 | 1011 | Alabama_Bullock | 11.055 |
| AL | 1 | 13 | 1013 | Alabama_Butler | 20.766 |
| AL | 1 | 15 | 1015 | Alabama_Calhoun | 112.141 |
| AL | 1 | 17 | 1017 | Alabama_Chambers | 35.460 |
| AL | 1 | 19 | 1019 | Alabama_Cherokee | 24.522 |
Conclusión
- En este capítulo se ha presentado el paquete
tidyry su conjunto de funciones para crear conjuntos de datos ordenados. El próximo capítulo le enseñará los fundamentos de la exploración de datos datos utilizando R ytidyverse.
Ejercicio para entregar
- Considere el conjunto de datos us_state_population.tsv
utilizado en la sección de Python, para la creación del mapa coroplético
de Estados Unidos. Repita el procedimiento planteado en cada ítem de
esta sección para obtener el nuevo dataframe con las nuevas columnas
YearyPopulation. Realice unión y separación utilizando las columnasStateyCode.
Técnicas básicas de exploración y visualización de datos en R
Técnicas básicas de exploración
El Análisis Exploratorio de Datos (EDA) es un flujo de trabajo diseñado para obtener una mejor comprensión de sus datos. El flujo de trabajo consta de tres pasos.
- El primero consiste en generar preguntas sobre los datos. En este paso se quiere ser lo más amplio posible, ya que en este momento no se tiene una buena idea de los datos.
- A continuación, se buscan las respuestas a estas preguntas mediante la visualización, la transformación y el modelado de los datos.
- Por último, refine sus preguntas o genere nuevas preguntas.
En R hay dos herramientas principales que apoyan el proceso de exploración de datos: los gráficos y los estadísticos de resumen. Los datos pueden dividirse generalmente en tipos categóricos o continuos.
Las variables categóricas consisten en un pequeño conjunto de valores, mientras que las continuas tienen un un conjunto potencialmente infinito de valores ordenados.
Las variables categóricas suelen visualizarse con gráficos de barras, y las continuas con histogramas. Tanto datos categóricos y continuos pueden representarse a través de varios gráficos creados con R.
Al realizar la visualización básica de las variables, tendemos a medir la variación o la covariación. La variación es la tendencia de los valores de una variable a cambiar de una medición a otra. La covariación es la tendencia de los valores de dos o más variables a variar de una manera relacionada.
- Medir la variación categórica con un gráfico de barras
- Medición de la variación continua con un histograma
- Medición de la covariación con gráficos de caja
- Medición de la covariación con el tamaño de los símbolos
- Creación de gráficos 2D y hexadecimales
- Generación de estadísticas de resumen
Medición de la variación categórica con un gráfico de barras
- Un gráfico de barras es una buena manera de visualizar datos categóricos. Se separa cada categoría en una barra separada y la altura de cada barra se define por el número de ocurrencias en esa categoría.
Los ejercicios de esta sección requieren los siguientes paquetes:
readr, dplyr, ggplot2. Pueden cargarse desde el panel de paquetes, el panel de la consola o un scriptUtilice la función
read_csv()para cargar el conjunto de datos en un marco dataframe.
library(dplyr)
library(readr)
library(ggplot2)
dfFires <- read_csv("C:/Data/StudyArea.csv", col_types = list(UNIT = col_character()), col_names = TRUE)
str(dfFires)## spc_tbl_ [439,362 × 14] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
## $ FID : num [1:439362] 0 1 2 3 4 5 6 18 20 21 ...
## $ ORGANIZATI: chr [1:439362] "FWS" "FWS" "FWS" "FWS" ...
## $ UNIT : chr [1:439362] "81682" "81682" "81682" "81682" ...
## $ SUBUNIT : chr [1:439362] "USCADBR" "USCADBR" "USCADBR" "USCADBR" ...
## $ SUBUNIT2 : chr [1:439362] "San Diego Bay National Wildlife Refuge" "San Diego Bay National Wildlife Refuge" "San Diego Bay National Wildlife Refuge" "San Diego Bay National Wildlife Refuge" ...
## $ FIRENAME : chr [1:439362] "PUMP HOUSE" "I5" "SOUTHBAY" "MARINA" ...
## $ CAUSE : chr [1:439362] "Human" "Human" "Human" "Human" ...
## $ YEAR_ : num [1:439362] 2001 2002 2002 2001 1994 ...
## $ STARTDATED: chr [1:439362] "1/1/01 0:00" "5/3/02 0:00" "6/1/02 0:00" "7/12/01 0:00" ...
## $ CONTRDATED: chr [1:439362] "1/1/01 0:00" "5/3/02 0:00" "6/1/02 0:00" "7/12/01 0:00" ...
## $ OUTDATED : chr [1:439362] NA NA NA NA ...
## $ STATE : chr [1:439362] "California" "California" "California" "California" ...
## $ STATE_FIPS: num [1:439362] 6 6 6 6 6 6 6 6 6 6 ...
## $ TOTALACRES: num [1:439362] 0.1 3 0.5 0.1 1 0.1 3 0.1 0.1 0.1 ...
## - attr(*, "spec")=
## .. cols(
## .. FID = col_double(),
## .. ORGANIZATI = col_character(),
## .. UNIT = col_character(),
## .. SUBUNIT = col_character(),
## .. SUBUNIT2 = col_character(),
## .. FIRENAME = col_character(),
## .. CAUSE = col_character(),
## .. YEAR_ = col_double(),
## .. STARTDATED = col_character(),
## .. CONTRDATED = col_character(),
## .. OUTDATED = col_character(),
## .. STATE = col_character(),
## .. STATE_FIPS = col_double(),
## .. TOTALACRES = col_double()
## .. )
## - attr(*, "problems")=<externalptr>
- Para este análisis, filtraremos los datos de modo que solo estén representados los incendios que quemaron más de 1.000 acres (4046.86 metros cuadrados o 0.4047 hectáreas) en los años 2010 a 2016. Añada el código que que se ve a continuación para filtrar los datos y enviar los resultados a un gráfico de barras.
df <- filter(dfFires, TOTALACRES >= 1000,
YEAR_ %in% c(2010, 2011, 2012, 2013, 2014, 2015, 2016))
ggplot(data = df) +
geom_bar(mapping = aes(x = YEAR_)) +
scale_x_continuous(breaks = c(2010, 2011, 2012, 2013, 2014, 2015, 2016)) +
labs(x = "Year", y = "Count")- Utilice la función
count()para obtener el recuento real de cada categoría.
## # A tibble: 7 × 2
## YEAR_ n
## <dbl> <int>
## 1 2010 127
## 2 2011 240
## 3 2012 352
## 4 2013 170
## 5 2014 131
## 6 2015 205
## 7 2016 152
Medición de la variación continua con un histograma
- La distribución de una variable continua se puede medir con el uso de un histograma. En este ejercicio crearás un histograma de los acres quemados en incendios forestales
- En una nueva línea, utilice la función
read_csv()para cargar el archivo StudyArea.csv
dfFires <- read_csv("C:/Data/StudyArea.csv", col_types = list(UNIT = col_character()), col_names = TRUE)- Introduzca el marco de datos y utilice la función
select()para limitar las columnas y filtrar las filas para que sólo se incluyan los incendios superiores a 1.000 acres. Dado que tenemos un gran número de incendios forestales que quemaron sólo un pequeño número de acres, nos centraremos en los incendios que son un poco más grandes en este caso.
## # A tibble: 1,377 × 5
## ORGANIZATI STATE YEAR_ TOTALACRES CAUSE
## <chr> <chr> <dbl> <dbl> <chr>
## 1 FWS Washington 2010 2755 Natural
## 2 FWS Idaho 2010 1801 Undetermined
## 3 FWS Montana 2010 27898 Natural
## 4 FWS Montana 2012 9468 Natural
## 5 FWS Washington 2012 1141 Human
## 6 FWS Oregon 2010 3083 Human
## 7 FWS Oregon 2013 51015 Undetermined
## 8 FWS Washington 2016 171915 Human
## 9 FWS Arizona 2015 6000 Natural
## 10 FWS Arizona 2016 1624 Natural
## # ℹ 1,367 more rows
- Cree el histograma utilizando ggplot() con geom_hist() y un tamaño de la celda de 500. Los datos están claramente sesgados hacia el extremo inferior del número de acres quemados.
df %>%
select(ORGANIZATI, STATE, YEAR_, TOTALACRES, CAUSE) %>%
filter(TOTALACRES >= 1000) %>%
slice(1:300) %>%
ggplot() +
geom_histogram(mapping = aes(x=TOTALACRES), binwidth=1000) +
labs(x = "Total Acres", y = "Count")- También se puede obtener un recuento físico del número de incendios que han caído en cada casilla. Al ver el histograma y el recuento, se puede oservar que la gran mayoría de incendios son pequeños.
## # A tibble: 338 × 2
## `cut_width(TOTALACRES, 100)` n
## <fct> <int>
## 1 [950,1050] 43
## 2 (1050,1150] 73
## 3 (1150,1250] 38
## 4 (1250,1350] 34
## 5 (1350,1450] 39
## 6 (1450,1550] 36
## 7 (1550,1650] 26
## 8 (1650,1750] 43
## 9 (1750,1850] 37
## 10 (1850,1950] 24
## # ℹ 328 more rows
- Reconstruya el histograma utilizando un tamaño de celda de 5000. ¿Cuál es el efecto en la salida?
Medición de la covariación con gráficos de caja
- Los gráficos de caja proporcionan una representación visual de la dispersión de los datos de una variable. Estos gráficos muestran el rango de valores de una variable junto con la mediana y los cuartiles. Siga las instrucciones proporcionadas a continuación para crear un gráfico de caja que mide la covariación entre la organización y la superficie total quemada.
- Utilice la función
read_csv()para cargar el archivo StudyArea.csv en un dataframe
dfFires <- read_csv("C:/Data/StudyArea.csv",
col_types = list(UNIT = col_character()),
col_names = TRUE)- Extraiga el marco de datos y filtre las filas para que sólo se
incluyan los incendios de entre 5000 y 10000
acres. A continuación, agrupe los datos por organización.
La columna ORGANIZATI del conjunto de datos contiene datos categóricos
de los organismos del gobierno federal de EE.UU. que han
tenido tierras afectadas por incendios forestales. Por
último utilice
ggplot()congeom_boxplot()para crear unboxplotque muestre la distribución de incendios forestales por organización
df %>%
filter(TOTALACRES >= 5000 & TOTALACRES <= 10000) %>%
group_by(ORGANIZATI) %>%
ggplot(mapping = aes(x = ORGANIZATI, y = TOTALACRES)) + geom_boxplot() +
labs(x = "Organization", y = "Total Acres")- Puede crear también un nuevo
boxplotque mapee la covariación deCAUSEyTOTALACRES
Medición de la covariación con el tamaño del símbolo
- La función
geom_count()puede utilizarse conggplot()para medir la covariación entre variables utilizando diferentes tamaños de símbolos. Siga las instrucciones proporcionadas a continuación para medir la covariación entre la organización y la causa del incendio forestal utilizando el tamaño del símbolo
- Use la función
read_csv()para cargar el conjunto de datos en undataframe
dfFires <- read_csv("C:/Data/StudyArea.csv",
col_types = list(UNIT = col_character()),
col_names = TRUE)- Extraiga el marco de datos y filtre las filas para que sólo se
incluyan los incendios forestales que se originaron por
causas naturales o humanas. Esto eliminará cualquier
registro que son desconocidos o tienen valores perdidos. A continuación,
utilice
geom_count()para crear un gráfico de símbolos graduados basado en el número de incendios por organización.
df %>%
filter(CAUSE == "Natural" | CAUSE == "Human") %>%
group_by(ORGANIZATI) %>%
ggplot() + geom_count(mapping = aes(x = ORGANIZATI, y = CAUSE)) +
labs(x = "Organization", y = "Cause")- También puede obtener un recuento exacto del número de incendios por organización y causa
## # A tibble: 11 × 3
## ORGANIZATI CAUSE n
## <chr> <chr> <int>
## 1 BIA Human 49
## 2 BIA Natural 91
## 3 BLM Human 187
## 4 BLM Natural 386
## 5 FS Human 158
## 6 FS Natural 431
## 7 FWS Human 10
## 8 FWS Natural 7
## 9 FWS Undetermined 6
## 10 NPS Human 6
## 11 NPS Natural 46
Gráficos 2D de contenedores y hexágonos
- También puede utilizar gráficos 2D de contenedores y hexadecimales como una forma alternativa de ver la distribución de dos variables. Siga las instrucciones que se proporcionan a continuación para crear gráficos 2D de cubos y hexágonos que visualicen la relación entre el año y la superficie total quemada
- Utilice la función
read_csv()para cargar el conjunto de datos en un marco de datos
dfFires <- read_csv("C:/Data/StudyArea.csv",
col_types = list(UNIT = col_character()),
col_names = TRUE)- Cree un mapa 2D con
YEAR_en el eje \(x\) yTOTALACRESen el eje \(y\)
ggplot(data = dfFires) +
geom_bin2d(mapping = aes(x=YEAR_, y=TOTALACRES)) +
labs(x = "Year", y = "Total Acres")Generación de estadísticas de resumen
- Otra técnica básica para realizar un análisis exploratorio de datos
es generar varias estadísticas de resumen sobre un conjunto
de datos.
Rincluye una serie de funciones individuales para generar estadísticas de resumen específicas o puede utilizar la funciónsummary()para generar un conjunto de estadísticas de resumen
- Recargar el archivo StudyArea.csv en un marco de datos
- Restringir la lista de columnas
- Filtra la lista para incluir sólo los incendios forestales de más de 1000 acres
- Llame a la función
mean(), pasando una referencia al marco de datos y a la columna columna TOTALACRES.
## [1] 10813.06
- Llama a la función
median()
## [1] 3240
- En lugar de llamar a las funciones individuales de estadísticas de
resumen, puede simplemente utilizar la función
summary()para devolver una lista de estadísticas de resumen.
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 1000 1670 3240 10813 8282 590620
Conclusiones
- En esta sección ha aprendido algunas técnicas básicas de exploración de datos utilizando R. Ha aprendido a medir la variación categórica y continua con gráficos de barras e histogramas, y la covariación con gráficos de caja y diferentes tamaños de símbolos. Por último, finalmente, aprendió a generar estadísticas de resumen y a crear gráficos 2D y hexadecimales.
Técnicas básicas de visualización de datos
El paquete
ggplot2es una biblioteca que permite la creación de muchos tipos de visualización de datos, incluyendo varios tipos de gráficos y diagramas. Esta biblioteca fue creada creada por Hadley Wickham en 2005 y es una implementación en R de la Wilkinson’s Grammar of Graphics. La idea de este paquete es especificar bloques de construcción de gráficos y luego combinarlos para crear una pantalla gráfica.En este capítulo cubriremos los siguientes temas:
- Creación de un gráfico de dispersión
- Añadir una línea de regresión a un gráfico de dispersión
- Trazado de categorías
- Etiquetado del gráfico
- Diseños de leyendas
- Creación de una faceta
- Tematización
- Creación de gráficos de barras
- Creación de gráficos de violines
- Creación de gráficos de densidad
Creación de un gráfico de dispersión
- Un diagrama de dispersión es un gráfico en el que los valores de dos variables se trazan a lo largo de dos ejes, y el patrón de los puntos resultantes revela cualquier correlación presente
Los ejercicios de este capítulo requieren los siguientes paquetes:
readr, dplyr, ggplot2. Pueden cargarse desde el panel de paquetes, el panel de la consola o un scriptAbra RStudio y busque el panel de la consola
Cargar el contenido del archivo StudyArea.csv en un marco de datos
dfWildfires <- read_csv("C:/Data/StudyArea.csv",
col_types = list(UNIT = col_character()),
col_names = TRUE)- Crear un subconjunto de columnas
- Agrupa los registros por año
- Resuma los datos según el número total de hectáreas quemadas
- Utilice
ggplot()para crear un gráfico de dispersión con el año en el eje x y el total de acres quemados en el eje Y.
- Hay ocasiones en las que tiene sentido utilizar las escalas
logarítmicas en los cuadros y gráficos. Una de las razones es responder
a la asimetría hacia los valores grandes, es decir, los casos en los que
uno o unos pocos puntos son mucho más grandes que el grueso de los
datos. En el gráfico que acabamos de crear hay un par de puntos que
entran en esta categoría en en el eje Y. Vuelva a crear el gráfico, pero
esta vez utilice la función
log()en la columna totalacres
Añadir una línea de regresión al gráfico de dispersión
- Los gráficos construidos con
ggplot()pueden tener más de una geometría. Es habitual añadir una línea de predicción (regresión) al gráfico.
- Hay varias maneras de añadir una línea de regresión al gráfico de
dispersión, una de ellas es utilizar la función
geom_smooth()con el método establecido comolm(línea recta) y el parámetro se establecido en FALSE. Añade la línea de código que ves abajo en la ventana de la consola
- Cambia el método a
loessel efecto en la línea de regresión
ggplot(data=sm, aes(x=YEAR_, y=log(totalacres))) +
geom_point() +
geom_smooth(method=loess, se=FALSE)- Puede añadir un intervalo de confianza alrededor de la línea de
regresión estableciendo
se = TRUE
ggplot(data=sm, aes(x=YEAR_, y=log(totalacres))) +
geom_point() +
geom_smooth(method=loess, se=TRUE)Trazado de categorías
- En lugar de representar gráficamente todo el conjunto de incendios forestales, es posible que se quiera comprender mejor las tendencias por estado. En este paso creará un nuevo gráfico de dispersión que visualice las tendencias de los incendios forestales a lo largo del tiempo por estado
- Reagrupar el marco de datos de los incendios forestales por estado y año
- Resuma los grupos por el total de hectáreas quemadas
- Añade un parámetro de parámetro de color a la función
aes()para que los puntos y la línea de regresión se se mapeen de acuerdo con el estado en el que se produjeron
ggplot(data=sm, aes(x=YEAR_, y=totalacres, colour=STATE)) +
geom_point(aes(colour = STATE)) +
stat_smooth(method=lm, se=FALSE)Etiquetar el gráfico
- Puede añadir etiquetas a su gráfico mediante la función
geom_text()o la funcióngeom_ label()
- Etiquete cada uno de los puntos del gráfico de dispersión utilizando
geom_text()con un tamaño de etiqueta de 3
ggplot(data=sm, aes(x=YEAR_, y=log(totalacres))) +
geom_point() +
geom_smooth(method=loess, se=TRUE) +
geom_text(aes(label=STATE), size=3)La pantalla está extremadamente desordenada así que vamos a ajustar algunos parámetros para hacer esto más fácil de leer.
- Puede utilizar el parámetro
check_overlappara eliminar cualquier etiqueta superpuesta. Actualice su código como se ve a continuación
ggplot(data=sm, aes(x=YEAR_, y=log(totalacres))) +
geom_point() +
geom_smooth(method=loess, se=TRUE) +
geom_text(aes(label=STATE), size=3, check_overlap = TRUE)Esto se ve un poco mejor, pero si se cambia el tamaño de la etiqueta a 2 se reducirá aún más reducir el desorden y la superposición, mientras que esperamos que siga siendo legible
Habrás observado que las etiquetas se sitúan directamente sobre los temas. Puede utilizar los parámetros
nudge_xynudge_ypara mover las etiquetas en relación con el punto. Utilicenudge_xcomo se ve a continuación para ver cómo esto mueve las etiquetas horizontalmente
ggplot(data=sm, aes(x=YEAR_, y=log(totalacres))) +
geom_point() +
geom_smooth(method=loess, se=TRUE) +
geom_text(aes(label=STATE), size=2, check_overlap = TRUE, nudge_x = 1.0)- También puede colorear las etiquetas por categoría añadiendo el
parámetro de color a la función
aes()parageom_text()
ggplot(data=sm, aes(x=YEAR_, y=log(totalacres))) +
geom_point() +
geom_smooth(method=loess, se=TRUE) +
geom_text(aes(label=STATE, color=STATE), size=2, check_overlap = TRUE, nudge_x = 1.0)- También puedes añadir un subtítulo y un pie de foto con el código que ves a continuación
ggplot(data=sm, aes(x=YEAR_, y=log(totalacres))) +
geom_point() +
geom_smooth(method=loess, se=TRUE) +
labs(title=paste("Acreage Burned by Wildfires Has Increased In the Past Few Decades"), subtitle=paste("1980-2016"), caption="Data from USGS")- También puede actualizar las etiquetas X e Y del gráfico. Actualice estas etiquetas en su gráfico usando el código que ve abajo
ggplot(data=sm, aes(x=YEAR_, y=log(totalacres))) +
geom_point() +
geom_smooth(method=loess, se=TRUE) +
labs(title=paste("Acreage Burned by Wildfires Has Increased In the Past Few Decades"), subtitle=paste("1980-2016"), caption="Data from USGS") +
scale_y_continuous(name="Log of Total Acres Burned") +
scale_x_continuous(name="Burn Year")Diseños de leyendas
- La función
theme()puede utilizarse para controlar la ubicación de la leyenda y la funciónguide()puede utilizarse para proporcionar un control adicional de la leyenda
- La función
theme()junto con el argumentolegend.postionse utiliza para controlar la ubicación de la leyenda en el gráfico. Por defecto, la leyenda que hemos visto hasta ahora se ha colocado en el lado derecho del gráfico con una orientación vertical. Reposicione la leyenda en la parte inferior con el código siguiente.
ggplot(data=sm, aes(x=YEAR_, y=log(totalacres), color=STATE)) +
geom_point() +
labs(title=paste("Acreage Burned by Wildfires Has Increased In the Past Few Decades"),
subtitle=paste("1980-2016"), caption="Data from USGS") +
scale_y_continuous(name="Log of Total Acres Burned") +
scale_x_continuous(name="Burn Year") +
theme(legend.position="bottom")También puede eliminar explícitamente una leyenda estableciendo
legend.position = "none". Pruebe eso ahora si lo deseaOtros aspectos de la leyenda, como el número de filas en la leyenda, así como el tamaño del símbolo pueden ser controlados a través de la función
guides(). Utilice el código que se ve a continuación para actualizar la leyenda a dos filas y con cada símbolo de tamaño 4
ggplot(data=sm, aes(x=YEAR_, y=log(totalacres), color=STATE)) +
geom_point() +
labs(title=paste("Acreage Burned by Wildfires Has Increased In the Past Few
Decades"), subtitle=paste("1980-2016"), caption="Data from USGS") +
scale_y_continuous(name="Log of Total Acres Burned") +
scale_x_continuous(name="Burn Year") +
theme(legend.position = "bottom") +
guides(color=guide_legend(nrow=2,override.aes=list(size=4)))Crear una faceta
- Una forma particularmente buena de graficar variables categóricas es
dividir el gráfico en facetas, que son subparcelas que muestran cada una
un subconjunto de los datos. Las funciones
facet_wrap()yfacet_grid()pueden utilizarse para crear facetas
- Utilice la función
facet_wrap()que se muestra en el código siguiente para crear un mapa de facetas que muestre el total de acres quemados por estado
ggplot(data=sm, mapping = aes(x=YEAR_, y=log(totalacres))) +
geom_point() + facet_wrap(~STATE) + geom_smooth(method=loess, se=TRUE)Temática
- Existen ocho temas incorporados que pueden utilizarse para
personalizar el estilo de
ggplot2de los elementos que no son datos de su gráfico.
- Los ocho temas incluidos en
ggplot2sontheme_bw, theme_ classic, theme_dark, theme_gray, theme_light, theme_ linedraw, theme_minimal, theme_void. Añade el código que ves a continuación para cambiar la faceta atheme_dark
ggplot(data=sm, mapping = aes(x=YEAR_, y=log(totalacres))) +
geom_point() + facet_wrap(~STATE) + geom_smooth(method=loess, se=TRUE) +
theme_dark()- Experimenta con los temas para ver las diferencias de estilo.
Creación de gráficos de barras
- Puede utilizar
geom_bar()ogeom_chart()para crear gráficos de barras conggplot2. Sin embargo, hay una diferencia significativa entre ambos. La funcióngeom_bar()generará un recuento del número de instancias de una variable. En otras palabras, cambia la estadística que ya se ha generado para el grupo. La funcióngeom_col()mantiene la variable ya generada para el grupo. Para ver la diferencia, complete los siguientes pasos
- Cargue el archivo StudyArea.csv y obtenga un subconjunto de columnas
dfWildfires <- read_csv("C:/Data/StudyArea.csv",
col_types = list(UNIT = col_character()), col_names = TRUE)
df <- select(dfWildfires, ORGANIZATI, STATE, YEAR_, TOTALACRES, CAUSE)- Filtrar el marco de datos para que sólo se incluyan los incendios forestales de California
- Agrupar el marco de datos por YEAR_
- Trace los datos utilizando
geom_bar()como se ve a continuación. Observe que el gráfico de barras que se producido es un recuento del número de incendios para cada año
- Ahora utilice
geom_col()para ver la diferencia. La variable TOTALACRES se mantiene en este caso
Creación de tramas de violín
- Los gráficos de violín, que son similares a los gráficos de caja,
también muestran la densidad de probabilidad en varios valores. Las
áreas más gruesas del gráfico de violín indican una mayor probabilidad
en ese valor. Normalmente, los gráficos de violín también incluyen un
marcador para la mediana junto con el rango intercuartil (IQR). La
función
geom_violin()se utiliza para crear gráficos violín enggplot2
- Cargue el archivo StudyArea.csv y obtenga un subconjunto de columnas
dfWildfires <- read_csv("C:/Data/StudyArea.csv",
col_types = list(UNIT = col_character()), col_names = TRUE)
df <- select(dfWildfires, ORGANIZATI, STATE, YEAR_, TOTALACRES, CAUSE)- Filtrar el marco de datos para que sólo se incluyan los incendios forestales de más de 5.000 acres se incluyan.
- Agrupe los incendios forestales por organización
- Crear una gráfica básica de violín.
- Puede añadir las observaciones individuales utilizando
geom_jitter(). Si necesita mantener los puntos y dispersarlos horizontalmente, puede utilizargeom_jitter(height = 0)
ggplot(data=grpWildfires, mapping = aes(x=ORGANIZATI, y=log(TOTALACRES))) +
geom_violin() +
geom_jitter(height = 0, width = 0.1)- La media puede añadirse utilizando
stat_summary()como se ve a continuación
ggplot(data=grpWildfires, mapping = aes(x=ORGANIZATI, y=log(TOTALACRES))) +
geom_violin() + geom_jitter(height = 0, width = 0.1) +
stat_summary(fun.y=median, geom="point", size=3, color="red") +
stat_summary(fun.y=mean, geom="point", size=3, color="green")- La función
box_plot()puede utilizarse para añadir lamedianay elIQR
ggplot(data=grpWildfires, mapping = aes(x=ORGANIZATI, y=log(TOTALACRES))) +
geom_violin() + geom_boxplot(width=0.2)Creación de gráficos de densidad
- Los gráficos de densidad, creados con
geom_density()calculan una estimación de la densidad, que es una versión suavizada de un histograma y se utiliza con datos continuos.ggplot2también puede calcular versiones 2D de la densidad, incluyendo contornos y gráficos de densidad con estilo de polígono
- En esta primera parte del ejercicio crearás un gráfico de densidad básico. Cargue el archivo StudyArea.csv y obtenga un subconjunto de columnas
dfWildfires <- read_csv("C:/Data/StudyArea.csv",
col_types = list(UNIT = col_character()), col_names = TRUE)
df <- select(dfWildfires, ORGANIZATI, STATE, YEAR_, TOTALACRES, CAUSE)- Filtrar el marco de datos para que sólo se incluyan los incendios forestales de más de 1.000 acres
- Cree un gráfico de densidad con la función
geom_density()
- También puede crear el mismo gráfico de densidad con una versión registrada de los datos
- A continuación, crearás gráficos 2D de los datos empezando por los contornos. Añade el código que ves a continuación
- Por último, cree una superficie de densidad 2D utilizando
stat_density_2d()
ggplot(dfWildfires, aes(x=YEAR_, y=log(TOTALACRES))) +
geom_density_2d() +
stat_density_2d(geom="raster", aes(fill=..density..), contour=FALSE)Conclusiones
- En este capítulo has aprendido varias técnicas de visualización de
datos utilizando
ggplot2. Comenzamos con gráficos de dispersión básicos, añadimos líneas de regresión, etiquetamos los gráficos de varias formas y creamos una leyenda. Además, se aprendió a crear gráficos de facetas y a trabajar conggplot2y las opciones de tematización. También aprendió a crear gráficos de barras, gráficos de violín y gráficos de densidad.
Ejercicio para entregar
Considere los datos asociados a precios de casas
house_prices.csvque aparece en el directorio RDataSets.zip enGithub. Nótese que el archivo posee su respectiva descripciónhouse_prices_description.txtque puede ser de gran utilidad. Realice un análisis exploratorio de los datos relacionados con los precios de casas, teniendo en cuenta cada ítem estudiado en esta sección. Debería agregar todas las visualizaciones extras, que sean necesarias y le permitan generar y refinar preguntas sobre los datos.Considere los datos asociados a precios de casas
house_prices.csvque aparece en el directorio RDataSets.zip en Github. A manera de aplicación, utilice las técnicas básicas de visualización de datos estudiadas en cada ítem de esta sección, ahora aplicadas al conjunto de datos relacionado con la predicción del precio de casas, el cual puede ser influenciado por los distintos factores, presentados en las columnas del archivohouse_prices.csv, cuyas descripciones puede encontrar en el archivohouse_prices_description.txt. Puede agregar visualizaciones extras, que le permitan generar y refinar preguntas sobre los datos.
Visualización de datos geográficos con ggmap
El paquete
ggmappermite visualizar datos espaciales y estadísticas espaciales en formato de mapa utilizando el enfoque por capas deggplot2. Este paquete también incluye mapas base que dan contexto a sus visualizaciones, incluyendo Google Maps, Open Street Map, Stamen Maps y CloudMade. Además, se proporcionan funciones para acceder a varios servicios de Google, comoGeocoding, Distance Matrix, and Directions.El paquete
ggmapse basa enggplot2, lo que significa que adoptará un enfoque por capas y constará de los mismos cinco componentes que se encuentran enggplot2. Estos incluyen un conjunto de datos por defecto con mapeos estéticos donde \(x\) es longitud, \(y\) es latitud, y el sistema de coordenadas se fija enMercator. Otros componentes incluyen una o más capas definidas con un objeto geométrico y una transformación para cada mapa estético, el sistema de coordenadas y la especificación de las facetas. Debido a queggmapestá construido sobreggplot2tiene acceso a toda la gama deggplot2ya aprendidas.En esta sección cubriremos los siguientes temas:
- Creación de un mapa base
- Añadir capas operativas
- Añadir capas desde un
shapefile
Creación de un mapa base
- Hay dos pasos básicos para crear un mapa con
ggmap. En general sólo hay que descargar el mapa rasterizado (basemap) y luego trazar los datos operativos en el mapa base. El primer paso se consigue con la funciónget_map(), que puede utilizarse para crear un mapa base desde Google, Stamen, Open Street Map o CloudMade. Aprenderás cómo hacerlo en este paso. En un próximo paso aprenderás a añadir y estilizar los datos operativos de varias maneras
- Cargue el paquete
ggmapyendo al panel de paquetes en RStudio y haciendo clic en la casilla junto al nombre del paquete. También puede cargarlo desde la consola escribiendo
- Crea una variable llamada myLocation y defínela como California, ubicación que usaremos más adelante
- Llama a la función
get_map()y pasa la variable de localización junto con un nivel de Zoom de 6. Necesitaras primero crear unaAPI keyen Google Cloud Platform. Si descargas la versión de desarrollo deggmapdesde GitHub, hay una función que guarda tu clave de la API de Google y la utiliza en las siguientes llamadas a la API a través deggmap. Para instalarla desde la consola escribir
- Utilice las siguientes instrucciones para descargar el mapa. Cargue
primero su API key y luego use la función
get_googlemappara obtener el mapa de interés. Para que las siguientes instrucciones funcionen, debe realizar los siguientes pasos en Google Cloud Platform
Debe registrarse en Google Cloud Platform. Desafortunadamente para el registro deberá usar una Tarjeta de credito. Obtendrá $300 gratis para usarlos por 90 días. Luego de esto se cargarán a su cuenta $5 mensuales, dependiendo del uso que haga de la API. Recomiendo usar una tarjeta crédito prepago como por ejemplo Daviplata o similiares
Debe habilitar los siguientes servicios en la Biblioteca
- Maps Static API
- Geocoding API
- Geolocation API
- Maps Embed API
Para finalizar genere una API restringida a cada unos de los servicios de localización habilitados anteriormente. Las siguientes imágenes muestran como quedaría la configuración requerida
- Para obtener mas información sobre el uso de la función
get_googlemap()ver ggmap. Por favor, no use la API keymykey = "AIzaSyABAXSsDR-f9qby3LK2o6bh0BypFWlKrm4", pues está será cambiada por otra, en este caso es usada a manera de ejemplo. Cree su propia API Google Cloud Platform
mykey = "AIzaSyABAXSsDR-f9qby3LK2o6bh0BypFWlKrm4"
register_google(key = mykey)
get_googlemap("waco, texas") %>% ggmap()- Lo siguientes tipos de mapas también están disponibles para vista satelital en los mapas. Para ver más opciones visitar la documentación de get_googlemap
Añadir capas de datos operativos
ggmap()devuelve un objetoggplot, lo que significa que actúa como capa base en elggplot2. Esto permite la gama completa de capacidades deggplot2lo que significa que puede trazar puntos en el mapa, añadir contornos, mapas de calor 2D y mucho más. Examinaremos algunas de estas capacidades en esta sección.
- El siguiente código es usado para producir un mapa de California que muestre los incendios forestales entre los años 1980-2016 que quemaron más de 1,000 hectáreas.
library(dplyr)
df <- select(dfWildfires, STATE, YEAR_, TOTALACRES, DLATITUDE, DLONGITUDE)
knitr::kable(head(df))| STATE | YEAR_ | TOTALACRES | DLATITUDE | DLONGITUDE |
|---|---|---|---|---|
| Arizona | 1988 | 1500 | 31.58333 | -111.5500 |
| Arizona | 1986 | 10390 | 32.50000 | -111.5167 |
| Montana | 1986 | 1400 | 47.50000 | -111.4333 |
| Arizona | 2002 | 1035 | 31.70000 | -111.4830 |
| Arizona | 2000 | 5700 | 31.51600 | -111.5170 |
| Arizona | 2000 | 2750 | 31.64900 | -111.4910 |
- Ahora vamos a hacer algo un poco más interesante. En primer lugar,
utilizaremos la función de
dplyrmutate()para agrupar los incendios por década
df <- mutate(df,
DECADE = ifelse(YEAR_ %in% 1980:1989, "1980-1989",
ifelse(YEAR_ %in% 1990:1999, "1990-1999",
ifelse(YEAR_ %in% 2000:2009, "2000-2009",
ifelse(YEAR_ %in% 2010:2016, "2010-2016", "-99")))))- A continuación, codifica por colores los incendios forestales por DECADE y crea un mapa de símbolos graduados en función del tamaño de cada incendio. La propiedad de color define la columna a utilizar para la agrupación, y la propiedad de tamaño define la columna a utilizar para el tamaño de cada símbolo.
- Nótese que además es posible hacer dinámico el
mapa usando
ggplotly. Para esto utilizamos la funciónggplot_buildpara convertir el plot deggmapa un objetoggplot.
last_plot <- ggmap(myMap) +
geom_point(data=df, aes(x = DLONGITUDE, y = DLATITUDE, colour= DECADE, size = TOTALACRES))- Vamos a cambiar la vista del mapa para centrarnos más en el sur de California, y en en particular el área al norte de Los Ángeles.
myMap <- get_map(location = "Santa Clarita, California", zoom = 10)
ggmap(myMap) +
geom_point(data=df,
aes(x = DLONGITUDE, y = DLATITUDE, colour= DECADE, size = TOTALACRES))- A continuación, añadiremos capas de contorno y de
calor. La función
geom_density2d()se utiliza para crear los contornos mientras que la funciónstat_density2d()crea el mapa de calor. Puedes experimentar con los colores usando las propiedadesscale_fill_gradient(low and high).
- Aquí los hemos establecido en verde y rojo respectivamente, pero
puede cambiar el esquema de colores según su preferencia. En este
ejemplo, la sintaxis
fill = ..level..se utiliza para rellenar los contornos en el gráfico basado en los niveles de densidad de los puntos de datos.
myMap <- get_map(location = "California", zoom = 6)
ggmap(myMap, extent = "device") +
geom_density2d(data = df, aes(x =DLONGITUDE, y = DLATITUDE), size = 0.3) +
stat_density2d(data = df,
aes(x = DLONGITUDE, y = DLATITUDE, fill = ..level..,
alpha =..level..),
size = 0.01, bins = 16, geom = "polygon") +
scale_fill_gradient(low = "green", high ="red") +
scale_alpha(range = c(0, 0.3), guide = FALSE)- Si prefiere ver el mapa de calor sin contornos, el código puede simplificarse como sigue
ggmap(myMap, extent = "device") +
stat_density2d(data = df,
aes(x =DLONGITUDE, y = DLATITUDE, fill = ..level..,
alpha = ..level..),
size = 0.01,bins = 16, geom = "polygon") +
scale_fill_gradient(low = "green", high ="red") +
scale_alpha(range = c(0, 0.3), guide = FALSE)- Por último, vamos a crear un mapa de facetas que represente los
puntos críticos de cada año de la década
actual. El conjunto de datos contiene información hasta el
año 2016.
facet_wrap()es una función útil enggplot2para crear múltiples mapas mostrados juntos en un diseño de cuadrícula, cada uno representando un subconjunto de los datos.
df <- filter(df, YEAR_ %in% c(2010, 2011, 2012, 2013, 2014, 2015, 2016))
gg <- ggmap(myMap, extent = "device") +
stat_density2d(data = df, aes(x = DLONGITUDE, y = DLATITUDE, fill = ..level.., alpha = ..level..),
size = 0.01, bins = 16, geom = "polygon") +
scale_fill_gradient(low = "green", high ="red") +
scale_alpha(range = c(0, 0.3), guide = FALSE) +
facet_wrap(~YEAR_, ncol = 4)
theme(plot.title = element_text(size = 20)) +
theme(plot.margin = margin(1, 1, 1, 1, "cm"))## List of 2
## $ plot.title :List of 11
## ..$ family : NULL
## ..$ face : NULL
## ..$ colour : NULL
## ..$ size : num 20
## ..$ hjust : NULL
## ..$ vjust : NULL
## ..$ angle : NULL
## ..$ lineheight : NULL
## ..$ margin : NULL
## ..$ debug : NULL
## ..$ inherit.blank: logi FALSE
## ..- attr(*, "class")= chr [1:2] "element_text" "element"
## $ plot.margin: 'margin' num [1:4] 1cm 1cm 1cm 1cm
## ..- attr(*, "unit")= int 1
## - attr(*, "class")= chr [1:2] "theme" "gg"
## - attr(*, "complete")= logi FALSE
## - attr(*, "validate")= logi TRUE
Añadir capas a partir de Shapefiles
- Aunque se trata de un formato de datos SIG algo más antiguo, los
shapefiles siguen siendo utilizados comúnmente para
representar características geográficas. Con un poco de
manipulación, puede obtener datos de trazado de shapefiles en
ggmap.
- Para este ejercicio necesitarás instalar un paquete adicional
llamado
rgdal. Utilice el panel de paquetes para encontrar e instalarrgdalo introduzca el código que ve a continuación
- Cargue el paquete
rgdala través del panel de paquetes o introduzca el código que ve a continuación
- La carpeta RDataSets
que contiene los datos del ejercicio para este curso, contiene un
archivo shape llamado
S_USA.Wilderness.
- Estos archivos se combinan para crear lo que se llama un
shapefile. Este archivo contiene los límites
de las áreas silvestres designadas en los Estados Unidos.
Utilice la función
readOGR()dergdalpara cargar los datos en una variable.
## OGR data source with driver: ESRI Shapefile
## Source: "C:\Data", layer: "S_USA.Wilderness"
## with 447 features
## It has 8 fields
## Integer64 fields read as strings: WID
- La función
fortify(), que forma parte deggplot2, convierte todos los puntos individuales que definen cada límite en un marco de datos que se puede utilizar para trazar los límites del polígono.
- Utilice la función
ggmapqmap()(qmapsignifica mapa rápido) para crear el mapa base que se utilizará como referencia para los límites de los espacios naturales. Centra el mapa enMontana.
- Antes de trazar los límites de los espacios naturales como polígonos en el mapa, observe el marco de datos creado por la función fortify() para entender mejor la estructura creada por esta función
| long | lat | order | hole | piece | id | group |
|---|---|---|---|---|---|---|
| -70.94738 | 44.27397 | 1 | FALSE | 1 | 0 | 0.1 |
| -70.94757 | 44.27229 | 2 | FALSE | 1 | 0 | 0.1 |
| -70.94924 | 44.27145 | 3 | FALSE | 1 | 0 | 0.1 |
| -70.95507 | 44.27034 | 4 | FALSE | 1 | 0 | 0.1 |
| -70.95729 | 44.26868 | 5 | FALSE | 1 | 0 | 0.1 |
| -70.95862 | 44.26679 | 6 | FALSE | 1 | 0 | 0.1 |
Fíjese en la columna
group. Esta columna identifica de forma exclusiva límites de los espacios naturales. Los límites de los espacios naturales son polígonos, y los polígonos están definidos por un conjunto de puntos que definen la estructura del polígono. Es algo así como como jugar a conectar los puntos, donde cada punto es un par de coordenadas delatitud/longituddefinidas por las columnas long y lat en el marco de datos.Por ejemplo, mire el grupo 0.1. Observe que hay varias filas que contiene el valor 0.1, y que cada fila tiene valores únicos de
longitudylat. Estos son todos los puntos utilizados para definir los límites de ese polígono.
- Ahora traza los límites de los espacios naturales en el mapa base. Observe el uso de la columna para agrupar los polígonos. El trazado de los límites en el mapa lleva algo de tiempo, así que sea paciente con este paso. Al final debería ver un mapa similar a la captura de pantalla de abajo.
montana +
geom_polygon(aes(x=long,y=lat, group=group, alpha=0.25), data=wild, fill="white") +
geom_polygon(aes(x=long, y=lat, group=group), data=wild, color="black", fill=NA)Conclusión
En este capítulo ha aprendido a utilizar el paquete ggmap para crear atractivas visualizaciones de datos en formato de mapa.
Ha aprendido a *crear mapas base utilizando Google como fuente de datos, añadir capas de datos operativos, crear varios tipos de visualizaciones de mapas utilizando fuentes de datos externas, y cargar archivos de forma.
Ejercicio para entregar
Considere el conjunto de datos
violent_crimes.csven RDataSets.zip el cual abarca una gran variedad de delitos, y realice visualizaciones geográficas para cada tipo de crimen.Considere cada ítem estudiado en esta sección para realizar las visualizaciones respectivas con este dataset.
Verifique si existe algún patrón dentro de los delitos violentos que pueda explorarse visualmente. Los datos incluyen dentro de sus columnas la longitud y latitud, la categoría en la que se clasifica el delito, la fecha y hora.
Análisis con datos faltantes
Introducción
Los datos faltantes es uno de los temas que se ignoran en la mayoría de los textos introductorios. Probablemente, parte de la razón por la que esto es así es que todavía abundan muchos mitos sobre el análisis con datos faltantes. Además, algunas de las investigaciones sobre técnicas de vanguardia es aún relativamente nueva. Una razón más legítima para su ausencia en los textos introductorios es que la mayoría de las metodologías son bastante complicadas, desde el punto de vista matemático. Sin embargo, la increíble ubicuidad de los problemas relacionados con los datos faltantes en el análisis de datos de la vida real requiere que se aborde el tema.
Esta sección sirve como una suave introducción al tema y a una de las técnicas más eficaces para tratarla. Un refrán común sobre el tema es algo así como que, la mejor manera de tratar con los datos que faltan es no tenerlos. Es cierto que los datos que faltan son un tema complicado, y hay muchas maneras de hacerlo, de las cuales algunas pueden estar mal. Es importante no llevar este consejo al extremo, sin embargo, para eludir los problemas de los datos faltantes, algunos han impedido participantes en una encuesta, que por ejemplo, siguen sin responder a todas las preguntas de un formulario.
Hay tratamientos para los datos que faltan, pero no hay tratamientos para los datos malos o incorrectos. El tratamiento estándar para el problema de los datos que faltan es reemplazar los datos que faltan por valores no ausentes. Este proceso se denomina imputación. En la mayoría de los casos, el objetivo de la imputación no es recrear el conjunto de datos faltantes completo, sino permitir que se realicen estimaciones o inferencias estadísticas válidas a partir de los datos faltantes.
Por ello, la eficacia de las diferentes técnicas de imputación no puede evaluarse por su capacidad de recrear los datos con la mayor exactitud posible a partir de un conjunto de datos faltantes simulado, sino que deben juzgarse por su capacidad de apoyar las mismas inferencias estadísticas que se obtendrían del análisis de los datos completos que se extraen del análisis.
De este modo, rellenar los datos que faltan es sólo un paso hacia el verdadero objetivo: el análisis. El conjunto de datos imputados rara vez se considera el objetivo final de la imputación. En la práctica, hay muchas formas diferentes de tratar los datos que faltan, algunas son buenas y otras no tanto. Algunas están bien en determinadas circunstancias, pero no en otras. Algunas implican la eliminación de datos faltantes, mientras que otras implican la imputación. Vamos a mencionar brevemente algunos de los métodos más comunes. Sin embargo, el objetivo final de esta sección, es iniciarle en lo que a menudo se describe como el estándar de oro de las técnicas de imputación: la imputación múltiple.
Visualización de los datos que faltan
Para demostrar la visualización de patrones de datos ausentes, primero tenemos que crear algunos datos ausentes. Para mostrar cómo utilizar la imputación múltiple en un escenario semirealista, vamos a crear una versión del conjunto de datos
mtcarscon algunos valores faltantes. Configuramosseed(para la aleatoriedad determinista), y creamos una variable para mantener nuestro nuevo conjunto de datos.Los datos se extrajeron de la revista
Motor Trend USde 1974 y comprenden el consumo de combustible y 10 aspectos del diseño y el rendimiento de 32 automóviles (modelos de 1973 a 1974) (ver MTCARS).
- En primer lugar, vamos a crear siete valores faltantes en
drat(alrededor del 20 por ciento), cinco valores faltantes en la columnampg(alrededor del 15 por ciento), cinco valores faltantes en la columnacyl, tres valores faltantes enwt(alrededor del 10 por ciento), y tres valores faltantes envs
some_rows <- sample(1:nrow(miss_mtcars), 7)
miss_mtcars$drat[some_rows] <- NA
some_rows <- sample(1:nrow(miss_mtcars), 5)
miss_mtcars$mpg[some_rows] <- NA
some_rows <- sample(1:nrow(miss_mtcars), 5)
miss_mtcars$cyl[some_rows] <- NA
some_rows <- sample(1:nrow(miss_mtcars), 3)
miss_mtcars$wt[some_rows] <- NA
some_rows <- sample(1:nrow(miss_mtcars), 3)
miss_mtcars$vs[some_rows] <- NA- Ahora, vamos a crear cuatro valores faltantes en
qsec, pero sólo para los coches automáticos
only_automatic <- which(miss_mtcars$am==0)
some_rows <- sample(only_automatic, 4)
miss_mtcars$qsec[some_rows] <- NA- Ahora, observemos las primeras filas del nuevo conjunto de datos:
## mpg cyl disp hp drat wt qsec vs am gear carb
## Mazda RX4 21.0 NA 160 110 3.90 2.620 16.46 NA 1 4 4
## Mazda RX4 Wag 21.0 6 160 110 3.90 2.875 17.02 0 1 4 4
## Datsun 710 22.8 NA 108 93 3.85 2.320 18.61 1 1 4 1
## Hornet 4 Drive 21.4 6 258 110 3.08 3.215 19.44 1 0 3 1
## Hornet Sportabout 18.7 8 360 175 3.15 3.440 17.02 0 0 3 2
## Valiant 18.1 6 225 105 NA 3.460 20.22 1 0 3 1
## 'data.frame': 32 obs. of 11 variables:
## $ mpg : num 21 21 22.8 21.4 18.7 18.1 14.3 24.4 NA 19.2 ...
## $ cyl : num NA 6 NA 6 8 6 8 4 4 6 ...
## $ disp: num 160 160 108 258 360 ...
## $ hp : num 110 110 93 110 175 105 245 62 95 123 ...
## $ drat: num 3.9 3.9 3.85 3.08 3.15 NA 3.21 NA 3.92 3.92 ...
## $ wt : num 2.62 2.88 2.32 3.21 3.44 ...
## $ qsec: num 16.5 17 18.6 19.4 17 ...
## $ vs : num NA 0 1 1 0 1 NA 1 1 1 ...
## $ am : num 1 1 1 0 0 0 0 0 0 0 ...
## $ gear: num 4 4 4 3 3 3 3 4 4 4 ...
## $ carb: num 4 4 1 1 2 1 4 2 2 4 ...
- Ahora vamos a visualizar los datos faltantes. La primera forma en
que vamos a visualizar el patrón de los datos faltantes es
utilizando la función
md.patterndel paquetemice(que es también el paquete que vamos a utilizar para imputar nuestros datos faltantes). Si no tiene el paquete instálelo antes
## disp hp am gear carb wt vs qsec mpg cyl drat
## 12 1 1 1 1 1 1 1 1 1 1 1 0
## 6 1 1 1 1 1 1 1 1 1 1 0 1
## 2 1 1 1 1 1 1 1 1 1 0 1 1
## 3 1 1 1 1 1 1 1 1 0 1 1 1
## 1 1 1 1 1 1 1 1 1 0 0 1 2
## 1 1 1 1 1 1 1 1 0 1 1 1 1
## 1 1 1 1 1 1 1 1 0 0 1 1 2
## 1 1 1 1 1 1 1 0 1 1 0 1 2
## 1 1 1 1 1 1 1 0 0 1 1 1 2
## 1 1 1 1 1 1 1 0 0 1 0 1 3
## 2 1 1 1 1 1 0 1 1 1 1 1 1
## 1 1 1 1 1 1 0 1 1 1 1 0 2
## 0 0 0 0 0 3 3 4 5 5 7 27
Un patrón de datos faltantes por fila se refiere a las columnas que faltan en cada fila. Esta función agrega y contabiliza el número de filas con el mismo patrón de datos faltantes. Esta función produce una matriz binaria (0 y 1). Las celdas con un 1 representan datos no faltantes; los 0 representan datos que faltan. Como las filas están ordenadas en un orden creciente de ausencia, la primera fila siempre se refiere al patrón de datos ausentes que contiene la menor cantidad de datos faltantes.
La columna de la izquierda es un conteo del número de filas que muestran el patrón de datos faltantes, y la columna más a la derecha es un recuento del número de puntos faltantes en ese patrón. La última fila contiene un recuento del número de puntos faltantes en cada columna. Como puede ver, 12 de las filas no contienen datos faltantes.
Sólo hay seis filas que contienen más de un valor perdido. Sólo una de estas filas contiene más de dos valores faltantes (como se muestra en la penúltima fila). En cuanto a los conjuntos de datos con datos faltantes, este en particular no contiene mucho. No es raro que en algunos conjuntos de datos falte más del 30% de los datos. Este conjunto de datos no llega ni al 3%. Ahora vamos a visualizar el patrón de datos faltantes gráficamente utilizando el paquete
VIM, el cual debe instalar en caso de no contar con este.
- A simple vista, esta representación nos muestra, sin esfuerzo, que
la columna
dratrepresenta la mayor proporción de datos faltantes, por columnas, seguida dempg, cyl, qsec, vsywt. El gráfico de la derecha nos muestra información similar a la de la salida demd.pattern. Esta representación, sin embargo, hace que sea más fácil saber si hay algún patrón sistemático de omisión. Las celdas azules representan los datos no ausentes, y las rojas representan los datos que faltan. Los números de la derecha del gráfico representan la proporción de filas que muestran ese patrón de datos faltantes. El 37.5% de las filas no contienen ningún tipo de dato faltante.
Marginplots
Parallel boxplots: El métodoVIMpermite comparar las distribuciones condicionales de una variable continua según un conjunto de variables, con valores recodificados comofaltantesono faltantes, mediante múltiples diagramas de caja paralelos.Estos diagramas son útiles para explorar si una variable continua explica la distribución de valores faltantes en otra variable. En la siguiente Figura se muestra un ejemplo con la variable
ageen los datos de EU-SILC User Guide.Además del diagrama de caja estándar, se muestran diagramas de caja agrupados por valores observados (gris claro) y faltantes (gris oscuro) en los diferentes componentes de ingresos. El ancho de las cajas puede ser proporcional al tamaño del grupo o de tamaño igual. Sin embargo, en este ejemplo no es posible utilizar la primera opción porque la proporción de valores faltantes es cercana a 0 para algunos componentes de ingresos.
En muchos componentes, la presencia de valores faltantes claramente depende de la magnitud de los valores de la edad, por ejemplo, los valores faltantes en la variable
py090n(beneficios por desempleo) ocurren predominantemente para individuos más jóvenes. Esto indica situaciones MAR para los valores faltantes en estas variables, lo cual es información útil para los especialistas en la materia.Scatterplots: La implementación enVIMtambién incluye boxplots para datos disponibles y faltantes en los márgenes del gráfico. Las frecuencias de valores faltantes en una o ambas variables se representan mediante números en la esquina inferior izquierda. Este gráfico será denominado en adelantemarginplot.
- El diagrama de caja rojo de la izquierda muestra la
distribución del número de cilindros
cyl, agrupados por datos faltantes parampg, mientras que el diagrama de caja azul muestra esta distribución para datos observados dempg. Lo mismo ocurre con los gráficos de caja decylen la parte inferior del gráfico. Si los datos tienen mecanismo MCAR se espera que los gráficos de caja rojos y azules sean muy similares. Para mas información ver el artículo asociado Exploring incomplete data using visualization techniques.
Tipos de datos faltantes
El paquete
VIMnos permitió visualizar los patrones de datos faltantes. Un término relacionado, al mecanismo de datos faltantes, describe el proceso que determina la probabilidad de que cada punto de datos sea faltante. Hay tres categorías principales de mecanismos de datos faltantes: Missing Completely At Random (MCAR), Missing At Random (MAR), y Missing Not At Random (MNAR). La discriminación basada en el mecanismo de datos faltantes es crucial, ya que nos informa sobre las opciones para manejar los datos faltantes.Faltantes completamente al azar (MCAR)
- El primer mecanismo,
MCAR, se produce cuando la falta de datos no está relacionada con la datos. Esto ocurriría, por ejemplo, si se borraran filas de una base de datos al azar, o si una ráfaga de viento se llevara una muestra aleatoria de los formularios de encuesta de un investigador. El mecanismo que rige la ausencia dedrat, mpg, cyl, wt, yvsesMCAR, porque seleccionamos al azar los elementos que faltan. Este mecanismo, aunque es el más fácil de trabajar, rara vez es sostenible en la práctica.
- El primer mecanismo,
Faltantes no al azar (MNAR)
- El mecanismo
MNARse produce cuando la ausencia de una variable está relacionada con la variable en sí misma. Por ejemplo, supongamos que la báscula que pesa cada coche tiene una capacidad de sólo 3.700 libras y, por ello, los ocho coches que pesaban más se registraron comoNA. Este es un ejemplo clásico del mecanismoMNAR, es el propio peso de la observación la causa de que falte. - Otro ejemplo sería si durante el transcurso del ensayo de un fármaco antidepresivo los participantes a los que no les ayudaba el fármaco se deprimían demasiado para continuar con el ensayo. Al final del ensayo, cuando se accede y se registra el nivel de depresión de todos los participantes, habrá valores que falten para los participantes cuyo motivo de ausencia está relacionado con su nivel de depresión.
- El mecanismo
Faltantes al azar (MAR)
El mecanismo, faltante al azar, tiene un nombre un tanto desafortunado, al contrario de lo que pueda parecer, significa que existe una relación sistemática entre la ausencia de una variable resultado y otras variables observadas, pero no la variable de resultado en sí misma.
Un ejemplo es que los hombres son menos propensos a completar una encuesta sobre depresión, pero esto no tiene nada que ver con su nivel de depresión, después de tener en cuenta la masculinidad.
Por ejemplo, puede que todas las observaciones de nuestro conjunto de datos con un valor ausente de fiebre no se hayan registrado porque se asumió que todos los pacientes con escalofríos y dolores tenían fiebre, por lo que nunca se les tomó la temperatura. Si es cierto, podríamos predecir fácilmente que cada observación que falta con escalofríos y dolores también tiene fiebre y utilizar esta información para imputar nuestros datos que faltan.
Observación
- Quizá haya observado que el lugar que ocupa un conjunto de datos
concreto en la taxonomía del mecanismo de datos faltantes depende de las
variables que incluye. Por ejemplo, sabemos que el mecanismo detrás
de
qsecesMAR, pero si el conjunto de datos no incluyeraam, seríaMNAR. Como somos nosotros los que creamos los datos, sabemos el procedimiento que dio lugar a los valores faltantes deqsec. Si no fuéramos nosotros los que creamos los datos, como ocurre en el mundo real, y el conjunto de datos no contuviera la columnaamsimplemente veríamos una cantidad de valoresqsecque faltan arbitrariamente. - Esto podría llevarnos a creer que los datos son
MCAR. Sin embargo, no lo es; sólo porque la variable con la cual otra variable faltante está sistemáticamente relacionada no se observa, no quiere decir que esta no exista. Esto plantea una cuestión crítica: ¿Podemos estar seguros de que nuestros datos no sonMCAR? La respuesta desafortunada es no. Dado que los datos que necesitamos para demostrar o refutar elMNARfaltan ipso facto, la suposición deMNARnunca puede ser desconfirmada de forma concluyente. Es nuestro trabajo, como analistas de datos con pensamiento crítico, preguntar si es probable que haya un mecanismoMNARo no.
- Quizá haya observado que el lugar que ocupa un conjunto de datos
concreto en la taxonomía del mecanismo de datos faltantes depende de las
variables que incluye. Por ejemplo, sabemos que el mecanismo detrás
de
Visualización de datos faltantes
Tomando el conjunto de datos
airquality, un conjunto de datos de mediciones diarias de la calidad del aire en Nueva York de mayo a septiembre de 1973, que tiene valoresNAdentro de sus variables (ver airquality). Las filas del conjunto de datos representan 154 días consecutivos. Cualquier eliminación de estas filas afectará a la continuidad del tiempo, lo que puede afectar a cualquier análisis de series temporales que se realice. Veamos con más detalle el conjunto de datos de la calidad del aireIniciamos visualizando los datos faltantes. Eliminaremos algunos puntos de datos del conjunto de datos para este ejemplo. En lo que respecta a las variables categóricas, la sustitución de las mismas no suele ser aconsejable. Algunas prácticas comunes incluyen la sustitución de las variables categóricas que faltan por la moda de las observadas, sin embargo, es cuestionable si es una buena opción. Aunque en este caso no faltan puntos de datos de las variables categóricas, las eliminamos de nuestro conjunto de datos (podemos volver a añadirlas más tarde si es necesario) y echamos un vistazo a los datos utilizando
summary()
| Ozone | Solar.R | Wind | Temp | Month | Day |
|---|---|---|---|---|---|
| 41 | 190 | 7.4 | 67 | 5 | 1 |
| 36 | 118 | 8.0 | 72 | 5 | 2 |
| 12 | 149 | 12.6 | 74 | 5 | 3 |
| 18 | 313 | 11.5 | 62 | 5 | 4 |
| NA | NA | 14.3 | 56 | 5 | 5 |
| 28 | NA | 14.9 | 66 | 5 | 6 |
## Ozone Solar.R Wind Temp
## Min. : 1.00 Min. : 7.0 Min. : 1.700 Min. :57.00
## 1st Qu.: 18.00 1st Qu.:115.8 1st Qu.: 7.400 1st Qu.:73.00
## Median : 31.50 Median :205.0 Median : 9.700 Median :79.00
## Mean : 42.13 Mean :185.9 Mean : 9.806 Mean :78.28
## 3rd Qu.: 63.25 3rd Qu.:258.8 3rd Qu.:11.500 3rd Qu.:85.00
## Max. :168.00 Max. :334.0 Max. :20.700 Max. :97.00
## NA's :37 NA's :7 NA's :7 NA's :5
| Ozone | Solar.R | Wind | Temp |
|---|---|---|---|
| 41 | 190 | 7.4 | NA |
| 36 | 118 | 8.0 | NA |
| 12 | 149 | 12.6 | NA |
| 18 | 313 | NA | NA |
| NA | NA | NA | NA |
| 28 | NA | NA | 66 |
- Nótese que,
Ozonees la variable con más puntos de datos faltantes. A continuación vamos a profundizar en los patrones de datos que faltan. Suponiendo que los datos seanMCAR, demasiados datos faltantes también pueden ser un problema. Por lo general, un umbral máximo seguro es el 5% del total para conjuntos de datos grandes. Si los datos que faltan para una determinada característica o muestra son superiores al 5%, probablemente deba dejar de lado esa característica o muestra. Por lo tanto, comprobamos las características (columnas) y las muestras (filas) en las que falta más del 5% de los datos mediante una sencilla función
## Ozone Solar.R Wind Temp
## 24.183007 4.575163 4.575163 3.267974
Vemos que al ozono le falta casi el 25% de los puntos de datos, por lo que podríamos considerar la posibilidad de eliminarlo del análisis o de reunir más mediciones. Las demás variables están por debajo del umbral del 5%, por lo que podemos mantenerlas.
Usemos
md.pattern()para conocer mejor el patrón de los datos que faltan
## Temp Solar.R Wind Ozone
## 104 1 1 1 1 0
## 34 1 1 1 0 1
## 3 1 1 0 1 1
## 1 1 1 0 0 2
## 4 1 0 1 1 1
## 1 1 0 1 0 2
## 1 1 0 0 1 2
## 3 0 1 1 1 1
## 1 0 1 0 1 2
## 1 0 0 0 0 4
## 5 7 7 37 56
- La salida nos dice que 104 muestras están completas, que a 34
muestras les falta sólo la medición de
Ozone, que a 4 muestras les falta sólo el valor deSolar.Ry así sucesivamente. Una representación visual quizás más útil puede obtenerse utilizando el paqueteVIMde la siguiente manera
library(VIM)
aggr_plot <- aggr(data, col=c('navyblue','red'), numbers=TRUE, sortVars=TRUE, labels=names(data),
cex.axis=.7, gap=3, ylab=c("Histogram of missing data","Pattern"))##
## Variables sorted by number of missings:
## Variable Count
## Ozone 0.24183007
## Solar.R 0.04575163
## Wind 0.04575163
## Temp 0.03267974
- El gráfico nos ayuda a entender que a casi el 70% de las muestras no
les falta ninguna información, al 22% le falta el valor de
Ozonoy las restantes muestran otros patrones de ausencia. A través de este enfoque, la situación parece un poco más clara. Otra aproximación visual (esperemos) útil es un gráfico de caja especial
- Aquí estamos limitados a trazar sólo 2 variables a la vez, pero sin
embargo podemos obtener algunas ideas interesantes. El diagrama de
caja rojo de la izquierda muestra la distribución de datos faltantes
para
Ozono, mientras que, el diagrama de caja azul muestra la distribución de los puntos de datos restantes. Lo mismo ocurre con los gráficos de caja deSolar.Ren la parte inferior del gráfico. Si nuestra suposición de los datosMCARes correcta, entonces esperamos que los gráficos de caja rojos y azules sean muy similares.
Borrado de la lista
El método más utilizado por los científicos de datos para tratar los datos que faltan es simplemente omitir los casos con datos que faltan, analizando únicamente el resto del conjunto de datos. Este método se conoce como eliminación por lista o análisis de casos completos. La función
na.omit()enRelimina todos los casos con uno o más valores de datos faltantes en un conjunto de datos.La mayor ventaja de este método es su comodidad. Sin embargo, si la naturaleza de los datos es
MCAR, la eliminación de la lista dará lugar a errores estándar que son significativos para los datos reducidos, pero no para todo el conjunto de datos, que tenía los datos que faltaban. Este método de tratar los datos que faltan es posiblemente un desperdicio. Si se eliminan los casos de esta manera, hay que ser consciente de la disminución de la capacidad para detectar el verdadero efecto de las variables de interés.Sin embargo, si los datos no son
MCAR, el análisis de casos completos puede influir mucho en las estimaciones de la media, los coeficientes de regresión y las correlaciones. La supresión de la lista puede causar submuestras sin sentido.A continuación, realizamos una simple función
na.omit()para eliminar los casos que tienenNAs. Vemos que todas las filas que contenían algúnNAen cualquier variable son eliminadas del dataframe
## Ozone Solar.R Wind Temp
## 12 16 256 9.7 69
## 13 11 290 9.2 66
## 14 14 274 10.9 68
## 15 18 65 13.2 58
## 16 14 334 11.5 64
## 17 34 307 12.0 66
- Dibujemos los histogramas antes y después de la
imputación/eliminación usando
ggplot
ggp1 <- ggplot(data.frame(value=data$Ozone), aes(x=value)) +
geom_histogram(fill="#FBD000", color="#E52521", alpha=0.9) +
ggtitle("Original data") +
xlab('Ozone') + ylab('Frequency') +
theme_ipsum() +
theme(plot.title = element_text(size=15))
ggp2 <- ggplot(data.frame(value=airquality_omit$Ozone), aes(x=value)) +
geom_histogram(fill="#43B047", color="#049CD8", alpha=0.9) +
ggtitle("Listwise Deletion") +
xlab('Ozone') + ylab('Frequency') +
theme_ipsum() +
theme(plot.title = element_text(size=15))
grid.arrange(ggp1, ggp2, ncol = 2)Imputación por emparejamiento predictivo medio
Algunos científicos de datos o estadísticos pueden buscar una solución rápida sustituyendo los datos que faltan por la media. La media se utiliza a menudo para imputar datos no categóricos. Por ejemplo, en el conjunto de datos de calidad del aire, supongamos que queremos imputar la media de sus valores faltantes. En este caso, utilizamos el paquete
Rmice. Cambiando el argumentomethod = mean, se especifica la imputación de la media, el argumentom = 1cambia las iteraciones a 1, lo que significa no (iteración).El fundamento teórico de utilizar la media para imputar los datos faltantes es que la media es una buena estimación para seleccionar aleatoriamente una observación de una distribución normal. Ahora, intentaremos imputar la media para las variables
OzonoySolar.Rdel conjunto de datosairquality. En primer lugar, vamos a cargar los paquetesmiceymipackages(instale el paquete deCRANprimero usandoinstall.packages("mice")yinstall.packages("mi")). Para mas información sobre el paquete utilizado (ver Mice). Podemos recuperar el conjunto de datos completo utilizando la funcióncomplete().
- Los datos faltantes para
OzonoySolar.Rpueden ser imputados por la funciónmice().
El parámetro
mse refiere al número de conjuntos de datos imputados que se generarán. Es decir,mrepresenta la cantidad de veces que se imputarán los datos para crear múltiples conjuntos de datos imputados. Cada conjunto de datos imputados contendrá valores imputados diferentes para los datos faltantes, lo que permitecapturar la incertidumbre en el proceso de imputacióno falta de certeza sobre los valores reales de los datos faltantes.El parámetro
maxiten el paquete mice deRcontrola cuántas veces se repetirá el proceso de imputación en cada conjunto de datos para alcanzar una convergencia o un límite máximo de iteraciones. Durante cada iteración, el algoritmo intenta mejorar la imputación de los valores faltantes. Si el número de iteraciones alcanza el valor especificado enmaxity el proceso no converge, la imputación se detendrá y se generará un mensaje de advertencia. Ajustar adecuadamente el valor demaxitpuede ayudar a controlar el tiempo de ejecución y garantizar una convergencia adecuada del algoritmo de imputación.El valor por defecto es
m=5.method='pmm'(ver mice.impute.pmm) se refiere al método de imputación. En este caso, utilizamos el método de imputación por emparejamiento predictivo medio (PMM). Se pueden utilizar otros métodos de imputación, escribamethods(mice)para obtener una lista de los métodos de imputación disponibles.
## [1] mice.impute.2l.bin mice.impute.2l.lmer
## [3] mice.impute.2l.norm mice.impute.2l.pan
## [5] mice.impute.2lonly.mean mice.impute.2lonly.norm
## [7] mice.impute.2lonly.pmm mice.impute.cart
## [9] mice.impute.jomoImpute mice.impute.lasso.logreg
## [11] mice.impute.lasso.norm mice.impute.lasso.select.logreg
## [13] mice.impute.lasso.select.norm mice.impute.lda
## [15] mice.impute.logreg mice.impute.logreg.boot
## [17] mice.impute.mean mice.impute.midastouch
## [19] mice.impute.mnar.logreg mice.impute.mnar.norm
## [21] mice.impute.mpmm mice.impute.norm
## [23] mice.impute.norm.boot mice.impute.norm.nob
## [25] mice.impute.norm.predict mice.impute.panImpute
## [27] mice.impute.passive mice.impute.pmm
## [29] mice.impute.polr mice.impute.polyreg
## [31] mice.impute.quadratic mice.impute.rf
## [33] mice.impute.ri mice.impute.sample
## [35] mice.mids mice.theme
## see '?methods' for accessing help and source code
## Ozone Solar.R Wind Temp
## 1 41 190 7.4 87
## 2 36 118 8.0 80
## 3 12 149 12.6 74
## 4 18 313 10.9 66
## 5 13 81 16.6 57
## 6 28 78 7.4 66
- La siguiente imagen ilustra el funcionamiento del método de imputación múltiple por ecuaciones encadenadas
- Además, la siguiente figura ilustra el funcionamiento del
método de imputación por emparejamiento predictivo
medio usado en el ejemplo para
airquality
Observación: El método
PMMutiliza aleatoriedad en la imputación para introducir variabilidad en las estimaciones de los valores faltantes. Esto ayuda a evitar la sobreestimación o subestimación sistemática de los valores imputados y a capturar la incertidumbre asociada con la imputación de datos faltantes. La introducción de aleatoriedad también puede ayudar a mejorar la robustez y generalización del modelo de imputación, especialmente en situaciones donde los datos faltantes siguen patrones complejos o no aleatorios.Tracemos histograma y diagrama de dispersión del aspecto del conjunto de datos de la calidad del aire tras la imputación mediante método PMM. Los siguientes son los histogramas asociados
ggp1 <- ggplot(data.frame(value=data$Ozone), aes(x=value)) +
geom_histogram(fill="#FBD000", color="#E52521", alpha=0.9) +
ggtitle("Original data") +
xlab('Ozone') + ylab('Frequency') +
theme_ipsum() +
theme(plot.title = element_text(size=15))
ggp2 <- ggplot(data.frame(value=imp_df$Ozone), aes(x=value)) +
geom_histogram(fill="#43B047", color="#049CD8", alpha=0.9) +
ggtitle("PMM imputation") +
xlab('Ozone') + ylab('Frequency') +
theme_ipsum() +
theme(plot.title = element_text(size=15))
grid.arrange(ggp1, ggp2, ncol = 2)- Vamos a comparar las distribuciones de los datos originales e
imputados utilizando algunos gráficos útiles. En primer lugar,
podemos utilizar un gráfico de dispersión y
comparar
Ozonecon todas las demás variables.
- Los valores de
pchestán asociados con los símbolos gráficos desplegados.cexcontrola el tamaño de los símbolos, por defecto el tamaño es 1, podemos poner valores menores o superiores.
- Lo que queremos ver es que la forma de los puntos rojos (imputados) coincida con la de los azules (observados). La coincidencia de la forma nos indica que los valores imputados son realmente valores plausibles. Otro gráfico útil es el de la densidad:
La densidad de los datos imputados para cada conjunto de datos imputados se muestra en color rojo, mientras que la densidad de los datos observados se muestra en azul. De nuevo, según nuestros supuestos anteriores, esperamos que las distribuciones sean similares.
Es muy recomendable comprobar visualmente la convergencia. Lo comprobamos cuando llamamos a la función de trazado a la que asignamos la salida de
mice, para mostrar gráficos de seguimiento de la media y desviación estándar de todas las variables implicadas en las imputaciones.
- Cada línea es una de las
mconjuntos de datos imputados, a saberm=5por defecto. Como se puede ver en el gráfico de trazado anterior sobreimp, no hay tendencias claras y las variables se superponen de una iteración a la siguiente. Dicho de otro modo, la varianza dentro de una cadena (haymcadenas) debería ser aproximadamente igual a la varianza entre las cadenas. Esto indica que se ha logrado la convergencia. Si no se logra la convergencia, se puede aumentar el número de iteraciones quemiceemplea especificando explícitamente el parámetromaxita la funciónmice.
Otras técnicas de imputación
La función
miceentrega distintos métodos de imputación los cuales puede estudiar y aplicar, de acuerdo a lo que requieran sus datos. Puede aplicar la imputación por regresión en R con la configuración del métodomethod = "norm.predict"en la funciónmice. Puede aplicar la imputación de regresión estocástica enRcon la funciónmiceutilizando el métodomethod = "norm.nob".El paquete
micetambién incluye un procedimiento de imputación de regresión estocástica Bayesiana. Puede aplicar este procedimiento de imputación con la funciónmicey utilizar como métodomethod = "norm". Para estos métodos debe seleccionar primero dos columna del conjunto de datos de interés para ajustar constantes del modelo.
##
## iter imp variable
## 1 1 Solar.R Wind
## 1
## 4 9.736111
## 5 9.806622
## 6 9.805925
## 7 9.743832
## 8 9.854140
## 9 9.898263
## 10 9.801744
- En la actualidad, hay un número limitado de análisis que pueden ser
automáticamente agrupados por
miceel más importante es el delm/glm. Sin embargo, si se recuerda, el modelo lineal generalizado es extremadamente flexible, y puede utilizarse para expresar una amplia gama de análisis diferentes. Por extensión, podríamos utilizar la imputación múltiple no sólo para regresión lineal, sino para la regresión logística, la regresión de Poisson, las pruebas \(t\), elANOVAANCOVA, etc. Cada una de estas temáticas pueden ser abordadas como proyectos finales en este curso.
Detección de valores atípicos
Un valor atípico es un valor o una observación que se aleja de otras observaciones, es decir, un punto de datos que difiere significativamente de otros puntos de datos. Enderlein (1987) va incluso más allá, ya que el autor considera que los valores atípicos son aquellos que se desvían tanto de otras observaciones que se podría suponer un mecanismo de muestreo subyacente diferente.
Una observación debe compararse siempre con otras observaciones realizadas sobre el mismo fenómeno antes de calificarla realmente de atípica. En efecto, una persona de 200 cm de altura (1,90 m en EE.UU.) se considerará probablemente un valor atípico en comparación con la población general, pero esa misma persona podría no considerarse un valor atípico si midiéramos la altura de los jugadores de baloncesto.
En esta sección, presentamos varios enfoques para detectar valores atípicos en
R, desde técnicas sencillas como la estadística descriptiva (que incluye el mínimo, el máximo, el histograma, el boxplot y los percentiles) hasta técnicas más formales como el filtro deHampel, elGrubbs, elDixony las pruebas deRosnerpara detectar valores atípicos. Algunas pruebas estadísticas exigen la ausencia de valores atípicos para sacar conclusiones sólidas, pero la eliminación de valores atípicos no se recomienda en todos los casos y debe hacerse con precaución.
Estadísticas descriptivas
- El primer paso para detectar los valores atípicos en
Res comenzar con algunos estadísticos descriptivos, y en particular con el mínimo y el máximo. EnR, esto se puede hacer fácilmente con la funciónsummary(). El conjunto de datos a utilizar esmpgasociado a consumo de combustible de 1999 a 2008 de 38 modelos populares de coches (ver ggplot2::mpg).
## # A tibble: 6 × 11
## manufacturer model displ year cyl trans drv cty hwy fl class
## <chr> <chr> <dbl> <int> <int> <chr> <chr> <int> <int> <chr> <chr>
## 1 audi a4 1.8 1999 4 auto(l5) f 18 29 p compa…
## 2 audi a4 1.8 1999 4 manual(m5) f 21 29 p compa…
## 3 audi a4 2 2008 4 manual(m6) f 20 31 p compa…
## 4 audi a4 2 2008 4 auto(av) f 21 30 p compa…
## 5 audi a4 2.8 1999 6 auto(l5) f 16 26 p compa…
## 6 audi a4 2.8 1999 6 manual(m5) f 18 26 p compa…
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 12.00 18.00 24.00 23.44 27.00 44.00
- El mínimo y el máximo son, respectivamente, el primer y el último
valor de
hwy. Como alternativa, también pueden calcularse con las funcionesmin()ymax()
## [1] 12
## [1] 44
- Un claro error de codificación, como un peso de 786 kg (1733 libras) para un humano, ya se detectará fácilmente con esta técnica tan sencilla.
Histogramas
- Otra forma básica de detectar valores atípicos es dibujar un
histograma de los datos. Utilizando la base de
R(con el número debinscorrespondiente a la raíz cuadrada del número de observaciones para tener másbinsque la opción por defecto), o usandoggplot2. Considere la columnahwy: highway mileage(miles per gallon)
library(ggplot2)
ggplot(dat) +
aes(x = hwy) +
geom_histogram(bins = 30L, fill = "#0c4c8a") +
theme_minimal()- Con base en este histograma, se evidencia que hay un par de observaciones más altas que todas las demás
Boxplot
- Además de los histogramas, los boxplots también son útiles para
detectar posibles valores atípicos. Considere nuevamente a manera
de ejemplo la columna
hwy
Un diagrama de caja ayuda a visualizar una variable cuantitativa mostrando cinco resúmenes de localización comunes (mínimo, mediana, primer y tercer cuartil y máximo) y cualquier observación que se haya clasificado como presunto valor atípico utilizando el criterio del rango intercuartílico
(IQR).Las observaciones consideradas como posibles valores atípicos según el criterio
IQRse muestran como puntos en el diagrama de caja. Según este criterio, hay 2 valores atípicos potenciales (véanse los 2 puntos por encima de la línea vertical, en la parte superior delboxplot).Recuerde que no por el hecho de que una observación sea considerada como un valor atípico potencial por el criterio IQR debe eliminarla. Eliminar o mantener un valor atípico depende de: (i) el contexto de su análisis, (ii) si las pruebas que va a realizar en el conjunto de datos son robustas a los valores atípicos o no, y (iii) a qué distancia está el valor atípico de otras observaciones.
También es posible extraer los valores de los posibles valores atípicos basándose en el criterio
IQRgracias a la funciónboxplot.stats()$out:
## [1] 44 44 41
- Como puede ver, en realidad hay 3 puntos considerados como
posibles valores atípicos: 2 observaciones con un valor de 44 y 1
observación con un valor de 41. Gracias a la función
which()es posible extraer el número de fila correspondiente a estos valores atípicos:
## [1] 213 222 223
- Con esta información, ahora puede volver fácilmente a las filas específicas del conjunto de datos para verificarlas, o imprimir todas las variables para estos valores atípicos:
## # A tibble: 3 × 11
## manufacturer model displ year cyl trans drv cty hwy fl class
## <chr> <chr> <dbl> <int> <int> <chr> <chr> <int> <int> <chr> <chr>
## 1 volkswagen jetta 1.9 1999 4 manua… f 33 44 d comp…
## 2 volkswagen new beetle 1.9 1999 4 manua… f 35 44 d subc…
## 3 volkswagen new beetle 1.9 1999 4 auto(… f 29 41 d subc…
- También es posible imprimir los valores de los valores atípicos
directamente en el boxplot con la función
mtext()
boxplot(dat$hwy,
ylab = "hwy",
main = "Boxplot of highway miles per gallon"
)
mtext(paste("Outliers: ", paste(out, collapse = ", ")))Percentiles
- Este método de detección de valores atípicos se basa en los percentiles. Con el método de los percentiles, todas las observaciones que se encuentren fuera del intervalo formado por los percentiles 2.5 y 97.5 se considerarán como posibles valores atípicos
\[ I_{\text{out}}=[q_{0.025}; q_{0.975}]^{C} \]
- También pueden considerarse otros percentiles, como el 1 y
el 99, o el 5 y el 95, para construir el intervalo. Los valores
de los percentiles inferior y superior (y, por tanto, los límites
inferior y superior del intervalo) pueden calcularse con la función
quantile()
## 2.5%
## 14
## 97.5%
## 35.175
- Según este método, todas las observaciones por debajo de
14 y por encima de 35.175 se considerarán posibles valores
atípicos. Los números de fila de las observaciones fuera
del intervalo pueden extraerse entonces con la función
which()
## [1] 55 60 66 70 106 107 127 197 213 222 223
## # A tibble: 11 × 1
## hwy
## <int>
## 1 12
## 2 12
## 3 12
## 4 12
## 5 36
## 6 36
## 7 12
## 8 37
## 9 44
## 10 44
## 11 41
## # A tibble: 11 × 11
## manufacturer model displ year cyl trans drv cty hwy fl class
## <chr> <chr> <dbl> <int> <int> <chr> <chr> <int> <int> <chr> <chr>
## 1 dodge dakota pi… 4.7 2008 8 auto… 4 9 12 e pick…
## 2 dodge durango 4… 4.7 2008 8 auto… 4 9 12 e suv
## 3 dodge ram 1500 … 4.7 2008 8 auto… 4 9 12 e pick…
## 4 dodge ram 1500 … 4.7 2008 8 manu… 4 9 12 e pick…
## 5 honda civic 1.8 2008 4 auto… f 25 36 r subc…
## 6 honda civic 1.8 2008 4 auto… f 24 36 c subc…
## 7 jeep grand che… 4.7 2008 8 auto… 4 9 12 e suv
## 8 toyota corolla 1.8 2008 4 manu… f 28 37 r comp…
## 9 volkswagen jetta 1.9 1999 4 manu… f 33 44 d comp…
## 10 volkswagen new beetle 1.9 1999 4 manu… f 35 44 d subc…
## 11 volkswagen new beetle 1.9 1999 4 auto… f 29 41 d subc…
- Hay 11 valores atípicos potenciales según el método de los percentiles. Para reducir este número, puede establecer los percentiles en 1 y 99:
lower_bound <- quantile(dat$hwy, 0.01)
upper_bound <- quantile(dat$hwy, 0.99)
outlier_ind <- which(dat$hwy < lower_bound | dat$hwy > upper_bound)
dat[outlier_ind, ]## # A tibble: 3 × 11
## manufacturer model displ year cyl trans drv cty hwy fl class
## <chr> <chr> <dbl> <int> <int> <chr> <chr> <int> <int> <chr> <chr>
## 1 volkswagen jetta 1.9 1999 4 manua… f 33 44 d comp…
## 2 volkswagen new beetle 1.9 1999 4 manua… f 35 44 d subc…
## 3 volkswagen new beetle 1.9 1999 4 auto(… f 29 41 d subc…
Filtro de Hampel
Otro método, conocido como filtro de Hampel, consiste en considerar como valores atípicos los valores fuera del intervalo formado por la mediana, más o menos 3 desviaciones absolutas de la mediana
\[ I=[\tilde{X}-3\cdot\textsf{MAD}, \tilde{X}+3\cdot\textsf{MAD}], \] donde
MADes la desviación absoluta de la mediana y se define como la mediana de las desviaciones absolutas de la mediana \(\tilde{X}=\textsf{median}(X)\) de los datos\[ \textsf{MAD}=k\times\textsf{median}(|X_{i}-\tilde{X}|) \]
Para este método, primero establecemos los límites del intervalo, gracias a las funciones
median()ymad(). En este caso,krepresenta el factor de escala, por defecto,Rlo considera como 1.
## [1] 9
## [1] 39
- Según este método, todas las observaciones por debajo de 9 y por
encima de 39 se considerarán como posibles valores atípicos. Los
números de fila de las observaciones que están fuera del intervalo
pueden entonces extraerse con la función
which(). Según el filtro deHampel, hay 3 valores atípicos para la variablehwy.
## [1] 213 222 223
## # A tibble: 3 × 11
## manufacturer model displ year cyl trans drv cty hwy fl class
## <chr> <chr> <dbl> <int> <int> <chr> <chr> <int> <int> <chr> <chr>
## 1 volkswagen jetta 1.9 1999 4 manua… f 33 44 d comp…
## 2 volkswagen new beetle 1.9 1999 4 manua… f 35 44 d subc…
## 3 volkswagen new beetle 1.9 1999 4 auto(… f 29 41 d subc…
Prueba de Grubbs
La prueba de
Grubbspermite detectar si el valor más alto o más bajo de un conjunto de datos es un valor atípico (ver Grubbs). La prueba deGrubbsdetecta un valor atípico cada vez (valor más alto o más bajo), por lo que las hipótesis nula y alternativa son las siguientes- \(H_{0}\): El valor más alto/bajo no es un valor atípico
- \(H_{1}\): El valor más alto/bajo es un valor atípico
Como para cualquier prueba estadística, si el valor \(p\) es inferior al umbral de significación elegido (generalmente \(\alpha=0.05\)), se rechaza la hipótesis nula y se concluye que el valor más bajo/más alto es un valor atípico. Por el contrario, si el valor \(p\) es mayor o igual que el nivel de significación, no se rechaza la hipótesis nula y concluiremos que, basándonos en los datos, no rechazamos la hipótesis de que el valor más bajo/más alto no es un valor atípico. Tenga en cuenta que la prueba de
Grubbsno es apropiada para un tamaño de muestra \(n\leq6\).Para realizar la prueba de
GrubbsenR, utilizamos la funcióngrubbs.test()del paqueteoutliers. La función recibe los siguientes argumentos:grubbs.test(x, type = 10, opposite = FALSE, two.sided = FALSE). En este caso,type: 10 = prueba si el valor máximo es un valor atípico, 11 = prueba si tanto el valor mínimo como el máximo son valores atípicos, 20 = prueba si hay dos valores atípicos en una cola.
##
## Grubbs test for one outlier
##
## data: dat$hwy
## G = 3.45274, U = 0.94862, p-value = 0.05555
## alternative hypothesis: highest value 44 is an outlier
- El valor \(p\) es de 0.056 approx.
Al nivel de significación del 5%, no rechazamos la hipótesis de que
el valor más alto 44 no es un valor atípico. No tenemos
evidencia suficiente para decir que 44 es un valor
atípico. Por defecto, la prueba se realiza sobre el valor
más alto (como se muestra en la salida de
R: hipótesis alternativa: el valor más alto 44 es un valor atípico). Si desea realizar la prueba para el valor más bajo, simplemente añada el argumentoopposite = TRUEen la funcióngrubbs.test()
##
## Grubbs test for one outlier
##
## data: dat$hwy
## G = 1.92122, U = 0.98409, p-value = 1
## alternative hypothesis: lowest value 12 is an outlier
La salida de
Rindica que la prueba se realiza ahora sobre el valor más bajo (véase la hipótesis alternativa: el valor más bajo 12 es un valor atípico). El valor \(p\) es 1. Al nivel de significación del 5%, no rechazamos la hipótesis de que el valor más bajo 12 no es un valor atípico.A modo de ilustración, sustituiremos ahora una observación por un valor más extremo y realizaremos la prueba de
Grubbsen este nuevo conjunto de datos. Reemplacemos el \(34^{\text{th}}\) por un valor de 212
- Y ahora aplicamos la prueba de
Grubbspara comprobar si el valor más alto es un valor atípico
##
## Grubbs test for one outlier
##
## data: dat$hwy
## G = 13.72240, U = 0.18836, p-value < 2.2e-16
## alternative hypothesis: highest value 212 is an outlier
- El valor \(p\) es menor que la significancia. Al nivel de significancia del 5%, concluimos que el valor más alto 212 es un valor atípico.
Prueba de Dixon
Al igual que la prueba de Grubbs, la prueba de Dixon se utiliza para comprobar si un único valor bajo o alto es un valor atípico. Por lo tanto, si se sospecha que hay más de un valor atípico, la prueba tiene que realizarse en estos valores atípicos sospechosos individualmente. Tenga en cuenta que la prueba de
Dixones más útil para muestras de pequeño tamaño (normalmente \(n\leq 25\)).Para realizar la prueba de
DixonenR, utilizamos la funcióndixon.test()del paqueteoutliers. Sin embargo, restringimos nuestro conjunto de datos a las20primeras observaciones, ya que la prueba deDixonsólo se puede realizar en muestras de pequeño tamaño (Rarrojará un error y sólo acepta conjuntos de datos de 3 a 30 observaciones)
##
## Dixon test for outliers
##
## data: subdat$hwy
## Q = 0.57143, p-value = 0.006508
## alternative hypothesis: lowest value 15 is an outlier
- Los resultados muestran que el valor más bajo, 15, es un
valor atípico, con valor \(p\) menor que la significancia del 5%. Para
comprobar el valor más alto, basta con añadir el argumento
opuesto = TRUEa la funcióndixon.test()
##
## Dixon test for outliers
##
## data: subdat$hwy
## Q = 0.25, p-value = 0.8582
## alternative hypothesis: highest value 31 is an outlier
- Los resultados muestran que el valor más alto 31 no es un valor atípico (valor \(p = 0.8582\)). Es una buena práctica comprobar siempre los resultados de la prueba estadística de valores atípicos con el diagrama de caja para asegurarse de que hemos comprobado todos los valores atípicos potenciales
out <- boxplot.stats(subdat$hwy)$out
boxplot(subdat$hwy, ylab = "hwy")
mtext(paste("Outliers: ", paste(out, collapse = ", ")))- A partir del
boxplot, vemos que también podríamos aplicar la prueba deDixonsobre el valor 20 además del valor 15 realizado anteriormente. Esto puede hacerse encontrando el número de fila del valor mínimo, excluyendo este número de fila del conjunto de datos y aplicando finalmente la prueba deDixona este nuevo conjunto de datos
## # A tibble: 6 × 11
## manufacturer model displ year cyl trans drv cty hwy fl class
## <chr> <chr> <dbl> <int> <int> <chr> <chr> <int> <int> <chr> <chr>
## 1 audi a4 quattro 3.1 2008 6 auto… 4 17 25 p comp…
## 2 audi a4 quattro 3.1 2008 6 manu… 4 15 25 p comp…
## 3 audi a6 quattro 2.8 1999 6 auto… 4 15 24 p mids…
## 4 audi a6 quattro 3.1 2008 6 auto… 4 17 25 p mids…
## 5 audi a6 quattro 4.2 2008 8 auto… 4 16 23 p mids…
## 6 chevrolet c1500 subu… 5.3 2008 8 auto… r 14 20 r suv
##
## Dixon test for outliers
##
## data: subsubdat$hwy
## Q = 0.44444, p-value = 0.1297
## alternative hypothesis: lowest value 20 is an outlier
- Los resultados muestran que el segundo valor más bajo, 20, no es un valor atípico (valor \(p\) = 0.13).
Prueba de Rosner
La prueba de
Rosnerpara detectar valores atípicos tiene las siguientes ventajas. Se utiliza para detectar varios valores atípicos a la vez (a diferencia de la prueba deGrubbsyDixon, que debe realizarse de forma iterativa para detectar múltiples valores atípicos), y está diseñado para evitar el problema del enmascaramiento, en el que un valor atípico cercano a otro atípico puede pasar desapercibido. A diferencia de la prueba deDixon, hay que tener en cuenta que la prueba deRosneres más apropiada cuando el tamaño de la muestra es grande (\(n\geq 20\)). Por tanto, volvemos a utilizar el conjunto de datos inicialdat, que incluye 234 observaciones.Para realizar la prueba de
Rosnerutilizamos la funciónrosnerTest()del paqueteEnvStats. Esta función requiere al menos 2 argumentos: los datos y el número de presuntos valores atípicosk(conk = 3como número de presuntos valores atípicos por defecto). Para este ejemplo, establecemos que el número de presuntos valores atípicos sea igual a 3, tal y como sugiere el número de posibles valores atípicos esbozado en elboxplotal principio del artículo.
- Los resultados interesantes se ofrecen en la tabla
all.stats
## i Mean.i SD.i Value Obs.Num R.i+1 lambda.i+1 Outlier
## 1 0 24.21795 13.684345 212 34 13.722399 3.652091 TRUE
## 2 1 23.41202 5.951835 44 213 3.459098 3.650836 FALSE
## 3 2 23.32328 5.808172 44 222 3.559936 3.649575 FALSE
- Basándonos en la prueba de
Rosner, vemos que sólo hay un valor atípico (véase la columna de valores atípicos), y que es la observación34(véaseObs.Num) con un valor de212(véaseValue).
Tratamiento de los valores atípicos
Una vez identificados los valores atípicos y habiendo decidido enmendar la situación según la naturaleza del problema, puede considerar uno de los siguientes enfoques.
Imputación
- Este método se ha tratado en detalle en la discusión sobre el tratamiento de los valores faltantes.
Capping
- Para los valores que se encuentran fuera de los límites de \(1.5\cdot\mathsf{IQR}\), podríamos poner un tope sustituyendo las observaciones que se encuentran fuera del límite inferior por el valor del \(5^{\textsf{th}}\) percentíl y las que se encuentran por encima del límite superior, por el valor del \(95^{\textsf{th}}\) percentíl. A continuación se muestra un código de ejemplo que logra esto
x <- dat$hwy
qnt <- quantile(x, probs=c(.25, .75), na.rm = T) #Quartiles
caps <- quantile(x, probs=c(.05, .95), na.rm = T) #5th,95th Percentiles## [1] 29 29 31 30 26 26
Predicción
- En otro enfoque, los valores atípicos pueden sustituirse por
valores faltantes
(NA)y luego pueden predecirse considerándolos como una variable de respuesta. Ya hemos hablado de cómo predecir los valores faltantes en la sección anterior.
- En otro enfoque, los valores atípicos pueden sustituirse por
valores faltantes
Ejercicio para entregar
- Realicé un EDA para el siguiente conjunto de datos (ver Diabetes
Datasert). El conjunto de datos consta de varias variables médicas
predictoras (independientes) y una variable objetivo (dependiente),
resultado. Las variables independientes incluyen el número de embarazos
que ha tenido la paciente, BMI (Body mass index (weight in kg/(height in
m)^2), nivel de insulina, edad, etc. En este dataset, sustituimos todos
los valores nulos “ausentes” por NAN y realizamos el respectivo análisis
de datos faltantes. Nótese que está escrito en
Python, escriba el equivalente enR.
df.loc[df["Glucose"] == 0.0, "Glucose"] = np.NAN
df.loc[df["BloodPressure"] == 0.0, "BloodPressure"] = np.NAN
df.loc[df["SkinThickness"] == 0.0, "SkinThickness"] = np.NAN
df.loc[df["Insulin"] == 0.0, "Insulin"] = np.NAN
df.loc[df["BMI"] == 0.0, "BMI"] = np.NANAplique imputación de datos usando las siguientes opciones para method:
method="pmm"”method="norm.predict"method="norm.nob"method="norm"
Identifique datos atípicos para cada variable en el dataset usando las técnicas estudiadas en clase. Además, realice imputación de los datos atípicos con base en lo desarrollado en el ítem anterior.
- Imputación/Capping/Predicción
- Test de Rosner, Dixon, Grubbs, Hampel, Percentiles, Boxplots, Histograms, Descriptivos
Modelo de series de tiempo
- En esta sección estudiaremos la forma de implementar el modelo
ARIMAestudiado antes en la sección dePython. La teoría abordada para este modelo es la misma, por lo tanto, algunas cosas serán omitidas ypasaremos directamente a la implementación en Rde este modelo predictivo.
Modelo ARIMA
El modelo Media Móvil Integrada Autorregresiva
(ARIMA)es el nombre genérico de una familia de modelos predictivos que se basan en los procesos Autorregresivo(AR)y de Media Móvil(MA).Entre los modelos predictivos tradicionales (por ejemplo, la regresión lineal, suavización exponencial, etc.), el modelo ARIMA se considera el enfoque más avanzado y robusto.
En esta sección, presentaremos los componentes del modelo: los procesos
ARyMAy el componente de diferenciaciónI. Además, nos centraremos en los métodos y enfoques para ajustar los parámetros del modelo con el uso de diferenciación, la función de autocorrelación(ACF)y la función de autocorrelación parcial(PACF).En esta sección cubriremos lo siguiente
- El
proceso estacionario - El proceso
random walk- Random walk: \(X_{t}=X_{t-1}+\varepsilon_{t}\), donde \(\varepsilon_{t}\), v. a. determina la dirección del movimiento tomando valores “izquierda (-1) o derecha (1)” con igual probabilidad.
- Los procesos
ARyMA - Los modelos
ARMAyARIMA - El modelo
ARIMAestacional
- El
Librerías:
forecastTSstudioplotlydplyrlubridatestatsdatasetsbase
El proceso estacionario
Uno de los principales supuestos de la familia de modelos ARIMA es que las series de entrada siguen la estructura de un proceso estacionario. Este supuesto se basa en el Teorema de representación de Wold, que afirma que cualquier proceso estacionario puede representarse como una combinación lineal de ruido blanco.
Por lo tanto, antes de sumergirnos en los componentes del modelo
ARIMA, vamos a detenernos a hablar del proceso estacionario. El proceso estacionario, en el contexto de los datos de las series temporales, describe un estado estocástico de la serie. Los datos de las series temporales son estacionarios si se dan las siguientes condiciones:- La media y la varianza de la serie no cambian con el tiempo: \(E(X_{t})=E(X_{t+k})=\mu\) y \(V(X_{t})=V(X_{t+k})=\sigma^2\)
- La estructura de correlación de la serie, junto con sus rezagos, permanece igual a lo largo del tiempo tiempo: \[\gamma_{k}=E[(X_{t}-\mu)(X_{t+k}-\mu)]\]
En los siguientes ejemplos, utilizaremos la función
arima.simdel paquetestatspara simular datos de series temporales estacionarios y no estacionarios y los representaremos con la funciónts_plotdel paqueteTSstudio. La funciónarima.simnos permite simular datos de series temporales basándonos en los componentes y características principales del modeloARIMA:Un proceso autorregresivo (AR): Establece una relación entre la serie y sus \(p\) rezagos pasados con el uso de un modelo de regresión
Un proceso de media móvil (MA): Similar al proceso
AR, el procesoMAestablece la relación con el término de error en el tiempo \(t\) y los términos de error pasados, con el uso de regresión entre los dos componentesProceso integrado (I): El proceso de diferenciar la serie con sus \(d\) rezagos para transformar la serie en un estado estacionario. Aquí, el argumento del modelo de la función define \(p, q\) y \(d\), así como el orden de los procesos
AR, MA, y los procesosIdel modelo.
Consideraríamos una serie temporal de datos como no estacionaria siempre que las condiciones mencionadas anteriormente no se cumplan. Los ejemplos más comunes de una serie con una estructura no estacionaria son los siguientes:
Serie con tendencia dominante: La media de la serie cambia a lo largo del tiempo como función del cambio en la tendencia de la serie, y por tanto la serie es no estacionaria
Serie con componente estacional multiplicativo: En este caso, la varianza de la serie es una función de la oscilación estacional a lo largo del tiempo, que aumenta o disminuye con el tiempo
La serie clásica
AirPassenger(el número mensual de pasajeros de las aerolíneas entre 1949 y 1960) del paquete de conjuntos de datos es un buen ejemplo de una serie que viola las dos condiciones del proceso estacionario.Puede visualizar la serie usando
View(AirPassengers). Dado que la serie tiene tanto una fuerte tendencia lineal como un componente estacional multiplicativo, tanto la media como la varianza cambian con el tiempo:
## [1] "ts"
## Time-Series [1:144] from 1949 to 1961: 112 118 132 129 121 135 148 148 136 119 ...
ts_plot(AirPassengers,
title = "Monthly Airline Passenger Numbers 1949-1960",
Ytitle = "Thousands of Passengers",
Xtitle = "Year")## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 104.0 180.0 265.5 280.3 360.5 622.0
hist(AirPassengers, main = "Histgram for Air Passengers", xlab = "Passengers", breaks = "Sturges", probability = TRUE)
lines(density(AirPassengers))## [1] 0
## [1] 12
## Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
## 1949 1 2 3 4 5 6 7 8 9 10 11 12
## 1950 1 2 3 4 5 6 7 8 9 10 11 12
## 1951 1 2 3 4 5 6 7 8 9 10 11 12
## 1952 1 2 3 4 5 6 7 8 9 10 11 12
## 1953 1 2 3 4 5 6 7 8 9 10 11 12
## 1954 1 2 3 4 5 6 7 8 9 10 11 12
## 1955 1 2 3 4 5 6 7 8 9 10 11 12
## 1956 1 2 3 4 5 6 7 8 9 10 11 12
## 1957 1 2 3 4 5 6 7 8 9 10 11 12
## 1958 1 2 3 4 5 6 7 8 9 10 11 12
## 1959 1 2 3 4 5 6 7 8 9 10 11 12
## 1960 1 2 3 4 5 6 7 8 9 10 11 12
boxplot(AirPassengers~cycle(AirPassengers),xlab="Date", ylab = "Passenger Numbers (1000's)" ,main ="Monthly Air Passengers Boxplot from 1949 to 1961")Transformación de una serie no estacionaria en una serie serie estacionaria
- En la mayoría de los casos, a menos que tenga mucha suerte, sus datos brutos probablemente vendrán con una tendencia u otra forma de oscilación que viole los supuestos del proceso estacionario. Por lo tanto, para manejar esto, tendrá que aplicar algunos pasos de transformación con el fin de llevar la serie a un estado estacionario. Los métodos de transformación más comunes son la diferenciación de la serie (detrending) y la transformación logarítmica (o ambas). Repasemos las aplicaciones de estos métodos.
Diferenciación de series temporales
El método más habitual para transformar los datos de una serie temporal no estacionaria en un estado estacionario es diferenciar la serie con sus rezagos. El principal efecto de diferenciar una serie es la eliminación de la tendencia de la serie (o la pérdida de tendencia de la serie), que ayuda a estabilizar la media de la serie.
Medimos el grado u orden de diferenciación de la serie por el número de veces que diferenciamos la serie con sus rezagos. Por ejemplo, la siguiente ecuación define la diferencia de primer orden:
\[ Y_{t}'=Y_{t}-Y_{t-1} \]
- Aquí \(Y_{t}'\), representa la diferencia de primer orden de la serie, y \(Y_{t}, Y_{t-1}\) representan la serie y su primer rezago respectivamente. En algunos casos, el uso de la diferencia de primer orden no es suficiente para llevar la serie a un estado estacionario, y es posible que desee aplicar la diferencia de segundo orden:
\[ Y_{t}^{''}=Y_{t}^{'}-Y_{t-1}^{'}=(Y_{t}-Y_{t-1})-(Y_{t-1}-Y_{t-2})= Y_{t}-2Y_{t-1}+Y_{t-2} \]
- Otra forma de diferenciación es la diferenciación estacional, que se basa en diferenciar la series con el rezago estacional:
\[ Y_{t}^{'}=Y_{t}-Y_{t-f} \]
Aquí, \(f\) representa la frecuencia de la serie y \(Y_{t-f}\) representa el rezago estacional de la serie. La función
diffdel paquete base deRdiferencia la serie de entrada con un rezago específico, configurando el argumentolagde rezago correspondiente. Volvamos a la serieAirPassengery veamos cómo el primer orden y la diferenciación estacional afectan a la estructura de la serie.Empezaremos con la diferencia de primer orden:
ts_plot(diff(AirPassengers, lag = 1),
title = "AirPassengers Series - First Differencing",
Xtitle = "Year",
Ytitle = "Differencing of Thousands of Passengers")Puede ver que la primera diferencia de la serie
AirPassengereliminó la tendencia de la serie y que la media de la serie es, en general, constante en el tiempo. Por otra parte, hay una clara evidencia de que la variación de la serie está aumentando con el tiempo, y por lo tanto la serie aún no es estacionaria.Además de la diferencia de primer orden, tomar la diferencia estacional de la serie podría resolver este problema. Realicemos la diferencia estacional a la diferencia de primer orden y dibujemos la serie resultante
ts_plot(diff(diff(AirPassengers, lag = 1), 12),
title = "AirPassengers Series - First and Seasonal Differencing",
Xtitle = "Year",
Ytitle = "Differencing of Thousands of Passengers")- La diferencia estacional ha conseguido estabilizar la variación de la serie. Ahora la serie parece ser estacionaria.
Transformación logarítmica
Podemos utilizar transformación logarítmica para estabilizar una oscilación estacional multiplicativa, si es que existe. Este enfoque no es una sustitución de la diferenciación, sino una adición. Por ejemplo, en el caso de
AirPassengerde la sección anterior, vimos que la primera diferenciación hace un gran trabajo al estabilizar la media de la serie, pero no es suficiente para estabilizar la varianza de la serie.Por lo tanto, podemos aplicar una transformación logarítmica para transformar la estructura estacional de multiplicable a aditiva y luego aplicar la diferencia de primer orden para estacionar la serie
ts_plot(log(AirPassengers),
title = "AirPassengers Series - Log Transformation",
Xtitle = "Year",
Ytitle = "Log of Thousands of Passengers")ts_plot(diff(log(AirPassengers), lag = 1),
title = "AirPassengers Series - First Differencing with Log Transformation",
Xtitle = "Year",
Ytitle = "Differencing/Log of Thousands of Passengers")- La transformación logarítmica con la diferenciación de primer orden está haciendo un mejor trabajo de transformación de la serie en un estado estacionario con respecto a la doble diferenciación (de primer orden con diferenciación estacional) que utilizamos anteriormente.
Proceso ARIMA
Una de las limitaciones de los modelos
AR, MAyARMAes que no pueden manejar datos de series temporales no estacionarias. Por lo tanto, si la serie de entrada es no estacionaria, se requiere un paso de pre-procesamiento para transformar la serie de un estado no estacionario a un estado estacionario.El modelo
ARIMAofrece una solución para este problema al añadir el proceso integrado para el modeloARMA. El proceso integrado(I)consiste simplemente en diferenciar la serie con sus rezagos, donde el grado de diferenciación está representado por el parámetrod. Podemos generalizar el proceso de diferenciación con la siguiente ecuación:\[ Y_{d}=(Y_{t}-Y_{t-1})-\cdots-(Y_{t-d+1}-Y_{t-d}), \] donde \(Y_{d}\) es la \(d\) diferenciación de la series. Agreguemos el componente de diferenciación al modelo
ARMAy formalicemos el modeloARIMA:\[ \textsf{ARIMA}(p, d, q): Y_{d}=c+\sum_{i=1}^{p}\phi_{i}Y_{d-i}+\sum_{i=1}^{q}\theta_{i}\epsilon_{t-i}+\epsilon_{t}, \]
donde
- \(\textsf{ARIMA}(p,d,q)\) define un
proceso
ARIMAcon un procesoARde orden \(p\), un grado \(d\) de diferenciación, y un procesoMAde orden \(q\) - \(Y_{d}\) es la diferencia \(d\) de la serie \(Y_{t}\)
- \(c\) representa una constante (o desviación)
- \(p\) define el número de rezagos que se regresan contra \(Y_{t}\)
- \(Y_{d-i}\) es el coeficiente del rezago \(i\) de la serie
- \(q\) define el número de términos de error pasados que se utilizarán en la ecuación
- \(\theta_{i}\) es el coeficiente correspondiente de \(\epsilon_{t-i}\)
- \(\epsilon_{t-q},\dots,\epsilon_{t}\) son términos de error de ruido blanco
- \(\epsilon_{t}\) representa el término de error, que es ruido blanco
- \(\textsf{ARIMA}(p,d,q)\) define un
proceso
Se pueden representar los modelos
AR, MAoARMAcon el modeloARIMA, por ejemplo:- El modelo
ARIMA(0, 0, 0)es equivalente al ruido blanco - El modelo
ARIMA(0, 1, 0)es equivalente a un paseo aleatorio - El modelo
ARIMA(1, 0, 0)es equivalente a un proceso AR(1) - El modelo
ARIMA(0, 0, 1)es equivalente a un proceso MA(1) - El modelo
ARIMA(1, 0, 1)es equivalente a un proceso ARMA(1,1)
- El modelo
Identificación del grado de diferenciación del modelo
- Al igual que los parámetros \(p\) y
\(q\), el ajuste del parámetro \(d\) (el grado de diferenciación de la de la
serie) puede hacerse con los gráficos
ACFyPACF. En el siguiente ejemplo, utilizaremos los precios mensuales de Robusta Coffee desde el año 2000. - Esta serie forma parte del objeto
Coffee_Pricesdel paqueteTSstudio. Empezaremos cargando la serieCoffee_Pricesy restando los precios mensuales del café robusta desde enero de 2010 utilizando la funciónwindow:
data("Coffee_Prices")
robusta_price <- window(Coffee_Prices[,1], start = c(2000, 1))
ts_plot(robusta_price,
title = "The Robusta Coffee Monthly Prices",
Ytitle = "Price in USD",
Xtitle = "Year")Como se puede ver, los precios del Café Robusta a lo largo del tiempo tienen una tendencia al alza, por lo que no se encuentra en un estado estacionario. Además, como esta serie representa precios continuos, es probable que la serie tenga una fuerte relación de correlación con sus rezagos pasados (ya que los cambios en el precio son típicamente cercanos al precio anterior).
Volveremos a utilizar la función
acf()para identificar el tipo de relación entre la serie y sus rezagos:
- Como se puede ver en la salida anterior del gráfico
ACF, la autocorrelación de la serie con sus rezagos decae lentamente en el tiempo de forma lineal. La eliminación de la tendencia de la serie y de la correlación entre la serie y sus rezagos puede hacerse por diferenciación. Comenzaremos con la primera diferenciación utilizando la funcióndiff:
robusta_price_d1 <- diff(robusta_price)
par(mfrow=c(1,2))
acf(robusta_price_d1, lag.max = 20)
pacf(robusta_price_d1, lag.max = 20)Los gráficos
ACFyPACFde la primera diferencia de la serie indican que un procesoAR(2)es apropiado para utilizar en la serie diferenciada, ya que el ACF es de cola (geometric) y la banda de significancia en la PACF corta significativamente en dos rezagos.Por lo tanto, aplicaremos un modelo
ARIMA(2,1,0)a la serierobusta_pricepara incluir la primera diferencia:
- Utilizaremos la función
summarypara revisar los detalles del modelo:
##
## Call:
## arima(x = robusta_price, order = c(2, 1, 0))
##
## Coefficients:
## ar1 ar2
## 0.2806 -0.0095
## s.e. 0.0674 0.0673
##
## sigma^2 estimated as 0.007142: log likelihood = 231.39, aic = -456.78
##
## Training set error measures:
## ME RMSE MAE MPE MAPE MASE
## Training set 0.002618444 0.08431708 0.06491399 0.08073421 4.252179 1.001022
## ACF1
## Training set -0.001067261
- Por último, pero no menos importante, comprobaremos los residuos del modelo:
##
## Ljung-Box test
##
## data: Residuals from ARIMA(2,1,0)
## Q* = 26.584, df = 22, p-value = 0.2275
##
## Model df: 2. Total lags used: 24
En general, el gráfico de los residuos del modelo y la prueba de Ljung-Box indican que los residuos son
white noise. El gráficoACFindica que hay algunos rezagos correlacionados, pero ellos son solo significativos en los bordes, por lo que podemos ignorarlos.Muchas pruebas estadísticas se utilizan para intentar rechazar alguna hipótesis nula. En este caso concreto, la prueba de
Ljung-Boxtrata de rechazar la hipótesis nula de independencia de los residuosSi el valor \(p\leq0.05\): Puede rechazar la hipótesis nula asumiendo un 5% de probabilidad de error. Por lo tanto, puede asumir que sus valores muestran dependencia entre sí.
Si el valor \(p > 0.05\): No tiene suficiente evidencia estadística para rechazar la hipótesis nula. Así que no puede asumir que sus valores son dependientes.
Esto podría significar que sus valores son dependientes de todos modos o puede significar que sus valores son independientes. Pero usted no está probando ninguna posibilidad específica, lo que su prueba realmente es que usted no puede afirmar la dependencia de los valores, ni puede afirmar la independencia de los valores.
Predicción del consumo mensual de gas natural en EE.UU
El modelo ARIMA estacional (SARIMA), como su nombre indica, es una versión designada del modelo
ARIMApara series temporales con un componente estacional. Una serie temporal con un componente estacional tiene una fuerte relación con sus rezagos estacionales.El modelo
SARIMAutiliza los rezagos estacionales de manera similar a como lo hace el modeloARIMA, esto es, utiliza los rezagos no estacionales con los procesosARyMAy la diferenciación. Para ello, añade los tres componentes siguientes al modelo ARIMAProceso SAR(P): Un proceso
ARestacional de la serie con susPrezagos estacionales pasados. Por ejemplo, unSAR(2)es un procesoARde la serie con sus dos últimos rezagos estacionales, es decir, \(Y_{t}=c+\Phi_{1}Y_{t-f}+\Phi_{2}Y_{t-2f}+\epsilon_{t}\) donde \(\Phi\) representa el coeficiente estacional del procesoSAR, y \(f\) representa la frecuencia de la serie.Proceso SMA(Q): Un proceso
MAestacional de la serie con susQtérminos de error estacionales pasados. Por ejemplo, unSMA(1)es un proceso de media móvil de la serie con su término de error estacional pasado, es decir, \(Y_{t}=\mu+\epsilon_{t}+\Theta_{1}\epsilon_{t-f}\), donde \(\Theta\) representa el coeficiente estacional del procesoSMA, y \(f\) representa la frecuencia de la serie.Proceso SI(D): Una diferenciación estacional de la serie con sus últimos
Drezagos estacionales. De forma similar, podemos diferenciar la serie con su rezago estacional, es decir \(Y_{D=1}^{'}=Y_{t}-Y_{t-f}\).
Utilizamos la siguiente notación para denotar los parámetros
SARIMA, donde los parámetros \(P\) y \(Q\) representan los ordenes correspondientes de los procesosARyMAestacionales de la serie con sus rezagos estacionales, y \(D\) define el grado de diferenciación de la serie con sus rezagos estacionales.
\[ \textsf{SARIMA}(p, d, q)\times(P, D, Q), \]
- Aplicaremos lo aprendido acerca del modelo
ARIMApara pronosticar US Monthly Natural Gas consumption. Vamos a cargar la serieUSgasdel paqueteTSstudio:
library(TSstudio)
data(USgas)
ts_plot(USgas,
title = "US Monthly Natural Gas consumption",
Ytitle = "Billion Cubic Feet",
Xtitle = "Year")La serie US Monthly Natural Gas consumption tiene un fuerte patrón estacional, además la serie tiene una tendencia al alza, por lo tanto, se requiere el uso de los modelos
SARIMA. Puede usar las funciones:ts_info(USgas)yts_decompose(USgas)para identificar tendencia, estacionalidad e información importantes relacionada con la serie de tiempo.Empezaremos por definir particiones
trainingytestingusando la funciónts_split, dejando los últimos 12 meses de la serie como partición de prueba:
## The USgas series is a ts object with 1 variable and 238 observations
## Frequency: 12
## Start time: 2000 1
## End time: 2019 10
- Antes de iniciar el proceso de entrenamiento del modelo
SARIMA, realizaremos diagnósticos con respecto a la correlación de la serie con las funcionesACFyPACF. Dado que nos interesa ver la relación de la serie con sus rezagos estacionales, aumentaremos el número de rezagos a calcular y mostrar, estableciendo el argumentolag.maxa 60 rezagos
El gráfico
ACFanterior indica que la serie tiene una fuerte correlación tanto con la parte estacional como con los rezagos no estacionales. Además, el decaimiento lineal de los rezagos estacionales indica que la serie no es estacionaria y que se requiere una diferenciación estacional.Comenzaremos con diferenciación estacional de la serie y trazaremos el resultado para identificar si la serie se encuentra en un estado estacionario:
USgas_d12 <- diff(train, 12)
ts_plot(USgas_d12,
title = "US Monthly Natural Gas consumption - First Seasonal Difference",
Ytitle = "Billion Cubic Feet (First Difference)",
Xtitle = "Year")- Aunque hemos eliminado la tendencia de la serie, nótese que la variación de la serie aún no es estable. Por lo tanto, también intentaremos tomar la primera diferencia de la serie:
USgas_d12_1 <- diff(diff(USgas_d12, 1))
ts_plot(USgas_d12_1,
title = "US Monthly Natural Gas consumption - First Seasonal and Non-Seasonal Differencing",
Ytitle = "Billion Cubic Feet (Difference)",
Xtitle = "Year")- Después de tomar la diferenciación de primer orden, junto con la
diferenciación estacional de primer orden, la serie parece estabilizarse
en torno a la línea del eje \(x\) cero
(o bastante cerca de ser estable). Después de
transformar la serie en un estado estacionario, podemos revisar las
funciones
ACFyPACFde nuevo para identificar el proceso necesario
- La principal observación de los gráficos
ACFyPACFanteriores es que tanto los rezagos no estacionales como los rezagos estacionales (en ambos gráficos) se están reduciendo. Por lo tanto, podemos concluir que después de diferenciar las series y transformarlas en un estado estacionario, deberíamos aplicar un proceso procesoARMApara los componentes estacionales y no estacionales del modeloSARIMA
| ACF | PACF | |
|---|---|---|
| AR | Decaimiento geométrico | Significativo hasta \(p\) rezagos |
| MA | Significativo hasta \(p\) rezagos | Decaimiento geométrico |
| ARMA | Decaimiento geométrico | Decaimiento geométrico |
La función auto.arima
Uno de los principales retos de la previsión con la familia de modelos
ARIMAes el proceso de ajuste de los modelos. Como hemos visto en este capítulo, este proceso incluye muchos pasos manuales que se requieren para verificar la estructura de las series (estacionarias o no estacionarias), las transformaciones de los datos, el análisis descriptivo con los gráficos ACF y PACF para identificar el tipo de proceso y, finalmente, afinar los parámetros del modelo.Si bien entrenar un modelo
ARIMApara una sola serie puede llevar unos minutos, es posible que no se pueda hacer si se tienen docenas de series que pronosticar. La funciónauto.arimadel paqueteforecastofrece una solución a este problema. Este algoritmo automatiza el proceso de ajuste del modeloARIMAcon el uso de métodos para identificar tanto la estructura de la serie (estacionaria o no) como el tipo (estacional o no), y establece los parámetros del modelo en consecuencia. Por ejemplo, podemos utilizar la función para pronosticarUSgas:
## Series: train
## ARIMA(2,1,1)(2,1,1)[12]
##
## Coefficients:
## ar1 ar2 ma1 sar1 sar2 sma1
## 0.4301 -0.0372 -0.9098 0.0117 -0.2673 -0.7431
## s.e. 0.0794 0.0741 0.0452 0.0887 0.0830 0.0751
##
## sigma^2 = 10446: log likelihood = -1292.83
## AIC=2599.67 AICc=2600.22 BIC=2623.2
Utilizando los argumentos por defecto de la función
auto.arimase obtiene el modeloARIMAque minimice la puntuaciónAIC. En este caso, se seleccionó un modelo con una puntuaciónAICde 2599.67.Los valores de
s.ecorresponden al error estándar de los parámetros AR/MA estimados. Además,sigma^2es la varianza de los valores residuales \(\sigma^{2}(\varepsilon_{t})\).Por defecto, la función
auto.arimaaplica una búsqueda de modelos más corta utilizando un enfoque por pasos para reducir el tiempo de búsqueda. La contrapartida de este enfoque es que el modelo puede pasar por alto algunos modelos que pueden obtener mejores resultados.Podemos improvisar con los resultados de
auto.arimaajustando el argumento de búsqueda del modelo. El argumentostep-wise, cuando se establece enFALSE, permite establecer una búsqueda más robusta y exhaustiva, con el coste de un mayor tiempo de búsqueda.Esta compensación entre el rendimiento y el tiempo de cálculo puede equilibrarse siempre que se tenga un conocimiento previo de la estructura y las características de la serie. Por ejemplo, volvamos a entrenar el conjunto de entrenamiento de la serie
USgasesta vez con la siguiente configuración:- Establezca los parámetros de diferenciación
dyDen 1. - Limite el orden del modelo a siete utilizando el argumento
max.order. El argumentomax.orderdefine los valores máximos de \(p+q+P+Q\), por lo que deberíamos fijarlo en cinco (dado que \(d\) y \(D\) están fijados en 1). - Bajo estas restricciones, busque todas las combinaciones posibles
estableciendo el argumento argumento
stepwiseaFALSE. - Establezca el argumento
aproximationenFALSEpara obtener cálculos más precisos de los criterios de información:
- Establezca los parámetros de diferenciación
USgas_auto_md2 <- auto.arima(train,
max.order = 5,
D = 1,
d = 1,
stepwise = FALSE,
approximation = FALSE)
USgas_auto_md2## Series: train
## ARIMA(1,1,1)(2,1,1)[12]
##
## Coefficients:
## ar1 ma1 sar1 sar2 sma1
## 0.4247 -0.9180 0.0132 -0.2639 -0.7449
## s.e. 0.0770 0.0376 0.0894 0.0834 0.0753
##
## sigma^2 = 10405: log likelihood = -1292.96
## AIC=2597.91 AICc=2598.32 BIC=2618.08
- Utilicemos el modelo entrenado
USgas_best_md2para pronosticar las observaciones correspondientes del del conjuntotesting:
- Evaluamos el rendimiento del modelo con la función
accuracy:
## ME RMSE MAE MPE MAPE MASE
## Training set 6.081099 97.85701 73.36854 0.1298714 3.517097 0.6371821
## Test set 42.211253 104.79281 83.09943 1.4913412 3.314280 0.7216918
## ACF1 Theil's U
## Training set 0.004565602 NA
## Test set -0.049999868 0.3469228
- Ahora, utilizaremos la función
test_forecastpara obtener una visión más intuitiva del rendimiento del modelo del modelo en las particiones de entrenamiento y de prueba:
- Dado que se han cumplido las condiciones anteriores, podemos pasar al último paso del proceso de predicción y generar la predicción final con el modelo seleccionado. Empezaremos por reentrenando el modelo seleccionado en toda la serie:
- Antes de pronosticar los próximos 12 meses, verifiquemos que los residuos del modelo satisfacen la condición del modelo:
##
## Ljung-Box test
##
## data: Residuals from ARIMA(1,1,1)(2,1,1)[12]
## Q* = 30.173, df = 19, p-value = 0.04964
##
## Model df: 5. Total lags used: 24
- Observando el gráfico de residuos anterior, se puede ver que
residuos son white noise y se distribuyen normalmente. Además,
la prueba de
Ljung-Boxconfirma que no hay autocorrelación con un valor \(p\) de 0.04964, por lo tanto no podemos rechazar la hipótesis nula de que los residuos son white noise. Utilicemos la función de predicción para pronosticar los próximos 12 meses de la serieUSgas:
- Podemos trazar la predicción con la función
plot_forecast:
plot_forecast(USgas_fc,
title = "US Natural Gas Consumption - Forecast",
Ytitle = "Billion Cubic Feet",
Xtitle = "Year")- Para evitar el problema de búsqueda de modelos más corta utilizada
por el modelo
auto.arima, la cual puede pasar por alto algunos modelos que pueden obtener mejores resultados, a continuación definimos una función la cual encuentra el mejor modeloARIMAcon base en la minimización del coeficiente de Akaike (AIC) para distintos rangos de los parámetros \(p, d, q\) del modelo, para obtener el mejor modelo en términos de bondad de ajuste.
best_ARIMA <- function(ts_in, p_n, d_n, q_n) {
best_aic <- Inf
best_pdq <- NULL
best_PDQ <- NULL
fit <- NULL
for(p in 1:p_n) {
for(d in 1:d_n) {
for (q in 1:q_n) {
for(P in 1:p_n) {
for(D in 1:d_n) {
for (Q in 1:q_n) {
tryCatch({
fit <- arima(scale(ts_in),
order=c(p, d, q),
seasonal = list(order = c(P, D, Q), period = 12),
xreg=1:length(ts_in),
method="CSS-ML")
tmp_aic <- AIC(fit)
if (tmp_aic < best_aic) {
best_aic <- tmp_aic
best_pdq = c(p, d, q)
best_PDQ = c(P, D, Q)
}
}, error=function(e){})
}
}
}
}
}
}
return(list("best_aic" = best_aic, "best_pdq" = best_pdq, "best_PDQ" = best_PDQ))
}Aquí
CSSminimiza la suma de los residuos al cuadrado,MLmaximiza la función log-verosimilitud del modeloARIMAyCSS-MLcombina ambos métodos.CSSes el más rápido pero menos preciso,MLEes el más lento pero el más preciso; CSS-MLE es un híbrido (medio rápido, medio preciso).Utilizamos la función creada
(best_ARIMA), para encontrar el mejor modeloARIMA, utilizando por ejemplo, un rango de 0 a 2 para todos los seis parámetros del modelo. Puede probar con un rango mas amplio el rango para los valores \(p, d, q\).
best_model <- NULL
if(file.exists("best_arima.rda")) {
best_model = readRDS("best_arima.rda")
} else {
best_model = best_ARIMA(train, 2, 2, 2)
saveRDS(best_model, file = "best_arima.rda")
}- Reconstruimos el modelo con base en los mejores parámetros encontrados
fit_model <- arima(train, order=best_model$best_pdq, seasonal = list(order = best_model$best_PDQ, period = 12))##
## Call:
## arima(x = train, order = best_model$best_pdq, seasonal = list(order = best_model$best_PDQ,
## period = 12))
##
## Coefficients:
## ar1 ma1 sar1 sar2 sma1
## 0.4247 -0.9180 0.0132 -0.2639 -0.7449
## s.e. 0.0770 0.0376 0.0894 0.0834 0.0753
##
## sigma^2 estimated as 10160: log likelihood = -1292.96, aic = 2597.91
- Nótese que el modelo encontrado por medio del loop implementado,
coincide con el obtenido utilizando la librería
auto.arima. Esto sucede debido a que la metodología de selección de modelos es la misma y además, el rango para cada uno de los parámetros componentes, lo hemos limitado a dos.
- Verificamos los supuestos de
normalidad e independencia de los residuosobenidos a partir de nuestro modelo.
##
## Ljung-Box test
##
## data: Residuals from ARIMA(1,1,1)(2,1,1)[12]
## Q* = 25.336, df = 19, p-value = 0.1498
##
## Model df: 5. Total lags used: 24
- Los resultados entregados por medio de la función
checkresidualsmuestran que los residuos se distribuyen de una forma normal aproximada y además son independedientes. Seguidamente, procedemos a realizar el gráfico con las predicciones obtenidas por medio del modelo encontrado.
plot_forecast(USgas_pred,
title = "US Natural Gas Consumption - Forecast",
Ytitle = "Billion Cubic Feet",
Xtitle = "Year")- Con base en las
predicciones para un horizonte de 12 días, calculamos métricas de precisión para el modelo obtenido.
## ME RMSE MAE MPE MAPE MASE
## Training set 6.081099 97.85701 73.36854 0.1298714 3.517097 0.6371821
## Test set 42.211253 104.79281 83.09943 1.4913412 3.314280 0.7216918
## ACF1 Theil's U
## Training set 0.004565602 NA
## Test set -0.049999868 0.3469228
- Utilizamos nuevamente la función
test_forecastpara visualizar de forma dinámica el ajuste del modelos y las respectivas predicciones.
Ejercicio para entregar
Considere la serie de tiempo asociada con las acciones de Tecnoglass desde que comenzó a comercializarse hasta la fecha del día de hoy. Puede utilizar la API de
Yahoo Financepara obtener esta serie de tiempo (ver yahoofinancer).Repita TODOS los pasos indicados en esta sección para encontrar modelos ARIMA para predecir el precio de las acciones de Tecnoglass con los siguientes horizontes: 7, 14 días, 21 días, 28 días. Utilizar siempre predicciones usando rolling con ventana de predicción continua de un día. Cualquier cantidad de pasos extra para enriquecer su análisis predictivo serán aceptados siempre y cuando sean acordes con lo que indica la teoría de análisis de series de tiempo.
Repita el paso 2 ahora sin utilizar rolling. Esto es, realice el pronóstico solo utilizando
forecast()para los diferentes horizontes de predicción, 7, 14 días, 21 días, 28 días.Realice tablas de error para los ítems 1 y 2, utilizando las métricas:
MAPE, MAE, RMSE, MSE, R2. Además, agregue el gráfico de correlación entre la observación real y su predicción en el test, \(\text{Corr}(y_{t}, \tilde{y}_{t})\).Repita el análisis desarrollado en los pasos anteriores, considerando ahora el criterio de inferencia Bayesiana (BIC) y el criterio de información de Hannan–Quinn (HQIC) para encontrar el mejor modelo ARIMA y, compare los errores con aquellos obtenidos con el criterio de Akaike.
Escriba en cada paso las conclusiones y análisis estadísticos asociados con los resultados obtenidos. Realice tests de normalidad e independencia para los residuales obtenidos para cada predicción, en cada caso agregue las correspondientes conclusiones. Figuras y algoritmos que no estén acompañados de una conclusión, descripción y análisis estadístico, no serán tenidas en cuenta.
Introducción a PostgreSQL en R
Cuando se trata de grandes conjuntos de datos que potencialmente exceden la memoria de su máquina, es bueno tener otra posibilidad, como su propio servidor con una base de datos
SQL/PostgreSQL, donde se puede consultar los datos. Por ejemplo, un conjunto de datos financieros de 5 GB caben en una memoria RAM básica, pero los datos consumen muchos recursos. Una solución es utilizar una base de datos basada enSQL, donde puedo consultar los datos en trozos más pequeños, dejando recursos para el cálculo.Aunque
MySQLes la más utilizada,PostgreSQLtiene la ventaja de ser de código abierto y gratuita para todos los usos. Sin embargo, todavía tenemos que conseguir un servidor. Una forma posible de hacerlo es alquilar un servidor deAmazon, sin embargo, existe la opción de utilizarRendertal como se explicó en la sección dePython.
Conexión con R
Ahora es el momento de conectarse a la base de datos con
R. Este enfoque utiliza el paqueteRPostgreSQL. Los siguientes paquetes y herramientas deben ser instalados para poder hacer uso de laAPIy realizar la conexión con éxito- RPostgresql
- DBI
- Su propia base de datos PostgreSQL
- Acceso remoto a su base de datos
El paquete
RPostgreSQLyDBIse puede instalar desdeCRANoGithub
install.packages("RPostgreSQL")
install.packages("DBI")
devtools::install_git('https://github.com/r-dbi/DBI.git')
devtools::install_git('https://github.com/cran/RPostgreSQL.git)Para conectar, necesitamos introducir los siguientes comandos en
R, nos conectaremos a la base de datos creada anteriormente enDockeren la sección dePython.RPostgreses una interfaz compatible conDBIpara la base de datosPostgres. Es una reescritura desde cero usandoC++yRcpp.Este paquete actúa tanto como el controlador de la base de datos como la interfaz
DBI. El código y la información adicional están disponibles en su repositorioGitHubaquí: RPostgres. Debe modificar la información que recibe la funciondbConnect()por aquella que obtuvo al crear su base de datos enDocker
library(DBI)
library(RPostgreSQL)
con <- dbConnect(RPostgres::Postgres(),
dbname = "undatascience-db",
host = "localhost",
port = 5432,
user = "undatascience-user",
password = "undatascience-password")- Para visualizar la lista de tablas que hemos
importado antes en la base de datos utilizamos
dbListTables()
## [1] "company" "tecnoglass"
Importación de tablas
- Carguemos como
dataframeun archivoCSVque contiene los precios de las acciones de Tecnoglass Inc. (TGLS), empresaBarranquilleraque cotiza enNasdaq. Para esto usamos la funciónread.csv().
## Date Open High Low Close Adj.Close Volume
## 1 2012-05-10 9.97 10.00 9.50 9.80 7.945550 6900
## 2 2012-05-11 9.70 9.70 9.70 9.70 7.864471 300
## 3 2012-05-14 9.80 9.80 9.80 9.80 7.945550 100
## 4 2012-05-15 9.75 9.75 9.75 9.75 7.905012 300
## 5 2012-05-16 9.75 9.75 9.75 9.75 7.905012 0
## 6 2012-05-17 9.60 9.60 9.60 9.60 7.783393 800
Los nombres de las columnas de este dataframe son problemáticos para las bases de datos (y especialmente para
PostgreSQL) por varias razones: los"."en los nombres pueden ser un problema,PostgreSQLespera que los nombres de las columnas estén todos en minúsculas.Aquí hay una función para hacer que los nombres de las columnas sean seguros para las bases de datos (ver Dealing with Regular Expressions). Luego obtenemos algunos estadísticos usando la función
summary()
dbSafeNames = function(names) {
names = gsub('[^a-z0-9]+','_',tolower(names))
names = make.names(names, unique=TRUE, allow_=TRUE)
names = gsub('.','_',names, fixed=TRUE)
names
}
colnames(TGLS_df) = dbSafeNames(colnames(TGLS_df))
head(TGLS_df)## date open high low close adj_close volume
## 1 2012-05-10 9.97 10.00 9.50 9.80 7.945550 6900
## 2 2012-05-11 9.70 9.70 9.70 9.70 7.864471 300
## 3 2012-05-14 9.80 9.80 9.80 9.80 7.945550 100
## 4 2012-05-15 9.75 9.75 9.75 9.75 7.905012 300
## 5 2012-05-16 9.75 9.75 9.75 9.75 7.905012 0
## 6 2012-05-17 9.60 9.60 9.60 9.60 7.783393 800
## date open high low
## Length:2200 Min. : 2.440 Min. : 2.540 Min. : 2.150
## Class :character 1st Qu.: 7.987 1st Qu.: 8.117 1st Qu.: 7.900
## Mode :character Median : 9.840 Median : 9.870 Median : 9.800
## Mean : 9.493 Mean : 9.614 Mean : 9.351
## 3rd Qu.:10.940 3rd Qu.:11.045 3rd Qu.:10.790
## Max. :15.950 Max. :15.950 Max. :14.920
## close adj_close volume
## Min. : 2.290 Min. : 2.243 Min. : 0
## 1st Qu.: 7.960 1st Qu.: 7.497 1st Qu.: 2000
## Median : 9.840 Median : 8.067 Median : 15200
## Mean : 9.477 Mean : 8.060 Mean : 31071
## 3rd Qu.:10.910 3rd Qu.: 8.930 3rd Qu.: 38600
## Max. :15.060 Max. :12.210 Max. :1439200
- Ahora procedemos a insertar tabla
TGLS_dfen nuestra base de datosDockerusando la funcióndbWriteTable()
- La función
dbWriteTable()devuelveTRUEsi la tabla fue escrita con éxito. Tenga en cuenta que esta llamada fallará si ya existe la tabla en la base de datos. Utiliceoverwrite = TRUEpara forzar la sobreescritura de una tabla existente, yappend = TRUEpara añadirla a una tabla existente. Verifique que la tabla fué creada usandopgAdmin
- Ahora puedes volver a leer la tabla usando
dbGetQuery()odbReadTable
## date open high low
## Length:2200 Min. : 2.440 Min. : 2.540 Min. : 2.150
## Class :character 1st Qu.: 7.987 1st Qu.: 8.117 1st Qu.: 7.900
## Mode :character Median : 9.840 Median : 9.870 Median : 9.800
## Mean : 9.493 Mean : 9.614 Mean : 9.351
## 3rd Qu.:10.940 3rd Qu.:11.045 3rd Qu.:10.790
## Max. :15.950 Max. :15.950 Max. :14.920
## close adj_close volume
## Min. : 2.290 Min. : 2.243 Min. : 0
## 1st Qu.: 7.960 1st Qu.: 7.497 1st Qu.: 2000
## Median : 9.840 Median : 8.067 Median : 15200
## Mean : 9.477 Mean : 8.060 Mean : 31071
## 3rd Qu.:10.910 3rd Qu.: 8.930 3rd Qu.: 38600
## Max. :15.060 Max. :12.210 Max. :1439200
## date open high low
## Length:2200 Min. : 2.440 Min. : 2.540 Min. : 2.150
## Class :character 1st Qu.: 7.987 1st Qu.: 8.117 1st Qu.: 7.900
## Mode :character Median : 9.840 Median : 9.870 Median : 9.800
## Mean : 9.493 Mean : 9.614 Mean : 9.351
## 3rd Qu.:10.940 3rd Qu.:11.045 3rd Qu.:10.790
## Max. :15.950 Max. :15.950 Max. :14.920
## close adj_close volume
## Min. : 2.290 Min. : 2.243 Min. : 0
## 1st Qu.: 7.960 1st Qu.: 7.497 1st Qu.: 2000
## Median : 9.840 Median : 8.067 Median : 15200
## Mean : 9.477 Mean : 8.060 Mean : 31071
## 3rd Qu.:10.910 3rd Qu.: 8.930 3rd Qu.: 38600
## Max. :15.060 Max. :12.210 Max. :1439200
- Por supuesto, el objetivo de utilizar una base de datos es extraer
subconjuntos o transformaciones de sus datos, utilizando
SQL. Para esto procedemos de la siguiente forma
## date open close high low
## 1 2012-05-10 9.97 9.80 10.00 9.50
## 2 2012-05-11 9.70 9.70 9.70 9.70
## 3 2012-05-14 9.80 9.80 9.80 9.80
## 4 2012-05-15 9.75 9.75 9.75 9.75
## 5 2012-05-16 9.75 9.75 9.75 9.75
## 6 2012-05-17 9.60 9.60 9.60 9.60
- Puedes utilizar
dbSendQuerypara enviar consultas que no devuelven un resultado tipo marco de datos. Luego asegúrese de enviar los cambios a la base de datos usandodbCommit
- Cuando hayas terminado, desconecta
PostgreSQL Neon Server
Neon es un servidor de base de datos PostgreSQL sin servidor totalmente gestionado con un generoso nivel gratuito. Ofrece características modernas para desarrolladores, como escalabilidad automática, ramificación, almacenamiento ilimitado y más. Neon se destaca por su alta disponibilidad, su almacenamiento específico para la nube y su compatibilidad con las últimas versiones de PostgreSQL. Es de código abierto, escrito en Rust, y se integra con sistemas de almacenamiento en la nube como S3 para ofrecer un rendimiento óptimo y una reducción significativa de costos.
Para usar
Neondebe crear inicialmente una cuenta en el sitio web neon.tech. Posteriormente, elija el nombre del proyecto, versión de PostgreSQL, así como también la versión de la base de datos a utilizar. A la derecha podrá visualizar las capacidades de la base de datos en su versión Free Tier. Haga click en Create Project
- Posterior a esto, podrá visualizar la información acerca de
credenciales para poder realizar su conexión a la base de
datos. Hacemos click en Parameters
only, para poder visualizar las credenciales que nos
permitirán realizar la conexión a la base de datos desde
Rpor ejemplo. Guarde la información y haga click en I’ll do this later
- Ahora podrá conectarse a la base de datos creada
en
NeondesdeRusando las libreríasDBIyRPostgreSQL. Además de escribir sobre esta base de datos la tablatecnoglasscomo en el ejercicio anterior
library(DBI)
library(RPostgreSQL)
con <- dbConnect(RPostgres::Postgres(),
dbname = "datavizuninortedb",
host = "ep-dawn-dew-a5bt3cti.us-east-2.aws.neon.tech",
port = 5432,
user = "datavizuninortedb_owner",
password = "8CTM7rcdXlgG")rm(dtab)
dtab = dbGetQuery(con, "SELECT date, open, close, high, low FROM tecnoglass")
knitr::kable(head(dtab)) | date | open | close | high | low |
|---|---|---|---|---|
| 2012-05-10 | 9.97 | 9.80 | 10.00 | 9.50 |
| 2012-05-11 | 9.70 | 9.70 | 9.70 | 9.70 |
| 2012-05-14 | 9.80 | 9.80 | 9.80 | 9.80 |
| 2012-05-15 | 9.75 | 9.75 | 9.75 | 9.75 |
| 2012-05-16 | 9.75 | 9.75 | 9.75 | 9.75 |
| 2012-05-17 | 9.60 | 9.60 | 9.60 | 9.60 |
## date open high low
## Length:2200 Min. : 2.440 Min. : 2.540 Min. : 2.150
## Class :character 1st Qu.: 7.987 1st Qu.: 8.117 1st Qu.: 7.900
## Mode :character Median : 9.840 Median : 9.870 Median : 9.800
## Mean : 9.493 Mean : 9.614 Mean : 9.351
## 3rd Qu.:10.940 3rd Qu.:11.045 3rd Qu.:10.790
## Max. :15.950 Max. :15.950 Max. :14.920
## close adj_close volume
## Min. : 2.290 Min. : 2.243 Min. : 0
## 1st Qu.: 7.960 1st Qu.: 7.497 1st Qu.: 2000
## Median : 9.840 Median : 8.067 Median : 15200
## Mean : 9.477 Mean : 8.060 Mean : 31071
## 3rd Qu.:10.910 3rd Qu.: 8.930 3rd Qu.: 38600
## Max. :15.060 Max. :12.210 Max. :1439200
- Podrá visualizar desde su
Neon Dashboardla tabla creadatecnoglassademás de otras funcionalidades ofrecidas
Candlesticks plot
- Usaremos los datos importados en la base de datos de
Neonpara realizar gráfico de velas japonesas usando plotly. Inciamos realizando la conexión a nuestra base de datos enNeon, luego realizamos la figura utilizando la funciónplot_ly()usandotype="candlestick"
library(DBI)
library(RPostgreSQL)
con <- dbConnect(RPostgres::Postgres(),
dbname = "datavizuninortedb",
host = "ep-dawn-dew-a5bt3cti.us-east-2.aws.neon.tech",
port = 5432,
user = "datavizuninortedb_owner",
password = "8CTM7rcdXlgG")## date open high low close adj_close volume
## 1 2012-05-10 9.97 10.00 9.50 9.80 7.945550 6900
## 2 2012-05-11 9.70 9.70 9.70 9.70 7.864471 300
## 3 2012-05-14 9.80 9.80 9.80 9.80 7.945550 100
## 4 2012-05-15 9.75 9.75 9.75 9.75 7.905012 300
## 5 2012-05-16 9.75 9.75 9.75 9.75 7.905012 0
## 6 2012-05-17 9.60 9.60 9.60 9.60 7.783393 800
library(plotly)
fig <- dtab %>% plot_ly(x = ~date, type="candlestick",
open = ~open, close = ~close,
high = ~high, low = ~low)
fig <- fig %>% layout(title = "Basic Candlestick Chart",
xaxis = list(title = 'Day'),
yaxis = list(title = 'TGLS-USD'))
figi <- list(line = list(color = '#FFD700'))
d <- list(line = list(color = '#0000ff'))
fig <- dtab %>% plot_ly(x = ~date, type="ohlc",
open = ~open, close = ~close,
high = ~high, low = ~low,
increasing = i, decreasing = d)
fig <- fig %>% layout(title = "Basic OHLC Chart",
xaxis = list(rangeslider = list(visible = F), title = 'Day'),
yaxis = list(title = 'TGLS-USD'))
figEjercicio para entregar
Cada una de las consultas
SQLestudiadas en la sección dePythonse pueden realizar desdeR. La diferencia central se encuentra en la forma como se usa laAPIdePostgreSQLparaR, a saberRPostgreSQL, pero, las consultasSQLse mantienen iguales.A manera de ejercicio, en esta tarea se propone dar solución a los problemas PostgreSQL Exercises, haciendo uso de
Docker,Neony la libreríaRPostgreSQL. Los ejercicios que se proponen resolver, serán un complemento a las consultas estudiadas en la sección dePythonyR. Son de gran importancia en su tarea con Científico de Datos. Siga los siguientes pasosUsando
Docker,NeonyRPostgreSQLcrear las tablas que aparecen el el siguiente link gettingstarted. Para descargar los datos a insertar en las tablasbookings, facilities, members, debe usar las consultasINSERT INTOque aparece en el siguiente link pgexercises.sql.Resuelva los ejercicios que aparecen en el siguiente link: questions. Esto es, debe resolver los ejercicios de las secciones:
- Simple SQL Queries
- Joins and Subqueries
- Modifying data
- Aggregation
- Working with Timestamps
- String Operations
- Recursive Queries
Al final, como todas las tareas del curso, debe entregar un link de
RPubs/Quartocon la solución a estos ejercicios y un ZIP con la base de datos creada y los archivos fuente, así como screenshots de las tablas creadas enNeonsi estas no superan 0.5 GB.
Visualización con plotly y shiny
Introducción a plotly
- Cualquier gráfico hecho con el paquete
Rdeplotlyes impulsado por la bibliotecaJavaScriptplotly.js. La funciónplot_ly()proporciona una interfaz ‘directa’ aplotly.jscon algunas abstracciones adicionales para ayudar a reducir la escritura. Estas abstracciones, inspiradas en la gramática de los gráficos yggplot2, hacen que sea mucho más rápido iterar de un gráfico a otro, facilitando el descubrimiento de características interesantes en los datos(Wilkinson 2005; Wickham 2009). Para demostrarlo, usaremosplot_ly()para explorar el conjunto de datosdiamondsdeggplot2y aprenderemos un poco cómo funcionaplotly
| carat | cut | color | clarity | depth | table | price | x | y | z |
|---|---|---|---|---|---|---|---|---|---|
| 0.23 | Ideal | E | SI2 | 61.5 | 55 | 326 | 3.95 | 3.98 | 2.43 |
| 0.21 | Premium | E | SI1 | 59.8 | 61 | 326 | 3.89 | 3.84 | 2.31 |
| 0.23 | Good | E | VS1 | 56.9 | 65 | 327 | 4.05 | 4.07 | 2.31 |
| 0.29 | Premium | I | VS2 | 62.4 | 58 | 334 | 4.20 | 4.23 | 2.63 |
| 0.31 | Good | J | SI2 | 63.3 | 58 | 335 | 4.34 | 4.35 | 2.75 |
| 0.24 | Very Good | J | VVS2 | 62.8 | 57 | 336 | 3.94 | 3.96 | 2.48 |
- La siguiente es una descripción de cada una de las variables en
diamonds
- Si asignamos nombres de variables (p. ej.,
cut, clarity, etc.) a propiedades visuales (p. ej.,x, y, color, etc.) dentro deplot_ly(), como se ve en la figura, se intenta encontrar una representación geométrica sensata de esa información por nosotros. En breve veremos cómo especificar estas representaciones geométricas (así como otras codificaciones visuales) para crear diferentes tipos de gráficos.
- La función
plot_ly()tiene numerosos argumentos que son exclusivos del paqueteR(por ejemplo,color, stroke, span, symbol, linetype, etc.) y facilitan la codificación de las variables de datos (por ejemplo, claridad del diamante) como propiedades visuales (por ejemplo, color). Por defecto, estos argumentos asignan valores de una variable de datos a un rango visual definido por la forma plural del argumento.
Introducción a ggplotly
La función
ggplotly()del paqueteplotlytiene la capacidad de traducirggplot2aplotly. Esta funcionalidad puede ser realmente útil para añadir rápidamente interactividad a su flujo de trabajoggplot2existente. Además, incluso si conoce bienplot_ly()yggplotly()puede seguir siendo deseable para crear visualizaciones que no son necesariamente fáciles de lograr sin ella. Para demostrarlo, vamos a explorar la relación entrepricey otras variables del conocido conjunto de datosdiamonds.El binning hexagonal (es decir,
geom_hex()) es una forma útil de visualizar una densidad2D, como la relación entrepriceycarat, como se muestra en la siguiente figura, donde podemos ver que existe una fuerte relación lineal positiva entre el logaritmo de los quilates y el precio. También muestra que, para muchos, el quilate sólo se redondea a un número determinado, indicado por las bandas azul claro. Hacer este gráfico interactivo facilita la decodificación de los colores hexagonales en los recuentos que representan.
- Es bueno utilizar
ggplotly()sobreplot_ly()para aprovechar la interfaz consistente y expresiva deggplot2para explorar resúmenes estadísticos entre grupos. Por ejemplo, al incluir una variable de color discreta (e.gcut) congeom_freqpoly(), se obtiene un polígono de frecuencia para cada nivel de esa variable. Esta capacidad de generar rápidamente codificaciones visuales de resúmenes estadísticos a través de un número arbitrario de grupos funciona básicamente para cualquiergeom(e.g,geom_boxplot(), geom_histogram(), geom_density(), etc) y es una característica clave deggplot2.
Barras e histogramas
Las funciones
add_bars()yadd_histogram()envuelven los tipos de trazado de barras e histogramas deplotly.js. La principal diferencia entre ellas es que las trazas de barras requieren alturas de barras (tantoxcomoy), mientras que las trazas de histograma requieren sólo una variable, yplotly.jsmaneja el binning en el navegador. Y quizás de forma confusa, ambas funciones pueden utilizarse para visualizar la distribución de una variable numérica o discreta. Así que, esencialmente, la única diferencia entre ellas es dónde se produce el binning.La siguiente figura compara el algoritmo de agrupación por defecto en
plotly.jscon algunos algoritmos diferentes disponibles enRa través de la funciónhist(). Aunqueplotly.jstiene la capacidad de personalizar losbinsdel histograma a través dexbins/ybins,Rtiene diversas facilidades para estimar el número óptimo debinsen un histograma que podemos aprovechar fácilmente. La funciónhist()por sí sola nos permite referenciar 3 algoritmos famosos por su nombre (Sturges 1926; Freedman-Diaconis 1981yScott 1979), pero también hay paquetes (por ejemplo, el paquetehistogram) que amplían esta interfaz para incorporar más metodología (Mildenberger, Rozenholc y Zasada. 2009). La funciónprice_hist()que aparece a continuación envuelve la funciónhist()para obtener los resultados del binning, y mapear esos bins a una versión graficada del histograma usandoadd_bars()
p1 <- plot_ly(diamonds, x = ~price) %>%
add_histogram(name = "plotly.js")
price_hist <- function(method = "FD") {
h <- hist(diamonds$price, breaks = method, plot = FALSE)
plot_ly(x = h$mids, y = h$counts) %>% add_bars(name = method)
}
subplot(
p1, price_hist(), price_hist("Sturges"), price_hist("Scott"),
nrows = 4, shareX = TRUE
)- La siguiente figura muestra dos formas de crear un
gráfico de barras básico. Aunque los resultados visuales son los mismos,
vale la pena señalar la diferencia en la implementación.
La función
add_histogram()envía todos los valores observados al navegador y deja queplotly.jsrealice el binning. Se necesita más esfuerzo humano para realizar el binning enR, pero hacerlo tiene la ventaja de enviar menos datos, y requiere menos trabajo de cálculo del navegador web. En este caso, sólo tenemos unos 50.000 registros, por lo que no hay mucha diferencia en los tiempos de carga o el tamaño de la página. Sin embargo, con 1 millón de registros, el tiempo de carga de la página es más del doble y el tamaño de la página casi se duplica.
library(dplyr)
p1 <- plot_ly(diamonds, x = ~cut) %>%
add_histogram()
p2 <- diamonds %>%
count(cut) %>%
plot_ly(x = ~cut, y = ~n) %>%
add_bars()
subplot(p1, p2) %>% hide_legend()- A menudo es útil ver cómo cambia la distribución numérica con
respecto a una variable discreta. Cuando se utilizan barras para
visualizar múltiples distribuciones numéricas, se
recomienda trazar cada distribución en su propio eje utilizando una
pantalla de múltiplos pequeños, en lugar de intentar superponerlas en un
solo eje. Obsérvese cómo la función
one_plot()define lo que debe mostrarse en cada panel, y luego se emplea una estrategia de dividir-aplicar-recombinar (es decir,split(), lapply(), subplot()) para generar la visualización del enrejado
one_plot <- function(d) {
plot_ly(d, x = ~price) %>%
add_annotations(
~unique(clarity), x = 0.5, y = 1,
xref = "paper", yref = "paper", showarrow = FALSE
)
}
diamonds %>%
split(.$clarity) %>%
lapply(one_plot) %>%
subplot(nrows = 2, shareX = TRUE, titleX = FALSE) %>%
hide_legend()- La visualización de las distribuciones discretas múltiples es difícil. Esta sutil complejidad se debe a que tanto los recuentos como las proporciones son importantes para comprender las distribuciones discretas multivariadas. La figura siguiente presenta los recuentos de diamantes, divididos por su talla y su claridad, mediante un gráfico de barras agrupadas.
- La distribución de la claridad dentro de los diamantes “ideales” parece ser bastante similar a la de otros diamantes, pero es difícil hacer esta comparación utilizando los recuentos brutos. La siguiente figura facilita esta comparación mostrando la frecuencia relativa de los diamantes por claridad, dado un corte.
cc <- count(diamonds, cut, clarity) #counts for frequencies of combinations
cc2 <- left_join(cc, count(cc, cut, wt = n, name = 'nn')) # counts for cut frequencies in cc
str(cc2)## tibble [40 × 4] (S3: tbl_df/tbl/data.frame)
## $ cut : Ord.factor w/ 5 levels "Fair"<"Good"<..: 1 1 1 1 1 1 1 1 2 2 ...
## $ clarity: Ord.factor w/ 8 levels "I1"<"SI2"<"SI1"<..: 1 2 3 4 5 6 7 8 1 2 ...
## $ n : int [1:40] 210 466 408 261 170 69 17 9 96 1081 ...
## $ nn : int [1:40] 1610 1610 1610 1610 1610 1610 1610 1610 4906 4906 ...
Boxplots
- Los boxplots codifican el resumen de cinco números de una variable
numérica, y proporcionan una forma decente de comparar muchas
distribuciones numéricas. La tarea visual de comparar múltiples boxplots
es relativamente fácil (es decir, comparar la posición a lo largo de una
escala común) en comparación con algunas alternativas comunes (por
ejemplo, una visualización enrejada de histogramas), pero el
boxplotes a veces inadecuado para capturar distribuciones complejas (por ejemplo, multimodales) (en este caso, un polígono de frecuencia proporciona una buena alternativa). La funciónadd_boxplot()requiere una variable numérica y garantiza que losboxplotsse orienten correctamente, independientemente de si la variable numérica se coloca en la escalaxoy. Como muestra la siguiente figura, en el eje ortogonal al eje numérico, puede proporcionar una variable discreta (para el condicionamiento) o suministrar un único valor (para nombrar la categoría del eje).
p <- plot_ly(diamonds, y = ~price, color = I("black"),
alpha = 0.1, boxpoints = "suspectedoutliers")
p1 <- p %>% add_boxplot(x = "Overall")
p2 <- p %>% add_boxplot(x = ~cut)
subplot(
p1, p2, shareY = TRUE,
widths = c(0.2, 0.8), margin = 0
) %>% hide_legend()- Si desea realizar una partición por más de una variable discreta, podría utilizar la interacción de esas variables con el eje discreto, y colorear por la variable anidada, como hace la siguiente figura con la claridad y la talla del diamante. Otro enfoque sería utilizar una visualización enrejada
plot_ly(diamonds, x = ~price, y = ~interaction(clarity, cut)) %>%
add_boxplot(color = ~clarity) %>%
layout(yaxis = list(title = ""))- También es útil ordenar los gráficos de caja según algo
significativo, como la mediana del precio. La siguiente figura presenta
la misma información anterior, pero ordena los gráficos de caja por su
mediana, y deja claro de inmediato que los diamantes con una talla
"SI2"tienen el precio más alto, por término medio
d <- diamonds %>%
mutate(cc = interaction(clarity, cut))
lvls <- d %>%
group_by(cc) %>%
summarise(m = median(price)) %>%
arrange(m) %>%
pull(cc)
plot_ly(d, x = ~price, y = ~factor(cc, lvls)) %>%
add_boxplot(color = ~clarity) %>%
layout(yaxis = list(title = ""))- Al igual que
add_histogram(),add_boxplot()envía los datos brutos al navegador y permite aplotly.jscalcular las estadísticas de resumen. Desafortunadamente,plotly.jstodavía no permite calcular estadísticas paraboxplots.
Mapas
Hay numerosas maneras de hacer un mapa con
plotly, cada una con sus propios puntos fuertes y débiles. En general, los enfoques se dividen en dos categorías: integrados o personalizados. Los mapas integrados aprovechan el soporte integrado deplotly.jspara renderizar una capa de mapa base. Actualmente hay dos formas de hacer mapas integrados: a través deMapboxo a través de un mapa base integrado cond3.js. El enfoque integrado es conveniente si necesitas un mapa rápido y no necesariamente necesitas representaciones sofisticadas de objetos geoespaciales. Por otro lado, el enfoque de mapeo personalizado ofrece un control completo, ya que usted está proporcionando toda la información necesaria para representar el/los objetos geoespaciales. Mas adelante revisaremos la creación de mapas sofisticados (por ejemplo, cartogramas) utilizando el paquetesf R, pero también es posible hacer mapas personalizados deplotlya través de otras herramientas para la geoinformática (por ejemplo,sp, ggmap, etc).Vale la pena señalar que
plotlypretende ser una biblioteca de visualización de propósito general, por lo tanto, no pretende ser el conjunto de herramientas de visualización geoespacial más completo. Dicho esto, hay beneficios en el uso de mapas basados enplotly, ya que lasAPIsde mapeo son muy similares al resto deplotly, y se puede aprovechar el ecosistema más grande deplotly(por ejemplo, la vinculación de las vistas del lado del cliente). Sin embargo, si te encuentras con limitaciones con la funcionalidad de mapeo deplotly, hay un conjunto muy rico de herramientas para la visualización geoespacial interactiva enR, incluyendo pero no limitado a:leaflet, mapview, mapedit, tmap, ymapdeck.
Mapas integrados
- Si tiene datos de latitud/longitud bastante simples y quiere hacer
un mapa rápido, puede probar una de las opciones de mapeo integradas de
plotly(es decir,plot_mapbox()yplot_geo()). En general, puede tratar estas funciones constructoras como un reemplazo directo deplot_ly()y obtener un mapa base dinámico representado detrás de sus datos. Además, todas las capas basadas en la dispersión funcionan como cabría esperar conplot_ly(). Por ejemplo, la siguiente figura utilizaplot_mapbox()yadd_markers()para crear un gráfico de burbujas. Para poder usarplot_mapbox, primero debes crear unMAPBOX_TOKENen el sitio web de Mapbox y luego agregar la siguiente línea a tu código.
## name country.etc pop lat long capital
## 1 Abbotsford BC BC 157795 49.06 -122.30 0
## 2 Acton ON ON 8308 43.63 -80.03 0
## 3 Acton Vale QC QC 5153 45.63 -72.57 0
## 4 Airdrie AB AB 25863 51.30 -114.02 0
## 5 Aklavik NT NT 643 68.22 -135.00 0
## 6 Albanel QC QC 1090 48.87 -72.42 0
library(plotly)
Sys.setenv('MAPBOX_TOKEN' = 'pk.eyJ1IjoibGloa2lyIiwiYSI6ImNrdXdtcWNyeTE4cngybm1wYzBpbjZrZzEifQ.gdBWHAZHRJQL265am4jHpg')
plot_mapbox(maps::canada.cities) %>%
add_markers(
x = ~long,
y = ~lat,
size = ~pop,
color = ~country.etc,
colors = "Accent",
text = ~paste(name, pop),
hoverinfo = "text"
)- El estilo del mapa base de
Mapboxse controla a través del atributolayout.mapbox.style. El paquete deplotlyviene con soporte para 7 estilos diferentes, pero también puedes suministrar unaURLpersonalizada a un estilomapboxpersonalizado. Para obtener todos los nombres de estilo de mapa base pre-empaquetados, puedes tomarlos del esquema oficialplotly.js(). Antes debes instalarlistviewerpara que funcione la siguiente orden
## [1] "basic" "streets" "outdoors"
## [4] "light" "dark" "satellite"
## [7] "satellite-streets" "carto-darkmatter" "carto-positron"
## [10] "open-street-map" "stamen-terrain" "stamen-toner"
## [13] "stamen-watercolor" "white-bg"
- Cualquiera de estos valores puede ser utilizado para un estilo
mapbox. La siguiente figura muestra el mapa base de imágenes terrestres por satélite.
- Ahora veamos cómo crear un menú desplegable integrado en
plotly.jspara controlar el estilo del mapa base a través del atributolayout.updatemenus. La idea detrás de un menú desplegable integrado deplotly.jses proporcionar una lista de botones (es decir, elementos de menú) donde cada botón invoca un método deplotly.jscon algunos argumentos. En este caso, cada botón utiliza el métodorelayoutpara modificar el atributolayout.mapbox.style.
style_buttons <- lapply(styles, function(s) {
list(
label = s,
method = "relayout", #modifica atributos de diseño mapbox.style
args = list("mapbox.style", s)
)
})
layout(
plot_mapbox(),
mapbox = list(style = "dark"),
updatemenus = list(
list(y = 0.8, buttons = style_buttons)
)
)- La otra solución de mapeo integrada en
plotlyesplot_geo(). En comparación conplot_mapbox(), este enfoque tiene soporte para diferentes proyecciones de mapeo, pero el estilo del mapa base es limitado y puede ser más engorroso. La siguiente rutina muestra el uso deplot_geo()junto conadd_markers()yadd_segments()para visualizar las rutas de vuelo dentro de los Estados Unidos. Mientras queplot_mapbox()está fijado a una proyección, el constructorplot_geo()tiene un puñado de proyecciones diferentes disponibles, incluyendo la proyección que da la ilusión del globo 3D.
library(plotly)
library(dplyr)
air <- read.csv('https://plotly-r.com/data-raw/airport_locations.csv')
knitr::kable(head(air))| iata | airport | city | state | country | lat | long | cnt |
|---|---|---|---|---|---|---|---|
| ORD | Chicago O’Hare International | Chicago | IL | USA | 41.97960 | -87.90446 | 25129 |
| ATL | William B Hartsfield-Atlanta Intl | Atlanta | GA | USA | 33.64044 | -84.42694 | 21925 |
| DFW | Dallas-Fort Worth International | Dallas-Fort Worth | TX | USA | 32.89595 | -97.03720 | 20662 |
| PHX | Phoenix Sky Harbor International | Phoenix | AZ | USA | 33.43417 | -112.00806 | 17290 |
| DEN | Denver Intl | Denver | CO | USA | 39.85841 | -104.66700 | 13781 |
| IAH | George Bush Intercontinental | Houston | TX | USA | 29.98047 | -95.33972 | 13223 |
| start_lat | start_lon | end_lat | end_lon | airline | airport1 | airport2 | cnt |
|---|---|---|---|---|---|---|---|
| 32.89595 | -97.03720 | 35.04022 | -106.60919 | AA | DFW | ABQ | 444 |
| 41.97960 | -87.90446 | 30.19453 | -97.66987 | AA | ORD | AUS | 166 |
| 32.89595 | -97.03720 | 41.93887 | -72.68323 | AA | DFW | BDL | 162 |
| 18.43942 | -66.00183 | 41.93887 | -72.68323 | AA | SJU | BDL | 56 |
| 32.89595 | -97.03720 | 33.56294 | -86.75355 | AA | DFW | BHM | 168 |
| 25.79325 | -80.29056 | 36.12448 | -86.67818 | AA | MIA | BNA | 56 |
geo <- list(
projection = list(
type = 'orthographic',
rotation = list(lon = -100, lat = 40, roll = 0)
),
showland = TRUE,
landcolor = toRGB("gray95"),
countrycolor = toRGB("gray80")
)
plot_geo(color = I("red")) %>% # "I("red") color should be "red" without any further transformation
add_markers(
data = air, x = ~long, y = ~lat, text = ~airport,
size = ~cnt, hoverinfo = "text", alpha = 0.5
) %>%
add_segments(
data = group_by(flights, id),
x = ~start_lon, xend = ~end_lon,
y = ~start_lat, yend = ~end_lat,
alpha = 0.3, size = I(1), hoverinfo = "none"
) %>%
layout(geo = geo, showlegend = FALSE)Mapas coropléticos
Además de los trazos de dispersión, ambas soluciones de mapeo integrado (es decir,
plot_mapbox()yplot_geo()) tienen un tipo de trazo de coroplético optimizado (es decir, los tiposchoroplethmapboxychoroplethtrace). Comparativamente,choroplethmapboxes más potente porque se puede especificar completamente la colección de características utilizandoGeoJSON, pero,choroplethpuede ser un poco más fácil de utilizar si se ajusta a su caso de uso.La siguiente rutina muestra la densidad de población de los Estados Unidos a través del trazado de mapas coropléticos utilizando los datos de los estados de los Estados Unidos del paquete de conjuntos de datos (
RCore Team 2016). Con solo proporcionar un atributoz, los objetos deplotly_geo()intentarán crear un mapacoroplético, pero también tendrá que proporcionar ubicaciones y un modo de ubicación.Vale la pena señalar que el modo de ubicación se limita actualmente a los países y los estados de EE.UU., por lo que si necesita una unidad geográfica diferente (por ejemplo, condados, municipios, etc.), debe utilizar el tipo de trazado
choroplethmapboxy/o utilizar un enfoque de mapeo “personalizado” que revisaremos mas adelante
| Population | Income | Illiteracy | Life Exp | Murder | HS Grad | Frost | Area | |
|---|---|---|---|---|---|---|---|---|
| Alabama | 3615 | 3624 | 2.1 | 69.05 | 15.1 | 41.3 | 20 | 50708 |
| Alaska | 365 | 6315 | 1.5 | 69.31 | 11.3 | 66.7 | 152 | 566432 |
| Arizona | 2212 | 4530 | 1.8 | 70.55 | 7.8 | 58.1 | 15 | 113417 |
| Arkansas | 2110 | 3378 | 1.9 | 70.66 | 10.1 | 39.9 | 65 | 51945 |
| California | 21198 | 5114 | 1.1 | 71.71 | 10.3 | 62.6 | 20 | 156361 |
| Colorado | 2541 | 4884 | 0.7 | 72.06 | 6.8 | 63.9 | 166 | 103766 |
| x |
|---|
| AL |
| AK |
| AZ |
| AR |
| CA |
| CO |
density <- state.x77[, "Population"] / state.x77[, "Area"]
g <- list(
scope = 'usa',
projection = list(type = 'albers usa'),
lakecolor = toRGB('white')
)
plot_geo() %>%
add_trace(
z = ~density, text = state.name, span = I(0),
locations = state.abb, locationmode = 'USA-states'
) %>%
layout(geo = g)Choroplethmapboxes más flexible quechoroplethporque usted suministra su propia definiciónGeoJSONdelchoropletha través del atributogeojson. Actualmente este atributo debe ser unaURLque apunte a un archivogeojson. Además, la ubicación debe apuntar a un atributoidde nivel superior de cada característica dentro del archivogeojson. La siguiente figura muestra cómo podríamos visualizar la información anterior, pero esta vez utilizandochoroplethmapbox
plot_ly() %>%
add_trace(
type = "choroplethmapbox",
geojson = paste(c(
"https://gist.githubusercontent.com/cpsievert/",
"7cdcb444fb2670bd2767d349379ae886/raw/",
"cf5631bfd2e385891bb0a9788a179d7f023bf6c8/",
"us-states.json"
), collapse = ""),
locations = row.names(state.x77),
z = state.x77[, "Population"] / state.x77[, "Area"],
span = I(0) #span, o anchura del trazo, cero por defecto
) %>%
layout(
mapbox = list(
style = "light",
zoom = 4,
center = list(lon = -98.58, lat = 39.82)
)
) %>%
config(
mapboxAccessToken = Sys.getenv("MAPBOX_TOKEN"),
toImageButtonOptions = list(
format = "svg", #Scalable Vector Graphics (SVG)
width = NULL,
height = NULL
)
)- Las figuras anteriores no son una forma ideal de visualizar la
población estatal desde el punto de vista de la percepción gráfica.
Normalmente utilizamos el color en las
coropletaspara codificar una variable numérica (por ejemplo, el PIB, las exportaciones netas, la nota media de la selectividad, etc,…) y el ojo percibe de forma natural el área que cubre un color concreto como proporcional a su efecto global. Esto acaba siendo engañoso, ya que el área que cubre el color no suele tener ninguna relación razonable con los datos codificados por el color.
Ejercicio para entregar
El siguiente ejercicio busca poner en practica lo aprendido con respecto a las visualizaciones recientemente estudiadas usando
PlotlyyMapbox. Cada una de las siguientes practicas han sido desarrolladas en lenguajePython, es tarea del estudiante, recrear cada visualización usando lenguajeR. Cada figura debe llevar su respectivo análisis e interpretación.
Shiny
Hay varios marcos diferentes para crear aplicaciones web a través de
R, pero centraremos nuestra atención en la vinculación de gráficosplotlyconshinyun paquete deRpara crear aplicaciones web reactivas completamente enR. El modelo de programación reactiva deShinypermite a los programadores deRaprovechar sus conocimientos existentes deRy crear aplicaciones web basadas en datos sin ninguna experiencia previa en programación web.Shinyen sí mismo es en gran medida agnóstico al motor utilizado para renderizar las vistas de datos (es decir, puede incorporar cualquier tipo de salida deR), pero el propioshinytambién agrega algún soporte especial para interactuar con gráficos e imágenes estáticas deR(Chang 2017).Al vincular los gráficos en una aplicación web, hay compensaciones a considerar cuando se utilizan gráficos estáticos de
Ren lugar de gráficos basados en la web. Resulta que esas compensaciones se complementan muy bien con las fortalezas y debilidades relativas de la vinculación de vistas conplotly, lo que hace que su combinación sea un potente conjunto de herramientas para vincular vistas en la web desdeR.El propioShinyproporciona una forma de acceder a eventos con gráficos estáticos hechos con cualquiera de los siguientes paquetes deR:graphics, ggplot2ylattice. Estos paquetes son muy maduros, con todas las funciones, bien probados, y soportan una gama increíblemente amplia de gráficos, pero como deben ser regenerados en el servidor, son fundamentalmente limitados desde una perspectiva de gráficos interactivos.Comparativamente,
plotlyno tiene la misma gama e historia, pero proporciona más opciones y control sobre la interactividad. Más concretamente, debido a queplotlyestá intrínsecamente basado en la web, permite un mayor control sobre cómo se actualizan los gráficos en respuesta a la entrada del usuario (por ejemplo, cambiar el color de unos pocos puntos en lugar de redibujar toda la imagen). Esta sección aborda el cómo utilizar gráficosplotlydentro deshiny, cómo conseguir que esos gráficos se comuniquen con otros tipos de vistas de datos, y cómo hacerlo todo de forma eficiente.
Su primera aplicación Shiny
El patrón más común de
plotly+shinyutiliza una entrada deshinypara controlar una salida deplotly. Mostraremos un ejemplo sencillo de uso de la funciónselectizeInput()deshinypara crear un desplegable que controla un gráfico deplotly. Este ejemplo, así como cualquier otra aplicaciónshiny, tiene dos partes principales:La interfaz de usuario,
ui, define cómo se muestran los widgets de entrada y salida en la página. La funciónfluidPage()ofrece una forma agradable y rápida de obtener un diseño responsivo basado en una cuadrícula, pero también vale la pena señalar que laUIes completamente personalizable, y paquetes comoshinydashboardfacilitan el aprovechamiento de marcos de diseño más sofisticados (Chang y Borges Ribeiro 2018).La función del servidor,
server, define un mapeo de los valores de entrada a los widgets de salida. Más concretamente, el servidorshinyes unaR function()entre los valores ingresados por el cliente y las salidas generadas en el servidor web.
Cada widget de entrada, incluido el
selectizeInput(), está vinculado a un valor de entrada al que se puede acceder en el servidor dentro de una expresión reactiva. Las expresiones reactivas deShinyconstruyen un gráfico de dependencia entre las salidas (también conocidas como puntos finales reactivos) y las entradas (también conocidas como fuentes reactivas). La verdadera potencia de las expresiones reactivas reside en su capacidad para encadenar y almacenar en caché los cálculos, pero centrémonos primero en la generación de salidas. Para generar una salida, tienes que elegir una función adecuada para renderizar el resultado de una expresión reactiva.Consideraremos el siguiente dataset txhousing para este ejemplo. La siguiente rutina utiliza la función
renderPlotly()para renderizar una expresión reactiva que genera un gráficoplotly. Esta expresión depende del valor de entradainput$cities(es decir, el valor de entrada ligado al widget de entrada con uninputIdde"cities") y almacena la salida comooutput$p. Esto indica ashinyque inserte el gráfico reactivo en el contenedorplotlyOutput(outputId = "p")definido en la interfaz de usuario.
## tibble [8,602 × 9] (S3: tbl_df/tbl/data.frame)
## $ city : chr [1:8602] "Abilene" "Abilene" "Abilene" "Abilene" ...
## $ year : int [1:8602] 2000 2000 2000 2000 2000 2000 2000 2000 2000 2000 ...
## $ month : int [1:8602] 1 2 3 4 5 6 7 8 9 10 ...
## $ sales : num [1:8602] 72 98 130 98 141 156 152 131 104 101 ...
## $ volume : num [1:8602] 5380000 6505000 9285000 9730000 10590000 ...
## $ median : num [1:8602] 71400 58700 58100 68600 67300 66900 73500 75000 64500 59300 ...
## $ listings : num [1:8602] 701 746 784 785 794 780 742 765 771 764 ...
## $ inventory: num [1:8602] 6.3 6.6 6.8 6.9 6.8 6.6 6.2 6.4 6.5 6.6 ...
## $ date : num [1:8602] 2000 2000 2000 2000 2000 ...
library(shiny)
library(plotly)
ui <- fluidPage(
selectizeInput(
inputId = "cities",
label = "Select a city",
choices = unique(txhousing$city),
selected = "Abilene",
multiple = TRUE
),
plotlyOutput(outputId = "p")
)
server <- function(input, output) {
output$p <- renderPlotly({
plot_ly(txhousing, x = ~date, y = ~median) %>%
filter(city %in% input$cities) %>%
group_by(city) %>%
add_lines()
})
}
shinyApp(ui, server)Si, en lugar de un gráfico
plotly, una expresión reactiva genera un gráficoRestático, simplemente utilicerenderPlot()(en lugar derenderPlotly()) para renderizarlo yplotOutput()(en lugar deplotlyOutput()) para posicionarlo. Otros widgets de salidashinyutilizan esta convención de nombres:renderDataTable()/datatableOutput(), renderPrint()/verbatimTextOutput(), renderText()/textOutput(), renderImage()/imageOutput(), etc. Los paquetes que se basan en el estándarhtmlwidgets(por ejemplo,plotlyyleaflet) son, en cierto sentido, también widgets de salida deShinyque se animan a seguir esta misma convención de nombres (por ejemplo,renderPlotly()/plotlyOutput()yrenderLeaflet()/leafletOutput()).Shinytambién viene preempaquetado con un puñado de otros widgets de entrada útiles. Aunque muchas aplicaciones deShinylos utilizan directamente"out-of-the-box", los widgets de entrada pueden estilizarse fácilmente conCSSy/oSASS, e incluso se pueden integrar widgets de entrada personalizados (Mastny 2018; RStudio 2014a).selectInput()/selectizeInput()para los menús desplegables.numericInput()para un solo número.sliderInput()para un rango numérico.textInput()para una cadena de caracteres.dateInput()para una sola fecha.dateRangeInput()para un rango de fechas.fileInput()para subir archivos.checkboxInput()/checkboxGroupInput()/radioButtons()para elegir una lista de opciones.
A partir de ahora nuestro enfoque es enlazar múltiples gráficos en
shinya través de la manipulación directa, por lo que nos centramos menos en el uso de estos widgets de entrada, y más en el uso deplotlyy los gráficos estáticos deRcomo entradas a otros widgets de salida.
Ocultación y rediseño al cambiar de tamaño
La función
renderPlotly()renderiza cualquier cosa que la funciónplotly_build()entienda, incluyendo los objetosplot_ly(), ggplotly()yggplot2. También renderizaNULLcomo undivHTMLvacío, lo que es útil para ciertos casos en los que no tiene sentido renderizar un gráfico. La siguiente rutina aprovecha estas características para representar undivvacío mientras se muestra el marcador de posición deselectizeInput(), pero luego representa un gráficoplotlymedianteggplotly()una vez que se han seleccionado las ciudades.También muestra cómo hacer que la salida de
plotlydependa del tamaño del contenedor que contiene el gráfico deplotly. Por defecto, cuando un navegador cambia de tamaño, el tamaño del gráfico se cambia puramente del lado del cliente, pero esta expresión reactiva se reejecutará cuando la ventana del navegador cambie de tamaño. Debido a razones técnicas, esto puede mejorar el comportamiento del redimensionamiento deggplotly(), pero debe usarse con precaución cuando se manejan grandes datos y largos tiempos de representación. Aquíreqse asegúrese de que los valores están disponibles, antes de proceder con un cálculo o acción.
library(shiny)
cities <- unique(txhousing$city)
ui <- fluidPage(
selectizeInput(
inputId = "cities",
label = NULL,
choices = c("Please choose a city" = "", cities),
multiple = TRUE
),
plotlyOutput(outputId = "p")
)
server <- function(input, output, session) {
output$p <- renderPlotly({
req(input$cities)
if (identical(input$cities, "")) return(NULL)
p <- ggplot(data = filter(txhousing, city %in% input$cities)) +
geom_line(aes(date, median, group = city))
height <- session$clientData$output_p_height
width <- session$clientData$output_p_width
ggplotly(p, height = height, width = width)
})
}
shinyApp(ui, server)Eventos de alcance
Esta sección aprovecha la interfaz para acceder a los eventos de entrada de
plotlypara informar a otras vistas de datos sobre esos eventos. Cuando se gestionan múltiples vistas que se comunican entre sí, es necesario saber qué vistas son una fuente de interacción y cuáles son un objetivo (¡una vista puede ser ambas cosas a la vez!). La funciónevent_data()proporciona un argumento de origen para ayudar a refinar qué vista(s) sirve(n) como fuente de un evento. El argumento fuente toma unIDde cadena, y cuando eseIDcoincide con la fuente de un gráfico deplot_ly()/ggplotly(), entonces elevent_data()se “asigna” a esa vista. Para tener una mejor idea de cómo funciona esto, considere el siguiente videoclipNótese que permite hacer clic en una celda de un mapa de calor de correlación para generar un gráfico de dispersión de las dos variables correspondientes, lo que permite ver más de cerca su relación. En el caso de un mapa de calor, los datos de eventos vinculados a un evento
plotly_clickcontienen las categoríasxeyrelevantes (por ejemplo, los nombres de las variables de datos de interés) y el valorz(por ejemplo, la correlación de Pearson entre esas variables). Para obtener los datos de los clics del mapa térmico, y sólo del mapa térmico, es importante que el argumento fuente de la funciónevent_data()coincida con el argumento fuente deplot_ly(). De lo contrario, si el argumento fuente no se especificaraevent_data("plotly_click")también se dispararía cuando el usuario hiciera clic en el gráfico de dispersión, causando probablemente un error. En este ejemplo utilizaremos el dataset mtcars deR. Se usa el ajuste por medio de un modelo lineal con la función lm.
| mpg | cyl | disp | hp | drat | wt | qsec | vs | am | gear | carb | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| Mazda RX4 | 21.0 | 6 | 160 | 110 | 3.90 | 2.620 | 16.46 | 0 | 1 | 4 | 4 |
| Mazda RX4 Wag | 21.0 | 6 | 160 | 110 | 3.90 | 2.875 | 17.02 | 0 | 1 | 4 | 4 |
| Datsun 710 | 22.8 | 4 | 108 | 93 | 3.85 | 2.320 | 18.61 | 1 | 1 | 4 | 1 |
| Hornet 4 Drive | 21.4 | 6 | 258 | 110 | 3.08 | 3.215 | 19.44 | 1 | 0 | 3 | 1 |
| Hornet Sportabout | 18.7 | 8 | 360 | 175 | 3.15 | 3.440 | 17.02 | 0 | 0 | 3 | 2 |
| Valiant | 18.1 | 6 | 225 | 105 | 2.76 | 3.460 | 20.22 | 1 | 0 | 3 | 1 |
library(shiny)
# cache computation of the correlation matrix
correlation <- round(cor(mtcars), 3)
ui <- fluidPage(
plotlyOutput("heat"),
plotlyOutput("scatterplot")
)
server <- function(input, output, session) {
output$heat <- renderPlotly({
plot_ly(source = "heat_plot") %>%
add_heatmap(
x = names(mtcars),
y = names(mtcars),
z = correlation
)
})
output$scatterplot <- renderPlotly({
# if there is no click data, render nothing!
clickData <- event_data("plotly_click", source = "heat_plot")
if (is.null(clickData)) return(NULL)
# Obtain the clicked x/y variables and fit linear model
vars <- c(clickData[["x"]], clickData[["y"]])
d <- setNames(mtcars[vars], c("x", "y"))
yhat <- fitted(lm(y ~ x, data = d))
# scatterplot with fitted line
plot_ly(d, x = ~x) %>%
add_markers(y = ~y) %>%
add_lines(y = ~yhat) %>%
layout(
xaxis = list(title = clickData[["x"]]),
yaxis = list(title = clickData[["y"]]),
showlegend = FALSE
)
})
}
shinyApp(ui, server)Drill-down
La siguiente app muestra las ventas desglosadas por categoría de negocio (por ejemplo,
Furniture, Office Supplies, Technology) en un gráfico circular. Permite al usuario hacer clic en una porción del gráfico para “profundizar” en las subcategorías de la categoría elegida.En términos de implementación, el aspecto clave aquí es mantener el estado de la categoría actualmente seleccionada a través de un
reactiveVal()y actualizar ese valor cuando se hace clic en una categoría o se pulsa el botón"Back". La funciónreactive()envuelve una expresión normal para crear una expresión reactiva. Conceptualmente, una expresión reactiva es una expresión cuyo resultado cambiará con el tiempo.
| id | category | sub_category | order_date | sales |
|---|---|---|---|---|
| CG-12520 | Furniture | Bookcases | 2016-11-08 | 261.9600 |
| CG-12520 | Furniture | Chairs | 2016-11-08 | 731.9400 |
| DV-13045 | Office Supplies | Labels | 2016-06-12 | 14.6200 |
| SO-20335 | Furniture | Tables | 2015-10-11 | 957.5775 |
| SO-20335 | Office Supplies | Storage | 2015-10-11 | 22.3680 |
| BH-11710 | Furniture | Furnishings | 2014-06-09 | 48.8600 |
library(shiny)
library(dplyr)
library(readr)
library(purrr) # just for `%||%`
sales <- read_csv("https://plotly-r.com/data-raw/sales.csv")
categories <- unique(sales$category)
ui <- fluidPage(plotlyOutput("pie"), uiOutput("back"))
server <- function(input, output, session) {
# for maintaining the current category (i.e. selection)
current_category <- reactiveVal()
# report sales by category, unless a category is chosen
sales_data <- reactive({
if (!length(current_category())) {
return(count(sales, category, wt = sales))
}
sales %>%
filter(category %in% current_category()) %>%
count(sub_category, wt = sales)
})
# Note that pie charts don't currently attach the label/value
# with the click data, but we can include as `customdata`
output$pie <- renderPlotly({
d <- setNames(sales_data(), c("labels", "values"))
plot_ly(d) %>%
add_pie(
labels = ~labels,
values = ~values,
customdata = ~labels
) %>%
layout(title = current_category() %||% "Total Sales")
})
# update the current category when appropriate
observe({
cd <- event_data("plotly_click")$customdata[[1]]
if (isTRUE(cd %in% categories)) current_category(cd)
})
# populate back button if category is chosen
output$back <- renderUI({
if (length(current_category()))
actionButton("clear", "Back", icon("chevron-left"))
})
# clear the chosen category on back button press
observeEvent(input$clear, current_category(NULL))
}
shinyApp(ui, server)- Un desglose básico como el de la app anterior es algo útil por sí
mismo, pero se vuelve mucho más útil cuando se vincula a múltiples
vistas de los datos. La siguiente rutina mejora la app anteriror para
mostrar las ventas a lo largo del tiempo por categoría o subcategoría
(si se elige una categoría). Observe que el aspecto clave de la
implementación sigue siendo el mismo (es decir, el mantenimiento del
estado a través de
reactiveValue()), la principal diferencia es que la vista de la serie de tiempo ahora también responde a los cambios en la categoría actualmente seleccionada. Es decir, ambas vistas muestran las ventas por categoría cuando no se selecciona ninguna categoría, y las ventas por subcategoría cuando se selecciona una categoría.
library(shiny)
library(dplyr)
library(readr)
library(plotly)
library(purrr)
sales <- read_csv("https://plotly-r.com/data-raw/sales.csv")
categories <- unique(sales$category)
ui <- fluidPage(
plotlyOutput("bar"),
uiOutput("back"),
plotlyOutput("time")
)
server <- function(input, output, session) {
current_category <- reactiveVal()
# report sales by category, unless a category is chosen
sales_data <- reactive({
if (!length(current_category())) {
return(count(sales, category, wt = sales))
}
sales %>%
filter(category %in% current_category()) %>%
count(sub_category, wt = sales)
})
# the pie chart
output$bar <- renderPlotly({
d <- setNames(sales_data(), c("x", "y"))
plot_ly(d) %>%
add_bars(x = ~x, y = ~y, color = ~x) %>%
layout(title = current_category() %||% "Total Sales")
})
# same as sales_data
sales_data_time <- reactive({
if (!length(current_category())) {
return(count(sales, category, order_date, wt = sales))
}
sales %>%
filter(category %in% current_category()) %>%
count(sub_category, order_date, wt = sales)
})
output$time <- renderPlotly({
d <- setNames(sales_data_time(), c("color", "x", "y"))
plot_ly(d) %>%
add_lines(x = ~x, y = ~y, color = ~color)
})
# update the current category when appropriate
observe({
cd <- event_data("plotly_click")$x
if (isTRUE(cd %in% categories)) current_category(cd)
})
# populate back button if category is chosen
output$back <- renderUI({
if (length(current_category()))
actionButton("clear", "Back", icon("chevron-left"))
})
# clear the chosen category on back button press
observeEvent(input$clear, current_category(NULL))
}
shinyApp(ui, server)Despliegue de aplicación Shiny
- La plataforma
shinyapps.ioes bastante usada para desplegar aplicacionesShiny. Por lo tanto para seguir con los siguientes pasos debe crearse una cuenta en shinyapps.io.shinyapps.ioofrece un plan gratuito, pero está limitado a 5 aplicaciones activas y a un uso mensual de 25 horas activas máximo. Si despliegas tu aplicación a disposición de un público amplio, esperarías superar el límite mensual de horas activas con bastante rapidez. Para aumentar el límite mensual (o para publicar más de 5 aplicaciones), tendrás que actualizar tu plan a uno de pago.
- Abra
RStudioy cree una nueva aplicaciónShiny
- Asignele un nombre (sin espacio), elige dónde guardarlo y haz clic
en el botón
Create
- Luego de esto copie en la app por defecto el código por ejemplo de
la anterior app creada para el
drill-down. De la misma manera que cuando se abre un nuevo documentoR Markdown, se crea el código para una aplicaciónShinybásica. Ejecute la aplicación haciendo clic en el botónRun Apppara ver el resultado
4. Se abrirá la app. Para
publicarla, debemos hacer click en el botón
Publish
- Luego de esto hacemos click en
ShinyApps.iopara conectarnos a nuestra cuenta, creada anteriormente
- Luego de esto necesitaremos un token para poder conectar nuestra app
al servidor de
ShinyApps.io. Con este fin, seguimos las instrucciones que aparecen en la ventana, esto es, vamos a nuestra cuenta y hacemos click en nuestro nombre de usuario, luego presionamos el botonTokens, despues de esto hacemos click enShowpara visualizar el token, luegoShow secret/Copy to clipboard. Luego de realizar esto, pegamos nuestro token en la ventana de publicación de nuestra app
- Luego le va a aparecer la siguiente ventana con la opción de
publicar su aplicación. Haga click en
Publish
- Después de varios segundos (dependiendo del peso de su aplicación), la aplicación Shiny debería aparecer en su navegador de Internet. Para volver a desplegar la aplicación puede hacerlo desde su aplicación haciendo click en el botón resaltado en la imagen y deberá visualizar su despliegue en la parte inferior. Ahora puede realizar cambio y usar sólo ese botón cuando desde hacer el despliegue de su aplicación. La app de este ejemplo ha sido publicada en el siguiente link https://lihkir.shinyapps.io/drill-down-app/.
- Puede revisar en su cuenta que se ha creado satisfactoriamente su aplicación. Puede visualizar las conexiones y demás información en los botones que se resaltan en rojo. Revise que información entrega cada uno.
Ejercicio para entregar
Despliegue en
shinnyapps.iolos dashboard de los dos ejemplos estudiados en la sección dePython: Introducción a Dash,Dos ejemplos de Dash. Esto es, desplegar enshinyapps.iolosDashasociados con:Dash App para MapasyDash App Financieraestudiados en esta sección dePython.Posteriormente, realice cambios en el (
back/front end) de los dosDashboards. Por ejemplo,- Agregue a los dashboard
nuevos estilos: Cambiar título, logo, color de fondo,….. Además, agregué un nuevo Dropdown para la columnaAffected by. Agregue divisiones con diagramas descriptivos para cada una de las columnas deldataframe. Realice agrupación porState, Affected by, Yeary realice gráficos descriptivos que entreguen información valiosa. - Agregue además de la ventana de selección de stocks, un nuevo
Dropdown, el cual le permita por medio de un menú de opciones,seleccionar entre 4 indicadores de análisis técnico(ver Commonly Used Technical Indicators; Technical Indicators Mathematical Description; 7 Technical Indicators to Build a Trading Toolkit, Best Day Trading Indicators for Beginners), por ejemplo:- On-balance volume (OBV)
- Accumulation/distribution (A/D) line
- Average directional index
- Aroon oscillator
- Moving average convergence divergence (MACD)
- Relative strength index (RSI)
- Stochastic oscillator
- Agregue a los dashboard
Cada uno de estos indicadores cuenta con parámetros que pueden modificarse desde un
Dropdown, por ejemplo, en el caso de lamedia movil, agregue unDropdownpara seleccionar el periodo. Para lasBollinger Bands, agregue otroDropdownpara, además del periodo de la media móvil, agregar, el número de desviaciones estándar que se desviará de la media cada banda, etc,…
Asegúrese de que
todos los indicadores seleccionados aparezcan en el mismo candlestick, esto es fundamental para la toma de decisiones. Por ejemplo, en un solo chart, el objetivo es que el inversionista pueda obtener un mínimo de 4 confirmaciones, por medio del análisis visual de cada uno de los indicadores suministrados por el Dashboard, antes de tomar una decisión de compra o venta (long/short). Por lo tanto,debe visualizar todos los 4 indicadores en un solo plotsobrepuesto.