Cohen's d vs Hedges' g vs Glass's delta
Which standardised mean difference to report, Cohen's d, Hedges' g or Glass's delta, by sample size, unequal spread and purpose, with a worked R example.
Updated . Written by Matt Gencer.
On this page
Cohen’s d, Hedges’ g and Glass’s delta all express a difference between two means in standard deviation units. They differ in one thing each: Hedges’ g corrects d for its small-sample bias, and Glass’s delta swaps the pooled standard deviation for the control group’s. So the choice comes down to two questions about your study: how small are the groups, and could the treatment have changed the spread as well as the mean?
This post answers both, shows what each version computes and how to get it in R, and works one example through a deterministic engine: a small two-group study in which the treatment was simulated to widen the spread, the case where the three versions part company. For which effect size goes with which test, start with effect sizes explained; this page assumes you already want a standardised mean difference.
One numerator, three yardsticks
All three share the same numerator, the difference between the two group means. What changes is the standard deviation it is divided by, and whether a correction is applied afterwards.
| Divides by | Small-sample correction | R (effectsize 1.0.2) | |
|---|---|---|---|
| Cohen’s d | Pooled SD of both groups | None | cohens_d(x, y) |
| Hedges’ g | Pooled SD of both groups | Yes, d times J | hedges_g(x, y) |
| Glass’s delta | SD of the reference group only | Yes, by default in effectsize | glass_delta(x, y) |
The pooled SD is a weighted average of the two groups’ variances, square-rooted. It is the right yardstick when both groups share one population spread, because averaging two estimates of the same thing beats either alone. When the spreads genuinely differ it averages two different things into a number that describes neither group.
The same name can mean different computations in different software, so a reported “d” is only interpretable when the reader knows which standardiser sat under it. Lakens recommends subscripts (d_s for two independent groups, d_z for differences) for this reason.
In symbols, with group means m1 and m2, standard deviations s1 and s2, and sizes n1 and n2:
d = (m1 - m2) / s_pooled
s_pooled = sqrt( ((n1 - 1) × s1² + (n2 - 1) × s2²) / (n1 + n2 - 2) )
Reading the number, and why report it at all
An effect size answers a question a p-value cannot: how big is the difference, in units that do not depend on the measurement scale? For a real difference, the p-value shrinks as the sample grows while d does not, so the two together say more than either alone.
The familiar labels of d = 0.2 small, 0.5 medium and 0.8 large come from Cohen. Lakens calls them arbitrary and says they should not be interpreted rigidly, so judge size against what matters in your field. He calls the common language effect size (also called the probability of superiority), the chance that a randomly chosen unit from one group scores higher than one from the other, more intuitive than d. Translating d into that probability or into overlap assumes roughly normal data with similar spreads: Hedges (2024) shows that with non-normal data or clearly unequal standard deviations, the link between d and overlap can be very different.
A d can also be converted to a correlation, but the conversion depends on the group sizes, so use software rather than a remembered formula.
Cohen’s d versus Hedges’ g: the small-sample correction
Cohen’s d computed from a sample is a slightly biased estimate of the population effect: on average it comes out a little too far from zero, and the smaller the groups, the larger the overshoot. Hedges derived the correction. Hedges’ g is d multiplied by a factor J that depends only on the degrees of freedom of the pooled SD (the two group sizes added, minus 2). J is always below 1, approaches 1 as the groups grow, and is often written in its approximate form:
J ≈ 1 - 3 / (4 × (n1 + n2) - 9)
Software may use the exact gamma-function form; the two differ negligibly except in very small groups. Because J only rescales d, the interval for g is the interval for d multiplied by the same factor. It never changes the sign or the p-value.
How much does it matter? Lakens describes the difference between g and d as very small, especially in sample sizes above 20 (he does not say whether that means in total or per group), and adds that reporting g is preferable and just as easy. The correction is easy to size: at 10 per group (18 degrees of freedom) J is about 0.958, so g is about 4 percent smaller than d, the gap in the example below; at 5 per group it is about 10 percent.
The practical rule: report Hedges’ g for two independent groups whenever the estimate may be compared or pooled, which in practice means always. Report d when a reader or power calculation expects it, and say which you used.
Glass’s delta: when the treatment changes the spread
Glass’s delta divides the mean difference by the standard deviation of one group only, normally the control or baseline group. The reasoning is that a treatment can do two things: shift the average and widen (or narrow) the spread. If it widens the spread, the pooled SD is inflated by the treatment itself, and dividing by it makes the effect look smaller than it is on the scale of the untreated population. The control group’s SD is untouched by the treatment, so it measures the effect against the natural variation of people or units who did not get it. Lakens’ summary table gives the same reason: use delta when the manipulation might affect the SD.
Three practical points follow.
Name the reference group, and choose it before seeing the data. Delta changes if you swap the reference group. Picking the one that gives the larger number is a quiet form of selective reporting.
Delta is less precise. Its standardiser comes from one group’s data instead of two, so it rests on fewer degrees of freedom and its interval is typically wider than d’s. Hedges’ 1981 paper derived the exact distribution of Glass’s estimator, showed it is biased in small samples and gave the unbiased version.
Know what your software does by default. In effectsize 1.0.2 (the version the example below was run against), glass_delta(x, y) divides by the SD of y, the second group, and applies the small-sample correction unless you set adjust = FALSE. The reference argument only works in the formula interface, where it names the reference level; in the two-vector form it is silently ignored, so put the control group second:
library(effectsize)
glass_delta(treated, untreated) # untreated SD, corrected
glass_delta(score ~ group, data = d, reference = "untreated") # same idea, formula form
glass_delta(treated, untreated, adjust = FALSE) # Glass's original, uncorrected
In the formula form, reference also sets the direction: the result is the other group minus the reference group.
Worked example: a small study where the spread changed
The data are seeded synthetic data, used because the lesson needs a known truth: 10 units in an untreated group and 10 in a treated group, every unit in exactly one group. The values were drawn from normal distributions in which the treatment raised the mean from 50 to 58 and the standard deviation from 8 to 14. So the true answer is known: the treatment changed both the mean and the spread. The numbers describe no real study or person.
The engine ran Welch’s t-test, the two-sample default when spreads may differ (see t-tests explained), and reported Cohen’s d with its 95% interval.
| Quantity | Engine output |
|---|---|
| Group means | treated 53.94, untreated 50.61 |
| Difference (treated minus untreated) | 3.33, 95% CI -8.523 to 15.18 |
| Welch’s t | 0.6012 on 14.35 degrees of freedom, p = 0.557 |
| Cohen’s d (pooled SD) | 0.2689, 95% CI -0.6157 to 1.146 |
| Levene’s test for equal variances | p = 0.276, unequal spread not detected |
| Shapiro-Wilk by group | p = 0.217 (untreated), p = 0.371 (treated) |
| Sensitivity: smallest d detectable with 80% power at n = 20 | 1.325 |
Hedges’ g. The engine reports Hedges’ g only in its pairwise comparisons, which it runs only after a test of three or more groups. So, to get g from the engine for this same comparison, we used a workaround, and it is worth being open about: a second run used the same seed to regenerate the same 20 values (we checked that they are identical, value for value), then added a third, simulated group that exists only to unlock the pairwise step. Its Games-Howell step reported g for the treated-versus-untreated pair, computed from those two groups’ values alone: g = 0.2575, 95% CI -0.5896 to 1.098. Nothing else from that run is quoted. As a check, 0.2575 divided by 0.2689 is 0.9576, which is exactly the factor J for 18 degrees of freedom, so g is d shrunk by about 4 percent, and the interval bounds shrink by the same factor.
Glass’s delta. The engine does not report delta, so this post quotes no number for it. The call for this example is glass_delta(treated, untreated), dividing by the untreated group’s SD. What we can say about its size comes from how the data were generated, not from a computed value: the treated group was drawn with a standard deviation of 14 against 8 for the untreated group, so in data like these the untreated SD will usually sit below the pooled SD, and delta will usually come out larger than d. That is an expectation from the generating SDs, not a guarantee for any one sample.
What the example teaches
The variance test did not see a difference that was built in. The population standard deviations were 8 and 14, yet Levene’s test returned p = 0.276. With 10 per group a variance test has little power, so a non-significant result is “not detected”, not evidence that the spreads are equal. Choose the standardiser from the design (is there a control, could the treatment change the spread?), not from a preliminary variance test.
The d interval leans on an assumption the data were built to break. The interval for d (and so for g) comes from the noncentral t distribution, which assumes equal population variances. Grissom and Kim review how pooling, and the usual intervals for the standardised difference, rest on that assumption, and note that unequal variances seem to abound in psychological data. Bonett gives interval methods that do not need equal variances. When spreads may differ, report the raw difference with its Welch interval next to the standardised one.
The correction is small next to the uncertainty. The step from d to g moved the estimate by about one hundredth. The interval for d runs from -0.6157 to 1.146, so the data are compatible with a modest negative effect and with a large positive one. The sensitivity line says why: with 10 per group, only effects of about d = 1.325 or larger would be detected reliably, so this non-significant result is inconclusive, not evidence of no effect. The standardiser matters; having enough data matters more.
Paired and repeated designs
When the same units are measured twice, there are more choices, and they are not interchangeable:
- d_z divides the mean difference by the SD of the differences. It is what a paired t-test’s power depends on, but it is not comparable with a two-group d, because it grows as the two measurements become more correlated.
- d_av divides by the average of the two measurements’ SDs, and d_rm corrects for the correlation; both sit on a scale closer to the between-groups d. Lakens generally recommends Hedges’ g_av for comparison across designs.
In effectsize 1.0.2, cohens_d(x, y, paired = TRUE) returns d_z without a small-sample correction. The formula form refuses paired = TRUE, so use the two-vector form. For d_av or d_rm use repeated_measures_d(x, y, method = "av"), which applies the correction by default. The paired t-test versus Wilcoxon guide covers choosing the test itself.
Which one to report: a decision rule
- Is there a pairing? If yes, report d_z or d_av by name (see above), not a two-group d.
- Two independent groups with a clear control, and the treatment could plausibly change the spread? Report Glass’s delta with the control group named as the reference, set before analysis. Report Hedges’ g too, so the result can be compared with studies that used the pooled yardstick.
- Two independent groups, no reason to expect different spreads? Report Hedges’ g. In larger samples d and g are close, and reporting g is still the safer habit.
- Spreads differ and neither group is a natural reference (two active treatments, two existing populations)? A single standardised difference describes neither group well. Report the raw mean difference with its Welch interval as the headline, and if a standardised value is needed, say which SD you used and that its interval assumes equal variances, or use an interval method that does not, such as Bonett’s.
- Will the number be pooled in a meta-analysis? Hedges’ g, plus means, SDs and group sizes so any version can be recomputed.
If the outcome is badly skewed or ordinal, a mean-based effect size may be the wrong family altogether; the t-test versus Mann-Whitney guide explains when a rank-based effect size fits better.
How to report it
Give the means, SDs and group sizes, the raw difference with its interval, and the standardised effect with its interval, its name and its standardiser. For the example above:
Mean score was 53.94 in the treated group and 50.61 in the untreated group (10 per group); the difference was 3.33 (95% CI -8.523 to 15.18; Welch’s t = 0.6012, p = 0.557). Hedges’ g (pooled SD) was 0.2575 (95% CI -0.5896 to 1.098); the interval assumes equal variances.
Add the group SDs from your own data, and name the reference group if you report Glass’s delta.
Common mistakes
- Reporting “d” without the standardiser. A reader cannot tell a pooled-SD d from d_z or delta, and they can differ a lot.
- Choosing the version that gives the biggest number. Decide from the design before analysis.
- Letting a variance test decide. It has least power exactly where the choice matters most, in small groups.
- Planning the next study from this effect size. One small study’s estimate has a wide interval; plan for the smallest effect that would matter instead.
- Reading the size labels as verdicts. They are rough conventions, not judgements of importance.
Rerunning the example
The script under the example is the engine’s own reproduce script. It reproduces the Welch t-test and the sensitivity power figure from the bundled data. It does not recompute the effect sizes; each is one line with effectsize:
effectsize::cohens_d(score ~ group, data = data)
effectsize::hedges_g(score ~ group, data = data)
effectsize::glass_delta(score ~ group, data = data, reference = "untreated")
The first should match the engine’s d, the second the g from the second run, and the third gives delta against the untreated group. None involves randomness, so for a given package version the results are deterministic; the example used effectsize 1.0.2. For how the reproducible package is put together, see reproducible statistical analysis, and for the test itself, the Welch t-test method reference.
This example is about the method, using simulated data; it supports research reporting, not decisions about any individual.
Worked example
Data: synthetic, generated in the script below. Seed: 20261022.
Run on Celeus engine 0.30.0, seed 20261022, synthetic data.
Output
# Results - Test: Welch's t-test - n: 20 - statistic: 0.6012 - p-value: 0.557 - effect size: Cohen's d = 0.2689 (95% CI -0.6157 to 1.146) - 95% CI: [-8.523, 15.18] - seed: 20261022 ## Assumption checks - normality by group (Shapiro-Wilk): passed=TRUE - variance homogeneity (Levene): passed=TRUE (no evidence of unequal variances) - sample size: passed=TRUE (adequate n) ## Power (sensitivity, not observed power) - minimum detectable effect at n=20: Cohen's d = 1.325 (severely underpowered) - to detect a conventional medium effect: 64 per group to detect d=0.50 - alpha = 0.05, target power = 0.8
R script
# Celeus - reproduce.R (run from this package folder; bundled data in data/)
# Test: Welch's t-test | engine 0.30.0 | catalog c39-d3cd
# Research context: not provided for this run.
# Reproduces the recorded result deterministically.
set.seed(20261022)
.data_path <- "data/cohens-d-vs-hedges-g.csv"
if (!file.exists(.data_path)) stop("Run reproduce.R from the package folder (the bundled data is in data/).")
data <- readr::read_csv(.data_path, show_col_types = FALSE) # needs the readr package
clean_cells <- function (df)
{
ctrl_collapse <- "[\r\n\t\v\f]+"
zero_width_chars <- intToUtf8(c(8203L, 65279L), multiple = TRUE)
uni_space_chars <- intToUtf8(c(160L, 8239L, 8199L), multiple = TRUE)
clean_text <- function(x) {
out <- x
for (ch in zero_width_chars) out <- gsub(ch, "", out,
fixed = TRUE)
out <- gsub(ctrl_collapse, " ", out)
for (ch in uni_space_chars) out <- gsub(ch, " ", out,
fixed = TRUE)
trimws(gsub(" +", " ", out))
}
changed_cols <- character(0)
cells_cleaned <- 0L
retyped <- character(0)
for (j in seq_along(df)) {
col <- df[[j]]
if (!is.character(col))
next
cleaned <- clean_text(col)
n_changed <- sum(!is.na(col) & !is.na(cleaned) & col !=
cleaned, na.rm = TRUE)
if (n_changed > 0L) {
changed_cols <- c(changed_cols, names(df)[j])
cells_cleaned <- cells_cleaned + n_changed
}
df[[j]] <- cleaned
non_blank <- cleaned[!is.na(cleaned) & nzchar(cleaned)]
if (length(non_blank) && all(grepl("^[+-]?(0|[1-9][0-9]*)([.][0-9]+)?$",
non_blank))) {
parsed <- suppressWarnings(as.numeric(cleaned))
was_blank <- is.na(cleaned) | !nzchar(cleaned)
lost <- (is.na(parsed) | is.infinite(parsed)) & !was_blank
if (!any(lost)) {
finite <- parsed[!is.na(parsed)]
all_whole <- all(finite == floor(finite))
in_range <- all(abs(finite) <= .Machine$integer.max)
df[[j]] <- if (all_whole && in_range)
as.integer(parsed)
else parsed
retyped <- c(retyped, names(df)[j])
}
}
}
names(df) <- clean_text(names(df))
if (length(changed_cols)) {
attr(df, "ingest_clean_columns") <- changed_cols
attr(df, "ingest_clean_cells_cleaned") <- cells_cleaned
}
if (length(retyped))
attr(df, "ingest_clean_retyped") <- retyped
df
}
data <- clean_cells(data)
# Complete-case analysis: drop rows missing any analysis value.
.acols <- c("score", "group")
data <- data[stats::complete.cases(data[, .acols, drop = FALSE]), , drop = FALSE]
result <- t.test(`score` ~ `group`, data = data, var.equal = FALSE)
print(result)
# Sensitivity: minimum detectable effect at n=20 (power=0.8, alpha=0.05). NOT observed power.
power <- pwr::pwr.t2n.test(n1 = 10, n2 = 10, sig.level = 0.05, power = 0.8)
print(power)Common questions
What is the difference between Cohen's d and Hedges' g?
Both divide the difference between two means by the pooled standard deviation. Hedges' g then multiplies Cohen's d by a correction factor slightly below 1, which removes the small upward bias d has in small samples. In larger samples the two are close; in small ones, g is the better estimate.
When should I use Glass's delta instead of Cohen's d?
Use Glass's delta when there is a clear control or baseline group and the treatment may have changed the spread as well as the mean. Dividing by the control group's standard deviation keeps the treatment's extra variability out of the yardstick. Name the reference group, because the value changes if you switch it.
Which effect size should I report for a meta-analysis?
Hedges' g is the usual choice for two independent groups, because meta-analyses pool many studies, some of them small, and the uncorrected d would carry its small-sample bias into the pooled estimate. Report the means, standard deviations and group sizes as well, so others can recompute any version.
Does the Cohen's d confidence interval assume equal variances?
The usual interval, built from the noncentral t distribution and computed by most software including the effectsize package, assumes the two populations have the same variance. When the spreads clearly differ, say so, and consider an interval method that does not need equal variances, or report the raw mean difference with a Welch interval alongside.
How do I calculate Cohen's d for paired data in R?
With the effectsize package, cohens_d(x, y, paired = TRUE) gives d_z, the mean difference divided by the standard deviation of the differences. In effectsize 1.0.2 the formula interface refuses paired = TRUE. For d_av or d_rm, use repeated_measures_d(), which applies the small-sample correction by default.
What is a good Cohen's d value?
There is no universal good value. Cohen's labels of 0.2 small, 0.5 medium and 0.8 large are arbitrary conventions that should not be applied rigidly. Judge the size against what is meaningful in your field and against the interval around the estimate, not the label alone.
Can I use Cohen's d when my data are not normal?
You can compute it, but its usual interpretations, such as overlap between the two groups, assume roughly normal data with similar spreads and can be unreliable otherwise. For skewed or ordinal outcomes, consider a rank-based effect size or a transformation, and report the raw difference alongside.
Sources
- Hedges LV (1981). Distribution theory for Glass's estimator of effect size and related estimators. Journal of Educational Statistics 6(2):107-128. doi:10.3102/10769986006002107
- Lakens D (2013). Calculating and reporting effect sizes to facilitate cumulative science: a practical primer for t-tests and ANOVAs. Frontiers in Psychology 4:863. doi:10.3389/fpsyg.2013.00863
- Grissom RJ, Kim JJ (2001). Review of assumptions and problems in the appropriate conceptualization of effect size. Psychological Methods 6(2):135-146. doi:10.1037/1082-989X.6.2.135
- Bonett DG (2008). Confidence intervals for standardized linear contrasts of means. Psychological Methods 13(2):99-109. doi:10.1037/1082-989X.13.2.99
- Hedges LV (2024). Interpretation of the standardized mean difference effect size when distributions are not normal or homoscedastic. Educational and Psychological Measurement 85(2):245-257. doi:10.1177/00131644241278928
- Ben-Shachar MS, Lüdecke D, Makowski D (2020). effectsize: Estimation of effect size indices and standardized parameters. Journal of Open Source Software 5(56):2815. doi:10.21105/joss.02815
Methods discussed
Try it on your own data
Upload a dataset and Celeus suggests a method, checks its assumptions, runs it in R, and gives you the script to rerun it.
Try it in Celeus