---
title: "FAR-RIGHT CLIMATE FRAMES"
author: "Anonymized for Peer Review"
date: "28/08/2023"
output:
  html_document:
    toc: yes
    toc_depth: 6
    number_sections: yes
    theme: united
    highlight: tango
    toc_float: TRUE
  pdf_document:
    toc: yes
    toc_depth: 6
    number_sections: yes
  word_document:
    toc: yes
    toc_depth: '6'
always_allow_html: yes
---


This Markdown document will analyze the dataframe "klimafd_data_anon.csv", which contains text-based posts and articles across 5 different actor types -- wherein the individual account names have been anonymized for data-sharing purposes. 

```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE)
options(scipen = 999)
library(tidyverse)
library(tidytext)
library(ngram)
library(readr)
library(tokenizers)
library(stopwords)
library(quanteda)
library(readxl)
library(kableExtra)
library(ggcorrplot)
library(lubridate)
library(tseries)
library(vars)
library(lmtest)
library(bruceR)
library(janitor)
```



# Read Data

```{r}
df <- read.csv("klimafd_data_anon.csv", stringsAsFactors = FALSE)

# Rename type variable
df$type <- recode(df$type, mainstream = "Legacy_Press", midlevel = "AfD_low", topdown = "AfD_top", twitter = "Hyperactive_Accounts", rnis = "RNS")
```


# Table 1

```{r}

table_1 <- 
  df %>%
    group_by(type) %>%
      summarize(
        authors = n_distinct(author),                    # Number of unique authors
        posts = n(),                                     # Number of posts
        mean_posts_per_author = posts/authors)  %>%   # Average number of posts per author 
          left_join(df %>%
              unnest_tokens(word, text) %>%          # Unnest the words
                group_by(type) %>%
                  summarise(total_words = n()),          # Count the words
                    by = "type") %>%
                      mutate(words_per_post = total_words / posts)       # Words per post

# Define the order
type_order <- c("Legacy_Press", "AfD_top", "AfD_low", "RNS", "Hyperactive_Accounts")
# Reorder the factor levels
table_1$type <- factor(table_1$type, levels = type_order)
# Arrange by the type factor
table_1 <- table_1 %>%
  arrange(type)

# Print the summary table
table_1 %>% 
  kable(digits = 1,  format.args = list(big.mark = ",")) %>% 
    kable_classic(full_width = F, html_font = "Cambria")

# Save table_1
write.csv(table_1, "table_1.csv", row.names = TRUE)
```


# Figure 2

```{r, fig1, out.height = '140%',  out.width = '140%'} 


# Convert the 'date' variable to the date format
df$date_date_format <- as.Date(df$date)
# Convert date to "year-month" format and create a new variable 'month_year'
df$month_year <- format(df$date_date_format, "%Y-%m")

# Count observations for each month and each type
df_count <- df %>%
  group_by(month_year, type) %>%
  summarise(count = n(),
            count_min = n()*.5,
            count_max = n()*1.2,
            .groups = "drop")




# Visualize
ggplot(df_count, aes(x = as.Date(paste(month_year, "-01", sep="")), y = count, label = count, group = type)) +
geom_line(color = "darkgreen", linewidth = 1.2) +
#geom_text(vjust = -0.5, size = 2.5) +
facet_wrap(type ~ ., scales = "free_y", nrow=4) +
geom_blank(aes(y = count_min)) +
geom_blank(aes(y = count_max)) +
geom_hline(yintercept = 0, color = "gray80") + 
labs(x = "Date", y = "Number of Observations") +
theme_minimal() +
theme(panel.spacing = unit(1, "lines")) +
ylim(0, NA)

```

# Appendix A2  - Analysis of Time Series

```{r}
df_count_2 <- df %>%
  group_by(date_date_format, type) %>%
  summarise(count = n())

df_wide <- df_count_2 %>%
  spread(key = type, value = count)
df_wide[is.na(df_wide)] <- 0
```


Create ts objects

