Factors

Defining Categorical Types

Rodney Dyer, PhD

Topics Covered

In this brief presentation, we’ll be introducing the following items:

  • Factor Data Types
  • The library
  • Workflows & Pipes
  • Tables

Categorical Data

Categorical Data Types

 

Unique and individual grouping that can be applied to a study design.

  • Case sensitive
  • Can be ordinal
  • Typically defined as character type
weekdays <- c("Monday","Tuesday","Wednesday",
              "Thursday","Friday","Saturday", 
              "Sunday")
class( weekdays )
[1] "character"
weekdays
[1] "Monday"    "Tuesday"   "Wednesday" "Thursday"  "Friday"    "Saturday" 
[7] "Sunday"   

Making Up Data 🤷🏻‍

The function sample() allows us to take a random sample of elements from a vector of potential values.

chooseOne <- sample( c("Heads","Tails"), size=1 )
chooseOne
[1] "Tails"

Making Up More Data 🤷🏻‍

However, if we want a large number items, we can have them with or without replacement.

sample( c("Heads","Tails"), size=10, replace=TRUE )
 [1] "Heads" "Tails" "Heads" "Heads" "Heads" "Heads" "Tails" "Tails" "Tails"
[10] "Heads"

Weekdays as example

We’ll pretend we have a bunch of data related to the day of the week.

days <- sample( weekdays, size=40, replace=TRUE)
summary( days )
   Length     Class      Mode 
       40 character character 
days
 [1] "Thursday"  "Sunday"    "Thursday"  "Monday"    "Sunday"    "Thursday" 
 [7] "Wednesday" "Thursday"  "Thursday"  "Thursday"  "Thursday"  "Monday"   
[13] "Tuesday"   "Tuesday"   "Friday"    "Wednesday" "Friday"    "Sunday"   
[19] "Monday"    "Saturday"  "Monday"    "Monday"    "Monday"    "Thursday" 
[25] "Thursday"  "Sunday"    "Tuesday"   "Friday"    "Saturday"  "Tuesday"  
[31] "Thursday"  "Sunday"    "Sunday"    "Monday"    "Sunday"    "Friday"   
[37] "Monday"    "Monday"    "Friday"    "Saturday" 

Turn it into a factor

data <- factor( days )
is.factor( data )
[1] TRUE
class( data )
[1] "factor"

Data Type Specific Printing & Summaries

data
 [1] Thursday  Sunday    Thursday  Monday    Sunday    Thursday  Wednesday
 [8] Thursday  Thursday  Thursday  Thursday  Monday    Tuesday   Tuesday  
[15] Friday    Wednesday Friday    Sunday    Monday    Saturday  Monday   
[22] Monday    Monday    Thursday  Thursday  Sunday    Tuesday   Friday   
[29] Saturday  Tuesday   Thursday  Sunday    Sunday    Monday    Sunday   
[36] Friday    Monday    Monday    Friday    Saturday 
Levels: Friday Monday Saturday Sunday Thursday Tuesday Wednesday

Factor Levels

Each factor variable is defined by the levels that constitute the data. This is a .red[finite] set of unique values

levels( data)
[1] "Friday"    "Monday"    "Saturday"  "Sunday"    "Thursday"  "Tuesday"  
[7] "Wednesday"

Ordinal Categorical Data

Factor Ordination

If a factor is not ordinal, it does nota allow the use relational comparison operators.

data[1] < data[2]
Warning in Ops.factor(data[1], data[2]): '<' not meaningful for factors
[1] NA

Ordination = Ordered

is.ordered( data )
[1] FALSE

Ordination of Factors

Where ordination matters:

  • Fertilizer Treatments in KG of N2 per hectare: 10 kg N2, 20 N2, 30 N2,

  • Days of the Week: Friday is not followed by Monday,

  • Life History Stage: seed, seedling, juvenile, adult, etc.

Where ordination is irrelevant:

  • River

  • State or Region

  • Sample Location

Making Ordered Factors

data <- factor( days, ordered = TRUE)
is.ordered( data )
[1] TRUE
data
 [1] Thursday  Sunday    Thursday  Monday    Sunday    Thursday  Wednesday
 [8] Thursday  Thursday  Thursday  Thursday  Monday    Tuesday   Tuesday  
