Package R Tidyr et ses nouvelles fonctions pivot_longer et pivot_wider
Forfait ranger inclus au cœur de l'une des bibliothèques les plus populaires du langage R - Tidyverse.
L'objectif principal du package est de présenter les données sous une forme précise.
Déjà disponible sur Habré publication dédié à ce package, mais il date de 2015. Et je veux vous parler des changements les plus récents, annoncés il y a quelques jours par son auteur, Hedley Wickham.
SJK: Est-ce que collect() et spread() seront obsolètes ?
Hadley Wickham: Dans une certaine mesure. Nous ne recommanderons plus l’utilisation de ces fonctions et n’y corrigerons plus les bugs, mais elles continueront d’être présentes dans le package dans leur état actuel.
Teneur
Si vous êtes intéressé par l'analyse des données, vous pourriez être intéressé par mon télégramme и Youtube canaux. La plupart du contenu est dédié au langage R.
Objectif ranger - vous aider à mettre les données sous une forme dite soignée. Les données soignées sont des données où :
Chaque variable est dans une colonne.
Chaque observation est une chaîne.
Chaque valeur est une cellule.
Il est beaucoup plus facile et pratique de travailler avec des données présentées de manière ordonnée lors de l'analyse.
Principales fonctions incluses dans le package Tidyr
Tidyr contient un ensemble de fonctions conçues pour transformer des tables :
fill() — remplir les valeurs manquantes dans une colonne avec les valeurs précédentes ;
separate() — divise un champ en plusieurs à l'aide d'un séparateur ;
unite() — effectue l'opération de combinaison de plusieurs champs en un seul, l'action inverse de la fonction separate();
pivot_longer() — une fonction qui convertit les données du format large au format long ;
pivot_wider() - une fonction qui convertit les données du format long au format large. L'opération inverse de celle effectuée par la fonction pivot_longer().
gather()obsolète — une fonction qui convertit les données du format large au format long ;
spread()obsolète - une fonction qui convertit les données du format long au format large. L'opération inverse de celle effectuée par la fonction gather().
Nouveau concept pour convertir des données du format large au format long et vice versa
Auparavant, les fonctions étaient utilisées pour ce type de transformation gather() и spread(). Au fil des années d'existence de ces fonctions, il est devenu évident que pour la plupart des utilisateurs, y compris l'auteur du package, les noms de ces fonctions et leurs arguments n'étaient pas tout à fait évidents, ce qui rendait difficile de les trouver et de comprendre laquelle de ces fonctions convertit un cadre de date du format large au format long, et vice versa.
À cet égard, dans ranger Deux nouvelles fonctions importantes ont été ajoutées, conçues pour transformer les cadres de date.
De nouvelles fonctionnalités pivot_longer() и pivot_wider() ont été inspirés par certaines des fonctionnalités du package données c, créé par John Mount et Nina Zumel.
Installation de la version la plus récente de Tidyr 0.8.3.9000
Pour installer la nouvelle version la plus récente du package ranger0.8.3.9000, là où de nouvelles fonctionnalités sont disponibles, utilisez le code suivant.
devtools::install_github("tidyverse/tidyr")
Au moment de la rédaction, ces fonctions ne sont disponibles que dans la version dev du package sur GitHub.
Transition vers de nouvelles fonctionnalités
En fait, il n'est pas difficile de transférer d'anciens scripts pour travailler avec de nouvelles fonctions ; pour une meilleure compréhension, je prendrai un exemple de la documentation des anciennes fonctions et montrerai comment les mêmes opérations sont effectuées en utilisant les nouvelles. pivot_*() fonctions.
Convertissez le format large en format long.
Exemple de code de la documentation de la fonction de collecte
# example
library(dplyr)
stocks <- data.frame(
time = as.Date('2009-01-01') + 0:9,
X = rnorm(10, 0, 1),
Y = rnorm(10, 0, 2),
Z = rnorm(10, 0, 4)
)
# old
stocks_gather <- stocks %>% gather(key = stock,
value = price,
-time)
# new
stocks_long <- stocks %>% pivot_longer(cols = -time,
names_to = "stock",
values_to = "price")
Conversion du format long en format large.
Exemple de code de la documentation de la fonction spread
# old
stocks_spread <- stocks_gather %>% spread(key = stock,
value = price)
# new
stock_wide <- stocks_long %>% pivot_wider(names_from = "stock",
values_from = "price")
Parce que dans les exemples ci-dessus de travail avec pivot_longer() и pivot_wider(), dans le tableau d'origine les stocks aucune colonne répertoriée dans les arguments noms_à и valeurs_à leurs noms doivent être entre guillemets.
Un tableau qui vous aidera à comprendre plus facilement comment passer au travail avec un nouveau concept ranger.
Remarque de l'auteur
Tout le texte ci-dessous est adaptatif, je dirais même traduction gratuite vignettes sur le site officiel de la bibliothèque Tidyverse.
Un exemple simple de conversion de données du format large au format long
pivot_longer () — allonge les ensembles de données en réduisant le nombre de colonnes et en augmentant le nombre de lignes.
Pour exécuter les exemples présentés dans l'article, vous devez d'abord connecter les packages nécessaires :
library(tidyr)
library(dplyr)
library(readr)
Disons que nous avons un tableau avec les résultats d'une enquête qui (entre autres choses) interrogeait les gens sur leur religion et leur revenu annuel :
Ce tableau contient les données sur la religion des répondants en lignes et les niveaux de revenus sont répartis entre les noms de colonnes. Le nombre de répondants de chaque catégorie est stocké dans les valeurs des cellules à l'intersection de la religion et du niveau de revenu. Pour remettre le tableau dans un format soigné et correct, il suffit d'utiliser pivot_longer():
Premier argument cols, décrit les colonnes qui doivent être fusionnées. Dans ce cas, toutes les colonnes sauf fiable.
Argument noms_à donne le nom de la variable qui sera créée à partir des noms des colonnes que nous avons concaténées.
valeurs_à donne le nom d'une variable qui sera créée à partir des données stockées dans les valeurs des cellules des colonnes fusionnées.
spécification
Il s'agit d'une nouvelle fonctionnalité du package ranger, qui n'était auparavant pas disponible lorsque vous utilisiez des fonctions héritées.
Une spécification est un bloc de données dont chaque ligne correspond à une colonne dans le nouveau bloc de dates de sortie et à deux colonnes spéciales qui commencent par :
. Nom contient le nom de la colonne d'origine.
.valeur contient le nom de la colonne qui contiendra les valeurs des cellules.
Les colonnes restantes de la spécification reflètent la façon dont la nouvelle colonne affichera le nom des colonnes compressées de . Nom.
La spécification décrit les métadonnées stockées dans un nom de colonne, avec une ligne pour chaque colonne et une colonne pour chaque variable combinée avec le nom de colonne, ce qui peut sembler déroutant pour le moment, mais après avoir examiné quelques exemples, cela deviendra beaucoup plus clair.
Le but de la spécification est que vous pouvez récupérer, modifier et définir de nouvelles métadonnées pour la trame de données en cours de conversion.
Pour travailler avec des spécifications lors de la conversion d'un tableau d'un format large en un format long, utilisez la fonction pivot_longer_spec().
Le fonctionnement de cette fonction est qu'elle prend n'importe quelle période et génère ses métadonnées de la manière décrite ci-dessus.
À titre d'exemple, prenons l'ensemble de données who fourni avec le package. ranger. Cet ensemble de données contient des informations fournies par l'organisation internationale de la santé sur l'incidence de la tuberculose.
who
#> # A tibble: 7,240 x 60
#> country iso2 iso3 year new_sp_m014 new_sp_m1524 new_sp_m2534
#> <chr> <chr> <chr> <int> <int> <int> <int>
#> 1 Afghan… AF AFG 1980 NA NA NA
#> 2 Afghan… AF AFG 1981 NA NA NA
#> 3 Afghan… AF AFG 1982 NA NA NA
#> 4 Afghan… AF AFG 1983 NA NA NA
#> 5 Afghan… AF AFG 1984 NA NA NA
#> 6 Afghan… AF AFG 1985 NA NA NA
#> 7 Afghan… AF AFG 1986 NA NA NA
#> 8 Afghan… AF AFG 1987 NA NA NA
#> 9 Afghan… AF AFG 1988 NA NA NA
#> 10 Afghan… AF AFG 1989 NA NA NA
#> # … with 7,230 more rows, and 53 more variables
Construisons sa spécification.
spec <- who %>%
pivot_longer_spec(new_sp_m014:newrel_f65, values_to = "count")
champs Pays, iso2, iso3 sont déjà des variables. Notre tâche est d'inverser les colonnes avec nouveau_sp_m014 sur newrel_f65.
Les noms de ces colonnes stockent les informations suivantes :
Préfixe new_ indique que la colonne contient des données sur les nouveaux cas de tuberculose, la période actuelle contient des informations uniquement sur les nouvelles maladies, donc ce préfixe dans le contexte actuel n'a aucune signification.
sp/rel/sp/ep décrit une méthode pour diagnostiquer une maladie.
m/f le sexe du patient.
014/1524/2535/3544/4554/65 tranche d’âge des patients.
Nous pouvons diviser ces colonnes en utilisant la fonction extract()en utilisant une expression régulière.
Enfin, afin d'appliquer la spécification que nous avons créée au cadre de date d'origine pour qui nous devons utiliser un argument spec en fonction pivot_longer().
who %>% pivot_longer(spec = spec)
#> # A tibble: 405,440 x 8
#> country iso2 iso3 year diagnosis gender age count
#> <chr> <chr> <chr> <int> <chr> <fct> <ord> <int>
#> 1 Afghanistan AF AFG 1980 sp m 014 NA
#> 2 Afghanistan AF AFG 1980 sp m 1524 NA
#> 3 Afghanistan AF AFG 1980 sp m 2534 NA
#> 4 Afghanistan AF AFG 1980 sp m 3544 NA
#> 5 Afghanistan AF AFG 1980 sp m 4554 NA
#> 6 Afghanistan AF AFG 1980 sp m 5564 NA
#> 7 Afghanistan AF AFG 1980 sp m 65 NA
#> 8 Afghanistan AF AFG 1980 sp f 014 NA
#> 9 Afghanistan AF AFG 1980 sp f 1524 NA
#> 10 Afghanistan AF AFG 1980 sp f 2534 NA
#> # … with 405,430 more rows
Tout ce que nous venons de faire peut être schématiquement représenté comme suit :
Spécification utilisant plusieurs valeurs (.value)
Dans l'exemple ci-dessus, la colonne de spécification .valeur ne contenait qu’une seule valeur, c’est le cas dans la plupart des cas.
Mais parfois, une situation peut survenir lorsque vous devez collecter des données à partir de colonnes avec différents types de données dans les valeurs. Utiliser une fonction héritée spread() ce serait assez difficile à faire.
L'exemple ci-dessous est tiré de vignettes au colis données.table.
Le cadre de date créé contient des données sur les enfants d'une famille dans chaque ligne. Les familles peuvent avoir un ou deux enfants. Pour chaque enfant, des données sont fournies sur la date de naissance et le sexe, et les données pour chaque enfant sont dans des colonnes séparées ; notre tâche est d'amener ces données au format correct pour l'analyse.
Veuillez noter que nous disposons de deux variables avec des informations sur chaque enfant : son sexe et sa date de naissance (colonnes avec le préfixe baptême contenir la date de naissance, les colonnes avec préfixe le sexe contenir le sexe de l'enfant). Le résultat attendu est qu’ils apparaissent dans des colonnes distinctes. Nous pouvons le faire en générant une spécification dans laquelle la colonne .value aura deux significations différentes.
spec <- family %>%
pivot_longer_spec(-family) %>%
separate(col = name, into = c(".value", "child"))%>%
mutate(child = parse_number(child))
#> # A tibble: 4 x 3
#> .name .value child
#> <chr> <chr> <dbl>
#> 1 dob_child1 dob 1
#> 2 dob_child2 dob 2
#> 3 gender_child1 gender 1
#> 4 gender_child2 gender 2
Examinons donc étape par étape les actions effectuées par le code ci-dessus.
pivot_longer_spec(-family) — créer une spécification qui compresse toutes les colonnes existantes à l'exception de la colonne famille.
separate(col = name, into = c(".value", "child")) - diviser la colonne . Nom, qui contient les noms des champs sources, en utilisant le trait de soulignement et en saisissant les valeurs résultantes dans les colonnes .valeur и enfant.
mutate(child = parse_number(child)) — transformer les valeurs du champ enfant du texte au type de données numérique.
Nous pouvons maintenant appliquer la spécification résultante à la trame de données d'origine et amener la table à la forme souhaitée.
Nous utilisons l'argumentation na.rm = TRUE, car la forme actuelle des données force la création de lignes supplémentaires pour les observations inexistantes. Parce que la famille 2 n'a qu'un seul enfant, na.rm = TRUE garantit que la famille 2 aura une ligne dans la sortie.
Conversion des cadres de date du format long au format large
pivot_wider() - est la transformation inverse, et vice versa augmente le nombre de colonnes du cadre de date en réduisant le nombre de lignes.
Ce type de transformation est extrêmement rarement utilisé pour mettre des données sous une forme précise. Cependant, cette technique peut être utile pour créer des tableaux croisés dynamiques utilisés dans des présentations ou pour l'intégration avec d'autres outils.
En fait les fonctions pivot_longer() и pivot_wider() sont symétriques et produisent des actions inverses les unes des autres, c'est-à-dire : df %>% pivot_longer(spec = spec) %>% pivot_wider(spec = spec) и df %>% pivot_wider(spec = spec) %>% pivot_longer(spec = spec) renverra le df original.
L'exemple le plus simple de conversion d'un tableau en format large
Pour démontrer le fonctionnement de la fonction pivot_wider() nous utiliserons l'ensemble de données rencontres_poissons, qui stocke des informations sur la manière dont différentes stations enregistrent le mouvement des poissons le long de la rivière.
#> # A tibble: 114 x 3
#> fish station seen
#> <fct> <fct> <int>
#> 1 4842 Release 1
#> 2 4842 I80_1 1
#> 3 4842 Lisbon 1
#> 4 4842 Rstr 1
#> 5 4842 Base_TD 1
#> 6 4842 BCE 1
#> 7 4842 BCW 1
#> 8 4842 BCE2 1
#> 9 4842 BCW2 1
#> 10 4842 MAE 1
#> # … with 104 more rows
Dans la plupart des cas, ce tableau sera plus informatif et plus facile à utiliser si vous présentez les informations pour chaque station dans une colonne distincte.
fish_encounters %>% pivot_wider(names_from = station, values_from = seen)
#> # A tibble: 19 x 12
#> fish Release I80_1 Lisbon Rstr Base_TD BCE BCW BCE2 BCW2 MAE
#> <fct> <int> <int> <int> <int> <int> <int> <int> <int> <int> <int>
#> 1 4842 1 1 1 1 1 1 1 1 1 1
#> 2 4843 1 1 1 1 1 1 1 1 1 1
#> 3 4844 1 1 1 1 1 1 1 1 1 1
#> 4 4845 1 1 1 1 1 NA NA NA NA NA
#> 5 4847 1 1 1 NA NA NA NA NA NA NA
#> 6 4848 1 1 1 1 NA NA NA NA NA NA
#> 7 4849 1 1 NA NA NA NA NA NA NA NA
#> 8 4850 1 1 NA 1 1 1 1 NA NA NA
#> 9 4851 1 1 NA NA NA NA NA NA NA NA
#> 10 4854 1 1 NA NA NA NA NA NA NA NA
#> # … with 9 more rows, and 1 more variable: MAW <int>
Cet ensemble de données n'enregistre les informations que lorsque des poissons ont été détectés par la station, c'est-à-dire si un poisson n'a pas été enregistré par une station, ces données ne figureront pas dans le tableau. Cela signifie que la sortie sera remplie de NA.
Cependant, dans ce cas, nous savons que l'absence d'enregistrement signifie que le poisson n'a pas été vu, nous pouvons donc utiliser l'argument valeurs_remplissage en fonction pivot_wider() et remplissez ces valeurs manquantes avec des zéros :
Générer un nom de colonne à partir de plusieurs variables sources
Imaginez que nous ayons un tableau contenant une combinaison de produit, de pays et d'année. Pour générer une période de test, vous pouvez exécuter le code suivant :
df <- expand_grid(
product = c("A", "B"),
country = c("AI", "EI"),
year = 2000:2014
) %>%
filter((product == "A" & country == "AI") | product == "B") %>%
mutate(value = rnorm(nrow(.)))
#> # A tibble: 45 x 4
#> product country year value
#> <chr> <chr> <int> <dbl>
#> 1 A AI 2000 -2.05
#> 2 A AI 2001 -0.676
#> 3 A AI 2002 1.60
#> 4 A AI 2003 -0.353
#> 5 A AI 2004 -0.00530
#> 6 A AI 2005 0.442
#> 7 A AI 2006 -0.610
#> 8 A AI 2007 -2.77
#> 9 A AI 2008 0.899
#> 10 A AI 2009 -0.106
#> # … with 35 more rows
Notre tâche consiste à étendre le bloc de données afin qu'une colonne contienne des données pour chaque combinaison de produit et de pays. Pour ce faire, transmettez simplement l'argument noms_from un vecteur contenant les noms des champs à fusionner.
Vous pouvez également appliquer des spécifications à une fonction pivot_wider(). Mais lorsqu'il est soumis à pivot_wider() la spécification fait la conversion inverse pivot_longer(): Les colonnes spécifiées dans . Nom, en utilisant les valeurs de .valeur et d'autres colonnes.
Pour cet ensemble de données, vous pouvez générer une spécification personnalisée si vous souhaitez que chaque combinaison possible de pays et de produits ait sa propre colonne, et pas seulement celles présentes dans les données :
#> # A tibble: 4 x 4
#> .name product country .value
#> <chr> <chr> <chr> <chr>
#> 1 A_AI A AI value
#> 2 A_EI A EI value
#> 3 B_AI B AI value
#> 4 B_EI B EI value
df %>% pivot_wider(spec = spec) %>% head()
#> # A tibble: 6 x 5
#> year A_AI A_EI B_AI B_EI
#> <int> <dbl> <dbl> <dbl> <dbl>
#> 1 2000 -2.05 NA 0.607 1.20
#> 2 2001 -0.676 NA 1.65 -0.114
#> 3 2002 1.60 NA -0.0245 0.501
#> 4 2003 -0.353 NA 1.30 -0.459
#> 5 2004 -0.00530 NA 0.921 -0.0589
#> 6 2005 0.442 NA -1.55 0.594
Plusieurs exemples avancés de travail avec le nouveau concept Tidyr
Nettoyage des données en utilisant l'ensemble de données du recensement américain sur les revenus et les loyers comme exemple.
Ensemble de données us_rent_ Income contient des informations sur le revenu médian et le loyer pour chaque État des États-Unis pour 2017 (ensemble de données disponible dans le package recensement bien rangé).
us_rent_income
#> # A tibble: 104 x 5
#> GEOID NAME variable estimate moe
#> <chr> <chr> <chr> <dbl> <dbl>
#> 1 01 Alabama income 24476 136
#> 2 01 Alabama rent 747 3
#> 3 02 Alaska income 32940 508
#> 4 02 Alaska rent 1200 13
#> 5 04 Arizona income 27517 148
#> 6 04 Arizona rent 972 4
#> 7 05 Arkansas income 23789 165
#> 8 05 Arkansas rent 709 5
#> 9 06 California income 29454 109
#> 10 06 California rent 1358 3
#> # … with 94 more rows
Sous la forme sous laquelle les données sont stockées dans l'ensemble de données us_rent_ Income travailler avec eux est extrêmement gênant, nous aimerions donc créer un ensemble de données avec des colonnes : location, rent_moe, comment, revenu_moe. Il existe de nombreuses façons de créer cette spécification, mais le point principal est que nous devons générer chaque combinaison de valeurs de variables et estimation/moispuis générez le nom de la colonne.
Parfois, amener un ensemble de données sous la forme souhaitée nécessite plusieurs étapes.
Base de données world_bank_pop contient des données de la Banque mondiale sur la population de chaque pays entre 2000 et 2018.
Notre objectif est de créer un ensemble de données soigné avec chaque variable dans sa propre colonne. On ne sait pas exactement quelles étapes sont nécessaires, mais nous allons commencer par le problème le plus évident : l'année est répartie sur plusieurs colonnes.
Pour résoudre ce problème, vous devez utiliser la fonction pivot_longer().
L'étape suivante consiste à examiner la variable indicatrice. pop2 %>% count(indicator)
#> # A tibble: 4 x 2
#> indicator n
#> <chr> <int>
#> 1 SP.POP.GROW 4752
#> 2 SP.POP.TOTL 4752
#> 3 SP.URB.GROW 4752
#> 4 SP.URB.TOTL 4752
Où SP.POP.GROW représente la croissance démographique, SP.POP.TOTL la population totale et SP.URB. * la même chose, mais uniquement pour les zones urbaines. Divisons ces valeurs en deux variables : superficie - superficie (totale ou urbaine) et une variable contenant des données réelles (population ou croissance) :
La tabulation de cette liste est assez difficile car il n'existe aucune variable permettant d'identifier quelles données appartiennent à quel contact. Nous pouvons résoudre ce problème en notant que les données de chaque nouveau contact commencent par « nom », nous pouvons donc créer un identifiant unique et l'incrémenter de un à chaque fois que la colonne du champ contient la valeur « nom » :
#> # A tibble: 6 x 3
#> field value person_id
#> <chr> <chr> <int>
#> 1 name Jiena McLellan 1
#> 2 company Toyota 1
#> 3 name John Smith 2
#> 4 company google 2
#> 5 email [email protected] 2
#> 6 name Huxley Ratcliffe 3
Maintenant que nous avons un identifiant unique pour chaque contact, nous pouvons transformer le champ et la valeur en colonnes :
#> # A tibble: 3 x 4
#> person_id name company email
#> <int> <chr> <chr> <chr>
#> 1 1 Jiena McLellan Toyota <NA>
#> 2 2 John Smith google [email protected]
#> 3 3 Huxley Ratcliffe <NA> <NA>
Conclusion
Mon opinion personnelle est que le nouveau concept ranger vraiment plus intuitif et nettement supérieur en termes de fonctionnalités aux fonctions existantes spread() и gather(). J'espère que cet article vous a aidé à gérer pivot_longer() и pivot_wider().