October, 2015

Today

New survey

Guldølsopgave

New deadline for assignment 1

Data manipulation tools

  • split-apply-combine approach to data manipulation
  • tidy data

New survey

Optional guldølsopgave

Do you the chance to win one of these?!

Then the non-mandatory part of Assignment 1 is for you!

Non-mandatory assignment

The newly elected Danish government has proposed a federal budget for 2016

This is a great data visualization exercise

The assignment is not mandatory, so only do it if you're interested (but remember the incentives!)

Deadline the same as Assignment 1: October 14

One attempt…

Here's how Berlingske solved it - can you do better?

What I want you to do

Find something interesting in the budget proposal for 2016

Visualize it

Send the result to me (along with the R script and a few lines explaining what you've done)

The best answer wins a Guldøl - and I'll send your visualization to Berlingske

Data

Data is available through the Danish Ministry of Finance

… in a horrible format

Data in nice, tidy format available here

Script here

Data Manipulation

Intro

"Herein lies the dirty secret about most data scientists' work – it's more data munging than deep learning. The best minds of my generation are deleting commas from log files, and that makes me sad. A Ph.D. is a terrible thing to waste."

source

Data janitor

Raw versus processed data

Raw data

  • the original source of the data
  • often hard to use for data analysis
  • you should never process your original data

Processed data

  • Data that is ready for analysis

Data manipulation involves going from raw to processed data.

This can include merging, subsetting, transforming, etc.

All steps that take you from raw to processed data should be scripted

Data for today

In this part of the lecture we will work with the federal budget proposal for 2016

library("readr")
df = read_csv("https://raw.githubusercontent.com/sebastianbarfort/sds/gh-pages/data/finanslov_tidy.csv")

Some nice guy has already cleaned this data for you

Overview of your data

Useful functions:

  • str: displays the structure of your data frame
  • head: displays the first rows
  • summary: gives summary statistics
  • glimpse (from the dplyr package): modern alternative to str

str

str(df)
## Classes 'tbl_df', 'tbl' and 'data.frame':    8430 obs. of  6 variables:
##  $ paragraf   : chr  "Dronningen " "Medlemmer af det kongelige hus m.fl. " "Folketinget " "Folketinget " ...
##  $ hovedomrode: chr  "Statsydelse " "Ã…rpenge mv. " "Udgifter ved Folketinget " "Udgifter ved Folketinget " ...
##  $ aktivitet  : chr  "Statsydelse " "Ã…rpenge mv. " "Folketinget " "Folketinget " ...
##  $ hovedkonto : chr  "Statsydelse " "Ã…rpenge mv. " "Folketinget " "Folketingets medlemmer " ...
##  $ aar        : int  2014 2014 2014 2014 2014 2014 2014 2014 2014 2014 ...
##  $ udgift     : num  77.7 26.4 387.8 264.2 6.5 ...

head

summary

summary(df)
##    paragraf         hovedomrode         aktivitet        
##  Length:8430        Length:8430        Length:8430       
##  Class :character   Class :character   Class :character  
##  Mode  :character   Mode  :character   Mode  :character  
##                                                          
##                                                          
##                                                          
##   hovedkonto             aar           udgift          
##  Length:8430        Min.   :2014   Min.   :-306424.10  
##  Class :character   1st Qu.:2015   1st Qu.:      0.00  
##  Mode  :character   Median :2016   Median :      7.00  
##                     Mean   :2016   Mean   :    223.18  
##                     3rd Qu.:2018   3rd Qu.:     89.95  
##                     Max.   :2019   Max.   : 132541.40

glimpse

library("dplyr")
glimpse(df)
## Observations: 8,430
## Variables: 6
## $ paragraf    (chr) "Dronningen ", "Medlemmer af det kongelige hus m.f...
## $ hovedomrode (chr) "Statsydelse ", "Ã…rpenge mv. ", "Udgifter ved Folk...
## $ aktivitet   (chr) "Statsydelse ", "Ã…rpenge mv. ", "Folketinget ", "F...
## $ hovedkonto  (chr) "Statsydelse ", "Ã…rpenge mv. ", "Folketinget ", "F...
## $ aar         (int) 2014, 2014, 2014, 2014, 2014, 2014, 2014, 2014, 20...
## $ udgift      (dbl) 77.7, 26.4, 387.8, 264.2, 6.5, 2.2, 63.3, 5.0, 202...

The Split-Apply-Combine approach

Many data analysis problems involve the application of a split-apply-combine strategy, where you break up a big problem into manageable pieces, opereate on each piece independetly and then put the pieces back together

Split-Apply-Combine

The dplyr package

dplyr: (efficiently) split-apply-combine for data frames

Verbs a verb is a function that takes a data frame as it's first argument

  • filter: select rows
  • arrange: order rows
  • select: select columns
  • rename: rename columns
  • distinct: find distinct rows
  • mutate: add new variables
  • summarise: summarize across a data set
  • sample_n: sample from a data set

The filter function I

filter(df, udgift == min(udgift))
## Source: local data frame [1 x 6]
## 
##               paragraf                    hovedomrode      aktivitet
##                  (chr)                          (chr)          (chr)
## 1 Skatter og afgifter  Skatter på indkomst og formue  Personskatter 
## Variables not shown: hovedkonto (chr), aar (int), udgift (dbl)
filter(df, paragraf == "Skatter og afgifter ")
## Source: local data frame [174 x 6]
## 
##                paragraf                    hovedomrode
##                   (chr)                          (chr)
## 1  Skatter og afgifter  Skatter på indkomst og formue 
## 2  Skatter og afgifter  Skatter på indkomst og formue 
## 3  Skatter og afgifter  Skatter på indkomst og formue 
## 4  Skatter og afgifter  Skatter på indkomst og formue 
## 5  Skatter og afgifter  Skatter på indkomst og formue 
## 6  Skatter og afgifter  Skatter på indkomst og formue 
## 7  Skatter og afgifter  Skatter på indkomst og formue 
## 8  Skatter og afgifter  Skatter på indkomst og formue 
## 9  Skatter og afgifter      Told- og forbrugsafgifter 
## 10 Skatter og afgifter      Told- og forbrugsafgifter 
## ..                  ...                            ...
## Variables not shown: aktivitet (chr), hovedkonto (chr), aar (int), udgift
##   (dbl)

Combining conditions: and

You can easily combine conditions

filter(df, paragraf == "Skatter og afgifter " & aktivitet == "Personskatter ")
## Source: local data frame [12 x 6]
## 
##                paragraf                    hovedomrode      aktivitet
##                   (chr)                          (chr)          (chr)
## 1  Skatter og afgifter  Skatter på indkomst og formue  Personskatter 
## 2  Skatter og afgifter  Skatter på indkomst og formue  Personskatter 
## 3  Skatter og afgifter  Skatter på indkomst og formue  Personskatter 
## 4  Skatter og afgifter  Skatter på indkomst og formue  Personskatter 
## 5  Skatter og afgifter  Skatter på indkomst og formue  Personskatter 
## 6  Skatter og afgifter  Skatter på indkomst og formue  Personskatter 
## 7  Skatter og afgifter  Skatter på indkomst og formue  Personskatter 
## 8  Skatter og afgifter  Skatter på indkomst og formue  Personskatter 
## 9  Skatter og afgifter  Skatter på indkomst og formue  Personskatter 
## 10 Skatter og afgifter  Skatter på indkomst og formue  Personskatter 
## 11 Skatter og afgifter  Skatter på indkomst og formue  Personskatter 
## 12 Skatter og afgifter  Skatter på indkomst og formue  Personskatter 
## Variables not shown: hovedkonto (chr), aar (int), udgift (dbl)

Combining conditions: or

filter(df, paragraf == "Skatter og afgifter " | aktivitet == "Personskatter ")
## Source: local data frame [174 x 6]
## 
##                paragraf                    hovedomrode
##                   (chr)                          (chr)
## 1  Skatter og afgifter  Skatter på indkomst og formue 
## 2  Skatter og afgifter  Skatter på indkomst og formue 
## 3  Skatter og afgifter  Skatter på indkomst og formue 
## 4  Skatter og afgifter  Skatter på indkomst og formue 
## 5  Skatter og afgifter  Skatter på indkomst og formue 
## 6  Skatter og afgifter  Skatter på indkomst og formue 
## 7  Skatter og afgifter  Skatter på indkomst og formue 
## 8  Skatter og afgifter  Skatter på indkomst og formue 
## 9  Skatter og afgifter      Told- og forbrugsafgifter 
## 10 Skatter og afgifter      Told- og forbrugsafgifter 
## ..                  ...                            ...
## Variables not shown: aktivitet (chr), hovedkonto (chr), aar (int), udgift
##   (dbl)

The select function

select(df, aar, udgift)
## Source: local data frame [8,430 x 2]
## 
##      aar udgift
##    (int)  (dbl)
## 1   2014   77.7
## 2   2014   26.4
## 3   2014  387.8
## 4   2014  264.2
## 5   2014    6.5
## 6   2014    2.2
## 7   2014   63.3
## 8   2014    5.0
## 9   2014  202.5
## 10  2014   76.7
## ..   ...    ...

The arrange function

arrange(df, hovedomrode, udgift)
## Source: local data frame [8,430 x 6]
## 
##                                        paragraf         hovedomrode
##                                           (chr)               (chr)
## 1  Min. for Børn, Undervisning og Ligestilling  Administration mv. 
## 2  Min. for Børn, Undervisning og Ligestilling  Administration mv. 
## 3  Min. for Børn, Undervisning og Ligestilling  Administration mv. 
## 4  Min. for Børn, Undervisning og Ligestilling  Administration mv. 
## 5  Min. for Børn, Undervisning og Ligestilling  Administration mv. 
## 6  Min. for Børn, Undervisning og Ligestilling  Administration mv. 
## 7  Min. for Børn, Undervisning og Ligestilling  Administration mv. 
## 8  Min. for Børn, Undervisning og Ligestilling  Administration mv. 
## 9  Min. for Børn, Undervisning og Ligestilling  Administration mv. 
## 10 Min. for Børn, Undervisning og Ligestilling  Administration mv. 
## ..                                          ...                 ...
## Variables not shown: aktivitet (chr), hovedkonto (chr), aar (int), udgift
##   (dbl)

Arrange by numeric variable

arrange(df, -aar)
## Source: local data frame [8,430 x 6]
## 
##                                 paragraf
##                                    (chr)
## 1                            Dronningen 
## 2  Medlemmer af det kongelige hus m.fl. 
## 3                           Folketinget 
## 4                           Folketinget 
## 5                           Folketinget 
## 6                           Folketinget 
## 7                           Folketinget 
## 8                           Folketinget 
## 9                           Folketinget 
## 10                          Folketinget 
## ..                                   ...
## Variables not shown: hovedomrode (chr), aktivitet (chr), hovedkonto (chr),
##   aar (int), udgift (dbl)

The mutate function

mutate let's you add new variables to your data frame

df.mutated = mutate(df, newVar = udgift/2)
select(df.mutated, newVar, udgift)
## Source: local data frame [8,430 x 2]
## 
##    newVar udgift
##     (dbl)  (dbl)
## 1   38.85   77.7
## 2   13.20   26.4
## 3  193.90  387.8
## 4  132.10  264.2
## 5    3.25    6.5
## 6    1.10    2.2
## 7   31.65   63.3
## 8    2.50    5.0
## 9  101.25  202.5
## 10  38.35   76.7
## ..    ...    ...

The sample_n function

sample_n(df, 3)
## Source: local data frame [3 x 6]
## 
##                                 paragraf            hovedomrode
##                                    (chr)                  (chr)
## 1             Beskæftigelsesministeriet  Arbejdsmarkedsservice 
## 2                     Finansministeriet   Grønland og Færøerne 
## 3 Uddannelses- og Forskningsministeriet        Støtteordninger 
## Variables not shown: aktivitet (chr), hovedkonto (chr), aar (int), udgift
##   (dbl)

Digression: Meet the pipe

The pipe operator

The pipe operator %>% (RStudio has keyboard shortcuts, learn to use them!) let's you write sequences instead of nested functions

x %>% f(y) -> f(x,y)

x %>% f(z, .) -> f(z, x)

Read %>% as "then". First do this, then do this, etc…

It's implemented in R by a Danish econometrician

Intuition

Combining dplyr and the pipe

dplyr is designed to work with the pipe.

So

df %>% 
  select(aar, udgift) %>% 
  filter(aar == 2014)

returns the same as

filter(select(df, aar, udgift), aar == 2014)

Example

Show me a random sample of the data from 2014, where paragraf == Folketinget and udgift is above the mean.

df.1 = filter(df, aar == 2014 & paragraf == "Folketinget ")
df.2 = filter(df.1, udgift > mean(udgift, na.rm = TRUE))
df.3 = sample_n(df.2, 3)
df.3
## Source: local data frame [3 x 6]
## 
##       paragraf                         hovedomrode
##          (chr)                               (chr)
## 1 Folketinget  Statsrevisorerne og Rigsrevisionen 
## 2 Folketinget            Udgifter ved Folketinget 
## 3 Folketinget            Udgifter ved Folketinget 
## Variables not shown: aktivitet (chr), hovedkonto (chr), aar (int), udgift
##   (dbl)

With the pipe

df %>% 
  filter(aar == 2014 & paragraf == "Folketinget ") %>% 
  filter(udgift > mean(udgift, na.rm = TRUE)) %>% 
  sample_n(3)
## Source: local data frame [3 x 6]
## 
##       paragraf                         hovedomrode
##          (chr)                               (chr)
## 1 Folketinget            Udgifter ved Folketinget 
## 2 Folketinget            Udgifter ved Folketinget 
## 3 Folketinget  Statsrevisorerne og Rigsrevisionen 
## Variables not shown: aktivitet (chr), hovedkonto (chr), aar (int), udgift
##   (dbl)

Note how readable the code is. Almost like a grammer of data manipulation?

Grouped operations

So far, we have primarily learned how to manipulate data frames.

The dplyr package becomes really powerful when we introduce the group_by function

group_by breaks down a dataset into specified groups of rows. When you then apply the verbs above on the resulting object they’ll be automatically applied "by group".

Use in conjunction with mutate (to add existing rows to your data frame) or summarise (to create a new data frame)

Common mutate/summarise options

  • mean: mean within groups
  • sum: sum within groups
  • sd: standard deviation within groups
  • max: max within groups
  • n(): number in each group
  • first: first in group
  • last: last in group
  • nth(n = 3): nth in group (3rd here)

group_by in action I

Which ministry has the largest expenses?

df %>% filter(udgift >= 0) %>% group_by(paragraf) %>% 
  summarise(totale.udgifter = sum(udgift, na.rm = TRUE)) %>% 
  arrange(-totale.udgifter)
## Source: local data frame [28 x 2]
## 
##                                        paragraf totale.udgifter
##                                           (chr)           (dbl)
## 1                    Beskæftigelsesministeriet        1302463.4
## 2              Social- og Indenrigsministeriet        1232885.0
## 3        Uddannelses- og Forskningsministeriet         298811.5
## 4  Min. for Børn, Undervisning og Ligestilling         216024.7
## 5                            Finansministeriet         161103.4
## 6                              Pensionsvæsenet         139325.5
## 7                          Forsvarsministeriet         125691.9
## 8            Transport- og Bygningsministeriet         112422.0
## 9                Afdrag på statsgælden (netto)         104312.3
## 10                                      Renter         103464.2
## ..                                          ...             ...

group_by in action II

Add totale.udgifter to the existing data frame

df %>% filter(udgift >= 0) %>% group_by(paragraf) %>% 
  mutate(totale.udgifter = sum(udgift, na.rm = TRUE)) %>% 
  select(aar, udgift, totale.udgifter)
## Source: local data frame [7,609 x 4]
## Groups: paragraf [28]
## 
##                                 paragraf   aar udgift totale.udgifter
##                                    (chr) (int)  (dbl)           (dbl)
## 1                            Dronningen   2014   77.7           474.7
## 2  Medlemmer af det kongelige hus m.fl.   2014   26.4           161.2
## 3                           Folketinget   2014  387.8          6137.6
## 4                           Folketinget   2014  264.2          6137.6
## 5                           Folketinget   2014    6.5          6137.6
## 6                           Folketinget   2014    2.2          6137.6
## 7                           Folketinget   2014   63.3          6137.6
## 8                           Folketinget   2014    5.0          6137.6
## 9                           Folketinget   2014  202.5          6137.6
## 10                          Folketinget   2014   76.7          6137.6
## ..                                   ...   ...    ...             ...

group_by in action III

You can group by several variables

df %>% filter(udgift >= 0) %>% group_by(aar, paragraf) %>% 
  summarise(totale.udgifter = sum(udgift, na.rm = TRUE)) %>% 
  arrange(-totale.udgifter)
## Source: local data frame [168 x 3]
## Groups: aar [6]
## 
##      aar                                     paragraf totale.udgifter
##    (int)                                        (chr)           (dbl)
## 1   2014                   Beskæftigelsesministeriet         219491.3
## 2   2014             Social- og Indenrigsministeriet         203209.1
## 3   2014               Afdrag på statsgælden (netto)          68672.5
## 4   2014       Uddannelses- og Forskningsministeriet          49393.7
## 5   2014 Min. for Børn, Undervisning og Ligestilling          35775.8
## 6   2014                                      Renter          34449.0
## 7   2014                           Finansministeriet          26022.6
## 8   2014                             Pensionsvæsenet          22896.7
## 9   2014                         Forsvarsministeriet          20232.7
## 10  2014           Transport- og Bygningsministeriet          19597.0
## ..   ...                                          ...             ...

group_by in action IV

You can group by several variables

df %>% filter(udgift >= 0) %>% group_by(paragraf, hovedomrode) %>% 
  summarise(totale.udgifter = sum(udgift, na.rm = TRUE)) %>% 
  arrange(-totale.udgifter)
## Source: local data frame [120 x 3]
## Groups: paragraf [28]
## 
##                          paragraf
##                             (chr)
## 1  Afdrag på statsgælden (netto) 
## 2      Beholdningsbevægelser mv. 
## 3      Beholdningsbevægelser mv. 
## 4      Beholdningsbevægelser mv. 
## 5      Beskæftigelsesministeriet 
## 6      Beskæftigelsesministeriet 
## 7      Beskæftigelsesministeriet 
## 8      Beskæftigelsesministeriet 
## 9      Beskæftigelsesministeriet 
## 10     Beskæftigelsesministeriet 
## ..                            ...
## Variables not shown: hovedomrode (chr), totale.udgifter (dbl)

Merging data sets

Merging two data sets can be tricky and depends on your needs. It's important to think about what you want before joining.

Superhero example

https://stat545-ubc.github.io/bit001_dplyr-cheatsheet.html

superheroes = c("    name, alignment, gender,         publisher",
    " Magneto,       bad,   male,            Marvel",
    "   Storm,      good, female,            Marvel",
    "Mystique,       bad, female,            Marvel",
    "  Batman,      good,   male,                DC",
    "   Joker,       bad,   male,                DC",
    "Catwoman,       bad, female,                DC",
    " Hellboy,      good,   male, Dark Horse Comics")

superheroes = read.csv(text = superheroes, strip.white = TRUE)
head(superheroes)
##       name alignment gender publisher
## 1  Magneto       bad   male    Marvel
## 2    Storm      good female    Marvel
## 3 Mystique       bad female    Marvel
## 4   Batman      good   male        DC
## 5    Joker       bad   male        DC
## 6 Catwoman       bad female        DC

Publishers

publishers = c("publisher, yr_founded",
    "       DC,       1934",
    "   Marvel,       1939",
    "    Image,       1992")
publishers = read.csv(text = publishers, strip.white = TRUE)
head(publishers)
##   publisher yr_founded
## 1        DC       1934
## 2    Marvel       1939
## 3     Image       1992

Inner join

ijsp = inner_join(superheroes, publishers)
## Joining by: "publisher"
## Warning in inner_join_impl(x, y, by$x, by$y): joining factors with
## different levels, coercing to character vector
ijsp
##       name alignment gender publisher yr_founded
## 1  Magneto       bad   male    Marvel       1939
## 2    Storm      good female    Marvel       1939
## 3 Mystique       bad female    Marvel       1939
## 4   Batman      good   male        DC       1934
## 5    Joker       bad   male        DC       1934
## 6 Catwoman       bad female        DC       1934

Left join

ljsp = left_join(superheroes, publishers)
## Joining by: "publisher"
## Warning in left_join_impl(x, y, by$x, by$y): joining factors with different
## levels, coercing to character vector
ljsp
##       name alignment gender         publisher yr_founded
## 1  Magneto       bad   male            Marvel       1939
## 2    Storm      good female            Marvel       1939
## 3 Mystique       bad female            Marvel       1939
## 4   Batman      good   male                DC       1934
## 5    Joker       bad   male                DC       1934
## 6 Catwoman       bad female                DC       1934
## 7  Hellboy      good   male Dark Horse Comics         NA

Merging different names

superheroes = mutate(superheroes,
                seblikes = (publisher=="Marvel"))
publishers = mutate(publishers,
                    seb = (publisher == "Marvel"))
ij2 = inner_join(superheroes,publishers)
## Joining by: "publisher"
## Warning in inner_join_impl(x, y, by$x, by$y): joining factors with
## different levels, coercing to character vector
ij2
##       name alignment gender publisher seblikes yr_founded   seb
## 1  Magneto       bad   male    Marvel     TRUE       1939  TRUE
## 2    Storm      good female    Marvel     TRUE       1939  TRUE
## 3 Mystique       bad female    Marvel     TRUE       1939  TRUE
## 4   Batman      good   male        DC    FALSE       1934 FALSE
## 5    Joker       bad   male        DC    FALSE       1934 FALSE
## 6 Catwoman       bad female        DC    FALSE       1934 FALSE

Merging different names

ij2 = inner_join(superheroes,publishers,
                    by=c("publisher"="publisher",
                            "seblikes"="seb"))
## Warning in inner_join_impl(x, y, by$x, by$y): joining factors with
## different levels, coercing to character vector
ij2
##       name alignment gender publisher seblikes yr_founded
## 1  Magneto       bad   male    Marvel     TRUE       1939
## 2    Storm      good female    Marvel     TRUE       1939
## 3 Mystique       bad female    Marvel     TRUE       1939
## 4   Batman      good   male        DC    FALSE       1934
## 5    Joker       bad   male        DC    FALSE       1934
## 6 Catwoman       bad female        DC    FALSE       1934

Strings

the strsplit function

Split the elements of a character vector x into substrings according to the matches to substring split within them.

strsplit(names(df), "t")
## [[1]]
## [1] "paragraf"
## 
## [[2]]
## [1] "hovedomrode"
## 
## [[3]]
## [1] "ak"  "ivi" "e"  
## 
## [[4]]
## [1] "hovedkon" "o"       
## 
## [[5]]
## [1] "aar"
## 
## [[6]]
## [1] "udgif"

the gsub function

gsub("t", " ", names(df))
## [1] "paragraf"    "hovedomrode" "ak ivi e "   "hovedkon o"  "aar"        
## [6] "udgif "

the substr function

Extract or replace substrings in a character vector.

substr(names(df), 1, 3)
## [1] "par" "hov" "akt" "hov" "aar" "udg"

the grep function

Regular expression matching

grep("t", names(df))
## [1] 3 4 6
grepl("t", names(df))
## [1] FALSE FALSE  TRUE  TRUE FALSE  TRUE

tolower and toupper

tolower(names(df))
## [1] "paragraf"    "hovedomrode" "aktivitet"   "hovedkonto"  "aar"        
## [6] "udgift"
toupper(names(df))
## [1] "PARAGRAF"    "HOVEDOMRODE" "AKTIVITET"   "HOVEDKONTO"  "AAR"        
## [6] "UDGIFT"

The stringr package

library("stringr")
str_detect(names(df), "t")
## [1] FALSE FALSE  TRUE  TRUE FALSE  TRUE
str_subset(names(df), "t")
## [1] "aktivitet"  "hovedkonto" "udgift"

Useful functions

str_trim("Sebastian           ")
## [1] "Sebastian"
paste0("Sebastian", "Barfort")
## [1] "SebastianBarfort"
nchar("SebastianBarfort")
## [1] 16

Next

Tidy data

Regular expressions