Kobieta analizuje dane na dwóch monitorach przy biurku w biurze
Źródło: Pexels | Autor: Kampus Production
Rate this post

Nawigacja po artykule:

Kiedy test Friedmana ma sens zamiast ANOVA z powtarzanymi pomiarami

Typowe sytuacje badawcze dla testu Friedmana

Test Friedmana jest rozsądnym wyborem, gdy masz te same osoby lub obiekty oceniane w kilku warunkach, a dane są co najmniej w skali porządkowej i daleko im do normalności. Klasyczny przypadek: ten sam respondent ocenia trzy wersje interfejsu w skali 1–5, albo ten sam pacjent ocenia nasilenie bólu przed zabiegiem, bezpośrednio po oraz tydzień po.

Jeżeli Twoje dane:

  • pochodzą z powtarzanych pomiarów (ta sama osoba mierzona kilka razy),
  • są na skali porządkowej (np. skale Likerta, stopnie oceny), albo na skali liczbowej, ale mocno odstają od normalności,
  • mają sensowną liczbę poziomów czynnika (najczęściej 3–6 warunków),

to test Friedmana będzie tani czasowo i koncepcyjnie w porównaniu z próbami „naprawiania” danych pod ANOVA z powtarzanymi pomiarami (RM ANOVA).

Różnice: RM ANOVA vs test Friedmana vs Kruskal-Wallis

W uproszczeniu można patrzeć na te testy jak na nieco inne narzędzia do podobnych zadań:

  • ANOVA z powtarzanymi pomiarami – dane ilościowe, w miarę normalne, równość wariancji, brak poważnych odstających. Mocny i elastyczny, ale wymagający pod względem założeń.
  • Test Kruskala-Wallisa – nieparametryczny odpowiednik jednoczynnikowej ANOVA dla prób niezależnych. Używasz, gdy porównujesz np. trzy różne grupy badanych, a nie te same osoby w trzech warunkach.
  • Test Friedmana – nieparametryczny odpowiednik jednoczynnikowej ANOVA z powtarzanymi pomiarami. Te same osoby, kilka warunków, skale porządkowe lub brak normalności.

Jeżeli projekt jest w układzie within-subjects (każda osoba w każdym warunku), wyboru dokonujesz głównie między RM ANOVA a Friedmanem. Gdy układ jest between-subjects (różne osoby w różnych warunkach), wtedy raczej wybierasz między klasyczną ANOVA a testem Kruskala-Wallisa.

Minimalne wymagania i kompletność danych

Żeby test Friedmana miał sens:

  • Liczba poziomów czynnika – minimum 3 warunki. Dla 2 warunków wystarczy test Wilcoxona dla prób zależnych.
  • Liczba badanych – praktycznie, dobrze mieć przynajmniej 8–10 „bloków” (osób/obiektów). Przy bardzo małych próbach test ma niską moc i wyniki trudno bronić.
  • Kompletność danych – każda osoba powinna mieć wynik w każdym warunku. Standardowa implementacja friedman.test() nie lubi braków (NA) w środku macierzy.

Jeżeli pojawiają się braki danych, najprostsza i najszybsza strategia to ograniczenie się do pełnych przypadków (rzadko utrata kilku rekordów zrujnuje analizę). Bardziej wyrafinowane rozwiązania (np. imputacja) wymagają znacznie większego nakładu pracy i uzasadnienia, więc przy niewielkiej skali badania zazwyczaj się nie opłacają.

Proste kryterium decyzyjne: kiedy od razu iść w Friedmana

Z praktycznego punktu widzenia:

  • jeśli masz skale Likerta,
  • jeśli histogramy dla poszczególnych warunków są mocno skośne,
  • jeśli wstępne testy normalności (np. Shapiro-Wilka) masowo odrzucają normalność,

to zamiast tracić czas na logarytmizacje, Box-Coxa i inne transformacje, lepiej przejść prosto do testu Friedmana + sensowne porównania par. Dla większości raportów zespołowych, prac magisterskich czy analiz produktowych taki zestaw jest w zupełności wystarczający i trudny do zakwestionowania, o ile wyniki są czytelnie opisane.

Mężczyzna analizuje wykresy biznesowe na laptopie z perspektywy zza pleców
Źródło: Pexels | Autor: RDNE Stock project

Założenia testu Friedmana i co naprawdę trzeba sprawdzać

Struktura danych: bloki i warunki

Test Friedmana operuje na blokach. Najczęściej blok to jedna osoba, ale może to być też jeden sklep, linia produkcyjna czy urządzenie. Każdy blok jest oceniany w każdym z warunków, a w obrębie bloku porównywane są rangi wyników między warunkami.

