Declaration

*** By including this statement, we the authors of this work, verify that: • We hold a copy of this assignment that we can produce if the original is lost or damaged. • We hereby certify that no part of this assignment/product has been copied from any other student’s work or from any other source except where due acknowledgement is made in the assignment. • No part of this assignment/product has been written/produced for us by another person except where such collaboration has been authorised by the subject lecturer/tutor concerned. • We are aware that this work maybe reproduced and submitted to plagiarism detection software programs for the purpose of detecting possible plagiarism (which may retain a copy on its database for future plagiarism checking). • We hereby certify that we have read and understand what the School of Computing, Engineering and Mathematics defines as minor and substantial breaches of misconduct as outlined in the learning guide for this unit.
*** Note: An examiner or lecturer/tutor has the right not to mark this project report if the above declaration has not been added to the cover of the report.

Loading Libraries

To begin with I assigned this chunk to be where I load any libraries needed between each question. I used tidyverse to streamline and simplify the analysis process.

library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.1.4     ✔ readr     2.1.5
## ✔ forcats   1.0.0     ✔ stringr   1.5.1
## ✔ ggplot2   3.5.2     ✔ tibble    3.2.1
## ✔ lubridate 1.9.4     ✔ tidyr     1.3.1
## ✔ purrr     1.0.4     
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(kableExtra)
## 
## Attaching package: 'kableExtra'
## 
## The following object is masked from 'package:dplyr':
## 
##     group_rows
library(ggplot2)

Loading Data

I first loaded the CSVs provided for this assignment and used head to observe how information is sorted between each dataset.

business= read.csv("C:\\Users\\dlcal\\Downloads\\Programing fun ass example\\businesses.csv")
reviews= read.csv("C:\\Users\\dlcal\\Downloads\\reviews.csv")
users= read.csv("C:\\Users\\dlcal\\Downloads\\users.csv")

head(business, 2)
##   business_id                         name        city state business.avg.stars
## 1         b_0 Steele, Hampton and Odonnell Michaelbury    NV                2.5
## 2         b_1       Kim, Andrews and Joyce  East Susan    KY                4.8
##   review_count         categories business_group
## 1          351 anything, week, if              A
## 2          267              right              A
head(reviews, 2)
##   review_id user_id business_id stars       date
## 1       r_0 u_11073      b_4559     5 2023-02-01
## 2       r_1 u_35221     b_10665     3 2023-03-12
##                                                                                                        text
## 1 Audience hour west television. Live central spend machine. Agree would claim behavior table prevent pick.
## 2                                                            Summer ability art beat race else large space.
head(users, 2)
##   user_id name review_count average_stars member_since
## 1     u_0 Alan           32          2.08   2019-04-05
## 2     u_1 Joel           90          1.97   2015-11-15

Question 1

Write the code to analyse the review behaviour across user groups. The users should be grouped into 3 group: Veteran, Intermediate and New (based on their member since date) before 2017, between 2017-2022, and after 2022 respectively. Calculate the numbers of users, their average review stars and average number of reviews per user. Tabulate the data using kable or kableextra. Visualise the Average Review Stars by User Age Group. You are required to make sure you handle the NA value in your analysis. Explain your findings.

Creating the Categories by the Date the User Joined

Utilising the member_since column of users.csv to create each group I included - TRUE ~ NA_character_And - filter(!is.na(user_group)) to identify any slots that contain null/NA values for the date but also remove them from my calculations.

users_categorised = users %>%
  mutate(member_since = as.Date(member_since),
    user_group = case_when(
      member_since < as.Date("2017-01-01") ~ "Veteran",
      member_since >= as.Date("2017-01-01") & member_since <= as.Date("2022-12-31") ~ "Intermediate",
      member_since > as.Date("2022-12-31") ~ "New",
      TRUE ~ NA_character_ # Identifies any NA dates
      )) %>%
# Filter/clean data
  filter(!is.na(user_group)) # Removes those with a NA value 

Combine User Data and Calculate Metrics

