This assignment develops a rule-based text classification system to organize Honeywell Glassdoor employee comments into meaningful HR topic categories using regular expressions. The workflow begins by preparing and standardizing the raw review data before applying a consistent set of regex patterns to both the Pros and Cons comment fields. The resulting classifications are summarized and exported into a structured Excel workbook, creating a dataset that supports further exploration of recurring employee experience themes.


Stage 1: Load Packages and Data

rm(list = ls())

if (!requireNamespace("pacman", quietly = TRUE)) {
  install.packages("pacman", repos = "http://cran.r-project.org")
}

library(pacman)

pacman::p_load(
  tidyverse,
  readr,
  lubridate,
  openxlsx,
  janitor,
  scales
)

data_path <- "~/Desktop/1 - Coursework/Graduate Coursework/UGA/4 - Summer 2026/Advanced Analytics (PSYC 6841)/Data/glassdoortest1.csv"

if (file.exists(path.expand(data_path))) {
  raw_df <- read.csv(path.expand(data_path))
} else {
  raw_df <- read.csv(
    "https://github.com/RobStilson/Employee-Datasets/raw/refs/heads/main/Honeywell%20Glassdoor%20Dataset/glassdoortest1.csv"
  )
}

raw_df <- raw_df %>%
  clean_names() %>%
  rename(id = x)

glimpse(raw_df)
## Rows: 2,000
## Columns: 5
## $ id    <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 1…
## $ date  <chr> "13-Apr-18", "16-Apr-18", "12-Apr-18", "11-Apr-18", "12-Apr-18",…
## $ title <chr> "Good Company to Work For", "First Impressions", "Sr. Engineerin…
## $ pros  <chr> "Great Pay, Flexible Hours, Unlimited Vacation.", "Great staff a…
## $ cons  <chr> "Health Care, 401K, nothing else really", "As a relatively new e…

Stage 2: Define Comment Categories

Rather than relying on a small number of broad topics, I operationalized employee feedback into 21 substantive categories organized around recurring HR themes, along with an “Other” category to capture comments that did not match any predefined pattern. Where appropriate, broader constructs such as Benefits and Management were further divided into subcategories to improve specificity and better distinguish between different aspects of the employee experience.

