2 Basics 1: Import og håndtering af data
Dette kapitel introducerer til nogle basics vedrørende import og håndtering af data.
2.1 Import af data
R er bygget til at analysere data - men i udgangspunktet ikke til at skabe rå data (undtagelsen er når vi skaber datasæt ved scraping af web-data). Historikeres data eksisterer typisk i en fysisk form, på papir, og vi har efterhånden en lang række værktøjer, der kan hjælpe os til at omforme dem til tekstdata i søgbare, computervenlige (vær venlig mod din computer, tænk på alt det, den gør for dig) formater. Senest har Transkribus revolutioneret arbejdet med håndskrift. Før du kan importere data i R, skal de med andre ord eksistere i digital form.
R håndterer både tekst og tal. Ofte arbejder vi i en form, hvor data eksisterer i tabelformat. Der findes en række regnearksprogrammer, der kan bruges til at skabe tabeller - f.eks. Google Sheets, Apple Numbers og Microsoft Excel. Hver af disse har sit eget dataformat, men kan også eksportere data i universelle formater - f.eks. det ofte brugte .csv-format.
Når vi importerer data antager R i udgangspunktet, at data findes i vores projektmappe. Det kan dog være smart, at lave en mappe kaldet “data” inde i projektmappen, da denne hurtigt kan blive rodet. For at tilskynde god praksis antager denne bogs kapitler derfor, at vores data ligger i en mappe med titlen data, der altså findes i vores projektmappe.
En .csv-fil med data til brug i dette kapitel kan hentes her. Den kan du downloade og lægge ind i din data-mappe.
I R kan vi importere .csv med funktionen read_csv2 (der er også en read_csv uden 2-tallet, men den virker ikke til det format, denne fil benytter).
library(tidyverse)
<- read_csv2("data/Slave_1741_1800_clean.csv")
df
head(df)
# A tibble: 6 × 16
Navn SLAVE…¹ Indkommen Udkom…² Afslu…³ Fejls…⁴ Baggr…⁵ Ærlig…⁶ Relig…⁷ Alder
<chr> <dbl> <date> <chr> <chr> <chr> <chr> <chr> <chr> <dbl>
1 Mort… 1504 1741-05-17 1741-1… Dead No fai… Army Uærlig Luther… 40
2 Hove… 1505 1741-06-21 1741-0… Releas… No fai… Navy Ærlig Luther… 53
3 Pede… 1506 1741-06-21 1741-0… Releas… No fai… Navy Ærlig Luther… 26
4 Knud… 1507 1741-06-21 1741-0… Releas… No fai… Navy Ærlig Luther… 35
5 Lars… 1508 1742-06-12 1742-0… Releas… No fai… Civili… Ærlig Luther… 54
6 Mads… 1509 1742-06-12 1742-0… Releas… No fai… Civili… Ærlig Luther… 63
# … with 6 more variables: Ægteskab <chr>, Kropsstraf_kat <chr>, Livstid <chr>,
# Tyv <chr>, Desertør <chr>, Brændemærket <chr>, and abbreviated variable
# names ¹SLAVE_ID, ²Udkommen, ³Afslutning, ⁴Fejlslagne_flugtforsøg,
# ⁵Baggrund, ⁶Ærlighed, ⁷Religion
R kan ved hjælp af pakken readxl også importere .xlsx-filer, der er det format, der bruges af Microsoft Excel. En version af vores data i dette format, kan downloades her:
Lad os prøve at importere filen.
library(readxl)
<- read_excel("data/Slave_1741_1800_clean.xlsx")
df
head(df)
# A tibble: 6 × 16
Navn SLAVE…¹ Indko…² Udkom…³ Afslu…⁴ Fejls…⁵ Baggr…⁶ Ærlig…⁷ Relig…⁸ Alder
<chr> <dbl> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <dbl>
1 Morten … 1504 1741-0… 1741-1… Dead No fai… Army Uærlig Luther… 40
2 Hovel N… 1505 1741-0… 1741-0… Releas… No fai… Navy Ærlig Luther… 53
3 Peder L… 1506 1741-0… 1741-0… Releas… No fai… Navy Ærlig Luther… 26
4 Knud Ol… 1507 1741-0… 1741-0… Releas… No fai… Navy Ærlig Luther… 35
5 Lars La… 1508 1742-0… 1742-0… Releas… No fai… Civili… Ærlig Luther… 54
6 Mads Je… 1509 1742-0… 1742-0… Releas… No fai… Civili… Ærlig Luther… 63
# … with 6 more variables: Ægteskab <chr>, Kropsstraf_kat <chr>, Livstid <chr>,
# Tyv <chr>, Desertør <chr>, Brændemærket <chr>, and abbreviated variable
# names ¹SLAVE_ID, ²Indkommen, ³Udkommen, ⁴Afslutning,
# ⁵Fejlslagne_flugtforsøg, ⁶Baggrund, ⁷Ærlighed, ⁸Religion
Som det fremgår er resultatet det samme, uanset om vi bruger read_excel eller read_csv2. Det vi får kaldes en “dataframe” og er den mest almindelige form for tabel i R.
Når vi importerer, er det også muligt at bruge R-studios indbyggede import-funktion. Den virker essentielt ved at konstruere et tilsvarende kodestykke for os, men gør det muligt for os at klikke os frem, hvis den ligger gemt i en afkrog på harddisken.
<-
I kodestykkerne ovenfor brugte jeg en mærkværdig pil <-
. Du kan tænke denne pil som en genstand, der definerer noget. Det den definerer står på venstre side (det som pilen peger på). Dette defineres så af det, der står på højre side af pilen. Ovenfor definerer vi derfor et objekt: df (for “data frame”, et hyppigt anvendt tabelformat i R) som resultatet af funktionen read_excel. Du laver pilen med genvejen options plus - (Mac) eller alt plus - (Windows).
De data vi bruger i dette kapitel stammer fra Københavns såkaldte “Stokhusslaveri”, der var et fængsel for mænd. Det åbnede i 1741 og vores data løber til 1800. Dataene bygger på fængslets mandtalsprotokoller, men er “rensede” - forstået på den måde, at de er standardiseret. De fremstår derfor ikke længere som i de oprindelige kilder.
2.2 Grundverbum # 1 - select
I dette kapitel vil jeg introducere nogle verber, du kommer til at bruge igen og igen, når du arbejder i R. Det er verber, der gør os i stand til at håndtere data.
Det første verbum er select. Dets funktion er simpel: Select udvælger enkelte kolonner og fravælger resten. Vi bruger typisk select, når vi i et datasæt med mange kolonner blot ønsker at arbejde med nogle få af dem.
library(tidyverse)
<- df %>%
df select(Indkommen, Udkommen, Livstid, Baggrund, Alder)
head(df)
# A tibble: 6 × 5
Indkommen Udkommen Livstid Baggrund Alder
<chr> <chr> <chr> <chr> <dbl>
1 1741-05-17 1741-10-07 Lifetime sentence Army 40
2 1741-06-21 1741-07-21 Set duration sentence Navy 53
3 1741-06-21 1741-07-21 Set duration sentence Navy 26
4 1741-06-21 1741-07-21 Set duration sentence Navy 35
5 1742-06-12 1742-08-27 Set duration sentence Civilian 54
6 1742-06-12 1742-08-27 Set duration sentence Civilian 63
Her går vi fra en tabel med 16 kolonner til et datasæt, der blot har de kolonner vi har udvalgt.
Ovenfor brugte jeg yderligere et mærkeligt sæt af symboler (bare rolig, det var sidste gang for nu). Dette mærkværdige udtryk kaldes for “piben”. Det er en del af tidyverse-pakken. Det sender data videre til de efterfølgende funktioner. Uden piben ville vi være tvunget til at placere parenteser inde i parenteser. Den gør det derfor meget mere læseligt. Man får den frem med genvejen m plus shift plus command (Mac) eller m plus shift plus ctrl (Windows).
2.3 Grundverbum # 2 - mutate
Det næste verbum er mutate. Mutate-funktionen skaber en eller flere nye kolonne, typisk på baggrund af data i allerede eksisterende kolonner. Den gør det oftest ved brug af yderligere fuktioner.
library(lubridate)
<- df %>%
df mutate(Ankomstdato = ymd(Indkommen),
Slutdato = ymd(Udkommen),
Varighed = Slutdato - Ankomstdato)
Warning: There was 1 warning in `mutate()`.
ℹ In argument: `Slutdato = ymd(Udkommen)`.
Caused by warning:
! 5 failed to parse.
Koden ovenfor skaber 3 nye kolonner i vores tabel. Alle tre skabes med udgangspunkt i data, der allerede eksisterer i kolonner. Ankomstdato skaber en kolonne med en dato af den allerede eksisterende kolonne Indkommen, hvor datoen står som en tekststreng i formatet år-måned-dag. Den skaber denne datokolonne med funktionen ymd (der står year-month-date, og har søskende-funktioner med samme bogstaver i andre kombinationer). Slutdato skabes på samme måde på baggrund af tekststrengen i kolonnen Udkommen. Til sidst udregnes en Varighed ved at trække den ene dato fra den anden. Læg mærke til advarselsbeskeden: i 75 tilfælde manglede der en slutdato, fordi 1700-tals protokoller sjældent er fuldstændigt systematiske og ikke altid helt lette at tyde. Funktionen ymd( ) (der laver datasættets angivelser af dato om til noget R forstår som en faktisk dato) kan i sagens natur ikke lave en NA-værdi om til en dato. Fordi Varighed er udregnet med udgangspunkt i slutdatoen arver denne kolonne den samme manglende data.
Mutate er vores go-to værktøj, når vi skaber nye kolonner i tabeller. Kigger vi igen på vores tabel, vil vi se, at den har den samme struktur, men blot har fået tre nye kolonner.
head(df)
# A tibble: 6 × 8
Indkommen Udkommen Livstid Baggr…¹ Alder Ankomstd…² Slutdato Varig…³
<chr> <chr> <chr> <chr> <dbl> <date> <date> <drtn>
1 1741-05-17 1741-10-07 Lifetime se… Army 40 1741-05-17 1741-10-07 143 da…
2 1741-06-21 1741-07-21 Set duratio… Navy 53 1741-06-21 1741-07-21 30 da…
3 1741-06-21 1741-07-21 Set duratio… Navy 26 1741-06-21 1741-07-21 30 da…
4 1741-06-21 1741-07-21 Set duratio… Navy 35 1741-06-21 1741-07-21 30 da…
5 1742-06-12 1742-08-27 Set duratio… Civili… 54 1742-06-12 1742-08-27 76 da…
6 1742-06-12 1742-08-27 Set duratio… Civili… 63 1742-06-12 1742-08-27 76 da…
# … with abbreviated variable names ¹Baggrund, ²Ankomstdato, ³Varighed
2.4 Grundverber # 3 og 4 - group_by og summarise
Når vi arbejder med dataene har vi igen og igen brug for at gruppere dem efter værdier i dataene. Dette gør vi vha. af group_by-funktionen. Denne kombineres stort set altid med andre funktioner, for vi grupperer som regel med det formål at bearbejde vores data ifht. enkelte grupper. Et simpelt eksempel kunne være, at vi ønskede at gruppere vores data efter en simpel kategorisk variabel og tælle, hvor mange observationer, der er for hver kategori.
<- df %>%
df_grouped group_by(Livstid) %>%
summarise(antal = n())
df_grouped
# A tibble: 3 × 2
Livstid antal
<chr> <int>
1 Lifetime sentence 1342
2 Set duration sentence 1843
3 <NA> 5
Her grupperes vores data efter kategorierne i kolonnen Livstid. Næste bid af kode skaber så en ny kolonne, på samme måde som mutate gjorde det. Her udregner vi et antal ved at bruge en funktion, der er designet netop til dette formål, og som sjovt nok netop hedder n.
Summarise funktionen betyder, at vi beholder den gruppering, vi har skabt. Dermed får datasættet en ny struktur med et antal af rækker, der svarer til antallet af kategorier i den kolonne, vi har grupperet efter. Smart. Hvis vi havde brugt mutate, var vores gruppering derimod blevet ophævet og n havde optrådt som en kolonne i den oprindelige datastruktur. Til sammenligning:
%>%
df group_by(Livstid) %>%
mutate(antal = n())
# A tibble: 3,190 × 9
# Groups: Livstid [3]
Indkommen Udkommen Livstid Baggr…¹ Alder Ankomstd…² Slutdato Varig…³ antal
<chr> <chr> <chr> <chr> <dbl> <date> <date> <drtn> <int>
1 1741-05-17 1741-10… Lifeti… Army 40 1741-05-17 1741-10-07 143 d… 1342
2 1741-06-21 1741-07… Set du… Navy 53 1741-06-21 1741-07-21 30 d… 1843
3 1741-06-21 1741-07… Set du… Navy 26 1741-06-21 1741-07-21 30 d… 1843
4 1741-06-21 1741-07… Set du… Navy 35 1741-06-21 1741-07-21 30 d… 1843
5 1742-06-12 1742-08… Set du… Civili… 54 1742-06-12 1742-08-27 76 d… 1843
6 1742-06-12 1742-08… Set du… Civili… 63 1742-06-12 1742-08-27 76 d… 1843
7 1742-06-12 1742-09… Set du… Civili… 49 1742-06-12 1742-09-11 91 d… 1843
8 1742-06-12 1742-12… Set du… Civili… 60 1742-06-12 1742-12-10 181 d… 1843
9 1742-07-07 1742-10… Set du… Army 40 1742-07-07 1742-10-07 92 d… 1843
10 1742-07-11 1747-09… Lifeti… Army 30 1742-07-11 1747-09-01 1878 d… 1342
# … with 3,180 more rows, and abbreviated variable names ¹Baggrund,
# ²Ankomstdato, ³Varighed
Prøv at klikke dig helt ud til den yderste kolonne. Her finder du n. Men udregningen i kolonnen svarer til den grupperede værdi.
Summarise og mutate virker med andre ord ved at skabe en kolonne. Forskellen er, at summarise resulterer i en tabel, med en række for hver gruppe, imens mutate blot skaber den nye kolonne i den allerede eksisterende tabel.
Øvelse: Ovenfor skabte vi en ny kolonne. Men hvis vi igen kigger på dataene er den væk. Kan du regne ud, hvorfor den er forsvundet?
head(df)
# A tibble: 6 × 8
Indkommen Udkommen Livstid Baggr…¹ Alder Ankomstd…² Slutdato Varig…³
<chr> <chr> <chr> <chr> <dbl> <date> <date> <drtn>
1 1741-05-17 1741-10-07 Lifetime se… Army 40 1741-05-17 1741-10-07 143 da…
2 1741-06-21 1741-07-21 Set duratio… Navy 53 1741-06-21 1741-07-21 30 da…
3 1741-06-21 1741-07-21 Set duratio… Navy 26 1741-06-21 1741-07-21 30 da…
4 1741-06-21 1741-07-21 Set duratio… Navy 35 1741-06-21 1741-07-21 30 da…
5 1742-06-12 1742-08-27 Set duratio… Civili… 54 1742-06-12 1742-08-27 76 da…
6 1742-06-12 1742-08-27 Set duratio… Civili… 63 1742-06-12 1742-08-27 76 da…
# … with abbreviated variable names ¹Baggrund, ²Ankomstdato, ³Varighed
Ovenfor brugte vi en kombination af group_by()
, summarise()
og n()
til at lave en optælling over antal observationer pr. gruppe. Er en simpel optælling med n()
vores eneste mål med en gruppering, findes der en genvej i form af funktionen count()
. Vi kunne derfor have fået samme resultat sådan her:
%>%
df count(Livstid)
# A tibble: 3 × 2
Livstid n
<chr> <int>
1 Lifetime sentence 1342
2 Set duration sentence 1843
3 <NA> 5
Når jeg således viser en mere omstændig vej til samme resultat, er det for at vise et workflow, du kommer til at bruge igen og igen. Ofte vil vi nemlig ikke kun lave en simpel optælling, men udregne flere variable på en og samme tid.
2.5 Grundverber # 5 - na.omit
Vi er allerede flere gange stødt på det problem, at der er manglende værdier - NA-værdier - i vores data. Laver vi eksempelvis et kald med summarise som vi har gjort ovenfor optræder disse værdier som en række i vores sammenfattede tabel. Vi kan imidlertid skabe samme tabel, men sortere disse værdier fra. Det gør vi ved først at udvælge kolonnerne og derefter bruge funktinen na.omit.
%>%
df select(Livstid) %>%
na.omit() %>%
group_by(Livstid) %>%
summarise(antal = n())
# A tibble: 2 × 2
Livstid antal
<chr> <int>
1 Lifetime sentence 1342
2 Set duration sentence 1843
2.6 Grundverbum # 6 - filter
Vi har lært at udvælge kolonner baseret på deres navn. Men hvad hvis jeg gerne vil udvælge rækker/observationer baseret på hvad de indeholder? Her bruger vi filter.
<- df %>%
df_soldater filter(Baggrund == "Army")
head(df_soldater)
# A tibble: 6 × 8
Indkommen Udkommen Livstid Baggr…¹ Alder Ankomstd…² Slutdato Varig…³
<chr> <chr> <chr> <chr> <dbl> <date> <date> <drtn>
1 1741-05-17 1741-10-07 Lifetime se… Army 40 1741-05-17 1741-10-07 143 d…
2 1742-07-07 1742-10-07 Set duratio… Army 40 1742-07-07 1742-10-07 92 d…
3 1742-07-11 1747-09-01 Lifetime se… Army 30 1742-07-11 1747-09-01 1878 d…
4 1742-08-11 1748-08-27 Lifetime se… Army 41 1742-08-11 1748-08-27 2208 d…
5 1742-08-18 1742-11-18 Set duratio… Army 32 1742-08-18 1742-11-18 92 d…
6 1742-09-03 1745-07-07 Lifetime se… Army 36 1742-09-03 1745-07-07 1038 d…
# … with abbreviated variable names ¹Baggrund, ²Ankomstdato, ³Varighed
Vi kan i et og samme kald filtrere på flere parametre samtidigt:
<- df %>%
df_soldater_med_livstid filter(Baggrund == "Army" & Livstid == "Lifetime sentence")
head(df_soldater_med_livstid)
# A tibble: 6 × 8
Indkommen Udkommen Livstid Baggr…¹ Alder Ankomstd…² Slutdato Varig…³
<chr> <chr> <chr> <chr> <dbl> <date> <date> <drtn>
1 1741-05-17 1741-10-07 Lifetime se… Army 40 1741-05-17 1741-10-07 143 d…
2 1742-07-11 1747-09-01 Lifetime se… Army 30 1742-07-11 1747-09-01 1878 d…
3 1742-08-11 1748-08-27 Lifetime se… Army 41 1742-08-11 1748-08-27 2208 d…
4 1742-09-03 1745-07-07 Lifetime se… Army 36 1742-09-03 1745-07-07 1038 d…
5 1742-09-03 1743-03-29 Lifetime se… Army 28 1742-09-03 1743-03-29 207 d…
6 1742-09-04 1754-08-16 Lifetime se… Army 30 1742-09-04 1754-08-16 4364 d…
# … with abbreviated variable names ¹Baggrund, ²Ankomstdato, ³Varighed
Her får vi alle hits, der matcher både første og andet kriterium. Hvis jeg i stedet vil filtrere på en måde, så jeg blot kræver et match på et af kriterierne, er det også muligt:
<- df %>%
df_soldater_og_søfolk filter(Baggrund == "Army" | Baggrund == "Navy")
head(df_soldater_og_søfolk)
# A tibble: 6 × 8
Indkommen Udkommen Livstid Baggr…¹ Alder Ankomstd…² Slutdato Varig…³
<chr> <chr> <chr> <chr> <dbl> <date> <date> <drtn>
1 1741-05-17 1741-10-07 Lifetime se… Army 40 1741-05-17 1741-10-07 143 d…
2 1741-06-21 1741-07-21 Set duratio… Navy 53 1741-06-21 1741-07-21 30 d…
3 1741-06-21 1741-07-21 Set duratio… Navy 26 1741-06-21 1741-07-21 30 d…
4 1741-06-21 1741-07-21 Set duratio… Navy 35 1741-06-21 1741-07-21 30 d…
5 1742-07-07 1742-10-07 Set duratio… Army 40 1742-07-07 1742-10-07 92 d…
6 1742-07-11 1747-09-01 Lifetime se… Army 30 1742-07-11 1747-09-01 1878 d…
# … with abbreviated variable names ¹Baggrund, ²Ankomstdato, ³Varighed