Load Required Libraries
library(ggplot2)
library(dplyr)
library(readr)
library(tidyr)
library(tm)
library(wordcloud)
library(RColorBrewer)
library(sqldf)
Load Datasets
my_working_dir_mac <- "/Users/matthewvasquez/Desktop/Sephora"
myFile_products <- "products.csv"
myFile_reviews <- "reviews_updated.csv"
myFile_authors <- "authors.csv"
setwd(my_working_dir_mac)
products <- read.csv(myFile_products)
reviews <- read.csv(myFile_reviews)
authors <- read.csv(myFile_authors)
Data Overview
head(reviews)
## X review_id author_id product_id rating is_recommended helpfulness
## 1 1 1035085 5229029371 P387511 5 1 1.000000
## 2 2 1035086 35439265952 P387511 2 0 0.500000
## 3 3 1035087 27285381637 P387511 4 0 1.000000
## 4 4 1035088 1696370280 P387511 4 1 0.750000
## 5 5 1035089 2692934863 P387511 4 1 0.933333
## 6 6 1035090 7178152696 P387511 3 0 0.500000
## total_feedback_count total_neg_feedback_count total_pos_feedback_count
## 1 3 0 3
## 2 4 2 2
## 3 6 0 6
## 4 4 1 3
## 5 15 1 14
## 6 4 2 2
## submission_date
## 1 11/29/22
## 2 8/12/22
## 3 2/18/22
## 4 1/12/21
## 5 12/9/20
## 6 11/17/20
## review_text
## 1 This cream is pricey, but it is so worth it! I started getting acne in my 20s from a stressful job and tried a bunch of expensive products and procedures (chemical peels, facials, microdermabrasion). None of them worked as effectively as this one did. Since using this cream, my face has gotten smooth and soft. I rarely gets pimples now, and when I see one coming, I just apply this cream and it pretty much disappears overnight. On the downside, the fragrance does take a while to get used to because L’Occitane products tend to have a strong (but still pleasant) scent.
## 2 I used this in rotation with the Divine Youth Oil. Neither of these products left me feeling wowed in any way. If anything I found the cream slightly more effective than the oil, but at a price...red itchy spots on my cheeks and jawline area after 2 weeks of use. I’m guessing this was down to the Rosemary extract (the same ingredient that brings a rush of short-lived color to your cheeks - due to its stimulating effect on the skin). The Vitamin C in this did give me a glow which was good, and the texture of this cream was light and totally non-greasy. However something in it reacted negatively with my skin, causing bumps in areas I’ve never had bumps before. Definitely not something I could use as a daily moisturizer, at best, I’d say this is something I could use sparingly, every so often.~ My Sephora Top 7 must-have list ~(My top 7 must-have beauty products of all time, focusing mainly on my purchases at Sephora)1. Hair Loss Treatment: ‘Divine Herbal’ - Hair Oil 2. Shampoo: ’Sephora Collection’ - Deep Cleaning Shampoo3. Face Mask: ‘Peter Thomas Roth’ – Pumpkin Enzyme Mask 4. Pigmentation + Anti-Aging Serum: ‘Divine Herbal’ - Facial Treatment Serum5. Face Scrub: ‘Sisley’ - Botanical Gentle Buffing Cream6. Lip Balm: ‘Nuxe’ – Reve De Miel Lip Balm7. Lip Liner: ‘Sephora Collection’ - Rouge Gel Lip Liner
## 3 I really did like this cream but I don’t think I’d buy it again just because of how expensive it is. It smells great with a slightly spicy scent and spreads so smoothly but leaves a slightly greasy feel after application. It’s really moisturizing and lasts a while too but I don’t think it’s a really good value for what it is.
## 4 Received a 5 ml sample of this cream, and it’s great! I love a high end moisturizer that is thick and rich, but absorbs easily into skin
## 5 I love this face cream - I don’t love the price lol. This weird thing always happens to me when I finish a jar - my skin is amazing while I’m using it, but then if I run out and don’t start a new jar right away, I get super dry patches around my nose and mouth area. It’s like it trains your skin to only accept this expensive af cream and then when you stop, your skin revolts. Anyway, I have oily skin and this never breaks me out. It’s a great cream, but the price makes replacing it annoying, and if I don’t replace it right away my skin freaks out.
## 6 It’s a nice cream. It has a smokey, earthy scent to it. When I put it on my skin felt oily, but it dried really quickly. But I can feel it on my skin. I don’t think it’s worth the price.
## review_title Pos Neu Neg But Compound ReviewTextChars
## 1 My holy grail 0.162 0.757 0.081 2 0.902 573
## 2 Not for me! 0.188 0.791 0.021 1 0.993 1322
## 3 0.209 0.791 0.000 3 0.946 327
## 4 0.320 0.680 0.000 1 0.870 136
## 5 0.199 0.733 0.068 2 0.964 553
## 6 Great scent, oily feel 0.000 0.000 0.000 0 0.000 186
str(reviews)
## 'data.frame': 1536 obs. of 19 variables:
## $ X : int 1 2 3 4 5 6 7 8 9 10 ...
## $ review_id : int 1035085 1035086 1035087 1035088 1035089 1035090 1035091 1035092 1035093 1035094 ...
## $ author_id : num 5.23e+09 3.54e+10 2.73e+10 1.70e+09 2.69e+09 ...
## $ product_id : chr "P387511" "P387511" "P387511" "P387511" ...
## $ rating : int 5 2 4 4 4 3 2 5 3 3 ...
## $ is_recommended : int 1 0 0 1 1 0 0 1 0 1 ...
## $ helpfulness : num 1 0.5 1 0.75 0.933 ...
## $ total_feedback_count : int 3 4 6 4 15 4 7 2 12 15 ...
## $ total_neg_feedback_count: int 0 2 0 1 1 2 2 0 7 8 ...
## $ total_pos_feedback_count: int 3 2 6 3 14 2 5 2 5 7 ...
## $ submission_date : chr "11/29/22" "8/12/22" "2/18/22" "1/12/21" ...
## $ review_text : chr "This cream is pricey, but it is so worth it! I started getting acne in my 20s from a stressful job and tried a"| __truncated__ "I used this in rotation with the Divine Youth Oil. Neither of these products left me feeling wowed in any way. "| __truncated__ "I really did like this cream but I don’t think I’d buy it again just because of how expensive it is. It smells "| __truncated__ "Received a 5 ml sample of this cream, and it’s great! I love a high end moisturizer that is thick and rich, but"| __truncated__ ...
## $ review_title : chr "My holy grail" "Not for me!" "" "" ...
## $ Pos : num 0.162 0.188 0.209 0.32 0.199 0 0 0 0 0 ...
## $ Neu : num 0.757 0.791 0.791 0.68 0.733 0 0 0 0 0 ...
## $ Neg : num 0.081 0.021 0 0 0.068 0 0 0 0 0 ...
## $ But : int 2 1 3 1 2 0 0 0 0 0 ...
## $ Compound : num 0.902 0.993 0.946 0.87 0.964 0 0 0 0 0 ...
## $ ReviewTextChars : int 573 1322 327 136 553 186 56 133 207 90 ...
Word Cloud of 5-Star Reviews
docsS <- Corpus(VectorSource(reviews[reviews$rating == 5,]$review_text))
toSpace <- content_transformer(function (x, pattern) gsub(pattern, " ", x))
docsS <- tm_map(docsS, toSpace, "“")
docsS <- tm_map(docsS, toSpace, "”")
docsS <- tm_map(docsS, toSpace, "’")
docsS <- tm_map(docsS, toSpace, "—")
docsS <- tm_map(docsS, content_transformer(tolower))
docsS <- tm_map(docsS, removeNumbers)
docsS <- tm_map(docsS, removePunctuation)
docsS <- tm_map(docsS, removeWords, stopwords("english"))
docsS <- tm_map(docsS, removeWords, c("mer", "la", "loccitane", "cream", "product", "skin", "lamer", "use", "using", "face"))
docsS <- tm_map(docsS, stripWhitespace)
mS <- sort(rowSums(as.matrix(TermDocumentMatrix(docsS))), decreasing = TRUE)
dS <- data.frame(word = names(mS), freq = mS)
head(dS, 20)
## word freq
## love love 335
## dry dry 288
## like like 276
## moisturizer moisturizer 272
## little little 268
## really really 268
## long long 239
## just just 225
## worth worth 222
## can can 222
## will will 220
## way way 218
## soft soft 194
## goes goes 183
## one one 175
## amazing amazing 170
## used used 169
## creme creme 167
## time time 165
## price price 163
Word Cloud Visualization
set.seed(1234)
wordcloud(words = dS$word, freq = dS$freq, min.freq = 2,
max.words = 200, random.order = FALSE, rot.per = 0.4,
scale = c(2.2, 0.44), colors = brewer.pal(8, "Dark2"))

