Introduction

This analysis looks at how the New York Times covered the Gaza conflict between December 2023 and March 2026. Specifically, it asks whether coverage using genocide-related language increased after major legal and institutional rulings.

The central question is simple: when organizations like the ICJ or ICC made formal findings related to genocide, did the NYT start writing about it more?

To measure this, we tracked two things each month: the total number of NYT articles mentioning “Gaza,” and the number of those articles that also mentioned “genocide Israel.” Dividing the second by the first gives us a monthly percentage — how much of Gaza coverage used genocide language that month.

Three keywords were tracked in total: “Gaza,” “genocide Israel,” and “ICJ Israel.” Data was collected using the NYT Article Search API, which returns article counts by keyword and date range.

get_article_count <- function(keyword, year, month, api_key) {
  
  start_date <- sprintf("%d%02d01", year, month)
  end_date <- sprintf("%d%02d%02d", year, month,
                days_in_month(as.Date(paste(year, month, "01", sep = "-"))))
  
  response <- GET(
    url = "https://api.nytimes.com/svc/search/v2/articlesearch.json",
    query = list(
      q = keyword,
      begin_date = start_date,
      end_date = end_date,
      "api-key" = api_key
    )
  )
  
  parsed <- content(response, as = "parsed", type = "application/json")
  
  hits <- tryCatch({
    val <- parsed$response$meta$hits
    if (is.null(val) || length(val) == 0) 0 else as.integer(val)
  }, error = function(e) {
    message("Failed for: ", keyword, " ", year, "/", month)
    return(0)
  })
  
  return(data.frame(
    keyword = keyword,
    year = year,
    month = month,
    count = hits
  ))
}
# Define keywords and date range
keywords <- c("Gaza", "genocide Israel", "ICJ Israel")

# All months from December 2023 to March 2026
dates <- seq(as.Date("2023-12-01"), as.Date("2026-03-01"), by = "month")

# Empty list to store results
results <- list()

