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ónungroup()
.
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).
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 variableQ1
, la operación sería5 – Q1
. En el caso de la variableQ2
, la operación sería5 – 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