Public Bluesky posts were scraped with the atrrr package
using four queries: “Becerra”, “Xavier Becerra”, “Steve Hilton”, and
“Hilton governor” (200 posts per query, pulled October 29, 2026). All
results are reported in aggregate only. No usernames are shown.
| step | remaining | removed |
|---|---|---|
| Raw scrape | 806 | NA |
| Remove empty/NA text | 789 | 17 |
| Remove duplicate text | 731 | 58 |
| Remove non-English posts | 729 | 2 |
| Text names the candidate | 596 | 133 |
| Remove hotel / Paris Hilton noise | 593 | 3 |
| Require political context | 551 | 42 |
| Remove sports Becerra noise | 550 | 1 |
| Remove posts mentioning both candidates | 361 | 189 |
| Remove posts under 3 words | 359 | 2 |
Other issues noted: near-duplicate copy-pasted posts, a few accounts posting repeatedly, non-English posts, wrong-person matches (a boxer named Becerra, Newsom posts), and all posts coming from roughly one day.
I am analyzing public Bluesky posts to forecast the 2026 California governor’s race (Xavier Becerra, Democrat, vs. Steve Hilton, Republican). I have a cleaned CSV called “Governor_clean.csv” with 359 rows and these columns: text, author_handle, indexed_at, created_at, like_count, repost_count, reply_count, candidate (“Becerra” or “Hilton”), query, and text_clean (post text with URLs and @mentions removed). Each row is one post about one candidate.
Write R code that scores each post’s sentiment with tidytext and the Bing lexicon, then summarizes the results by candidate.
Method: add a post_id, tokenize text_clean into words, inner join with the Bing lexicon, and count positive and negative words per post. Net score = positive words minus negative words. Label each post positive (net > 0), negative (net < 0), or neutral (net = 0 or no lexicon words). Then summarize by candidate: number of posts, number of positive/negative/neutral posts, positive share (positive / (positive + negative)), mean net score, and volume share (candidate posts / all posts).
Constraints: use the pipe %>% (not |>), use function() instead of backslash lambdas, and make the code copy-paste friendly. Output only aggregate results and never print usernames. Flag any words in the Bing lexicon that could be misleading in a political context (for example “trump” is coded positive) and remove them with a custom list. Keep the code beginner-friendly with short comments. Save the scored posts to “Governor_scored.csv” without the author_handle column.
| candidate | posts | positive | negative | neutral | positive_share | mean_net | volume_share |
|---|---|---|---|---|---|---|---|
| Becerra | 137 | 38 | 32 | 67 | 0.543 | 0.131 | 0.382 |
| Hilton | 222 | 60 | 68 | 94 | 0.469 | -0.135 | 0.618 |
A random sample of 20 posts (seed 123) was labeled for stance toward the named candidate. These labels were generated with AI assistance (Claude), not by a human coder, and were then compared with the lexicon’s labels.
| my_label | code_label | n |
|---|---|---|
| negative | negative | 4 |
| negative | neutral | 3 |
| negative | positive | 1 |
| neutral | negative | 1 |
| neutral | neutral | 2 |
| neutral | positive | 3 |
| positive | negative | 1 |
| positive | neutral | 3 |
| positive | positive | 2 |
Validation accuracy: 8 of 20 (40%). The lexicon often scored posts as neutral when positive and negative words cancelled out or when no lexicon words appeared. It also misread tone and policy vocabulary.
Rule: positive share = positive / (positive + negative) for each candidate. Predicted Becerra vote share = Becerra positive share / (Becerra + Hilton positive shares).
The interval crosses 50%, so sampling noise alone could flip this result. The forecast is a lean toward Becerra, not a confident call.
Who is on Bluesky. Bluesky users are not a random sample of California voters. The platform skews younger, more online, and more left-leaning, so a Democrat doing better than a Republican here says little about the electorate. The sample is also small (359 posts) and covers about one day, and a handful of heavy posters can account for many posts.
Sarcasm and tone vs. stance. The lexicon counts words and does not understand meaning. In validation it matched my stance labels only 40% of the time. Sarcasm, criticism that uses positive words, and neutral news headlines with loaded words all confuse it. The bootstrap measures sampling noise only, so it cannot capture this measurement error, and the true uncertainty is larger than the interval suggests.
Does more posts mean more support? No. Hilton had more posts (62% of volume) but more negative sentiment, because people post about candidates they criticize, mock, or find newsworthy. Volume measures attention, not approval, and under a volume-share rule the forecast would flip to Hilton. Different prediction rules give different winners, which shows how fragile social-media forecasts are.