Introduction to vivaglint

Overview

vivaglint is an R package built for HR analysts who work with Microsoft Viva Glint survey exports. It handles the repetitive data wrangling that Glint’s native UI doesn’t support — multi-cycle trend analysis, manager roll-ups, demographic segmentation, attrition risk scoring, and comment search — so you can spend more time interpreting results and less time reshaping data.

Key Capabilities

Getting Started

Installation

devtools::install_github("microsoft/vivaglint")
library(vivaglint)

Basic Workflow

1. Import Your Glint Export

Export your survey from Viva Glint as a CSV and load it with read_glint_survey():

survey_path <- system.file("extdata", "survey_export.csv", package = "vivaglint")
survey <- read_glint_survey(survey_path, emp_id_col = "EMP ID")

If you prefer to pull data directly from the Viva Glint API, configure credentials once and use read_glint_survey_api():

glint_setup(
  tenant_id = "your-tenant-id",
  client_id = "your-client-id",
  client_secret = "your-client-secret",
  experience_name = "your-experience-name"
)
survey <- read_glint_survey_api(
  survey_uuid = "your-survey-uuid",
  cycle_id = "your-cycle-id",
  emp_id_col = "EMP ID"
)

This returns a glint_survey object containing: - data: The full survey response table - metadata: Question names, column mappings, and respondent counts

2. Get a Question-Level Summary

Calculate metrics across all survey questions in one call:

summary <- summarize_survey(survey, scale_points = 5)

Each row in the output represents one question and includes: - mean — Average response on the raw scale - sd — Standard deviation - glint_score — Score transformed to 0–100, matching what appears in the Viva Glint UI - n_responses — Number of employees who answered the question - n_skips — Number of employees who skipped the question - n_total — Total respondents - pct_favorable — Percentage of favorable responses - pct_neutral — Percentage of neutral responses - pct_unfavorable — Percentage of unfavorable responses

About Glint Score: The Glint Score is calculated as round(((mean - 1) / (scale_points - 1)) * 100), placing every question on a common 0–100 scale regardless of the original scale format.

About Favorability: The package uses Viva Glint’s standard favorability thresholds for each scale type. On a 5-point scale, for example, 4–5 is favorable, 3 is neutral, and 1–2 is unfavorable.

3. Focus on Specific Questions

If you’re presenting to a leadership team or investigating a particular theme, filter to just the questions you care about:

# Engagement-specific questions
engagement_qs <- c(
  "I would recommend my team as a great place to work",
  "My work is meaningful"
)
engagement_summary <- summarize_survey(survey,
                                       scale_points = 5,
                                       questions = engagement_qs)

4. Explore Response Distributions

See exactly how many employees chose each response value — useful when a mean alone doesn’t tell the full story:

distributions <- get_response_dist(survey, scale_points = 5)

The output adds columns like count_1, count_2, pct_1, pct_2, etc. for each response value.


Multi-Cycle Trend Analysis

One of the most common HR reporting tasks is tracking whether engagement improved since the last survey. compare_cycles() takes multiple survey objects and aligns them by question:

survey_path <- system.file("extdata", "survey_export.csv", package = "vivaglint")
survey_q1 <- read_glint_survey(survey_path, emp_id_col = "EMP ID")
survey_q2 <- read_glint_survey(survey_path, emp_id_col = "EMP ID")
survey_q3 <- read_glint_survey(survey_path, emp_id_col = "EMP ID")

trends <- compare_cycles(
  survey_q1, survey_q2, survey_q3,
  scale_points = 5,
  cycle_names = c("Q1 FY25", "Q2 FY25", "Q3 FY25")
)

The output includes all metrics from summarize_survey() for each cycle, plus: - change_from_previous — Point change in mean score vs. prior cycle - pct_change_from_previous — Percentage change vs. prior cycle

This is the foundation for executive trend slides: which items are improving, holding steady, or declining quarter over quarter.


Manager-Level Analysis

HR business partners often need to understand which managers’ teams are scoring below average, or identify pockets of high engagement to learn from.

Roll Up to Manager Level

# Direct reports only
manager_summary <- aggregate_by_manager(survey, scale_points = 5)

# Full org tree (includes indirect reports)
manager_full <- aggregate_by_manager(survey, scale_points = 5, full_tree = TRUE)

Each row represents one manager × question combination and includes: - manager_id, manager_name - team_size - All standard metrics: mean, sd, glint_score, n_responses, n_skips, n_total, pct_favorable, pct_neutral, pct_unfavorable

You can filter the result to identify managers with low favorability on a specific question:

library(dplyr)

# Managers where fewer than 50% of their team is favorable on a key item
low_engagement_managers <- manager_summary %>%
  filter(question == "I would recommend my team as a great place to work",
         pct_favorable < 50) %>%
  arrange(pct_favorable)

Demographic Analysis

Segment by Employee Attributes

analyze_by_attributes() lets you break survey results down by any combination of employee attributes — department, gender, tenure group, location, job level, etc.

attr_path <- system.file("extdata", "employee_attributes.csv", package = "vivaglint")
demo_results <- analyze_by_attributes(
  survey,
  attribute_file = attr_path,
  scale_points = 5,
  attribute_cols = c("Department", "Gender", "Tenure Group"),
  min_group_size = 10  # Suppress groups below this size for privacy
)

This is useful for identifying which employee populations have systematically lower scores and on which questions.

Pre-Joining Attributes for Multiple Analyses

