Promoting the use of R in the NHS

Blog Article

Text Mining – Term Frequency analysis and Word Cloud creation in R

Analysing the pre-conference workshop sentiments

Loading in the required packages:

  1. install_or_load_pack <- function(pack){
  2.   create.pkg <- pack[!(pack %in% installed.packages()[, "Package"])]
  3.   if (length(create.pkg))
  4.     install.packages(create.pkg, dependencies = TRUE)
  5.   sapply(pack, require, character.only = TRUE)
  6.   }
  7. packages <- c("ggplot2", "tidyverse",  "data.table", "wordcloud", "tm", "wordcloud2",
  8.               "scales", "tidytext", "devtools", "twitteR", "caret", "magrittr", "RColorBrewer", "tidytext", "ggdendro",
  9.               "tidyr", "topicmodels", "SnowballC", "gtools")
  10. install_or_load_pack(packages)

This function was previously covered in blog post:

Here I specify that I want to load the main packages for dealing with sentiment and discourse analysis in R. Libraries such as tm, wordcloud and wordcloud2 are loaded for working with this type of data.

Choosing the file to import

The file we have to import is a prepared csv file and instead of hard coding the path to load the file from I simply use:

11. path <- choose_files()

This is a special  function which allows you to open a dialog UI from R:













From this dialog I select the csv file I want to be imported. Once I have selected the csv and hit open, the path variable will be filled with the location of the file to work with.

Creating the R Data Frame

To create the data frame I can now pass the variable path to the read_csv command:

12.  workshop_sentiment <- read_csv(path, col_names = T)

This will read the textual data from the workshops in to a data frame with 2 columns. The first relates to what the attendees enjoyed about the workshop and  the second relates to improvements that can be made:











Separate the master data frame

The master data frame now needs to be separated into two separate data frames, as text analysis requires one column with the number of rows for each sentence, as demonstrated. Here I use maggrittr to divide this into two new data frames:

 13.  ws_highlights <- workshop_sentiment %>%

14.   .[,1]

15. #Copy for improvements

16. ws_improvements <- workshop_sentiment %>%

17.   .[,2]

The ws_highlights data frame uses the first column and  the ws_improvements data frame uses the second.

Function to create textual corpus

