{"id":3292,"date":"2023-01-02T14:40:00","date_gmt":"2023-01-02T13:40:00","guid":{"rendered":"https:\/\/www.gironi.it\/blog\/?p=3292"},"modified":"2026-07-18T09:04:50","modified_gmt":"2026-07-18T08:04:50","slug":"non-parametric-tests-the-wilcoxon-test-for-non-normal-data","status":"publish","type":"post","link":"https:\/\/www.gironi.it\/blog\/en\/non-parametric-tests-the-wilcoxon-test-for-non-normal-data\/","title":{"rendered":"Non-Parametric Tests: The Wilcoxon Test for Non-Normal Data (with R Examples)"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Imagine comparing session durations between two groups of pages: those with a <strong>featured snippet<\/strong> and those without. You run a <strong>Student&#8217;s t-test<\/strong> and get a p-value of 0.08. Not significant, apparently. But looking at the data, you notice the distributions are heavily skewed: a few pages with very long sessions, many with very short ones. The t-test assumes normality, and here we are light-years away.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The <strong>Wilcoxon test<\/strong> is a <strong><a href=\"https:\/\/www.gironi.it\/blog\/en\/statistical-parametric-and-non-parametric-tests\/\" target=\"_blank\" data-type=\"post\" data-id=\"3309\" rel=\"noreferrer noopener\">non-parametric test<\/a><\/strong> that makes no assumptions about the shape of the distribution. It works on <strong>ranks<\/strong>, not on original values: it orders all data from smallest to largest and assigns scores based on position. This makes it especially useful in SEO, where many metrics (sessions, CTR, rankings) are anything but normal.<\/p>\n\n\n\n<!--more-->\n\n\n\n<h2 class=\"wp-block-heading\">When to Use Wilcoxon<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Parametric tests like the t-test assume the data follows a <strong>normal distribution<\/strong>. In SEO, this assumption is almost always violated:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Sessions<\/strong> per page follow a skewed distribution (a few pages drive most of the traffic).<\/li>\n<li><strong>CTR<\/strong> is concentrated near zero with a long tail upward.<\/li>\n<li><strong>Rankings<\/strong> are ordinal variables, not continuous.<\/li>\n<li><strong>Page load times<\/strong> have a heavy right tail.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">When data is non-normal, the t-test can give misleading results. The Wilcoxon test, being rank-based, is <strong>robust<\/strong> to these violations.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Two Versions of the Wilcoxon Test<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The name &#8220;Wilcoxon test&#8221; actually covers two distinct variants:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Wilcoxon-Mann-Whitney<\/strong> (or U test): for comparing two independent samples. It is the non-parametric alternative to the two-sample t-test.<\/li>\n<li><strong>Wilcoxon signed-rank<\/strong>: for comparing two paired samples (before\/after measurements on the same group). Alternative to the paired t-test.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Let&#8217;s look at an SEO example with the independent samples version.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Comparing Two Groups: An SEO Case<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Suppose we want to compare session durations between pages with featured snippets and pages without. Real session data is typically skewed: most sessions last a few seconds, but some last minutes. This is exactly the scenario where Wilcoxon shines.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The figure below shows two simulated distributions similar to what we observe in real cases. The t-test gives a p-value that could be misleading, while Wilcoxon correctly captures the difference between groups.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1125\" height=\"675\" src=\"https:\/\/www.gironi.it\/blog\/wp-content\/uploads\/2026\/07\/wilcoxon-distribuzioni.png\" alt=\"Comparison of two skewed session duration distributions with and without featured snippets, showing t-test and Wilcoxon p-values\" class=\"wp-image-4391\" srcset=\"https:\/\/www.gironi.it\/blog\/wp-content\/uploads\/2026\/07\/wilcoxon-distribuzioni.png 1125w, https:\/\/www.gironi.it\/blog\/wp-content\/uploads\/2026\/07\/wilcoxon-distribuzioni-300x180.png 300w, https:\/\/www.gironi.it\/blog\/wp-content\/uploads\/2026\/07\/wilcoxon-distribuzioni-1024x614.png 1024w\" sizes=\"auto, (max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 62vw, 840px\" \/><figcaption class=\"wp-element-caption\">Two skewed session duration distributions: the t-test may fail to detect the difference, Wilcoxon does not.<\/figcaption><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">The reason Wilcoxon works better in this scenario lies in the <strong>ranking<\/strong> mechanism. Instead of comparing means (which are sensitive to extreme values), it transforms each value into its rank within the union of the two groups and compares the sums of ranks.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1050\" height=\"525\" src=\"https:\/\/www.gironi.it\/blog\/wp-content\/uploads\/2026\/07\/wilcoxon-ranghi.png\" alt=\"Conversion of raw values to ranks for the Wilcoxon test\" class=\"wp-image-4392\" srcset=\"https:\/\/www.gironi.it\/blog\/wp-content\/uploads\/2026\/07\/wilcoxon-ranghi.png 1050w, https:\/\/www.gironi.it\/blog\/wp-content\/uploads\/2026\/07\/wilcoxon-ranghi-300x150.png 300w, https:\/\/www.gironi.it\/blog\/wp-content\/uploads\/2026\/07\/wilcoxon-ranghi-1024x512.png 1024w\" sizes=\"auto, (max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 62vw, 840px\" \/><figcaption class=\"wp-element-caption\">Raw values are sorted and converted to ranks: sums of ranks are compared, not original values.<\/figcaption><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">After converting to ranks, we lose information about the distance between values, but we gain robustness: extreme values no longer distort the result.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Wilcoxon Test in R<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Let&#8217;s compute Wilcoxon on the same data from the figure:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>set.seed(20260718)\n# Session duration in seconds, two groups\nwith_snippet &lt;- rlnorm(40, meanlog = 4.8, sdlog = 0.7)\nwithout_snippet &lt;- rlnorm(40, meanlog = 5.2, sdlog = 0.7)\n\n# Wilcoxon-Mann-Whitney test\nwilcox.test(with_snippet, without_snippet)<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The output returns the W statistic (the sum of ranks of the first group) and the p-value. If the p-value is below 0.05, we can conclude that the difference between the two groups is statistically significant.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For paired samples (same pages before and after an intervention), use the <strong>signed-rank<\/strong> variant:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Same pages, before and after optimization\nbefore &lt;- rlnorm(30, meanlog = 4.5, sdlog = 0.6)\nafter &lt;- before + rnorm(30, mean = 15, sd = 25)\n# some values may be negative (decreased sessions)\n\n# Wilcoxon for paired samples\nwilcox.test(before, after, paired = TRUE)<\/code><\/pre>\n\n\n\n<p class=\"has-light-gray-background-color has-background wp-block-paragraph\"><strong>A common mistake<\/strong>: thinking that Wilcoxon compares medians. It doesn&#8217;t. Wilcoxon tests whether the distributions of the two groups are stochastically shifted relative to each other, not whether their medians differ. In practice, if the two distributions have similar shapes, it behaves like a median test \u2014 but it&#8217;s not the same thing. A second mistake: using Wilcoxon even when the data is normal. In that case the t-test has more statistical power (it can detect smaller differences). The rule is: <strong>normal \u2192 t-test; non-normal \u2192 Wilcoxon<\/strong>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Try It Yourself<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Here are session data for two groups of pages (Group A: pages with optimized titles; Group B: pages without).<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>group_A &lt;- c(45, 132, 28, 67, 312, 54, 89, 43, 156, 78,\n             92, 34, 201, 67, 88, 43, 156, 67, 234, 55)\ngroup_B &lt;- c(67, 156, 89, 123, 445, 98, 134, 67, 189, 102,\n             145, 56, 267, 89, 123, 78, 198, 89, 312, 78)\n\n# Question 1: run the Wilcoxon test. What does the p-value suggest?<br># Question 2: also run a t-test. Do the two tests agree?<br># Question 3: what happens if you add an extreme outlier to group_A (value 5000)?<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Solution:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>wilcox.test(group_A, group_B)\nt.test(group_A, group_B)\n\n# With outlier\ngroup_A_out &lt;- c(group_A, 5000)\nwilcox.test(group_A_out, group_B)\nt.test(group_A_out, group_B)<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">You&#8217;ll notice that the t-test changes drastically with the outlier, while Wilcoxon remains much more stable: that&#8217;s the power of ranks.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<p class=\"wp-block-paragraph\">Wilcoxon is the first line of defense when data is non-normal: it works with small samples, resists outliers, and in R it&#8217;s just one line of code. But it has a limitation: it only compares two groups. When we have more than two groups and the data is still non-normal, we need an extension \u2014 the <strong>non-parametric analysis of variance<\/strong>. We&#8217;ll cover that in the next article.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h3 class=\"wp-block-heading\">Further Reading<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Non-parametric tests, Wilcoxon included, are covered systematically in <a href=\"https:\/\/www.amazon.it\/dp\/8891910651?tag=consulenzeinf-21\" rel=\"nofollow sponsored noopener\" target=\"_blank\"><em>Statistica<\/em><\/a> by Newbold, Carlson and Thorne (Italian edition), with the conditions that guide the choice between parametric and non-parametric alternatives. For a more hands-on approach with R, <a href=\"https:\/\/www.amazon.it\/dp\/8848141571?tag=consulenzeinf-21\" rel=\"nofollow sponsored noopener\" target=\"_blank\"><em>Analisi dei dati con R<\/em><\/a> by D&#8217;Orazio is an excellent reference with ready-to-use examples.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Imagine comparing session durations between two groups of pages: those with a featured snippet and those without. You run a Student&#8217;s t-test and get a p-value of 0.08. Not significant, apparently. But looking at the data, you notice the distributions are heavily skewed: a few pages with very long sessions, many with very short ones. &hellip; <a href=\"https:\/\/www.gironi.it\/blog\/en\/non-parametric-tests-the-wilcoxon-test-for-non-normal-data\/\" class=\"more-link\">Leggi tutto<span class=\"screen-reader-text\"> &#8220;Non-Parametric Tests: The Wilcoxon Test for Non-Normal Data (with R Examples)&#8221;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_uag_custom_page_level_css":"","footnotes":""},"categories":[161],"tags":[1208,301],"class_list":["post-3292","post","type-post","status-publish","format-standard","hentry","category-statistics","tag-non-parametric-test","tag-wilcoxon"],"lang":"en","translations":{"en":3292,"it":2655},"uagb_featured_image_src":{"full":false,"thumbnail":false,"medium":false,"medium_large":false,"large":false,"1536x1536":false,"2048x2048":false,"post-thumbnail":false},"uagb_author_info":{"display_name":"paolo","author_link":"https:\/\/www.gironi.it\/blog\/author\/paolo\/"},"uagb_comment_info":246,"uagb_excerpt":"Imagine comparing session durations between two groups of pages: those with a featured snippet and those without. You run a Student&#8217;s t-test and get a p-value of 0.08. Not significant, apparently. But looking at the data, you notice the distributions are heavily skewed: a few pages with very long sessions, many with very short ones.&hellip;","_links":{"self":[{"href":"https:\/\/www.gironi.it\/blog\/wp-json\/wp\/v2\/posts\/3292","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.gironi.it\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.gironi.it\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.gironi.it\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.gironi.it\/blog\/wp-json\/wp\/v2\/comments?post=3292"}],"version-history":[{"count":5,"href":"https:\/\/www.gironi.it\/blog\/wp-json\/wp\/v2\/posts\/3292\/revisions"}],"predecessor-version":[{"id":4394,"href":"https:\/\/www.gironi.it\/blog\/wp-json\/wp\/v2\/posts\/3292\/revisions\/4394"}],"wp:attachment":[{"href":"https:\/\/www.gironi.it\/blog\/wp-json\/wp\/v2\/media?parent=3292"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.gironi.it\/blog\/wp-json\/wp\/v2\/categories?post=3292"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.gironi.it\/blog\/wp-json\/wp\/v2\/tags?post=3292"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}