Introduction

This code through is designed to inform the reader about data wrangling with the R package ‘dplyr’. We will explore ways to clean, structure, and prepare a dataset for analysis.


Content Overview

Specifically, we’ll be using a dataset sourced from Kaggle. This dataset is comprised of the behavior of around 40,000 unique players.

Why You Should Care

This topic is valuable because before you can make valuable insights using data, you must understand how to structure and prepare the dataset for analysis. Raw data is messy and majority of the time, a dataset will need to be cleaned before you move forward. These functions within dplyr can be used with any dataset you would like to explore.


Learning Objectives

Specifically, we’ll be learning how to use the following functions:

  • The pipe operator: %>%
  • Useful verbs: mutate(), select(), filter(), arrange()
  • Grouping with group_by, summarise(), and count()
  • Using case_when() and if_else()
  • Joining two tables using left_join() `

Data Wrangling with dplyr

Here, we’ll showcase the core verbs of dplyr, explain how they work, and combine them to accomplish our goal of data wrangling. We will begin by loading in our data and taking a glimpse of what it looks like.

Note: Please save the csv in the same folder as the .Rmd file if you’d like to follow along

# Read in the data

gaming <- read.csv("online_gaming_behavior_insights.csv")

# Take a glimpse

glimpse(gaming)
## Rows: 40,034
## Columns: 13
## $ PlayerID                  <int> 9000, 9001, 9002, 9003, 9004, 9005, 9006, 90…
## $ Age                       <int> 43, 29, 22, 35, 33, 37, 25, 25, 38, 38, 17, …
## $ Gender                    <chr> "Male", "Female", "Female", "Male", "Male", …
## $ Location                  <chr> "Other", "USA", "USA", "USA", "Europe", "Eur…
## $ GameGenre                 <chr> "Strategy", "Strategy", "Sports", "Action", …
## $ PlayTimeHours             <dbl> 16.271119, 5.525961, 8.223755, 5.265351, 15.…
## $ InGamePurchases           <int> 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,…
## $ GameDifficulty            <chr> "Medium", "Medium", "Easy", "Easy", "Medium"…
## $ SessionsPerWeek           <int> 6, 5, 16, 9, 2, 2, 1, 10, 5, 13, 8, 16, 9, 0…
## $ AvgSessionDurationMinutes <int> 108, 144, 142, 85, 131, 81, 50, 48, 101, 95,…
## $ PlayerLevel               <int> 79, 11, 35, 57, 95, 74, 13, 27, 23, 99, 14, …
## $ AchievementsUnlocked      <int> 25, 10, 41, 47, 37, 22, 2, 23, 41, 36, 12, 3…
## $ EngagementLevel           <chr> "Medium", "Medium", "High", "Medium", "Mediu…

From this output, we see there are 40,034 players, and 13 variables. I will list the variables we will be focusing on below:

Variable Description
PlayerID Unique ID for individual player
Age Age of player
GameGenre Game genre played in session
InGamePurchases Did the player make an in-game purchase?
SessionsPerWeek Number of sessions played per week
AvgSessionDurationMinutes Average length of session in minutes
PlayerLevel Player level in-game
Engagement Level Level of engagement


Further Exposition

Wickham and other authors describe dplyr as a grammar of data manipulation. Dplyr provides a few verbs that can be used to tackle most of your data manipulation needs.

Verb Description
select() Select variables
filter() Filter cases based on a condition
arrange() Sort rows
mutate() Create or modify columns
group_by() Group data together
summarise() Takes multiple values and transforms into a single summary


The Pipe Operator

In addition to dplyr’s common verbs, the pipe operator can be called with “%>%” or “|>”. The pipe operator allows to create an easy to read pipeline and can be read as “next”.


Basic Example

A basic example shows hows the common verbs can work together to derive insights from our data. In this case, we will use dplyr’s verbs to see players with the highest amount of hours per week. Using mutate(), we can create a new variable called WeeklyHours, which will assist us with our inquiry.

most_hours <- gaming %>%
  select(PlayerID, Age, GameGenre, SessionsPerWeek, AvgSessionDurationMinutes) %>% 
  filter(SessionsPerWeek > 0) %>%  # filter to exclude players without any sessions
  mutate(WeeklyHours = round(SessionsPerWeek * AvgSessionDurationMinutes /60, 1)) %>% 
  arrange(desc(WeeklyHours)) # ordering by most hours played
 
most_hours %>%
  head(10) %>%
  kable()
PlayerID Age GameGenre SessionsPerWeek AvgSessionDurationMinutes WeeklyHours
11738 36 Strategy 19 179 56.7
12832 22 Simulation 19 179 56.7
27340 25 Strategy 19 179 56.7
30531 31 Simulation 19 179 56.7
36344 16 RPG 19 179 56.7
41318 33 Strategy 19 179 56.7
45233 48 RPG 19 179 56.7
12443 18 Sports 19 178 56.4
17032 18 Simulation 19 178 56.4
21404 31 Simulation 19 178 56.4

Note: select() allowed us to select which variables we want to keep and we created a new variable to caclulate hours per week by using mutate()

glimpse(most_hours)
## Rows: 38,067
## Columns: 6
## $ PlayerID                  <int> 11738, 12832, 27340, 30531, 36344, 41318, 45…
## $ Age                       <int> 36, 22, 25, 31, 16, 33, 48, 18, 18, 31, 28, …
## $ GameGenre                 <chr> "Strategy", "Simulation", "Strategy", "Simul…
## $ SessionsPerWeek           <int> 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, …
## $ AvgSessionDurationMinutes <int> 179, 179, 179, 179, 179, 179, 179, 178, 178,…
## $ WeeklyHours               <dbl> 56.7, 56.7, 56.7, 56.7, 56.7, 56.7, 56.7, 56…

Our initial dataset had 40,034 players, however, after removing players with zero gaming sessions per week, we are left with 38,067 players. The top 10 players with most hours played come in at around 56 hours and 19 gaming sessions within a week. Our raw data remains unchanged, however the dataframe most_hours, is now an object we can manipulate to our liking.


Advanced Examples

More specifically, we can use these verbs to compare groups, categorize our data, and combine multiple data sources. We will explore engagement in our next few examples.

Grouping with group_by()

engagement <- gaming %>%
  group_by(EngagementLevel) %>%   # grouping output on engagement level variable
  summarise(Players = n(),        # counts number of rows
            AvgSessionsPerWeek = round(mean(SessionsPerWeek), 1), # avg sessions per week
            AvgSessionMinutes = round(mean(AvgSessionDurationMinutes), 1), # avg minutes per week
            PurchaseRate = round(mean(InGamePurchases) * 100, 1)) %>% # avg purchase rate 
  arrange(AvgSessionsPerWeek) # orders by avg sessions per week, lowest to highest

engagement %>% 
  kable()
EngagementLevel Players AvgSessionsPerWeek AvgSessionMinutes PurchaseRate
Low 10324 4.5 66.9 19.7
Medium 19374 9.6 89.9 20.0
High 10336 14.3 131.9 20.6

Upon closer inspection, we see that a majority of the players have a medium engagement level. Additionally, there is not a large difference in purchase rate between the three engagement levels.


Categorizing using case_when() and if_else()

What’s more, case_when() and if_else() can be used to transform data into categories based on values. We will use if_else() for values we want to aggregate into two categories, and case_when() for values we want to aggregate for three or more categories.

purchase <- gaming %>%
  mutate(PurchaseStatus = if_else(InGamePurchases == 1, "Made Purchase", "No Purchase"), # categorizing values by purchase status
         AgeGroup = case_when(Age < 20 ~ "15-19",
                              Age < 30 ~ "21-29",
                              Age < 40 ~ "31-39",
                              Age < 50 ~ "41-49"))

purchase %>%
  select(PlayerID, InGamePurchases, PurchaseStatus, Age, AgeGroup) %>%
  head(10) %>%
  kable()
PlayerID InGamePurchases PurchaseStatus Age AgeGroup
9000 0 No Purchase 43 41-49
9001 0 No Purchase 29 21-29
9002 0 No Purchase 22 21-29
9003 1 Made Purchase 35 31-39
9004 0 No Purchase 33 31-39
9005 0 No Purchase 37 31-39
9006 0 No Purchase 25 21-29
9007 0 No Purchase 25 21-29
9008 0 No Purchase 38 31-39
9009 0 No Purchase 38 31-39

We were able to create age groups and a purchase status by utilizing the case_when() and if_else() functions.

purchase %>%
  group_by(AgeGroup) %>%
  summarise(Players = n(),
            PurchaseRate = round(mean(PurchaseStatus == "Made Purchase") * 100, 1),
            AvgSessionsPerWeek = round(mean(SessionsPerWeek), 1)) %>%
  kable()
AgeGroup Players PurchaseRate AvgSessionsPerWeek
15-19 5694 20.4 9.5
21-29 11401 20.0 9.4
31-39 11559 19.8 9.5
41-49 11380 20.3 9.5

We created a variable using mutate(), categorized the values using case_when() and if_else(), and then used group_by() to aggregate our data on our newly created variable.


Joining tables with left_join()

Most notably, left_join() can combine data from multiple tables. There will be times where we can find value in combining data sources. We will create a dummy lookup table with genre types to assist with this example.

GenreLookup <- tibble(
  GameGenre = c("Action", "RPG", "Simulation", "Sports", "Strategy"),
  GenreType = c("Fast-Paced", "MMO", "Simulator", "Competitive", "Competitive")
)

GenreLookup %>%
  kable()
GameGenre GenreType
Action Fast-Paced
RPG MMO
Simulation Simulator
Sports Competitive
Strategy Competitive

Next, we will utilize left_join(), which will keep all the data from our left table, and match it to the right table.

Joined <- gaming %>%
  select(PlayerID, GameGenre, PlayerLevel, InGamePurchases) %>%
  left_join(GenreLookup, by = "GameGenre") # the variable we are matching the two tables on

Joined %>%
  head(10) %>%
  kable()
PlayerID GameGenre PlayerLevel InGamePurchases GenreType
9000 Strategy 79 0 Competitive
9001 Strategy 11 0 Competitive
9002 Sports 35 0 Competitive
9003 Action 57 1 Fast-Paced
9004 Action 95 0 Fast-Paced
9005 RPG 74 0 MMO
9006 Action 13 0 Fast-Paced
9007 RPG 27 0 MMO
9008 Simulation 23 0 Simulator
9009 Sports 99 0 Competitive

Notice the by argument within the left_join function, we were able to join both tables on the variable GameGenre. Now, each player has an associated genre type. We will now summarize our data on genre type instead of genre:

Joined %>%
  group_by(GenreType) %>%
  summarise(Players = n(),
            AvgLevel = round(mean(PlayerLevel), 1),
            PurchaseRate = round(mean(InGamePurchases) * 100, 1)) %>%
  arrange(desc(Players)) %>% 
  kable()
GenreType Players AvgLevel PurchaseRate
Competitive 16060 49.9 20.6
Fast-Paced 8039 49.8 19.3
Simulator 7983 49.2 20.2
MMO 7952 49.4 19.8

We see that “Competitive” has the largest amount of players, but we did group both “Sports” and “Strategy” game genres within the same genre type. After using all of the common verbs of dplyr, we were able to create new categories and variables, join tables, and learn techniques for future data manipulation.



Further Resources

Learn more about dplyr, data manipulation, data transformation, and R markdown with the following:




Works Cited

This code through references and cites the following sources: