Manejo de datos con R (II):
the tidyverse way

Programación y manejo de datos con R
(Web del curso aquí)

Data munging (II): dplyr



  • En el tema anterior (05A) vimos:

    • la pipe (%>% y |>), para encadenar funciones
    • qué son los datos tidy y cómo conseguirlos con tidyr (pivot_longer(), pivot_wider(), separate(), unite(), …)
  • Ahora que ya tenemos los datos tidy, toca manipularlos para responder a nuestras preguntas: eso lo haremos con el paquete dplyr

DPLYR


  • el paquete dplyr es el paquete más importante a la hora de manipular datos.

dplyr

  • dplyr es un paquete que permite manipular datos de forma intuitiva


Tiene 6-7 funciones (o verbos) principales

  • Cada función hace “una sola cosa”, así que para realizar transformaciones complejas hay que ir concatenando instrucciones sencillas con el operador pipe (%>%)

sintaxis de dplyr

Todas las funciones tienen una estructura o comportamiento similar:

  • el primer argumento siempre es un df

  • los siguientes argumentos describen qué hacer con los datos.

  • el resultado es siempre un nuevo df


Las siguientes 3 expresiones hacen exactamente lo mismo:

filter(df, X1 >= 10)

df %>% filter(. , X1 >= 10)

df %>% filter(X1 >= 10)

principales funciones de dplyr

  • select() : selecciona columnas
  • filter() : filtra/selecciona filas (que cumplen una o varias condiciones)


  • mutate() : crea nuevas columnas (variables)
  • arrange(): reordena las filas
  • rename() : cambia los nombres de las columnas


  • summarise() : resume (colapsa) los valores de una columna a un solo valor. Por ejemplo, calcula la media, máximo, mínimo, etc…

hay una séptima función (que da mucha potencia a dplyr)

  • group_by() : “agrupa” filas (en función de una o varias condiciones)
  • [🌶] En las extensiones veremos además across() y where()

Vamos a aprender a usar dplyr con ejemplos



trabajaremos con datos del pkg gapminder

  • ¿Supongo que ya sabéis qué hace el siguiente código?

    • Claro que sí: hacemos accesibles en memoria de R el fichero de datos gapminder que está en el paquete gapminder
gapminder <- gapminder::gapminder   

select() se utiliza para seleccionar variables

  • Seleccionar variables por nombre
aa <- gapminder %>% select(year, lifeExp) 
  • Eliminar variables por nombre
aa <- gapminder %>% select(-year, -lifeExp)


  • Seleccionar variables por posición
aa <- gapminder %>% select(1:3, 5)
  • Eliminar variables por posición
aa <- gapminder %>% select(-c(1:3, 5))

A veces queremos reordenar las columnas/variables

  • Podemos hacerlo con relocate()
aa <- gapminder %>% dplyr::relocate(country, .after = lifeExp)

aa <- gapminder %>% dplyr::relocate(country, .before = lifeExp)

A veces queremos renombrar las columnas

  • Podemos hacerlo con rename()
gapminder %>% rename(life_exp = lifeExp)



la función names() de R-base es útil

aa <- gapminder

names(aa)
[1] "country"   "continent" "year"      "lifeExp"   "pop"       "gdpPercap"

A veces queremos CREAR nuevas columnas

  • Podemos hacerlo con mutate()

  • Por ejemplo, podemos crear la variable: GDP = pop * gdpPercap

aa <- gapminder %>% mutate(GDP = pop * gdpPercap)


Extensión:

Por defecto, la nueva variable creada se situará al final del df, a no ser que usemos los argumentos .after y .before

aa <- gapminder %>% mutate(GDP = pop * gdpPercap, .after = pop)

filter(): permite seleccionar filas

filas que cumplan unas determinadas condiciones o criterios lógicos

gapminder <- gapminder::gapminder  #- cargamos los datos

#- Observaciones de España (country == "Spain")
aa <- gapminder %>% filter(country == "Spain") 

#- filas con valores de "lifeExp" < 29
aa <- gapminder %>% filter(lifeExp < 29)       

#- filas con valores de "lifeExp" entre [29, 32]
aa <- gapminder %>% filter(lifeExp >=  29 &  lifeExp <= 32)  
aa <- gapminder %>% filter(between(lifeExp, 29, 32))       

#- observaciones de países de África con lifeExp > 72
aa <- gapminder %>% filter(lifeExp > 72 &  continent == "Africa") 

#- observaciones de países de África o Asia con lifeExp > 72
aa <- gapminder %>% filter(lifeExp > 72 &  continent %in% c("Africa", "Asia") )  
aa <- gapminder %>% filter(lifeExp > 72 & (continent == "Africa" | continent == "Asia") ) 

slice() es una variante de filter()

permite seleccionar filas pero por posición


  • filter() y slice() ambas seleccionan filas, la primera por condiciones y la segunda por posición:


#- selecciona las observaciones de la décima a la decimoquinta
aa <- gapminder %>% slice(c(10:15)) 

#- selecciona las 4 primeras observaciones, de la 41 a la 43, y las 4 últimas 
aa <- gapminder %>%
  slice( c(1:4, 41:43, (n()-3):n() ) ) 

variantes de slice()

  • slice_max() y slice_min(): seleccionan filas con valor máximo (o mínimo) de una variable:
#- selecciona las 3 filas con mayor valor de lifeExp
aa <- gapminder %>% slice_max(lifeExp, n = 3)

#- selecciona las 4 filas con MENOR valor de pop
aa <- gapminder %>% slice_min(pop, n = 4)

#- observaciones en el primer decil en cuanto a esperanza de vida, 10% con menor esperanza de vida
aa <- gapminder %>% slice_min(lifeExp, prop = 0.1)

#- 1% de observaciones con mayor población. Imagino que estarán China e India
aa <- gapminder %>% slice_max(pop, prop = 0.01)

variantes de slice()

  • slice_sample(): permite obtener una muestra aleatoria de los datos


#- selecciona (aleatoriamente) 100 filas de los datos
aa <- gapminder %>% slice_sample(n = 100)

#- selecciona (aleatoriamente) un 5% de los datos
aa <- gapminder %>% slice_sample(prop = 0.05)

arrange(): permite reordenar las filas de un df


#- ordena las filas de MENOR a mayor según los valores de la v. lifeExp 
aa <- gapminder %>% arrange(lifeExp)

#- ordena las filas de MAYOR a menor según los valores de la v. lifeExp
aa <- gapminder %>% arrange(desc(lifeExp))  

#- ordena las filas de MENOR a mayor según los valores de la v. lifeExp. 
#- Si hubiese empates se resuelve con la variable "pop"
aa <- gapminder %>% arrange(lifeExp, pop) 

summarise() para “resumir” variables

  • summarise(): coge una variable como input y devuelve un solo valor; por ejemplo, calcula la media aritmética (o el mínimo, o el máximo …) de una columna/variable


  • Empezamos “resumiendo” una sola variable:


aa <- gapminder %>% summarise(maximo = max(pop))  #- el valor máximo de la variable "pop"

aa <- gapminder %>% summarise(NN = n())           #- el número de observaciones

aa <- gapminder %>% summarise(media = mean(lifeExp))  #- la media de la variable "lifeExp"

aa <- gapminder %>% summarise(desviacion_tipica = sd(lifeExp))  #- os lo explicarán en Estadística 

summarise(): “resumimos” dos variables


#- retornará 2 valores: las medias de "lifeExp" y "gdpPercap"
aa <- gapminder %>% summarise(mean(lifeExp), mean(gdpPercap))  



summarise(): hacemos 2 resúmenes de una variable


#- retornará 2 valores: la media y máximo de la v. "lifeExp"
aa <- gapminder %>% summarise(mean(lifeExp), max(lifeExp))