category_patterns <- list(
  "Benefits-General" = c(
    "\\bbenefit",
    "\\bbeneficial",
    "\\bbenefits\\b",
    "(?=.*perk)(?=.*(?:employee|company))"
  ),
  "Benefits-Healthcare" = c(
    "\\brx\\b",
    "^.*medic.*$",
    "(?=.*bene)(?=.*(?:health))",
    "(?=.*coverage)(?=.*(?:medic|deduct|prescrip|insur|drug|health|dependent))",
    "\\b(?:health\\W+(?:\\w+\\W+){0,1}?care)\\b",
    "\\bhealthcare\\b",
    "\\bhealth\\s?care\\b",
    "\\b(?:medical\\W+(?:\\w+\\W+){0,3}?benefits|benefits\\W+(?:\\w+\\W+){0,3}?medical)\\b",
    "\\bdeductible\\b",
    "\\bhigh[- ]deductible\\b"
  ),
  "Benefits-Insurance" = c(
    "(?=.*insur)(?=.*(?:medic|dental|life|vision|supplement|disabl))",
    "\\b(?:insurance\\W+(?:\\w+\\W+){0,1}?premium)\\b",
    "\\binsurance\\b",
    "\\bdental\\b",
    "\\bvision\\b"
  ),
  "Benefits-PTO" = c(
    "^.*vacation.*$",
    "\\bpto\\b",
    "\\bpaid time off\\b",
    "\\bunlimited vacation\\b",
    "\\btime off\\b",
    "\\bflexible hours\\b",
    "\\bflex time\\b"
  ),
  "Benefits-401K" = c(
    "\\b401k\\b",
    "\\b401\\s?k\\b",
    "\\bretirement\\b",
    "\\bpension\\b",
    "\\bmatching\\b",
    "\\bcompany match\\b"
  ),
  "Compensation" = c(
    "\\bsalary\\b",
    "^.*compen.*$",
    "\\bpay\\b",
    "^.*incent.*$",
    "\\bbonus\\b",
    "\\braise\\b",
    "\\bstock\\b",
    "\\bequity\\b",
    "\\bmoney\\b",
    "\\bincome\\b"
  ),
  "Career-Advancement" = c(
    "\\bpromot",
    "\\badvancement\\b",
    "\\bcareer\\b",
    "\\bopportunit",
    "\\bupward mobility\\b",
    "\\bgrowth\\b",
    "\\btenure\\b",
    "\\bclimb\\b"
  ),
  "Work-Life-Balance" = c(
    "\\bwork[- ]life\\b",
    "\\bworklife\\b",
    "\\bbalance\\b",
    "\\bflexible\\b",
    "\\bflexibility\\b",
    "\\bwork from home\\b",
    "\\bremote\\b",
    "\\bwfh\\b",
    "\\btelecommut"
  ),
  "Management-Direct" = c(
    "\\bmanager\\b",
    "\\bsupervisor\\b",
    "\\bboss\\b",
    "\\bdirect report\\b",
    "\\bmy lead\\b",
    "\\bteam lead\\b"
  ),
  "Management-Senior" = c(
    "\\bceo\\b",
    "\\bupper management\\b",
    "\\bsenior management\\b",
    "\\bexecutive\\b",
    "\\bleadership\\b",
    "\\bcorporate\\b",
    "\\bboard\\b",
    "\\bshareholder\\b"
  ),
  "Culture-People" = c(
    "\\bpeople\\b",
    "\\bcoworker",
    "\\bco[- ]worker",
    "\\bteam\\b",
    "\\bcolleague",
    "\\bculture\\b",
    "\\bfriendly\\b",
    "\\bstaff\\b",
    "\\benvironment\\b",
    "\\bteamwork\\b"
  ),
  "Job-Security" = c(
    "\\bjob security\\b",
    "\\bsecurity\\b",
    "\\blayoff",
    "\\brif\\b",
    "\\breduction in force\\b",
    "\\bdownsiz",
    "\\bfurlough",
    "\\bunstable\\b",
    "\\bat[- ]will\\b"
  ),
  "Commute-Location" = c(
    "\\bcommute\\b",
    "\\bdrive\\b",
    "\\blocation\\b",
    "\\boffice\\b",
    "\\brelocat",
    "\\btravel\\b",
    "\\bmile\\b",
    "\\bparking\\b"
  ),
  "Training-Development" = c(
    "\\btrain",
    "\\bdevelopment\\b",
    "\\blearning\\b",
    "\\beducation\\b",
    "\\bmentor",
    "\\bonboarding\\b",
    "\\bcoaching\\b"
  ),
  "Innovation-Technology" = c(
    "\\binnovat",
    "\\btechnolog",
    "\\bdigital\\b",
    "\\bsoftware\\b",
    "\\bengineering\\b",
    "\\bproduct\\b",
    "\\bmanufactur",
    "\\bautomation\\b",
    "\\bai\\b",
    "\\bdata\\b",
    "\\banalytics\\b"
  ),
  "Ethics-Integrity" = c(
    "\\bethic",
    "\\bintegrity\\b",
    "\\bethical\\b",
    "\\bcompliance\\b",
    "\\bvalues\\b",
    "\\brespect\\b",
    "\\btrust\\b"
  ),
  "Workload-Hours" = c(
    "\\bworkload\\b",
    "\\blong hours\\b",
    "\\bovertime\\b",
    "\\bover[- ]time\\b",
    "\\bbusy\\b",
    "\\bhectic\\b",
    "\\bunderstaff",
    "\\bmore people\\b",
    "\\btoo much work\\b"
  ),
  "Diversity-Inclusion" = c(
    "\\bdivers",
    "\\binclusion\\b",
    "\\bequal\\b",
    "\\bgender\\b",
    "\\brace\\b",
    "\\bwomen\\b",
    "\\bminority\\b"
  ),
  "Safety" = c(
    "\\bsafety\\b",
    "\\bsafe\\b",
    "\\bhazard\\b",
    "\\baccident\\b",
    "\\bhealth and safety\\b",
    "\\bprotective\\b"
  ),
  "Recognition-Rewards" = c(
    "\\brecognit",
    "\\breward",
    "\\bappreciat",
    "\\backnowledg",
    "\\bpraise\\b",
    "\\bincentiv"
  ),
  "Communication" = c(
    "\\bcommunicat",
    "\\btransparen",
    "\\bfeedback\\b",
    "\\bopen door\\b",
    "\\bkept in the loop\\b",
    "\\bno communication\\b",
    "\\bunclear\\b"
  )
)

length(category_patterns)
## [1] 21
names(category_patterns)
##  [1] "Benefits-General"      "Benefits-Healthcare"   "Benefits-Insurance"   
##  [4] "Benefits-PTO"          "Benefits-401K"         "Compensation"         
##  [7] "Career-Advancement"    "Work-Life-Balance"     "Management-Direct"    
## [10] "Management-Senior"     "Culture-People"        "Job-Security"         
## [13] "Commute-Location"      "Training-Development"  "Innovation-Technology"
## [16] "Ethics-Integrity"      "Workload-Hours"        "Diversity-Inclusion"  
## [19] "Safety"                "Recognition-Rewards"   "Communication"