# Loop through every keyword and every month
for (keyword in keywords) {
  for (date in dates) {
    date <- as.Date(date, origin = "1970-01-01")
    year  <- as.integer(format(date, "%Y"))
    month <- as.integer(format(date, "%m"))
    
    # Print progress so you know it's working
    cat("Fetching:", keyword, year, month, "\n")
    
    # Call the API
    row <- get_article_count(keyword, year, month, api_key)
    results <- append(results, list(row))
    
    # Pause between calls to avoid hitting rate limits
    Sys.sleep(6)
  }
}
## Fetching: Gaza 2023 12 
## Fetching: Gaza 2024 1 
## Fetching: Gaza 2024 2 
## Fetching: Gaza 2024 3 
## Fetching: Gaza 2024 4 
## Fetching: Gaza 2024 5 
## Fetching: Gaza 2024 6 
## Fetching: Gaza 2024 7 
## Fetching: Gaza 2024 8 
## Fetching: Gaza 2024 9 
## Fetching: Gaza 2024 10 
## Fetching: Gaza 2024 11 
## Fetching: Gaza 2024 12 
## Fetching: Gaza 2025 1 
## Fetching: Gaza 2025 2 
## Fetching: Gaza 2025 3 
## Fetching: Gaza 2025 4 
## Fetching: Gaza 2025 5 
## Fetching: Gaza 2025 6 
## Fetching: Gaza 2025 7 
## Fetching: Gaza 2025 8 
## Fetching: Gaza 2025 9 
## Fetching: Gaza 2025 10 
## Fetching: Gaza 2025 11 
## Fetching: Gaza 2025 12 
## Fetching: Gaza 2026 1 
## Fetching: Gaza 2026 2 
## Fetching: Gaza 2026 3 
## Fetching: genocide Israel 2023 12 
## Fetching: genocide Israel 2024 1 
## Fetching: genocide Israel 2024 2 
## Fetching: genocide Israel 2024 3 
## Fetching: genocide Israel 2024 4 
## Fetching: genocide Israel 2024 5 
## Fetching: genocide Israel 2024 6 
## Fetching: genocide Israel 2024 7 
## Fetching: genocide Israel 2024 8 
## Fetching: genocide Israel 2024 9 
## Fetching: genocide Israel 2024 10 
## Fetching: genocide Israel 2024 11 
## Fetching: genocide Israel 2024 12 
## Fetching: genocide Israel 2025 1 
## Fetching: genocide Israel 2025 2 
## Fetching: genocide Israel 2025 3 
## Fetching: genocide Israel 2025 4 
## Fetching: genocide Israel 2025 5 
## Fetching: genocide Israel 2025 6 
## Fetching: genocide Israel 2025 7 
## Fetching: genocide Israel 2025 8 
## Fetching: genocide Israel 2025 9 
## Fetching: genocide Israel 2025 10 
## Fetching: genocide Israel 2025 11 
## Fetching: genocide Israel 2025 12 
## Fetching: genocide Israel 2026 1 
## Fetching: genocide Israel 2026 2 
## Fetching: genocide Israel 2026 3 
## Fetching: ICJ Israel 2023 12 
## Fetching: ICJ Israel 2024 1 
## Fetching: ICJ Israel 2024 2 
## Fetching: ICJ Israel 2024 3 
## Fetching: ICJ Israel 2024 4 
## Fetching: ICJ Israel 2024 5 
## Fetching: ICJ Israel 2024 6 
## Fetching: ICJ Israel 2024 7 
## Fetching: ICJ Israel 2024 8 
## Fetching: ICJ Israel 2024 9 
## Fetching: ICJ Israel 2024 10 
## Fetching: ICJ Israel 2024 11 
## Fetching: ICJ Israel 2024 12 
## Fetching: ICJ Israel 2025 1 
## Fetching: ICJ Israel 2025 2 
## Fetching: ICJ Israel 2025 3 
## Fetching: ICJ Israel 2025 4 
## Fetching: ICJ Israel 2025 5 
## Fetching: ICJ Israel 2025 6 
## Fetching: ICJ Israel 2025 7 
## Fetching: ICJ Israel 2025 8 
## Fetching: ICJ Israel 2025 9 
## Fetching: ICJ Israel 2025 10 
## Fetching: ICJ Israel 2025 11 
## Fetching: ICJ Israel 2025 12 
## Fetching: ICJ Israel 2026 1 
## Fetching: ICJ Israel 2026 2 
## Fetching: ICJ Israel 2026 3
# Combine everything into one dataframe
articles_df <- bind_rows(results)
library(tidyr)
library(lubridate)

# Create date column
articles_df <- articles_df %>%
  mutate(date = as.Date(paste(year, month, "01", sep = "-")))

# Recode keywords to clean labels
articles_df <- articles_df %>%
  mutate(keyword = recode(keyword,
    "Gaza"            = "Gaza",
    "genocide Israel" = "Genocide + Israel",
    "ICJ Israel"      = "ICJ + Israel"
  ))

# Pivot wider
wide_df <- articles_df %>%
  select(keyword, date, count) %>%
  pivot_wider(names_from = keyword, values_from = count) %>%
  rename(
    gaza     = `Gaza`,
    genocide = `Genocide + Israel`
  ) %>%
  mutate(pct_genocide = (genocide / gaza) * 100) %>%
  filter(gaza > 0, pct_genocide > 0)

# Remove known bad months with zero counts
bad_months <- as.Date(c(
  "2024-02-01", "2024-03-01", "2024-04-01",
  "2024-05-01", "2024-06-01", "2024-11-01",
  "2025-01-01", "2025-03-01"
))

wide_df <- wide_df %>%
  filter(!date %in% bad_months)

head(wide_df)
## # A tibble: 6 × 5
##   date        gaza genocide `ICJ + Israel` pct_genocide
##   <date>     <dbl>    <dbl>          <dbl>        <dbl>
## 1 2023-12-01   629       66            754        10.5 
## 2 2024-01-01   555      112             10        20.2 
## 3 2024-08-01   462       33              1         7.14
## 4 2024-09-01   374       24              1         6.42
## 5 2024-10-01   532       37            723         6.95
## 6 2024-12-01   231       19              1         8.23
milestones <- data.frame(
  date = as.Date(c(
    "2024-01-01",
    "2024-05-01",
    "2024-11-01"
  )),
  label = c(
    "ICJ preliminary\nruling",
    "ICJ orders halt\nof Rafah offensive",
    "ICC arrest warrants\nfor Netanyahu"
  )
)

