1 Load Data

news_data <- read.csv("C:/Users/ASUS/Downloads/yahoo_news.csv", sep = ";")

2 Visualization of Data

# Load the necessary libraries
library(tm)
## Warning: package 'tm' was built under R version 4.3.3
## Loading required package: NLP
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(topicmodels)
## Warning: package 'topicmodels' was built under R version 4.3.3
library(wordcloud)
## Warning: package 'wordcloud' was built under R version 4.3.3
## Loading required package: RColorBrewer
library(ggplot2)
## 
## Attaching package: 'ggplot2'
## The following object is masked from 'package:NLP':
## 
##     annotate
library(lubridate)
## Warning: package 'lubridate' was built under R version 4.3.3
## 
## Attaching package: 'lubridate'
## The following objects are masked from 'package:base':
## 
##     date, intersect, setdiff, union
library(tidytext)
## Warning: package 'tidytext' was built under R version 4.3.3
library(tidyr)
library(sentimentr)
## Warning: package 'sentimentr' was built under R version 4.3.3
library(LDAvis)
## Warning: package 'LDAvis' was built under R version 4.3.3
# Convert titles to character type
news_data$titles <- as.character(news_data$titles)

# Create a corpus from the titles
titles <- Corpus(VectorSource(news_data$titles))

# Define custom stopwords
custom_stopwords <- c("pro", "us", "will", "is", "the", "to", "of", "in", 
                      "for", "with", "on", "at", "by", "an", "be", "as", "that", 
                      "from", "this", "it", "are", "was", "has", "have", "had", 
                      "were", "their", "his", "her", "he", "she", "they", 
                      "them", "our", "we", "you", "your", "and", "or", "whole")

# Text cleaning function
clean_text <- function(corpus) {
  corpus <- tm_map(corpus, content_transformer(tolower)) # Convert to lowercase
  corpus <- tm_map(corpus, removePunctuation) # Remove punctuation
  corpus <- tm_map(corpus, removeNumbers) # Remove numbers
  corpus <- tm_map(corpus, removeWords, custom_stopwords) # Remove common and custom stopwords
  corpus <- tm_map(corpus, stripWhitespace) # Remove extra whitespace
  return(corpus)
}

# Apply the cleaning function
clean_corpus <- clean_text(titles)
## Warning in tm_map.SimpleCorpus(corpus, content_transformer(tolower)):
## transformation drops documents
## Warning in tm_map.SimpleCorpus(corpus, removePunctuation): transformation drops
## documents
## Warning in tm_map.SimpleCorpus(corpus, removeNumbers): transformation drops
## documents
## Warning in tm_map.SimpleCorpus(corpus, removeWords, custom_stopwords):
## transformation drops documents
## Warning in tm_map.SimpleCorpus(corpus, stripWhitespace): transformation drops
## documents
# Convert corpus to a tidy text format
titles_content <- sapply(clean_corpus, as.character)
words <- unlist(strsplit(titles_content, "\\s+"))
words <- words[words != ""]

# Create a data frame for tidy text analysis
titles_df <- data.frame(text = titles_content, stringsAsFactors = FALSE)

2.1 Analisis Keyword Extraction

tokenized_titles <- titles_df %>%
  unnest_tokens(word, text) %>%
  count(word, sort = TRUE) %>%
  ungroup()
print(head(tokenized_titles, 10))
##          word  n
## 1           a 17
## 2       after 14
## 3        says 12
## 4         new  8
## 5       close  5
## 6  retirement  5
## 7        what  5
## 8       woman  5
## 9       years  5
## 10       city  4
tf_idf <- titles_df %>%
  mutate(document = row_number()) %>%
  unnest_tokens(word, text) %>%
  count(document, word, sort = TRUE) %>%
  bind_tf_idf(word, document, n) %>%
  arrange(desc(tf_idf))
print(head(tf_idf, 10))
##    document       word n        tf      idf    tf_idf
## 1        71  indonesia 1 0.2500000 4.553877 1.1384692
## 2        71     python 1 0.2500000 4.553877 1.1384692
## 3        71   swallows 1 0.2500000 4.553877 1.1384692
## 4        35       bank 2 0.2000000 4.553877 0.9107754
## 5         4    italian 2 0.1818182 4.553877 0.8279776
## 6        37      korea 2 0.1666667 4.553877 0.7589795
## 7         3    balance 1 0.1666667 4.553877 0.7589795
## 8         3  rocksolid 1 0.1666667 4.553877 0.7589795
## 9         3      sheet 1 0.1666667 4.553877 0.7589795
## 10       27 identified 1 0.1666667 4.553877 0.7589795

2.2 Top Keywords

