The purpose of this project is to investigate whether public conversations on Bluesky can provide insight into the California gubernatorial election. The analysis focuses on two candidates: Xavier Becerra, a Democrat, and Steve Hilton, a Republican.
Public Bluesky posts were collected using the R package
atrrr. The posts were cleaned and analyzed using the Bing
sentiment lexicon from the tidytext package.
The project uses sentiment analysis to compare positive, negative, and neutral posts about each candidate. A bootstrap analysis was also conducted to measure sampling variability in the results.
Bluesky posts were collected using the atrrr package in
R.
Two separate searches were conducted: - Xavier Becerra - Steve Hilton
The initial collection produced 58 posts: - 20 posts about Xavier Becerra. - 38 posts about Steve Hilton.
The two datasets were combined into one dataset and labeled according to candidate.
The collected data was cleaned before analysis.
The cleaning process included: - Removing posts with missing or empty text. - Removing irrelevant references to Hilton Hotels and Paris Hilton. - Removing duplicate posts.
After cleaning, 54 posts remained: - 19 posts about Xavier Becerra. - 35 posts about Steve Hilton.
Four posts were removed during cleaning.
Additional limitations include possible spam, reposts, automated accounts, and posts that mention a candidate without actually discussing their campaign.
The following prompt was used with ChatGPT to generate the sentiment analysis code:
“I am completing a social-media analytics project using public Bluesky posts about two California gubernatorial candidates: Xavier Becerra (Democrat) and Steve Hilton (Republican).
My dataset is a CSV file containing the following columns:
Please write R code using the tidyverse and tidytext packages to analyze the sentiment of these posts.
Requirements:
Please make the code beginner-friendly and ensure that it can run in RStudio. Do not invent data or assume that the sentiment results indicate actual voter preferences. Explain the limitations of using a word-based sentiment lexicon to measure political support.”
The Bing sentiment lexicon was used to identify positive and negative words in each post.
Each positive word received a score of +1, and each negative word received a score of -1.
The scores were added together to create a sentiment score for each post.
Posts were classified as: - Positive: score greater than zero. - Negative: score less than zero. - Neutral: score equal to zero.
The analysis produced the following results:
| Candidate | Positive | Negative | Neutral |
|---|---|---|---|
| Xavier Becerra | 8 (42.1%) | 2 (10.5%) | 9 (47.4%) |
| Steve Hilton | 13 (37.1%) | 10 (28.6%) | 12 (34.3%) |
Becerra had a higher percentage of positive posts than Hilton. Becerra also had a lower percentage of negative posts.
Hilton received more total positive posts, but this was partly because more posts about Hilton were collected.
A random sample of 20 posts was selected for manual review.
Each post was manually classified according to whether the author supported, opposed, or expressed a neutral stance toward the candidate.
The manual classifications were compared with the computer-generated sentiment classifications.
Human validation accuracy: [human audit completed.csv]
This comparison is important because positive or negative language does not always represent political support or opposition.
The prediction rule selected for this project was positive-post share.
The positive-post share was calculated by dividing the number of positive posts about each candidate by the total number of positive posts about both candidates.
There were 21 positive posts in total.
| Candidate | Positive posts | Positive-post share |
|---|---|---|
| Xavier Becerra | 8 | 38.10% |
| Steve Hilton | 13 | 61.90% |
Based on this prediction rule, Steve Hilton is the predicted winner of the social-media analysis, with a positive-post share of approximately 61.90%.
Xavier Becerra received approximately 38.10%.
These percentages represent positive-post shares, not actual predicted election vote shares.
A bootstrap analysis with 1,000 resamples was conducted to measure sampling variability.
The posts were resampled separately for each candidate, and the positive-post shares were recalculated for each sample.
The results were:
| Candidate | Average share | Lower 95% | Upper 95% |
|---|---|---|---|
| Xavier Becerra | 38.32% | 21.42% | 55.00% |
| Steve Hilton | 61.68% | 45.00% | 78.58% |
The bootstrap results suggest that Hilton had a higher average positive-post share in the resampled datasets.
However, the intervals are relatively wide, indicating substantial sampling variability.
The bootstrap measures uncertainty from sampling the collected posts. It does not account for all possible biases in the original Bluesky data.
There are several limitations to using Bluesky posts to predict an election.
First, Bluesky users may not represent the overall California voting population. Certain age groups, political communities, and demographic groups may be more active on the platform than others.
Second, sentiment analysis cannot always understand sarcasm, humor, political context, or tone. A negative-sounding post does not necessarily mean that the author opposes the candidate being discussed.
Third, having more posts about a candidate does not necessarily mean that candidate has more support. People may discuss a candidate because they disagree with them, criticize their policies, or respond to controversial news.
Another limitation is the relatively small sample size. Only 54 cleaned posts were collected, which is not enough to represent the opinions of millions of California voters.
The human validation process is also important because it demonstrates that automated sentiment scores do not always accurately identify political stance.
Finally, the bootstrap analysis measures sampling variability but cannot eliminate selection bias or make the sample representative of actual voters.
Overall, Bluesky sentiment analysis provides an interesting snapshot of online political conversations, but it should not be treated as a reliable replacement for representative election polling.
Based on the positive-post-share prediction rule, Steve Hilton received the larger share of positive Bluesky posts, with approximately 61.90%, compared with 38.10% for Xavier Becerra.
The bootstrap analysis also showed a higher average positive-post share for Hilton.
However, these findings should be interpreted cautiously because social-media activity does not necessarily reflect actual voter preferences or election outcomes.
library(ggplot2)
graph_data <- data.frame(
candidate = c(
"Becerra", "Becerra", "Becerra",
"Hilton", "Hilton", "Hilton"
),
sentiment = c(
"Positive", "Negative", "Neutral",
"Positive", "Negative", "Neutral"
),
percentage = c(
42.1, 10.5, 47.4,
37.1, 28.6, 34.3
)
)
ggplot(graph_data,
aes(x = candidate,
y = percentage,
fill = sentiment)) +
geom_col(position = "dodge") +
labs(
title = "Bluesky Sentiment by Candidate",
x = "Candidate",
y = "Percentage of Posts",
fill = "Sentiment"
) +
theme_minimal()
library(dplyr)
##
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
##
## filter, lag
## The following objects are masked from 'package:base':
##
## intersect, setdiff, setequal, union
library(stringr)
audit <- read.csv("human audit completed.csv")
audit <- audit %>%
mutate(
human_label = str_to_title(str_trim(human_label)),
computer_label = case_when(
sentiment == "Positive" ~ "Support",
sentiment == "Negative" ~ "Oppose",
sentiment == "Neutral" ~ "Neutral"
)
)
accuracy <- mean(
audit$human_label == audit$computer_label
)
cat("Human validation accuracy:",
round(accuracy * 100, 1), "%")
## Human validation accuracy: 40 %
table(
Human = audit$human_label,
Computer = audit$computer_label
)
## Computer
## Human Neutral Oppose Support
## Neutral 2 1 2
## Oppose 3 3 1
## Support 4 1 3
Name it `california_governor_analysis.Rmd`.
</box>
Choose Knit to HTML. Check that your report generates without errors.
</box>
Click Publish in RStudio and select RPubs. Sign in or create an RPubs account if prompted.
</box>
Open your published report and copy its URL for your class discussion.
</box>
You can visit .
One last reminder: Include your actual post-collection dates and make sure the data was collected before the election outcome was known if you’re presenting this as a forecast. Publish aggregate results only, without usernames or identifying information.