As I want to replicate this for highlights and improvements – I have created a function that could be replicated with any text analysis to create what is known as a text corpus (see: this creates a series of documents, in our case sentences.

18.  corpus_tm <- function(x){

19.   library(tm)

20.   corpus_tm <- Corpus(VectorSource(x))

21. }

This function allows you to pass any data frame to the function and creates a corpus for each data frame you pass to the function. The data frame would be passed to the x parameter. The VectorSource() function creates an element for each part of the corpus.

Create Corpus for Highlights and Improvements data frame

Now the function has been created, I can simpily pass the two separate data frames I created before to create two corpuses:

22. corpus_positive <- corpus_tm(ws_highlights$Highlights)

23. corpus_improvements <- corpus_tm(ws_improvements$Improvements)

The code block above shows that I create a corpus for the positive (highlights) data frame and an improvements corpus. This will display as hereunder in your environment:








Function to clean data in the corpus

The most common cleaning task of working with text data is to remove things like punctuation, common English words, etc. This is something I have to repeat multiple times when dealing with discourse analysis:

24. clean_corpus <- function(corpus_to_use){

25.  library(magrittr)

26.  library(tm)

27.  corpus_to_use %>%

28.    tm_map(removePunctuation) %>%

29.    tm_map(stripWhitespace) %>%

30.    tm_map(content_transformer(function(x) iconv(x, to='UTF-8', sub='byte'))) %>%

31.    tm_map(removeNumbers) %>%

32.    tm_map(removeWords, stopwords("en")) %>%

33.    tm_map(content_transformer(tolower)) %>%

34.    tm_map(removeWords, c("etc","ie", "eg", stopwords("english")))


36. }

The parameter here takes the corpus object previously created and uses the corpus passed to:

  • Remove punctuation
  • Strip out whitespace between each text item, as the VectorSource has stripped out each word from each sentence in the data frame
  • Change the underlying formatting of the text to UTF-8
  • Remove numbers
  • Remove common English word (stop words)
  • Change the case to lower case
  • Remove a custom vector of words to adjust for things like e.g., i.e., etc.

To clean the corpus objects I simply pass the original corpus objects back through this function to perform cleaning:

37. corpus_positive <- clean_corpus(corpus_positive)

38. corpus_improvements <- clean_corpus(corpus_improvements)

Inspection of one of the data frames confirms that this has successfully been cleaned:

Create TermDocumentMatrix to attain frequent terms

The term document matrix (explained well here: can be used with the corpus to identify frequent terms by classification on a matrix. However, more code is needed to do this:

39. find_freq_terms_fun <- function(corpus_in){

40.  doc_term_mat <- TermDocumentMatrix(corpus_in)

41.  freq_terms <- findFreqTerms(doc_term_mat)[1:max(doc_term_mat$nrow)]

42.  terms_grouped <-

43.    doc_term_mat[freq_terms,] %>%

44.    as.matrix() %>%

45.    rowSums() %>%

46.    data.frame(Term=freq_terms, Frequency = .) %>%

47.    arrange(desc(Frequency)) %>%

48.    mutate(prop_term_to_total_terms=Frequency/nrow(.))

49.  return(data.frame(terms_grouped))

50. }

This function needs explanation. The function uses as a single parameter the corpus that you need to pass in, then a variable is created to create the doc_term_mat which uses the tm TermDocumentMatrix.

Next, I use the findFreqTerms function to iterate from the first entry to the maximum number of rows in the matrix. These are the powerhouses of the function, as they highlight how many times a word has been used in a sentence across all the rows of text.

The terms_grouped variable then slices the term matrix with the frequent terms, this is converted to a matrix, sum of each row are calculated i.e. the number of times the word appears. Then, a data frame is created of the terms in the function with the headings term and Frequency.

Next, we use the power of DPLYR to use arrange by the frequency descending and to add a mutated column to the data frame to calculate the proportion of that specific term over all terms. The return(data.frame(terms_group)) then forces R to return the results of the function.

I then pass my data frames (highlights and improvements) to the function I have just created to see if this method works:

51. positive_freq_terms <- data.frame(find_freq_terms_fun(corpus_positive))

52. improvement_freq_terms <- data.frame(find_freq_terms_fun(corpus_improvements))

These will be built as data frames and can be viewed in R Studio’s Data environment window:









This has worked just as expected. You could now use ggplot2 to produce a bar chart / pareto chart of the terms.

 Create a Word Cloud with the wordcloud2 package

R has a wordcloud package that produces relatively nice looking word clouds, but wordcloud2 surpasses this in terms of visualisation. To use this function is easy now I have the frequent terms data frame – using the highlights data frame this can be implemented by using the below syntax:

53. wordcloud2(positive_freq_terms[,1:2],

54.           shape="pentagon",

55.           color="random-dark")

To use the function I pass the data frame and use the term and frequency fields only to use the visualisation. There are a number of options and these can be accessed by using the help(“wordcloud2”) function. Here I use the shape and color parameters to set the display of the word cloud:











This can be exported in the viewer window by using the Export function:









This word cloud relates to the pre workshop prior to the conference. I personally thought the NHS-R conference was amazing and I was honoured to have a spot to speak amongst so many other brilliant R users.

R is so versatile – every day is like a school day when you are learning it, but what a journey.

Gary Hutson

(Head of Solutions and AI)

Draper and Dash

My background is in Advanced Analytics and my passions are Machine, Deep Learning, Computer Vision and general programming concepts. I have been using...

View Author Bio & (8) Posts

Leave a Reply