```{r}
ts_afd_low <- ts(df_wide$AfD_low, start = c(2018,1,1), frequency = 365)
ts_afd_top <- ts(df_wide$AfD_top, start = c(2018,1,1), frequency = 365)
ts_hyperactive <- ts(df_wide$Hyperactive_Accounts, start = c(2018,1,1), frequency = 365)
ts_legacy <- ts(df_wide$Legacy_Press, start = c(2018,1,1), frequency = 365)
ts_rns <- ts(df_wide$RNS, start = c(2018,1,1), frequency = 365)
```

## (Augmented) Dickey-Fuller Tests for Stationarity

```{r}
adf.test(ts_afd_low)
adf.test(ts_afd_top)
adf.test(ts_hyperactive)
adf.test(ts_legacy)
adf.test(ts_rns)

# Standard Dickey-Fuller Test
adf.test(ts_afd_low, k=0)
adf.test(ts_afd_top, k=0)
adf.test(ts_hyperactive, k=0)
adf.test(ts_legacy, k=0)
adf.test(ts_rns, k=0)

```

## KPSS Tests

```{r}
tseries::kpss.test(ts_afd_low, null="Trend")
tseries::kpss.test(ts_afd_top, null="Trend")
tseries::kpss.test(ts_hyperactive, null="Trend")
tseries::kpss.test(ts_rns, null="Trend")
tseries::kpss.test(ts_legacy, null="Trend")

```
This means only the Legacy Press timeseries is trend-stationary, i.e. does not follow a trend over time. We apply first-difference transformation to eliminate trend non-stationarity and repeat the KPSS (and ADF) tests. 

```{r}


tseries::kpss.test(diff(ts_afd_low), null="Trend")
tseries::kpss.test(diff(ts_afd_top), null="Trend")
tseries::kpss.test(diff(ts_hyperactive), null="Trend")
tseries::kpss.test(diff(ts_rns), null="Trend")

adf.test(diff(ts_afd_low))
adf.test(diff(ts_afd_top))
adf.test(diff(ts_hyperactive))
adf.test(diff(ts_rns))

```

## Vector Autoregression and Granger Causality 

```{r}
ts_afd_low <- diff(ts_afd_low)
ts_afd_top <- diff(ts_afd_top)
ts_hyperactive <- diff(ts_hyperactive)
ts_rns <- diff(ts_rns)
#ts_legacy <- diff(ts_legacy)

```



```{r}
ts_01 <- cbind(ts_afd_low, ts_afd_top, ts_hyperactive, ts_rns, ts_legacy) 
ts_01[is.na(ts_01)] <- 0
print(VARselect(ts_01, lag.max = 3))
tsVAR <- VAR(ts_01, p = 3, type="const")

summary(tsVAR)


causality(tsVAR, cause = c("ts_afd_low", "ts_afd_top", "ts_hyperactive","ts_legacy"))
causality(tsVAR, cause = c("ts_rns", "ts_afd_top", "ts_hyperactive","ts_legacy"))
causality(tsVAR, cause = c("ts_afd_low", "ts_afd_top", "ts_hyperactive","ts_legacy"))
causality(tsVAR, cause = c("ts_afd_low", "ts_afd_top", "ts_rns","ts_legacy"))
causality(tsVAR, cause = c("ts_afd_low", "ts_afd_top", "ts_rns","ts_hyperactive"))


```




### Correlation of  Weekly Time series 


Correlation of "climate" ("Klima") mentions between actor types over time (weeks)

```{r}



# Count observations for each day and each type
df$week <- lubridate::floor_date(df$date_date_format, "week")


df_count <- df %>%
  group_by(week, type) %>%
  summarise(count = n(), .groups = "drop")



# Reshape data to wide format
df_wide <- df_count %>%
  pivot_wider(names_from = type, values_from = count)

# Replace NA values with 0
df_wide[is.na(df_wide)] <- 0

# Calculate correlations
table_2 <- cor(df_wide[,-1], use="pairwise.complete.obs")

# View the correlation matrix

# Visualization

ggcorrplot(table_2, type="lower", lab = T,
           ggtheme = ggplot2::theme_minimal(),
            colors = c("#af8dc3","#f7f7f7","#af8dc3"))

# Table

table_2 %>%
  kable(digits = 2) %>% 
    kable_classic(full_width = F, html_font = "Cambria")


```