Analysis of “Immortelle” 5-Star Reviews
FiveStarImmortelle <- sqldf::sqldf("
SELECT r.review_text
FROM products p INNER JOIN reviews r
ON p.product_id = r.product_id
WHERE r.rating = 5 AND product_name LIKE 'Immortelle%'
")
docs <- Corpus(VectorSource(FiveStarImmortelle$review_text))
docs <- tm_map(docs, removePunctuation)
customRemovePunctuation <- content_transformer(function(x) gsub("[[:punct:]]+", "", x))
docs <- tm_map(docs, customRemovePunctuation)
docs <- tm_map(docs, content_transformer(tolower))
docs <- tm_map(docs, removeNumbers)
docs <- tm_map(docs, removeWords, stopwords("english"))
docs <- tm_map(docs, removeWords, c("skin", "cream", "ive", "loccitane"))
m <- sort(rowSums(as.matrix(TermDocumentMatrix(docs))), decreasing = TRUE)
d <- data.frame(word = names(m), freq = m)
head(d, 20)
## word freq
## face face 35
## using using 32
## product product 31
## use use 31
## like like 31
## love love 29
## oily oily 26
## soft soft 23
## amazing amazing 22
## sample sample 22
## night night 21
## just just 20
## one one 20
## used used 20
## dont dont 20
## really really 20
## feels feels 19
## dry dry 19
## will will 19
## moisturizer moisturizer 19
Word Cloud for Immortelle Reviews
set.seed(1234)
wordcloud(words = d$word, freq = d$freq, min.freq = 1,
max.words = 200, random.order = FALSE,
rot.per = 0.35, colors = brewer.pal(8, "Dark2"))

Product Reviews Data
reviews_products <- sqldf::sqldf("
SELECT r.*, p.brand_name, p.product_name
FROM reviews r
INNER JOIN products p
ON r.product_id = p.product_id
")
mean_rating <- mean(reviews_products$rating, na.rm = TRUE)
mean_rating
## [1] 4.038411
Distribution of Ratings
hist(reviews_products$rating, breaks = seq(0.5, 5.5, by = 1),
main = "Distribution of Ratings", xlab = "Review Ratings (in Stars)")

Review Length Analysis
mean_length <- mean(reviews$ReviewTextChars, na.rm = TRUE)
ggplot(reviews, aes(x = ReviewTextChars)) +
geom_histogram(bins = 30, fill = "blue", color = "black") +
labs(title = "Histogram of Review Lengths",
x = "Review Length (Characters)",
y = "Frequency")

Classifying Review Types
reviews_products$ReviewType <- ifelse(reviews_products$ReviewTextChars > 404 + 50, "Long",
ifelse(reviews_products$ReviewTextChars < 404 - 50, "Short", "Average"))