Executive summary

Generative AI has become an indispensable part of our daily lives. According to statistics released by Adobe in April 2024, 53% of Americans have used generative models, with 81% applying them for personal tasks, 30% for work, and 17% for academic purposes. As the market has grown, the variety of generative AI services has diversified, and competition has intensified. As of January 2025, there are approximately 67,000 companies related to generative AI. In this vast generative AI market, it is crucial to first understand how different generative AI apps are perceived by users in terms of their key characteristics and satisfaction to deliver attractive services to consumers. Accordingly, the primary research question set for this study was: “How are five generative apps positioned among users in terms of satisfaction and use?” To address this, the following sub-questions were developed:

  1. What emotions do people feel toward each generative AI app, and how satisfied are they?
  2. What differentiating features and characteristics do users perceive in each generative AI app?
  3. Which features or functions of each generative AI app do users find satisfying or unsatisfying?

To answer these questions, sentiment analysis, tf-idf analysis, and bigram analysis were conducted. The results, based on reviews written in early 2024, allowed for the classification of the five AI apps by satisfaction and use. Satisfaction was defined by how positive users’ emotions were toward each app. Use was determined by whether users recognized the app’s primary purpose as new content generation, or whether they perceived it as serving other purposes such as information retrieval or as an assistant. Using the ‘afinn’ lexicon, average sentiment scores were calculated, ranking the apps as follows: Copilot, ChatGPT, BingAI, DaVinci, and Gemini. The apps were mapped according to the actual gaps in sentiment scores. For ChatGPT, tf-idf and bigram analyses confirmed active use for information retrieval, image generation, and essay writing. Copilot showed similar patterns to ChatGPT but with relatively more mentions of image generation. Bing was mainly used for search purposes, with significantly more mentions related to app-tech and auxiliary uses. DaVinci was found to be specialized in image and design generation based on frequently mentioned words. Lastly, Gemini was primarily used as a replacement for Google Assistant to assist with phone functions, with dissatisfaction noted regarding its generative features such as note creation. This analysis was conducted using reviews collected in early 2024, so its timeliness may be somewhat limited. The uneven distribution of review counts, platforms, and collection periods also makes generalization difficult. Additionally, it was challenging to compare app-specific differentiators with other AI services provided on computers using reviews alone. Therefore, collecting more recent reviews—including those from communities like Reddit—and conducting positioning analyses across more than two dimensions would be a meaningful direction for future research.

Data background

The review data for generative AI apps was downloaded from Kaggle, extracted by Reham Alabduljabbar. The dataset consists of reviews from users of five generative AI applications: ChatGPT, Bing AI, Microsoft Co-Pilot, Gemini AI, and Da Vinci AI. User reviews were collected from the App Store and Google Play between January 2023 and March 2024, all written in English by users in the United States. The distribution of review periods and platforms varies by app, as shown in the accompanying table. Notably, no reviews for Gemini were collected from the App Store, and only one review for DaVinci was collected from Google Play. Additionally, Gemini reviews were collected only in March 2024, and Copilot reviews were collected starting from December 2023, unlike the other apps. Investigation of app release dates confirmed that Gemini was not available on the App Store until November 2024, making review collection before March 2024 impossible. On Google Play, Gemini was released in early February 2024, and review activity increased from March onward.

The dataset used for analysis consists of a total of 7,736 reviews (rows) and 12 columns: ‘No.of.reviews’, ‘review_id’ (reviewer ID), ‘source’ (review platform), ‘review_title’, ‘review_description’, ‘rating’ and ‘thumbs_up’ (satisfaction ratings from the app installation platform), ‘review_date’, ‘appVersion’, ‘language_code’, ‘country_code’, and ‘App_name’ (type of generative AI app)

Data loading, cleaning and preprocessing

ai_review <- read.csv("ai_app_review.csv", encoding = "UTF-8")
head(ai_review)
##   No.of.reviews                            review_id      source review_title
## 1             0 d2676cae-da30-4e57-9a05-21ee5cd78495 Google Play             
## 2             1 b313673d-5446-4479-acc0-5c780181d482 Google Play             
## 3             2 9bce6943-4a9e-4fc1-a2f7-eef460fd1b1d Google Play             
## 4             3 eb57122a-2da6-4d25-8610-f211bf06fbe1 Google Play             
## 5             4 c70e4556-b060-4538-bde3-6b272a18153c Google Play             
## 6             5 0b91d0fb-d2e3-46fc-9916-8ec75e51e6fd Google Play             
##                                                                                                                                                                                                                                                                                                                                                     review_description
## 1 quite refreshing and impressive but not sure why the search function stuck halfway, failed to load any hit even after retrying, not sure if it's the issue with my phone or internet connection but when this happen and i try Chrome it works well and fast. keep up the good job... But why it keeps force closing when I'm streaming video as well as tab freeze.
## 2                                                                                                                                                                                                                                                                                                                                                               nice 🙂
## 3                                                                                                                                                                                                                                                                                                                                                                   ✌🏻
## 4                                                                                                                                                                                                                                                                                                                              very good and intresting .i lov rashiya
## 5                                                                                                                                                                                                     In Bing news, there is no way to translate it, in Google news, there is option to translate it just like I do on browser. Please add basic features it's a shame
## 6                                                                                                                                                                                                                                                                                      it's help me with all my mathematics assignment so for it's a solid five star 🌟
##   rating thumbs_up     review_date     appVersion laguage_code country_code
## 1      3         0 3/24/2024 18:05 28.0.420319014           en           us
## 2      1         0 3/24/2024 18:04 28.0.420319010           en           us
## 3      5         0 3/24/2024 17:42 27.9.420301046           en           us
## 4      5         0 3/24/2024 17:17 27.9.420301046           en           us
## 5      2         0 3/24/2024 16:58                          en           us
## 6      5         0 3/24/2024 16:52 27.9.420301046           en           us
##   App_name
## 1   BingAI
## 2   BingAI
## 3   BingAI
## 4   BingAI
## 5   BingAI
## 6   BingAI
review_clean <- ai_review %>%
  mutate(date = mdy_hm(review_date)) %>%  
  mutate(date = as.Date(date)) %>%
  mutate(rating = as.numeric(rating)) %>%
  rename(number = No.of.reviews, platform = source, app = App_name) %>%
  select(number, platform, review_description, rating, date, app) %>%
  filter(app %in% c("BingAI", "DAVINCI", "chatgpt", "copilot", "gemini")) %>%
  filter(str_length(review_description) >= 5)
head(review_clean)
##   number    platform
## 1      0 Google Play
## 2      1 Google Play
## 3      3 Google Play
## 4      4 Google Play
## 5      5 Google Play
## 6      6 Google Play
##                                                                                                                                                                                                                                                                                                                                                     review_description
## 1 quite refreshing and impressive but not sure why the search function stuck halfway, failed to load any hit even after retrying, not sure if it's the issue with my phone or internet connection but when this happen and i try Chrome it works well and fast. keep up the good job... But why it keeps force closing when I'm streaming video as well as tab freeze.
## 2                                                                                                                                                                                                                                                                                                                                                               nice 🙂
## 3                                                                                                                                                                                                                                                                                                                              very good and intresting .i lov rashiya
## 4                                                                                                                                                                                                     In Bing news, there is no way to translate it, in Google news, there is option to translate it just like I do on browser. Please add basic features it's a shame
## 5                                                                                                                                                                                                                                                                                      it's help me with all my mathematics assignment so for it's a solid five star 🌟
## 6                                                                                                                                                                                                                                                                                                                                                               sahi h
##   rating       date    app
## 1      3 2024-03-24 BingAI
## 2      1 2024-03-24 BingAI
## 3      5 2024-03-24 BingAI
## 4      2 2024-03-24 BingAI
## 5      5 2024-03-24 BingAI
## 6      3 2024-03-24 BingAI
tidy_review <- review_clean %>%
  unnest_tokens(input = review_description, output = word, drop = F) %>%
  anti_join(stop_words) %>%
  select(number, app, word, rating, date, platform)
