Friedman test in R, or the nonparametric version of the repeated measures ANOVA
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

Introduction
In a previous article, we showed how to perform a repeated measures ANOVA in R to compare a quantitative variable measured on the same subjects under three or more related conditions, or at three or more points in time.
As for many statistical tests, its results can only be trusted if some assumptions are met, in particular the normality of the residuals (at least for small samples) and the sphericity of the data. When they are not, or when the dependent variable is only ordinal, its nonparametric version can be used: the Friedman test, proposed by the economist Milton Friedman in 1937 in a paper whose title sums up its purpose: “The use of ranks to avoid the assumption of normality implicit in the analysis of variance” (Friedman 1937).
The article about the Kruskal-Wallis test, the nonparametric version of the one-way ANOVA, already pointed in this direction: if the observations between samples are dependent (for instance, the same individuals measured before, during and after a treatment), the Friedman test should be preferred to take this dependency into account. The present article picks up that thread.
These four tests, all used to compare three groups or more, form two pairs of parametric and nonparametric tests:
| Independent samples | Related samples | |
|---|---|---|
| Parametric | One-way ANOVA | Repeated measures ANOVA |
| Nonparametric | Kruskal-Wallis test | Friedman test |
In other words, the Friedman test is the “related-samples” counterpart of the Kruskal-Wallis test. Both work on ranks instead of raw values, but the Kruskal-Wallis test ranks all observations together, whereas the Friedman test ranks the measurements within each subject, which is how it takes the dependency between the samples into account.
In the rest of the article, we show how to perform and interpret the Friedman test in R, how to follow it up with post-hoc tests, and how to present all the results on a single plot.
Data
We use the same scenario as in the repeated measures ANOVA article: a treatment against chronic pain, with pain measured from 0 (no pain at all) to 100 (unbearable pain) on the same patients (i) before, (ii) during and (iii) one month after the treatment. This time, however, the data come from a small pilot study with 16 patients, and some scores are affected by flare-ups:
# number of patients
n <- 16
# each patient has its own baseline level of pain
patient_effect <- rnorm(n, mean = 0, sd = 6)
# pain score at the 3 moments, with right-skewed fluctuations (flare-ups)
before <- 60 + patient_effect + rexp(n, rate = 1 / 6)
during <- 48 + patient_effect + rexp(n, rate = 1 / 6)
after <- 46 + patient_effect + rexp(n, rate = 1 / 6)
# dataset in the long format
dat <- data.frame(
patient = factor(rep(1:n, times = 3)),
time = factor(rep(c("before", "during", "after"), each = n),
levels = c("before", "during", "after")
),
pain = round(c(before, during, after), 1)
)
head(dat)
## patient time pain
## 1 1 before 75.7
## 2 2 before 58.8
## 3 3 before 91.4
## 4 4 before 67.8
## 5 5 before 92.4
## 6 6 before 60.7
(A seed has been set in the background with set.seed(42), so the data and all the results below are reproducible.)
As before, the term patient_effect creates the dependency between the three scores of a given patient, and the data are in the long format (one row per patient and per moment). The difference lies in the fluctuations around the usual level of each patient, drawn from a strongly right-skewed exponential distribution (rexp()) instead of a normal one. This mimics flare-ups: most patients report a score close to their usual level, but a few of them report a much higher one. With so few patients, normality cannot be taken for granted.
Since we are going to use a test based on ranks, the median and the interquartile range are the most relevant descriptive statistics, but the mean is shown for comparison:
# install.packages("dplyr")
library(dplyr)
dat %>%
group_by(time) %>%
summarise(
n = n(),
median = median(pain),
IQR = IQR(pain),
mean = round(mean(pain), 2)
) %>%
as.data.frame() # to print all decimals
## time n median IQR mean
## 1 before 16 68.50 15.325 71.14
## 2 during 16 57.45 12.025 59.45
## 3 after 16 52.10 7.475 55.54
The boxplots compare the three moments, and the second plot joins the successive scores of each patient:
# install.packages("ggplot2")
library(ggplot2)
ggplot(dat) +
aes(x = time, y = pain, fill = time) +
geom_boxplot() +
theme(legend.position = "none") +
labs(
x = "Moment of the measurement",
y = "Pain score"
)