After I merged he data from the now categorised user data with the review data, using inter_join to only have rows with matching user IDs between both tables. After keeping the remaining rows and assigning each one their respective group I counted each unique ID and then the average review stars and average number of reviews per use within each group. I decided to round the answers to 2 decimal places for readability.

# Combine user data
results = reviews %>%
  inner_join(users_categorised, by = "user_id") %>% # Keep rows where user_id exists in all datasheets
  drop_na(stars) %>%  # Remove rows with NA star values

# Calculate metrics
group_by(user_group) %>%
  summarise(num_users = n_distinct(user_id), # Count user ids
  avg_review_stars = mean(stars) %>% round(2), # Calculates average stars and rounds to 2 decimal places
  avg_reviews_per_user = n() / num_users %>% round(2)) # Calculates average reviews per user and rounds to 2 decimal places

Display Results

Using KableExtra I generated a graph with the striped and hover style setting enabled for readability.

results %>%
  kbl(caption = "Review Analysis by User Group",
    col.names = c("User Group", "Number of Users", "Average Stars", "Avg Reviews per User")) %>%
  kable_styling(bootstrap_options = c("striped", "hover"))
Review Analysis by User Group
User Group Number of Users Average Stars Avg Reviews per User
Intermediate 22471 3.00 5.009746
New 8311 3.01 4.758272
Veteran 6518 2.99 4.745628

Visualise the Needed Data

Using the ggplot that comes with kableextra I created the needed viuslisation in the style of a column graph for how it should allow for easy comparisons between the groups. Red was chosen for its bold colour that sticks out.

ggplot(results, aes(x = user_group, y = avg_review_stars)) +
  geom_col(fill = "red") +
  labs(title = "Average Review Stars by User Group",x = "User Age Group", y = "Average Stars")

## Findings

From the data given the majority of the userbase clearly lies in the ‘intermediate’ group with a total 22471 unique users, significantly larger than New’s 8311 and Veteran’s 6518. However, the average star reviews as well as the average reviews per user remained mostly the same between each group with a median of 3 with a 0.01 point of variance (New: 3.01, Intermediate: 3, Veteran: 2.99). This factor is the same with the average reviews per user therefore, the age of a user profile doesn’t seem to affect the activity of the user.

Question 2

Write the code to analyse the average reviews star by State. Calculate the average review star, the number of reviews and the number of unique users. Visualise the Average Review Stars by State. You are required to make sure you take care of the NA value in your analysis. Elaborate on the findings.

Initial Filtering

To start I wanted to remove businesses that lack any state ID to begin with as well as any rows missing star values since that csv was the only one with state information.

business_clean = business %>%
  filter(!is.na(state)) # Removes any business records with no state information

Combine Cleaned Business Data and Calculate Metrics

Taking a similar approach to the first question I am using the inner_join command to match the business IDs between the two CSVs, removing rows with no state or star data.From here I calculated by state the average stars, number of revies and unique users sorted from the highest rated state to the lowest for readability.

# Combine data 
state_analysis = reviews %>%  
  inner_join(business_clean, by = "business_id") %>%
  filter(!is.na(stars) & !is.na(state)) %>% # Remove reviews with NA stars and no state information 
  
# Calculate by state
group_by(state) %>%
  summarise(num_reviews = n(), # Find the number of reviews
    avg_review_stars = mean(stars) %>% round(2), # Average stars per state rounded to 2 decimal places
    num_unique_users = n_distinct(user_id)) %>% # Find how many users per state
  arrange(desc(avg_review_stars))  # Sort by highest rated states

Display Results

Yet again I using KableExtra I generated a graph with the striped and hover style setting enabled for readability.

state_analysis %>%
  kbl(caption = "Review Analysis by State",
    col.names = c("State", "Number of Reviews", "Average Stars", "Unique Users")) %>%
  kable_styling(bootstrap_options = c("striped", "hover"))