group_by(): con esta función ya se puede ver la potencia de dplyr

En análisis de datos muchas operaciones queremos calcularlas para distintos grupos (p. ej. mujer/hombre, diferentes países, provincias …). group_by() permite hacerlo.

  • group_by() coge un df y lo convierte en un “df agrupado”. En ese nuevo “df agrupado”, las operaciones que hagamos, se harán por separado para cada uno de los grupos que hayamos definido. Ahora lo vemos.
  • Por ejemplo: queremos calcular el nº de observaciones en el df y el nº de observaciones de cada continente
# calculamos el nº de observaciones en el df
aa <- gapminder %>% summarise(NN = n()) 
gt::gt(aa)
NN
1704
# ahora queremos calcular el nº de observaciones de cada continente
# cogemos el df y lo agrupamos por grupos definidos por la variable "continent"
# o sea, habrá 5 grupos (5 continentes)
# después con summarise() calcularemos el nº de observaciones en cada grupo;
# es decir, nos retornará un df con una fila por cada continente
# con el nº de observaciones de cada continente

bb <- gapminder %>% group_by(continent) %>% summarise(NN = n())
gt::gt(bb)
continent NN
Africa 624
Americas 300
Asia 396
Europe 360
Oceania 24

Tarea: practicando los verbos de dplyr


  • Con los datos de gapminder, obtén los países de Europa que en 2007 tenían una esperanza de vida mayor de 79 años


  • Quédate solo con las columnas country y lifeExp, y ordénalos de mayor a menor esperanza de vida


  • Tendrás que usar 3 verbos: filter(), select() y arrange()


  • En filter() puedes poner varias condiciones separadas por & (o por comas)


  • Para ordenar de mayor a menor: desc()
aa <- gapminder %>% 
  filter(continent == "Europe" & year == 2007 & lifeExp > 79) %>% 
  select(country, lifeExp) %>% 
  arrange(desc(lifeExp))
gt::gt(aa)
country lifeExp
Iceland 81.757
Switzerland 81.701
Spain 80.941
Sweden 80.884
France 80.657
Italy 80.546
Norway 80.196
Austria 79.829
Netherlands 79.762
Greece 79.483
Belgium 79.441
United Kingdom 79.425
Germany 79.406
Finland 79.313

Extensiones




con dplyr 1.0.0, en 2020, aparecieron dos funciones importantes: across() y where()

  • across() y where(): estas funciones son un poco diferentes, solo se usan en combinación de otra función/verbo. Son 2 funciones que en la jerga del tidyverse no son verbos sino adverbios, cualifican/cambian lo que hacen otras funciones como select()

Ejemplo de uso: select() junto a la función where() [🌶🌶🌶🌶]

  • En gapminder las 2 primeras variables (country y continent) son factores y las 4 siguientes son variables numéricas.


  • Imagina que queremos seleccionar sólo las variables numéricas. Podemos hacerlo por nombre o por posición pero mejor hacerlo con select() y where()
aa <- gapminder %>% select(is.numeric)        #- funciona, pero da un aviso: está obsoleto

aa <- gapminder %>% select(where(is.numeric)) #- es "preferible" esta segunda expresión


  • Si quisiéramos seleccionar las variables que NO son numéricas haríamos:
aa <- gapminder %>% select(!where(is.numeric)) 

más ejemplos de across() y where()


  • Imagina que queremos calcular la media de todas las variables de gapminder. Tendríamos que repetir código 6 veces … o usar across()
#- media de todas (everything()) las variables. 
#- Devuelve 2 warnings porque las 2 primeras son factores. No se puede calcular la media de continent y country
gapminder %>% summarise(across(everything(), mean) )


  • Ahora calculamos la media de la tercera a la sexta variable:
gapminder %>% summarise(across(3:6, mean) )

across() y where() con summarise()       😱


  • Dentro de across() se puede utilizar where()

  • Con esto conseguimos poder aplicar criterios lógicos en la selección de variables: [🌶] [ 🌟 ]


Si quisiéramos calcular la media de las variables numéricas

gapminder %>% summarise(across(where(is.numeric), mean))



Ahora no os asustéis de lo que salga: es lo mismo que lo de arriba pero con los nombres de los argumentos

#- con los nombres de los argumentos (más largo pero conviene verlo de vez en cuando)
gapminder %>% summarise(across(.cols = where(is.numeric), .fns = mean))


across() también funciona con mutate(): por ejemplo, para redondear todas las variables numéricas

gapminder %>% mutate(across(where(is.numeric), round))

summarise() con across() y varias funciones         😱😱😱😱

  • Si quieres calcular varios estadísticos de varias variables; por ejemplo, la media y la desviación típica de un grupo de variables

  • Tendrás que seguir utilizando summarise() con across() pero, además, tendrás que poner la lista de funciones dentro de list(). [🌶] [ 🌟 ]

Si quisiéramos calcular la media y la desviación típica de las variables 3 a 6

gapminder %>% summarise(across(3:6, list(media = mean, desv = sd)))


Otra vez, no os asustéis de lo que salga: es lo mismo que lo de arriba pero con los nombres de los argumentos

#- lo mismo, pero explicitando los nombres de los argumentos [🌶] 
gapminder %>% summarise(across(.cols = 3:6, .fns = list(media = mean, desv = sd) ))


Ya lo último: añadimos un argumento más .names, que nos permite controlar el nombre de las nuevas variables

#- lo mismo otra vez, pero eligiendo el nombre de las variables que se van a crear con .names [🌶] [🌶] 
gapminder %>% summarise(across(3:6, list(media = mean, desv = sd), .names = "{fn}_{col}"))

Más ejemplos para afianzar el uso de across()


Creamos un nuevo df: “gapminder_gr” o “gapminder agrupado”

gapminder_gr <- gapminder %>% filter(year %in% c(1952, 2007)) %>%
                 group_by(continent, year) 

Hacemos cálculos partiendo de “gapminder_gr”. Recuerda que está “agrupado” por continente y año

#- si queremos calcular la media de varias variables tenemos que usar across()
gapminder_gr %>% summarise(across(c(lifeExp, gdpPercap), mean))

#- si queremos calcular la media de todas las variables numéricas tenemos que usar across() y where()
gapminder_gr %>% summarise(across(where(is.numeric), mean))

#- si queremos calcular la media y la mediana, hay que usar list()
gapminder_gr %>% summarise(across(c(lifeExp, gdpPercap), 
                            list (media = mean, mediana = median) ))

#- si ponemos los nombres de los argumentos quedaría como
gapminder_gr %>% summarise(across(.cols = c(lifeExp, gdpPercap), 
                                  .fns = list (media = mean, mediana = median)))

#- además, podemos controlar el nombre de las variables creadas con el argumento .names
gapminder_gr %>% summarise(across(c(lifeExp, gdpPercap), 
                        list (media = mean, mediana = median), 
                        .names = "{fn}_{col}"))

Extensión: agrupar “solo para una operación” con .by [🌶🌶]

  • Cuando agrupamos por 2 variables, summarise() devuelve un df que sigue agrupado (por la primera variable). Por eso hacemos tantos ungroup()
aa <- gapminder %>% 
  group_by(continent, year) %>% 
  summarise(media = mean(lifeExp)) %>% 
  ungroup()                               #- hay que acordarse de desagrupar



  • Desde dplyr 1.1.0 (2023) se puede usar el argumento .by: agrupa solo para esa operación y el resultado ya sale desagrupado
aa <- gapminder %>% 
  summarise(media = mean(lifeExp), .by = c(continent, year))


  • Nosotros seguiremos usando group_by(), que es lo que verás en la mayoría del código, pero conviene conocer .by