[15] Friday    Wednesday Friday    Sunday    Monday    Saturday  Monday   
[22] Monday    Monday    Thursday  Thursday  Sunday    Tuesday   Friday   
[29] Saturday  Tuesday   Thursday  Sunday    Sunday    Monday    Sunday   
[36] Friday    Monday    Monday    Friday    Saturday 
7 Levels: Friday < Monday < Saturday < Sunday < Thursday < ... < Wednesday

The problem is that the default ordering is actually alphabetical!

Specifying the Order

Specifying the Order of Ordinal Factors

data <- factor( days, ordered = TRUE, levels = weekdays)
data
 [1] Thursday  Sunday    Thursday  Monday    Sunday    Thursday  Wednesday
 [8] Thursday  Thursday  Thursday  Thursday  Monday    Tuesday   Tuesday  
[15] Friday    Wednesday Friday    Sunday    Monday    Saturday  Monday   
[22] Monday    Monday    Thursday  Thursday  Sunday    Tuesday   Friday   
[29] Saturday  Tuesday   Thursday  Sunday    Sunday    Monday    Sunday   
[36] Friday    Monday    Monday    Friday    Saturday 
7 Levels: Monday < Tuesday < Wednesday < Thursday < Friday < ... < Sunday

Sorting Is Now Relevant

sort( data )
 [1] Monday    Monday    Monday    Monday    Monday    Monday    Monday   
 [8] Monday    Monday    Tuesday   Tuesday   Tuesday   Tuesday   Wednesday
[15] Wednesday Thursday  Thursday  Thursday  Thursday  Thursday  Thursday 
[22] Thursday  Thursday  Thursday  Thursday  Friday    Friday    Friday   
[29] Friday    Friday    Saturday  Saturday  Saturday  Sunday    Sunday   
[36] Sunday    Sunday    Sunday    Sunday    Sunday   
7 Levels: Monday < Tuesday < Wednesday < Thursday < Friday < ... < Sunday

Fixed Set of Levels

You cannot assign a value to a factor that is not one of the pre-defined levels.

data[3] <- "Bob"
Warning in `[<-.factor`(`*tmp*`, 3, value = "Bob"): invalid factor level, NA
generated

forcats

The forcats library

Part of the tidyverse group of packages.

library( tidyverse )
library(forcats)

This library has a lot of helper functions that make working with factors a bit easier. I’m going to give you a few examples here but strongly encourage you to look a the cheat sheet for all the other options.

StarWars API

There is a StarWars API at https://swapi.py4e.com, see ?starwars to learn more about the data it contains. Let’s take this data to play with the library.

 

names(starwars)
 [1] "name"       "height"     "mass"       "hair_color" "skin_color"
 [6] "eye_color"  "birth_year" "sex"        "gender"     "homeworld" 
[11] "species"    "films"      "vehicles"   "starships" 

Homeworld as Factor

starwars |>
  filter( !is.na(homeworld), !is.na(mass) ) |>
  mutate( homeworld = factor( homeworld ) ) -> df
df$homeworld
 [1] Tatooine       Tatooine       Naboo          Tatooine       Alderaan      
 [6] Tatooine       Tatooine       Tatooine       Tatooine       Stewjon       
[11] Tatooine       Kashyyyk       Corellia       Rodia          Nal Hutta     
[16] Corellia       Bestine IV     Naboo          Kamino         Trandosha     
[21] Socorro        Bespin         Mon Cala       Endor          Sullust       
[26] Cato Neimoidia Naboo          Naboo          Naboo          Malastare     
[31] Dathomir       Ryloth         Aleen Minor    Vulpter        Tund          
[36] Haruun Kal     Cerea          Glee Anselm    Coruscant      Dorin         
[41] Naboo          Geonosis       Mirial         Mirial         Serenno       
[46] Concord Dawn   Zolan          Ojom           Kamino         Skako         
[51] Shili          Kalee          Kashyyyk       Alderaan       Umbara        
[56] Utapau        
39 Levels: Alderaan Aleen Minor Bespin Bestine IV Cato Neimoidia ... Zolan

Homeworld as Ordered

starwars |>
  filter( !is.na(homeworld), !is.na(mass) ) |>
  mutate( homeworld = factor( homeworld, ordered=TRUE ) ) -> df
df$homeworld
 [1] Tatooine       Tatooine       Naboo          Tatooine       Alderaan      
 [6] Tatooine       Tatooine       Tatooine       Tatooine       Stewjon       
