Converted from California_Governor_2026_Sentiment_Analysis.xlsx. All four worksheets are preserved below. Tables use the workbook’s saved formula results. The original R scripts are included as R chunks with eval=FALSE to preserve the reported results and allow knitting without downloading lexicons. To run those scripts, install tidyverse and tidytext and change their chunk options to eval=TRUE. The Bing lexicon may require a first-use download.

1 Executive Summary

Data Source: Bluesky Social Media | Candidates: Xavier Becerra (D) vs. Steve Hilton (R)

Metric Xavier Becerra (D) Steve Hilton (R) Difference / Lead
Predicted Vote / Sentiment Share 64.2% 35.8% 28.4%
Manual Validation Accuracy 60.0% 60.0% N/A
Bootstrap 95% CI Lower Bound 11.7% N/A N/A
Bootstrap 95% CI Upper Bound 43.3% N/A N/A
Bootstrap p-value (H0: Lead <= 0) 0 N/A Significant (p < 0.001)

1.1 Key Methodological Findings & Data Limitations

  1. AI Sentiment Accuracy: The Bing dictionary achieved 60% accuracy on manual validation, struggling with political slang (‘hack’), sarcasm, and jargon (‘landslide’).

  2. Statistical Significance: The 2,000-replicate bootstrap test confirmed Becerra’s sentiment lead is statistically significant (p < 0.001, 95% CI: [11.7%, 43.3%]).

  3. Demographic Bias: Bluesky users skew left-leaning and highly engaged, creating sample selection bias relative to the California voting electorate.

  4. Volume vs. Votes: Post volume reflects vocal active users and news bots rather than voting representative counts.

Conversion note: The executive summary’s 64.2% / 35.8% figures and 28.4 percentage point lead are retained as supplied. The bootstrap input table instead gives within-candidate positive shares of 45/70 (64.3%) and 22/60 (36.7%), a difference of 27.6 percentage points. These are different quantities; the workbook does not reconcile them. The reported p-value of 0 is an empirical bootstrap tail fraction, not proof that the true probability is exactly zero. Claims and sample descriptions are reproduced from the workbook, not independently verified.

2 AI Prompt & Code

2.1 Prompt Used for ChatGPT / Claude

“Write an R script using the tidyverse and tidytext packages to perform sentiment analysis on text data related to two political candidates: Xavier Becerra and Steve Hilton. 1. Create a mock dataset containing example text/posts for each candidate. 2. Tokenize the text into individual words using unnest_tokens(). 3. Remove standard stop words using anti_join(stop_words). 4. Inner join with the bing sentiment lexicon from tidytext to categorize words as either ‘positive’ or ‘negative’. 5. Calculate total positive, negative, and net sentiment scores for each candidate, and summarize the final counts in a clean output.”

2.2 R Code Implementation

# Load required libraries
library(tidyverse)
library(tidytext)

# 1. Mock text data
candidate_data <- tibble(
  candidate = c(rep("Xavier Becerra", 3), rep("Steve Hilton", 3)),
  doc_id = 1:6,
  text = c(
    "Becerra highlights steady leadership, effective progress, and strong support for public health initiatives.",
    "Supporters praise his administrative experience, though critics express concern over bureaucratic delays.",
    "A clean campaign focusing on stability, affordable housing, and expanded opportunity for working families.",
    "Hilton calls for aggressive action to fix government failure, waste, and rising costs.",
    "Enthusiastic crowds welcome his energetic proposals, but opponents label his tax plans risky and divisive.",
    "A bold campaign promising disruption, lower taxes, and reform to tackle state corruption."
  )
)

# 2, 3, & 4. Tokenize, remove stop words, and join with Bing lexicon
sentiment_scored <- candidate_data %>%
  unnest_tokens(word, text) %>%
  anti_join(stop_words, by = "word") %>%
  inner_join(get_sentiments("bing"), by = "word")

# 5. Summary
candidate_sentiment_summary <- sentiment_scored %>%
  count(candidate, sentiment) %>%
  pivot_wider(names_from = sentiment, values_from = n, values_fill = 0) %>%
  mutate(net_sentiment = positive - negative)

print(candidate_sentiment_summary)

3 Manual Validation

Manual Verification of AI Sentiment Scoring (15 Bluesky Sample Posts)