ggplot(dat) +
aes(x = time, y = pain, group = patient) +
geom_line(alpha = 0.4) +
geom_point(alpha = 0.4) +
labs(
x = "Moment of the measurement",
y = "Pain score"
)

In our sample, the median pain score decreased from 68.5 before the treatment to 57.45 during the treatment and 52.1 one month after it, and most patients follow this downward trend. Both plots also reveal two isolated high scores (one during and one after the treatment) due to flare-ups. (The low score flagged after the treatment is less extreme, and simply reflects a patient who responded particularly well.) These flare-ups pull the mean upwards (55.5 versus a median of 52.1 one month after the treatment) and would weigh heavily on a test based on means, whereas in a test based on ranks they simply count as the highest score of the patients concerned.
Only a sound statistical test will tell us whether these differences can be generalized to the population.
Friedman test
Aim and hypotheses
The Friedman test is used to compare \(k \geq 3\) related conditions (or points in time) in terms of a quantitative or ordinal variable measured on the same subjects. In our example, it helps us to answer the question: “Is the pain score different before, during and after the treatment?”.
Its principle is simple: the \(k\) measurements of each subject are ranked from 1 (the smallest) to \(k\) (the largest), with tied values receiving the average of their ranks, and the ranks are then summed for each condition. If the conditions do not differ, these sums, denoted \(R_1, \dots, R_k\), should all be close to \(n(k+1)/2\), where \(n\) is the number of subjects. The test statistic measures how far they are from this value:
\[Q = \frac{12}{n k (k+1)} \sum_{j=1}^k R_j^2 - 3n(k+1)\]
Under the null hypothesis, and if \(n\) is not too small, \(Q\) follows approximately a chi-square distribution with \(k - 1\) degrees of freedom (with a correction in case of ties).1 Since the ranking is done within each subject, the general level of each patient plays no role, just as the repeated measures ANOVA removes the variability between subjects from its error term.
The null and alternative hypotheses of the Friedman test are:
- \(H_0\): the \(k\) related conditions have the same distribution, so no condition tends to receive higher or lower ranks than the others
- \(H_1\): at least one condition is different from the others
In our example, \(H_0\) means that the pain score has the same distribution before, during and after the treatment, and \(H_1\) that at least one of the three moments differs from the other two.
Be careful that, as for the ANOVA and the Kruskal-Wallis test, the alternative hypothesis is not that all conditions are different from each other. If the null hypothesis is rejected, we only know that at least one moment differs from the others; post-hoc tests, covered later, tell us which ones.
Note also that the Friedman test is often presented as a comparison of medians. This shortcut is only valid under additional assumptions (in particular, distributions with the same shape that only differ by a shift), which are not needed as long as the test is used to compare conditions in general, as we do here.
Assumptions
First, the Friedman test requires one dependent variable, at least ordinal, measured on the same subjects across \(k \geq 3\) related conditions or points in time: a within-subjects design in which each subject is measured once in each condition (subjects with a missing measurement are removed by friedman.test()). Here, the pain score is measured at 3 moments on the same 16 patients, so this assumption is met.2
Second, the subjects must be independent from each other. Independence is required between subjects, but not within them: the \(k\) measurements of a given patient are of course dependent (this is the whole point of the design, and exactly what the ranking within subjects accounts for), but the scores of one patient must not influence those of another. This is verified based on the design of the study: here, patients have been selected at random and treated individually.
Third, as a nonparametric test, the Friedman test does not require normality, and since it compares neither means nor variances of differences, it does not require sphericity either. This is precisely why it is preferred over the repeated measures ANOVA when the normality or sphericity assumptions of the latter are not satisfied.
In our example, normality can be assessed on the residuals of a model including the moment and the patient, as in the repeated measures ANOVA article:
# residuals of the model, taking the patient effect into account
res_lm <- lm(pain ~ time + patient, data = dat)
# install.packages("car")
library(car)
qqPlot(residuals(res_lm),
id = FALSE # remove point identification
)