Struktura danych powinna być taka, że:

  • każdy blok ma jeden wynik na warunek,
  • blok jest tą samą jednostką w każdym warunku,
  • warunki są porównywane względem siebie w obrębie bloku (np. ta sama osoba ocenia A, B i C).

Jeżeli projekt badania nie jest w pełni zrównoważony (np. nie wszyscy respondenci oceniali wszystkie warunki), analiza Friedmanem staje się bardziej kłopotliwa – w prostym, „budżetowym” workflow bez mieszania modeli mieszanych najłatwiej jest ograniczyć się do tych, którzy mają komplet obserwacji.

Skala pomiaru i brak wymogu normalności

Kluczowa zaleta testu Friedmana: brak założenia normalności oraz możliwość pracy na skalach porządkowych. W praktyce:

  • jeżeli wyniki są na skali 1–5, 1–7 itp. – Friedmana można stosować bez kompleksów,
  • jeżeli dane są metryczne, ale rozkłady są bardzo dalekie od normalnych – Friedmana także można użyć bez transformacji.

Test bazuje na rangach, więc pojedyncze skrajne wartości mają mniejszy wpływ niż w modelach parametrycznych. Mimo to skrajne odstające obserwacje potrafią wypaczyć interpretację – szczególnie w małych próbach. Warto je chociaż pobieżnie przejrzeć (wykres pudełkowy dla każdego warunku).

Podobne rozkłady w obrębie warunków

Test Friedmana zakłada, że rozkłady w obrębie warunków są w miarę podobne i różnią się głównie położeniem (mediana). Jeśli jeden warunek ma długi „ogon”, a inny jest symetryczny, interpretacja p-value może być mniej czytelna.

Szybka kontrola:

  • wykresy pudełkowe (boxplot) dla poszczególnych warunków,
  • porównanie median i rozrzutu,
  • sprawdzenie, czy nie ma rażąco większej liczby wartości skrajnych w jednym z warunków.

Nie chodzi o akademicką perfekcję, tylko o stwierdzenie, że wyniki z grubsza „grają” między warunkami i nie ma jawnej katastrofy pomiarowej.

Braki danych: pełne przypadki czy kombinowanie

Test Friedmana w bazowym R wymaga kompletności w obrębie bloków. Braki danych ogarniasz zwykle tak:

  • tworzysz podzbiór tylko z osobami mającymi wyniki we wszystkich warunkach (complete cases),
  • uruchamiasz test Friedmana na tym podzbiorze.

Jest to rozwiązanie ekonomiczne czasowo i w 90% przypadków wystarczające. Imputacja braków (np. średnią, medianą, modelem wielokrotnej imputacji) zwiększa złożoność analizy, wymaga dodatkowego opisu w raporcie i otwiera pole do krytyki, jeśli nie jest bardzo dobrze uzasadniona. Przy niewielkich odsetkach braków taniej jest uczciwie zaznaczyć, że analiza opiera się na pełnych przypadkach.

Jak przygotować dane w R: format szeroki vs długi

Przykładowy układ: 10 osób, 3 warunki

Wyobraźmy sobie prosty eksperyment UX: 10 użytkowników ocenia trzy wersje strony (A, B, C) w skali 1–7 pod kątem wygody. Masz więc:

  • zmienną identyfikującą osobę (np. id),
  • trzy kolumny z ocenami: A, B, C.

To jest klasyczny przykład danych, które można trzymać w formacie szerokim. Do prostego testu Friedmana szeroki format jest często najszybszy. Jeżeli natomiast planujesz szereg wykresów, modele mieszane lub rozbudowane post-hoc, wygodniejszy będzie format długi.

Dane w formacie szerokim: szybko i prosto

Format szeroki to struktura:

  • 1 wiersz = 1 osoba,
  • kolumny = poszczególne warunki.

Przykładowo:

idABC
1564
2342

W takim układzie możesz bardzo szybko użyć friedman.test(), przekazując macierz ocen (kolumny A, B, C) oraz identyfikator osoby jako blok. Szeroki format jest też wygodny, gdy dane przychodzą z Excela w jednym arkuszu.

Dane w formacie długim: elastyczność i integracja z tidyverse

Format długi oznacza, że:

  • 1 wiersz = 1 pomiar (jedna osoba w jednym warunku),
  • masz kolumnę z identyfikatorem osoby, kolumnę z nazwą warunku oraz kolumnę z wynikiem.

Przykładowo:

idwarunekocena
1A5
1B6
1C4

Taki układ jest idealny do:

  • użycia składni formułowej friedman.test(ocena ~ warunek | id, data = ...),
  • budowania wykresów w ggplot2,
  • łatwych filtracji/rekodowań warunków w stylu tidyverse.

Jeśli planujesz więcej analiz niż jeden test, inwestycja w uporządkowanie danych do formatu długiego szybko się zwraca – mniej ręcznego przepisywania i mniejsze ryzyko pomyłek.

