library(data.table)
library(dplyr)
library(ggplot2)
library(lubridate)
library(RColorBrewer)
library(rvest)
urls <- paste0( 'http://www.longecity.org/forum',
                '/topic/82770-nicotinamide-riboside-curated')
pages <-
  read_html(urls) %>%
  html_node(xpath='(//*[@class="last"]/a)[1]/@href') %>%
  html_text() %>%
  gsub(pattern='.*-', replacement='')
if (!is.na(pages)) urls <- paste0(urls, paste0('/page-', seq_len(pages)))
ratings <- 
  do.call( rbind, lapply( urls, function(url) {
    posts <- html_nodes( read_html(url), '.post_body')
    do.call( rbind, lapply( posts, function(post) {
      ratings <- post %>%
        html_nodes('.ratingtitle') %>%
        html_text()
      if (length(ratings)) {
        date <- post %>%
          html_nodes('.published') %>%
          html_attr('title') %>%
          as_date()
        count <- post %>%
          html_nodes('.ratingfreq') %>%
          html_text() %>%
          as.numeric()
        data.table( date, rating = ratings, frequency = count)
      }
    }))
  }))
rating_types <- c(
  'Good Point' = "Positive",
  'Informative' = "Positive",
  'Cheerful' = "Positive",
  'Well Written' = "Positive",
  'WellResearched' = "Positive",
  'like' = "Neutral",
  'Agree' = "Neutral",
  'Enjoying the show' = "Neutral",
  'Needs references' = "Neutral",
  'unsure' = "Neutral",
  'Disagree' = "Neutral",
  'dislike' = "Neutral",
  'Ill informed' = "Negative",
  'Off-Topic' = "Negative",
  'Dangerous, Irresponsible' = "Negative",
  'Pointless, Timewasting' = "Negative",
  'Unfriendly' = "Negative")
ratings[, period := format(date, "%Y-%m")]
ratings[, rating := factor(rating, levels = names(rating_types))]
ratings[, type := factor(rating_types[rating], levels=unique(rating_types))]
rating_colors <- c( rev(brewer.pal(6,"Greens")[-1]),
                    rev(brewer.pal(4, "Blues")[-1]),
                    "#FFFFFF",
                    brewer.pal(4, "Purples")[-1],
                    brewer.pal(6,"Reds")[-1])
names(rating_colors) <- names(rating_types)
type_colors <- c('Negative' = '#FB6A4A',
                 'Neutral' = "#C5CFE4",
                 'Positive' = '#74C476')

Total rating percentages.

ratings %>%
  group_by(rating) %>%
  tally(frequency) %>%
  mutate(n = round(100 * n / sum(n))) %>%
  arrange(rating) %>%
  ggplot(data = ., aes(x = factor(1), y = n, fill = rating)) +
    geom_bar(stat = 'identity', width = 1) +
    scale_fill_manual(values = rating_colors) +
    coord_polar(theta = "y") +
    theme_void()

Rating type volume each month.

ratings %>%
  arrange(period, type) %>%
  ggplot(data = ., aes(period, frequency, fill = type)) +
    geom_bar(position = "stack", stat = "identity") +
    scale_fill_manual(values = type_colors) +
    guides(fill = guide_legend(reverse=TRUE, title = 'Rating Type')) +
    theme(axis.text.x  = element_text(angle=50, vjust = .5))

Ratings as a percent of total ratings each month.

ratings %>%
  group_by(period) %>%
  mutate(freqTtl = sum(frequency)) %>%
  group_by(period, rating) %>%
  mutate(percent = round(100 * frequency / freqTtl, 2)) %>%
  arrange(period, factor(rating, levels=names(rating_types))) %>%
  ggplot(aes(period, percent, fill = factor(rating, levels = names(rating_types)))) +
    geom_bar(position = "stack", stat = "identity") +
    scale_fill_manual(values = rating_colors) +
    guides(fill = guide_legend(reverse=TRUE, title = 'Rating')) +
    theme(axis.text.x  = element_text(angle=50, vjust = .5))