## Joining with `by = join_by(word)`
head(tidy_review)
##   number    app       word rating       date    platform
## 1      0 BingAI refreshing      3 2024-03-24 Google Play
## 2      0 BingAI impressive      3 2024-03-24 Google Play
## 3      0 BingAI     search      3 2024-03-24 Google Play
## 4      0 BingAI   function      3 2024-03-24 Google Play
## 5      0 BingAI      stuck      3 2024-03-24 Google Play
## 6      0 BingAI    halfway      3 2024-03-24 Google Play

Text data analysis

Anaysis and Figure 1

To address the first research question, sentiment analysis was performed using the ‘afinn’ lexicon, which measures both the direction (positive/negative) and intensity of emotions, enabling more precise and intuitive sentiment evaluation.

1. Comparison of Sentiment Score and Rating Score

By comparing sentiment scores, I aimed to quantitatively assess the emotions users experienced while using each app. Additionally, by comparing the sentiment scores from app reviews with the rating scores provided on each platform, I sought to determine whether the sentiment expressed in written feedback was consistent with the users’ formal satisfaction ratings. For objectivity, the sentiment score was defined as the mean of the AFINN sentiment values, and the rating score as the mean of the rating values.

Graph layout

For visualization, a scatter plot was chosen, with the x-axis representing sentiment score and the y-axis representing rating score, so that the distribution between the two values could be easily observed. To enhance readability, the axis labels were revised, the ‘theme_minimal’ function was used to simplify the background, and the size of the points and app names was adjusted.

Result analysis

The results showed that all sentiment scores were above zero but generally clustered around one, indicating that overall sentiment was not particularly high. Sentiment scores were highest for Copilot, followed by ChatGPT, BingAI, DaVinci, and Gemini. Rating scores were highest for ChatGPT, then Copilot, BingAI, DaVinci, and Gemini. Except for Copilot and ChatGPT, there was a general trend that higher sentiment scores corresponded to higher rating scores, suggesting that users who used more positive words in their reviews also tended to give higher satisfaction ratings. To further refine the analysis, I examined whether there were differences between platforms. On the App Store, ChatGPT had the highest sentiment and rating scores, and DaVinci’s sentiment score was higher than BingAI’s, which differed from the combined results. On Google Play, the ranking was different: DaVinci, ChatGPT, Copilot, BingAI, and Gemini, in that order. I also examined how sentiment scores changed over time. For ChatGPT and BingAI, sentiment scores decreased and then increased again in March; for DaVinci, scores remained low until a sharp increase in March; and for Copilot, sentiment steadily increased after January. Across all apps, rating scores fluctuated greatly over time, which may reflect significant functional updates around February. While I could not find public update records for BingAI, there were documented updates for ChatGPT, Copilot, and DaVinci in February 2024. However, because the distribution of reviews by platform and time was not uniform for all apps, these results should be interpreted with caution. In particular, since there were no App Store reviews for Gemini, it was not possible to determine whether platform differences affected its evaluation.

1-1) Comparison of Average Sentiment Score and Rating by Generative AI App(combined Google Play and the App Store)

afinn <- get_sentiments("afinn")
afinn
## # A tibble: 2,477 × 2
##    word       value
##    <chr>      <dbl>
##  1 abandon       -2
##  2 abandoned     -2
##  3 abandons      -2
##  4 abducted      -2
##  5 abduction     -2
##  6 abductions    -2
##  7 abhor         -3
##  8 abhorred      -3
##  9 abhorrent     -3
## 10 abhors        -3
## # ℹ 2,467 more rows
sentiment_rating <- tidy_review %>%
  inner_join(afinn) %>%
  select(app, word, value, rating) %>%
  group_by(app) %>%
  summarise(sentiment_score = mean(value, na.rm = TRUE), 
            rating_score = mean(rating, na.rm = TRUE))
## Joining with `by = join_by(word)`
sentiment_rating 
## # A tibble: 5 × 3
##   app     sentiment_score rating_score
##   <chr>             <dbl>        <dbl>
## 1 BingAI            0.951         3.36
## 2 DAVINCI           0.621         2.82
## 3 chatgpt           1.16          4.05
## 4 copilot           1.17          3.80
## 5 gemini            0.174         2.47
sentiment_rating <- ggplot(sentiment_rating, aes(x = sentiment_score, y = rating_score, label = app)) +
  geom_point(size = 5, color = "steelblue") +
  geom_text(vjust = -0.5, size = 4) +
  labs(x = "Sentiment Score",
    y = "Rating Score",
    title = "Comparison of Average Sentiment Score and Rating by Generative AI App") +
  theme_minimal() +
  theme(plot.title = element_text(face = "bold", hjust = 0.5, size = 12),
         plot.margin = margin(t = 20, r = 20, b = 20, l = 20))
sentiment_rating

ggsave(filename = "Comparison of Average Sentiment Score and Rating by Generative AI App.png", 
       plot = sentiment_rating, width = 10, height = 6, dpi = 300, units = "in", bg = "white")

1-2) Comparison of Average Sentiment Score and Rating in APP store

ios_sentiment_rating <- tidy_review %>%
  inner_join(afinn) %>%
  select(app, word, value, rating, platform) %>%
  group_by(app) %>%
  filter(platform == "App Store") %>%
  summarise(sentiment_score = mean(value, na.rm = TRUE), 
            rating_score = mean(rating, na.rm = TRUE))
## Joining with `by = join_by(word)`
ios_sentiment_rating 
## # A tibble: 4 × 3
##   app     sentiment_score rating_score
##   <chr>             <dbl>        <dbl>
## 1 BingAI            0.584         3.16
## 2 DAVINCI           0.616         2.81
## 3 chatgpt           0.938         3.93
## 4 copilot           0.789         3.42
ios_sentiment_rating <- ggplot(ios_sentiment_rating, aes(x = sentiment_score, y = rating_score, label = app)) +
  geom_point(size = 5, color = "orange") +
  geom_text(vjust = -0.5, size = 4) +
  labs(x = "Sentiment Score",
    y = "Rating Score",
    title = "Comparison of Average Sentiment Score and Rating in APP store") +
  theme_minimal() +
  theme(plot.title = element_text(face = "bold", hjust = 0.5, size = 12),
        plot.margin = margin(t = 20, r = 20, b = 20, l = 20))
ios_sentiment_rating 

ggsave(filename = "Comparison of Average Sentiment Score and Rating in APP store.png", 
       plot = ios_sentiment_rating, width = 10, height = 6, dpi = 300, units = "in", bg = "white")

3) Comparison of Average Sentiment Score and Rating in Google Play

android_sentiment_rating <- tidy_review %>%
  inner_join(afinn) %>%
  select(app, word, value, rating, platform) %>%
  group_by(app) %>%
  filter(platform == "Google Play") %>%
  summarise(sentiment_score = mean(value, na.rm = TRUE), 
            rating_score = mean(rating, na.rm = TRUE))
## Joining with `by = join_by(word)`
android_sentiment_rating 
## # A tibble: 5 × 3
##   app     sentiment_score rating_score
##   <chr>             <dbl>        <dbl>
## 1 BingAI            1.26          3.53
## 2 DAVINCI           2.33          5   
## 3 chatgpt           1.85          4.43
## 4 copilot           1.64          4.26
## 5 gemini            0.174         2.47
android_sentiment_rating <- ggplot(android_sentiment_rating, aes(x = sentiment_score, y = rating_score, label = app)) +
  geom_point(size = 5, color = "tomato") +
  geom_text(vjust = -0.5, size = 4) +
  labs(x = "Sentiment Score",
    y = "Rating Score",
    title = "Comparison of Average Sentiment Score and Rating in Google Play") +
  theme_minimal() +
  theme(plot.title = element_text(face = "bold", hjust = 0.5, size = 12),
        plot.margin = margin(t = 20, r = 20, b = 20, l = 20))
android_sentiment_rating 

ggsave(filename = "Comparison of Average Sentiment Score and Rating in Google Play.png", 
       plot = android_sentiment_rating, width = 10, height = 6, dpi = 300, units = "in", bg = "white")

1-4) Monthly sentiment score changes by Generative AI app

sentiment_monthly <- tidy_review %>%
  inner_join(afinn) %>%
  mutate(date = floor_date(date, "month"), 
    date = format(date, "%Y-%m")) %>%
  group_by(app, date) %>%
  summarise(sentiment_score = mean(value, na.rm = TRUE)) %>%
  ungroup()