shapiro.test(residuals(res_lm)) ## ## Shapiro-Wilk normality test ## ## data: residuals(res_lm) ## W = 0.89685, p-value = 0.0004984
Several points of the QQ-plot lie far from the straight line and outside the confidence bands, and the Shapiro-Wilk test rejects the normality of the residuals (p-value < 0.05). With only 16 patients, the central limit theorem cannot be relied upon to make this deviation harmless, so the Friedman test is the appropriate choice.
This flexibility comes at a price, though: since only the order of the measurements within each subject is used, the Friedman test is generally less powerful than the repeated measures ANOVA when the assumptions of the latter are met. It is an alternative, not a default choice.
In R
With base R
In R, the Friedman test is done with the friedman.test() function. With data in the long format, the easiest is to use its formula interface, dependent variable ~ conditions | subjects:
friedman.test(pain ~ time | patient, data = dat ) ## ## Friedman rank sum test ## ## data: pain and time and patient ## Friedman chi-squared = 17.375, df = 2, p-value = 0.0001687
It also accepts a matrix with one row per subject and one column per condition (the wide format), which can be obtained with the pivot_wider() function of the {tidyr} package:
# install.packages("tidyr")
library(tidyr)
# from the long format to the wide format
dat_wide <- dat %>%
pivot_wider(names_from = time, values_from = pain)
head(dat_wide)
## # A tibble: 6 × 4
## patient before during after
## <fct> <dbl> <dbl> <dbl>
## 1 1 75.7 66.8 55.2
## 2 2 58.8 49 52.5
## 3 3 91.4 50.4 55
## 4 4 67.8 53.9 51.5
## 5 5 92.4 52.8 51.7
## 6 6 60.7 58.8 46.1
# Friedman test on the 3 columns of scores (without the patient column)
friedman.test(as.matrix(dat_wide[, -1]))
##
## Friedman rank sum test
##
## data: as.matrix(dat_wide[, -1])
## Friedman chi-squared = 17.375, df = 2, p-value = 0.0001687
Both give the same results: the test statistic \(Q\) (Friedman chi-squared), the number of degrees of freedom (df, equal to \(k - 1 = 2\)) and the p-value, which we interpret in the next section.
For the curious reader, the test statistic can easily be recomputed from the ranks within patients:
# rank the 3 scores within each patient ranks <- t(apply(dat_wide[, -1], 1, rank)) # sum of the ranks for each moment R <- colSums(ranks) R ## before during after ## 45 29 22 # test statistic k <- 3 12 / (n * k * (k + 1)) * sum(R^2) - 3 * n * (k + 1) ## [1] 17.375
Under the null hypothesis, each sum of ranks would be close to \(n(k+1)/2 = 32\). Here, the moment before the treatment collects much higher ranks, and we find the same statistic as friedman.test().
With the {rstatix} package
For an output consistent with the tidyverse, the friedman_test() function of the {rstatix} package uses the same formula and returns a data frame:
# install.packages("rstatix")
library(rstatix)
dat %>%
friedman_test(pain ~ time | patient)
## # A tibble: 1 × 6
## .y. n statistic df p method
## * <chr> <int> <dbl> <dbl> <dbl> <chr>
## 1 pain 16 17.4 2 0.000169 Friedman test
The same package provides an effect size, Kendall’s \(W\):
dat %>% friedman_effsize(pain ~ time | patient) ## # A tibble: 1 × 5 ## .y. n effsize method magnitude ## * <chr> <int> <dbl> <chr> <ord> ## 1 pain 16 0.543 Kendall W large
Kendall’s \(W\), computed as \(Q / (n(k - 1))\), ranges from 0 (no consistent ordering of the conditions across subjects) to 1 (all subjects rank the conditions in the same order). {rstatix} interprets it with the usual guidelines: 0.1 to < 0.3 for a small effect, 0.3 to < 0.5 for a moderate effect, and 0.5 or more for a large effect. Here, \(W =\) 0.54, so a large effect.
Interpretations
The p-value is smaller than the significance level \(\alpha = 0.05\), so we reject the null hypothesis and we conclude that the pain score is not the same at the three moments (\(\chi^2(2) = 17.38\), p-value < 0.001, Kendall’s \(W = 0.54\)).
(For the sake of illustration, with a p-value larger than 0.05, we could not have rejected the null hypothesis, and thus could not have concluded that the pain score changed over time.)
As any omnibus test, however, the Friedman test does not tell us which moments differ.
Post-hoc tests
To find out, we need post-hoc tests (in Latin, “after this”, so after a significant Friedman test), which compare the conditions two by two while controlling for multiple comparisons (see more details in the one-way ANOVA article). Since the data are related, these comparisons must take the within-subject structure into account: whereas the Dunn test follows a Kruskal-Wallis test, the most common post-hoc tests after a Friedman test are:
- pairwise Wilcoxon signed-rank tests
- the Nemenyi test
- the Conover test
Unlike pairwise Wilcoxon tests, which only use the two conditions being compared, the Nemenyi and Conover tests compare the rank sums of the Friedman test itself, computed on all conditions at once. This makes them more consistent with the omnibus test, but it also has a drawback: the conclusion for a given pair of conditions can change depending on which other conditions are included in the study. For this reason, some authors, such as Benavoli et al. (2016), recommend pairwise Wilcoxon signed-rank tests instead. In practice, both approaches are widely used, and they often lead to the same conclusions, as in our example below.
Pairwise Wilcoxon signed-rank tests
The Wilcoxon signed-rank test compares two paired samples. It belongs to the same family of rank-based tests as the Friedman test, and it is actually a one-sample Wilcoxon test applied to the differences within subjects. Pairwise tests with the Holm adjustment are obtained with pairwise_wilcox_test() and the argument paired = TRUE:3
dat %>%
pairwise_wilcox_test(pain ~ time,
paired = TRUE,
p.adjust.method = "holm"
)
## # A tibble: 3 × 9
## .y. group1 group2 n1 n2 statistic p p.adj p.adj.signif
## * <chr> <chr> <chr> <int> <int> <dbl> <dbl> <dbl> <chr>
## 1 pain before during 16 16 117 0.00919 0.0184 *
## 2 pain before after 16 16 122 0.00336 0.0101 *
## 3 pain during after 16 16 95 0.175 0.175 ns
(With paired = TRUE, observations are paired according to their order in the dataset, so the patients must appear in the same order at each moment, which is the case here.)
The base R function pairwise.wilcox.test() gives the same p-values:
pairwise.wilcox.test(dat$pain, dat$time, paired = TRUE, p.adjust.method = "holm" ) ## ## Pairwise comparisons using Wilcoxon signed rank exact test ## ## data: dat$pain and dat$time ## ## before during ## during 0.018 - ## after 0.010 0.175 ## ## P value adjustment method: holm
Comparing the adjusted p-values (the p.adj column of the first output) to the 5% significance level, we conclude that:
- the pain score differs significantly between before and during the treatment (adjusted p-value = 0.018),
- it differs significantly between before the treatment and one month after it (adjusted p-value = 0.010), and
- it does not differ significantly between during the treatment and one month after it (adjusted p-value = 0.175).
In other words, the treatment is associated with a significant decrease of the pain score (median from 68.5 to 57.45), which persists one month after the treatment (median of 52.1), while the further decrease after the treatment is not significant.
Nemenyi test
The Nemenyi test is available in the {PMCMRplus} package:
# install.packages("PMCMRplus")
library(PMCMRplus)
frdAllPairsNemenyiTest(pain ~ time | patient,
data = dat
)
## before during
## during 0.01299 -
## after 0.00014 0.43104
These p-values (based on the studentized range distribution, which already accounts for multiple comparisons) lead to the same conclusions: the moment before the treatment differs significantly from both other moments, while during and after do not differ significantly.
Conover test
Like the Nemenyi test, the Conover test compares the rank sums of the Friedman test, but it relies on a Student’s t distribution with an error term estimated from the ranks, which generally makes it more powerful than the Nemenyi test. It is available in the same package, with the frdAllPairsConoverTest() function. By default, this function uses a single-step adjustment based on the studentized range distribution (as for the Nemenyi test), but other adjustments can be chosen with the p.adjust.method argument, in which case the p-values are computed from the t distribution and then adjusted. For consistency with the pairwise Wilcoxon tests, we use the Holm method. Note that, unlike frdAllPairsNemenyiTest(), this function does not accept a formula, so the response, the conditions and the subjects are passed as separate vectors:
frdAllPairsConoverTest( y = dat$pain, groups = dat$time, blocks = dat$patient, p.adjust.method = "holm" ) ## before during ## during 0.00066 - ## after 6.9e-06 0.08650
These adjusted p-values lead to the same conclusions: the pain score before the treatment differs significantly from the pain score during and one month after the treatment, while the pain scores during and after the treatment do not differ significantly.
Note that you may also come across this test under the name Durbin-Conover test, for instance in the {ggstatsplot} package used in the next section. The Durbin test is a generalization of the Friedman test to designs in which each subject is measured under only some of the conditions (the so-called balanced incomplete block designs), and the Durbin-Conover test is its post-hoc test. When all subjects are measured under all conditions, as in our example, the Durbin test reduces to the Friedman test and the Durbin-Conover test reduces to the Conover test: despite the different names, the computations are exactly the same (the durbinAllPairsTest() function of the {PMCMRplus} package would give the same results as above).
Combination of statistical results and plot
As for the Kruskal-Wallis test, the {ggstatsplot} package can display the data and all the statistical results on a single plot, here with ggwithinstats(), designed for within-subjects designs:
# install.packages("ggstatsplot")
library(ggstatsplot)
ggwithinstats(
data = dat,
x = time,
y = pain,
subject.id = patient,
type = "nonparametric", # repeated measures ANOVA or Friedman test
pairwise.display = "all", # display all pairwise comparisons
bf.message = FALSE
)