# 3-month rolling average
wide_df <- wide_df %>%
  arrange(date) %>%
  mutate(rolling_avg = zoo::rollmean(pct_genocide, k = 3, fill = NA, align = "right"))

# Annotations
annotations <- data.frame(
  date  = as.Date(c("2024-03-01", "2025-07-01")),
  y     = c(18, 17),
  label = c("No sustained increase\naround ICJ ruling", "New baseline\nemerges in 2025")
)

ggplot(wide_df, aes(x = date, y = pct_genocide)) +
  
  # Faded gray bars for volume context
  geom_col(aes(y = gaza / max(gaza, na.rm = TRUE) * max(pct_genocide, na.rm = TRUE)),
         fill = "grey85", alpha = 0.12, width = 25) +
  
  # Raw line faint
  geom_line(color = "#e63946", linewidth = 0.8, alpha = 0.4) +
  geom_point(color = "#e63946", size = 2, alpha = 0.5) +
  
  # 3-month rolling average as main line
  geom_line(aes(y = rolling_avg), color = "#e63946", linewidth = 1.6) +
  
  # Milestone vertical lines
  geom_vline(data = milestones,
             aes(xintercept = date),
             linetype = "dashed",
             color = "grey50",
             alpha = 0.6) +
  
  # Milestone labels
  geom_text(data = milestones,
            aes(x = date, 
                y = max(wide_df$pct_genocide, na.rm = TRUE) * 0.95,
                label = label),
            inherit.aes = FALSE,
            hjust = -0.05,
            size = 2.8,
            color = "grey40") +
  
  # Analytical annotations
  geom_label(data = annotations,
             aes(x = date, y = y, label = label),
             inherit.aes = FALSE,
             size = 3,
             color = "#333333",
             fill = "white",
             label.size = 0.3,
             fontface = "italic") +
  
  scale_x_date(date_breaks = "2 months", date_labels = "%b %Y") +
  scale_y_continuous(labels = function(x) paste0(round(x, 1), "%")) +
  
  labs(
    title = "NYT genocide language shows no consistent sharp increases after legal rulings, but reaches a higher baseline by 2025",
    subtitle = "% of Gaza articles mentioning 'genocide' | bold line = 3-month rolling average | gray bars = total Gaza article volume",
    x        = "Month",
    y        = "% of Gaza Articles Mentioning Genocide",
    caption  = "Source: NYT Article Search API. Early percentages (Dec 2023–Mar 2024) reflect low total article volume and should be interpreted cautiously."
  ) +
  theme_minimal(base_size = 13) +
  theme(
    axis.text.x      = element_text(angle = 45, hjust = 1),
    plot.title       = element_text(face = "bold", size = 11),
    plot.subtitle    = element_text(size = 9, color = "grey40"),
    plot.caption     = element_text(hjust = 0, size = 8, color = "grey50"),
    panel.grid.minor = element_blank()
  )

Chart 1: NYT Gaza Coverage Over Time

The first chart shows monthly article counts for Gaza-related coverage from December 2023 through March 2026. The red line tracks overall Gaza coverage and the bold line shows the three-month rolling average of the percentage of articles mentioning genocide.

A few things stand out immediately.

Coverage was highest right after October 7, 2023 and dropped significantly through mid-2024. This reflects the initial burst of attention that typically follows a major news event.

The percentage of Gaza articles mentioning genocide fluctuates throughout the entire period. There is no clear moment where it permanently jumps up following a legal ruling. The ICJ preliminary ruling in January 2024 coincides with a spike, but that spike does not persist into the following months.

By 2025, the percentage appears to stabilize at a somewhat higher level than 2024. This suggests a gradual normalization of genocide language in Gaza coverage rather than a direct response to any single event.

The gray bars in the background show total Gaza article volume. Early percentages, particularly in December 2023 and January 2024, should be read with caution. The total number of articles was lower in those months, which means a small number of genocide-related articles could produce a misleadingly high percentage.

library(forcats)

# Hardcode confirmed values
before_after_plot <- data.frame(
  label  = factor(c("ICJ Rafah order", "ICC warrants", "ICJ preliminary ruling"),
                  levels = c("ICJ Rafah order", "ICC warrants", "ICJ preliminary ruling")),
  change = c(-3.62, 1.54, 1.83)
)