## Joining with `by = join_by(word)`
## `summarise()` has grouped output by 'app'. You can override using the `.groups`
## argument.
sentiment_monthly
## # A tibble: 14 × 3
##    app     date    sentiment_score
##    <chr>   <chr>             <dbl>
##  1 BingAI  2024-01          0.727 
##  2 BingAI  2024-02          0.558 
##  3 BingAI  2024-03          1.14  
##  4 DAVINCI 2024-01         -0.0208
##  5 DAVINCI 2024-02         -0.0154
##  6 DAVINCI 2024-03          1.31  
##  7 chatgpt 2024-01          1.06  
##  8 chatgpt 2024-02          0.801 
##  9 chatgpt 2024-03          1.49  
## 10 copilot 2023-12          0.657 
## 11 copilot 2024-01          0.473 
## 12 copilot 2024-02          0.751 
## 13 copilot 2024-03          1.37  
## 14 gemini  2024-03          0.174
sentiment_monthly <- ggplot(sentiment_monthly, aes(x = as.Date(paste0(date, "-01")), 
                             y = sentiment_score)) +
  geom_line(aes(color = app), linewidth = 1.2, show.legend = F) +          
  geom_point(aes(color = app), size = 3, show.legend = F) +               
  facet_wrap(~ app, , scales = "free", ncol = 3) +                           
  scale_x_date(date_breaks = "1 month",                  
               date_labels = "%Y-%m") +
  scale_color_manual(values = c(
  "chatgpt" = "#ab68ff",
  "copilot" = "#09AA6C",
  "BingAI"  = "#ffb900",
  "DAVINCI" = "#0970E9",
  "gemini"  = "#CA6673")) +           
  labs(
    title = "Monthly sentiment score changes by Generative AI app",
    x = "month",
    y = "sentiment score"
  ) +
  theme(axis.text.x = element_text(angle = 45, hjust = 1),   
        plot.title = element_text(face = "bold", hjust = 0.5, size = 12),
        plot.margin = margin(t = 20, r = 20, b = 20, l = 20))
sentiment_monthly
## `geom_line()`: Each group consists of only one observation.
## ℹ Do you need to adjust the group aesthetic?

ggsave(filename = "Monthly sentiment score changes by Generative AI app.png", 
       plot = sentiment_monthly, width = 10, height = 6, dpi = 300, units = "in", bg = "white")
## `geom_line()`: Each group consists of only one observation.
## ℹ Do you need to adjust the group aesthetic?

2. Comparison of Sentiment Words for Each Generative AI App

To gain a more detailed understanding of the specific emotions and evaluations users had for each Generative AI app, I compared the top 10 sentiment words used in the reviews of each app.

Graph layout

For visualization, since there were several apps, I used facet_wrap with free scales to examine the distribution of sentiment words within each app. I adjusted the margins for readability, hid the legend, and sorted the bars by frequency so that the results could be easily interpreted. I also used distinctive brand colors for each app and arranged the graphs in order of rating score to avoid confusion.

Result analysis

The analysis revealed that for ChatGPT, “love” was the most frequently used sentiment word, with “helpful” and “help” also ranking highly, suggesting that users found the app very useful in their work. However, “wrong” also appeared among the top ten, indicating that negative experiences were present as well. Copilot also had “love” as the most frequent sentiment word, with many positive words overall, but “wrong” was again among the top ten. For BingAI, “love” was the most common, and other positive words such as “nice,” “amazing,” “awesome,” “helpful,” and “excellent” were also prominent, with no negative sentiment words among the top ten. DaVinci’s reviews included both positive and negative words, such as “limited” and “waste.” In contrast, Gemini’s top sentiment words included more negative terms like “useless,” “worse,” and “bad,” which may be because reviews were collected only a month after its release, when users were more likely to report dissatisfaction. These findings can also be related to the earlier comparison of sentiment and rating scores. Copilot had a higher sentiment score than ChatGPT, and both had many positive sentiment words, but ChatGPT’s rating score was higher. This may be due to ChatGPT’s reputation as an innovative generative AI, which could have influenced users’ expectations and ratings. Overall, while sentiment analysis provided insight into user perceptions, the limitations of the data—such as uneven review distribution by platform and time—mean that the results should be interpreted with care.

ai_sentiment <- tidy_review %>%
  inner_join(afinn) %>%
  count(app, word, sort = TRUE) %>%
  group_by(app) %>%
  slice_max(n, n = 10, with_ties = FALSE) %>%
  ungroup()
## Joining with `by = join_by(word)`
ai_sentiment 
## # A tibble: 50 × 3
##    app    word          n
##    <chr>  <chr>     <int>
##  1 BingAI love         65
##  2 BingAI nice         54
##  3 BingAI rewards      46
##  4 BingAI amazing      30
##  5 BingAI easy         28
##  6 BingAI awesome      24
##  7 BingAI excellent    22
##  8 BingAI helpful      22
##  9 BingAI free         21
## 10 BingAI fun          21
## # ℹ 40 more rows
ai_sentiment$app <- factor(ai_sentiment$app, levels = c("chatgpt", "copilot", "BingAI", "DAVINCI", "gemini"))

ai_sentiment <- ggplot(ai_sentiment, aes(x = reorder_within(word, n, app), y = n, fill = app)) +
  geom_col(show.legend = FALSE) +
  facet_wrap(~ app, scales = "free", ncol = 3) +
  scale_x_reordered() +
  coord_flip() + 
  scale_fill_manual(values = c(
    "chatgpt" = "#ab68ff",
    "copilot" = "#09AA6C",
    "BingAI"  = "#ffb900",
    "DAVINCI" = "#0970E9",
    "gemini"  = "#CA6673")) +
  labs(title = "Comparison of sentiment words by Generative AI apps", x = "Word", y = "Count") +
  theme(plot.title = element_text(face = "bold", hjust = 0.5, size = 16),
         plot.margin = margin(t = 20, r = 20, b = 20, l = 20))
ai_sentiment

ggsave(filename = "Comparison of sentiment words by Generative AI apps.png", 
       plot = ai_sentiment, width = 12, height = 8, dpi = 300, units = "in", bg = "white")

Analysis and Figure 2

1. tf-idf Analysis

To address the second research question, a tf-idf analysis was conducted to identify words that are rare across all reviews but frequent within each app’s reviews. Since the dataset included multiple documents (apps), tf-idf was chosen over log odds ratio. Stop words and irrelevant terms (e.g., contractions like it’s, I’m) were filtered out before visualization.

Graph layout

Due to the number of apps, facet_wrap was used with free scales instead of a shared y-axis to examine the distribution of tf-idf words within each app. To enhance readability, margins were adjusted, legends were hidden, and labels were angled to prevent overlap caused by long tf-idf values (up to three decimal places). Bars were sorted by frequency for quick interpretation. Each app was assigned a distinct color based on its brand identity to improve differentiation. The graphs were ordered by the apps’ rating scores, consistent with the sentiment analysis, to minimize confusion.

Result analysis