Szybkie konwersje między szerokim a długim formatem w R

Do zmiany formatu używa się funkcji z pakietu tidyr. Dla szerokiego na długi:


library(tidyr)
library(dplyr)

# załóżmy, że mamy dane szerokie:
# kolumny: id, A, B, C

dane_long <- dane_wide |>
  pivot_longer(
    cols = c(A, B, C),
    names_to = "warunek",
    values_to = "ocena"
  )

Z długiego na szeroki:


dane_wide <- dane_long |>
  pivot_wider(
    names_from = warunek,
    values_from = ocena
  )

Jeśli analizujesz jeden eksperyment i chcesz „po prostu policzyć” test Friedmana, możesz zostać przy formacie, w którym już masz dane, byle spełniał wymogi funkcji friedman.test(). Dopracowywanie struktury ma sens wtedy, gdy te same dane będą używane wielokrotnie w projekcie.

Analityk finansowy przy biurku przegląda dane na kilku monitorach
Źródło: Pexels | Autor: AlphaTradeZone

Podstawowe liczenie testu Friedmana w bazowym R

Składnia friedman.test dla danych szerokich

Najprostszy wariant w bazowym R zakłada, że masz:

  • macierz lub ramkę z kolumnami będącymi warunkami,
  • informację o blokach (osobach) jako osobną zmienną.

Przykład (dane szerokie):


# Załóżmy, że mamy ramkę:
# id, A, B, C

dane <- data.frame(
  id = 1:10,
  A = c(5, 3, 4, 6, 4, 5, 3, 4, 6, 5),
  B = c(6, 4, 5, 7, 5, 6, 4, 5, 7, 6),
  C = c(4, 2, 3, 5, 3, 4, 2, 3, 5, 4)
)

# funkcja friedman.test "lubi", gdy dane są bez kolumny id:
wyniki <- as.matrix(dane[, c("A", "B", "C")])

friedman.test(wyniki)

W tej wersji friedman.test założy, że rzędy to bloki (osoby), a kolumny to warunki. Nie trzeba ręcznie podawać wektora bloków, jeżeli każda osoba ma dokładnie jeden wiersz.

Składnia formułowa dla danych długich

Dla uporządkowanych danych długich wygodniejsza jest wersja formułowa:

Formuła friedman.test z danymi długimi w praktyce

Dla danych w formacie długim struktura jest jasna: jedna kolumna z wynikiem, jedna z nazwą warunku, jedna z blokiem. Przykład:


dane_long <- data.frame(
  id = factor(rep(1:10, each = 3)),
  warunek = factor(rep(c("A", "B", "C"), times = 10)),
  ocena = c(5, 6, 4,
            3, 4, 2,
            4, 5, 3,
            6, 7, 5,
            4, 5, 3,
            5, 6, 4,
            3, 4, 2,
            4, 5, 3,
            6, 7, 5,
            5, 6, 4)
)

friedman.test(ocena ~ warunek | id, data = dane_long)

Elementy formuły:

  • ocena – zmienna zależna,
  • warunek – czynnik z poziomami, które porównujesz,
  • id – blok, czyli jednostka powtarzanych pomiarów.

Jeżeli id jest liczbowe, dobrze jest je przekonwertować na factor, żeby uniknąć niejasnych komunikatów w innych pakietach, które będziesz wykorzystywać później do post-hoc.

Co zwraca friedman.test i jak to czytać

Standardowy output friedman.test wygląda mniej więcej tak:


	Friedman rank sum test

data:  ocena and warunek and id
Friedman chi-squared = 15.2, df = 2, p-value = 0.0005

Najczęściej wystarczy:

  • wartość statystyki (Friedman chi-squared) – nie jest to klasyczna chi-kwadrat z tabeli SPSS; interesuje głównie w raportach bardziej technicznych,
  • liczba stopni swobody (df) – równa k - 1, gdzie k to liczba warunków,
  • p-value – kluczowa decyzja: istotny efekt główny warunku czy nie.

Jeżeli p-value jest poniżej przyjętego poziomu alfa (np. 0,05), można przejść do porównań par, bo sam test Friedmana tylko mówi, że „coś się różni”, ale nie co z czym.

Uproszczony workflow: sprawdzenie kompletności i uruchomienie testu

Zanim zaczniesz liczyć Friedmana w wersji „budżetowej”, dobrze przejść szybki check-list:

  1. Usuń w oczywisty sposób błędne rekordy (np. ocena 99 na skali 1–7).
  2. Sprawdź, które osoby mają komplet ocen we wszystkich warunkach.
  3. Zrób prosty wykres pudełkowy dla każdego warunku, żeby ogarnąć rozkład.

