| topic | Total_Stories | Weekly_Minimum | Weekly_Maximum | Weekly_Median | Weekly_Mean | Weekly_SD |
|---|---|---|---|---|---|---|
| Competition Frame | 99967 | 824 | 7324 | 2004 | 2324.81 | 1266.64 |
| Business Frame | 38774 | 304 | 1800 | 870 | 901.72 | 311.01 |
This analysis is based on framing theory. Framing theory argues that the way an issue is presented in the media can influence how audiences understand and interpret that issue. Rather than telling people what to think, media frames encourage people to think about an issue from a particular perspective. Framing theory applies to this analysis because the focus is not on whether college sports are covered in the news, but on how college sports are covered. Specifically, the analysis compares a competition frame and a business frame. The competition frame emphasizes athletic performance, championships, rankings, coaches, and games, while the business frame emphasizes topics such as revenue, NIL deals, sponsorships, contracts, and media rights. By comparing these frames, the analysis examines which perspective receives more attention in news coverage.
U.S. news coverage of college sports will be more likely to use a competition frame than a business frame. Because sports reporting traditionally focuses on games, teams, rankings, and championships, I expect competition related coverage to appear more frequently throughout the study.
The independent variable is Competition Frame and/or Business Frame.
The dependent variable is the weekly number of news stories identified by Media Cloud.
To conduct the analysis, Media Cloud was used to collect U.S. online news stories between January 2026 and October 2026. Searches were created using keywords associated with each frame. Daily story counts were downloaded and then collected into weekly totals. The results were summarized in a table and displayed in an interactive line graph showing changes in coverage over time.
The results support the hypothesis. The graph shows that the Competition Frame received substantially more coverage than the Business Frame throughout most of the study. Weekly story counts for the competition frame were consistently higher and experienced several major spikes, particularly during March and early September. These peaks likely correspond to major college sports events and postseason competition.
The Business Frame also received news coverage but at much lower levels. Although there were increases in coverage during some weeks, the business frame rarely approached the volume of stories associated with the competition frame. This suggests that news organizations continue to focus primarily on athletic competition rather than the financial aspects of college sports. Overall, the findings indicate that college sports are more commonly framed as athletic competitions than as business enterprises, which is consistent with expectations based on framing theory.
The analysis used a paired-samples t-test to assess the difference between each week’s Competition Frame coverage and Business Frame coverage fell nonrandomly above or below zero. This box plot summarizes the distributions of the Competition Frame weekly story counts (V1) and the Business Frame weekly story counts (V2).
Pair differences
The distribution of weekly story counts suggests that the Competition Frame consistently received more media attention than the Business Frame. The median weekly story count for the Competition Frame was approximately 2,300 stories, while the Business Frame had a median closer to 900 stories. The Competition Frame also showed substantially greater variability and several large spikes in coverage, indicating periods of particularly intense media attention.
Paired t-test results
The paired-samples t-test indicated a difference between the weekly story counts for the Competition Frame and the Business Frame, t(42) = -9.35, p < .001. These findings support the hypothesis that competition-oriented coverage received more attention than business-oriented coverage during the study period.
| Paired-Samples t-Test | |||||
| statistic | parameter | p.value | conf.low | conf.high | method |
|---|---|---|---|---|---|
| −9.3455 | 42 | 0.0000 | −1,730.3972 | −1,115.7889 | Paired t-test |
One possible explanation for the difference in coverage is that competition-related stories are tied directly to the events that attract the largest audiences in college sports. News headlines such as games, rankings, playoffs, championships, and coaching decisions generate new stories on a daily basis and therefore provide journalists with more newsworthy content. Topics associated with the Business Frame, such as NIL agreements, finances, media rights, and sponsorship deals, often develop more slowly and tend to receive concentrated attention only when a major announcement or controversy occurs. As a result, coverage of competition-related topics remained consistently higher throughout the study period, while business-oriented coverage appeared at lower levels despite the growing financial influence of college athletics.
############################################################
# STEP 1: Install and Load Required Packages
############################################################
required_packages <- c(
"tidyverse",
"httr2",
"lubridate",
"plotly",
"knitr",
"kableExtra"
)
for (pkg in required_packages) {
if (!requireNamespace(pkg, quietly = TRUE)) {
install.packages(pkg)
}
}
library(tidyverse)
library(httr2)
library(lubridate)
library(plotly)
library(knitr)
library(kableExtra)
############################################################
# STEP 2: Verify Media Cloud API Key
############################################################
if (!nzchar(Sys.getenv("MEDIACLOUD_KEY"))) {
stop(
"No Media Cloud API key was found.\n",
"Run the setup section once,\n",
"then restart R and try again."
)
}
mc_key <- Sys.getenv("MEDIACLOUD_KEY")
############################################################
# STEP 3: Define the Date Range
############################################################
start_date <- "2026-01-01"
end_date <- "2026-10-20"
############################################################
# STEP 4: Define Search Queries
############################################################
competition_query <-
"((NCAA OR \"college sports\" OR \"college football\" OR \"college basketball\") AND
(championship OR playoff* OR ranking* OR coach* OR
victory OR win* OR season OR game*))"
business_query <-
"((NCAA OR \"college sports\" OR \"college football\" OR \"college basketball\") AND
(revenue OR NIL OR contract* OR sponsorship* OR
money OR profit* OR \"media rights\" OR
\"television deal*\"))"
############################################################
# STEP 5: Create Topic Definitions
############################################################
topics <- c(
"Competition Frame" = competition_query,
"Business Frame" = business_query
)
############################################################
# STEP 6: Create a Function to Retrieve Daily Story Counts
############################################################
mc_counts_over_time <- function(
query,
start_date,
end_date,
collection_id = 34412234
) {
resp <-
request(
"https://search.mediacloud.org/api/search/count-over-time"
) |>
req_headers(
Authorization = paste("Token", mc_key)
) |>
req_url_query(
q = query,
start = start_date,
end = end_date,
platform = "onlinenews-mediacloud",
cs = collection_id
) |>
req_perform()
results <- resp |>
resp_body_json()
map_dfr(
results$count_over_time$counts,
as_tibble
) |>
mutate(
date = as.Date(date)
)
}
############################################################
# STEP 7: Download Daily Counts for All Frames
############################################################
topic_data <-
imap_dfr(
topics,
function(search_string, topic_name) {
message(
"Downloading: ",
topic_name
)
results <-
mc_counts_over_time(
query = search_string,
start_date = start_date,
end_date = end_date
)
Sys.sleep(35)
results |>
mutate(
topic = topic_name
)
}
)
############################################################
# STEP 8: Examine the Downloaded Daily Data
############################################################
head(topic_data)
glimpse(topic_data)
############################################################
# STEP 9: Convert Daily Counts to Weekly Counts
############################################################
weekly_counts <-
topic_data |>
mutate(
week = floor_date(
date,
unit = "week"
)
) |>
group_by(
topic,
week
) |>
summarise(
stories = sum(count),
.groups = "drop"
)
############################################################
# STEP 10: Examine Weekly Counts
############################################################
weekly_counts
############################################################
# STEP 11: Summarize Coverage by Frame
############################################################
topic_summary <-
weekly_counts |>
group_by(topic) |>
summarise(
Total_Stories = sum(stories),
Weekly_Minimum = min(stories),
Weekly_Maximum = max(stories),
Weekly_Median = median(stories),
Weekly_Mean = round(
mean(stories),
2
),
Weekly_SD = round(
sd(stories),
2
),
.groups = "drop"
) |>
arrange(
desc(Total_Stories)
)
Table <-
topic_summary |>
kbl(
caption =
"College Sports Framing Summary Statistics",
align = "lrrrrrr"
) |>
kable_styling(
full_width = FALSE,
bootstrap_options = c(
"striped",
"hover",
"condensed"
)
)
Table
############################################################
# STEP 12: Create an Interactive Plotly Graph
############################################################
weekly_counts <- weekly_counts |>
mutate(
stories_formatted = formatC(
stories,
format = "d",
big.mark = ","
)
)
Graph <- plot_ly(
data = weekly_counts,
x = ~week,
y = ~stories,
color = ~topic,
colors = "Set1",
type = "scatter",
mode = "lines+markers",
customdata = ~topic,
text = ~stories_formatted,
hovertemplate =
paste(
"<b>%{customdata}</b><br>",
"Week: %{x|%Y-%m-%d}<br>",
"Stories: %{text}<br>",
"<extra></extra>"
)
) |>
layout(
title =
"College Sports Coverage: Competition vs. Business Frames",
hovermode = "x unified",
xaxis = list(
title = "Week"
),
yaxis = list(
title = "Weekly Story Count",
separatethousands = TRUE
)
)
Graph
############################################################
# STEP 13: Reshape Data for Paired-Samples Analysis
############################################################
weekly_counts_wide <-
weekly_counts %>%
mutate(
topic = make.names(topic)
) %>%
select(week, topic, stories) %>%
pivot_wider(
names_from = topic,
values_from = stories
)
############################################################
# STEP 14: Install and Load Additional Packages
############################################################
if (!require("gt")) install.packages("gt")
if (!require("gtExtras")) install.packages("gtExtras")
if (!require("broom")) install.packages("broom")
library(gt)
library(gtExtras)
library(broom)
options(scipen = 999)
############################################################
# STEP 15: Create Analysis Dataset
############################################################
mydata <- weekly_counts_wide
mydata$V1 <- mydata$Competition.Frame
mydata$V2 <- mydata$Business.Frame
############################################################
# STEP 16: Compute Pair Differences
############################################################
mydata$PairDifferences <- mydata$V2 - mydata$V1
############################################################
# STEP 17: Interactive Histogram of Pair Differences
############################################################
hist_plot <- plot_ly(
data = mydata,
x = ~PairDifferences,
type = "histogram",
marker = list(
color = "#1f78b4",
line = list(
color = "black",
width = 1
)
)
) %>%
layout(
title = "Distribution of Pair Differences: Business Frame - Competition Frame",
xaxis = list(title = "Pair Differences"),
yaxis = list(title = "Count")
)
############################################################
# STEP 18: Descriptive Statistics
############################################################
desc_stats <- mydata %>%
summarise(
count = n(),
mean = mean(PairDifferences, na.rm = TRUE),
sd = sd(PairDifferences, na.rm = TRUE),
min = min(PairDifferences, na.rm = TRUE),
max = max(PairDifferences, na.rm = TRUE)
)
desc_table <- desc_stats %>%
gt() %>%
gt_theme_538() %>%
tab_header(
title = "Descriptive Statistics: Pair Differences"
) %>%
fmt_number(
columns = where(is.numeric),
decimals = 3
)
############################################################
# STEP 19: Normality Test
############################################################
shapiro_res <- shapiro.test(
mydata$PairDifferences
)
shapiro_table <- tidy(shapiro_res) %>%
select(
statistic,
p.value,
method
) %>%
gt() %>%
gt_theme_538() %>%
tab_header(
title = "Normality Test (Shapiro-Wilk)"
) %>%
fmt_number(
columns = c(statistic, p.value),
decimals = 4
)
############################################################
# STEP 20: Reshape Data for Repeated-Measures Plot
############################################################
df_long <- mydata %>%
pivot_longer(
cols = c(V1, V2),
names_to = "Measure",
values_to = "Value"
)
############################################################
# STEP 21: Repeated-Measures Boxplot
############################################################
group_means <- df_long %>%
group_by(Measure) %>%
summarise(
mean_value = mean(Value),
.groups = "drop"
)
boxplot_measures <- plot_ly() %>%
add_trace(
data = df_long,
x = ~Measure,
y = ~Value,
type = "box",
boxpoints = "outliers",
marker = list(
color = "red",
size = 4
),
line = list(color = "black"),
fillcolor = "royalblue",
name = ""
) %>%
add_trace(
data = group_means,
x = ~Measure,
y = ~mean_value,
type = "scatter",
mode = "markers",
marker = list(
symbol = "diamond",
size = 9,
color = "black"
),
text = ~paste0(
"Mean = ",
round(mean_value, 2)
),
hoverinfo = "text",
name = "Group Mean"
) %>%
layout(
title = "Competition Frame vs. Business Frame",
xaxis = list(title = "Frame"),
yaxis = list(title = "Weekly Stories"),
showlegend = FALSE
)
############################################################
# STEP 22: Paired-Samples t-Test
############################################################
t_res <- t.test(
mydata$V2,
mydata$V1,
paired = TRUE
)
t_table <- tidy(t_res) %>%
select(
statistic,
parameter,
p.value,
conf.low,
conf.high,
method
) %>%
gt() %>%
gt_theme_538() %>%
tab_header(
title = "Paired-Samples t-Test"
) %>%
fmt_number(
columns = c(
statistic,
p.value,
conf.low,
conf.high
),
decimals = 4
)
############################################################
# STEP 23: Group Means and Standard Deviations
############################################################
t_summary <- mydata %>%
select(V1, V2) %>%
summarise_all(
list(
Mean = mean,
SD = sd
)
) %>%
gt() %>%
gt_theme_538() %>%
tab_header(
title = "Group Means and SDs"
) %>%
fmt_number(
columns = everything(),
decimals = 3
)
############################################################
# STEP 24: Wilcoxon Signed Rank Test
############################################################
wilcox_res <- wilcox.test(
mydata$V1,
mydata$V2,
paired = TRUE,
exact = FALSE
)
wilcox_table <- tidy(wilcox_res) %>%
select(
statistic,
p.value,
method
) %>%
gt() %>%
gt_theme_538() %>%
tab_header(
title = "Wilcoxon Signed Rank Test"
) %>%
fmt_number(
columns = c(
statistic,
p.value
),
decimals = 4
)
############################################################
# STEP 25: Display Results
############################################################
hist_plot
desc_table
shapiro_table
boxplot_measures
t_table
t_summary
wilcox_table