Review Analysis by State
State Number of Reviews Average Stars Unique Users
NE 3202 3.04 2992
WI 3283 3.04 3054
WV 3685 3.04 3416
AL 3548 3.03 3295
MD 3351 3.03 3133
ND 3792 3.03 3523
TN 3335 3.03 3105
UT 3173 3.03 2935
VT 3333 3.03 3095
AR 3484 3.02 3250
DC 3632 3.02 3381
ID 3336 3.02 3091
MS 3477 3.02 3240
NJ 3460 3.02 3212
SC 3621 3.02 3367
TX 3642 3.02 3390
CT 9197 3.01 8014
GA 3525 3.01 3254
IN 3036 3.01 2828
LA 3503 3.01 3256
ME 3381 3.01 3157
NY 3460 3.01 3206
KS 3258 3.00 3052
MI 3354 3.00 3119
MN 3521 3.00 3309
NM 3659 3.00 3396
OR 3552 3.00 3300
5423 2.99 4956
AK 3464 2.99 3207
AZ 3605 2.99 3363
CA 3534 2.99 3287
CO 3464 2.99 3217
DE 3401 2.99 3185
MA 3300 2.99 3068
NC 3513 2.99 3264
PA 3671 2.99 3419
WA 3758 2.99 3504
HI 3726 2.98 3450
IA 3506 2.98 3262
IL 3309 2.98 3060
MT 3630 2.98 3380
NH 3341 2.98 3109
OH 3608 2.98 3369
VA 3499 2.98 3244
WY 3350 2.98 3127
KY 3466 2.97 3237
OK 3760 2.97 3478
SD 3669 2.97 3417
FL 3353 2.96 3122
NV 3099 2.96 2887
RI 3350 2.96 3126
MO 3736 2.95 3504

Visualise the Needed Data

I created a column graph moved to its side for readability using the same method as before.

state_analysis %>%
  ggplot(aes(x = reorder(state, avg_review_stars), y = avg_review_stars)) +
  geom_col(fill = "red") +
  coord_flip() +  # Horizontal bars for readability
  labs(title = "Average Review Stars by State", x = "State", y = "Average Stars") 

## Findings After the visualisation of the average stars along with the table there is a three-way tie for the state featuring the highest average stars of 3.04 in their reviews (Nebraska, Wisconsin and West Verginia, two of these from the Mid-West Region). A patter these three share is a relatively low number of unique users, with Nebraska featuring under 3000 unique users. Also, the gap between the highest rated and lowest rated is only 0.09 apart.

Question 3

Write the code to analyse the top users and their behaviours. First, identify the top 10 users by the review count. For those top 10 users, calculate their average review stars. Tabulate the summary of the data (kable/kableextra). You are required to make sure you handle the NA value in your analysis. Visualise their rating distrubtion using ggplot2- boxplot. Discuss your findings.

Identify Top 10 Users by Review Count

After installing the ggplot2 library I took a similar approach as before, first removing rows of reviews.csv that lacked star and name information before calculating the averages. To get the top 10 I arranged the data by decending order then used slice_head.

top_reviewers = reviews %>%
  inner_join(users, by = "user_id") %>%
  filter(!is.na(name), name != "", !is.na(stars)) %>%  # Remove NA and blank names
  group_by(name) %>%
  summarise(`Total Reviews` = n(), # Total reviews per user
    `Average Stars` = mean(stars) %>% round(2)) %>% # Average stars rounded to 2 decimal places
  arrange(desc(`Total Reviews`)) %>% # Descending order by total reviews
  slice_head(n = 10)  # Keep only top 10 

Display Results

I used KableExtra to generate a graph with the striped and hover style settings enabled for readability.

top_reviewers %>%
  kbl(caption = "Top 10 Most Active Reviewers",
      col.names = c("Name", "Total Reviews", "Average Stars")) %>%
  kable_styling(bootstrap_options = c("striped", "hover"))
Top 10 Most Active Reviewers
Name Total Reviews Average Stars
Hannah 6224 2.99
Michael 3886 2.96
Jennifer 2670 3.03
David 2642 3.03
Christopher 2585 3.00
John 2529 2.99
James 2522 2.99
Robert 2446 3.03
Jessica 1881 2.97
Matthew 1796 3.02