Minimalny kod:


library(dplyr)
library(tidyr)
library(ggplot2)

# 1. pełne przypadki dla wszystkich warunków
dane_complete <- dane_wide |>
  drop_na(A, B, C)

# 2. szybki rzut oka na rozkłady (w formacie długim)
dane_long <- dane_complete |>
  pivot_longer(cols = c(A, B, C),
               names_to = "warunek",
               values_to = "ocena")

ggplot(dane_long, aes(x = warunek, y = ocena)) +
  geom_boxplot()

# 3. test Friedmana
friedman.test(ocena ~ warunek | id, data = dane_long)

Taki pipeline zajmuje mało czasu, łatwo go wkleić do kolejnych projektów i nie wymaga żonglowania pakietami ponad absolutne minimum.

Post-hoc po Friedmanie: co zrobić, gdy wynik jest istotny

Logika: od efektu globalnego do porównań par

Istotny wynik testu Friedmana mówi: przynajmniej dwa warunki się różnią. Nie mówi: które. Post-hoc to właśnie rozbicie efektu globalnego na porównania par warunków:

  • A vs B,
  • A vs C,
  • B vs C.

W przypadku testu Friedmana naturalnym wyborem są parowe testy Wilcoxona dla prób zależnych (nonparametryczny odpowiednik testu t dla prób zależnych), z korektą na wielokrotne porównania.

Dlaczego nie robić porównań par „bez zastanowienia”

Trzy porównania to jeszcze niewiele, ale w wielu realnych badaniach liczba warunków rośnie do 4–6. Wtedy liczba par rośnie bardzo szybko (k*(k-1)/2) i:

  • ryzyko fałszywie dodatnich wyników (błąd I rodzaju) bez korekty jest spore,
  • p-value „bez korekty” wygląda bardziej spektakularnie, ale nie jest uczciwe,
  • odbiorcy raportów firmowych coraz częściej pytają, jak była robiona korekta.

Dlatego lepiej od razu przyjąć jeden sensowny, prosty wariant korekty i stosować go konsekwentnie.

Mężczyzna w słuchawkach analizuje dane na monitorze w biurze
Źródło: Pexels | Autor: Kampus Production

Porównania par w R: pairwise.wilcox.test i korekty p-value

Podstawowe użycie pairwise.wilcox.test

Najprostszy budżetowy sposób na post-hoc po Friedmanie w R to:

  • pairwise.wilcox.test() – testy Wilcoxona dla wszystkich par,
  • argument paired = TRUE – próbki zależne,
  • argument p.adjust.method – wybór korekty.

Przykład z danymi długimi:


# zakładamy, że mamy dane_long z kolumnami:
# id, warunek, ocena

# test globalny
friedman.test(ocena ~ warunek | id, data = dane_long)

# jeśli istotny, robimy post-hoc:
pairwise.wilcox.test(
  x = dane_long$ocena,
  g = dane_long$warunek,
  paired = TRUE,
  p.adjust.method = "BH"
)

Argumenty:

  • x – wektor z wynikami,
  • g – czynnik z nazwami warunków,
  • paired = TRUE – sygnał, że obserwacje w parach pochodzą od tych samych osób,
  • p.adjust.method – sposób korekty (np. "bonferroni", "holm", "BH").

Wybór korekty p-value: Bonferroni, Holm, BH

Nie ma jednej świętej korekty, ale kilka rozsądnych, które można ogarnąć bez doktoratu z teorii testowania hipotez:

  • Bonferroni – bardzo konserwatywna, mało fałszywych alarmów, ryzyko pominięcia realnych efektów; dobra, gdy porównań jest niewiele lub stawiasz na maksymalną ostrożność.
  • Holm – wersja krokowa, z reguły bardziej czuła niż Bonferroni przy tej samej kontroli błędu I rodzaju; niezły domyślny wybór do raportów akademickich i wewnętrznych.
  • BH (Benjamini–Hochberg) – kontrola FDR, zwykle mniej restrykcyjna; często stosowana w analizach eksploracyjnych lub wtedy, gdy chcesz bardziej „produktywnych” wniosków przy większej liczbie testów.

Do prostego projektu z kilkoma warunkami często wystarczy:


pairwise.wilcox.test(
  dane_long$ocena,
  dane_long$warunek,
  paired = TRUE,
  p.adjust.method = "holm"
)

Jeżeli ktoś w zespole ma silne preferencje (np. zawsze używa BH), nie ma sensu walczyć – ważne, żeby w raporcie jasno napisać, jaką korektę zastosowano.

Wynik pairwise.wilcox.test – jak go czytać i zgrabnie opisać

Output to zazwyczaj macierz p-value z porównań:


	Pairwise comparisons using Wilcoxon signed rank test 

