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.
Specifically, we’ll be using a dataset sourced from Kaggle. This
dataset is comprised of the behavior of around 40,000 unique players.
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.
Specifically, we’ll be learning how to use the following functions:
%>%mutate(), select(),
filter(), arrange()group_by, summarise(), and
count()case_when() and if_else()left_join() ` 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 |
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 |
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”.
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()
## 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.
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.
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.
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.
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.
Learn more about dplyr, data manipulation, data transformation, and R markdown with the following:
Resource I dplyr Data Transformation Cheat Sheet
Resource II A Grammar of Data Manipulation - dplyr
Resource III Data Transformation - R for2e)
Resource III R Markdown Cheatsheet
This code through references and cites the following sources:
WasiqALiYasir. (2025). [Online Gaming Behavior Dataset.Kaggle] https://doi.org/10.34740/KAGGLE/DSV/14228009
Wickham H, François R, Henry L, Müller K, Vaughan D (2026). dplyr: A Grammar of Data Manipulation. R package version 1.2.1, https://dplyr.tidyverse.org.
Wickham, H., Çetinkaya-Rundel, M., & Grolemund, G. (2023). R for data science (2nd ed.). O’Reilly Media. https://r4ds.hadley.nz/
Xie, Y., Allaire, J. J., & Grolemund, G. (2018). R Markdown: The Definitive Guide. (https://pkg.yihui.org/rmarkdown-book/)