Finding Distribution

To find the distribution I made a new data-frame that uses the users from the previous table and their star ratings. Mutate was used to ensure that the stars were seend as a numeric measure.

top_user_reviews = reviews %>%
  inner_join(users, by = "user_id") %>%
  filter(name %in% top_reviewers$name) %>%  # Keeps the names from the table
  select(name, stars) %>% # Keeps only the names and stars of each user
  mutate(stars = as.numeric(stars)) # Stars is seen as a number

Generate Visualisaton

With this new data I generated the box plot, leaving the x axis null and flipping the coordinates for readability.

reviews %>%
  inner_join(users, by = "user_id") %>% 
  filter(name %in% top_reviewers$name) %>%
  ggplot(aes(x = reorder(name, stars), y = stars)) +
  geom_boxplot(fill = "red") +
  labs(title = "Rating Distribution by Top Users",
       x = "",
       y = "Stars Given") +
  coord_flip()   # For readability

## Findings From the data, Hannah has the most reviews by a significant margin with 6224 toral reviews which dwarfs second place’s Micheal of 3886. Other than the outlier that is Hannah most of the top 10 users have around 2500 total reviews. It also seems that all the users have a similar star rating, most have an average around 3 stars with a majority of their reviews distributed around 2-4 stars.

Question 4

Write the code to analyse if there is a major difference between the review behavior of users who joined before and after 2020. For these 2 groups of users, compare their star rating behaviour and the length of the reviews (number of charaters in the review text). You are required to make sure you handle the NA value in your analysis. Visualise the average review length by the two groups. Discuss your findings.

Create Categories

Similar to Question 1 I first created categories by filtering users between pre 2020 join date and post while identifying NA rows so they can be removed.

users_time = users %>%
  mutate(member_since = as.Date(member_since),
         join_period = case_when(
           member_since < as.Date("2020-01-01") ~ "Pre-2020",
           member_since >= as.Date("2020-01-01") ~ "Post-2020",
           TRUE ~ NA_character_  # Identifies any NA dates
      )) %>%
# Filter/clean data
  filter(!is.na(join_period)) # Removes rows that has NA dates

Combine Data and Calculate Metrics

I combined the data with the Review.csv by user id, filtering out rows that doesn’t contain any values for names or the length of the review. After cleaning all the data, I averaged the stars and review length between these two groups.

# Combine data
review_comparison = reviews %>%
  inner_join(users_time, by = "user_id") %>%
  filter(!is.na(stars), !is.na(text)) %>% # Filter values again by removing null names and reviews
  mutate(review_length = nchar(text)) %>%  # Calculate length first
  group_by(join_period) %>%
  
# Calculate metrics
  summarise(num_users = n_distinct(user_id),
    avg_stars = mean(stars) %>% round(2), # Average stars rounded to 2 decimal places
    avg_length = mean(review_length) %>% round()) # Average review length rounded to 2 decimal places

Display results

using KableExtra I used the same settings as before to generate the table.

review_comparison %>%
  kbl(caption = "Review Behavior: Pre-2020 vs Post-2020 Users",
    col.names = c("Join Period", "Unique Users", "Avg Stars", "Avg Review Length")) %>%
  kable_styling(bootstrap_options = c("striped", "hover"))
Review Behavior: Pre-2020 vs Post-2020 Users
Join Period Unique Users Avg Stars Avg Review Length
Post-2020 19670 3 59
Pre-2020 17630 3 59

Visualise Needed Data

Using the same method as question 1 I visualised the data

ggplot(review_comparison, aes(x = join_period, y = avg_length)) +
  geom_col(fill = "red") +
  labs(title = "Average Review Length", 
       x = "Join Period", y = "Characters")

## Findings There seems to be no difference between the users that join before and after 2020. Both groups have 3 average stars and an average review length of 56 characters.