Post # Post Content (Anonymized) Manual Score Bing Lexicon Score Match? Discrepancy / Error Cause
1 PPIC poll showing Xavier Becerra 61% vs Steve Hilton 37%. 0 0 Yes Fact/Poll numbers containing no sentiment words.
2 Steve Hilton is misotic. Vote Xavier Becerra. -1 0 No Lexicon missed uncommon word ‘misotic’.
3 Why Xavier Becerra Is the Clear Choice Over Steve Hilton for California Governor. 1 1 Yes Word ‘clear’ mapped to positive sentiment.
4 Hilton Makes Final Push Against California’s Dem Machine. 0 -1 No Word ‘against’ scored negative in neutral context.
5 Vote Steve Hilton for California governor over establishment hack Xavier Becerra for real change. 1 0 No Words ‘hack’ (-1) and ‘change’ (+1) canceled out; missed endorsement context.
6 Steve Hilton storms Southern California as Xavier Becerra lays low… 0 -1 No Word ‘storms’ scored negative; descriptive phrasing misinterpreted.
7 Steve Hilton rips Xavier Becerra’s proposed mileage tax: ‘They never stop gouging you’ -1 -1 Yes Words ‘rips’ and ‘gouging’ correctly scored negative.
8 Xavier Becerra Headed for Landslide in California. 1 0 No Lexicon did not score ‘landslide’ as political positive.
9 I’m voting for Xavier fucking Becerra… Anything to deny Steve Hilton a seat. -1 -1 Yes Expressed opposition (‘deny’) correctly captured.
10 Repulsive ad features AI Xavier Becerra. -1 -1 Yes Word ‘repulsive’ accurately flagged negative.
11 Proposes abolishing DMV… Polls have him down 20+ points. -1 0 No Missed contextual negativity of election trajectory.
12 Becerra highlights steady leadership, effective progress… 1 1 Yes Explicit positive words (‘steady’, ‘effective’).
13 Opponents label his tax plans risky and divisive. -1 -1 Yes Explicit negative terms (‘risky’, ‘divisive’).
14 A clean campaign focusing on stability and affordable housing. 1 1 Yes Explicit positive terms (‘clean’, ‘stability’).
15 A bold campaign promising disruption, lower taxes, and reform… 1 1 Yes Words ‘bold’, ‘promising’, ‘reform’ scored positive.

Total Posts: 15
Accuracy Rate: 60%

3.1 Recalculate validation accuracy

manual_scores <- c(0, -1, 1, 0, 1, 0, -1, 1, -1, -1, -1, 1, -1, 1, 1)
bing_scores <- c(0, 0, 1, -1, 0, -1, -1, 0, -1, -1, 0, 1, -1, 1, 1)
matches <- manual_scores == bing_scores
data.frame(total_posts = length(matches), matched_posts = sum(matches),
           accuracy = mean(matches))
##   total_posts matched_posts accuracy
## 1          15             9      0.6

4 Bootstrap & Prediction

Bootstrap Significance Simulation (2,000 Replicates)

Candidate Positive Sentiment Posts Negative Sentiment Posts Total Scored Posts Positive Share
Xavier Becerra (D) 45 25 70 64.3%
Steve Hilton (R) 22 38 60 36.7%

4.1 R Bootstrap Simulation Script

library(tidyverse)
set.seed(2026)

# Create observed post sentiment dataset
bs_data <- tibble(
  candidate = c(rep("Becerra", 70), rep("Hilton", 60)),
  positive  = c(rep(1, 45), rep(0, 25), rep(1, 22), rep(0, 38))
)

# Bootstrap 2,000 replicates
boot_diffs <- replicate(2000, {
  s <- bs_data %>% group_by(candidate) %>% sample_frac(1, replace = TRUE) %>% summarize(r = mean(positive))
  sr[scandidate == "Becerra"] - sr[scandidate == "Hilton"]
})

# Results
ci_lower <- quantile(boot_diffs, 0.025)
ci_upper <- quantile(boot_diffs, 0.975)
p_val    <- mean(boot_diffs <= 0)

cat("Mean Difference:", mean(boot_diffs), "\n95% CI: [", ci_lower, ",", ci_upper, "]\np-value:", p_val)