Seguimos con dplyr



  • Seguimos con más ejemplos para afianzar nuestros conocimientos de los verbos de dplyr: select(), filter(), mutate(), arrange(), rename(), summarise() y group_by()

  • Empezamos con ejemplos sencillos; pronto llegaremos a “preguntas de verdad”

Ejemplos


para aprender a manipular datos con dplyr

CONTAR (observaciones)


  • Es importante saber cómo contar las observaciones que tenemos en distintos grupos

  • Por ejemplo, contar cuántas mujeres hay, o cuántas observaciones hay en cada continente, o cuántas empresas en diferentes sectores, …

… vamos a CONTAR

tenemos 3 formas distintas de contar el nº de observaciones (o filas)

1. Usando mutate() con n()

#- fíjate q con mutate() se mantienen todas las filas y todas las columnas
aa <- gapminder %>% mutate(NN = n())  

nrow(aa)  #- se mantienen las 1704 observaciones
[1] 1704

1. mutate() mantiene el nº de filas originales (1704)

bb <- head(aa, n = 3)
gt::gt(bb)
country continent year lifeExp pop gdpPercap NN
Afghanistan Asia 1952 28.801 8425333 779.4453 1704
Afghanistan Asia 1957 30.332 9240934 820.8530 1704
Afghanistan Asia 1962 31.997 10267083 853.1007 1704

2. Usando summarise() con n()

#- fíjate q  summarise() solo devuelve una fila y una columna
aa <- gapminder %>% summarise(NN = n())

2. summarise() devuelve una fila (por grupo)

gt::gt(aa)
NN
1704

3. Usando count()

aa <- gapminder %>% count()

3. count() devuelve una fila (por grupo)

gt::gt(aa)
n
1704

… seguimos CONTANDO

Pero ahora contamos el nº de observaciones de distintos grupos (por ejemplo el nº de observaciones de cada continente)

  • También podríamos usar mutate() pero esta vez solo usaremos summarise() y count()
# fíjate q  summarise() devuelve una fila por cada grupo
# una fila por cada continente
aa <- gapminder %>% 
  group_by(continent) %>% summarise(NN = n())
gt::gt(aa)
continent NN
Africa 624
Americas 300
Asia 396
Europe 360
Oceania 24
aa <- gapminder %>% count(continent)
gt::gt(aa)
continent n
Africa 624
Americas 300
Asia 396
Europe 360
Oceania 24

… aún seguimos CONTANDO

Contamos el nº de observaciones de distintos grupos, pero ahora definidos por dos variables (por ejemplo el nº de observaciones de cada continente y año)

# fíjate q hay 60 grupos (5 continentes x 12 periodos)
# por lo que devuelve 60 filas, una por grupo

aa <- gapminder %>% 
  group_by(continent, year) %>% summarise(NN = n())


aa <- gapminder %>% count(continent, year)
bb <- head(aa, n = 14)
gt::gt(bb) 
continent year n
Africa 1952 52
Africa 1957 52
Africa 1962 52
Africa 1967 52
Africa 1972 52
Africa 1977 52
Africa 1982 52
Africa 1987 52
Africa 1992 52
Africa 1997 52
Africa 2002 52
Africa 2007 52
Americas 1952 25
Americas 1957 25

Extensión: n() versus nrow()

  • n() es una función auxiliar en el tidyverse. Devuelve el nº de filas “in the current group”. Solo funciona en el tidyverse, concretamente en funciones como mutate() y summarise()
#- como agrupamos por año y continente, saldrán 60 grupos (12 x 5)

aa <- gapminder %>%
  group_by(continent, year) %>% 
  summarise(NN = n()) %>% 
  ungroup()
aa %>% slice(1, 2, 12, 13) %>% 
  gt::gt()
continent year NN
Africa 1952 52
Africa 1957 52
Africa 2007 52
Americas 1952 25
  • nrow() es una función de R-base. Devuelve el número de filas en x
#- como agrupamos por año y continente, saldrán 60 grupos (12 x 5)

#- con nrow()
bb <- gapminder %>% 
  group_by(continent, year) %>% 
  summarise(NN = nrow(.)) %>% 
  ungroup()
bb %>% slice(1, 2, 12, 13) %>% 
  gt::gt()
continent year NN
Africa 1952 1704
Africa 1957 1704
Africa 2007 1704
Americas 1952 1704
  • ⚠️ ¡Fíjate! Con nrow(.) sale 1704 en todos los grupos: nrow(.) cuenta todas las filas porque es una función antigua y no sabe que hacer con un df agrupado. Por eso, dentro de summarise() o mutate(), para contar las filas de cada grupo hay que usar n()

observaciones DISTINTAS


  • A veces es importante ver (y contar) las observaciones DISTINTAS que tengamos (en los distintos grupos de nuestros datos)

… ver/obtener observaciones DISTINTAS con distinct()

  • gapminder tiene 1.704 filas: ninguna repetida
# gapminder tiene 1.704 filas: ninguna repetida
aa <- gapminder %>% distinct()
# gapminder no tiene filas repetidas
nrow(aa) == nrow(gapminder)
[1] TRUE
  • En gapminder hay 5 valores distintos para los continentes
# en gapminder hay 5 valores distintos para los continentes
aa <- gapminder %>% distinct(continent)
# efectivamente 5 continentes
gt::gt(aa)
continent
Asia
Europe
Africa
Americas
Oceania
  • gapminder tiene 60 filas/combinaciones distintas para continente-año (5 x 12 = 60)
# combinaciones distintas de continente y año
aa <- gapminder %>% distinct(continent, year)
# efectivamente 60:  5 continentes x 12 periodos
nrow(aa)
[1] 60

… CONTAR observaciones DISTINTAS con n_distinct()

  • ¿cuántos países (distintos) hay en cada continente?
# nº de países distintos en cada continente
aa <- gapminder %>% 
  group_by(continent) %>% 
  summarise(NN = n_distinct(country))
gt::gt(aa)
continent NN
Africa 52
Americas 25
Asia 33
Europe 30
Oceania 2
  • ¿cuántos países (distintos) hay cada año en cada continente?
# nº de países distintos en cada continente y año
aa <- gapminder %>% 
  group_by(year, continent) %>% 
  summarise(NN = n_distinct(country)) %>% 
  ungroup()
gt::gt(head(aa, n = 6))
year continent NN
1952 Africa 52
1952 Americas 25
1952 Asia 33
1952 Europe 30
1952 Oceania 2
1957 Africa 52

obtener/calcular estadísticos


  • Ya os explicarán en Estadística y Econometría qué es un estadístico, cuáles son los más importantes y cómo se calculan 😉
  • Los estadísticos más sencillos de entender son: el máximo, el mínimo y la media

calculando ESTADÍSTICOS: máximo, mínimo, media, ……

  • obtener el máximo, mínimo y media de “lifeExp”:
aa <- gapminder %>% 
  summarise(maximo = max(lifeExp, na.rm = TRUE), 
            minimo = min(lifeExp, na.rm = TRUE), 
            media = mean(lifeExp, na.rm = TRUE) )
gt::gt(aa)
maximo minimo media
82.603 23.599 59.47444


calcular ESTADÍSTICOS (máximo, mínimo y media) por continente


#- calcular estadísticos por continente
aa <- gapminder %>% group_by(continent) %>% 
  summarise(maximo = max(lifeExp, na.rm = TRUE), 
            minimo = min(lifeExp, na.rm = TRUE), 
            media = mean(lifeExp, na.rm = TRUE) )
aa %>% gt::gt()
continent maximo minimo media
Africa 76.442 23.599 48.86533
Americas 80.653 37.579 64.65874
Asia 82.603 28.801 60.06490
Europe 81.757 43.585 71.90369
Oceania 81.235 69.120 74.32621