### Cross-Correlation Distance Between Weekly Time Series


```{r}

a <- df_wide$AfD_low
b <- df_wide$AfD_top
c <- df_wide$Hyperactive_Accounts
d <- df_wide$Legacy_Press
e <- df_wide$RNS

#cor(a,b)
#cor.test(a,b,)

#TSdist::EuclideanDistance(a,b)
#TSdist::CorDistance(a,b)

# use the nicely named table to override the values:
table_2[1,2] <- TSdist::CCorDistance(a,b)
table_2[2,1] <- TSdist::CCorDistance(a,b)
table_2[1,3] <- TSdist::CCorDistance(a,c)
table_2[3,1] <- TSdist::CCorDistance(a,c)
table_2[1,4] <- TSdist::CCorDistance(a,d)
table_2[4,1] <- TSdist::CCorDistance(a,d)
table_2[1,5] <- TSdist::CCorDistance(a,e)
table_2[5,1] <- TSdist::CCorDistance(a,e)

table_2[2,3] <- TSdist::CCorDistance(b,c)
table_2[3,2] <- TSdist::CCorDistance(b,c)
table_2[2,4] <- TSdist::CCorDistance(b,d)
table_2[4,2] <- TSdist::CCorDistance(b,d)
table_2[2,5] <- TSdist::CCorDistance(b,e)
table_2[5,2] <- TSdist::CCorDistance(b,e)

table_2[3,4] <- TSdist::CCorDistance(c,d)
table_2[4,3] <- TSdist::CCorDistance(c,d)
table_2[3,5] <- TSdist::CCorDistance(c,e)
table_2[5,3] <- TSdist::CCorDistance(c,e)

table_2[4,5] <- TSdist::CCorDistance(d,e)
table_2[5,4] <- TSdist::CCorDistance(d,e)

table_2[1,1] <- TSdist::CCorDistance(a,a)
table_2[2,2] <- TSdist::CCorDistance(b,b)
table_2[3,3] <- TSdist::CCorDistance(c,c)
table_2[4,4] <- TSdist::CCorDistance(d,d)
table_2[5,5] <- TSdist::CCorDistance(e,e)


table_2 %>%
  kable(digits = 2) %>% 
    kable_classic(full_width = F, html_font = "Cambria")
```


### Correlation of Daily Time series 

Correlation of "climate" ("Klima") mentions between actor types over time (days)


```{r}


# Count observations for each day and each type
df_count <- df %>%
  group_by(date_date_format, type) %>%
  summarise(count = n(), .groups = "drop")



# Reshape data to wide format
df_wide <- df_count %>%
  pivot_wider(names_from = type, values_from = count)

# Replace NA values with 0
df_wide[is.na(df_wide)] <- 0

# Calculate correlations
table_2 <- cor(df_wide[,-1], use="pairwise.complete.obs")

# View the correlation matrix

# Visualization

ggcorrplot(table_2, type="lower", lab = T,
           ggtheme = ggplot2::theme_minimal(),
            colors = c("#af8dc3","#f7f7f7","#af8dc3"))

# Table

table_2 %>%
  kable(digits = 2) %>% 
    kable_classic(full_width = F, html_font = "Cambria")

```

### Cross-Correlation Distance Between Time Series


