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.
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…
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"
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))
}
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%
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%
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"
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.