App names themselves frequently appear as top words due to review context. However, for BingAI, the term copilot was also prominent, suggesting a strong association. This aligns with the fact that both BingAI and Copilot are Microsoft products. The presence of microsoft among BingAI’s top tf-idf words further supports this, especially after Microsoft rebranded Bing as “Microsoft Copilot” in November 2023. In the case of ChatGPT, words such as ‘question’, ‘answer’, ‘school’, ‘essay’, and ‘custom’ were frequently extracted. From this, it can be inferred that users are utilizing AI functions in a question-and-answer format and mainly using ChatGPT for essay writing. As for ‘custom’ and ‘log’, since they are often used with multiple meanings, more detailed analysis is required. In the case of Copilot, similar to Bing, words related to its developer, such as ‘Microsoft’ and ‘365’, were extracted as top-ranking terms. Words like ‘chat’ and ‘gpt’ also appeared frequently, indicating that Copilot is often mentioned alongside ChatGPT. In fact, it was observed that many data/IT-related platforms such as DataNorth, DataCamp, and A-Zapier have published content comparing Copilot and ChatGPT. Similar to ChatGPT, the frequent appearance of ‘questions’ and ‘answer’ suggests that it is also used in a Q&A format. In addition, the frequent use of the word ‘create’ implies that Copilot is also utilized for content generation. For Bing, words like ‘rewards’, ‘earn’, and ‘receipt’ were frequently extracted. This is likely due to the ‘Bing Deal’ feature, which allows users to scan receipts to receive paybacks. It was also confirmed that a significant number of videos related to receipt scanning using Bing have been uploaded on TikTok. Since terms like ‘receipts’ and ‘notifications’ can be used in various contexts, further in-depth analysis is needed. In the case of DaVinci, the word ‘tattoo’ was among the top-ranked terms, which can be attributed to DaVinci’s tattoo design feature. Additionally, the frequent use of words such as ‘avatars’, ‘styles’, ‘create’, and ‘artwork’ once again confirms DaVinci’s specialization in generating artistic content. Since the word ‘trial’ can also be used in various situations, additional analysis is required. For Gemini, words like ‘assistant’ and ‘Google’s’ were extracted as high-frequency terms. This is likely because, after installing the Gemini app, it takes over functions previously provided by Google Assistant, such as voice commands, schedule management, and information search. As a result, the two are frequently mentioned together. Words like ‘reminders’ and ‘replace’ were also likely to be extracted for this reason. Additionally, ‘Spotify’ and ‘song’ appeared often, which can be explained by the fact that Gemini is linked with the Spotify app, allowing users to play music via voice commands.

ai_tf_idf <- tidy_review %>%
  count(app, word, sort = T) %>%
  bind_tf_idf(term = word,           
              document = app,  
              n = n) %>%
  mutate(word_clean = tolower(
    str_replace_all(word, "['’‘`]", "") %>%
      str_replace_all("[[:punct:]]", "")
  )) %>%
  filter(!word_clean %in% c("im", "its", "ive", "aaaaaaaaaaaaaaa")) %>%
  group_by(app) %>%
  arrange(desc(tf_idf)) %>%
  slice_max(tf_idf, n = 10, with_ties = F) 
ai_tf_idf
## # A tibble: 50 × 7
## # Groups:   app [5]
##    app    word              n      tf   idf  tf_idf word_clean   
##    <chr>  <chr>         <int>   <dbl> <dbl>   <dbl> <chr>        
##  1 BingAI rewards          46 0.00625 0.916 0.00573 rewards      
##  2 BingAI bing            161 0.0219  0.223 0.00488 bing         
##  3 BingAI earn             18 0.00245 0.916 0.00224 earn         
##  4 BingAI tabs             10 0.00136 1.61  0.00219 tabs         
##  5 BingAI receipt           9 0.00122 1.61  0.00197 receipt      
##  6 BingAI receipts          9 0.00122 1.61  0.00197 receipts     
##  7 BingAI reward           15 0.00204 0.916 0.00187 reward       
##  8 BingAI copilot          61 0.00829 0.223 0.00185 copilot      
##  9 BingAI microsoft        59 0.00802 0.223 0.00179 microsoft    
## 10 BingAI notifications    14 0.00190 0.916 0.00174 notifications
## # ℹ 40 more rows
ai_tf_idf$app <- factor(ai_tf_idf$app, levels = c("chatgpt", "copilot", "BingAI", "DAVINCI", "gemini"))

ai_tf_idf <- ggplot(ai_tf_idf, aes(x = reorder_within(word, tf_idf, app),
                  y = tf_idf,
                  fill = app)) +
  geom_col(width = 0.7, show.legend = F) +
  coord_flip() +
  facet_wrap(~ app, scales = "free", ncol = 3) +
  scale_x_reordered() +
  scale_fill_manual(values = c(
    "chatgpt" = "#ab68ff",
    "copilot" = "#09AA6C",
    "BingAI"  = "#ffb900",
    "DAVINCI" = "#0970E9",
    "gemini"  = "#CA6673")) +
  labs(title = "Top 10 words in tf-idf by Generative AI App",
       x = "Words", y = "tf-idf") +
  theme(axis.text.x = element_text(angle = 45, hjust = 1),
        plot.title = element_text(face = "bold", hjust = 0.5, size = 16),
        plot.margin = margin(t = 20, r = 20, b = 20, l = 20))
ai_tf_idf

ggsave("Top 10 words in tf-idf by Generative AI App.png", 
       plot = ai_tf_idf, width = 10, height = 6, dpi = 300, units = "in", bg = "white")

2. Bigram analysis

As discussed above, TF-IDF allowed for a simple identification of the characteristics of each app, but it was sometimes difficult to understand the precise context in which certain words were used. To analyze the perceived features of each app in more detail, bigram analysis was conducted. Since bigrams capture two words used consecutively, they are more useful in understanding contextual meaning.

Graph layout

Initially, each app’s name appeared so frequently that bigram networks were formed mainly around those names. Therefore, the app names were removed prior to building the bigram networks. To ensure readability and preserve sufficient information for each app, a frequency filter was applied: 10 for ChatGPT, 5 for Copilot, and 4 for Bing, DaVinci, and Gemini. A similar number of bigrams was extracted for each app with these settings. Relatively more bigrams were observed in Copilot and ChatGPT, which is likely due to the larger number of total reviews for these two apps, as previously confirmed. To avoid confusion, brand colors were used for each app in the graphs, as applied above, and care was taken to prevent overlapping between words and nodes to improve readability. Additionally, the edges were darkened according to frequency, making the results easier to interpret.

Result analysis

Chatgpt

For Chatgpt, the context of the word ‘custom’, which was previously unclear, was clarified. The term ‘custom instructions’ was frequently used in reviews, suggesting that users recognize the feature that allows them to inform ChatGPT of their preferences, roles, and desired answer formats in advance, so that responses are tailored accordingly in future conversations. Also, frequent use of terms like ‘paid version’ and ‘free version’ indicates user awareness of differences between subscription plans. Moreover, phrases such as ‘real time’ and ‘images generate’ show that users perceive real-time responses and image generation features as notable aspects. Positive evaluations were also evident, with frequent bigrams like ‘user friendly’, ‘game changer’, and ‘highly recommend’.

Copilot

For Copilot, bigrams such as ‘create images’, ‘image generation’, and ‘search results’ were widely used, indicating core functionalities of Copilot like image creation and information retrieval. Compared to other generative AI apps like ChatGPT, Gemini, or Bing—which are not primarily focused on image creation—Copilot had more image-related bigrams, suggesting that users actively use it for generating images. Furthermore, as Copilot is integrated with the Bing search engine and allows real-time information retrieval, it is reasonable to assume that bigrams like ‘search engine’ appeared frequently. Like ChatGPT, positive expressions such as ‘user friendly’, ‘highly recommend’, and ‘game changer’ were also commonly found.

BingAI

For Bing, the frequent extraction of the term ‘search engine’ indicates that users mainly use Bing for information retrieval. The mention of ‘edge app’ also appeared often, reflecting Bing’s integration with the Edge browser. Bigrams such as ‘microsoft rewards’ and ‘gift card’ refer to app-based incentive features. Through the BingAI app, users can earn points from Microsoft by performing searches, which can be redeemed for various gift cards. This suggests that users perceive Bing not only as an AI app but also as a broader platform including reward-based utility features.

DaVinci

For DaVinci, as expected, many bigrams were related to image and design generation, confirming DaVinci’s focus on artistic content creation. Bigrams such as ‘tattoo ideas’, ‘AI art’, and ‘image generator’ were frequently found. DaVinci was also mentioned alongside another image-generation AI system, ‘Mid Journey’. Compared to other apps, there were more references to pricing plans, including bigrams like ‘free version’, ‘pro version’, ‘daily limit’, ‘yearly subscription’, ‘free trial’, and ‘don’t waste’. These reflect user awareness of DaVinci’s limitations, such as image generation quotas even under paid plans and restrictions in the free version. This may also explain the relatively lower user ratings for DaVinci.

Gemini

For Gemini, the most frequently used term was ‘Google Assistant’, and many bigrams were related to assistant functions and user satisfaction, such as ‘voice commands’, ‘hands free’, ‘hey Google’, ‘set reminders’, and ‘lock screen’. The bigram ‘forever takes’ was also extracted, indicating dissatisfaction; this aligns with blog reviews at the time that complained Gemini’s AI note-taking feature was very slow to respond. This could be one of the reasons for Gemini’s lower rating and sentiment scores, as observed earlier. Although Gemini also provides information search features, the majority of extracted bigrams were related to basic mobile assistant functions, suggesting that users primarily perceived Gemini as an assistant-like service during the analysis period.