ESTADÍSTICOS para Europa año a año ……


#- calcular estadísticos por CONTINENTE Y AÑO
aa <- gapminder %>% 
1  group_by(continent, year) %>%
2  summarise(maximo = max(lifeExp, na.rm = TRUE),
            minimo = min(lifeExp, na.rm = TRUE), 
            mean = mean(lifeExp, na.rm = TRUE) ) %>% 
3  ungroup() %>%
4  filter(continent == "Europe")
1
primero agrupamos por continente y año
2
Calculamos los estadísticos (para las observaciones de cada año y continente)
3
Después de usar group_by() es una buena práctica hacer un ungroup()
4
Seleccionamos solo los datos de Europa
aa %>% gt::gt() %>% gt::fmt_number(3:5, decimals = 2)
continent year maximo minimo mean
Europe 1952 72.67 43.59 64.41
Europe 1957 73.47 48.08 66.70
Europe 1962 73.68 52.10 68.54
Europe 1967 74.16 54.34 69.74
Europe 1972 74.72 57.01 70.78
Europe 1977 76.11 59.51 71.94
Europe 1982 76.99 61.04 72.81
Europe 1987 77.41 63.11 73.64
Europe 1992 78.77 66.15 74.44
Europe 1997 79.39 68.83 75.51
Europe 2002 80.62 70.84 76.70
Europe 2007 81.76 71.78 77.65

[🌶🌶] Vuelvo a hacer lo mismo pero usando across():

aa <- gapminder %>% 
  group_by(continent, year) %>%
  summarise(across(lifeExp, list(maximo = max, minimo = min, mean = mean), .names = "{.fn}")) %>% 
  ungroup() %>% filter(continent == "Europe")

en la slide anterior, al calcular la esperanza de vida media por continente,
estábamos promediando países con diferente población 🫣


  • Vuelve a calcular la esperanza de vida media por continente y año, PERO ahora, ten en cuenta que los países tienen distinto nº de habitantes

  • Es decir, hemos de calcular la media, pero ponderada por la población (pop)

  • Pista: Igual puedes reusar el código de la página anterior, pero en lugar de usar mean() usar weighted.mean()

  • Tendrás que mirar la ayuda de weighted.mean() para usar un nuevo argumento

aa <- gapminder %>% 
  group_by(continent, year) %>%   
1  summarise(mean = mean(lifeExp, na.rm = TRUE),
            mean_w = weighted.mean(lifeExp, w = pop, na.rm = TRUE)) %>%    
  ungroup() %>%   
2  filter(year == 2007)
1
Calculamos la media y la media ponderada (para las observaciones de cada año y continente)
2
Ahora en lugar de ver los resultados para Europa, filtramos el año 2007
aa %>% gt::gt() %>% 
  gt::fmt_number(3:4, decimals = 2)
continent year mean mean_w
Africa 2007 54.81 54.56
Americas 2007 73.61 75.36
Asia 2007 70.73 69.44
Europe 2007 77.65 77.89
Oceania 2007 80.72 81.06

group_by() no es solo para summarise()

  • Con summarise() obtenemos una fila por grupo. Pero group_by() también se usa con mutate() y con filter(): se mantienen las filas y el cálculo se hace dentro de cada grupo
  • group_by() + mutate() nos permite calcular estadísticas de cada grupo y añadirlas como nuevas columnas al df original
#- ¿cuánto se desvía cada país de la media de su continente? (en 2007)
aa <- gapminder %>% 
  filter(year == 2007) %>% 
  select(continent, country, lifeExp) %>% 
  group_by(continent) %>% 
1  mutate(media_cont = mean(lifeExp)) %>%
  mutate(dif = lifeExp - media_cont) %>% 
  ungroup()
1
media_cont será la media de cada continente
aa %>% 
  filter(country %in% c("Spain", "France", "Angola")) %>% 
  gt::gt() %>% gt::fmt_number(3:5, decimals = 1)
continent country lifeExp media_cont dif
Africa Angola 42.7 54.8 −12.1
Europe France 80.7 77.6 3.0
Europe Spain 80.9 77.6 3.3
  • group_by() + filter() nos permite filtrar filas dentro de cada grupo
#- el país con mayor esperanza de vida de cada continente (en 2007)
bb <- gapminder %>% 
  filter(year == 2007) %>% 
  select(continent, country, lifeExp) %>% 
  group_by(continent) %>% 
1  filter(lifeExp == max(lifeExp)) %>%
  ungroup()
1
max(lifeExp) es el máximo de cada continente: nos quedamos con una fila por grupo
gt::gt(bb)
continent country lifeExp
Oceania Australia 81.235
Americas Canada 80.653
Europe Iceland 81.757
Asia Japan 82.603
Africa Reunion 76.442

Seguimos con más ejemplos



Por favor, sed conscientes de que: “las cosas no salen a la primera”


En palabras de Jenny Bryan:

Break the code into pieces, starting at the top, and inspect the intermediate results. That’s certainly how I was able to write such a thing. These commands do not leap fully formed out of anyone’s forehead – they are built up gradually, with lots of errors and refinements along the way. Is the statement above really hard for you to read? If yes, then by all means break it into pieces and make some intermediate objects. Your code should be easy to write and read when you’re done.

empezamos con una pregunta sencilla, pero …

  • ¿Cuántos países distintos hay en gapminder?

Algunas funciones del tidyverse que te podrían servir:

  • count()

  • summarise() junto a otras funciones

  • distinct()

  • n_distinct(): es un poco peculiar

  • n()

Lógicamente con R-base también se puede contar:

  • nrow()

  • length()

  • unique()

aa <- gapminder %>% group_by(country) %>%  
  summarise(NN = n())


aa <- gapminder %>% count(country)
aa %>% slice(1, 2, 142) %>% gt::gt()
country n
Afghanistan 12
Albania 12
Zimbabwe 12
aa <- gapminder %>% 
  summarise(NN_paises = n_distinct(country))
aa %>% gt::gt()
NN_paises
142
aa <- distinct(gapminder, country) 
aa %>% slice(1, 2, 142) %>% gt::gt()
country
Afghanistan
Albania
Zimbabwe
aa <- n_distinct(gapminder$country)

aa <- n_distinct(gapminder %>% select(country))
aa 
[1] 142
  • Solución “canónica” en R-base (además, se sigue viendo mucho):
length(unique(gapminder$country))
[1] 142


  • lo más equivalente en el tidyverse sería:
gapminder %>% count(country) %>% nrow()
[1] 142


  • o con pull(), que extrae una columna como vector (lo mismo que hace $):
gapminder %>% pull(country) %>% n_distinct()
[1] 142



  • Con esta pregunta “tan sencilla” quería:

    • mostrar algunas funciones (length(), unique() y nrow()) de R-base

    • mostraros que … cuando hay que obtener “una sola cosa” , y esta cosa no es complicada, entonces, la solución en R-base suele ser sencilla


  • PERO los análisis de datos son bastante más complejos, y es entonces, cuando el tidyverse se hace casi “indispensable”

otra pregunta


  • En gapminder ¿Cuántos países distintos hay en cada continente?

  • Después, calcula el porcentaje de países de cada continente sobre el total

#- Solución à la tidyverse
aa <- gapminder %>% 
  group_by(continent) %>% 
  summarise(NN = n_distinct(country)) %>% 
  mutate(NN_total = sum(NN)) %>%
  mutate(porcentaje = (NN / NN_total) * 100) 
gt::gt(aa)
continent NN NN_total porcentaje
Africa 52 142 36.619718
Americas 25 142 17.605634
Asia 33 142 23.239437
Europe 30 142 21.126761
Oceania 2 142 1.408451
#- solución à la R-base (seguro q se puede mejorar algo ...)
bb <- unique(gapminder[, c("country", "continent")])
cc <- aggregate(country ~ continent, data = bb, FUN = length)