```{r}

d <- df_wide$AfD_low
e <- df_wide$AfD_top
a <- df_wide$Hyperactive_Accounts
b <- df_wide$Legacy_Press
c <- df_wide$RNS

#cor(a,b)
#cor.test(a,b,)

#TSdist::EuclideanDistance(a,b)
#TSdist::CorDistance(a,b)

# use the nicely named table to override the values:
table_2[1,2] <- TSdist::CCorDistance(a,b)
table_2[2,1] <- TSdist::CCorDistance(a,b)
table_2[1,3] <- TSdist::CCorDistance(a,c)
table_2[3,1] <- TSdist::CCorDistance(a,c)
table_2[1,4] <- TSdist::CCorDistance(a,d)
table_2[4,1] <- TSdist::CCorDistance(a,d)
table_2[1,5] <- TSdist::CCorDistance(a,e)
table_2[5,1] <- TSdist::CCorDistance(a,e)

table_2[2,3] <- TSdist::CCorDistance(b,c)
table_2[3,2] <- TSdist::CCorDistance(b,c)
table_2[2,4] <- TSdist::CCorDistance(b,d)
table_2[4,2] <- TSdist::CCorDistance(b,d)
table_2[2,5] <- TSdist::CCorDistance(b,e)
table_2[5,2] <- TSdist::CCorDistance(b,e)

table_2[3,4] <- TSdist::CCorDistance(c,d)
table_2[4,3] <- TSdist::CCorDistance(c,d)
table_2[3,5] <- TSdist::CCorDistance(c,e)
table_2[5,3] <- TSdist::CCorDistance(c,e)

table_2[4,5] <- TSdist::CCorDistance(d,e)
table_2[5,4] <- TSdist::CCorDistance(d,e)

table_2[1,1] <- TSdist::CCorDistance(a,a)
table_2[2,2] <- TSdist::CCorDistance(b,b)
table_2[3,3] <- TSdist::CCorDistance(c,c)
table_2[4,4] <- TSdist::CCorDistance(d,d)
table_2[5,5] <- TSdist::CCorDistance(e,e)


table_2 %>%
  kable(digits = 2) %>% 
    kable_classic(full_width = F, html_font = "Cambria")
```


# Table 2 - Granger Causality Tests 


```{r}
granger_causality(tsVAR)
```


# Frame Analysis Preparation

## Data Preprocessing 

Cleaning data and generating 1-3 word ngrams that are exclusive to the 4 far-right actor types (by controlling for "legacy press" content)

```{r}

# Load German stopwords
stop_words_german <- read_lines("German stop words de.txt")
stop_words_german <- data.frame(word = stop_words_german, stringsAsFactors = FALSE)

# Text cleaning
df$text <- df$text %>%
  str_to_lower() %>%
  str_replace_all("http[^[:blank:]]+|www\\.[^[:blank:]]+|@[a-z0-9_]+|fb\\.me\\/[^[:blank:]]+|t\\.co\\/[^[:blank:]]+", " ") %>% # Remove hyperlinks, emails, Twitter handles, and shortened links
  str_replace_all("\\d{5,}", " ") %>% # Remove longer digit combinations
  str_squish()


# tokenize 
df_words <- df %>%
  unnest_tokens(word, text, token = "regex", pattern = "(?!-)[[:punct:]]| ", drop = T)



# Remove stopwords
df_words <- df_words %>% 
  anti_join(stop_words_german, by = "word")

```

## N-gram Extraction


```{r}
# Generate unigrams, bigrams, and trigrams

df_ngrams <- df_words %>%
  mutate(bigram = str_c(word, lead(word), sep = " "),
         trigram = str_c(word, lead(word), lead(word, 2), sep = " ")) %>%
  gather(key, ngram, word:trigram)

```