# Plot top 10 keywords with the highest TF-IDF
tf_idf_top20 <- head(tf_idf, 10)
top_keywords <- tf_idf_top20 %>% mutate(word = reorder(word, tf_idf))

# Create plot with purple color and attractive background
ggplot(top_keywords, aes(x = word, y = tf_idf, fill = tf_idf)) +
  geom_col(show.legend = FALSE) +
  coord_flip() +
  scale_fill_gradient(low = "#E0BBE4", high = "#957DAD") +
  labs(title = "Top Keywords", x = "Keywords", y = "TF-IDF") +
  theme_minimal(base_size = 15) +
  theme(
    plot.title = element_text(hjust = 0.5, size = 17, face = "bold", color = "#351C4D"),
    axis.title.x = element_text(size = 10, face = "bold", color = "#351C4D"),
    axis.title.y = element_text(size = 10, face = "bold", color = "#351C4D"),
    axis.text.x = element_text(size = 12, color = "#351C4D"),
    axis.text.y = element_text(size = 12, color = "#351C4D"),
    plot.background = element_rect(fill = "#F4EAFF"),
    panel.grid.major = element_line(color = "#F4EAFF"),
    panel.grid.minor = element_blank()
  )

2.3 WordCloud Top Keywords

library(ggplot2)
library(ggwordcloud)
## Warning: package 'ggwordcloud' was built under R version 4.3.3
# Creating word cloud based on TF-IDF
wordcloud_data <- tf_idf %>%
  select(word, tf_idf) %>%
  arrange(desc(tf_idf)) %>%
  head(50) 

# Creating a word cloud with ggwordcloud
ggplot(wordcloud_data, aes(label = word, size = tf_idf, color = tf_idf)) +
  geom_text_wordcloud(area_corr_power = 1) +
  scale_size_area(max_size = 8) +
  scale_color_gradient(low = "#0071C9", high = "#823430") +
  theme_minimal() +
  theme(
    plot.background = element_rect(fill = "#D2B4DE", color = NA),
    panel.background = element_rect(fill = "#D2B4DE", color = NA),
    plot.title = element_text(hjust = 0.5, size = 20, face = "bold", color = "#41008F"),
    plot.margin = margin(8, 8, 8, 8)
  ) +
  labs(title = "Top Keywords") +
  theme(
    panel.grid.major = element_line(size = 0.5, linetype = 'solid', colour = "gray90"),
    panel.grid.minor = element_blank(),
    panel.background = element_rect(fill = "#F4EAFF")
  )
## Warning in geom_text_wordcloud(area_corr_power = 1): Ignoring unknown
## parameters: `area_corr_power`
## Warning: The `size` argument of `element_line()` is deprecated as of ggplot2 3.4.0.
## ℹ Please use the `linewidth` argument instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.

2.4 Topic Modeling

# Topic Modeling with LDA
dtm <- DocumentTermMatrix(clean_corpus)
lda_model <- LDA(dtm, k = 5, control = list(seed = 1234)) 

# Extract topics and terms
topics <- tidy(lda_model, matrix = "beta")

# Topic visualization
top_terms <- topics %>%
  group_by(topic) %>%
  top_n(10, beta) %>%
  ungroup() %>%
  arrange(topic, -beta)

# Visualization with ggplot2
top_terms %>%
  mutate(term = reorder_within(term, beta, topic)) %>%
  ggplot(aes(term, beta, fill = factor(topic))) +
  geom_col(show.legend = FALSE) +
  facet_wrap(~ topic, scales = "free") +
  coord_flip() +
  scale_x_reordered() +
  labs(title = "Top 10 Terms in Each Topic", x = "Terms", y = "Beta") +
  theme_minimal(base_size = 15) +
  theme(
    plot.title = element_text(hjust = 0.5, size = 17, face = "bold"),
    axis.title.x = element_text(size = 5, face = "bold"),
    axis.title.y = element_text(size = 5, face = "bold"),
    axis.text.x = element_text(size = 5),
    axis.text.y = element_text(size = 5)
  )

topic_terms <- topics %>%
  group_by(topic) %>%
  top_n(10, beta) %>%
  ungroup() %>%
  arrange(topic, -beta) %>%
  mutate(term = reorder(term, beta))

2.5 Topic Modeling Category

# Defining topic categories or labels
# For example, after looking at the top words, we assign the following labels:
topic_labels <- c("Technology", "Environment", "Economy", "Education", "Health")

# Display labels for each topic
top_terms <- top_terms %>%
  mutate(topic_label = factor(topic_labels[topic], levels = topic_labels))

