R-paketet tidyr och dess nya funktioner pivot_longer och pivot_wider
paket tidyr ingår i kärnan av ett av de mest populära biblioteken i R-språket - tidyverse.
Huvudsyftet med paketet är att få informationen till en korrekt form.
Finns redan på Habré publikation tillägnad detta paket, men det går tillbaka till 2015. Och jag vill berätta om de senaste förändringarna, som tillkännagavs för några dagar sedan av dess författare, Hedley Wickham.
SJK: Kommer gather() och spread() att fasas ut?
Hadley Wickham: I viss utsträckning. Vi kommer inte längre att rekommendera användningen av dessa funktioner och fixa buggar i dem, men de kommer att fortsätta att finnas i paketet i deras nuvarande tillstånd.
Innehåll
Om du är intresserad av dataanalys kan du vara intresserad av min telegram и Youtube kanaler. Det mesta av innehållet är tillägnat R-språket.
Mål tidyr — hjälpa dig att få uppgifterna till en så kallad snygg form. Snygga data är data där:
Varje variabel finns i en kolumn.
Varje observation är en sträng.
Varje värde är en cell.
Det är mycket enklare och bekvämare att arbeta med data som presenteras i snygg data när man gör analys.
Huvudfunktioner ingår i tidyr-paketet
tidyr innehåller en uppsättning funktioner utformade för att transformera tabeller:
fill() — fylla i saknade värden i en kolumn med tidigare värden;
separate() — delar upp ett fält i flera med hjälp av en separator;
unite() — utför operationen att kombinera flera fält till ett, den omvända verkan av funktionen separate();
pivot_longer() — en funktion som konverterar data från bredformat till långformat;
pivot_wider() - en funktion som konverterar data från långformat till bredformat. Den omvända operationen av den som utförs av funktionen pivot_longer().
gather()föråldrad — en funktion som konverterar data från bredformat till långformat;
spread()föråldrad - en funktion som konverterar data från långformat till bredformat. Den omvända operationen av den som utförs av funktionen gather().
Nytt koncept för att konvertera data från brett till långt format och vice versa
Tidigare användes funktioner för denna typ av transformation gather() и spread(). Under åren av existensen av dessa funktioner blev det uppenbart att för de flesta användare, inklusive författaren till paketet, var namnen på dessa funktioner och deras argument inte helt uppenbara och orsakade svårigheter att hitta dem och förstå vilka av dessa funktioner som konverterar en datumram från brett till långt format, och vice versa.
I detta avseende, i tidyr Två nya, viktiga funktioner har lagts till som är designade för att transformera datumramar.
Nya funktioner pivot_longer() и pivot_wider() inspirerades av några av funktionerna i paketet cdata, skapad av John Mount och Nina Zumel.
Installerar den senaste versionen av tidyr 0.8.3.9000
För att installera den nya, senaste versionen av paketet tidyr0.8.3.9000, där nya funktioner är tillgängliga, använd följande kod.
devtools::install_github("tidyverse/tidyr")
I skrivande stund är dessa funktioner endast tillgängliga i dev-versionen av paketet på GitHub.
Övergång till nya funktioner
Faktum är att det inte är svårt att överföra gamla skript för att arbeta med nya funktioner; för bättre förståelse kommer jag att ta ett exempel från dokumentationen av gamla funktioner och visa hur samma operationer utförs med nya. pivot_*() funktioner.
Konvertera bredformat till långt format.
Exempelkod från samlingsfunktionsdokumentationen
# 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")
Konvertera långformat till bredformat.
Exempelkod från spridningsfunktionsdokumentation
# old
stocks_spread <- stocks_gather %>% spread(key = stock,
value = price)
# new
stock_wide <- stocks_long %>% pivot_wider(names_from = "stock",
values_from = "price")
Därför att i ovanstående exempel på att arbeta med pivot_longer() и pivot_wider(), i den ursprungliga tabellen lagren inga kolumner listade i argument namn_till и värden_till deras namn måste stå inom citattecken.
En tabell som hjälper dig enklast att ta reda på hur du går över till att arbeta med ett nytt koncept tidyr.
Anteckning från författaren
All text nedan är adaptiv, jag skulle till och med säga fri översättning vinjetter från den officiella tidyverse-bibliotekets webbplats.
Ett enkelt exempel på att konvertera data från brett till långt format
pivot_longer () — gör datamängder längre genom att minska antalet kolumner och öka antalet rader.
För att köra exemplen som presenteras i artikeln måste du först ansluta de nödvändiga paketen:
library(tidyr)
library(dplyr)
library(readr)
Låt oss säga att vi har en tabell med resultaten från en undersökning som (bland annat) frågade människor om deras religion och årsinkomst:
Den här tabellen innehåller respondenternas religionsdata i rader, och inkomstnivåerna är utspridda över kolumnnamn. Antalet svarande från varje kategori lagras i cellvärdena i skärningspunkten mellan religion och inkomstnivå. För att få bordet i ett snyggt, korrekt format räcker det att använda pivot_longer():
Första argumentet cols, beskriver vilka kolumner som behöver slås samman. I det här fallet, alla kolumner utom tid.
argument namn_till ger namnet på variabeln som kommer att skapas från namnen på kolumnerna vi sammanfogade.
värden_till ger namnet på en variabel som kommer att skapas från data som lagras i värdena i cellerna i de sammanslagna kolumnerna.
Specifikationer
Detta är en ny funktion i paketet tidyr, som tidigare inte var tillgängligt när man arbetade med äldre funktioner.
En specifikation är en dataram där varje rad motsvarar en kolumn i den nya utdataramen och två specialkolumner som börjar med:
. Namn innehåller det ursprungliga kolumnnamnet.
.värde innehåller namnet på kolumnen som ska innehålla cellvärdena.
De återstående kolumnerna i specifikationen visar hur den nya kolumnen kommer att visa namnet på de komprimerade kolumnerna från . Namn.
Specifikationen beskriver metadata som lagras i ett kolumnnamn, med en rad för varje kolumn och en kolumn för varje variabel, kombinerat med kolumnnamnet, denna definition kan verka förvirrande för tillfället, men efter att ha tittat på några exempel kommer det att bli mycket klarare.
Poängen med specifikationen är att du kan hämta, modifiera och definiera ny metadata för den dataram som konverteras.
För att arbeta med specifikationer när du konverterar en tabell från ett brett format till ett långt format, använd funktionen pivot_longer_spec().
Hur den här funktionen fungerar är att den tar vilken datumram som helst och genererar sin metadata på det sätt som beskrivs ovan.
Som ett exempel, låt oss ta who-datasetet som tillhandahålls med paketet tidyr. Denna datauppsättning innehåller information från den internationella hälsoorganisationen om förekomsten av tuberkulos.
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
Låt oss bygga dess specifikation.
spec <- who %>%
pivot_longer_spec(new_sp_m014:newrel_f65, values_to = "count")
fält land, isoxnumx, isoxnumx är redan variabler. Vår uppgift är att vända kolumnerna med new_sp_m014 på newrel_f65.
Namnen på dessa kolumner lagrar följande information:
prefixet new_ indikerar att kolumnen innehåller uppgifter om nya fall av tuberkulos, den aktuella datumramen innehåller endast information om nya sjukdomar, så detta prefix i det aktuella sammanhanget har ingen betydelse.
sp/rel/sp/ep beskriver en metod för att diagnostisera en sjukdom.
Slutligen, för att tillämpa specifikationen som vi skapade på den ursprungliga datumramen som vi måste använda ett argument spec i funktion 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
Allt vi just gjorde kan schematiskt avbildas enligt följande:
Specifikation med flera värden (.value)
I exemplet ovan, specifikationskolumnen .värde innehöll endast ett värde, i de flesta fall är detta fallet.
Men ibland kan en situation uppstå när du behöver samla in data från kolumner med olika datatyper i värden. Använder en äldre funktion spread() detta skulle vara ganska svårt att göra.
Exemplet nedan är hämtat från vinjetter till paketet datatabell.
Den skapade datumramen innehåller data om barn i en familj på varje rad. Familjer kan ha ett eller två barn. För varje barn ges uppgifter om födelsedatum och kön, och uppgifterna för varje barn finns i separata kolumner, vår uppgift är att föra dessa data till rätt format för analys.
Observera att vi har två variabler med information om varje barn: deras kön och födelsedatum (kolumner med prefixet propp innehåller födelsedatum, kolumner med prefix kön innehålla barnets kön). Det förväntade resultatet är att de ska visas i separata kolumner. Vi kan göra detta genom att skapa en specifikation där kolumnen .value kommer att ha två olika betydelser.
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
Så låt oss ta en steg-för-steg titt på de åtgärder som utförs av ovanstående kod.
pivot_longer_spec(-family) — skapa en specifikation som komprimerar alla befintliga kolumner utom familjekolumnen.
separate(col = name, into = c(".value", "child")) - dela kolumnen . Namn, som innehåller namnen på källfälten, använder understrecket och anger de resulterande värdena i kolumnerna .värde и barn.
mutate(child = parse_number(child)) — omvandla fältvärdena barn från text till numerisk datatyp.
Nu kan vi tillämpa den resulterande specifikationen på den ursprungliga dataramen och föra tabellen till önskad form.
Vi använder argument na.rm = TRUE, eftersom den aktuella formen av data tvingar fram skapandet av extra rader för obefintliga observationer. Därför att familj 2 har bara ett barn, na.rm = TRUE garanterar att familj 2 kommer att ha en rad i utgången.
Konvertera datumramar från långt till brett format
pivot_wider() - är den omvända transformationen, och vice versa ökar antalet kolumner i datumramen genom att minska antalet rader.
Denna typ av transformation används extremt sällan för att få data till en korrekt form, men den här tekniken kan vara användbar för att skapa pivottabeller som används i presentationer, eller för integration med några andra verktyg.
Egentligen funktionerna pivot_longer() и pivot_wider() är symmetriska och producerar handlingar omvända till varandra, dvs. df %>% pivot_longer(spec = spec) %>% pivot_wider(spec = spec) и df %>% pivot_wider(spec = spec) %>% pivot_longer(spec = spec) kommer att returnera originalet df.
Det enklaste exemplet på att konvertera en tabell till ett brett format
För att visa hur funktionen fungerar pivot_wider() vi kommer att använda datamängden fisk_möten, som lagrar information om hur olika stationer registrerar fiskens rörelse längs floden.
#> # 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
I de flesta fall blir denna tabell mer informativ och enklare att använda om du presenterar information för varje station i en separat kolumn.
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>
Denna datamängd registrerar endast information när fisk har upptäckts av stationen, d.v.s. om någon fisk inte registrerades av någon station, kommer dessa data inte att finnas i tabellen. Detta betyder att utgången kommer att fyllas med NA.
Men i det här fallet vet vi att frånvaron av ett register betyder att fisken inte sågs, så vi kan använda argumentet värden_fyll i funktion pivot_wider() och fyll dessa saknade värden med nollor:
Föreställ dig att vi har en tabell som innehåller en kombination av produkt, land och år. För att generera en testdatumram kan du köra följande kod:
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
Vår uppgift är att utöka dataramen så att en kolumn innehåller data för varje kombination av produkt och land. För att göra detta, skicka bara in argumentet namn_från en vektor som innehåller namnen på de fält som ska slås samman.
Du kan också tillämpa specifikationer på en funktion pivot_wider(). Men när de lämnas till pivot_wider() specifikationen gör den motsatta konverteringen pivot_longer(): Kolumnerna som anges i . Namn, med hjälp av värden från .värde och andra kolumner.
För denna datauppsättning kan du generera en anpassad specifikation om du vill att alla möjliga länder och produktkombinationer ska ha sin egen kolumn, inte bara de som finns i data:
#> # 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
Flera avancerade exempel på att arbeta med det nya tidyr-konceptet
Rensa upp data med hjälp av US Census Income and Rent dataset som exempel.
Datauppsättning us_rent_income innehåller medianinkomst och hyresinformation för varje delstat i USA för 2017 (datauppsättning tillgänglig i paketet tidycensus).
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
I den form som data lagras i datamängden us_rent_income att arbeta med dem är extremt obekvämt, så vi skulle vilja skapa en datamängd med kolumner: hyra, rent_moe, komma, inkomst_moe. Det finns många sätt att skapa denna specifikation, men huvudpoängen är att vi måste generera varje kombination av variabelvärden och uppskattning/moeoch generera sedan kolumnnamnet.
Ibland krävs flera steg för att föra en datamängd till önskad form.
Datauppsättning world_bank_pop innehåller Världsbankens uppgifter om befolkningen i varje land mellan 2000 och 2018.
Vårt mål är att skapa en snygg datamängd med varje variabel i sin egen kolumn. Det är oklart exakt vilka steg som behövs, men vi börjar med det mest uppenbara problemet: året är spritt över flera kolumner.
För att fixa detta måste du använda funktionen pivot_longer().
Nästa steg är att titta på indikatorvariabeln. 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
Där SP.POP.GROW är befolkningstillväxt, SP.POP.TOTL är total befolkning och SP.URB. * samma sak, men bara för tätorter. Låt oss dela upp dessa värden i två variabler: area - area (totalt eller urban) och en variabel som innehåller faktiska data (befolkning eller tillväxt):
Att tabellera denna lista är ganska svårt eftersom det inte finns någon variabel som identifierar vilken data som hör till vilken kontakt. Vi kan fixa detta genom att notera att varje ny kontakts data börjar med "namn", så vi kan skapa en unik identifierare och öka den med en varje gång fältkolumnen innehåller värdet "namn":
#> # 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
Nu när vi har ett unikt ID för varje kontakt kan vi göra om fältet och värdet till kolumner:
#> # 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>
Slutsats
Min personliga åsikt är att det nya konceptet tidyr verkligen mer intuitivt och betydligt överlägsen i funktionalitet jämfört med äldre funktioner spread() и gather(). Jag hoppas att den här artikeln hjälpte dig att hantera pivot_longer() и pivot_wider().