data:  dane_long$ocena and dane_long$warunek 

  A       B      
B 0.012   -      
C 0.001   0.045  

P value adjustment method: holm 

Interpretacja w wersji „po ludzku”:

  • A vs B: p < 0,05 → różnica istotna,
  • A vs C: p < 0,05 → różnica istotna,
  • B vs C: p < 0,05 → także istotna (tu 0,045 po korekcie).

W raporcie nie trzeba przepisywać całej macierzy. Wystarczy wskazać pary, które pozostały istotne po korekcie, i podać orientacyjnie wartości testu lub same p-value, np.:

„Test Friedmana wykazał istotny efekt wersji interfejsu na ocenę wygody. W analizie post-hoc (Wilcoxon z korektą Holma) wersja C była oceniana niżej niż A (p = 0,001) oraz B (p = 0,045), natomiast A i B nie różniły się istotnie.”

Jak ręcznie wymusić kompletność przy pairwise.wilcox.test

pairwise.wilcox.test automatycznie pomija pary z brakami (NA) w obrębie porównywanych warunków, ale robi to osobno dla każdej pary. To może prowadzić do sytuacji, w której:

  • porównanie A vs B opiera się na innej liczbie osób niż A vs C,
  • porównania nie są w pełni porównywalne między sobą.

Jeśli chcesz prostoty i pełnej spójności:

  1. zbuduj szeroką tabelę z kolumnami warunków,
  2. odfiltruj wiersze z jakimikolwiek brakami,
  3. wróć do formatu długiego i dopiero wtedy uruchom pairwise.wilcox.test.

dane_complete <- dane_long |>
  pivot_wider(names_from = warunek, values_from = ocena) |>
  drop_na() |>
  pivot_longer(cols = -id, names_to = "warunek", values_to = "ocena")

pairwise.wilcox.test(
  dane_complete$ocena,
  dane_complete$warunek,
  paired = TRUE,
  p.adjust.method = "holm"
)

Koszt czasowy niewielki, a w raporcie możesz uczciwie napisać, że wszystkie porównania bazują na tym samym podzbiorze osób.

Efekty wielkości w parowych Wilcoxonach

Jeżeli raport ma być bardziej „analityczny”, same p-value bywają za słabe. Dla Wilcoxona parowego standardową miarą jest:

r = Z / sqrt(N),

gdzie Z to statystyka przekształcona do rozkładu normalnego, a N – liczba par. W bazowym R nie ma tego „na kliknięcie” w pairwise.wilcox.test, ale da się policzyć ręcznie dla pojedynczych porównań:


# przykład: A vs B
d_AB <- dane_complete |>
  pivot_wider(names_from = warunek, values_from = ocena)

wilc_AB <- wilcox.test(d_AB$A, d_AB$B, paired = TRUE, exact = FALSE)

# przybliżenie Z z p-value (dwustronne)
z_AB <- qnorm(wilc_AB$p.value / 2, lower.tail = FALSE)
N    <- sum(!is.na(d_AB$A) & !is.na(d_AB$B))

r_AB <- z_AB / sqrt(N)
r_AB

To już krok ponad „minimum życiowe”, ale jeżeli liczba porównań jest mała, a potrzebujesz efektów wielkości, taki kod wystarczy skopiować i lekko dostosować.

Wygodniejsze workflow w pakietach: rstatix, PMCMRplus, FSA

rstatix: wersja „dla ludzi z tidyverse”

Jeżeli używasz dplyr i ggplot2, rstatix zwykle układa się z tym dobrze. Pozwala policzyć test Friedmana i post-hoc w kilku zgrabnych krokach, zwracając ramki danych zamiast surowych outputów konsoli.

Instalacja (raz na maszynę):


install.packages("rstatix")

Podstawowy workflow:


library(rstatix)
library(dplyr)

# dane_long: id, warunek, ocena

# test Friedmana
friedman_res <- dane_long |>
  friedman_test(ocena ~ warunek | id)

friedman_res

Wynik to ramka z kolumnami typu: statistic, p, df. Można ją łatwo łączyć z innymi tabelami lub zaszyć w raport rmarkdown bez ręcznego przepisywania.

Post-hoc w rstatix: pairwise_wilcox_test i korekty

Największa zaleta rstatix to przyjazne post-hoc w tym samym stylu:


pw_res <- dane_long |>
  pairwise_wilcox_test(
    ocena ~ warunek,
    paired = TRUE,
    p.adjust.method = "holm"
  )

pw_res

Dostajesz ramkę z kolumnami:

  • group1, group2 – porównywane warunki,
  • p – p-value niekorygowane,
  • p.adj – p-value po korekcie,
  • Dodawanie efektów wielkości i etykiet istotności w rstatix

    rstatix dorzuca kilka udogodnień, które przyspieszają pracę przy raportach. Zamiast ręcznie liczyć efekty i sklejać je z wynikami post-hoc, możesz to zautomatyzować w jednym łańcuchu.

    Przykład z efektami wielkości i czytelnymi etykietami:


