6  Transformar grupos

6.1 Introducción

Hasta ahora hemos visto las funciones principales del paquete dplyr que nos permiten modificar las filas y las columnas de un marco de datos. Sin embargo, hay otras muchas funciones que no hemos explicado y que aprenderemos a medida que las vayamos necesitando para analizar datos. Dos funciones imprescindibles que nos serán muy útiles en este momento son group_by() y across(). Ambas funciones no tienen ninguna utilidad por sí solas, puesto que solo sirven cuando se combinan con algunas de las funciones que hemos visto anteriormente. En resumen:

  • group_by(): permite que las operaciones se hagan teniendo en cuenta las categorías de una o más variables.
  • across(): permite hacer la misma operación en varias variables.

6.2 Group_by

La función group_by() no tiene ninguna utilidad por sí sola. Pero, en combinación con filter(), mutate() u otras funciones que veremos más adelante, permite hacer operaciones dentro de los límites de las categorías de una variable categórica.

Anteriormente, con filter() hemos calculado cuál era la región más de derechas de España. Para ello teníamos que filtrar los datos por España y pedir el máximo de la variable lrscale. Pongamos como ejemplo que queremos saber cuál es la región más de derechas y la región más de izquierdas de cada país. Esto nos lo permitirá hacer combinando group_by() y filter():

  • En group_by(country) indicamos que nos haga los próximos cálculos de la pipe teniendo en cuenta los límites del país.
  • Dentro de filter() pedimos el valor máximo y el valor mínimo. Como en la función anterior hemos especificado que los cálculos deben realizarse teniendo en cuenta las agrupaciones por país, R calculará la región más de derechas y la región más de izquierdas de cada país.
  • Una vez hemos terminado de hacer las operaciones agrupadas, siempre es recomendable obtener el efecto de group_by() con la función ungroup().

