Wrangling Lab

S22 · Chapter 11 · MC 451 Research Methods in Mass Media

Dr. Alex Leith

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

Questions?