Stage 3: Build Classification Functions

This stage implements the classification workflow by separating data preparation, regex matching, and summary generation into modular functions. Each comment is evaluated independently against every category pattern, allowing multiple topics to be identified within the same review. This multi-label approach reflects the reality that employee feedback often addresses several workplace issues simultaneously rather than fitting into a single category.

clean_comments <- function(df, comment_col) {
  df %>%
    select(id, comments = all_of(comment_col)) %>%
    filter(!is.na(comments)) %>%
    mutate(
      comments = str_replace_all(comments, "\uFFFD", ""),
      comments = tolower(comments)
    ) %>%
    mutate(comments = gsub("[\r\n]", "", comments)) %>%
    na.omit()
}

classify_with_regex <- function(comments_df, pattern_list, category_name) {
  pattern <- paste(pattern_list, collapse = "|")
  matched_comments <- comments_df$comments[
    grep(pattern, comments_df$comments, value = FALSE, perl = TRUE)
  ]

  comments_df %>%
    mutate(
      !!category_name := ifelse(comments %in% matched_comments, "Y", "N")
    )
}

classify_all_categories <- function(comments_df, patterns = category_patterns) {
  start_time <- now()

  classified_df <- comments_df

  for (category_name in names(patterns)) {
    classified_df <- classify_with_regex(
      classified_df,
      patterns[[category_name]],
      category_name
    )
  }

  category_cols <- names(patterns)

  classified_df <- classified_df %>%
    mutate(
      Other = apply(
        select(., all_of(category_cols)),
        1,
        function(row_values) ifelse("Y" %in% row_values, "N", "Y")
      )
    )

  end_time <- now()
  cat(
    "Classification completed in",
    format(difftime(end_time, start_time, units = "secs")),
    "\n"
  )

  classified_df
}

category_summary <- function(classified_df, patterns = category_patterns) {
  category_cols <- c(names(patterns), "Other")

  classified_df %>%
    summarise(across(all_of(category_cols), ~ sum(.x == "Y"))) %>%
    pivot_longer(everything(), names_to = "category", values_to = "count") %>%
    mutate(share = percent(count / nrow(classified_df), accuracy = 0.1)) %>%
    arrange(desc(count))
}

Stage 4: Classify Pros Comments

pros_df <- clean_comments(raw_df, "pros")
pros_classified <- classify_all_categories(pros_df)
## Classification completed in 1.426628 secs
pros_summary <- category_summary(pros_classified)
print(pros_summary, n = Inf)
## # A tibble: 22 × 3
##    category              count share
##    <chr>                 <int> <chr>
##  1 Culture-People          756 37.8%
##  2 Other                   441 22.0%
##  3 Compensation            368 18.4%
##  4 Career-Advancement      348 17.4%
##  5 Work-Life-Balance       223 11.2%
##  6 Benefits-General        202 10.1%
##  7 Innovation-Technology   163 8.2% 
##  8 Training-Development    161 8.0% 
##  9 Benefits-PTO             80 4.0% 
## 10 Benefits-401K            76 3.8% 
## 11 Commute-Location         70 3.5% 
## 12 Management-Senior        65 3.2% 
## 13 Diversity-Inclusion      57 2.8% 
## 14 Management-Direct        51 2.5% 
## 15 Recognition-Rewards      44 2.2% 
## 16 Workload-Hours           36 1.8% 
## 17 Job-Security             35 1.8% 
## 18 Safety                   27 1.4% 
## 19 Benefits-Insurance       26 1.3% 
## 20 Ethics-Integrity         23 1.2% 
## 21 Benefits-Healthcare      22 1.1% 
## 22 Communication            14 0.7%

Stage 5: Classify Cons Comments

cons_df <- clean_comments(raw_df, "cons")
cons_classified <- classify_all_categories(cons_df)
## Classification completed in 6.01938 secs
cons_summary <- category_summary(cons_classified)
print(cons_summary, n = Inf)
## # A tibble: 22 × 3
##    category              count share
##    <chr>                 <int> <chr>
##  1 Other                   570 28.5%
##  2 Benefits-General        428 21.4%
##  3 Culture-People          398 19.9%
##  4 Compensation            319 16.0%
##  5 Management-Senior       292 14.6%
##  6 Job-Security            286 14.3%
##  7 Career-Advancement      223 11.2%
##  8 Benefits-Healthcare     178 8.9% 
##  9 Work-Life-Balance       169 8.4% 
## 10 Innovation-Technology   122 6.1% 
## 11 Workload-Hours          120 6.0% 
## 12 Benefits-Insurance      119 5.9% 
## 13 Commute-Location        101 5.0% 
## 14 Training-Development    100 5.0% 
## 15 Benefits-401K            73 3.6% 
## 16 Management-Direct        62 3.1% 
## 17 Benefits-PTO             57 2.8% 
## 18 Recognition-Rewards      51 2.5% 
## 19 Ethics-Integrity         43 2.1% 
## 20 Communication            36 1.8% 
## 21 Safety                   15 0.8% 
## 22 Diversity-Inclusion       5 0.2%