If you plan to run several analyses against the same attribute file, join it once and reuse the enriched survey object:

# Join once
attr_path <- system.file("extdata", "employee_attributes.csv", package = "vivaglint")
survey_enriched <- join_attributes(survey, attr_path)

# Reuse for multiple analyses — no need to re-read the file
dept_results <- analyze_by_attributes(
  survey_enriched,
  scale_points = 5,
  attribute_cols = "Department"
)

gender_results <- analyze_by_attributes(
  survey_enriched,
  scale_points = 5,
  attribute_cols = "Gender"
)

# Filter to a subpopulation before analyzing
na_only <- survey_enriched
na_only$data <- filter(survey_enriched$data, Region == "North America")
na_results <- analyze_by_attributes(na_only, scale_points = 5,
                                    attribute_cols = "Department")

Attributes are stored in survey$metadata$attribute_cols after joining, so they are excluded from question detection in downstream analyses.


Attrition Risk Analysis

Linking engagement scores to actual turnover is a high-value analysis for HR leaders. analyze_attrition() computes attrition rates by favorability group — showing whether employees who scored low on engagement were more likely to leave.

# Basic attrition analysis (90, 180, and 365 days post-survey)
attrition_path <- system.file("extdata", "attrition.csv", package = "vivaglint")
attrition <- analyze_attrition(
  survey,
  attrition_file = attrition_path,
  emp_id_col = "EMP ID",
  term_date_col = "Termination Date",
  scale_points = 5
)

The output shows, for each question and time period, the attrition rates for favorable vs. unfavorable responders along with a risk ratio — making it straightforward to identify which survey items are the strongest leading indicators of turnover.

Segment Attrition by Demographics

Combine attrition analysis with employee attributes to answer questions like “Are unfavorable responders in Engineering leaving at higher rates than those in Sales?”:

attr_path <- system.file("extdata", "employee_attributes.csv", package = "vivaglint")
attrition_path <- system.file("extdata", "attrition.csv", package = "vivaglint")
survey_enriched <- join_attributes(survey, attr_path)

attrition_by_dept <- analyze_attrition(
  survey_enriched,
  attrition_file = attrition_path,
  emp_id_col = "EMP ID",
  term_date_col = "Termination Date",
  scale_points = 5,
  attribute_cols = c("Department", "Job Level"),
  min_group_size = 10
)

Correlation and Factor Analysis

Understand Which Items Move Together

Correlation analysis is useful for identifying clusters of related questions — often as a first step before building a composite score or validating that a set of items measures a single construct:

# Pearson correlations in long format (default)
correlations <- get_correlations(survey)

# Spearman correlations (more robust for ordinal scale data)
correlations_spearman <- get_correlations(survey, method = "spearman")

# Correlation matrix
cor_matrix <- get_correlations(survey, format = "matrix")

Supported methods: "pearson" (default), "spearman", "kendall"

Factor Analysis

Factor analysis identifies the latent constructs underlying a set of survey items. This can validate whether your “manager effectiveness” items truly cluster together, or reveal unexpected groupings:

# Requires the psych package
factors <- extract_survey_factors(survey, n_factors = 3, rotation = "oblimin")

# Consolidated summary: item, factor assignment, loading, label, communality
print(factors$factor_summary)

# Filter to items with strong factor loadings only
strong_loaders <- dplyr::filter(factors$factor_summary, loading_label == "Strong")

# Access the raw psych object for advanced use
factors$fa_object

Loading labels: Strong (≥ 0.75), Medium (0.60–0.74), Weak (< 0.60)


Working with Comments

Convert to Long Format for NLP

To route comments into a text analysis or NLP pipeline, reshape the survey to long format and filter to rows with comments:

# All responses in long format
long_all <- pivot_long(survey, data_type = "all")

# Comments only
long_comments <- pivot_long(survey, data_type = "comments")

# Both as separate tibbles
both <- pivot_long(survey, data_type = "both")
comments_df <- both$comments

Separating Quantitative and Qualitative Data

For workflows that route numeric scores and open-text comments to different pipelines (e.g., numeric data to a statistical model, comments to an LLM), use split_survey_data():

parts <- split_survey_data(survey)

# Numeric scores only — standard respondent columns + one score column per question
quantitative <- parts$quantitative

# Comments only — EMP ID + all _COMMENT, _COMMENT_TOPICS, _SENSITIVE_COMMENT_FLAG columns
qualitative <- parts$qualitative

# Pass numeric data directly to vivaglint functions
summary <- summarize_survey(parts$quantitative, scale_points = 5,
                            emp_id_col = "EMP ID")

# Rejoin at any time using EMP ID
full_data <- dplyr::left_join(parts$quantitative, parts$qualitative, by = "EMP ID")

Privacy and Data Handling

Minimum Group Sizes

Use min_group_size wherever it’s available to suppress results for groups that are too small to protect individual anonymity:

# Default is 5; consider 10 or higher for sensitive analyses
attr_path <- system.file("extdata", "employee_attributes.csv", package = "vivaglint")
demo_results <- analyze_by_attributes(
  survey,
  attribute_file = attr_path,
  scale_points = 5,
  attribute_cols = c("Department", "Gender"),
  min_group_size = 10
)

Local Processing

This package processes all data locally within your R environment. No employee data is transmitted to any external service, including Microsoft. Always follow your organization’s data handling and privacy policies when working with employee survey data.


Additional Resources