```{r}
# Show ngrams that are common in "AfD_top", "AfD low", "RNS", "Hyperactive_Accounts" but missing from "Legacy Press"

df_far_right_ngrams <-
    df_ngrams %>%
      count(ngram, type) %>%
      spread(type, n, fill = 0) 

names(df_far_right_ngrams) <- c("ngram", "AfD_low","AfD_top","Hyperactive_Accounts", "Legacy_Press","RNS")

df_far_right_ngrams <- 
  df_far_right_ngrams %>%
      mutate(total = rowSums(dplyr::select(., AfD_low:RNS))) %>%
        filter(AfD_low > 0 & AfD_top > 0 & Hyperactive_Accounts > 0 & RNS > 0 & Legacy_Press == 0)



# Clean the results
df_far_right_ngrams <- df_far_right_ngrams %>%
  filter(str_count(ngram, "[[:digit:]]") < str_count(ngram, "[[:alpha:]]"))

# take out those who are uni- or bigrams already contained in bi- or trigrams
df_far_right_ngrams$dupe <- F
for (i in 2:length(df_far_right_ngrams$ngram)) {
  if (str_detect(df_far_right_ngrams$ngram[i], fixed(df_far_right_ngrams$ngram[i-1]))) {
  df_far_right_ngrams$dupe[i-1] <- T
   }
} 
df_far_right_ngrams <-
  df_far_right_ngrams %>%
    filter(dupe ==F) %>%
      dplyr::select(-dupe)

df_far_right_ngrams %>%
          kable() %>% 
            kable_classic(full_width = F, html_font = "Cambria")


# Save the dataframe, which was then used for further, manual analysis and cleaning.

write.csv(df_far_right_ngrams, "exclusive_far-right_ngrams.csv", row.names = FALSE)
```


END OF AUTOMATED CONTENT ANALYSIS

NOTE: We now used the resulting list on n-grams featured in "exclusive_far-right_ngrams.csv" to manually inspect and remove all terms or phrases that references websites or social media accounts. 
The goal here was to generate a list of terms or short phrases that clearly referenced aspects of climate policies and would be most likely to generate valid results. 

Nonsensical letter or number combinations were removed. All individual verbs or adjectives were removed, keeping only terms or phrases that consisted of proper nouns or full names with a potential connection to the overall theme of climate change. 

Self-references such as "AfD Berlin" were also removed. When specific terms appeared in conjunction with adjectives, we kept the longer, more precise version of the phrase rather than the single terms so long as there was no marked difference in the frequency at which they appeared. If this was not the case, we only kept the shorter version, which had more potential for variation. 

Terms or word combinations which only appeared in this data due to different hyphenation practices but which aim to reference the same aspect or noun as those used in mainstream sources (e.g. "co2 ausstoÃ" rather than "co2-ausstoÃ") were also removed from the list. 

Terms with a possessive "s" as a differently inflected version of a full noun (e.g. Staatsfunks) were removed from the list, if the same term did not also appear without the "s" -- this would mean that Legacy Press did feature the term, just not in the same grammatical context. 


This process of manual cleaning and classification of ngrams ultimately resulted in the file "far_right_climate_terms.xlsx", which we then used in subsequent analyses.

# Frame Analysis

## Read Coded Data

```{r}
far_right_climate_terms <- read_excel("far_right_climate_terms.xlsx")
far_right_climate_terms %>%
  kable() %>% 
    kable_classic(full_width = F, html_font = "Cambria")


```

## Occurrence of Terms across Levels/Types

```{r}
# Convert terms to a vector
terms_vector <- as.vector(unlist(far_right_climate_terms))

# Initialize empty data frame for results
df_term_counts <- data.frame()

# Loop over terms
for(term in terms_vector){
  # Count occurrences of term in text for each type
  df_term_count <- df %>%
    group_by(type) %>%
    summarise(term_count = sum(str_count(text, paste0("\\b", term, "\\b")))) %>%
    mutate(term = term) %>%
    ungroup()
  
  # Combine results
  df_term_counts <- rbind(df_term_counts, df_term_count)
}

# Pivot the data frame to have terms as rows and types as columns
df_term_counts_pivot <- df_term_counts %>%
  pivot_wider(names_from = type, values_from = term_count)

df_term_counts_pivot %>%
  kable() %>% 
    kable_classic(full_width = F, html_font = "Cambria")

# Save the table
write.csv(df_term_counts_pivot, "term_counts_by_type.csv", row.names = FALSE)

```