# Renombrar la columna de conteo
colnames(cc)[colnames(cc) == "country"] <- "NN"

# Calcular el número total de países únicos en gapminder
NN_total <- sum(cc$NN)
cc$NN_total <- NN_total

# Calcular el porcentaje de países de cada continente sobre el total
cc$porcentaje <- (cc$NN / cc$NN_total) * 100
gt::gt(cc)
continent NN NN_total porcentaje
Africa 52 142 36.619718
Americas 25 142 17.605634
Asia 33 142 23.239437
Europe 30 142 21.126761
Oceania 2 142 1.408451


Se podrá mejorar algo el código, pero, fíjate que hemos tenido que crear varios objetos intermedios

ahora … una pregunta de verdad

  • ¿En qué continente ha aumentado más la esperanza de vida en el periodo 1952-2007?


#- esta parte va a ser común en los 3 intentos

aa <- gapminder %>% 
  filter(year %in% c(1952, 2007)) %>%  
  group_by(continent, year) %>% 
  summarise(media = mean(lifeExp)) %>% ungroup()
gt::gt(aa)
continent year media
Africa 1952 39.13550
Africa 2007 54.80604
Americas 1952 53.27984
Americas 2007 73.60812
Asia 1952 46.31439
Asia 2007 70.72848
Europe 1952 64.40850
Europe 2007 77.64860
Oceania 1952 69.25500
Oceania 2007 80.71950
#- se podría hacer de una vez, pero partimos el código en 2 trozos

aa <- gapminder %>% filter(year %in% c(1952, 2007)) %>%  
  group_by(continent, year) %>% 
  summarise(media = mean(lifeExp)) %>% ungroup()

bb <- aa %>% group_by(continent) %>% 
  summarise(min_l = min(media), max_l = max(media)) %>% 
  mutate(dif = max_l-min_l) %>% 
  arrange(desc(dif))


En esta solución “podría haber un problema”

gt::gt(aa)
continent year media
Africa 1952 39.13550
Africa 2007 54.80604
Americas 1952 53.27984
Americas 2007 73.60812
Asia 1952 46.31439
Asia 2007 70.72848
Europe 1952 64.40850
Europe 2007 77.64860
Oceania 1952 69.25500
Oceania 2007 80.71950
gt::gt(bb)
continent min_l max_l dif
Asia 46.31439 70.72848 24.41409
Americas 53.27984 73.60812 20.32828
Africa 39.13550 54.80604 15.67054
Europe 64.40850 77.64860 13.24010
Oceania 69.25500 80.71950 11.46450
#- el primer trozo sigue siendo común

aa <- gapminder %>% filter(year %in% c(1952, 2007)) %>%  
         group_by(continent, year) %>% 
         summarise(media = mean(lifeExp)) %>% ungroup() 

#- usamos lag()
bb <- aa %>% group_by(continent) %>% 
              arrange(year) %>%
              mutate(variacion = media - lag(media)) %>% ungroup()

#- para mostrar  los resultados
cc <- bb %>% filter(year == 2007) %>% arrange(desc(variacion))
gt::gt(bb)
continent year media variacion
Africa 1952 39.13550 NA
Americas 1952 53.27984 NA
Asia 1952 46.31439 NA
Europe 1952 64.40850 NA
Oceania 1952 69.25500 NA
Africa 2007 54.80604 15.67054
Americas 2007 73.60812 20.32828
Asia 2007 70.72848 24.41409
Europe 2007 77.64860 13.24010
Oceania 2007 80.71950 11.46450
gt::gt(cc)
continent year media variacion
Asia 2007 70.72848 24.41409
Americas 2007 73.60812 20.32828
Africa 2007 54.80604 15.67054
Europe 2007 77.64860 13.24010
Oceania 2007 80.71950 11.46450
#- ya sabemos que la primera parte es común

aa <- gapminder %>% 
  filter(year %in% c(1952, 2007)) %>%  
  group_by(continent, year) %>% 
  summarise(media = mean(lifeExp)) %>% ungroup()

#- pero ahora usamos pivot_wider()
bb <- aa %>% pivot_wider(names_from = year, values_from = media) %>% 
     mutate(dif_l = 2007 - 1952) %>% 
     arrange(desc(dif_l))


¿qué error cometí?

gt::gt(aa)
continent year media
Africa 1952 39.13550
Africa 2007 54.80604
Americas 1952 53.27984
Americas 2007 73.60812
Asia 1952 46.31439
Asia 2007 70.72848
Europe 1952 64.40850
Europe 2007 77.64860
Oceania 1952 69.25500
Oceania 2007 80.71950
gt::gt(bb)
continent 1952 2007 dif_l
Africa 39.13550 54.80604 55
Americas 53.27984 73.60812 55
Asia 46.31439 70.72848 55
Europe 64.40850 77.64860 55
Oceania 69.25500 80.71950 55
bb <- aa %>% pivot_wider(names_from = year, values_from = media) %>% 
1     mutate(dif_l = `2007` - `1952`) %>%
     arrange(desc(dif_l))
1
2007 - 1952 resta dos números (siempre da 55). Para referirnos a las columnas que se llaman 2007 y 1952 (nombres “no sintácticos”, porque empiezan por un número) hay que usar comillas invertidas: `2007`


gt::gt(bb)
continent 1952 2007 dif_l
Asia 46.31439 70.72848 24.41409
Americas 53.27984 73.60812 20.32828
Africa 39.13550 54.80604 15.67054
Europe 64.40850 77.64860 13.24010
Oceania 69.25500 80.71950 11.46450

preguntas de verdad (II)

  • ¿qué hace el código de abajo?


aa <- gapminder %>% 
  group_by(continent, year) %>% 
  select(continent, year, lifeExp) %>% 
  summarise(mean_life = mean(lifeExp)) %>% 
  arrange(year) %>% 
  mutate(incre_mean_life_0 = mean_life - first(mean_life)) %>% 
  mutate(incre_mean_life_t = mean_life - lag(mean_life)) %>% 
  arrange(continent) %>% 
  ungroup()

#- por ejemplo veamos el resultado para Europe
bb <- aa %>% filter(continent == "Europe")


gt::gt(bb)
continent year mean_life incre_mean_life_0 incre_mean_life_t
Europe 1952 64.40850 0.000000 NA
Europe 1957 66.70307 2.294567 2.2945667
Europe 1962 68.53923 4.130733 1.8361667
Europe 1967 69.73760 5.329100 1.1983667
Europe 1972 70.77503 6.366533 1.0374333
Europe 1977 71.93777 7.529267 1.1627333
Europe 1982 72.80640 8.397900 0.8686333
Europe 1987 73.64217 9.233667 0.8357667
Europe 1992 74.44010 10.031600 0.7979333
Europe 1997 75.50517 11.096667 1.0650667
Europe 2002 76.70060 12.292100 1.1954333
Europe 2007 77.64860 13.240100 0.9480000

a ver si entendéis este ejemplo


aa <- gapminder %>%
  filter(continent == "Asia") %>%
  select(year, country, lifeExp) %>%
  group_by(year) %>%
  slice_max(n = 3, lifeExp) %>% 
  arrange(year) %>% 
  ungroup()


aa %>% head(10) %>% gt::gt()
year country lifeExp
1952 Israel 65.39
1952 Japan 63.03
1952 Hong Kong, China 60.96
1957 Israel 67.84
1957 Japan 65.50
1957 Hong Kong, China 64.75
1962 Israel 69.39
1962 Japan 68.73
1962 Hong Kong, China 67.65
1967 Japan 71.43