[11] Tatooine       Kashyyyk       Corellia       Rodia          Nal Hutta     
[16] Corellia       Bestine IV     Naboo          Kamino         Trandosha     
[21] Socorro        Bespin         Mon Cala       Endor          Sullust       
[26] Cato Neimoidia Naboo          Naboo          Naboo          Malastare     
[31] Dathomir       Ryloth         Aleen Minor    Vulpter        Tund          
[36] Haruun Kal     Cerea          Glee Anselm    Coruscant      Dorin         
[41] Naboo          Geonosis       Mirial         Mirial         Serenno       
[46] Concord Dawn   Zolan          Ojom           Kamino         Skako         
[51] Shili          Kalee          Kashyyyk       Alderaan       Umbara        
[56] Utapau        
39 Levels: Alderaan < Aleen Minor < Bespin < Bestine IV < ... < Zolan

Visualizing Counts of Levels

df |> 
  ggplot( aes(x=homeworld) ) + 
  geom_bar() + 
  coord_flip()

Occurrence Based Order In data.frame

df |> 
  mutate( homeworld = fct_inorder( homeworld ) ) |> 
  ggplot( aes(x=homeworld) ) + 
  geom_bar() + 
  coord_flip()

Reordering By Other Valiable

df |> 
  mutate( homeworld = fct_reorder( homeworld, mass )) |> 
  ggplot( aes(x=homeworld) ) + 
  geom_bar() + 
  coord_flip()

Frequency Based Ordering

df |> 
  mutate( homeworld = fct_infreq( homeworld ) ) |> 
  ggplot( aes(x=homeworld) ) + 
  geom_bar() + 
  coord_flip()

Reversing Orders

df |> 
  mutate( homeworld = fct_rev( fct_infreq( homeworld ) ) ) |> 
  ggplot( aes(x=homeworld) ) + 
  geom_bar() + 
  coord_flip()

Recoding Factors

New Value = Old Value

starwars |> 
  filter( !is.na(homeworld) ) |>
  mutate( homeworld = fct_recode(homeworld, 
                                 "Homeworld of Zolander" = "Zolan",
                                 "Outter Rim Territory Tund" = "Tund"
  ) ) |>
  ggplot( aes(x=homeworld) ) + 
  geom_bar() + 
  coord_flip()

Collapsing A Few Selected Levels

starwars |>
  filter( !is.na(homeworld) ) |>
  mutate( homeworld = fct_collapse( homeworld, 
                                    "<---- MEH ---->" = c("Bestine IV","Cerea", "Dorin","Miral", "Sullust"),
                                    \\_(ツ)_/¯" = c("Umbara","Kashyyyk","Concord Dawn"),
                                    "YES YES YES YES YSE YSE " = c("Nal Hutta","Ojom","Rodia","Ryloth","Serenno","Shili","Skako","Socorro")
  )) |>
  ggplot( aes(x=homeworld) ) + 
  geom_bar() + 
  coord_flip()

Lumping Based On Counts

starwars |> 
  filter( !is.na(homeworld) ) |>
  mutate( homeworld = fct_lump_min(homeworld, 2)) |>
  ggplot( aes(x=homeworld) ) + 
  geom_bar() + 
  coord_flip()

Lumping into N Groups

starwars |>
  filter( !is.na(homeworld) ) |>
  mutate( homeworld = fct_lump_n(homeworld, n=5)) |>
  ggplot( aes(x=homeworld) ) + 
  geom_bar() + 
  coord_flip()

Counting Factors & Occurances

starwars |> 
  filter( !is.na(homeworld) ) |> 
  group_by( homeworld ) |> 
  summarize( film = length(unique(films) ) ) |> 
  arrange( -film )
# A tibble: 48 × 2
   homeworld    film
   <chr>       <int>
 1 Tatooine        8
 2 Naboo           6
 3 Alderaan        3
 4 Coruscant       3
 5 Corellia        2
 6 Kamino          2
 7 Kashyyyk        2
 8 Mirial          2
 9 Ryloth          2
10 Aleen Minor     1
# ℹ 38 more rows

Anonymizing Factor Levels

starwars |>
  filter( !is.na(homeworld) ) |> 
  mutate( homeworld = fct_anon( fct_lump( homeworld, n=6 ) ) ) |>
  ggplot( aes(x=homeworld) ) + 
  geom_bar() + 
  coord_flip()

Questions

If you have any questions, please feel free to either post them as an “Issue” on your copy of this GitHub Repository, post to the Canvas discussion board for the class, or drop me an email.

Peter Sellers looking bored