## Sequences of Appearance

NOTE: here we are dealing with a data challenge - as the data for "AfD_top" did not contain time stamp data, only dates. We therefore manually validated each of the results, which is why the reported results deviate slightly from what we get via the script. In some cases we were able to get the correct timestamp by looking at the material by hand, meaning the validated results are more accurate than what we get here.

```{r}
# Convert date-time string to POSIXct object
df$date <- ymd_hms(df$date)
class(df$date)

# Convert terms to a vector
terms_vector <- as.vector(unlist(far_right_climate_terms))

# Filter data to include only the four specified types
df_filtered <- df %>%
  filter(type %in% c("AfD_top", "AfD_low", "RNS", "Hyperactive_Accounts"))

# Convert all the dates to date-time format, the time part for 'AfD_top' will be 00:00:00
df_filtered$date_time <- ymd_hms(df_filtered$date, tz="UTC", truncated = 3)

# Initialize empty data frame for results
df_results <- data.frame()

# Loop over terms
for(term in terms_vector){
  # Detect term in text
  df_term <- df_filtered %>%
    mutate(present = ifelse(str_detect(text, paste0("\\b", term, "\\b")), 1, 0),
           term = term) %>%
    filter(present == 1)
  
  # Find first appearance of term for each type and get the corresponding URL
  df_term_first <- df_term %>%
    group_by(type, term) %>%
    mutate(first_date_time = min(date_time, na.rm = TRUE)) %>%
    filter(date_time == first_date_time) %>%
    dplyr::select(type, term, first_date_time, url) %>%
    ungroup()
  
  # Combine results
  df_results <- rbind(df_results, df_term_first)
}

# Calculate rank
df_ranked <- df_results %>%
  group_by(term) %>%
  mutate(rank = rank(na.omit(first_date_time), ties.method = "min")) %>%
  ungroup()

# Summarize data
df_term_sequence <- df_ranked %>%
  group_by(type) %>%
  summarise(first = sum(rank == 1, na.rm = TRUE),
            second = sum(rank == 2, na.rm = TRUE),
            third = sum(rank == 3, na.rm = TRUE),
            fourth = sum(rank == 4, na.rm = TRUE))

# Pivot the table to get term rank for each type and corresponding URL
df_term_rank_url <- df_ranked %>%
  dplyr::select(term, type, rank, url) %>%
  pivot_wider(names_from = type, values_from = c(rank, url))

df_term_rank_url %>%
  kable() %>% 
    kable_classic(full_width = F, html_font = "Cambria")

```

As mentioned above, we then hand-validated the df_term_rank table, by controlling the original URLs and hand recording corrections were applicable. This was necessary, so as to ensure that we recorded the correct sequence order, since the timestamps for "AfD_top" were not granular enough.
This process produced the final table used as the basis for tables and graphs. 

### Save to CSV for Manual Coding
 
```{r}

df_term_rank_url <- df_term_rank_url %>%
  mutate(across(where(is.list), ~map_chr(., ~paste(., collapse = ", "))))

# Save
write.csv(df_term_rank_url, "term_sequence_urls.csv", row.names = FALSE)
```

Using this dataframe we checked URLs by hand, in order to obtain the validated sequence. This dataframe was also used for the examples we outline in the findings session, qualitatively describing the sequence of specific terms.


## Assigning Frame Categories to the Terms / Categorization

This was done via a round of manual, inductive code generation, which resulted in a total of 9 distinct frame categories (and "Other"). 4 coders then assigned each term a value of 0-9. (See "Intercoder-Reliability_Climate Frame Categories.xlsx") This process ultimately resulted in the table "Terms-as-categories.xlsx".

### Read Data