#Chat GPT
chatgpt_bigram <- review_clean %>%
  filter(app == "chatgpt") %>%
  unnest_tokens(input = review_description,
                output = bigram,
                token = "ngrams",
                n = 2) %>%
  separate(bigram, c("word1", "word2"), sep = " ") %>%
  filter(!word1 %in% c("br", stop_words$word, "chatgpt"),
         !word2 %in% c("br", stop_words$word, "chatgpt")) %>%
  count(word1, word2, sort = T) %>%
  na.omit() %>%
  filter(n >= 10) %>%
  graph_from_data_frame()
chatgpt_bigram
## IGRAPH 56b03d6 DN-- 34 24 -- 
## + attr: name (v/c), n (e/n)
## + edges from 56b03d6 (vertex names):
##  [1] chat      ->gpt          nice      ->app          voice     ->chat        
##  [4] 5         ->stars        game      ->changer      highly    ->recommend   
##  [7] gpt       ->4            free      ->version      ai        ->app         
## [10] real      ->time         user      ->friendly     amazing   ->app         
## [13] app       ->it’s        helpful   ->app          absolutely->love        
## [16] love      ->chat         custom    ->instructions generate  ->images      
## [19] 5         ->star         app       ->i’ve        excellent ->app         
## [22] it’s     ->amazing      mind      ->blowing      paid      ->version
set.seed(2023)
a <- grid::arrow(type = "closed", length = unit(.15, "inches"))

chatgpt_bigram <- ggraph(chatgpt_bigram, layout = "fr") +
  geom_edge_link(aes(edge_alpha = n), show.legend = FALSE,
                 arrow = a, end_cap = circle(.07, 'inches')) +
  geom_node_point(color = "#ab68ff", size = 5) +
  geom_node_text(aes(label = name), vjust = 1, hjust = 1, repel = T) +
  theme_void() +
  labs(title = "Bigram analysis of ChatGPT") +
  theme(plot.title = element_text(face = "bold", hjust = 0.5, size = 16),
        plot.margin = margin(t = 20, r = 20, b = 20, l = 20))
chatgpt_bigram

ggsave("Bigram analysis of ChatGPT.png", 
       plot = chatgpt_bigram, width = 10, height = 6, dpi = 300, units = "in", bg = "white")
#Copilot
copilot_bigram <- review_clean %>%
  filter(app == "copilot") %>%
  unnest_tokens(input = review_description,
                output = bigram,
                token = "ngrams",
                n = 2) %>%
  separate(bigram, c("word1", "word2"), sep = " ") %>%
  filter(!word1 %in% c("br", stop_words$word, "copilot"),
         !word2 %in% c("br", stop_words$word, "copilot")) %>%
  count(word1, word2, sort = T) %>%
  na.omit() %>%
  filter(n >= 5)
copilot_bigram
##         word1        word2  n
## 1          ai          app 26
## 2        chat          gpt 26
## 3         gpt            4 24
## 4      create       images 18
## 5        user     friendly 11
## 6       image   generation  9
## 7           5        stars  8
## 8     amazing          app  8
## 9        chat      history  8
## 10    chatgpt          app  8
## 11     highly    recommend  8
## 12       nice          app  8
## 13     search       engine  8
## 14         ai         tool  7
## 15 artificial intelligence  7
## 16       bing          app  7
## 17       game      changer  7
## 18      image     creation  7
## 19      image    generator  7
## 20         24        hours  6
## 21 absolutely         love  6
## 22       bing         chat  6
## 23     search      results  6
## 24         ai    assistant  5
## 25        app        store  5
## 26    chatgpt            4  5
## 27  excellent          app  5
## 28 generating       images  5
## 29     google       search  5
## 30   internet   connection  5
set.seed(2023)
a <- grid::arrow(type = "closed", length = unit(.15, "inches"))

copilot_bigram <- ggraph(copilot_bigram, layout = "fr") +
  geom_edge_link(aes(edge_alpha = n), show.legend = FALSE,
                 arrow = a, end_cap = circle(.07, 'inches')) +
  geom_node_point(color = "#09AA6C", size = 5) +
  geom_node_text(aes(label = name), vjust = 1, hjust = 1, repel = T) +
  theme_void() +
  labs(title = "Bigram analysis of Copilot") +
  theme(plot.title = element_text(face = "bold", hjust = 0.5, size = 16),
        plot.margin = margin(t = 20, r = 20, b = 20, l = 20))
copilot_bigram

ggsave("Bigram analysis of Copilot.png", 
       plot = copilot_bigram, width = 10, height = 6, dpi = 300, units = "in", bg = "white")
#BingAI
bing_bigram <- review_clean %>%
  filter(app == "BingAI") %>%
  unnest_tokens(input = review_description,
                output = bigram,
                token = "ngrams",
                n = 2) %>%
  separate(bigram, c("word1", "word2"), sep = " ") %>%
  filter(!word1 %in% c("br", stop_words$word, "BingAI"),
         !word2 %in% c("br", stop_words$word, "BingAI")) %>%
  count(word1, word2, sort = T) %>%
  na.omit() %>%
  filter(n >= 4)
bing_bigram
##        word1     word2  n
## 1     search    engine 19
## 2       bing       app 18
## 3       nice       app 18
## 4          5     stars 11
## 5       bing      chat 11
## 6       chat       gpt  9
## 7      image   creator  8
## 8         ai      chat  7
## 9        gpt         4  7
## 10      love      bing  7
## 11      bing        ai  6
## 12     image generator  6
## 13 microsoft   rewards  6
## 14    search   results  6
## 15        ai     image  5
## 16 excellent       app  5
## 17    search   engines  5
## 18        ai       app  4
## 19   copilot   feature  4
## 20      duty         4  4
## 21      edge       app  4
## 22      gift      card  4
## 23      gift     cards  4
## 24    search       box  4
set.seed(2023)
a <- grid::arrow(type = "closed", length = unit(.15, "inches"))

BingAI_bigram <- ggraph(bing_bigram, layout = "fr") +
  geom_edge_link(aes(edge_alpha = n), show.legend = FALSE,
                 arrow = a, end_cap = circle(.07, 'inches')) +
  geom_node_point(color = "#ffb900", size = 5) +
  geom_node_text(aes(label = name), vjust = 1, hjust = 1, repel = T) +
  theme_void() +
  labs(title = "Bigram analysis of BingAI") +
  theme(plot.title = element_text(face = "bold", hjust = 0.5, size = 16),
        plot.margin = margin(t = 20, r = 20, b = 20, l = 20))
BingAI_bigram

ggsave("Bigram analysis of BingAIt.png", 
       plot = BingAI_bigram, width = 10, height = 6, dpi = 300, units = "in", bg = "white")
#DaVinci
davinci_bigram <- review_clean %>%
  filter(app == "DAVINCI") %>%
  unnest_tokens(input = review_description,
                output = bigram,
                token = "ngrams",
                n = 2) %>%
  separate(bigram, c("word1", "word2"), sep = " ") %>%
  filter(!word1 %in% c("br", stop_words$word, "DAVINCI"),
         !word2 %in% c("br", stop_words$word, "DAVINCI")) %>%
  count(word1, word2, sort = T) %>%
  na.omit() %>%
  filter(n >= 4)
davinci_bigram
##         word1        word2  n
## 1        free        trial 37
## 2       daily        limit 12
## 3           3          day 10
## 4      don’t        waste  9
## 5         fun          fun  9
## 6          ai        image  8
## 7          50       images  7
## 8          ai          art  7
## 9          da        vinci  7
## 10       free      version  7
## 11        mid      journey  7
## 12         da      vinci's  6
## 13        day         free  6
## 14      image    generator  6
## 15     tattoo        ideas  6
## 16          5      minutes  5
## 17    awesome          app  5
## 18        day        trial  5
## 19   multiple        times  5
## 20     yearly subscription  5
## 21          5        stars  4
## 22 absolutely         love  4
## 23         ai         apps  4
## 24        app     doesn’t  4
## 25     can’t        spell  4
## 26     pretty         cool  4
## 27        pro      version  4
## 28  unlimited       images  4
set.seed(2023)
a <- grid::arrow(type = "closed", length = unit(.15, "inches"))