band_label <- data.frame(
  label = "near zero change (±2%)",
  x     = "ICJ Rafah order",
  y     = 1.5
)

ggplot(before_after_plot, aes(x = label, y = change, color = change > 0)) +
  annotate("rect", xmin = -Inf, xmax = Inf, ymin = -2, ymax = 2,
           fill = "grey90", alpha = 0.5) +
  geom_text(data = band_label,
            aes(x = x, y = y, label = label),
            inherit.aes = FALSE,
            size = 3, color = "grey50", fontface = "italic") +
  geom_hline(yintercept = 0, color = "grey30", linewidth = 0.8) +
  geom_linerange(aes(ymin = 0, ymax = change),
                 linewidth = 0.4, alpha = 0.3) +
  geom_point(size = 6) +
  geom_text(aes(label = paste0(ifelse(change > 0, "+", ""), round(change, 1), "%"),
                vjust = ifelse(change > 0, -1.2, 2.2)),
            size = 4, fontface = "bold", color = "grey20") +
  scale_color_manual(values = c("TRUE" = "#e63946", "FALSE" = "#457b9d"),
                     guide = "none") +
  scale_y_continuous(labels = function(x) paste0(x, "%"),
                     limits = c(-5, 3)) +
  labs(
    title    = "Changes are small (±2%) and inconsistent after each legal ruling",
    subtitle = "Percentage point change in Gaza articles mentioning genocide:\n2-month average after minus 2-month average before each event",
    x        = NULL,
    y        = "Percentage point change",
    caption  = "Source: NYT Article Search API. Red = increase, Blue = decrease after ruling."
  ) +
  theme_minimal(base_size = 13) +
  theme(
    plot.title         = element_text(face = "bold", size = 11),
    plot.subtitle      = element_text(size = 9, color = "grey40"),
    plot.caption       = element_text(hjust = 0, size = 8, color = "grey50"),
    panel.grid.major.x = element_blank(),
    panel.grid.minor   = element_blank()
  )

Chart 4: Coverage Centered Around Each Ruling

The fourth chart looks at each ruling individually and tracks what happened in the three months before and after. All three events are plotted on the same axis with month 0 marking the ruling itself.

The bold black line shows the average across all three events.

The average line is relatively flat. It rises slightly at month 0 and then falls back down. There is no sustained increase in the months following any of the rulings.

The individual event lines tell different stories. The ICJ preliminary ruling in January 2024 shows a spike at month 0 but that spike is driven largely by one month of elevated coverage and does not repeat across the other events. The ICJ Rafah order and ICC arrest warrants show no comparable spike.

This inconsistency is the key finding. If legal rulings consistently drove genocide language in NYT coverage, you would expect the lines to move in the same direction at the same time. They do not.

The average line captures this well. Because the individual events move in different directions, the average stays close to flat. That flat average is the clearest visual argument that rulings do not produce a consistent editorial response.

One caveat: with only three events, it is hard to draw firm conclusions. A larger sample of rulings would make this analysis more robust.

Conclusion

This analysis tracked NYT genocide language in Gaza coverage across three major legal and institutional milestones between December 2023 and March 2026.

The main finding is straightforward. There is no consistent immediate increase in genocide-related language following major legal rulings. The before/after analysis shows mixed and small changes. The event-centered chart shows no shared pattern across events. Rulings do not appear to directly trigger a shift in how the NYT frames Gaza coverage.

The more interesting finding is the gradual one. Genocide language does appear to have become more common over time. The median percentage in 2025-2026 is about 3 percentage points higher than in 2024. This suggests a slow normalization of the term rather than a reaction to specific events.

There are real limitations to this analysis. Missing months due to API rate limits created gaps in the data. The keyword approach measures mentions of the word genocide but not how it is used. An article can mention genocide while arguing against the framing. Volume alone does not capture tone or editorial stance.

Despite those limitations the data tells a consistent story across four different chart types. Legal rulings do not appear to move the needle immediately. But over time the language is shifting.

Whether that shift reflects editorial change, public pressure, or simply the accumulation of legal findings is a question this data cannot answer. But it is a question worth asking.