pw_res <- dane_long |>
  pairwise_wilcox_test(
    ocena ~ warunek,
    paired = TRUE,
    p.adjust.method = "holm"
  ) |>
  add_significance("p.adj")

pw_res

W wyniku pojawiają się m.in.:

  • p.adj.signif – gwiazdki istotności (np. "ns", "*", "**"),
  • p.adj – p-value po korekcie, gotowe do cytowania.

Efekty wielkości można dodać osobno dla konkretnych par lub w bardziej ogólnym workflow:


# przykład: Friedman + eta^2 + post-hoc
friedman_res <- dane_long |>
  friedman_effsize(ocena ~ warunek | id, effectsize = "kendall")

friedman_res

Kendall W to prosta miara „siły” efektu w Friedmanie; da się ją łatwo opisać słownie, np. jako słaby/średni/silny wpływ.

Łączenie rstatix z ggplot2 – szybkie wizualizacje wyników

Jeżeli trzeba dorzucić wykresy do slajdów albo raportu, rstatix dobrze współpracuje z ggpubr (który z kolei opiera się na ggplot2). Bez specjalnego dłubania można wygenerować boxploty z naniesionymi wynikami porównań.


install.packages("ggpubr")

library(ggpubr)

pw_res <- dane_long |>
  pairwise_wilcox_test(
    ocena ~ warunek,
    paired = TRUE,
    p.adjust.method = "holm"
  )

ggboxplot(
  dane_long,
  x = "warunek",
  y = "ocena",
  add = "jitter"
) +
  stat_pvalue_manual(
    pw_res,
    label = "p.adj.signif",
    y.position = c(5.5, 6, 6.5)  # ręczne ustawienie wysokości linii
  )

To nie jest niezbędne do samej statystyki, ale często oszczędza godzinę dłubania w Excelu, kiedy trzeba przygotować kilka prostych slajdów dla zespołu.

PMCMRplus: klasyczne procedury post-hoc po Friedmanie

Jeśli interesują Cię „kanoniczne” procedury post-hoc do testu Friedmana, PMCMRplus ma sporo gotowych funkcji, które implementują znane w literaturze metody. W małych projektach to bywa nadmiarowe, ale przy większej liczbie warunków lub bardziej wymagającym recenzencie robi różnicę.

Instalacja:


install.packages("PMCMRplus")
library(PMCMRplus)

Friedman i post-hoc w PMCMRplus – funkcje „wszystko w jednym”

W PMCMRplus nie musisz osobno wywoływać testu Friedmana i parowych Wilcoxonów. Sporo funkcji robi to w jednym strzale i od razu daje post-hoc z korektą.

Przykład z wykonaniem Friedmana z testem Nemenyi’ego (klasyczny post-hoc po Friedmanie, konserwatywny):


# dane w formacie szerokim: kolumny to warunki
dane_wide <- dane_long |>
  tidyr::pivot_wider(names_from = warunek, values_from = ocena)

# konwersja do macierzy (rzędy = osoby, kolumny = warunki)
mat <- as.matrix(dane_wide[,-1])  # zakładamy, że pierwsza kolumna to id

friedman_nemenyi <- frdAllPairsNemenyiTest(mat)
friedman_nemenyi

Output zawiera:

  • informację o teście globalnym (Friedman),
  • macierz p-value dla porównań par,
  • informację o użytej korekcie.

Zaletą jest jedno wywołanie i spójny pakiet metod, wadą – sensownie działa głównie przy kompletnych danych (bez braków w macierzy).

Wybór metody w PMCMRplus: Conover, Nemenyi, Nielsen

W codziennej pracy nie ma sensu uczyć się wszystkich dostępnych procedur. W kontekście Friedmana kilka metod pojawia się najczęściej:

  • frdAllPairsNemenyiTest() – odpowiednik testu Tukeya w wersji nieparametrycznej; konserwatywny, prosty do wytłumaczenia, dobry, gdy liczba warunków jest niewielka.
  • frdAllPairsConoverTest() – bardziej czuły niż Nemenyi, także szeroko opisywany w literaturze; rozsądny wybór, gdy spodziewasz się subtelnych różnic.
  • frdAllPairsNemenyiTest() z różnymi wariantami wariancji – czasem bywa wymagany w analizach porównawczych algorytmów (np. w ML); do zastosowań biznesowych najczęściej zbędny.

Zaawansowane ustawienia (np. p.adjust.method wewnątrz funkcji) można zwykle zostawić domyślne, chyba że recenzent explicite oczekuje konkretnej procedury.