Stage 6: Export Results to Excel

The final step translates the classification output into a format that is easier to review and analyze outside of R. Separate worksheets are created for Pros and Cons comments, preserving the original comment text alongside every topic classification. Basic formatting, filters, and frozen panes improve readability while allowing the workbook to function as a practical HR reporting tool.

output_dir <- "~/Desktop/1 - Coursework/Graduate Coursework/UGA/4 - Summer 2026/Advanced Analytics (PSYC 6841)/Homeworks/Class 4"
dir.create(path.expand(output_dir), showWarnings = FALSE, recursive = TRUE)

output_file <- file.path(
  path.expand(output_dir),
  paste0("Homework_4_Honeywell_Comment_Report_", format(Sys.Date(), "%Y_%m_%d"), ".xlsx")
)

intro_lines <- c(
  "Company Name: Honeywell",
  "Data Source: Glassdoor",
  "Data As Of: Q2 2026",
  paste0("Prepared on: ", format(Sys.Date(), "%m/%d/%Y")),
  "Prepared by: Ben Glinsky"
)

write_comment_sheet <- function(wb, sheet_name, intro_lines, classified_df) {
  addWorksheet(wb, sheet_name)
  writeData(wb, sheet_name, intro_lines)

  header_style <- createStyle(fontColour = "#000080", textDecoration = "Bold")
  addStyle(wb, sheet = sheet_name, style = header_style, rows = 1:5, cols = 1)

  writeData(wb, sheet_name, classified_df, startRow = 8)

  table_header_style <- createStyle(textDecoration = "Bold")
  addStyle(
    wb,
    sheet = sheet_name,
    style = table_header_style,
    rows = 8,
    cols = 1:ncol(classified_df)
  )

  freezePane(wb, sheet_name, firstActiveRow = 9)
  addFilter(wb, sheet_name, row = 8, cols = 1:ncol(classified_df))
}

wb <- createWorkbook()
write_comment_sheet(wb, "Pros", intro_lines, pros_classified)
write_comment_sheet(wb, "Cons", intro_lines, cons_classified)
saveWorkbook(wb, output_file, overwrite = TRUE)

output_file
## [1] "/Users/bglinsky/Desktop/1 - Coursework/Graduate Coursework/UGA/4 - Summer 2026/Advanced Analytics (PSYC 6841)/Homeworks/Class 4/Homework_4_Honeywell_Comment_Report_2026_07_19.xlsx"

Stage 7: Interpretation

Across both the Pros and Cons comments, the classification procedure identified several recurring themes in employee feedback. Compensation and benefits-related topics appeared frequently across the dataset, suggesting that total rewards remain one of the most commonly discussed aspects of the employee experience. By separating broader concepts into more specific subcategories, such as Healthcare, Insurance, PTO, and 401K within Benefits, or Direct Management and Senior Management within Leadership, the resulting classifications provide greater resolution than would be possible using only broad organizational themes.

The inclusion of an “Other” category also serves as an important quality check on the classification process. Comments assigned to this category indicate either language that falls outside the current regex definitions or emerging themes that may warrant the creation of additional categories. Rather than viewing these unmatched comments as errors, they provide useful feedback for iteratively refining the classification system and improving coverage over time.

An important characteristic of this approach is that the categories are intentionally non-mutually exclusive. Individual comments frequently reference multiple aspects of the workplace—for example, discussing compensation, workload, and management within the same review. Allowing comments to receive multiple classifications more accurately represents the complexity of employee feedback and produces a richer dataset for downstream analysis.

Overall, the resulting workbook transforms thousands of unstructured Glassdoor comments into an organized, searchable dataset that can support HR analytics. Topic-level filtering allows analysts to compare the prevalence of themes across Pros and Cons comments, identify areas of organizational strength and concern, and prioritize future investigations into employee experience, culture, retention, or total rewards. While the current rule-based approach depends on carefully constructed regex patterns, its transparent logic and ease of refinement make it a practical foundation for exploratory text analytics.