Fijaos que si no desagrupamos los datos al final (así se indica en la fila # Groups: country [18]), el nuevo marco de datos resultante mantendrá las agrupaciones para las futuras operaciones.

nuts |> 
  group_by(country) |>
  filter(lrscale == max(lrscale, na.rm = T) | lrscale == min(lrscale, na.rm = T)) |>
  select(country, name, lrscale)
# A tibble: 36 × 3
# Groups:   country [18]
   country        name                         lrscale
   <chr>          <chr>                          <dbl>
 1 Austria        Wien                            4.36
 2 Austria        Oberösterreich                  5.59
 3 Belgium        Région De Bruxelles-Capitale    4.35
 4 Belgium        Vlaams Gewest                   5.22
 5 Czech Republic Plzeňský Kraj                   5.70
 6 Czech Republic Kraj Vysočina                   4.28
 7 Germany        Bayern                          4.69
 8 Germany        Brandenburg                     3.81
 9 Estonia        Kirde-Eesti                     4.90
10 Estonia        Lääne-Eesti                     5.48
# ℹ 26 more rows

Si desagrupamos al final con la función ungroup(), devolveremos el marco de datos a su estado original, de forma que las futuras operaciones que realicemos con el marco de datos dejarán de ser agrupadas por país.

nuts |> 
  group_by(country) |>
  filter(lrscale == max(lrscale, na.rm = T) | lrscale == min(lrscale, na.rm = T)) |>
  select(country, name, lrscale) |> 
  ungroup()
# A tibble: 36 × 3
   country        name                         lrscale
   <chr>          <chr>                          <dbl>
 1 Austria        Wien                            4.36
 2 Austria        Oberösterreich                  5.59
 3 Belgium        Région De Bruxelles-Capitale    4.35
 4 Belgium        Vlaams Gewest                   5.22
 5 Czech Republic Plzeňský Kraj                   5.70
 6 Czech Republic Kraj Vysočina                   4.28
 7 Germany        Bayern                          4.69
 8 Germany        Brandenburg                     3.81
 9 Estonia        Kirde-Eesti                     4.90
10 Estonia        Lääne-Eesti                     5.48
# ℹ 26 more rows

De manera similar, group_by() también nos permite agrupar las operaciones que queramos hacer con la función mutate(). Anteriormente, hemos calculado el porcentaje de territorio que tiene cada región en relación con el total de Europa. Pero también podríamos hacer el mismo cálculo en relación con el total de cada país:

  • En group_by(country) indicamos que nos haga los próximos cálculos de la pipe teniendo en cuenta el país.
  • Dentro de mutate() no es necesario que hagamos ninguna modificación en la operación porque R utilizará el valor del territorio de cada región (land) y lo dividirá por el sumatorio del grupo, que es el país (en lugar de dividirlo por el sumatorio de todos los valores de la variable).
  • Finalmente, desagrupamos con ungroup().
nuts |> 
  group_by(country) |> 
  mutate(land_percentage = land / sum(land, na.rm = T) * 100) |> 
  select(name, land, land_percentage) |> 
  ungroup()
# A tibble: 368 × 4
   country name                          land land_percentage
   <chr>   <chr>                        <dbl>           <dbl>
 1 Austria Burgenland                    3962           4.72 
 2 Austria Niederösterreich             19186          22.9  
 3 Austria Wien                           415           0.495
 4 Austria Kärnten                       9538          11.4  
 5 Austria Steiermark                   16401          19.6  
 6 Austria Oberösterreich               11980          14.3  
 7 Austria Salzburg                      7156           8.53 
 8 Austria Tirol                        12640          15.1  
 9 Austria Vorarlberg                    2601           3.10 
10 Belgium Région De Bruxelles-Capitale   161           0.527
# ℹ 358 more rows

Hay dos cuestiones importantes que tener en cuenta cuando se utiliza group_by(). La primera es que podemos agrupar por varias variables. Veremos algún ejemplo de ello más adelante. Y la segunda es que las variables tienen que ser categóricas (como mínimo, discretas).

Ejercicio 6.1 (Las regiones con más población) ¿Cuáles son las regiones con más población de cada país?

  • Utiliza group_by(), filter() y select() para averiguarlo.
nuts |> 
  group_by(country) |> 
  filter(pop == max(pop)) |> 
  select(country, name)

6.3 Across

La función across() nos permite aplicar una misma operación a diversas variables, de forma que nos ahorrará mucho tiempo y muchas líneas de código. La estructura básica de la función es la siguiente:

  • En el primer argumento indicamos las columnas donde queremos aplicar una determinada función.
  • En el segundo argumento indicamos la tilde (~) seguida de la operación que queramos aplicar. El punto (.) sustituirá cada una de las variables indicadas en el primer argumento cuando hagamos la operación.
across(qué_columnas, ~ funcion(.))

A continuación, veremos cómo se utiliza esta función dentro de mutate(), aunque también hay otras funciones que permiten usarla. across() es útil en combinación con mutate() cuando queremos hacer la misma operación en varias variables.

En un módulo anterior ya hemos usado across() para modificar a la vez varias variables de WVS (Inglehart et al., 2020). En el ejemplo siguiente repescamos el marco de datos wvs_recod.

Si nos fijamos en el libro de códigos de la WVS, veremos que las variables Q1, Q2, Q3, Q4, Q5 y Q6 siguen la misma escala ordinal.

  • Todas son un vector numérico que adopta cuatro valores: 1, 2, 3 o 4.
  • En realidad, estas variables son ordinales y deberían tener los valores siguientes: “Not at all important”, “Not very important”, “Rather important”, “Very important”.
  • Por lo tanto, si queremos recodificarlas, tendríamos que aplicar exactamente la misma operación a las seis variables.
wvs_recod
# A tibble: 1,000 × 12
   A_WAVE C_COW_ALPHA C_COW_NUM A_YEAR    Q1    Q2    Q3    Q4    Q5    Q6    Q7
    <dbl> <chr>           <dbl>  <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
 1      7 MAL               820   2018     1     2     1     1     4     2     1
 2      7 GMY               255   2018     2     1     2     3     1     4     1
 3      7 KZK               705   2018     2     2     2     4     2     4     1
 4      7 RUS               365   2017     1     1     2     3     1     3     1
 5      7 KZK               705   2018     1     2     2     4     1     1     2
 6      7 SRB               345   2017     1     1     1     1     1     1     1
 7      7 AUL               900   2018     1     1     1     1     1     4     1
 8      7 MEX                70   2018     1     2     1     4     1     1     1
 9      7 DRV               816   2020     1     2     2     2     1     1     1
10      7 GRC               350   2017     1     1     1     4     1     1     1
# ℹ 990 more rows
# ℹ 1 more variable: Q8 <dbl>

Con across(), aplicamos la misma operación a las seis variables:

  • En el primer argumento indicamos las variables Q1:Q6.
  • En el segundo argumento marcamos la tilde (~) e indicamos que las queremos pasar a factor.
  • Como sabéis, dentro de factor() necesitamos especificar tres argumentos. En el primer argumento, el punto (.) sustituye la variable. Por lo tanto, en el caso de la variable Q1, la operación sería 5 – Q1. En el caso de la variable Q2, la operación sería 5 – Q2, y así sucesivamente. Esto es así porque originariamente el valor 1 corresponde a “Very important” y el valor 4 corresponde a “Not at all important”. Y lo primero que queremos hacer es girar la escala, de forma que “Very important” corresponda al valor 4 y “Not at all important” corresponda al valor 1.
wvs_recod |> 
  mutate(across(Q1:Q6, ~ factor(5 - ., 
       labels = c("Not at all important", "Not very important", 
                  "Rather important", "Very important"),
       ordered = T)))
# A tibble: 1,000 × 12
   A_WAVE C_COW_ALPHA C_COW_NUM A_YEAR Q1    Q2    Q3    Q4    Q5    Q6       Q7
    <dbl> <chr>           <dbl>  <dbl> <ord> <ord> <ord> <ord> <ord> <ord> <dbl>
 1      7 MAL               820   2018 Very… Rath… Very… Very… Not … Rath…     1
 2      7 GMY               255   2018 Rath… Very… Rath… Not … Very… Not …     1
 3      7 KZK               705   2018 Rath… Rath… Rath… Not … Rath… Not …     1
 4      7 RUS               365   2017 Very… Very… Rath… Not … Very… Not …     1
 5      7 KZK               705   2018 Very… Rath… Rath… Not … Very… Very…     2
 6      7 SRB               345   2017 Very… Very… Very… Very… Very… Very…     1
 7      7 AUL               900   2018 Very… Very… Very… Very… Very… Not …     1
 8      7 MEX                70   2018 Very… Rath… Very… Not … Very… Very…     1
 9      7 DRV               816   2020 Very… Rath… Rath… Rath… Very… Very…     1
10      7 GRC               350   2017 Very… Very… Very… Not … Very… Very…     1
# ℹ 990 more rows
# ℹ 1 more variable: Q8 <dbl>

La combinación de across() y mutate() es muy habitual en la normalización de variables, un proceso que siguen muchos indicadores internacionales como el índice EQI sobre la calidad de gobierno, que utiliza el método Z-Scores. A continuación vemos un ejemplo.

Supongamos que queremos construir un índice compuesto y necesitamos normalizar varias variables relacionadas con actitudes políticas que tenemos en el marco de datos nuts. En concreto, queremos construir nuestro índice a partir de la satisfacción con el gobierno (stfgov), la tolerancia de la homosexualidad (freehms), la tolerancia de la inmigración imueclt y el vínculo afectivo con la nación (atchctr).

nuts |> 
  select(country, name, stfgov:atchctr)
# A tibble: 368 × 6
   country name                         stfgov freehms imueclt atchctr
   <chr>   <chr>                         <dbl>   <dbl>   <dbl>   <dbl>
 1 Austria Burgenland                     4.5     1.97    4.09    8.66
 2 Austria Niederösterreich               4.60    2.06    4.28    7.98
 3 Austria Wien                           4.57    2.02    5.40    7.55
 4 Austria Kärnten                        3.27    2       3.57    8.41
 5 Austria Steiermark                     3.96    2.06    4.11    8.05
 6 Austria Oberösterreich                 5.01    1.91    4.44    7.98
 7 Austria Salzburg                       4.40    1.80    4.80    6.99
 8 Austria Tirol                          4.62    1.86    4.96    8.30
 9 Austria Vorarlberg                     3.66    2.45    3.89    7.94
10 Belgium Région De Bruxelles-Capitale   5.03    2.27    7.01    7.02
# ℹ 358 more rows

Podemos hacer la normalización por países de las cuatro variables de interés con el método Z-Scores, que otorga el valor 0 a la media y 1 a la desviación típica de cada distribución. En primer lugar, agrupamos por países y, después, indicamos dentro de mutate() las cuatro variables seguido de ~ y la fórmula de la normalización con Z-Scores.

nuts |> 
  select(country, name, stfgov:atchctr) |> 
  group_by(country) |> 
  mutate(across(stfgov:atchctr, ~ (. - mean(., na.rm = T)) / sd(., na.rm = T))) |> 
  ungroup()
# A tibble: 368 × 6
   country name                         stfgov freehms imueclt  atchctr
   <chr>   <chr>                         <dbl>   <dbl>   <dbl>    <dbl>
 1 Austria Burgenland                    0.386 -0.226  -0.529   1.38   
 2 Austria Niederösterreich              0.571  0.230  -0.200  -0.00156
 3 Austria Wien                          0.507  0.0222  1.76   -0.884  
 4 Austria Kärnten                      -1.86  -0.0738 -1.44    0.867  
 5 Austria Steiermark                   -0.598  0.257  -0.500   0.139  
 6 Austria Oberösterreich                1.33  -0.560   0.0777 -0.0111 
 7 Austria Salzburg                      0.210 -1.15    0.718  -2.03   
 8 Austria Tirol                         0.597 -0.841   0.985   0.635  
 9 Austria Vorarlberg                   -1.14   2.34   -0.871  -0.0988 
10 Belgium Région De Bruxelles-Capitale  0.703  1.15    1.15    0.368  
# ℹ 358 more rows