Calcular CRECIMIENTOS



  • Muchas veces interesa saber cuánto crecen las variables, por ejemplo el PIB, o el desempleo , …, o la esperanza de vida

calculando CRECIMIENTOS


  • calcular el crecimiento de la esperanza de vida, de un periodo a otro, en España
aa <- gapminder %>% 
1  select(country, year, lifeExp) %>%
2  group_by(country) %>%
3  arrange(year) %>%
4  mutate(crecimiento =  lifeExp - lag(lifeExp)) %>%
5  ungroup() %>%
6  filter(country == "Spain")
1
Selecciono las variables relevantes
2
Agrupamos por país: los cálculos siguientes se harán para cada país
3
Ordenamos por año: el año más antiguo (1952) el primero
4
Calculamos el crecimiento de la esperanza de vida de un año a otro
5
Al usar group_by() es una buena práctica hacer después un ungroup()
6
Seleccionamos solo los datos de España
gt::gt(aa) %>% 
  gt::fmt_number(3:4, decimals = 2)
country year lifeExp crecimiento
Spain 1952 64.94 NA
Spain 1957 66.66 1.72
Spain 1962 69.69 3.03
Spain 1967 71.44 1.75
Spain 1972 73.06 1.62
Spain 1977 74.39 1.33
Spain 1982 76.30 1.91
Spain 1987 76.90 0.60
Spain 1992 77.57 0.67
Spain 1997 78.77 1.20
Spain 2002 79.78 1.01
Spain 2007 80.94 1.16

calculando CRECIMIENTOS ACUMULADOS

  • calcular el crecimiento de la esperanza de vida ACUMULADO en España
aa <- gapminder %>% 
  select(country, year, lifeExp) %>%   
1  group_by(country) %>%
2  arrange(year) %>%
3  mutate(crec_1 = lifeExp - lag(lifeExp)) %>%
4  mutate(crec_acu_1 = cumsum(crec_1)) %>%
5  mutate(crec_acu_2 = lifeExp - first(lifeExp)) %>%
6  ungroup() %>%
  filter(country == "Spain") 
1
Agrupamos por país: los cálculos siguientes se harán para cada país
2
Ordenamos por año: el año más antiguo (1952) el primero
3
Calculamos el crecimiento de la esperanza de vida de un año a otro
4
Usamos cumsum() pero no va a funcionar: saldrá todo NA
5
Crecimiento acumulado: calculamos el incremento respecto a la 1ª observación
6
Al usar group_by() es una buena práctica hacer después un ungroup()
gt::gt(aa) %>% 
  gt::fmt_number(3:6, decimals = 2)
country year lifeExp crec_1 crec_acu_1 crec_acu_2
Spain 1952 64.94 NA NA 0.00
Spain 1957 66.66 1.72 NA 1.72
Spain 1962 69.69 3.03 NA 4.75
Spain 1967 71.44 1.75 NA 6.50
Spain 1972 73.06 1.62 NA 8.12
Spain 1977 74.39 1.33 NA 9.45
Spain 1982 76.30 1.91 NA 11.36
Spain 1987 76.90 0.60 NA 11.96
Spain 1992 77.57 0.67 NA 12.63
Spain 1997 78.77 1.20 NA 13.83
Spain 2002 79.78 1.01 NA 14.84
Spain 2007 80.94 1.16 NA 16.00

calculando TASAS DE CRECIMIENTO (en %)

  • Con el PIB, los precios, … lo que interesa no es cuánto crecen sino a qué tasa crecen (en %). Por ejemplo, el PIB per cápita de España
aa <- gapminder %>% 
  select(country, year, gdpPercap) %>% 
  group_by(country) %>% 
  arrange(year) %>% 
1  mutate(crec = gdpPercap - lag(gdpPercap)) %>%
2  mutate(tasa = crec / lag(gdpPercap) * 100) %>%
3  mutate(tasa_acu = (gdpPercap / first(gdpPercap) - 1) * 100) %>%
4  mutate(indice = gdpPercap / first(gdpPercap) * 100) %>%
  ungroup() %>% 
  filter(country == "Spain")
1
El crecimiento en niveles, como antes: el valor de un periodo menos el del periodo anterior
2
La tasa de crecimiento: el crecimiento dividido por el valor del periodo anterior (y multiplicado por 100). Ojo: en gapminder hay un dato cada 5 años, así que es la tasa del quinquenio, no la anual
3
La tasa de crecimiento acumulada: cuánto ha crecido respecto al primer valor (1952). Ojo: no se calcula sumando las tasas de cada periodo
4
Índice con base 100 en 1952: muy útil para comparar la evolución de países (o variables) con niveles distintos
gt::gt(aa) %>% 
  gt::fmt_number(3:7, decimals = 1)
country year gdpPercap crec tasa tasa_acu indice
Spain 1952 3,834.0 NA NA 0.0 100.0
Spain 1957 4,564.8 730.8 19.1 19.1 119.1
Spain 1962 5,693.8 1,129.0 24.7 48.5 148.5
Spain 1967 7,993.5 2,299.7 40.4 108.5 208.5
Spain 1972 10,638.8 2,645.2 33.1 177.5 277.5
Spain 1977 13,236.9 2,598.2 24.4 245.2 345.2
Spain 1982 13,926.2 689.2 5.2 263.2 363.2
Spain 1987 15,765.0 1,838.8 13.2 311.2 411.2
Spain 1992 18,603.1 2,838.1 18.0 385.2 485.2
Spain 1997 20,445.3 1,842.2 9.9 433.3 533.3
Spain 2002 24,835.5 4,390.2 21.5 547.8 647.8
Spain 2007 28,821.1 3,985.6 16.0 651.7 751.7

Calcular PORCENTAJES


  • Muchas veces interesa saber cuántas observaciones hay en cada grupo: eso se hace contando, que ya lo hemos visto


  • Una vez hemos contado, muchas veces interesa saber el porcentaje que representa cada grupo respecto al total

  • Para calcular esos porcentajes, hay que dividir el nº de observaciones de cada grupo entre el total. Veamos cómo hacerlo

calculando PORCENTAJES (con R à la tidyverse)

  • Calcular el % que representa cada continente en la población mundial:
#- Calcular % de población de cada continente 
aa <- gapminder %>% 
1  group_by(continent, year) %>%
2  summarise(pob_continent = sum(pop, na.rm = TRUE)) %>% ungroup() %>%
3  group_by(year) %>%
4  mutate(pob_mundo = sum(pob_continent),
5         pob_percent = pob_continent/pob_mundo * 100) %>%
  ungroup()
1
Agrupamos por continente y año
2
Calculo la población de cada continente para cada año
3
Vuelvo a agrupar, ahora solo por años
4
Calculo la población mundial para cada año
5
Finalmente calculamos el porcentaje


1bb <- aa %>% filter(year %in% c(1952, 2007))
1
Me quedo con el año inicial y final
gt::gt(bb) %>% 
  gt::fmt_number(pob_percent, decimals = 2)
continent year pob_continent pob_mundo pob_percent
Africa 1952 237640501 2406957150 9.87
Africa 2007 929539692 6251013179 14.87
Americas 1952 345152446 2406957150 14.34
Americas 2007 898871184 6251013179 14.38
Asia 1952 1395357351 2406957150 57.97
Asia 2007 3811953827 6251013179 60.98
Europe 1952 418120846 2406957150 17.37
Europe 2007 586098529 6251013179 9.38
Oceania 1952 10686006 2406957150 0.44
Oceania 2007 24549947 6251013179 0.39

Extensión: tabla con la importancia (en términos de población) de cada continente en el tiempo

  • Antes hemos calculado el % que representa la población de cada continente, pero lo tenemos en formato largo
continent year pob_continent pob_mundo pob_percent
Africa 1952 237640501 2406957150 9.9
Americas 1952 345152446 2406957150 14.3

