Wrangling Lab
S22 · Chapter 11 · MC 451 Research Methods in Mass Media
Today’s Agenda
The gaming label, built from the channel up
Joining two tables on a shared key
Inspecting what the join leaves behind
One pipeline that rebuilds everything
Finishing the Wrangle
Lab session
Tuesday: readable timestamps and message length
Today: the transformation the question turns on
The gaming label, the join, and what it leaves
Then one pipeline you can rerun
Open Tuesday’s script, and we continue from the last line
What Is Still Missing
timestamp exists, message_length exists
Nothing says whether a message came from a gaming channel
That sits in the stream table, which chat cannot see
So your central comparison has one side undefined
Joins go wrong for everyone the first few times, and they go wrong quietly.
Gaming Is a Channel Property
It is fixed by what the channel mostly streams
Bob Ross streams painting, so every message is non-gaming
So we classify channels, then attach the label
Find what each channel mostly streams
Decide whether that counts as gaming
Attach the decision to every message
Each step is one small pipeline.
Step 1, the Modal Category
channel_type <- streams %>%
filter (! is.na (game)) %>%
count (channel, game) %>%
group_by (channel) %>%
slice_max (n, n = 1 , with_ties = FALSE ) %>%
ungroup ()
Read it in order: drop snapshots with no category, count how many each channel logged in each category, then keep each channel’s most frequent one.
is.na() finds missing values and ! means “not”. The result is one row per channel.
Your Turn
Predict: what happens to a channel whose snapshots all lack a game?
Trace it through filter() and count()
Does that channel appear in channel_type at all?
Later, should R guess, drop, or flag its messages?
Step 2, Naming the Non-Gaming
nongaming_categories <- c (
"Art" , "ASMR" , "Beauty & Body Art" , "Creative" , "Food & Drink" ,
"IRL" , "Just Chatting" , "Makers & Crafting" , "Music" ,
"Music & Performing Arts" , "Science & Technology" ,
"Sports & Fitness" , "Talk Shows & Podcasts" , "Travel & Outdoors"
)
c() builds a list, here every category treated as not a game.
This is a judgment , so it belongs in the code, not buried in prose.
Applying the Rule
channel_type <- channel_type %>%
mutate (is_gaming = ! (game %in% nongaming_categories)) %>%
select (channel, is_gaming)
%in% asks whether a channel’s dominant category is on the non-gaming list and ! flips it, so is_gaming is TRUE for game channels.
channel_type is now a lookup: one row per channel, one column.
Step 3, the Join
chat <- chat %>%
left_join (channel_type, by = "channel" )
A join brings columns from one table onto another by matching a shared key, and left_join() keeps every row of chat while attaching each channel’s is_gaming.
by = "channel" names the key both tables share.
Joins Fail on Keys
A join is only as good as its key match
Twitch’s chat protocol writes names with a leading “#”
So #bobross in one source, bobross in another
Keys that disagree fail every row, silently
The v2v data is normalized; raw Twitch data would not be
Always Inspect What You Derived
chat %>% count (is_gaming)
is_gaming n
1 FALSE 3457
2 TRUE 31309
3 NA 501
count() on a new column is the cheapest check there is, and here it turns up a third value nobody asked for.
The 501 Messages
Two channels never had a category recorded
Every snapshot had a missing game, so no modal category
They never entered channel_type, so the join left NA
That is the correct outcome, not a bug
The data genuinely does not say, so those messages sit the comparison out.
The Whole Thing, One Block
chat <- v2v:: twitch_chat () %>%
mutate (
timestamp = as.POSIXct (date / 1000 , origin = "1970-01-01" , tz = "UTC" ),
message_length = str_length (message)
) %>%
left_join (channel_type, by = "channel" )
Four transformations in one pipeline, starting from the raw data every time, so the table rebuilds from scratch in one run.
What the Table Has Become
Rows: 35,267
Columns: 8
$ channel <chr> "sodapoppin", "xqcow", "forsen", …
$ message <chr> "???????????????????????", …
$ date <dbl> 1542578127023, 1542578135562, …
$ timestamp <dttm> 2018-11-18 21:55:27, 2018-11-18 21:55:35, …
$ message_length <int> 23, 441, 8, 12, 206, …
$ is_gaming <lgl> TRUE, FALSE, TRUE, TRUE, FALSE, …
The table is now tidy : one row per observation, one column per variable.
Common Errors Today
Join returns all NA: mismatched keys, so check for “#”
object 'channel_type' not found: you joined before building it
slice_max errors: you skipped group_by()
A misspelled category silently mislabels a channel
Rerun in a fresh session , or something is undocumented
Run ?v2v::common_errors for the ones specific to this data.
Checkpoint
You should now have:
A channel_type lookup, one row per channel, with is_gaming
A chat table of eight columns, including all three derived
A count(is_gaming) result you have actually looked at
A script that rebuilds all of it from raw data in one run
Before Next Time
Rerun from a clean session and confirm it reproduces
Comment your wrangling decisions
A reviewer will ask why you excluded what you excluded
Due this week: Sampling Plan and Pilot, and Data Wrangling
Read Chapter 12, Describing the Data