Konwersja danych z formatu długiego do szerokiego pod PMCMRplus

Większość funkcji z PMCMRplus lubi dane szerokie i kompletną macierz bez braków. Najszybszy przepis:


library(dplyr)
library(tidyr)

dane_wide <- dane_long |>
  select(id, warunek, ocena) |>
  pivot_wider(
    names_from = warunek,
    values_from = ocena
  ) |>
  drop_na()  # jeśli chcesz wymusić kompletne przypadki

mat <- as.matrix(dane_wide[,-1])

Tak przygotowana macierz działa nie tylko z Friedmanem, ale też z innymi rangowymi testami w PMCMRplus. Jeśli masz kilka podobnych projektów, ten fragment kodu można spokojnie włożyć do małej funkcji i nie powtarzać ręcznie.

FSA: proste narzędzia post-hoc „na szybko”

Pakiet FSA jest kojarzony z analizami w rybactwie, ale ma też jedną bardzo praktyczną funkcję: dunnTest(). To nie jest dosłowny post-hoc po Friedmanie (bardziej po Kruskalu–Wallisie), ale mechanizm korekty p-value i ogólny workflow są podobne:

  • automatyczne generowanie wszystkich par,
  • kilka wariantów korekt,
  • wygodny output w formie ramki danych.

Instalacja:


install.packages("FSA")
library(FSA)

Kiedy FSA ma sens przy danych „friedmanowych”

Jeśli w projekcie oprócz Friedmana robisz też Kruskala–Wallisa (np. porównujesz grupy niezależne i zależne w tym samym raporcie), opłaca się mieć jedno narzędzie do post-hoc dla rozkładów rangowych. dunnTest() dobrze ogarnia wersję „dla prób niezależnych”, ale:

  • nie uwzględnia parowania (brak prostego odpowiednika paired = TRUE),
  • bardziej nadaje się do scenariuszy „międzyosobowych” niż „wewnątrzosobowych”.

Dlatego przy klasycznym układzie „te same osoby, kilka warunków” lepiej zostać przy pairwise.wilcox.test lub metodach z rstatix/PMCMRplus, a FSA trzymać jako narzędzie zapasowe do innych testów rangowych.

Porównanie workflow: bazowy R vs rstatix vs PMCMRplus

W praktyce wybór pakietu sprowadza się do odpowiedzi na kilka krótkich pytań:

  • Chcesz minimalizować zależności od pakietów? Zostań przy bazowym R: friedman.test + pairwise.wilcox.test z korektą Holma.
  • Pracujesz w tidyverse i potrzebujesz czytelnych ramek danych? Weź rstatix: friedman_test, pairwise_wilcox_test, friedman_effsize.
  • Potrzebujesz „klasycznych” procedur post-hoc z literatury? Użyj PMCMRplus i funkcji typu frdAllPairsConoverTest czy frdAllPairsNemenyiTest.

W typowym małym projekcie UX albo prostym badaniu pracowniczym często wygrywa opcja „tania i szybka”: bazowy R lub rstatix. PMCMRplus zaczyna być opłacalny wtedy, gdy ilość warunków rośnie, a dyskusje metodologiczne mogą zająć więcej czasu niż sama analiza.

Minimalny „szkielet” kodu do wielokrotnego użycia

Zamiast za każdym razem pisać wszystko od zera, można przygotować jeden szablon analizy Friedmana z post-hoc i tylko podmieniać nazwy kolumn. Przykładowy, budżetowy wariant z rstatix:


analiza_friedman <- function(dane, id_col, group_col, value_col,
                             p_adjust = "holm") {
  library(dplyr)
  library(rstatix)

  dane <- dane |>
    rename(
      id = {{ id_col }},
      group = {{ group_col }},
      value = {{ value_col }}
    )

  test_globalny <- dane |>
    friedman_test(value ~ group | id) |>
    friedman_effsize(value ~ group | id, effectsize = "kendall")

  posthoc <- dane |>
    pairwise_wilcox_test(
      value ~ group,
      paired = TRUE,
      p.adjust.method = p_adjust
    ) |>
    add_significance("p.adj")

  list(
    global = test_globalny,
    posthoc = posthoc
  )
}

# użycie:
# wynik <- analiza_friedman(dane_long, id, warunek, ocena)
# wynik$global
# wynik$posthoc

Taki szablon da się włożyć do jednego pliku R i używać w kolejnych projektach, oszczędzając czas na klepaniu powtarzalnych komend i zmniejszając ryzyko literówek w nazwach zmiennych.

Najczęściej zadawane pytania (FAQ)

Kiedy lepiej użyć testu Friedmana zamiast ANOVA z powtarzanymi pomiarami?