The results of the Friedman test are shown in the subtitle (the p-value is after p =), the boxplots and violin plots show the distribution at each moment, and the post-hoc tests are displayed between each pair of moments. For these post-hoc tests, ggwithinstats() uses the Durbin-Conover test with the Holm adjustment which, as explained above, is the Conover test under another name. The adjusted p-values displayed on the plot are therefore exactly those obtained with frdAllPairsConoverTest() in the previous section.
Summary
In this article, we presented the Friedman test, the nonparametric version of the repeated measures ANOVA, used to compare three or more related conditions in terms of a quantitative or ordinal variable. Since it ranks the measurements within each subject, it requires independence between subjects but neither normality nor sphericity. In R, it is performed with friedman.test() or friedman_test() from {rstatix}, and a significant result, which only indicates that at least one condition differs, is followed by post-hoc tests such as pairwise Wilcoxon signed-rank tests with adjusted p-values. If you hesitate between several methods, see this overview of the most common statistical tests.
Thanks for reading.
As always, if you have a question or a suggestion related to the topic covered in this article, please add it as a comment so other readers can benefit from the discussion.
References
A common rule of thumb is \(n > 15\) or \(k > 4\). For smaller samples, exact tables or permutation p-values are more appropriate.↩︎
In theory, the Friedman test can also be used with two related conditions, but it then only uses the sign of the difference within each subject (it is equivalent to the sign test). In practice, the Wilcoxon signed-rank test, which also uses the size of the differences, is preferred for two related samples.↩︎
The Holm method is less conservative than the Bonferroni correction, while controlling the same global error rate. See
?p.adjustfor the other available methods.↩︎
R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.