Ahora quiero hacer la tabla presentable: he de pasarla a formato ancho

bb <- aa %>% 
  select(continent, year, pob_mundo, pob_percent) %>%
  tidyr::pivot_wider(names_from = continent, 
                     values_from = pob_percent) 
gt::gt(bb) %>% 
  gt::fmt_number(2, sep_mark = ".", decimals = 0) %>% 
  gt::fmt_number(3:7, decimals = 1)
year pob_mundo Africa Americas Asia Europe Oceania
1952 2.406.957.150 9.9 14.3 58.0 17.4 0.4
1957 2.664.404.580 9.9 14.5 58.7 16.4 0.4
1962 2.899.782.974 10.2 14.9 58.5 15.9 0.5
1967 3.217.478.384 10.4 14.9 59.2 15.0 0.5
1972 3.576.977.158 10.6 14.8 60.1 14.0 0.5
1977 3.930.045.807 11.0 14.7 60.7 13.2 0.4
1982 4.289.436.840 11.6 14.7 60.9 12.4 0.4
1987 4.691.477.418 12.3 14.6 61.2 11.6 0.4
1992 5.110.710.260 12.9 14.5 61.3 10.9 0.4
1997 5.515.204.472 13.5 14.4 61.3 10.3 0.4
2002 5.886.977.579 14.2 14.4 61.2 9.8 0.4
2007 6.251.013.179 14.9 14.4 61.0 9.4 0.4

Calcular RANKINGS


  • En CC.SS interesa saber el ranking que ocupa cada individuo/país/empresa/ …

calculando RANKINGs

  • Calcular el ranking de España en cuanto a Esperanza de vida
aa <- gapminder %>% 
1  select(country, year, lifeExp) %>%
2  group_by(year) %>%
3    mutate(rank_1 = row_number(desc(lifeExp))) %>%
    mutate(rank_2 = min_rank(desc(lifeExp)) ) %>% 
    #- también puedo calcular el ranking a mano
4    arrange(desc(lifeExp)) %>%
    mutate(rank_mio = 1:n())  %>%
  ungroup() 
1
Selecciono las variables relevantes
2
Agrupamos por año ya que queremos calcular el ranking cada año
3
Podemos usar distintos métodos para calcular el ranking, siempre en función del valor de lifeExp de mayor a menor. Solo se diferencian cuando hay empates (ver la extensión)
4
También puedo calcular el ranking “a mano” (es lo mismo que row_number()). Para ello, antes, he de ordenar las observaciones por desc(lifeExp)
aa %>% 
  filter(country == "Spain") %>% 
  gt::gt() %>% 
  gt::fmt_number(lifeExp, decimals = 1)
country year lifeExp rank_1 rank_2 rank_mio
Spain 2007 80.9 6 6 6
Spain 2002 79.8 8 8 8
Spain 1997 78.8 8 8 8
Spain 1992 77.6 7 7 7
Spain 1987 76.9 5 5 5
Spain 1982 76.3 4 4 4
Spain 1977 74.4 8 8 8
Spain 1972 73.1 8 8 8
Spain 1967 71.4 10 10 10
Spain 1962 69.7 18 18 18
Spain 1957 66.7 26 26 26
Spain 1952 64.9 24 24 24

Extensión: rankings con empates [🌶]

  • row_number(), min_rank() y dense_rank() solo se diferencian cuando hay empates:
df <- tibble(nota = c(10, 9, 9, 7)) %>% 
1  mutate(r_row   = row_number(desc(nota)),
2         r_min   = min_rank(desc(nota)),
3         r_dense = dense_rank(desc(nota)))
1
desempata por el orden de las filas: 1, 2, 3, 4
2
los empatados comparten puesto y se salta el siguiente: 1, 2, 2, 4 (como en el deporte)
3
los empatados comparten puesto sin saltos: 1, 2, 2, 3
gt::gt(df)
nota r_row r_min r_dense
10 1 1 1
9 2 2 2
9 3 2 2
7 4 4 3

2 funciones muy útiles: ifelse() y case_when()


  • ifelse(): ejecuta “algo” de manera condicional

  • case_when(): es una generalización de ifelse()


  • Sí, no parece muy sencillo, pero con ejemplos se entiende mucho mejor

ifelse(): ejecuta “algo” de manera condicional

veamos algunos ejemplos

aa <- gapminder %>% 
  select(-pop) %>% 
  mutate(X1 = ifelse(lifeExp > 70, "longevo", "no longevo")) %>% 
  mutate(X2 = ifelse(gdpPercap > 10000, "rico", "pobre")) %>% 
  mutate(X3 = ifelse(lifeExp > 70 & gdpPercap > 10000, 
                     "longevo y rico", 
                     "no longevo y/o pobre")) %>% 
  mutate(X4 = ifelse(continent == "Europe", "europeo", "no europeo")) 


el resultado es:

aa %>% filter(country %in% c("Spain", "Angola")) %>% 
  filter(year %in% c(1952, 2007)) %>% 
  gt::gt() %>% 
  gt::opt_stylize(style = 3) %>% 
  gt::cols_align(align = "center")
country continent year lifeExp gdpPercap X1 X2 X3 X4
Angola Africa 1952 30.015 3520.610 no longevo pobre no longevo y/o pobre no europeo
Angola Africa 2007 42.731 4797.231 no longevo pobre no longevo y/o pobre no europeo
Spain Europe 1952 64.940 3834.035 no longevo pobre no longevo y/o pobre europeo
Spain Europe 2007 80.941 28821.064 longevo rico longevo y rico europeo

case_when(): es una generalización de ifelse()

A ver si entiendes este ejemplo:

aa <- gapminder %>%
  group_by(continent, year)  %>%
  mutate(media_lifeExp = mean(lifeExp)) %>% 
  mutate(media_gdpPercap = mean(gdpPercap)) %>% 
1  mutate(GOOD_or_BAD = case_when(
2    lifeExp > media_lifeExp & gdpPercap > media_gdpPercap  ~ "good",
3    lifeExp < media_lifeExp & gdpPercap < media_gdpPercap  ~ "bad" ,
4    .default = "medium"  ) )
1
case_when() va comprobando las condiciones en orden; en cuanto una se cumple, asigna su valor
2
por encima de la media de su continente (y año) en las 2 variables
3
por debajo de la media en las 2 variables
4
el resto de casos (por encima en una y por debajo en la otra)

El resultado es:

aa %>% ungroup() %>% filter(country == "Spain", year <= 1957) %>% select(-pop) %>%
  gt::gt() %>% 
  gt::fmt_number(4:7, sep_mark = ".",   dec_mark = ",", decimals = 2) %>% 
  gt::opt_stylize(style = 2) %>% 
  gt::cols_align(align = "center")
country continent year lifeExp gdpPercap media_lifeExp media_gdpPercap GOOD_or_BAD
Spain Europe 1952 64,94 3.834,03 64,41 5.661,06 medium
Spain Europe 1957 66,66 4.564,80 66,70 6.963,01 bad

crear grupos a partir de una variable numérica: ntile() y cut()

  • Muchas veces queremos clasificar las observaciones en grupos según una variable numérica: quintiles de renta, tramos de edad, … Se puede hacer con case_when(), pero hay 2 funciones que lo hacen más rápido
aa <- gapminder %>% 
  filter(year == 2007) %>% 
  select(country, gdpPercap) %>% 