# Visualization with topic labels
top_terms %>%
  mutate(term = reorder_within(term, beta, topic_label)) %>%
  ggplot(aes(term, beta, fill = factor(topic_label))) +
  geom_col(show.legend = FALSE) +
  facet_wrap(~ topic_label, scales = "free") +
  coord_flip() +
  scale_x_reordered() +
  labs(title = "Top 10 Terms in Each Topic", x = "Terms", y = "Beta") +
  theme_minimal(base_size = 15) +
  theme(
    plot.title = element_text(hjust = 0.5, size = 17, face = "bold"),
    axis.title.x = element_text(size = 5, face = "bold"),
    axis.title.y = element_text(size = 5, face = "bold"),
    axis.text.x = element_text(size = 5),
    axis.text.y = element_text(size = 5)
  )

2.6 Analisis Sentimen Headlines

# Convert data into frame data
titles_df <- data.frame(id = 1:length(titles_content), text = titles_content, stringsAsFactors = FALSE)

# Tokenization of text
tidy_titles <- titles_df %>%
  unnest_tokens(word, text)

# Using Bing's sentiment lexicon
bing_sentiments <- get_sentiments("bing")

# Combining text with Bing's sentiment lexicon
sentiments <- tidy_titles %>%
  left_join(bing_sentiments, by = "word")

# Replace NA with "neutral"
sentiments$sentiment[is.na(sentiments$sentiment)] <- "neutral"

# Count sentiment per title
sentiment_per_title <- sentiments %>%
  group_by(id) %>%
  summarize(sentiment_score = sum(ifelse(sentiment == "positive", 1, ifelse(sentiment == "negative", -1, 0))))

# Determining overall sentiment per title
sentiment_per_title <- sentiment_per_title %>%
  mutate(sentiment = case_when(
    sentiment_score > 0 ~ "positive",
    sentiment_score < 0 ~ "negative",
    TRUE ~ "neutral"
  ))

# Count the number of titles per sentiment
sentiment_summary <- sentiment_per_title %>%
  count(sentiment)

print(sentiment_summary)
## # A tibble: 3 × 2
##   sentiment     n
##   <chr>     <int>
## 1 negative     38
## 2 neutral      45
## 3 positive     12
# Sentiment result plot 
ggplot(sentiment_summary, aes(x = sentiment, y = n, fill = sentiment)) +
  geom_bar(stat = "identity", show.legend = FALSE) +
  scale_fill_manual(values = c("positive" = "#8E44AD", "negative" = "#9B59B6", "neutral" = "#D2B4DE")) +
  labs(title = "Sentiment Analysis of Yahoo News Headlines",
       x = "Sentiment",
       y = "Count") +
  theme_minimal(base_size = 15) +
  theme(
    plot.title = element_text(hjust = 0.5, face = "bold", size = 15, color = "#4A235A"),
    axis.title.x = element_text(face = "bold", size = 15, color = "#4A235A"),
    axis.title.y = element_text(face = "bold", size = 15, color = "#4A235A"),
    axis.text = element_text(size = 12, color = "#4A235A"),
    panel.grid.major = element_line(color = "white"),
    panel.grid.minor = element_blank(),
    panel.background = element_rect(fill = "white"),
    plot.background = element_rect(fill = "#F4EAFF", color = "#4A235A", size = 5),
    plot.margin = unit(c(2, 2, 2, 2), "cm")
  )
## Warning: The `size` argument of `element_rect()` is deprecated as of ggplot2 3.4.0.
## ℹ Please use the `linewidth` argument instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.

2.8 Analisis N-Gram

# Define custom stopwords
custom_stopwords <- c("pro", "us", "will", "is", "the", "to", "of", "in", "for", "with", 
                      "on", "at", "by", "an", "be", "as", "that", "s", "the", "from", 
                      "this", "it", "are", "was", "has", "have", "had", "were", "their", 
                      "his", "her", "he", "she", "they", "them", "our", "we", "you", 
                      "your", "and", "or", "whole","a","not", "more")

# Function to clean up the text
clean_text <- function(text) {
  text <- tolower(text) # Konversi ke huruf kecil
  text <- removePunctuation(text) # Hapus tanda baca
  text <- removeNumbers(text) # Hapus angka
  text <- removeWords(text, custom_stopwords) # Hapus stopwords khusus
  text <- stripWhitespace(text) # Hapus spasi berlebihan
  return(text)
}

# Apply text cleanup on contents column
news_data$clean_contents <- sapply(news_data$contents, clean_text)

# Tokenization of content into bigrams
bigrams <- news_data %>%
  unnest_tokens(bigram, clean_contents, token = "ngrams", n = 2)

# Counting the frequency of bigrams
bigram_freq <- bigrams %>%
  count(bigram, sort = TRUE)