Test Friedmana ma przewagę, gdy:

  • masz te same osoby mierzone w kilku warunkach (układ within-subjects),
  • dane są w skali porządkowej (np. Likert 1–5, 1–7) albo rozkłady są mocno nienormalne,
  • liczba warunków jest sensowna – zwykle 3–6 poziomów.

Jeśli klasyczna RM ANOVA wymagałaby kombinowania z transformacjami, sprawdzania szeregu założeń i walki z odstającymi, Friedman daje prostszy, tańszy czasowo wariant, który w większości zastosowań badawczych jest w pełni akceptowalny.

Jaka jest różnica między testem Friedmana, Kruskala-Wallisa i ANOVA?

ANOVA z powtarzanymi pomiarami służy do danych ilościowych, w miarę normalnych, z założeniem równości wariancji. Używasz jej, gdy te same osoby przechodzą przez wszystkie warunki, a rozkłady nie odbiegają dramatycznie od normalności.

Test Kruskala-Wallisa to nieparametryczny odpowiednik jednoczynnikowej ANOVA dla grup niezależnych (różne osoby w różnych warunkach). Test Friedmana jest jego „kuzynem” dla danych zależnych – te same osoby oceniane w kilku warunkach, skale porządkowe lub silna nienormalność.

Jakie są minimalne wymagania, żeby test Friedmana miał sens?

Przydatne progi praktyczne są proste:

  • co najmniej 3 warunki (dla 2 warunków użyj testu Wilcoxona dla prób zależnych),
  • minimum około 8–10 bloków (osób/obiektów); przy mniejszych próbach moc testu jest niska,
  • kompletne dane – każda osoba ma wynik w każdym warunku.

Jeżeli kilku osobom brakuje pojedynczych pomiarów, najszybsza i zwykle wystarczająca strategia to ograniczenie się do pełnych przypadków, zamiast inwestowania czasu w imputację.

Jak poradzić sobie z brakami danych w teście Friedmana w R?

Standardowa funkcja friedman.test() w R wymaga pełnych danych w obrębie bloków. Najprostsze rozwiązanie:

  • wybierz tylko osoby z kompletem wyników we wszystkich warunkach (complete cases),
  • uruchom test Friedmana na tym podzbiorze.

Imputacja braków (np. średnią, medianą czy złożonymi modelami) zwykle pochłania więcej czasu niż przynosi korzyści przy niewielkim odsetku braków i małych badaniach. Oszczędzasz czas, a w raporcie wystarczy jasno napisać, że analiza opiera się na pełnych przypadkach.

Czy mogę stosować test Friedmana do skal Likerta?

Tak, skale Likerta 1–5, 1–7 i inne skale porządkowe to naturalne zastosowanie testu Friedmana. Test operuje na rangach, więc nie potrzebuje normalności ani precyzyjnej odległości między kolejnymi punktami skali.

Dobrym sygnałem do użycia Friedmana jest sytuacja, gdy:

  • masz kilka pomiarów od tych samych osób w różnych warunkach,
  • histogramy są wyraźnie skośne lub „poszarpane”,
  • test Shapiro-Wilka konsekwentnie odrzuca normalność.

Wtedy próby „naprawiania” rozkładu zazwyczaj są droższą drogą niż prosty Friedman + porównania par.

W jakim formacie trzymać dane w R do testu Friedmana: szerokim czy długim?

Do szybkiego uruchomienia testu Friedmana wygodniejszy bywa format szeroki: jeden wiersz to jedna osoba, a każda kolumna to inny warunek (np. A, B, C). Wtedy można bezpośrednio podać macierz kolumn z ocenami do friedman.test().

Format długi (1 wiersz = 1 pomiar; osobna kolumna z id, warunkiem i wynikiem) daje więcej elastyczności przy:

  • wykresach (ggplot2, pakiety tidyverse),
  • dalszych analizach, np. modelach mieszanych,
  • automatyzacji post-hoc i raportowania.
  • Jeśli potrzebujesz tylko jednego testu i prostego wniosku, format szeroki zwykle jest tańszy czasowo.

Czy przed testem Friedmana muszę sprawdzać normalność i równość wariancji?

Test Friedmana nie wymaga normalności, więc formalne testy typu Shapiro-Wilka nie są obowiązkowe. Kluczowe jest raczej to, aby dane spełniały:

  • strukturę bloków (ta sama jednostka we wszystkich warunkach),
  • w miarę podobne kształty rozkładów między warunkami (różnice głównie w medianie, a nie w ogonach).

Szybki przegląd boxplotów dla każdego warunku wystarcza w większości projektów. Pozwala wyłapać pojedyncze skrajne wartości i ewidentnie inne rozkłady, bez wchodzenia w kosztowne i mało potrzebne baterie testów założeń.