1  mutate(quintil = ntile(gdpPercap, 5)) %>%
2  mutate(tramo = cut(gdpPercap,
3                     breaks = c(0, 2000, 10000, Inf),
4                     labels = c("bajo", "medio", "alto")))
1
ntile() divide las observaciones en grupos con el mismo nº de observaciones: con 5 son quintiles (el 1 es el 20% de países más pobres y el 5 el 20% más ricos)
2
cut() (de R-base) divide en tramos que elegimos nosotros
3
los límites de los tramos: de 0 a 2.000, de 2.000 a 10.000 y más de 10.000
4
el nombre de cada tramo
aa %>% 
  filter(country %in% c("Angola", "China", 
                        "Morocco", "Spain")) %>% 
  gt::gt() %>% 
  gt::fmt_number(gdpPercap, decimals = 0)
country gdpPercap quintil tramo
Angola 4,797 3 medio
China 4,959 3 medio
Morocco 3,820 2 medio
Spain 28,821 5 alto

COMBINANDO tablas (joining df’s)


Hasta ahora hemos trabajado con un único df, pero …

… muchas veces tenemos que trabajar con datos que están en varias tablas


  • así que, a veces tendremos que juntar o fusionar tablas

dos casos ideales (sencillos de unir): bind_cols() y bind_rows()


A) Si los 2 dfs tienen exactamente las mismas filas o unidades de análisis (y además en el mismo orden)

Solo habría que juntar en una misma tabla las columnas de df1 y de df2

Esto lo podemos hacer con bind_cols() (o con cbind() de R-base)

df_1 <- iris[ , 1:2]  ; df_2 <- iris[ , 3:5]

df_1 <- iris %>% select(1:2)  ; df_2 <- iris %>% select(3:5) 

df_3 <- bind_cols(df_1, df_2)

identical(iris, df_3)


B) Si los 2 dfs tienen exactamente las mismas columnas (y además en el mismo orden)

Solo habría que juntar en una misma tabla las filas de los dos df’s

Esto lo podemos hacer con bind_rows() (o con rbind() de R-base)

df_1 <- iris[1:75, ]  ; df_2 <- iris[76:150, ]

df_1 <- iris %>% slice(1:75)  ; df_2 <- iris %>% slice(76:150) 

df_3 <- bind_rows(df_1, df_2)

identical(iris, df_3)

joining df’s

  • Hay diversos tipos de uniones de tablas: un buen recurso para estos temas es el capítulo dedicado a JOINS en R4DS2ed

  • Las uniones más comunes son las “mutating joins”, que añaden columnas de una tabla a otra. Lo vemos:


mutating joins (juntando df1 con df2)

  • Las mutating joins lo que hacen es añadir las variables de df2 a df1
  • Hay 4 tipos de mutating joins (aquí veremos 3). Todas añaden las columnas de df2 al df1; PERO …


  • … se diferencian en las filas que se seleccionan

  • Las filas que se seleccionan dependen del criterio para hacer el match. Lo vemos:

tipos de mutating joins

Recuerda que todas ellas añaden columnas (las columnas de df2 a df1) pero que se diferencian en las filas que se seleccionan

  • inner_join(df1,df2): retorna las filas de df1 que también existen en df2
  • left_join(df1,df2): retorna TODAS las filas de df1
  • full_join(df1,df2): retorna TODAS las filas de df1 y de df2; (o sea, retorna TODAS las filas y TODAS las columnas de las 2 tablas)
  • (también existe right_join(df1, df2), que retorna TODAS las filas de df2; es como un left_join() con las tablas al revés)

mutating joins: un ejemplo

  • En el ejemplo usaremos estos 2 df’s:
df1 <- tibble(id = 1:3, x = paste0("x", 1:3))
df2 <- tibble(id = 1:4, y = paste0("y", 1:4)) %>% slice(-3)
df1
id x
1 x1
2 x2
3 x3
df2
id y
1 y1
2 y2
4 y4

mutating joins: un ejemplo


df_inner <- inner_join(df1, df2, by = "id")  #- inner_join() solo incluye las filas que están en df1 y en df2

df_full_join <- full_join(df1, df2, by = "id")  #- full_join() incluye todas las filas de df1 y de df2

df_left_join <- left_join(df1, df2, by = "id")  #- left_join() incluye todas las filas de df1

  • Con by = "id" le decimos qué columna usar para hacer el match. Si no lo ponemos, R usa las columnas con el mismo nombre en las 2 tablas (y avisa con un mensaje). Mejor ponerlo siempre

Extensión: cuando las claves no coinciden [🌶]

  • A veces la columna para hacer el match se llama de forma diferente en cada tabla. Se lo decimos con join_by()
df3 <- df2 %>% rename(codigo = id)    #- df3 es como df2, pero la clave se llama "codigo"

aa <- left_join(df1, df3, by = join_by(id == codigo))
df1
id x
1 x1
2 x2
3 x3
df3
codigo y
1 y1
2 y2
4 y4
gt::gt(aa)
id x y
1 x1 y1
2 x2 y2
3 x3 NA
  • Verás también la forma antigua, que hace lo mismo: by = c("id" = "codigo")


  • Otras veces el match hay que hacerlo por 2 columnas; por ejemplo, país y año
pob   <- gapminder %>% select(country, year, pop)
renta <- gapminder %>% select(country, year, gdpPercap)

bb <- left_join(pob, renta, by = join_by(country, year))
  • Si hiciésemos el match solo por country, cada fila de pob se juntaría con las 12 filas (los 12 años) de ese país en renta

Extensión: filtering joins [🌶]

  • Las filtering joins no añaden columnas: solo filtran las filas de df1
  • semi_join(): por ejemplo, en una encuesta de presupuestos familiares, para quedarte con el gasto de los hogares que aparecen en la tabla de hogares con hipoteca
aa <- semi_join(df1, df2, by = "id")   #- filas de df1 que SÍ están en df2
df1
id x
1 x1
2 x2
3 x3
df2
id y
1 y1
2 y2
4 y4
gt::gt(aa)
id x
1 x1
2 x2
  • anti_join() es muy útil para encontrar problemas: por ejemplo, qué países de una tabla no aparecen en la otra
bb <- anti_join(df1, df2, by = "id")   #- filas de df1 que NO están en df2
df1
id x
1 x1
2 x2
3 x3
df2
id y
1 y1
2 y2
4 y4
gt::gt(bb)
id x
3 x3

Tarea: fusionar dos tablas


  • El paquete gapminder trae otra tabla, gapminder::country_codes, con los códigos ISO de cada país
codigos <- gapminder::country_codes
country iso_alpha iso_num
Afghanistan AFG 4
Albania ALB 8
Algeria DZA 12


  • Añade a gapminder las columnas con los códigos ISO de cada país


  • Tienes que usar un mutating join: queremos mantener todas las filas de gapminder y añadirle columnas


  • ¿Qué columna tienen en común las 2 tablas? Esa será la columna para hacer el match (by)
aa <- left_join(gapminder, codigos, by = "country")
country continent year lifeExp pop gdpPercap iso_alpha iso_num
Afghanistan Asia 1952 28.801 8425333 779.4453 AFG 4
Afghanistan Asia 1957 30.332 9240934 820.8530 AFG 4
Afghanistan Asia 1962 31.997 10267083 853.1007 AFG 4
Afghanistan Asia 1967 34.020 11537966 836.1971 AFG 4
  • [🌶] ¿Hay algún país de gapminder sin código? Compruébalo con anti_join(gapminder, codigos, by = "country")

Resumen



Para … usamos …
seleccionar columnas select() (y relocate(), rename())
seleccionar filas filter(), slice() y sus variantes, distinct()
crear o modificar columnas mutate() (con ifelse(), case_when(), lag(), cumsum(), …)
ordenar filas arrange()
resumir summarise() (con n(), n_distinct(), mean(), …), count()
hacer cálculos por grupos group_by() (y ungroup())
fusionar tablas bind_rows(), bind_cols(), left_join(), inner_join(), full_join()


En el siguiente tema: gráficos con ggplot2