# Display the 10 most frequently occurring bigrams
print(head(bigram_freq, 10))
##           bigram n
## 1    coast guard 5
## 2      las vegas 5
## 3        oil gas 4
## 4  arab emirates 3
## 5     chief paco 3
## 6  fresno police 3
## 7   gas industry 3
## 8    hours after 3
## 9       last two 3
## 10     last year 3

2.9 Most Frequent Bigrams in Article Content

ggplot(bigram_freq %>% head(10), aes(x = reorder(bigram, n), y = n, fill = n)) +
  geom_bar(stat = "identity", show.legend = FALSE) +
  coord_flip() +
  scale_fill_gradient(low = "#9B59B6", high = "#800080") +
  labs(title = "Most Frequent Bigrams in Article Content", x = "Bigram", y = "Frekuensi") +
  theme_minimal() +
  theme(
    plot.title = element_text(hjust = 0.5, size = 17, face = "bold", color = "#800080"),
    axis.title.x = element_text(size = 12, face = "bold", color = "#800080"),
    axis.title.y = element_text(size = 12, face = "bold", color = "#800080"),
    axis.text.x = element_text(size = 10, color = "#800080"),
    axis.text.y = element_text(size = 10, color = "#800080"),
    plot.background = element_rect(fill = "#F4EAFF", color = "#800080", size = 1, linetype = "solid"),
    panel.background = element_rect(fill = "#F4EAFF", color = "#800080", size = 1, linetype = "solid"),
    panel.grid.major = element_line(color = "#F4EAFF"),
    panel.grid.minor = element_blank()
  )

2.10 Sentiment Bigrams in Article Content

bing_sentiments <- get_sentiments("bing")

bigrams_separated <- bigrams %>%
  separate(bigram, into = c("word1", "word2"), sep = " ")

bigram_sentiments <- bigrams_separated %>%
  left_join(bing_sentiments, by = c("word1" = "word")) %>%
  rename(sentiment1 = sentiment) %>%
  left_join(bing_sentiments, by = c("word2" = "word")) %>%
  rename(sentiment2 = sentiment)

# Replace NA with "neutral"
bigram_sentiments <- bigram_sentiments %>%
  mutate(sentiment1 = ifelse(is.na(sentiment1), "neutral", sentiment1),
         sentiment2 = ifelse(is.na(sentiment2), "neutral", sentiment2))

# Calculate sentiment score per bigram
bigram_sentiments <- bigram_sentiments %>%
  mutate(sentiment_score = case_when(
    sentiment1 == "positive" & sentiment2 == "positive" ~ 2,
    sentiment1 == "negative" & sentiment2 == "negative" ~ -2,
    sentiment1 == "positive" & sentiment2 == "negative" ~ 0,
    sentiment1 == "negative" & sentiment2 == "positive" ~ 0,
    sentiment1 == "positive" | sentiment2 == "positive" ~ 1,
    sentiment1 == "negative" | sentiment2 == "negative" ~ -1,
    TRUE ~ 0
  ))

# Determining overall sentiment per bigram
bigram_sentiments <- bigram_sentiments %>%
  mutate(overall_sentiment = case_when(
    sentiment_score > 0 ~ "positive",
    sentiment_score < 0 ~ "negative",
    TRUE ~ "neutral"
  ))

# Count the number of bigrams per sentiment
sentiment_summary <- bigram_sentiments %>%
  count(overall_sentiment)

# Show sentiment summaries
print(sentiment_summary)
##   overall_sentiment    n
## 1          negative  239
## 2           neutral 2321
## 3          positive  141
# Plot sentiment summary
ggplot(sentiment_summary, aes(x = overall_sentiment, y = n, fill = overall_sentiment)) +
  geom_bar(stat = "identity", show.legend = FALSE) +
  scale_fill_manual(values = c("positive" = "#800080", "negative" = "#E0BBE4", "neutral" = "#9B59B6")) +
  labs(title = "Sentiment Bigrams in Article Content", x = "Sentiment", y = "Number of Bigrams") +
  theme_minimal() +
  theme(
    plot.title = element_text(hjust = 0.5, size = 17, face = "bold", color = "#800080"),
    axis.title.x = element_text(size = 12, face = "bold", color = "#800080"),
    axis.title.y = element_text(size = 12, face = "bold", color = "#800080"),
    axis.text.x = element_text(size = 10, color = "#800080"),
    axis.text.y = element_text(size = 10, color = "#800080"),
    plot.background = element_rect(fill = "#F4EAFF", color = NA),
    panel.background = element_rect(fill = "#F4EAFF", color = NA),
    panel.grid.major = element_line(color = "#D3CCE3"),
    panel.grid.minor = element_blank()
  )