```{r}
# View File containing codes assigned by various coders + codebook summary:
intercoder_frame_categories <- read_excel("Intercoder-Reliability_Climate Frame Categories.xlsx")
head(intercoder_frame_categories[,8:10],10)  %>%
  kable() %>% 
    kable_classic(full_width = F, html_font = "Cambria")

intercoder_frame_categories[,1:6] %>%
  kable() %>% 
    kable_classic(full_width = F, html_font = "Cambria")

# View Frame categories ultimately assigned to each term:
frames <- read_excel("Terms-as-categories.xlsx")
frames %>%
  kable() %>% 
    kable_classic(full_width = F, html_font = "Cambria")
```

### Join Dataframes to Coded Categories

This step is necessary to produce the data underlying table 3 (frames across actor types) and figure 3 (sequences of frame categories across actor types). 

```{r}

# Load term_sequence_urls data
df_term_rank_url <- read.csv("term_sequence_urls.csv", stringsAsFactors = FALSE)

# Join frames data with df_term_rank_url
df_term_rank_url <- df_term_rank_url %>%
  left_join(frames, by = "term")

# Rearrange columns
df_term_rank_url <- df_term_rank_url %>%
  dplyr::select(frame, term, everything())

```


# Table 3

Counts of frame categories across actor types.


```{r}
# Initialize empty data frame for results
df_term_counts <- data.frame()

# Loop over terms
for(term in terms_vector){
  # Count occurrences of term in text for each type
  # Use word boundaries with \\b before and after the term
  df_term_count <- df %>%
    group_by(type) %>%
    summarise(term_count = sum(str_count(text, paste0("\\b", term, "\\b")))) %>%
    mutate(term = term) %>%
    ungroup()
  
  # Combine results
  df_term_counts <- rbind(df_term_counts, df_term_count)
}

# Pivot the data frame to have terms as rows and types as columns
df_term_counts_pivot <- df_term_counts %>%
  pivot_wider(names_from = type, values_from = term_count)

# Merge df_term_counts_pivot with frames data
df_term_counts_pivot <- merge(df_term_counts_pivot, frames, by = "term", all.x = TRUE)

# Group by frame and summarise the counts for each type
df_table_3 <- df_term_counts_pivot %>%
  group_by(frame) %>%
  summarise(across(where(is.numeric), sum, na.rm = TRUE)) 


df_table_3 <- df_table_3[,c(1,2,3,4,6)]
names(df_table_3) <- c("Frame", "AfD_Low","AfD_Top","Hyperactive_Accounts","RNS")
     
df_table_3 <-df_table_3 %>%
  ungroup() %>%
  mutate(AfD_Low_perc = scales::percent(AfD_Low/sum(AfD_Low),accuracy = 0.1)) %>%
  mutate(AfD_Top_perc = scales::percent(AfD_Top/sum(AfD_Top),accuracy = 0.1)) %>%
  mutate(Hyper_perc = scales::percent(Hyperactive_Accounts/sum(Hyperactive_Accounts),accuracy = 0.1)) %>%
  mutate(RNS_perc = scales::percent(RNS/sum(RNS),accuracy = 0.1))
         
 df_table_3 <- df_table_3 %>% relocate(Frame,AfD_Low,AfD_Low_perc,AfD_Top,AfD_Top_perc,Hyperactive_Accounts,Hyper_perc,RNS,RNS_perc) 
 names(df_table_3)[6] <- 'Hyper' # just shortening the label for better  Markdown Visibility
 df_table_3$total_n <- df_table_3$AfD_Top + df_table_3$AfD_Low + df_table_3$Hyper + df_table_3$RNS

 df_table_3 <-df_table_3 %>%
   mutate(total_perc = scales::percent(total_n/sum(total_n),accuracy = 0.1))
 
tableorder <- c('Ideology','Totalitarianism','Insanity / Irrationality','Religion / Cult','Hysteria / Fearmongering','Hypocrisy / Moral superiority','Patronizing students / Thunberg','Denialism','Costs','Other') 

df_table_3 <- df_table_3 %>% 
  mutate(Frame = factor(Frame, levels = tableorder)) %>%
  arrange(Frame)  

df_table_3 %>%
  janitor::adorn_totals("row") %>%
    kable() %>% 
    kable_classic(full_width = F, html_font = "Cambria")

```


