*** 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.
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)
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
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.
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
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
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"))
| 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 |
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.
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.
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
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
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"))
| 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 |
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.
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.
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
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"))
| 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 |
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
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.
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.
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
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
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"))
| Join Period | Unique Users | Avg Stars | Avg Review Length |
|---|---|---|---|
| Post-2020 | 19670 | 3 | 59 |
| Pre-2020 | 17630 | 3 | 59 |
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.