davinci_bigram <- ggraph(davinci_bigram, layout = "fr") +
  geom_edge_link(aes(edge_alpha = n), show.legend = FALSE,
                 arrow = a, end_cap = circle(.07, 'inches')) +
  geom_node_point(color = "#0970E9", size = 5) +
  geom_node_text(aes(label = name), vjust = 1, hjust = 1, repel = T) +
  theme_void() +
  labs(title = "Bigram analysis of DaVinci") +
  theme(plot.title = element_text(face = "bold", hjust = 0.5, size = 16),
        plot.margin = margin(t = 20, r = 20, b = 20, l = 20))
davinci_bigram

ggsave("Bigram analysis of DaVinci.png", 
       plot = davinci_bigram, width = 10, height = 6, dpi = 300, units = "in", bg = "white")
#gemini
gemini_bigram <- review_clean %>%
  filter(app == "gemini") %>%
  unnest_tokens(input = review_description,
                output = bigram,
                token = "ngrams",
                n = 2) %>%
  separate(bigram, c("word1", "word2"), sep = " ") %>%
  filter(!word1 %in% c("br", stop_words$word, "gemini", "aaaaaaaaaaaaaaa"),
         !word2 %in% c("br", stop_words$word, "gemini", "aaaaaaaaaaaaaaa")) %>%
  count(word1, word2, sort = T) %>%
  na.omit() %>%
  filter(n >= 4)
gemini_bigram
##        word1     word2   n
## 1     google assistant 140
## 2    replace    google  14
## 3    replace assistant  12
## 4     google      home   8
## 5       play     music   8
## 6        hey    google   6
## 7   previous    google   6
## 8        set reminders   6
## 9     simple     tasks   6
## 10        ai      tool   5
## 11 assistant  features   5
## 12     pixel         8   5
## 13         8       pro   4
## 14        ai       app   4
## 15 assistant  multiple   4
## 16     basic     tasks   4
## 17   default assistant   4
## 18    google    search   4
## 19     hands      free   4
## 20      lock    screen   4
## 21  original assistant   4
## 22     pixel         6   4
## 23   regular    google   4
## 24     takes   forever   4
## 25     voice  commands   4
set.seed(2023)
a <- grid::arrow(type = "closed", length = unit(.15, "inches"))

gemini_bigram <- ggraph(gemini_bigram, layout = "fr") +
  geom_edge_link(aes(edge_alpha = n), show.legend = FALSE,
                 arrow = a, end_cap = circle(.07, 'inches')) +
  geom_node_point(color = "#CA6673", size = 5) +
  geom_node_text(aes(label = name), vjust = 1, hjust = 1, repel = T) +
  theme_void() +
  labs(title = "Bigram analysis of Gemini") +
  theme(plot.title = element_text(face = "bold", hjust = 0.5, size = 16),
        plot.margin = margin(t = 20, r = 20, b = 20, l = 20))
gemini_bigram

ggsave("Bigram analysis of Gemini.png", 
       plot = gemini_bigram, width = 10, height = 6, dpi = 300, units = "in", bg = "white")

Anaysis and Figure 3

Through the previous sentiment analysis, TF-IDF, and bigram analysis, we examined how satisfied users are with each app and which features they perceive as most significant. Now, we aim to combine these analyses to investigate which aspects of each app are evaluated positively or negatively by users. To achieve this, bigram networks were created by filtering for positive sentiment words such as “helpful” and “nice,” and negative ones like “bad” and “limited” from each app’s bigram table. Although the goal was to use “helpful” and “bad” as standard sentiment words across all apps for objective comparison, meaningful bigram results could not be secured for every app due to variations in expression. Therefore, we conducted test runs with alternative words for each app and selected the most meaningful positive and negative sentiment words accordingly.

Graph layout

To ensure readability and facilitate comparative analysis, the bigram networks related to positive and negative sentiment words were placed side by side.

Result analysis

ChatGPT (helpful)

Words such as tool, information, fast, suggestion, and advice were frequently used together with helpful. This suggests that users perceive ChatGPT as an app that provides convenient and useful information and functions. #### ChatGPT (bad) Words like gateway and rewriting were frequently used with bad. Many online reviews have reported “bad gateway” errors, and the term rewriting may reflect limitations in the app’s ability to properly revise text or incorporate user feedback during content generation.

Copilot (helpful)

Words such as answers, resource, results, and tool appeared often with helpful. This implies that users recognize Copilot as a search engine that provides especially useful information. #### Copilot (bad) Words such as attitude, feels, and reskin were often used with bad. This suggests that users may not perceive significant improvements or functional differences from Microsoft’s previous AI products, resulting in negative emotional responses during use.

Bing (nice)

Words like search, art, picture, and easy frequently appeared with nice. This indicates that Bing is performing well in terms of information delivery and image generation. #### Bing (bad) Words such as experience and search were used with bad, revealing that some users also have negative evaluations of Bing’s search function. Additionally, the appearance of economy in negative contexts suggests that some users may feel dissatisfaction with Bing’s reward-related features.

DaVinci (amazing)

Words like art, artwork, picture, and quality appeared with amazing, showing that users are highly satisfied with the quality of DaVinci’s image generation capabilities. #### DaVinci (limited) The word amount was most frequently used with limited, confirming that limitations on usage frequency, as seen in the bigram analysis, are negatively perceived by users.

Gemini (helpful)

Due to the low number of reviews and extracted bigrams, it was difficult to derive meaningful results. However, some evidence suggests that users have a generally positive perception of the app. #### Gemini (useless) Similarly, although meaningful results could not be derived due to the small volume of data, negative perceptions of the app were also present.

1) Chat GPT

#helpful 
chatgpt_helpful <- review_clean %>%
  filter(app == "chatgpt") %>%
  unnest_tokens(input = review_description,
                output = bigram,
                token = "ngrams",
                n = 2) %>%
  separate(bigram, c("word1", "word2"), sep = " ") %>%
  filter(!word1 %in% c("br", stop_words$word, "chatgpt"),
         !word2 %in% c("br", stop_words$word, "chatgpt")) %>%
  count(word1, word2, sort = T) %>%
  na.omit() %>%
  filter(word1 == "helpful") %>%
  graph_from_data_frame()
chatgpt_helpful
## IGRAPH 5b93dd7 DN-- 20 20 -- 
## + attr: name (v/c), n (e/n)
## + edges from 5b93dd7 (vertex names):
##  [1] helpful->app         helpful->tool        helpful->responses  
##  [4] helpful->application helpful->advice      helpful->ai         
##  [7] helpful->alot        helpful->apps        helpful->fast       
## [10] helpful->feature     helpful->helpful     helpful->information
## [13] helpful->it’s       helpful->life        helpful->lonely     
## [16] helpful->love        helpful->person      helpful->solve      
## [19] helpful->suggestions helpful->till
set.seed(2023)
a <- grid::arrow(type = "closed", length = unit(.15, "inches"))

chatgpt_helpful_graph <- ggraph(chatgpt_helpful, layout = "fr") +
  geom_edge_link(aes(edge_alpha = n), show.legend = FALSE,
                 arrow = a, end_cap = circle(.07, 'inches')) +
  geom_node_point(color = "#ab68ff", size = 5) +
  geom_node_text(aes(label = name), vjust = 1, hjust = 1, repel = T) +
  theme_void() +
  theme(plot.title = element_text(face = "bold", hjust = 0.5, size = 16),
        plot.margin = margin(t = 20, r = 20, b = 20, l = 20))



#bad
chatgpt_bad <- review_clean %>%
  filter(app == "chatgpt") %>%
  unnest_tokens(input = review_description,
                output = bigram,
                token = "ngrams",
                n = 2) %>%
  separate(bigram, c("word1", "word2"), sep = " ") %>%
  filter(!word1 %in% c("br", stop_words$word, "chatgpt"),
         !word2 %in% c("br", stop_words$word, "chatgpt")) %>%
  count(word1, word2, sort = T) %>%
  na.omit() %>%
  filter(word1 == "bad") %>%
  graph_from_data_frame()
