Als je data krijgt is het belangrijk om over enkele technieken te beschikken de data goed te ontrafelen en schoon te maken. Het pakket tidyverse is een standaardpakket waarmee je goed uit de voeten kunt. Ook het pakket janitor hoort thuis in de gereedschapskist van de analyticus. Een aantal codes van dat janitorpakket wilde ik goed leren kennen. Vandaar deze blog. Daarbij bouw ik voort op deze blog hier.
Janitor
Eerst maar eens janitor en tidyverse laden en de data binnenhalen (wel eerst installeren als je deze pakketten niet hebt).
library(janitor)
Attaching package: 'janitor'
The following objects are masked from 'package:stats':
chisq.test, fisher.test
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag() masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
Laten we een beetje met deze data werken. Eerst geven we de kolommen de naam columns om te voorkomen dat het rommelig wordt. Vervolgens gebruiken we de separate() functie om de kolommen te scheiden. Vervolgens filteren we de gegevens tot Berkshire County, omdat bij nadere inspectie van de gegevens duidelijk wordt dat er een paar items van buiten dit gebied zijn opgenomen. Dan gebruiken we mutate() om verder op te ruimen. str_replace() wordt gebruikt om de ID “598673” te vervangen door “598712,” een ID nummer dat al bestaat in de dataset, om zo een duplicaat ID te maken. Tenslotte wordt een extra kolom genaamd “extra_column” aangemaakt met NAs in elke rij:
Voor we verder gaan, maken we snel ook een tweede dataset aan. Deze noemen we “non_ma_names”, met data die niet van Berkshire County afkomstig zijn. Nogmaals lezen we het “GNIS Query Result.csv” bestand in en scheiden de kolomnamen. Vervolgens gebruiken we de clean_names() functie uit het janitor pakket, die we in de volgende sectie uitvoerig zullen behandelen. Tenslotte gebruiken we as.numeric() en as.factor() in een mutate stap om onze ele_ft variabele te transformeren naar een numerieke variabele en onze map variabele naar een factor:
Je hebt waarschijnlijk al heel wat databestanden ontvangen, waarschijnlijk in .xlsx-formaat. Bovenaan de spreadsheet staan er dan niet zelden een aantal rijen voordat de eigenlijke gegevens beginnen. Deze rijen kunnen leeg zijn of zijn gevuld met informatie en bedrijfslogo’s. Wanneer je dergelijke gegevens in R laadt, kan de inhoud van deze eerste rijen automatisch jouw kolomkoppen en eerste rijen worden. De functie row_to_names() in het janitor-pakket geeft joun de gelegenheid om aan te geven welke rij in jouw dataframe de eigenlijke kolomnamen bevat en om al het andere dat aan die rij voorafgaat te verwijderen. In onze dataset hadden de kolomnamen al de juiste plaats. Maar laten we deze functie toch eens proberen. We doen alsof de kolomnamen in de derde rij staan. We gebruiken de row_to_names()-functie om een nieuw data frame te maken genaamd “test_names”. De row_to_names() functie neemt de volgende argumenten: de gegevensbron, het rijnummer waar de kolomnamen vandaan moeten komen, of die rij moet worden verwijderd uit de gegevens, en of de rijen erboven moeten worden verwijderd uit de gegevens:
clean_names() functie wordt vaak gebruikt als je een nieuwe dataset in R laadt. Als je deze functie nog niet gebruikt, raad ik je sterk aan om deze in jouw workflow op te nemen. Het is niet voor niets de meest populaire functie uit het janitor- pakket - het is uiterst nuttig! Laten we even terugkijken naar onze kolomnamen. Er zijn allerlei hoofdletters en spaties (b.v. “Feature Name”, “BGN Date”) en ook symbolen (“Ele(ft)”). De clean_names() functie zet deze allemaal voor ons om naar kleine letters.
Het gebruik van clean_names() is eenvoudig en kan als volgt worden uitgevoerd:
Zoals je ziet, heeft deze ene functie alle soorten rommelige kolomnamen verwerkt. Alles ziet er nu netjes en opgeruimd uit. Kijk maar eens
head(place_names)
feature_name id class county state latitude longitude
1 A-Z Shopping Center 603538 Locale Berkshire MA 422755N 0731254W
2 Abbey Hill 607260 Summit Berkshire MA 420822N 0730930W
3 Abbey Lake 617758 Reservoir Berkshire MA 420818N 0730908W
4 Abbey Lake Dam 604930 Dam Berkshire MA 420806N 0730910W
5 Abbey Swamp 607261 Swamp Berkshire MA 420822N 0730930W
6 Abbott Memorial School 598712 School Berkshire MA 424032N 0730213W
ele_ft map bgn_date entry_date extra_column
1 1037 Pittsfield East 27-AUG-2002 NA
2 1798 Monterey 24-FEB-1974 NA
3 1463 Monterey 24-FEB-1974 NA
4 1486 Monterey 27-AUG-2002 NA
5 1798 Monterey 24-FEB-1974 NA
6 1696 North Adams 27-AUG-2002 NA
remove_empty()
De remove_empty() functie verwijdert, zoals de naam al zegt, kolommen die leeg zijn. We hebben een lege kolom gemaakt in ons “place_names” dataframe tijdens het voorbereiden van onze gegevens, dus we weten dat ten minste één kolom door deze functie zou moeten worden beïnvloed. Laten we het eens uitproberen:
place_names = place_names %>%remove_empty()
value for "which" not specified, defaulting to c("rows", "cols")
Zoals je kunt zien is de lege kolom (‘extra_column’) verdwenen en zijn er niet meer 12 maar 11 variabelen over.
De bgn_date-kolom lijkt leeg, maar het feit dat deze niet is verwijderd door remove_empty() vertelt ons dat er in ieder geval in één rij gegevens moeten zitten. Scroll maar in de dataset naar beneden en dan zie je het.
remove_constant()
De remove_constant() functie verwijdert kolommen met dezelfde waarde in alle rijen. Onze dataset heeft er momenteel twee - omdat we de data hebben gefilterd tot Berkshire County, en heel Berkshire County in Massachusetts ligt, is county = “Berkshire” en staat = “MA” voor alle rijen. Deze rijen zijn niet bijzonder nuttig om in de dataset te houden omdat ze geen rij-specifieke informatie geven. We zouden simpelweg select() kunnen gebruiken om deze kolommen te verwijderen, maar het voordeel van remove_constant() is dat deze functie de aanname alle gegevens hetzelfde zijn, dubbel controleert. In feite, door het gebruik van remove_constant() werd ook duidelijk dat 38 van de 1968 items in de ruwe data eigenlijk niet van Berkshire Country waren! Net als remove_empty(), is alle informatie die de remove_constant() functie nodig heeft, de dataset waarop het moet werken:
place_names = place_names %>%remove_constant()
Zoals je kunt zien, zijn de variabelen Berkshire(county) en MA(staat) nu er uit en zijn er nog negen variabelen over.
compare_df_cols()
Ooit geprobeerd om rbind() te gebruiken om twee data frames te stapelen en tegen een onverwachte fout aangelopen? De compare_df_cols() functie vergelijkt direct de kolommen in twee dataframes en is ongelooflijk handig voor het oplossen van dit probleem. Laten we het eens proberen door ons “place_names” data frame te vergelijken met het data frame dat we hebben gemaakt met gegevens buiten Berkshire County, “non_ma_names”:
compare_df_cols(place_names, non_ma_names)
column_name place_names non_ma_names
1 bgn_date character character
2 class character character
3 county <NA> character
4 ele_ft character numeric
5 entry_date character character
6 feature_name character character
7 id character character
8 latitude character character
9 longitude character character
10 map character factor
11 state <NA> character
De output is een handige tabel waarin de twee dataframes worden vergeleken. We zien “NA” voor county en state in place_names en “character” voor deze variabelen in non_ma_names. Dit komt omdat we deze kolommen met remove_constant() uit place_names hebben verwijderd, maar nooit iets hebben gedaan met de standaard karaktervariabelen in non_ma_names. We zien ook ele_ft als numeriek en map als een factor variabele in non_ma_names, die we specifiek hebben aangewezen tijdens datavoorbereiding. Als we deze dataframes zouden proberen samen te voegen, zou het nuttig zijn te weten welke kolommen ontbreken en welke kolommen inconsistente types hebben in de dataframes. In dataframes met veel kolommen kan compare_df_cols() de tijd die nodig is om deze vergelijkingen te maken, aanzienlijk verminderen.
get_dupes()
Ik heb vaak gewerkt aan projecten met unieke patiënt-ID’s waarvan je niet verwacht dat ze dubbel voorkomen in je dataset. Er zijn tal van andere gevallen waarin je ervoor zou willen zorgen dat een ID-variabele volledig unieke waarden heeft, waaronder onze GNIS-gegevens. Zoals je je zult herinneren, hebben we een dubbele ID aangemaakt toen we onze data voorbereidden. Laten we eens kijken hoe get_dupes() dit detecteert. De functie heeft enkel de naam van ons dataframe nodig en de naam van de kolom die als identifier fungeert:
get_dupes(place_names, id)
id dupe_count feature_name class latitude longitude ele_ft
1 598712 2 Abbott Memorial School School 424032N 0730213W 1696
2 598712 2 Abby Lodge School School 422440N 0731503W 1076
map bgn_date entry_date
1 North Adams 27-AUG-2002
2 Pittsfield West 27-AUG-2002
Zoals hieronder getoond, wordt het dataframe gefilterd tot de rijen met dubbele waarden in de kolom ID, zodat eventuele problemen gemakkelijk kunnen worden onderzocht:
tabyl()
De tabyl() functie is vergelijkbaar met de table() functie van tidyverse. Het is ook compatibel met het knitr pakket, en is erg handig voor data exploratie.Laten we het eerst uitproberen met een enkele variabele. Stel dat we geïnteresseerd zijn in hoeveel scholen er zijn in elk van de steden in Berkshire County. We filteren eerst onze klasse variabele op “School”, en gebruiken dan de tabyl() functie met onze map(locatie) variabele. Tenslotte pijpen we dat in knitr::kable() om de uitvoer in een mooie tabel te formatteren:
Warning: Using one column matrices in `filter()` was deprecated in dplyr 1.1.0.
ℹ Please use one dimensional logical vectors instead.
ℹ The deprecated feature was likely used in the janitor package.
Please report the issue at <https://github.com/sfirke/janitor/issues>.
map
n
percent
Bash Bish Falls
2
0.0143885
Becket
4
0.0287770
Cheshire
3
0.0215827
East Lee
3
0.0215827
Egremont
3
0.0215827
Great Barrington
8
0.0575540
Hancock
1
0.0071942
Monterey
2
0.0143885
North Adams
15
0.1079137
Peru
1
0.0071942
Pittsfield East
41
0.2949640
Pittsfield West
18
0.1294964
Stockbridge
21
0.1510791
Tolland Center
1
0.0071942
Williamstown
10
0.0719424
Windsor
6
0.0431655
Het uitvoeren van deze zeer eenvoudige code levert de volgende uitvoertabel op:
Wanneer we ons Rmd bestand ‘knitten’, zal de kable() functie de tabel mooi opmaken, zoals hierboven getoond. We krijgen een aantal scholen in elke stad, evenals het percentage van alle scholen in die stad. Het is gemakkelijk om opmerkingen te maken over deze gegevens, zoals dat 29,5% van alle scholen in Pittsfield East zijn, dat 41 scholen telt. Of dat 3 steden zo klein zijn dat ze maar 1 school hebben:
Laten we nu de kruistabellen van twee variabelen proberen. Laten we eens kijken hoeveel herkenningspunten van elk type aanwezig zijn in elke stad:
Een deel van onze tabel (eenmaal ‘geknit’) is hierboven afgebeeld. Voor elke stad kunnen we duidelijk zien hoeveel van elk oriëntatiepunttype er in de database zitten:
Hoewel eenvoudige tellingen als deze heel nuttig kunnen zijn, geven we misschien meer om kolompercentages. Met andere woorden, hoeveel procent van de items voor elk oriëntatiepunttype zijn er in elke stad? Dit is gemakkelijk te onderzoeken met tabyl() via de adorn_percentages() functie:
Nu zien we deze kolompercentages in plaats van tellingen, maar de tabel is nogal moeilijk te lezen.
We kunnen dit een beetje opschonen met de adorn_pct_formatting() functie, die de gebruiker toestaat het aantal decimalen op te geven dat in de uitvoer moet worden opgenomen. Precisie is niet bijzonder belangrijk voor deze verkennende tabel, dus laten we 0 decimalen gebruiken om deze tabel makkelijker leesbaar te maken:
Veel beter! Nu is het veel gemakkelijker om de tabel te lezen en onze kolompercentages te begrijpen:
Het is net zo eenvoudig om adorn_percentages() te gebruiken om in plaats daarvan naar rij percentages te kijken (in ons geval, het percentage vermeldingen van elke stad dat behoort tot elk oriëntatiepunt type):
In dit blog zijn de functies uit het janitor-pakket beschreven die nuttig zijn voor het dagelijkse werk. Dit is echter geen uitputtende lijst van janitor functies en ik raad aan om de documentatie te raadplegen voor meer informatie over dit pakket.
Er zijn nog een paar andere functies die op zijn minst de moeite waard zijn om hier te vermelden: excel_numeric_to_date(): Deze functie is ontworpen om veel van Excel’s datum formaten te verwerken en om deze numerieke variabelen om te zetten naar datum variabelen. Het lijkt een grote tijdsbesparing voor diegenen die vaak met gegevens in Excel werken. Als niet frequent gebruiker van Excel, vertrouw ik in plaats daarvan zwaar op het lubridate pakket voor het werken met datum variabelen.
round_to_fraction(): Met deze functie kun je decimale getallen afronden naar een precieze breuknoemer. Wil je al je waarden afgerond hebben naar het dichtstbijzijnde kwartier, of gebruik je decimalen om minuten in een uur weer te geven? Dan kan de round_to_fraction()-functie jou waarschijnlijk helpen.
top_levels(): Deze functie genereert een frequentietabel die een categorische variabele samenbrengt in hoge, middelste en lage niveaus. Veelgebruikte gevallen zijn onder andere het vereenvoudigen van Likert-achtige schalen.
Conclusie
Het is op dit punt algemeen bekend dat de meeste dataanalisten en -wetenschappers het grootste deel van hun tijd besteden aan het opschonen en verkennen van gegevens. Daarom is het goed om nieuwe pakketten en functies te ontdekken die deze processen een beetje efficiënter maken.
Of je het janitor-pakket nu wel of niet eerder hebt gebruikt, ik hoop dat deze blog jouw kennis heeft laten maken met enkele functies die nuttige toevoegingen zullen blijken te zijn aan je datawetenschapsgereedschapskist.