This lab compares public Bluesky posts about Xavier Becerra and Steve Hilton, the two candidates named in the assignment. I use the Bing sentiment lexicon to measure positive and negative language, check the results against my own labels, and make an experimental prediction. This is a classroom exercise; the percentages are a social-media proxy, not a representative voter poll.
I also supplied screenshots of the assignment, the previous lab’s
atrrr code, and my Posit Cloud setup. Those instructions
specified Bluesky data collection, removing unrelated Hilton posts,
using tidytext and the Bing lexicon, manually labeling
10–20 random posts, reporting validation accuracy, selecting a
prediction rule, bootstrapping, and publishing only aggregate results.
ChatGPT could not access the classmate’s page, so this report follows
the assignment screenshots. The analysis uses one row per post, with an
internal post ID, candidate, and text. My manual labels are entered
separately.
I search for each candidate’s full name using the same time window, English language filter, latest-first order, and a limit of 1,000 posts per candidate. The collection starts October 1, 2026, and ends at retrieval time or the start of election day in California, whichever is earlier. The API’s search results are a convenience sample and might not include every relevant post.
Retrieved: 2026-10-29 23:38:14 UTC
Search window (UTC): 2026-10-01T00:00:00Z to
2026-10-29T23:38:09Z (end exclusive).
| Candidate | Returned |
|---|---|
| Xavier Becerra | 640 |
| Steve Hilton | 1056 |
| Step | Removed | Remaining |
|---|---|---|
| Search results before cleaning | 0 | 1696 |
| Remove missing IDs or blank text | 10 | 1686 |
| Remove duplicate post IDs across searches | 161 | 1525 |
| Remove Paris Hilton / Hilton Hotel matches | 0 | 1525 |
| Remove posts without either full name in the text | 452 | 1073 |
| Remove posts naming both candidates (ambiguous target) | 120 | 953 |
| Remove repeated text after stripping links and mentions | 77 | 876 |
The cleaning removes duplicate IDs, repeated text, unrelated Hilton matches, and posts whose text does not name either candidate. I exclude posts naming both candidates because a single whole-post score cannot reliably identify which candidate the language targets. URLs and account mentions are removed before scoring. These filters may also remove some relevant posts. Repeated authors, possible bots, quoted opinions, and errors in language tags remain limitations. No usernames or individual post texts are published.
Each positive word contributes +1 and each negative word contributes −1. The post’s score is positive words minus negative words. A positive score receives a positive label, a negative score receives a negative label, and zero receives a neutral label. Posts without any matched words stay in the analysis as neutral; zero can also mean equal positive and negative counts. These labels describe the text’s tone, which may differ from its stance toward the candidate.
| Candidate | Posts | Positive | Negative | Neutral | No matched words | Positive rate | Mean net score |
|---|---|---|---|---|---|---|---|
| Steve Hilton | 614 | 164 | 224 | 226 | 141 | 26.7% | -0.21 |
| Xavier Becerra | 262 | 78 | 48 | 136 | 102 | 29.8% | 0.22 |
A simple random sample of up to 20 cleaned posts is selected with a fixed seed. For each post, I label its stance toward the named candidate as positive (supportive), negative (opposed), or neutral (unclear, mixed, or no expressed stance). The labeling tool hides the automatic label so it does not influence my choice. Agreement is the number of identical manual and automatic labels divided by the number of manually labeled posts.
DRAFT — manual validation is not finished. Do not submit this version.
Run source("label_posts.R") in the Console, label the
posts, and click Knit again.
I use a positive-share rule based on normalized positive rates. First, I calculate each candidate’s proportion of posts with a positive Bing label. Then I divide each proportion by the sum of the two proportions. This makes the estimated two-candidate vote shares add to 100% and avoids directly using the number of search results as support. The candidate above 50% is the predicted winner; 50–50 is a tie. This conversion is a classroom assumption, not a relationship calibrated using actual voting data.
\[ \widehat{V}_B = \frac{p_B}{p_B+p_H},\qquad \widehat{V}_H = 1-\widehat{V}_B \]
Here, \(p_B\) and \(p_H\) are the positive-post proportions for Becerra and Hilton. If neither has a positive post, this rule cannot produce a prediction.
Predicted winner under this rule: Xavier Becerra.
| Candidate | Estimated vote share (proxy) | 95% bootstrap interval |
|---|---|---|
| Xavier Becerra | 52.7% | 46.8% to 58.1% |
| Steve Hilton | 47.3% | 41.9% to 53.2% |
I ran 2,000 bootstrap resamples, sampling posts with replacement separately within each candidate’s group and recalculating the shares. 0 resamples had no positive posts for either candidate and were excluded because the rule was undefined.
The interval includes 50%, so the sample does not clearly separate the candidates under this rule.
These intervals describe resampling variability in the collected posts. They are not election margins of error and do not correct platform bias, search truncation, repeated authors, or sentiment-classification errors.
This analysis shows why social-media attention should not automatically be treated as voter support. Bluesky users are not a random sample of California voters. Some posters may live outside California, may not be eligible to vote, or may participate much more often than other people. Limiting the search to English also excludes other voices. A candidate can receive many mentions because of criticism or news coverage, so more posts do not necessarily mean more support.
The Bing lexicon counts emotional words without fully understanding context. A supportive post can include negative words about an opponent, and sarcasm can make positive words express opposition. The hand-labeling comparison checks this problem, but 10–20 posts provide only a small validation sample. Removing posts about both candidates reduces ambiguity while also removing potentially useful comparisons.
The bootstrap shows how much the estimate changes when the observed posts are resampled. It cannot make the sample representative or turn text tone into voting intention. Posts from the same person may also be correlated, which means a post-level bootstrap can understate uncertainty. I would treat the predicted winner and vote share as an exploratory class result and compare them with representative polling before drawing conclusions about the election.