chatgpt_bad
## IGRAPH 5bf8551 DN-- 16 15 -- 
## + attr: name (v/c), n (e/n)
## + edges from 5bf8551 (vertex names):
##  [1] bad->gateway    bad->reviews    bad->ad         bad->ai        
##  [5] bad->answer     bad->constantly bad->day        bad->experience
##  [9] bad->fast       bad->game       bad->giving     bad->platform  
## [13] bad->reasoning  bad->rewriting  bad->words
set.seed(2023)
a <- grid::arrow(type = "closed", length = unit(.15, "inches"))

chatgpt_bad_graph <- ggraph(chatgpt_bad, layout = "fr") +
  geom_edge_link(aes(edge_alpha = n), show.legend = FALSE,
                 arrow = a, end_cap = circle(.07, 'inches')) +
  geom_node_point(color = "#ab68ff", size = 5) +
  geom_node_text(aes(label = name), vjust = 1, hjust = 1, repel = T) +
  theme_void() +
  theme(plot.title = element_text(face = "bold", hjust = 0.5, size = 16),
        plot.margin = margin(t = 20, r = 20, b = 20, l = 20))



chatgpt_centered <- chatgpt_helpful_graph + chatgpt_bad_graph +
  plot_annotation(
    title = "Bigrams Centered on 'Helpful' and 'Bad' in ChatGPT Reviews",
    theme = theme(
      plot.title = element_text(face = "bold", hjust = 0.5, size = 16),
      plot.margin = margin(t = 20, r = 20, b = 20, l = 20)
    )
  )

chatgpt_centered

ggsave("Bigrams Centered on 'Helpful' and 'Bad' in ChatGPT Reviews.png", 
       plot = chatgpt_centered, width = 10, height = 6, dpi = 300, units = "in", bg = "white")

2) copilot

#helpful 
copilot_helpful <- review_clean %>%
  filter(app == "copilot") %>%
  unnest_tokens(input = review_description,
                output = bigram,
                token = "ngrams",
                n = 2) %>%
  separate(bigram, c("word1", "word2"), sep = " ") %>%
  filter(!word1 %in% c("br", stop_words$word, "copilot"),
         !word2 %in% c("br", stop_words$word, "copilot")) %>%
  count(word1, word2, sort = T) %>%
  na.omit() %>%
  filter(word1 == "helpful") %>%
  graph_from_data_frame()
copilot_helpful
## IGRAPH 5ce483c DN-- 12 11 -- 
## + attr: name (v/c), n (e/n)
## + edges from 5ce483c (vertex names):
##  [1] helpful->application helpful->app         helpful->resource   
##  [4] helpful->answers     helpful->assistant   helpful->easy       
##  [7] helpful->images      helpful->love        helpful->results    
## [10] helpful->software    helpful->tool
set.seed(2023)
a <- grid::arrow(type = "closed", length = unit(.15, "inches"))

copilot_helpful_graph <- ggraph(copilot_helpful, layout = "fr") +
  geom_edge_link(aes(edge_alpha = n), show.legend = FALSE,
                 arrow = a, end_cap = circle(.07, 'inches')) +
  geom_node_point(color = "#09AA6C", size = 5) +
  geom_node_text(aes(label = name), vjust = 1, hjust = 1, repel = T) +
  theme_void() +
  theme(plot.title = element_text(face = "bold", hjust = 0.5, size = 16),
        plot.margin = margin(t = 20, r = 20, b = 20, l = 20))



#bad
copilot_bad <- review_clean %>%
  filter(app == "copilot") %>%
  unnest_tokens(input = review_description,
                output = bigram,
                token = "ngrams",
                n = 2) %>%
  separate(bigram, c("word1", "word2"), sep = " ") %>%
  filter(!word1 %in% c("br", stop_words$word, "copilot"),
         !word2 %in% c("br", stop_words$word, "copilot")) %>%
  count(word1, word2, sort = T) %>%
  na.omit() %>%
  filter(word1 == "bad") %>%
  graph_from_data_frame()
copilot_bad
## IGRAPH 5d1a8f6 DN-- 7 6 -- 
## + attr: name (v/c), n (e/n)
## + edges from 5d1a8f6 (vertex names):
## [1] bad->attitude  bad->business  bad->feature   bad->feels     bad->microsoft
## [6] bad->reskin
set.seed(2023)
a <- grid::arrow(type = "closed", length = unit(.15, "inches"))

copilot_bad_graph <- ggraph(copilot_bad, layout = "fr") +
  geom_edge_link(aes(edge_alpha = n), show.legend = FALSE,
                 arrow = a, end_cap = circle(.07, 'inches')) +
  geom_node_point(color = "#09AA6C", size = 5) +
  geom_node_text(aes(label = name), vjust = 1, hjust = 1, repel = T) +
  theme_void() +
  theme(plot.title = element_text(face = "bold", hjust = 0.5, size = 16),
        plot.margin = margin(t = 20, r = 20, b = 20, l = 20))



copilot_centered <- copilot_helpful_graph + copilot_bad_graph +
  plot_annotation(
    title = "Bigrams Centered on 'Helpful' and 'Bad' in copilot Reviews",
    theme = theme(
      plot.title = element_text(face = "bold", hjust = 0.5, size = 16),
      plot.margin = margin(t = 20, r = 20, b = 20, l = 20)
    )
  )

copilot_centered

ggsave("Bigrams Centered on 'Helpful' and 'Bad' in copilot Reviews.png", 
       plot = copilot_centered, width = 10, height = 6, dpi = 300, units = "in", bg = "white")

3) BingAI

#nice 
BingAI_nice <- review_clean %>%
  filter(app == "BingAI") %>%
  unnest_tokens(input = review_description,
                output = bigram,
                token = "ngrams",
                n = 2) %>%
  separate(bigram, c("word1", "word2"), sep = " ") %>%
  filter(!word1 %in% c("br", stop_words$word, "BingAI"),
         !word2 %in% c("br", stop_words$word, "BingAI")) %>%
  count(word1, word2, sort = T) %>%
  na.omit() %>%
  filter(word1 == "nice") %>%
  graph_from_data_frame()
BingAI_nice 
## IGRAPH 5db6cc3 DN-- 12 11 -- 
## + attr: name (v/c), n (e/n)
## + edges from 5db6cc3 (vertex names):
##  [1] nice->app         nice->ai          nice->amount      nice->application
##  [5] nice->art         nice->cool        nice->easy        nice->hai        
##  [9] nice->pic         nice->search      nice->set
set.seed(2023)
a <- grid::arrow(type = "closed", length = unit(.15, "inches"))

BingAI_nice_graph <- ggraph(BingAI_nice, layout = "fr") +
  geom_edge_link(aes(edge_alpha = n), show.legend = FALSE,
                 arrow = a, end_cap = circle(.07, 'inches')) +
  geom_node_point(color = "#ffb900", size = 5) +
  geom_node_text(aes(label = name), vjust = 1, hjust = 1, repel = T) +
  theme_void() +
  theme(plot.title = element_text(face = "bold", hjust = 0.5, size = 16),
        plot.margin = margin(t = 20, r = 20, b = 20, l = 20))



#bad
BingAI_bad <- review_clean %>%
  filter(app == "BingAI") %>%
  unnest_tokens(input = review_description,
                output = bigram,
                token = "ngrams",
                n = 2) %>%
  separate(bigram, c("word1", "word2"), sep = " ") %>%
  filter(!word1 %in% c("br", stop_words$word, "BingAI"),
         !word2 %in% c("br", stop_words$word, "BingAI")) %>%
  count(word1, word2, sort = T) %>%
  na.omit() %>%
  filter(word1 == "bad") %>%
  graph_from_data_frame()
BingAI_bad
## IGRAPH 5dd3b79 DN-- 7 6 -- 
## + attr: name (v/c), n (e/n)
## + edges from 5dd3b79 (vertex names):
## [1] bad->app        bad->experience bad->search     bad->broken    
## [5] bad->economy    bad->people
set.seed(2023)
a <- grid::arrow(type = "closed", length = unit(.15, "inches"))