Save Table to csv

```{r}
# Save the table
write.csv(df_table_3, "table_3.csv", row.names = FALSE)
```




# Table 4

Sequence of appearance of terms, categorizes bby frame categories over actor types

NOTE: a process of hand-validating the sequences found and featured in "term_sequence_urls.csv" was conducted so as to manually check and control the timestamps for "AfD_top" (since these were missing from the original data). At times this led to unclarity, which post or article came first (as indicated in the file, via various double featured counts).
This process produced the file "validated_sequence_analysis.xlsx" which was then used for further analysis and to create table 5 and figure 3.

```{r}
# Load the data
df_frame_sequences <- read_excel("validated_sequence_analysis.xlsx")
names(df_frame_sequences) <- c("frame","term","Hyperactive_Accounts","AfD_low","RNS","AfD_top")
# Melt the data to long format, to compute the rank for each type
df_long <- df_frame_sequences %>% 
  tidyr::pivot_longer(c("AfD_low", "AfD_top", "RNS", "Hyperactive_Accounts"), names_to = "type", values_to = "rank")

# Compute the count of each rank per type
summary_df <- df_long %>%
  group_by(type, rank) %>%
  summarise(count = n(), .groups = "drop")

# Pivot the table to get count for each rank (1-4) for each type
summary_df <- summary_df %>%
  tidyr::pivot_wider(names_from = rank, values_from = count, values_fill = 0)

# Print the summary data
summary_df  %>% 
      janitor::adorn_percentages("row")  %>%
  janitor::adorn_pct_formatting(digits = 0) %>%
  janitor::adorn_ns(position = "front")   %>%
  kable() %>% 
    kable_classic(full_width = F, html_font = "Cambria")

# save TABLE 4
write.csv(summary_df, "table_4.csv", row.names = FALSE)


```


# Figure 3 

Note: Based on the output of the generated dataframe/csv file below, we used an external visualization software (JMP) to crete the visualization for Figure 3. To our knowledge, R does not provide the necessary packages to reproduce an equivalent output.


```{r}

# Melt the data to long format, to compute the rank for each type
df_long <- df_frame_sequences %>%
  pivot_longer(cols = c("AfD_low", "AfD_top", "RNS", "Hyperactive_Accounts"), names_to = "type", values_to = "rank")

# Compute the count of each rank per frame and type
summary_df <- df_long %>%
  group_by(frame, type, rank) %>%
  summarise(count = n(), .groups = "drop")

# Pivot the table to get count for each rank (1-4) for each type and frame
summary_df <- summary_df %>%
  tidyr::unite("type_rank", type, rank, sep = "") %>%
  pivot_wider(id_cols = frame, names_from = type_rank, values_from = count, values_fill = list(count = 0))

# Reorder columns
summary_df <- summary_df %>%
  dplyr::select(frame, 
         "AfD_low1", "AfD_low2", "AfD_low3", "AfD_low4",
         "AfD_top1", "AfD_top2", "AfD_top3", "AfD_top4",
                 "Hyperactive_Accounts1", "Hyperactive_Accounts2", "Hyperactive_Accounts3", "Hyperactive_Accounts4",
          "RNS1", "RNS2", "RNS3", "RNS4")

# Create total observations column
summary_df$total_observations <- rowSums(summary_df[,2:17])

# Arrange rows based on total observations
summary_df <- summary_df %>%
  arrange(desc(total_observations))

# Remove total observations column
summary_df$total_observations <- NULL

# Print the summary data
b <- summary_df %>% 
  adorn_totals() 
b %>%
  filter(frame == 'Total') %>%
  t() %>%
  kable() %>% 
    kable_classic(full_width = F, html_font = "Cambria")

b %>%
   kable() %>% 
    kable_classic(full_width = F, html_font = "Cambria")
```



```{r}
## Save csv to FIGURE 3
write.csv(summary_df, "figure_3.csv", row.names = FALSE)

```


