Провели анализ текста из reviews наших комиксов и с помощью встроенных библиотек таких как AFINN & BING - спрогнозировали среднюю оценку комикса в зависимости от использованных в отзыве слов, сопоставили с реальными данными с помощью independence_test и нашли наиболее часто употребляемые слова в отзывах, их эмоциональную окраску, создали собственный словарь стоп-слов.Проанализировали временные рамки и исключили нормальное распределение по написанию отзывов .
load("~/shared/minor2_2020/data/good_read/books_g_1.RData")
load("~/shared/minor2_2020/data/good_read/reviews_g_1.RData")
В первом дата сете можно проанализировать описание, возможно некоторые слова- типа увлекательный потрясающий привлекает больше просмотров комиксов , а следовательно и выше оценки, во втором датасете нам интересна колонка отзывов, там мы проанализируем тональность слов , подсчитаем количество тональных слов , а затем проанализируем, связанны ли они с оценками.
Анализ настроений с внутренним соединением Будем использовать три основных словаря для анализа настроений в отзывах
library(tidytext)
library(tidyr)
library(ggplot2)
library(dplyr)
library(knitr)
# Встроеный список
# Вычленим нужную нам колонку и токенизируем текст
reviews=goodread_reviews %>% dplyr::select(book_id,review_text)
reviews=reviews %>% group_by(book_id) %>% unnest_tokens(word, review_text) %>% count(book_id, word, sort = TRUE) %>%
anti_join(stop_words) # Удалим стопслова
nrcjoy <- get_sentiments("nrc") %>%
filter(sentiment == "joy")
# Теперь разделим их на позитивные и негативные c помощью bing – еще одного встроенного словаря
sentiments=reviews %>%
inner_join(get_sentiments("bing")) %>%
# Мы находим оценку настроений для каждого слова, используя лексикон Bing и inner_join()
count(book_id, sentiment) %>%
# Затем мы подсчитываем, сколько положительных и отрицательных слов содержится в отзыве
spread(sentiment, n, fill = 0) %>% #В широкий формат
mutate(sentiment = positive - negative)
# С помощью sentiments подсчитаем , на сколько позитивный тот или иной отзыв
# Теперь выведем наиболее позитивные и негативные слова, чтобы в дальнейшем проверить их влияние на рейтинг комикса – не густо
bing_word_counts <- reviews %>%
inner_join(get_sentiments("bing"), by = c(word = "word")) %>%
count(word, sentiment, sort = TRUE) %>%
ungroup() %>% arrange(-n)
# Многие слова совпадают по значению, но являются одной и той же словоформой, поэтому лемматизируем их с помощью встроенной библиотеки udpipe()
library(udpipe)
# Теперь добавим модель – там содержатся большинство слов, разделенных по частям речи
enmodel <- udpipe_download_model(language = "english")
library(udpipe)
bing_word_counts<-udpipe(bing_word_counts$word, object = enmodel,doc_id=bing_word_counts$book_id)
bing_word_counts <- data.frame(bing_word_counts)
bing_word_counts = select(bing_word_counts,doc_id,lemma)
bing_word_counts %>%inner_join(get_sentiments("bing"), by = c(lemma= "word")) %>%
count(sentiment,lemma) %>%
top_n(20) %>%
ungroup() %>%
mutate(n = ifelse(sentiment == "negative", -n, n)) %>%
mutate(term = reorder(lemma, n)) %>%
ggplot(aes(term, n, fill = sentiment)) +
geom_bar(stat = "identity") +
ylab("Contribution to sentiment") +
coord_flip()
# Если взглянуть на эти слова, то можно обратить внимание на слово plot – в нашем контексте оно играет роль сюжета и не носит никакой эмоциональной нагрузки, поэтому мы его уберем, а "plot" – добавим его а в наш личный словарь стоп-слов, уберем и слово issue по тем же лексическим причинам
reviews1=goodread_reviews %>% dplyr::select(book_id,review_text)
reviews1=reviews1 %>% group_by(book_id) %>% unnest_tokens(word, review_text) %>% count(book_id, word, sort = TRUE) %>%
anti_join(stop_words)
library(stringr)
# Попробуем теперь с отрицательными частями речи - для этого берем изначальный датасет сначала токенизируем и лематизируем , убираем стоп слова за исключением отрицательных слов, совмещаем со списком слов, передающих эмоциональную окраску текста, считаем sentiments и визуализируем
my_stop_words <- bind_rows(data_frame(word = c("plott","
issue","plot"),
lexicon = c("custom")),
stop_words)
# Попробуем теперь визуализировать
negative_words <- bind_rows(data_frame(
word = c("not", "without", "no", "can't", "don't", "won't","weren't","cannot")))
my_stop_words<-anti_join(my_stop_words,negative_words,by="word")
final = goodread_reviews %>% dplyr::select(book_id,review_text) %>% group_by(book_id) %>% unnest_tokens(word, review_text)%>%count(book_id, word, sort = TRUE) %>% anti_join(my_stop_words)
final$word=str_remove_all(final$word,pattern= "\\d+") #убиаем все цифры
final <-udpipe(final$word, object = enmodel,doc_id=final$book_id)
final= select(final,doc_id,lemma)
my_stop_words <- bind_rows(data_frame(word = c("plott","issue","plot"),
lexicon = c("SMART")),
stop_words)
final = filter(final,!lemma %in% my_stop_words$word)
# Визуализируем
final %>%inner_join(get_sentiments("bing"), by = c(lemma= "word")) %>%
count(sentiment,lemma) %>%
top_n(50) %>%
ungroup() %>%
mutate(n = ifelse(sentiment == "negative", -n, n)) %>%
mutate(term = reorder(lemma, n)) %>%
ggplot(aes(term, n, fill = sentiment)) +
geom_bar(stat = "identity") +
ylab("Contribution to sentiment") +
coord_flip()
final=final %>% inner_join(get_sentiments("bing"), by = c(lemma= "word"))
final=final %>% count(doc_id,sentiment)
ggplot(final)+geom_point(aes(x=doc_id,y=n,color=sentiment))
final_total =final %>%
mutate(n = ifelse(sentiment == "negative", -n, n)) %>% spread(sentiment, n, fill = 0) %>%
mutate(sentiment = positive + negative)
ggplot(final_total, aes(x = doc_id, y =sentiment)) +
geom_col(show.legend = FALSE)
# Посчитаем сколько негативных и позитивных отзывов у нас есть
final_count = final_total
final_count = get_sentiments("bing") %>%
filter(sentiment %in% c("positive",
"negative")) %>%
count(sentiment)
# Проверим самый позитивный и негативный отзыв, сопоставим с рейтингом
## max(final_total$sentiment) #10756755
## min(final_total$sentiment) #6912600
## goodread_reviews %>%filter( goodread_reviews$book_id==10756755) %>% mutate(mean_rate=mean(rating))
# 3.73 – средний рейтинг
## goodread_reviews %>%filter( goodread_reviews$book_id==6912600) %>% mutate(mean_rate=mean(rating))
# 4.2 – средний рейтинг
# Как мы видем данный метод не очень хорошо подходят для прогнозирования оценки,но это нужно допроверить, попробуем метод AFFIN
affin_sentiments<-goodread_reviews %>% dplyr::select(book_id,review_text) %>% group_by(book_id) %>% unnest_tokens(word, review_text)%>%count(book_id, word, sort = TRUE) %>% anti_join(my_stop_words)
affin_sentiments$word=str_remove_all(affin_sentiments$word,pattern= "\\d+")
affin_sentiments <-udpipe(affin_sentiments$word, object = enmodel,doc_id=affin_sentiments$book_id)
affin_sentiments=select(affin_sentiments,doc_id,lemma)
affin_sentiments=affin_sentiments%>% inner_join(get_sentiments("afinn"), by = c(lemma= "word"))
affin_sentiments=affin_sentiments%>% left_join(affin_sentiments) %>%
group_by(doc_id) %>%
summarise(score = sum(value)) %>%
replace_na(list(value = 0)) %>% arrange(score)
# Немного лучше но не идеально, но нужно проверять дальше
## goodread_reviews %>% filter( goodread_reviews$book_id==4437831) %>% mutate(mean_rate=mean(rating)) %>% select(book_id,review_text,rating,mean_rate) # 3.48 – средний рейтинг
## goodread_reviews %>% filter( goodread_reviews$book_id==9792887) %>% mutate(mean_rate=mean(rating))
# 3.18 – средний рейтинг
Проверим, насколько хороши наши модели : для первой модели , которая самостаятельно задает на основе негативного или позитивного лексического тона слова его оценку,которую мы переведем в пятибальную шкалу, перемешаем наши данные и посмотрим, насколько хорошо она предсказывает наши реальные рейтинги комиксов
Bing-sentiments
sentiments=sentiments %>% mutate(my_rating = case_when(
sentiment <= -25 ~ "0", sentiment >-25 & sentiment <=-13.3 ~ "1",
sentiment >-13.3 & sentiment <=-6 ~ "2",sentiment >-6 & sentiment <=0~ "3",
sentiment>0 & sentiment <=5 ~"4", sentiment >5 ~ "5", TRUE~"a"
))
sentiments$my_rating=as.numeric(sentiments$my_rating)
data_rediction=goodread_reviews %>% group_by(book_id) %>% mutate(meanrate=mean(rating)) %>% select(book_id,meanrate,)
data_rediction=inner_join(data_rediction,sentiments,by="book_id") %>% select(-positive,-negative,-sentiment)
data_rediction = group_by(data_rediction ,book_id) %>% distinct()
library(coin)
independence_test(meanrate~my_rating, data = data_rediction )
##
## Asymptotic General Independence Test
##
## data: meanrate by my_rating
## Z = 3.7388, p-value = 0.0001849
## alternative hypothesis: two.sided
# P-value эта наша вероятность ошибки
Afinn-score
quantile(affin_sentiments$score,probs = seq(0, 1, 0.15))
## 0% 15% 30% 45% 60% 75% 90%
## -249 -23 0 17 32 51 81
affin_sentiments=affin_sentiments %>% mutate(my_ratings = case_when(
score<= -23 ~ "0", score >-23 & score<=0 ~ "1",
score > 0& score<= 17 ~ "2",score >17& score<=32~ "3",
score>32 & score <= 51 ~"4", score >51~ "5", TRUE~"a"
))
affin_sentiments$my_ratings =as.numeric(affin_sentiments$my_ratings )
data_prediction=goodread_reviews %>% group_by(book_id) %>% mutate(meanrate=mean(rating)) %>% select(book_id,meanrate)
names(affin_sentiments)<-c( "book_id", "score","my_ratings")
affin_sentiments$book_id =as.numeric(affin_sentiments$book_id )
data_prediction=full_join(data_prediction,affin_sentiments,by="book_id")
data_prediction = group_by(data_prediction ,book_id) %>% distinct() %>% select(-score)
library(coin)
independence_test(meanrate~my_ratings, data = data_prediction )
##
## Asymptotic General Independence Test
##
## data: meanrate by my_ratings
## Z = 2.3833, p-value = 0.01716
## alternative hypothesis: two.sided
# P-value эта наша вероятность ошибки
Как мы видим p-value недостаточно мал для того, чтобы утверждать статистическую значимость полученных нами результатов, но включить в сетевой анализ их можно.
Преобразуем даты и время, найдем распределение отзывов по ним , проанализируем на равномерность распределения, найдем слова которые встречаются в топе-50 самых негативных и позитивных описаний, посчитаем среднюю оценку по всем отзывам. Создадим колонку день недели, месяц, число и год и распределдение отзывов по ним, проверим на нормальность.
goodread_reviews<-mutate(goodread_reviews,weekdate=str_extract(
goodread_reviews$date_added,pattern="^[A-z][a-z][a-z][:space:]"))
goodread_reviews<-mutate(goodread_reviews,month=str_extract(
goodread_reviews$date_added,pattern="[:space:][A-z][a-z][a-z][:space:]"))
goodread_reviews<-mutate(goodread_reviews,day=str_extract(
goodread_reviews$date_added,pattern="[:space:][0-9][0-9][:space:]"))
goodread_reviews<-mutate(goodread_reviews,year=str_extract(
goodread_reviews$date_added,pattern="[:space:][0-9][0-9][0-9][0-9]"))
ggplot(goodread_reviews)+geom_bar(aes(weekdate),fill="green")
ggplot(goodread_reviews)+geom_bar(aes(month),fill="green")
ggplot(goodread_reviews)+geom_bar(aes(day),fill="green")
ggplot(goodread_reviews)+geom_bar(aes(year),fill="green")
# Единственным найденным нами качественным признаком выборки является факт повышения количества отзывов с с 2007 по 2016 гг и резкое сокращение этого показателя к 2017
library(reshape2)
library(igraph)
# Создадим матрицу
# mdata=goodread_comics %>% select(book_id,country_code,average_rating,publisher)
tdata=goodread_reviews %>% select(book_id,rating,)
tdata=left_join(tdata,data_rediction) %>% select(-meanrate)
names(tdata)<-c( "book_id", "rating","bing_rating")
tdata=left_join(tdata,data_prediction,by="book_id")
tdata=select(tdata,-meanrate)
names(tdata)<-c( "book_id", "rating","bing_rating","affin_rating")
tdata=full_join(tdata,goodread_comics,by="book_id")
tdata=tdata %>% select(book_id,popular_shelves.1.name,average_rating,bing_rating,affin_rating,popular_shelves.2.name,popular_shelves.3.name,publisher) %>% distinct()
tdata$average_rating=as.numeric(tdata$average_rating)
tdata = tdata %>% pivot_wider(names_from =popular_shelves.1.name , values_from =average_rating, values_fill = 0)
Удаляем лишние переменные
tdata = tdata %>% dplyr::select(-popular_shelves.3.name,-popular_shelves.2.name,-publisher)
Считаем матрицу схожести комиксов Сначала переводим id в названия строк, т.к. мы не хотим, чтобы разница в id влияла на схожесть комиксов.
rownames = tdata$book_id
tdata = tdata %>% dplyr::select(-book_id)
rownames(tdata) = rownames
sim = lsa::cosine(t(as.matrix(tdata)))
library(igraph)
ig <- graph.adjacency(sim, mode="undirected", weighted=NULL)
plot(ig)
# Поработаем с сетью
# 445 – получилось слишком много сообществ, добавим новые параметры в нашу сеть
wtcommune <- walktrap.community(ig)
## tibble::enframe(membership(wtcommune))$value %>% unique()%>% length()
## modularity(wtcommune)
plot(wtcommune,ig, vertex.label = '')
Кажется, что читателю, которому понравился тот или иной комикс, могут быть так же понравиться другие комиксы, выпущенные в том же жанре, тематике и/или под той же серией и/или тем же издательством. Данное предположение основывается на том, что комиксы одной серии могут быть так или иначе связаны друг с другом – сюжетом, персонажами, вселенной, etc., а принадлежность комиксов к одному жанру, тематике подразумевать наличие каких-то других общих элементов: стилистических приемов, сюжетов в фокусе, etc.
Основываясь на этой логике произведем фильтрацию нашей сети по публикующей комиксы фильмам , так как сеть у нас уже создана по похожести по среднему рейтингу и первой полке, то ограничим нашу похожесть еще и фирмой-производителем – если при всем при этом и фирмы совпадают – то книги связываются
V(ig)$publisher=goodread_comics$publisher
# Так, например, для любителей комиксов Marvel посмотрим подходящие варианты комиксов
it<- delete_vertices(ig,V(ig)$publisher!="Marvel")
# Теперь – плохое разбиение, так мы ничего не предкажем
wtcommune <- walktrap.community(it)
## tibble::enframe(membership(wtcommune))$value %>% unique()%>% length()
## modularity(wtcommune)
plot(wtcommune,it, vertex.label = '')
Как мы видим здесь ничего мы не получили устойчивых соединений.
library(igraph)
library(igraphdata)
library(ggraph)
ig %>%
ggraph(layout = "nicely") +
geom_edge_link(alpha = 0.5) +
geom_node_point(aes(color = betweenness(ig), size = degree(ig)))+
theme_graph()+
scale_colour_gradient(low = 'blue', high = 'pink')+
labs(title = "Graph for first book shelf name")+geom_node_text(aes(label=case_when(betweenness(ig) >=2 & degree(ig)>10 ~ name)))
Как мы видим образовались две небольшие группы , которые мы можем порекомендовать поклонникам Marvel , посмотрим на них внимательнее. Выделим одну, обладающую наибольшей битвинностью и показателем degree.
Проведя текстовый и сетево анализ - мы сделали вывод, что , к сожалению, не одна из использованных нам систем не пригодится в предсказательных системах, а сетевой анализ не дал нам значимых результатов даже с использованием предсказанных нами метрик AFIN & BING.
Для рекомендательной системы на основе характеристик контента, в изначальном датасете были отобраны следующие переменные, полезные для формирования рекомендаций: полки (popular_shelves.0.name, popular_shelves.1.name, popular_shelves.2.name, popular_shelves.3.name), средний рейтинг (average_rating), год издания (publication_year), издательство (publisher) и количество страниц (num_pages). Более того, имела место и специальная обработка этих переменных. В случае полок – это перевод в широкий формат и объединение дублирующихся колонок с разных полок с суммированием в итоговой значения каждой из совпадающих. То есть если в датасете было две колонки “manga” и у какого-то комикса в каждой из них было значение равное 1, то в итоговой и единственной “manga” у пользователя было бы значение, равное 2. Три другие переменные, год издания, средний рейтинг и количество страниц были стандартизованы, чтобы сделать значения косинусного растояния (метод используемый для подсчета схожести объектов) более варьирующимися и следовательно более показательными.
# Преобразования датасета, использующегося в Content-based RS
comics = goodread_comics %>%
select(
-country_code,
-language_code,
-ratings_count,
-link,
-title,
-title_without_series,
-authors.0.role,
-authors.1.role,
-is_ebook) %>%
# Издательство – перовстепенный признак, год издания и кол-во страниц – второстепенные
# В связи с этим даляем записи без издательства, но не без года издания или кол-ва страниц
filter(publisher != "") %>%
mutate(prescence = 1) %>%
mutate(prescence_1 = 1) %>%
mutate(prescence_2 = 1) %>%
mutate(prescence_3 = 1) %>%
mutate(prescence_4 = 1)
reviews = goodread_reviews
comics_n = comics %>% select(book_id)
goodread_comics_ren = inner_join(goodread_comics,comics_n, by = "book_id")
# Дополнительные преобразования
# Преобразуем полки в столбцы
comics = comics %>%
pivot_wider(
names_from = popular_shelves.0.name,
values_from = prescence,
names_prefix = "sh.",
values_fill = 0) %>%
pivot_wider(
names_from = popular_shelves.1.name,
values_from = prescence_1, values_fill = 0,
names_prefix = "sh.",
names_repair = "minimal") %>%
pivot_wider(
names_from = popular_shelves.2.name,
values_from = prescence_2,
values_fill = 0,
names_prefix = "sh.",
names_repair = "minimal") %>%
pivot_wider(
names_from = popular_shelves.3.name,
values_from = prescence_3, values_fill = 0,
names_prefix = "sh.",
names_repair = "minimal") %>%
pivot_wider(
names_from = publisher,
values_from = prescence_4,
names_prefix = "pub.",
values_fill = 0)
comics = as.data.frame(lapply(split.default(comics, names(comics), lex.order = F), function(x) Reduce(`+`, x)))
comics = comics %>%
select(-description, -authors.0.author_id, -authors.1.author_id, -sh.to.read) %>%
mutate(publication_year = as.numeric(publication_year)) %>%
mutate(average_rating = as.numeric(average_rating)) %>%
mutate(num_pages = as.numeric(num_pages))
# Стандартизируем переменные с диапозонными значениями
comics = comics %>%
mutate(publication_year =
publication_year/do.call(pmax, c(comics$publication_year, list(na.rm = T)))) %>%
mutate(num_pages =
num_pages/do.call(pmax, c(comics$num_pages, list(na.rm = T)))) %>%
mutate(average_rating =
average_rating/do.call(pmax, c(comics$average_rating, list(na.rm = T))))
Использованы два метода построения рекомендательной системы меодом коллаборативной фитрации: User-based и Item-based. В ходе дальнейшего анализа было выявлено, что User-based метод наиболее эффективен и приводит к более точным результатам, поэтому в окончательной системе использовался именно он. Таким образом, мы получаем рекомендательную систему, которая способна порекомендовать несколько разных комиксов некоторым пользователям из исходных данных.
# Сохраним только оценки пользователей и комиксы
ratings = select(goodread_reviews, -date_added, -review_id, -review_text)
# Преобразуем данные к таблице в "широком" формате
rates = pivot_wider(ratings, names_from = book_id, values_from = rating)
# Сохраним имена (id) строк из соответсвующей колонки
userNames = rates$user_id
# Удалим колонку с именами (id) строк
rates = select(rates, -user_id)
# Переведем датасет в матричный формат
rates = as.matrix(rates)
#Присвоим строкам матрицы сохраненные ранее имена (id)
rownames(rates) = userNames
#Переводем матрицу в в спецаильный матричный формат для рекомендателньой системы
r = as(rates, "realRatingMatrix")
# Отфильтруем записи: rowCounts – сумма значений по строке, colCounts – по колонке
ratings_comics <- r[rowCounts(r) > 5, colCounts(r) > 10]
# Вычислим похожесть всех пар комиксов
similarity_users10 <- recommenderlab::similarity(r[1:10, ], method = "cosine", which = "users")
# Разделим данные на тестовую и обучающую выборки
# Делаем рандом фиксированным, чтоб выборки всегда одинаковыми получались
set.seed(100)
# Определим, какая часть пользователей пойдет на тренировку, а какая на предсказаниия, данной конфигурации 20% идут на предсказания
eval_sets <- evaluationScheme(
data = ratings_comics,
method = "split",
train = 0.8,
given = 3,
goodRating = 4)
# Создадим непосредственно рекомендательную модель, для начала воспользуемся методом IBCF
recc_model_ibcf <- Recommender(data = getData(eval_sets, "train"), method = "IBCF")
# Создадим еще одну рекомендательную модель, но используя метод UBCF
recc_model_ev_ubcf = Recommender(
data = getData(eval_sets, "train"),
method = "UBCF",
parameter = list(nn = 3))
Нами была создана функция, принимающая в качестве аргумента id пользователя, и выдающая желаемое количество (дефолтно – 5) рекомендаций. В основе: модель, созданная UBCF методом.
getComicsCF = function(id, num = 5) {
recc_model_ev_ubcf =
Recommender(data = ratings_comics, method = "UBCF", parameter = list(nn = 3))
person = reviews %>% filter(user_id == id & rating > 3)
if (nrow(person) > 2) {
recc_predicted =
predict(
object = recc_model_ev_ubcf,
newdata = ratings_comics,
n = num)
recc_user <- recc_predicted@items[[id]]
comics_user <- recc_predicted@itemLabels[recc_user]
names_comics_user <- goodread_comics$title[match(comics_user,goodread_comics$book_id)]
names_comics_user
} else {
print("Мало положительных оценок для персонализрованной рекомендации, но есть универсальное предложение: All my friends are dead by Avery Monsen and Jory John")
}}
Оценивание рекомендации: Проведя оценку двух моделей построенными с использованием методов UBCF и IBCF, мы выяснили, что User-based, в целом, имеет меньшую ошибку, чем Item-based: величина RMSE меньше на 0.2, MSE меньше на 0.7, MAE меньше на 0.2. Таким образом, выбор был сделан в пользу User-based colaborative filtering.
Оценка IBCF:
# Оцениваем качество предсказания для IBCF модели
recc_predicted_ev_ibcf = predict(
object = recc_model_ibcf,
newdata = getData(eval_sets, "known"),
n = 5,
type = "ratings")
eval_accuracy_ibcf = calcPredictionAccuracy(
x = recc_predicted_ev_ibcf,
data = getData(eval_sets, "unknown"),
byUser = F)
eval_accuracy_ibcf
## RMSE MSE MAE
## 1.622021 2.630952 1.282313
Оценка UBCF:
# Оцениваем качество предсказания для UBCF модели
recc_predicted_ev_ubcf = predict(
object = recc_model_ev_ubcf,
newdata = getData(eval_sets, "known"),
n = 5,
type = "ratings")
eval_accuracy_ubcf = calcPredictionAccuracy(
x = recc_predicted_ev_ubcf,
data = getData(eval_sets, "unknown"),
byUser = F)
eval_accuracy_ubcf
## RMSE MSE MAE
## 1.377218 1.896729 1.049753
Основа подсчета схожести: косинусное расстояние.
Использованные характеристики:
Рассуждения: кажется, что читателю, которому понравился тот или иной комикс, могут понравиться и другие комиксы, выпущенные в том же жанре, тематике, серии и/или тем же издательством. Данное предположение основывается на том, что комиксы одной серии могут быть так или иначе связаны друг с другом – сюжетом, персонажами, вселенной, etc., а принадлежность комиксов к одному жанру, тематике подразумевать наличие каких-то других общих элементов: стилистических приемов, сюжетов в фокусе, etc. Логика тут самая базовая: эти общие элементы и могут быть тем, за что читателю понравился тот или иной комикс и может понравиться рекомендованный. Издатели же, в свою очередь, зачастую могут концентрироваться на каких-то конкретных аудиториях и выпускать комиксы, заточенные под специфично их вкусы.
Двигаясь дальше, объем комиксов – характеристика, определяющая тот или иной формат потребления: к примеру, можно отдохнуть после работы/учебы за чтением небольшой книжечки (30-50 страниц) или же, наоборот, со всей серьезностью целый день изучать вселенную большущего талмуда (>500 страниц), целиком и полностью погружаясь в мельчайшие детали. В соответсвии с этим, можно предположить, что у читатаелей могут быть некоторые предпочтения касательно тех или иных форматов потребления, а значит и некоторые предпочтения касательно количества страниц.
Последнее, что хотелось бы отметить – год публикации. Тут предположение следующее: определенный временной период существования индустрии может быть связан с её конкретной экономической или кульутрной конъюнктурой – некими правилами, определяющими наличие общих элементов в большинсте комиксов тех лет. Эти общие элементы, как уже было раскрыто в начале, могут быть как раз тем, за что тот или иной комикс понравился читателю, а значит и тем, за что другие комиксы изданные в то же самое время могут тому же самому читателю так же и понравиться.
Доплнительно: Честно говоря сложно привзять какую-то осмысленную логику к средней оценке как фактору, определяющему схожесть комиксов: абсолютно непохожие комиксы могут иметь одинаковые средние оценки. Однако на практике оказалось, что учет этой переменной улучшает качество рекомендаций – поэтому она была включена в финальную модель.
rownames = comics$book_id
comics = comics %>% dplyr::select(-book_id)
rownames(comics) = rownames
sim = coop::cosine(t(as.matrix(comics)), use = "complete.obs")
rownames(sim) = rownames
colnames(sim) = rownames
diag(sim) = 0
Вспомогательная функция, находящая и выводящая до 10 наиболее похожих комиксов на те, что были переданы в неё в качестве аргумента. До 10, так как порекомендованные комиксы удаляются из выдачи, если пользователю их уже оценил – в таком случае может быть меньше, чем 10. Помимо описанных действий функция также подразумевает суммаризацию результатов: что было найдено на основе чего (наименование или id пользователя тоже передаётся в фукцию в качестве аругмента).
comicsAccess = function(book_df, input){
simCut = sim[,as.character(book_df$book_id)]
mostSimilar = head(sort(simCut, decreasing = T), n = 10)
a = which(simCut %in% mostSimilar, arr.ind = T, useNames = T)
index = arrayInd(a, .dim = dim(sim))
# unique - на случай если одна и та же книга являются наиболее похожей сразу на несколько оцененных
result = rownames(sim)[index[,1]]
result
mostSimilar = data.frame(book_id = as.numeric(result), similar = simCut[index[,1]])
result = unique(mostSimilar) %>%
left_join(goodread_comics_ren) %>%
select(
book_id,
title,
similar,
num_pages,
publisher,
publication_year,
popular_shelves.0.name,
popular_shelves.1.name,
popular_shelves.2.name,
popular_shelves.3.name) %>%
arrange(-similar) %>%
# На случай если книга сильно похожа на другую из оцененных
anti_join(book_df, by = "book_id")
print("Таблица 2 - наша рекомендация для пользователя")
number_of_comics = nrow(result)
whisker.render("Итог: {{number_of_comics}} комиксов были подобраны специально для {{input}}") %>%
print
print(result)
return(result)
}
Функция, находящая и выводящая наиболее похожие комиксы на те, что понравились (оценка не ниже 4) пользователю, id которого было передано в эту функцию в качестве аргумента. Важно ометить, что данная функция самостоятельно всего лишь обрабатывает сценарий отсутствия понравившихся комиксов (рекомендуя дефолтный объект) и отбирает эти понравившиеся комиксы, если они всё же имеются; все остальное (рекомендации, суммаризация, вывод) делает вспомогательная функция comicsAccess(), описанная выше.
getComicsById = function(p_id) {
if (any(reviews$user_id == p_id)) {
person = reviews %>% filter(user_id == p_id & rating > 3)
print("Таблица 1 - то, что нравится пользователю")
user_books = filter(goodread_comics_ren, book_id %in% person$book_id) %>% dplyr::select(
book_id,
title,
num_pages,
publisher,
publication_year,
popular_shelves.0.name,
popular_shelves.1.name,
popular_shelves.2.name,
popular_shelves.3.name)
print(user_books)
if (nrow(person) == 0) {
print("All my friends are dead by Avery Monsen and Jory John - наша рекомендация для пользователя")
# На случай если оцененного комикса не окажется в матрице схожести
} else { comicsAccess(user_books, p_id) }
} else { print("Введенного пользователя нет в базе, но мы можем вам порекомендовать следующее: All my friends are dead by Avery Monsen and Jory John")}
}
Функция, находящая и выводящая наиболее похожие комиксы на те объекты (или объект в случае серии), что был найден в соответсвии с наименованием (нравящийся комикс или серия комиксов), введенным внешним пользователем. Данная функция самостоятельно обрабатывает сценарий отсутствия комикса или серии в наборе данных (информируя об этом через консоль) и находит нужные объекты (объект), если они всё же представлены в данных; как и в случае с предыдущей функцией getComicsById() формировние рекомендации, суммаризацию и вывод в консоль делает вспомогательная функция comicsAccess(), описанная выше.
getComicsByComics = function(c_name) {
input = c_name
goodread_comics_work = goodread_comics_ren
# "+" обладает опредленными нюансами при детекции – лучше заменить на слово "plus"
c_name = str_replace_all(c_name, "\\+", "plus")
c_name = str_to_lower(c_name)
goodread_comics_work$title = str_replace_all(goodread_comics_work$title, "\\+", "plus")
goodread_comics_work$title = str_to_lower(goodread_comics_work$title)
goodread_comics_work = goodread_comics_work %>%
mutate(
inputed_comics = case_when(
str_detect(goodread_comics_work$title, c_name) ~ 1, TRUE ~ 0)) %>%
filter(inputed_comics == 1)
if (nrow(goodread_comics_work) > 4) { goodread_comics_work = goodread_comics_work[1:5,]}
if (nrow(goodread_comics_work) == 0) {
print("Введенного комикса или серии нет в базе")
} else {
goodread_comics_work$title = str_to_title(goodread_comics_work$title)
print("Таблица 1 - то, что было найдено в системе для пользователя")
print(goodread_comics_work %>% dplyr::select(
book_id,
title,
num_pages,
publisher,
publication_year,
popular_shelves.0.name,
popular_shelves.1.name,
popular_shelves.2.name,
popular_shelves.3.name))
comicsAccess(goodread_comics_work, input)
}}
Оценивание рекомендации: Мы проверили рекомендации, выдаваемые нашей системой, на адектватность - сравнивали характеристики комиксов (переменные publisher и полки), уже оцененых пользователем, с характеристиками комиксов, которые рекомендует система. Как мы можем заметить, значительные доли в обоих случаях составляют graphic-novels, graphic-novel, comics.
По переменной publisher: Image Comics и Marvel.
## [1] "Таблица 1 - то, что нравится пользователю"
## book_id title
## 1: 13570 Tsubasa: RESERVoir CHRoNiCLE, Vol. 01
## 2: 17570208 Why Grizzly Bears Should Wear Underpants
## 3: 7326875 Library Wars: Love & War, Vol. 2 (Library Wars: Love & War #2)
## num_pages publisher publication_year popular_shelves.0.name
## 1: 197 Del Rey 2004 to-read
## 2: 168 Andrews McMeel Publishing 2013 to-read
## 3: 189 VIZ Media 2010 manga
## popular_shelves.1.name popular_shelves.2.name popular_shelves.3.name
## 1: manga fantasy favorites
## 2: humor graphic-novels comics
## 3: to-read graphic-novels romance
## [1] "Таблица 2 - наша рекомендация для пользователя"
## [1] "Итог: 10 комиксов были подобраны специально для 80f2125bfbed5051dbdf401b0aa3585d"
## book_id title similar
## 1 555430 Ghost Hunt, Vol. 1 (Ghost Hunt, #1) 0.9999649
## 2 7982114 Tsubasa: RESERVoir CHRoNiCLE, Vol. 28 0.9997215
## 3 1118746 Kitchen Princess, Vol. 05 (Kitchen Princess, #5) 0.8075796
## 4 31046 Basilisk: The Kouga Ninja Scrolls, Vol. 1 (Basilisk #1) 0.7900993
## 5 233692 The Far Side Gallery 4 0.4611852
## 6 6801595 Cactus's Secret, Vol. 01 0.4201217
## 7 11029797 Skip Beat!, Vol. 26 0.3837097
## 8 3003909 Skip Beat!, Vol. 16 0.3825959
## 9 2425355 Skip Beat!, Vol. 13 0.3814773
## 10 15799936 How to Tell If Your Cat Is Plotting to Kill You 0.3443392
## num_pages publisher publication_year popular_shelves.0.name
## 1 224 Del Rey 2005 to-read
## 2 276 Del Rey 2010 to-read
## 3 200 Del Rey 2008 to-read
## 4 224 Del Rey 2006 to-read
## 5 167 Andrews McMeel Publishing 1993 to-read
## 6 192 VIZ Media 2010 to-read
## 7 184 VIZ Media LLC 2012 manga
## 8 210 VIZ Media LLC 2009 manga
## 9 208 VIZ Media LLC 2008 manga
## 10 132 Andrews McMeel Publishing 2012 to-read
## popular_shelves.1.name popular_shelves.2.name popular_shelves.3.name
## 1 manga horror mystery
## 2 manga fantasy favorites
## 3 manga romance kitchen-princess
## 4 manga comics seinen
## 5 comics humor fiction
## 6 manga romance favorites
## 7 to-read romance favorites
## 8 to-read romance favorites
## 9 to-read romance favorites
## 10 humor currently-reading graphic-novels
analise = goodread_comics %>%
filter(title %in% recommendation$title) %>%
select(popular_shelves.0.name, popular_shelves.1.name, popular_shelves.2.name, popular_shelves.3.name)
rec_shelves = as.data.frame(as.vector(unlist(as.list(analise)))) %>%
mutate(shelves = as.vector(unlist(as.list(analise)))) %>%
count(shelves) %>%
arrange(-n) %>%
mutate(prop = n/sum(n))
# Сравним с комиксами, оцененными данным пользователем
have = reviews %>%
filter(user_id == "8e7e5b546a63cb9add8431ee6914cf59")
have_shelves = goodread_comics %>%
filter(book_id %in% have$book_id) %>%
select(popular_shelves.0.name, popular_shelves.1.name, popular_shelves.2.name, popular_shelves.3.name)
user_shelves = as.data.frame(as.vector(unlist(as.list(have_shelves)))) %>%
mutate(shelves = as.vector(unlist(as.list(have_shelves)))) %>%
count(shelves) %>%
arrange(-n) %>%
mutate(prop = n/sum(n))
shelf = user_shelves %>%
full_join(rec_shelves, by = "shelves", suffix = c(".user's", ".reccomended"))
shelf
## shelves n.user's prop.user's n.reccomended prop.reccomended
## 1 to-read 13 0.25000000 10 0.250
## 2 graphic-novels 12 0.23076923 1 0.025
## 3 graphic-novel 10 0.19230769 NA NA
## 4 comics 6 0.11538462 2 0.050
## 5 cómics 2 0.03846154 NA NA
## 6 currently-reading 2 0.03846154 1 0.025
## 7 fantasy 2 0.03846154 1 0.025
## 8 horror 2 0.03846154 1 0.025
## 9 doctor-who 1 0.01923077 NA NA
## 10 humor 1 0.01923077 2 0.050
## 11 picture-books 1 0.01923077 NA NA
## 12 manga NA NA 8 0.200
## 13 favorites NA NA 5 0.125
## 14 romance NA NA 5 0.125
## 15 fiction NA NA 1 0.025
## 16 kitchen-princess NA NA 1 0.025
## 17 mystery NA NA 1 0.025
## 18 seinen NA NA 1 0.025
# Cравним по publisher
pub = goodread_reviews %>%
filter(user_id == "8e7e5b546a63cb9add8431ee6914cf59") %>%
arrange(-rating) %>%
top_n(5)
pub1 = goodread_comics %>%
filter(book_id %in% pub$book_id) %>%
count(publisher) %>%
arrange(-n) %>%
mutate(prop = n/sum(n))
# То, что порекомендовала система
sis = goodread_comics %>%
filter(title %in% recommendation$title) %>%
count(publisher) %>%
arrange(-n) %>%
mutate(prop = n/sum(n))
publish = pub1 %>%
full_join(sis, by = "publisher", suffix = c(".user's", ".reccommended"))
publish
## publisher n.user's prop.user's n.reccommended
## 1: Graphix 1 0.2 NA
## 2: IDW Publishing 1 0.2 NA
## 3: Image Comics 1 0.2 NA
## 4: Marvel 1 0.2 NA
## 5: Vertigo 1 0.2 NA
## 6: Del Rey NA NA 4
## 7: VIZ Media LLC NA NA 3
## 8: Andrews McMeel Publishing NA NA 2
## 9: VIZ Media NA NA 1
## prop.reccommended
## 1: NA
## 2: NA
## 3: NA
## 4: NA
## 5: NA
## 6: 0.4
## 7: 0.3
## 8: 0.2
## 9: 0.1
Так же мы проверили рекомендации, которые выдает наша функция на основании любимой категории. Для примера мы использовали комикс из серии “Death Note”. Наши представления и знания об этой серии комиксов позволили оценить рекомендации, выданные нашей системой. Можно заметить, что система выдает несколько категорий комиксов, в которые включен наш комикс: manga, fantasy, favorites. Так же можно заметить, что среди рекомендованных комиксов присутствует жанр romance, что не совсем подходит для нашего комикса.
В целом, полученные результаты не противоречат нашим представлениям, разве что за редким исключением.
recommendation1 = getComicsByComics("Death Note")
## [1] "Таблица 1 - то, что было найдено в системе для пользователя"
## book_id title num_pages
## 1: 13619 Death Note, Vol. 2: Confluence (Death Note, #2) 197
## publisher publication_year popular_shelves.0.name popular_shelves.1.name
## 1: VIZ Media LLC 2005 to-read manga
## popular_shelves.2.name popular_shelves.3.name
## 1: mangá graphic-novels
## [1] "Таблица 2 - наша рекомендация для пользователя"
## [1] "Итог: 10 комиксов были подобраны специально для Death Note"
## book_id title
## 1 10954370 Kamisama Kiss, Vol. 4
## 2 13183545 Kamisama Kiss, Vol. 9
## 3 832225 Eyeshield 21, Vol. 1: The Boy With the Golden Legs
## 4 26426 Fullmetal Alchemist, Vol. 12 (Fullmetal Alchemist, #12)
## 5 6065429 Fullmetal Alchemist, Vol. 19 (Fullmetal Alchemist, #19)
## 6 11029797 Skip Beat!, Vol. 26
## 7 3003909 Skip Beat!, Vol. 16
## 8 2425355 Skip Beat!, Vol. 13
## 9 400636 Naruto, Vol. 15: Naruto's Ninja Handbook! (Naruto, #15)
## 10 13590 Hikaru no Go, Vol. 1: Descent of the Go Master (Hikaru no Go, #1)
## similar num_pages publisher publication_year popular_shelves.0.name
## 1 0.9999899 192 VIZ Media LLC 2011 to-read
## 2 0.9999842 192 VIZ Media LLC 2012 to-read
## 3 0.9999056 208 VIZ Media LLC 2005 to-read
## 4 0.9998010 192 VIZ Media LLC 2007 to-read
## 5 0.8162968 192 VIZ Media LLC 2009 mangá
## 6 0.8160213 184 VIZ Media LLC 2012 manga
## 7 0.8154664 210 VIZ Media LLC 2009 manga
## 8 0.8149065 208 VIZ Media LLC 2008 manga
## 9 0.8093309 184 VIZ Media LLC 2007 to-read
## 10 0.8002280 187 VIZ Media LLC 2004 manga
## popular_shelves.1.name popular_shelves.2.name popular_shelves.3.name
## 1 manga fantasy romance
## 2 manga fantasy romance
## 3 mangá manga favorites
## 4 mangá fantasy graphic-novels
## 5 to-read fantasy favorites
## 6 to-read romance favorites
## 7 to-read romance favorites
## 8 to-read romance favorites
## 9 mangá naruto fantasy
## 10 to-read graphic-novels comics
## shelves n prop
## 1 to-read 10 0.250
## 2 manga 7 0.175
## 3 fantasy 5 0.125
## 4 favorites 5 0.125
## 5 romance 5 0.125
## 6 mangá 4 0.100
## 7 graphic-novels 2 0.050
## 8 comics 1 0.025
## 9 naruto 1 0.025
Случай, когда у пользователя мало положительных оценок: пользователь функции будет уведомлен о малом количестве положительных оценок и, более того, получит универсальную рекомендацию.
getComicsCF(id = "003f7ff55fde1b9717dc1f90bd47cb1e")
## [1] "Мало положительных оценок для персонализрованной рекомендации, но есть универсальное предложение: All my friends are dead by Avery Monsen and Jory John"
Случай, когда у пользователя достаточно положительных оценок: пользователь функции получит персонализированные рекомендации.
getComicsCF(id = "08d805375530cc208801531ca7fdefbc")
## [1] "A Bride's Story, Vol. 3 (A Bride's Story, #3)"
## [2] "Adventure Time Vol. 5"
## [3] "Wolverine: Old Man Logan"
## [4] "Superman: Red Son"
## [5] "Tsubasa: RESERVoir CHRoNiCLE, Vol. 01"
*Из-за слишком грамоздкого аутпута функции, код представлен только для первых двух примеров
Пример 1: Если мне нравятся комиксы про человека паука, увижу ли я комиксы про бетмена?
Ответ 1: Нет, к сожалению отсутвуют полки (“superheroes”, к примеру), которые могли бы их как-то объединять.
Код для проверки: getComicsByComics(“Spider-Man”).
getComicsByComics("Spider-Man")
## [1] "Таблица 1 - то, что было найдено в системе для пользователя"
## book_id title
## 1: 599052 The Amazing Spider-Man, Vol. 1: Coming Home
## 2: 23732103 Miles Morales: Ultimate Spider-Man Ultimate Collection Book 1
## 3: 429229 Ultimate Spider-Man, Volume 4: Legacy
## 4: 19539416 The Superior Spider-Man, Vol. 6: Goblin Nation
## 5: 28118845 Spider-Man/Deadpool, Vol. 1: Isn't It Bromantic
## num_pages publisher publication_year popular_shelves.0.name
## 1: Marvel Comics 2001 to-read
## 2: 400 Marvel Comics 2015 to-read
## 3: 160 Marvel 2006 to-read
## 4: 168 Marvel 2014 comics
## 5: 136 Marvel Comics 2016 to-read
## popular_shelves.1.name popular_shelves.2.name popular_shelves.3.name
## 1: comics graphic-novels cómics
## 2: comics graphic-novel graphic-novels
## 3: comics graphic-novels marvel
## 4: graphic-novels marvel spider-man
## 5: comics currently-reading graphic-novels
## [1] "Таблица 2 - наша рекомендация для пользователя"
## [1] "Итог: 7 комиксов были подобраны специально для Spider-Man"
## book_id title similar
## 1 25066752 Black Widow, Volume 3: Last Days 0.9999732
## 2 198475 Miracleman, Book Two: The Red King Syndrome 0.9994564
## 3 3192661 New X-Men by Grant Morrison Ultimate Collection - Book 1 0.4417685
## 4 299796 X-23: Innocence Lost 0.4417685
## 5 306608 X-Men: Days of Future Past 0.4417685
## 6 43612 Runaways, Vol. 2: Teenage Wasteland (Runaways, #2) 0.4363392
## 7 6512723 Dark Reign: Deadpool/Thunderbolts 0.4351112
## num_pages publisher publication_year popular_shelves.0.name
## 1 176 Marvel Comics 2015 to-read
## 2 224 Marvel Comics 2014 to-read
## 3 376 Marvel 2008 to-read
## 4 144 Marvel 2006 to-read
## 5 184 Marvel 2006 to-read
## 6 144 Marvel to-read
## 7 96 Marvel 2009 to-read
## popular_shelves.1.name popular_shelves.2.name popular_shelves.3.name
## 1 graphic-novels comics marvel
## 2 comics graphic-novels graphic-novel
## 3 comics graphic-novels marvel
## 4 comics graphic-novels marvel
## 5 comics graphic-novels currently-reading
## 6 comics graphic-novels graphic-novel
## 7 comics graphic-novels marvel
## book_id title similar
## 1 25066752 Black Widow, Volume 3: Last Days 0.9999732
## 2 198475 Miracleman, Book Two: The Red King Syndrome 0.9994564
## 3 3192661 New X-Men by Grant Morrison Ultimate Collection - Book 1 0.4417685
## 4 299796 X-23: Innocence Lost 0.4417685
## 5 306608 X-Men: Days of Future Past 0.4417685
## 6 43612 Runaways, Vol. 2: Teenage Wasteland (Runaways, #2) 0.4363392
## 7 6512723 Dark Reign: Deadpool/Thunderbolts 0.4351112
## num_pages publisher publication_year popular_shelves.0.name
## 1 176 Marvel Comics 2015 to-read
## 2 224 Marvel Comics 2014 to-read
## 3 376 Marvel 2008 to-read
## 4 144 Marvel 2006 to-read
## 5 184 Marvel 2006 to-read
## 6 144 Marvel to-read
## 7 96 Marvel 2009 to-read
## popular_shelves.1.name popular_shelves.2.name popular_shelves.3.name
## 1 graphic-novels comics marvel
## 2 comics graphic-novels graphic-novel
## 3 comics graphic-novels marvel
## 4 comics graphic-novels marvel
## 5 comics graphic-novels currently-reading
## 6 comics graphic-novels graphic-novel
## 7 comics graphic-novels marvel
Пример 2: Пользователю, которому нравятся в основном комиксы про супергероев, я ожидаю рекомендации комиксов про супергероев, при этом большинство из них будет входить в ту же серию комиксов, куда входили понравившиеся пользователю комиксы
Пример 3: Если мне нравятся комиксы про человека паука, увижу ли я комиксы про бетмена?
Ответ 2: Т.к. вероятно у нас просто отсутвуют комиксы про венома в исходных данных (не то чтобы он один из самых популярных персонажей), комиксы с ним не порекомендуются. Тем не менне из-за схожих полок и издательства, порекомендуются комиксы про других супергероев Марвел.
Код для проверки: getComicsByComics(“Spider-Man”)
getComicsByComics(“Iron Man”).
getComicsByComics("Iron Man")
## [1] "Таблица 1 - то, что было найдено в системе для пользователя"
## book_id title
## 1: 6746752 The Invincible Iron Man, Volume 3: World's Most Wanted, Book 2
## num_pages publisher publication_year popular_shelves.0.name
## 1: 160 Marvel 2010 comics
## popular_shelves.1.name popular_shelves.2.name popular_shelves.3.name
## 1: to-read graphic-novels marvel
## [1] "Таблица 2 - наша рекомендация для пользователя"
## [1] "Итог: 10 комиксов были подобраны специально для Iron Man"
## book_id title
## 1 20898006 Moon Knight, Volume 1: From the Dead (Moon Knight Vol. V, #1)
## 2 15746861 FF, Volume 4
## 3 3345775 The Immortal Iron Fist, Volume 3: The Book of the Iron Fist
## 4 29775584 Spider-Woman: Shifting Gears, Volume 2: Civil War II
## 5 6394175 Astonishing X-Men: Omnibus
## 6 19539416 The Superior Spider-Man, Vol. 6: Goblin Nation
## 7 6883138 Dark Reign: Young Avengers
## 8 3192661 New X-Men by Grant Morrison Ultimate Collection - Book 1
## 9 299796 X-23: Innocence Lost
## 10 306608 X-Men: Days of Future Past
## similar num_pages publisher publication_year popular_shelves.0.name
## 1 0.9999976 136 Marvel 2014 comics
## 2 0.9999703 168 Marvel 2015 comics
## 3 0.9998623 Marvel 2008 comics
## 4 0.9998431 112 Marvel 2017 comics
## 5 0.9992204 672 Marvel 2009 comics
## 6 0.8583594 168 Marvel 2014 comics
## 7 0.8473964 120 Marvel 2010 comics
## 8 0.8014056 376 Marvel 2008 to-read
## 9 0.8014056 144 Marvel 2006 to-read
## 10 0.8014056 184 Marvel 2006 to-read
## popular_shelves.1.name popular_shelves.2.name popular_shelves.3.name
## 1 graphic-novels marvel graphic-novel
## 2 to-read graphic-novels marvel
## 3 to-read graphic-novels marvel
## 4 to-read graphic-novels graphic-novel
## 5 graphic-novels favorites marvel
## 6 graphic-novels marvel spider-man
## 7 graphic-novels marvel marvel-comics
## 8 comics graphic-novels marvel
## 9 comics graphic-novels marvel
## 10 comics graphic-novels currently-reading
## book_id title
## 1 20898006 Moon Knight, Volume 1: From the Dead (Moon Knight Vol. V, #1)
## 2 15746861 FF, Volume 4
## 3 3345775 The Immortal Iron Fist, Volume 3: The Book of the Iron Fist
## 4 29775584 Spider-Woman: Shifting Gears, Volume 2: Civil War II
## 5 6394175 Astonishing X-Men: Omnibus
## 6 19539416 The Superior Spider-Man, Vol. 6: Goblin Nation
## 7 6883138 Dark Reign: Young Avengers
## 8 3192661 New X-Men by Grant Morrison Ultimate Collection - Book 1
## 9 299796 X-23: Innocence Lost
## 10 306608 X-Men: Days of Future Past
## similar num_pages publisher publication_year popular_shelves.0.name
## 1 0.9999976 136 Marvel 2014 comics
## 2 0.9999703 168 Marvel 2015 comics
## 3 0.9998623 Marvel 2008 comics
## 4 0.9998431 112 Marvel 2017 comics
## 5 0.9992204 672 Marvel 2009 comics
## 6 0.8583594 168 Marvel 2014 comics
## 7 0.8473964 120 Marvel 2010 comics
## 8 0.8014056 376 Marvel 2008 to-read
## 9 0.8014056 144 Marvel 2006 to-read
## 10 0.8014056 184 Marvel 2006 to-read
## popular_shelves.1.name popular_shelves.2.name popular_shelves.3.name
## 1 graphic-novels marvel graphic-novel
## 2 to-read graphic-novels marvel
## 3 to-read graphic-novels marvel
## 4 to-read graphic-novels graphic-novel
## 5 graphic-novels favorites marvel
## 6 graphic-novels marvel spider-man
## 7 graphic-novels marvel marvel-comics
## 8 comics graphic-novels marvel
## 9 comics graphic-novels marvel
## 10 comics graphic-novels currently-reading
Пример 4: В плане команды сказано, что новому пользователю будет порекомендован комикс “All my friends are dead”. Мне было бы интересно посмотреть, какую рекомендацию выдаст вторая рекомендательная система, если ввести туда этот комикс
Ответ 3: Выведутся юмористические комиксы, схожие по жанру-полке с введенным (“humor”).
Код для проверки:
getComicsByComics(“All my friends are dead”)
Пример 5: Что будет если вбить новый id пользователя? Возможно стоит порекомендовать топ самых популярных комиксов
Ответ 4: Мы уведомим пользователя нашей функции о том, что пользователь с введенным id не существует, а так же предлоижм универсальную рекомендацию. Код для проверки:
getComicsById(“666424”)
Пример 6: Ради интереса, можно придумать человека (конкретного пользователя), которому нравятся комиксы определенного жанра/издателя (например, DC и Marvel) и посмотреть, что даст рекомендательная система. Если в рекомендации хотя бы половина из предложенных комиксов будет этих издателей или же комиксов, похожих по содержанию, то можно будет сделать вывод, что система работает корректно.
Ответ 5: При вводе конкретного комикса, система выдает больше комиксов из этой серии, нежели при вводе серии.
Код для проверки:
getComicsByComics(“Batman: Snow”)
getComicsByComics(“batman”)
Пример 7: Если пользователю нравятся только самые старые издания из всех возможных у небольших издательств (Vertigo, Dark horse), что покажет итоговая проверка по полкам при рекомендации.
Ответ 6: Данные не препдологают учет старости/новизны изданий.
Пример 8: Ради интереса, можно придумать человека (конкретного пользователя), которому нравятся комиксы определенного жанра/издателя (например, DC и Marvel) и посмотреть, что даст рекомендательная система. Если в рекомендации хотя бы половина из предложенных комиксов будет этих издателей или же комиксов, похожих по содержанию, то можно будет сделать вывод, что система работает корректно.
Ответ 7: Мы не можем ответить, так как наша система не предусматривает ввода новых пользователей. е серии.
Уже имеющиеся оценки комиксов, а также такие характеристики как жанр, серия, год публикации, количество страниц и издательство могут быть полезны при формировании рекомендаций комиксов. В свою очередь оценка сентимента описания & отзывов, построение сетей на основе полок и прочие инстурменты текстового/сетевого анализа не являются пригодными для каких-либо рекомендательных задач.
Вопрос 1: не очень понятно что должен вводить новый пользователь. и может ли вообще манга повториться 3 раза в полках?
Ответ 1: Новый пользователь может воспользоваться функцией getComicsByComics(), формирующей рекомендации на основе введенного наименования (комикса или серии, нравящихся пользователю). Что касается манги, это был гипотетический пример, иллюстрирующий суть наших манипуляции с полками в виду того, что иногда они могут совпадать у одного и того же комикса (факт, озвученный во время презентации). Гипотетический пример не обязан соответствовать реальности. В связи с этим вопрос про 3 полки с мангой не релевантен.
Вопрос 2:
Вопрос скорее не по проекту, а по презентации проекта Хотелось бы чтобы первые вводные данные были представлены в более сжатом формате, так внимание очень сильно распыляется от такого большого количества кода (хотя само наличие кода в формулах приветствуется)
Вопрос 3: Немного сложно успевать за тем как листается страница с кодом.
Ответ 2: Данный вариант презентации на момент записи был наилучшим из доступных.
Вопрос 4: Нет камеры у участников, путались в словах.
Ответ 3: Данные аспекты нашей презентации вытекают из имевшихся возможностей членов команды касательно времени и формата записи. Итоговый вариант – максимум, доступный к моменту записи.
Вопрос 5: Как можно интерпретировать то что модель в среднем ошибается на 1 балл? Делались ли попытки ее улучшить?
Ответ 4: Модель в среднем ошибается на 1 балл. Это значит, что модель работает хорошо, но не идеально. Попыток улучшить данную модель нами не предпринималось, т.к. данный результат кажется нам удовлетворительным.
Вопрос 6: Не понимаю, зачем так много было рассказывать о текстовом и сетевом анализах (показывать большое количество почти никак не интерпретируемых графиков и графов). Еще не понравилось то, что команда очень много времени потратила на объяснение своих действий в плане написания кода, не понимаю, для чего и для кого это сделано.
Вопрос 7: На мой взгляд, показ результатов первичного анализа можно было сделать короче (например, с помощью презентации, а затем перейти к показу кода для демонстрации рекомендательных систем).
Ответ 5: Вероятно такие проблемы действительно присутствуют, т.к. при записи презентации у нас не было возможности заранее сбалансировать её содержательные части.
Вопрос 8: Использовали ли ребята еще какие-нибудь способы оценки моделей коллаборативный фильтрации, кроме сравнения RMSE, MAE, MSE Замечания: возможно, не стоило показывать прям настолько подробно свою работу, а сделать упор на основных выводах и результатах своих моделей. Чувствовалось что времени немного не хватало.
Ответ 6: Нет, каких-то других способов оценки моделей коллаборативный фильтрации, кроме сравнения RMSE, MAE, MSE мы не использовали. По поводу замечания, смотрите ответ 4.
Вопрос 9: Как вообще планировалось использовать текстовый и сетевой анализ в построении системы? Почти треть видео посвящена ему, и если про сетевой анализ была хотя бы гипотеза, то текстовый сделан как будто “чтобы просто был”.
Ответ 7: Мы планировали использовать все доступные нам инструменты (из лабораторных) и по итогу их работы и результатов посмотреть могут ли какие-то из них быть так или иначе полезны для RS. Оказалось, что нет, не могут.
Вопрос: Что будет, если в content-based системе ввести id несуществующего пользователя?
Ответ (Из примера 5 CB-RS): Мы уведомим пользователя нашей функции о том, что пользователь с введенным id не существует, а так же предлоижм универсальную рекомендацию.