BingAI_bad_graph <- ggraph(BingAI_bad, layout = "fr") +
  geom_edge_link(aes(edge_alpha = n), show.legend = FALSE,
                 arrow = a, end_cap = circle(.07, 'inches')) +
  geom_node_point(color = "#ffb900", size = 5) +
  geom_node_text(aes(label = name), vjust = 1, hjust = 1, repel = T) +
  theme_void() +
  theme(plot.title = element_text(face = "bold", hjust = 0.5, size = 16),
        plot.margin = margin(t = 20, r = 20, b = 20, l = 20))


BingAI_centered <- BingAI_nice_graph + BingAI_bad_graph +
  plot_annotation(
    title = "Bigrams Centered on 'Nice' and 'Bad' in BingAI Reviews",
    theme = theme(
      plot.title = element_text(face = "bold", hjust = 0.5, size = 12),
      plot.margin = margin(t = 20, r = 20, b = 20, l = 20)
    )
  )

BingAI_centered

ggsave("Bigrams Centered on 'Nice' and 'Bad' in BingAI Reviews.png", 
       plot = BingAI_centered, width = 10, height = 6, dpi = 300, units = "in", bg = "white")

4) DaVinci

#amazing
DaVinci_amazing <- review_clean %>%
  filter(app == "DAVINCI") %>%
  unnest_tokens(input = review_description,
                output = bigram,
                token = "ngrams",
                n = 2) %>%
  separate(bigram, c("word1", "word2"), sep = " ") %>%
  filter(!word1 %in% c("br", stop_words$word, "DAVINCI"),
         !word2 %in% c("br", stop_words$word, "DAVINCI")) %>%
  count(word1, word2, sort = T) %>%
  na.omit() %>%
  filter(word1 == "amazing") %>%
  graph_from_data_frame()
DaVinci_amazing
## IGRAPH 5e67598 DN-- 11 10 -- 
## + attr: name (v/c), n (e/n)
## + edges from 5e67598 (vertex names):
##  [1] amazing->app     amazing->art     amazing->artwork amazing->degree 
##  [5] amazing->easy    amazing->hug     amazing->images  amazing->photo  
##  [9] amazing->picture amazing->quality
set.seed(2023)
a <- grid::arrow(type = "closed", length = unit(.15, "inches"))

DaVinci_amazing_graph <- ggraph(DaVinci_amazing, layout = "fr") +
  geom_edge_link(aes(edge_alpha = n), show.legend = FALSE,
                 arrow = a, end_cap = circle(.07, 'inches')) +
  geom_node_point(color = "#0970E9", size = 5) +
  geom_node_text(aes(label = name), vjust = 1, hjust = 1, repel = T) +
  theme_void() +
  theme(plot.title = element_text(face = "bold", hjust = 0.5, size = 16),
        plot.margin = margin(t = 20, r = 20, b = 20, l = 20))



#limited 
DaVinci_limited <- review_clean %>%
  filter(app == "DAVINCI") %>%
  unnest_tokens(input = review_description,
                output = bigram,
                token = "ngrams",
                n = 2) %>%
  separate(bigram, c("word1", "word2"), sep = " ") %>%
  filter(!word1 %in% c("br", stop_words$word, "DAVINCI"),
         !word2 %in% c("br", stop_words$word, "DAVINCI")) %>%
  count(word1, word2, sort = T) %>%
  na.omit() %>%
  filter(word1 == "limited") %>%
  graph_from_data_frame()
DaVinci_limited
## IGRAPH 5e88cd0 DN-- 5 4 -- 
## + attr: name (v/c), n (e/n)
## + edges from 5e88cd0 (vertex names):
## [1] limited->amount  limited->50      limited->daily   limited->picture
set.seed(2023)
a <- grid::arrow(type = "closed", length = unit(.15, "inches"))

DaVinci_limited_graph <- ggraph(DaVinci_limited, layout = "fr") +
  geom_edge_link(aes(edge_alpha = n), show.legend = FALSE,
                 arrow = a, end_cap = circle(.07, 'inches')) +
  geom_node_point(color = "#0970E9", size = 5) +
  geom_node_text(aes(label = name), vjust = 1, hjust = 1, repel = T) +
  theme_void() +
  theme(plot.title = element_text(face = "bold", hjust = 0.5, size = 16),
        plot.margin = margin(t = 20, r = 20, b = 20, l = 20))



DaVinci_centered <- DaVinci_amazing_graph + DaVinci_limited_graph +
  plot_annotation(
    title = "Bigrams Centered on 'Amazing' and 'Limited' in DaVinci Reviews",
    theme = theme(
      plot.title = element_text(face = "bold", hjust = 0.5, size = 16),
      plot.margin = margin(t = 20, r = 20, b = 20, l = 20)
    )
  )

DaVinci_centered

ggsave("Bigrams Centered on 'Amazing' and 'Limited' in DaVinci Reviews.png", 
       plot = DaVinci_centered, width = 10, height = 6, dpi = 300, units = "in", bg = "white")

5) Gemini

#helpful 
gemini_helpful <- review_clean %>%
  filter(app == "gemini") %>%
  unnest_tokens(input = review_description,
                output = bigram,
                token = "ngrams",
                n = 2) %>%
  separate(bigram, c("word1", "word2"), sep = " ") %>%
  filter(!word1 %in% c("br", stop_words$word, "gemini"),
         !word2 %in% c("br", stop_words$word, "gemini")) %>%
  count(word1, word2, sort = T) %>%
  na.omit() %>%
  filter(word1 == "helpful") %>%
  graph_from_data_frame()
gemini_helpful
## IGRAPH 5f15036 DN-- 3 2 -- 
## + attr: name (v/c), n (e/n)
## + edges from 5f15036 (vertex names):
## [1] helpful->ai       helpful->software
set.seed(2023)
a <- grid::arrow(type = "closed", length = unit(.15, "inches"))

gemini_helpful_graph <- ggraph(gemini_helpful, layout = "fr") +
  geom_edge_link(aes(edge_alpha = n), show.legend = FALSE,
                 arrow = a, end_cap = circle(.07, 'inches')) +
  geom_node_point(color = "#CA6673", size = 5) +
  geom_node_text(aes(label = name), vjust = 1, hjust = 1, repel = T) +
  theme_void() +
  theme(plot.title = element_text(face = "bold", hjust = 0.5, size = 16),
        plot.margin = margin(t = 20, r = 20, b = 20, l = 20))



#useless  
gemini_useless <- review_clean %>%
  filter(app == "DAVINCI") %>%
  unnest_tokens(input = review_description,
                output = bigram,
                token = "ngrams",
                n = 2) %>%
  separate(bigram, c("word1", "word2"), sep = " ") %>%
  filter(!word1 %in% c("br", stop_words$word, "gemini"),
         !word2 %in% c("br", stop_words$word, "gemini")) %>%
  count(word1, word2, sort = T) %>%
  na.omit() %>%
  filter(word1 == "useless") %>%
  graph_from_data_frame()
gemini_useless
## IGRAPH 5f3235c DN-- 2 1 -- 
## + attr: name (v/c), n (e/n)
## + edge from 5f3235c (vertex names):
## [1] useless->app
set.seed(2023)
a <- grid::arrow(type = "closed", length = unit(.15, "inches"))

gemini_useless_graph <- ggraph(gemini_useless, layout = "fr") +
  geom_edge_link(aes(edge_alpha = n), show.legend = FALSE,
                 arrow = a, end_cap = circle(.07, 'inches')) +
  geom_node_point(color = "#CA6673", size = 5) +
  geom_node_text(aes(label = name), vjust = 1, hjust = 1, repel = T) +
  theme_void() +
  theme(plot.title = element_text(face = "bold", hjust = 0.5, size = 16),
        plot.margin = margin(t = 20, r = 20, b = 20, l = 20))



gemini_centered <- gemini_helpful_graph + gemini_useless_graph +
  plot_annotation(
    title = "Bigrams Centered on 'Helpful' and 'Useless' in gemini Reviews",
    theme = theme(
      plot.title = element_text(face = "bold", hjust = 0.5, size = 16),
      plot.margin = margin(t = 20, r = 20, b = 20, l = 20)
    )
  )

gemini_centered

ggsave("Bigrams Centered on 'Helpful' and 'Useless' in gemini Reviews.png", 
       plot = gemini_centered, width = 10, height = 6, dpi = 300, units = "in", bg = "white")