LS0tCnRpdGxlOiAiTG9uZ2VjaXR5IEZvcnVtIE5pY290aW5hbWlkZSBSaWJvc2lkZSBbY3VyYXRlZF0gVGhyZWFkIFBvc3QgUmF0aW5ncyIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKYGBge3J9CmxpYnJhcnkoZGF0YS50YWJsZSkKbGlicmFyeShkcGx5cikKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KGx1YnJpZGF0ZSkKbGlicmFyeShSQ29sb3JCcmV3ZXIpCmxpYnJhcnkocnZlc3QpCmBgYAoKYGBge3J9CnVybHMgPC0gcGFzdGUwKCAnaHR0cDovL3d3dy5sb25nZWNpdHkub3JnL2ZvcnVtJywKICAgICAgICAgICAgICAgICcvdG9waWMvODI3NzAtbmljb3RpbmFtaWRlLXJpYm9zaWRlLWN1cmF0ZWQnKQpwYWdlcyA8LQogIHJlYWRfaHRtbCh1cmxzKSAlPiUKICBodG1sX25vZGUoeHBhdGg9JygvLypbQGNsYXNzPSJsYXN0Il0vYSlbMV0vQGhyZWYnKSAlPiUKICBodG1sX3RleHQoKSAlPiUKICBnc3ViKHBhdHRlcm49Jy4qLScsIHJlcGxhY2VtZW50PScnKQppZiAoIWlzLm5hKHBhZ2VzKSkgdXJscyA8LSBwYXN0ZTAodXJscywgcGFzdGUwKCcvcGFnZS0nLCBzZXFfbGVuKHBhZ2VzKSkpCmBgYAoKYGBge3J9CnJhdGluZ3MgPC0gCiAgZG8uY2FsbCggcmJpbmQsIGxhcHBseSggdXJscywgZnVuY3Rpb24odXJsKSB7CiAgICBwb3N0cyA8LSBodG1sX25vZGVzKCByZWFkX2h0bWwodXJsKSwgJy5wb3N0X2JvZHknKQogICAgZG8uY2FsbCggcmJpbmQsIGxhcHBseSggcG9zdHMsIGZ1bmN0aW9uKHBvc3QpIHsKICAgICAgcmF0aW5ncyA8LSBwb3N0ICU+JQogICAgICAgIGh0bWxfbm9kZXMoJy5yYXRpbmd0aXRsZScpICU+JQogICAgICAgIGh0bWxfdGV4dCgpCiAgICAgIGlmIChsZW5ndGgocmF0aW5ncykpIHsKICAgICAgICBkYXRlIDwtIHBvc3QgJT4lCiAgICAgICAgICBodG1sX25vZGVzKCcucHVibGlzaGVkJykgJT4lCiAgICAgICAgICBodG1sX2F0dHIoJ3RpdGxlJykgJT4lCiAgICAgICAgICBhc19kYXRlKCkKICAgICAgICBjb3VudCA8LSBwb3N0ICU+JQogICAgICAgICAgaHRtbF9ub2RlcygnLnJhdGluZ2ZyZXEnKSAlPiUKICAgICAgICAgIGh0bWxfdGV4dCgpICU+JQogICAgICAgICAgYXMubnVtZXJpYygpCiAgICAgICAgZGF0YS50YWJsZSggZGF0ZSwgcmF0aW5nID0gcmF0aW5ncywgZnJlcXVlbmN5ID0gY291bnQpCiAgICAgIH0KICAgIH0pKQogIH0pKQpgYGAKCmBgYHtyLCByZXN1bHRzPSdoaWRlJ30KcmF0aW5nX3R5cGVzIDwtIGMoCiAgJ0dvb2QgUG9pbnQnID0gIlBvc2l0aXZlIiwKICAnSW5mb3JtYXRpdmUnID0gIlBvc2l0aXZlIiwKICAnQ2hlZXJmdWwnID0gIlBvc2l0aXZlIiwKICAnV2VsbCBXcml0dGVuJyA9ICJQb3NpdGl2ZSIsCiAgJ1dlbGxSZXNlYXJjaGVkJyA9ICJQb3NpdGl2ZSIsCiAgJ2xpa2UnID0gIk5ldXRyYWwiLAogICdBZ3JlZScgPSAiTmV1dHJhbCIsCiAgJ0Vuam95aW5nIHRoZSBzaG93JyA9ICJOZXV0cmFsIiwKICAnTmVlZHMgcmVmZXJlbmNlcycgPSAiTmV1dHJhbCIsCiAgJ3Vuc3VyZScgPSAiTmV1dHJhbCIsCiAgJ0Rpc2FncmVlJyA9ICJOZXV0cmFsIiwKICAnZGlzbGlrZScgPSAiTmV1dHJhbCIsCiAgJ0lsbCBpbmZvcm1lZCcgPSAiTmVnYXRpdmUiLAogICdPZmYtVG9waWMnID0gIk5lZ2F0aXZlIiwKICAnRGFuZ2Vyb3VzLCBJcnJlc3BvbnNpYmxlJyA9ICJOZWdhdGl2ZSIsCiAgJ1BvaW50bGVzcywgVGltZXdhc3RpbmcnID0gIk5lZ2F0aXZlIiwKICAnVW5mcmllbmRseScgPSAiTmVnYXRpdmUiKQpyYXRpbmdzWywgcGVyaW9kIDo9IGZvcm1hdChkYXRlLCAiJVktJW0iKV0KcmF0aW5nc1ssIHJhdGluZyA6PSBmYWN0b3IocmF0aW5nLCBsZXZlbHMgPSBuYW1lcyhyYXRpbmdfdHlwZXMpKV0KcmF0aW5nc1ssIHR5cGUgOj0gZmFjdG9yKHJhdGluZ190eXBlc1tyYXRpbmddLCBsZXZlbHM9dW5pcXVlKHJhdGluZ190eXBlcykpXQpgYGAKCmBgYHtyfQpyYXRpbmdfY29sb3JzIDwtIGMoIHJldihicmV3ZXIucGFsKDYsIkdyZWVucyIpWy0xXSksCiAgICAgICAgICAgICAgICAgICAgcmV2KGJyZXdlci5wYWwoNCwgIkJsdWVzIilbLTFdKSwKICAgICAgICAgICAgICAgICAgICAiI0ZGRkZGRiIsCiAgICAgICAgICAgICAgICAgICAgYnJld2VyLnBhbCg0LCAiUHVycGxlcyIpWy0xXSwKICAgICAgICAgICAgICAgICAgICBicmV3ZXIucGFsKDYsIlJlZHMiKVstMV0pCm5hbWVzKHJhdGluZ19jb2xvcnMpIDwtIG5hbWVzKHJhdGluZ190eXBlcykKdHlwZV9jb2xvcnMgPC0gYygnTmVnYXRpdmUnID0gJyNGQjZBNEEnLAogICAgICAgICAgICAgICAgICdOZXV0cmFsJyA9ICIjQzVDRkU0IiwKICAgICAgICAgICAgICAgICAnUG9zaXRpdmUnID0gJyM3NEM0NzYnKQpgYGAKClRvdGFsIHJhdGluZyBwZXJjZW50YWdlcy4KCmBgYHtyLCBmaWcud2lkdGg9MTB9CnJhdGluZ3MgJT4lCiAgZ3JvdXBfYnkocmF0aW5nKSAlPiUKICB0YWxseShmcmVxdWVuY3kpICU+JQogIG11dGF0ZShuID0gcm91bmQoMTAwICogbiAvIHN1bShuKSkpICU+JQogIGFycmFuZ2UocmF0aW5nKSAlPiUKICBnZ3Bsb3QoZGF0YSA9IC4sIGFlcyh4ID0gZmFjdG9yKDEpLCB5ID0gbiwgZmlsbCA9IHJhdGluZykpICsKICAgIGdlb21fYmFyKHN0YXQgPSAnaWRlbnRpdHknLCB3aWR0aCA9IDEpICsKICAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IHJhdGluZ19jb2xvcnMpICsKICAgIGNvb3JkX3BvbGFyKHRoZXRhID0gInkiKSArCiAgICB0aGVtZV92b2lkKCkKCmBgYAoKClJhdGluZyB0eXBlIHZvbHVtZSBlYWNoIG1vbnRoLgoKYGBge3IsIGZpZy5hbGlnbj0nY2VudGVyJywgZmlnLndpZHRoPTEwfQpyYXRpbmdzICU+JQogIGFycmFuZ2UocGVyaW9kLCB0eXBlKSAlPiUKICBnZ3Bsb3QoZGF0YSA9IC4sIGFlcyhwZXJpb2QsIGZyZXF1ZW5jeSwgZmlsbCA9IHR5cGUpKSArCiAgICBnZW9tX2Jhcihwb3NpdGlvbiA9ICJzdGFjayIsIHN0YXQgPSAiaWRlbnRpdHkiKSArCiAgICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSB0eXBlX2NvbG9ycykgKwogICAgZ3VpZGVzKGZpbGwgPSBndWlkZV9sZWdlbmQocmV2ZXJzZT1UUlVFLCB0aXRsZSA9ICdSYXRpbmcgVHlwZScpKSArCiAgICB0aGVtZShheGlzLnRleHQueCAgPSBlbGVtZW50X3RleHQoYW5nbGU9NTAsIHZqdXN0ID0gLjUpKQpgYGAKClJhdGluZ3MgYXMgYSBwZXJjZW50IG9mIHRvdGFsIHJhdGluZ3MgZWFjaCBtb250aC4KYGBge3IsIGZpZy53aWR0aD0xMH0KcmF0aW5ncyAlPiUKICBncm91cF9ieShwZXJpb2QpICU+JQogIG11dGF0ZShmcmVxVHRsID0gc3VtKGZyZXF1ZW5jeSkpICU+JQogIGdyb3VwX2J5KHBlcmlvZCwgcmF0aW5nKSAlPiUKICBtdXRhdGUocGVyY2VudCA9IHJvdW5kKDEwMCAqIGZyZXF1ZW5jeSAvIGZyZXFUdGwsIDIpKSAlPiUKICBhcnJhbmdlKHBlcmlvZCwgZmFjdG9yKHJhdGluZywgbGV2ZWxzPW5hbWVzKHJhdGluZ190eXBlcykpKSAlPiUKICBnZ3Bsb3QoYWVzKHBlcmlvZCwgcGVyY2VudCwgZmlsbCA9IGZhY3RvcihyYXRpbmcsIGxldmVscyA9IG5hbWVzKHJhdGluZ190eXBlcykpKSkgKwogICAgZ2VvbV9iYXIocG9zaXRpb24gPSAic3RhY2siLCBzdGF0ID0gImlkZW50aXR5IikgKwogICAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gcmF0aW5nX2NvbG9ycykgKwogICAgZ3VpZGVzKGZpbGwgPSBndWlkZV9sZWdlbmQocmV2ZXJzZT1UUlVFLCB0aXRsZSA9ICdSYXRpbmcnKSkgKwogICAgdGhlbWUoYXhpcy50ZXh0LnggID0gZWxlbWVudF90ZXh0KGFuZ2xlPTUwLCB2anVzdCA9IC41KSkKYGBgCgoK