shapiroWilkFlowchart = {
// Canvas setup with INCREASED WIDTH
const width = 1400;
const height = 500;
const padding = 60;
// Create SVG with explicit viewBox
const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [0, 0, width, height])
.attr("style", "max-width: 100%; height: auto; font: 16px sans-serif;");
// Add title at the bottom
svg.append("text")
.attr("x", width / 2)
.attr("y", height - 20)
.attr("text-anchor", "middle")
.attr("font-size", "20px")
.attr("font-weight", "bold")
.text("Shapiro-Wilk Normality Test Procedure");
// Define nodes with adjusted layout to match image and MORE SPACE
const nodes = [
// Input data
{id: "A", label: "Sample Data", x: padding + 90, y: 120},
// Initial step
{id: "B", label: "Order Values\nx₍₁₎ ≤ x₍₂₎ ≤ ... ≤ x₍ₙ₎", x: padding + 295, y: 120},
// Formula components - top branch
{id: "C", label: "Calculate\nSample Mean (x̄)", x: padding + 455, y: 45},
{id: "F", label: "Calculate Denominator\nΣ(xᵢ - x̄)²", x: padding + 655, y: 45},
// Formula components - bottom branch
{id: "D", label: "Calculate Weights\n(aᵢ)", x: padding + 455, y: 195},
{id: "E", label: "Calculate Numerator\n(Σaᵢx₍ᵢ₎)²", x: padding + 655, y: 195},
// W statistic
{id: "G", label: "Calculate W Statistic\nW = numerator/denominator", x: padding + 835, y: 120},
// Final path - top and bottom branches - WITH MORE SPACE
{id: "H", label: "Interpret W\n(0 to 1 scale)", x: padding + 1020, y: 45},
{id: "I", label: "Determine\np-value", x: padding + 1020, y: 175},
// Decision node - MOVED FURTHER RIGHT
{id: "J", label: "p-value < 0.05?", x: padding + 1220, y: 120, isDecision: true},
// Decision outcomes - ADJUSTED position (moved left outcome more left)
{id: "K", label: "Reject null hypothesis\n(data is not normal)", x: padding + 930, y: 340}, // Moved further left
{id: "L", label: "Fail to reject null hypothesis\n(data may be normal)", x: padding + 1240, y: 340} // Moved further right for more space
];
// Define edges with draw order
const edges = [
// Initial path
{source: "A", target: "B", label: "", order: 1},
// Divergent paths from ordered data
{source: "B", target: "C", label: "", order: 1},
{source: "B", target: "D", label: "", order: 1},
// Formula component paths
{source: "C", target: "F", label: "", order: 1},
{source: "D", target: "E", label: "", order: 1},
// W statistic calculation
{source: "F", target: "G", label: "", order: 1},
{source: "E", target: "G", label: "", order: 1},
// Interpretation and p-value
{source: "G", target: "H", label: "", order: 1},
{source: "G", target: "I", label: "", order: 1},
// To decision point
{source: "H", target: "J", label: "", order: 1},
{source: "I", target: "J", label: "", order: 1},
// Final decision outcomes
{source: "J", target: "K", label: "Yes", order: 2},
{source: "J", target: "L", label: "No", order: 2}
];
// Sort edges by order
edges.sort((a, b) => a.order - b.order);
// Define arrow marker with IMPROVED visibility
svg.append("defs").append("marker")
.attr("id", "arrowhead")
.attr("viewBox", "0 0 10 10")
.attr("refX", 9) // Adjusted to make arrowhead more visible
.attr("refY", 5)
.attr("markerWidth", 9) // Slightly larger
.attr("markerHeight", 9) // Slightly larger
.attr("orient", "auto")
.append("path")
.attr("d", "M 0 0 L 10 5 L 0 10 z")
.attr("fill", "#444"); // Darker for better visibility
// Draw edges with improved path calculation to avoid overlaps
const edgeLines = svg.selectAll("path.edge")
.data(edges)
.join("path")
.attr("class", d => `edge order-${d.order}`)
.attr("d", d => {
const source = nodes.find(n => n.id === d.source);
const target = nodes.find(n => n.id === d.target);
// Calculate connector points
let sourceX, sourceY, targetX, targetY;
let path = "";
// DECISION DIAMOND PATHS - IMPROVED for visibility
if (source.isDecision) {
if (d.label === "Yes") {
// Going down from decision diamond to "Reject null hypothesis"
sourceX = source.x - 20; // Offset to match image
sourceY = source.y + 30;
targetX = target.x;
targetY = target.y - 25;
// Create direct vertical path
path = `M${sourceX},${sourceY} L${sourceX},${targetY - 20} L${targetX},${targetY}`;
} else if (d.label === "No") {
// Going right and down to "Fail to reject null hypothesis"
sourceX = source.x + 30;
sourceY = source.y;
targetX = target.x;
targetY = target.y - 25;
// Create path matching screenshot
path = `M${sourceX},${sourceY} L${sourceX + 60},${sourceY} L${sourceX + 60},${targetY - 20} L${targetX},${targetY}`;
}
}
// INTERPRET W TO DECISION - ADJUSTED FOR MORE SPACE
else if (source.id === "H" && target.id === "J") {
sourceX = source.x + 80;
sourceY = source.y;
targetX = target.x - 60;
targetY = target.y;
// Create angled path going down as in screenshot
path = `M${sourceX},${sourceY} L${sourceX + 40},${sourceY} L${sourceX + 40},${targetY - 20} L${targetX},${targetY}`;
}
// P-VALUE TO DECISION - ADJUSTED FOR MORE SPACE
else if (source.id === "I" && target.id === "J") {
sourceX = source.x + 80;
sourceY = source.y;
targetX = target.x - 60;
targetY = target.y;
// Create angled path going up as in screenshot
path = `M${sourceX},${sourceY} L${sourceX + 40},${sourceY} L${sourceX + 40},${targetY + 20} L${targetX},${targetY}`;
}
// SAMPLE DATA TO ORDER VALUES
else if (source.id === "A" && target.id === "B") {
sourceX = source.x + 80;
sourceY = source.y;
targetX = target.x - 80;
targetY = target.y;
path = `M${sourceX},${sourceY} L${targetX},${targetY}`;
}
// ORDERED VALUES TO MEAN (up)
else if (source.id === "B" && target.id === "C") {
sourceX = source.x;
sourceY = source.y - 25;
targetX = target.x - 80;
targetY = target.y;
// Create angled path going up first
path = `M${sourceX},${sourceY} L${sourceX},${targetY} L${targetX},${targetY}`;
}
// ORDERED VALUES TO WEIGHTS (down)
else if (source.id === "B" && target.id === "D") {
sourceX = source.x;
sourceY = source.y + 25;
targetX = target.x - 80;
targetY = target.y;
// Create angled path going down first
path = `M${sourceX},${sourceY} L${sourceX},${targetY} L${targetX},${targetY}`;
}
// MEAN TO DENOMINATOR
else if (source.id === "C" && target.id === "F") {
sourceX = source.x + 80;
sourceY = source.y;
targetX = target.x - 80;
targetY = target.y;
path = `M${sourceX},${sourceY} L${targetX},${targetY}`;
}
// WEIGHTS TO NUMERATOR
else if (source.id === "D" && target.id === "E") {
sourceX = source.x + 80;
sourceY = source.y;
targetX = target.x - 80;
targetY = target.y;
path = `M${sourceX},${sourceY} L${targetX},${targetY}`;
}
// DENOMINATOR TO W STATISTIC
else if (source.id === "F" && target.id === "G") {
sourceX = source.x + 80;
sourceY = source.y;
targetX = target.x - 80;
targetY = target.y - 15;
// Create smooth curved path
path = `M${sourceX},${sourceY} C${sourceX + 30},${sourceY} ${targetX - 50},${targetY - 20} ${targetX},${targetY}`;
}
// NUMERATOR TO W STATISTIC
else if (source.id === "E" && target.id === "G") {
sourceX = source.x + 80;
sourceY = source.y;
targetX = target.x - 80;
targetY = target.y + 15;
// Create smooth curved path
path = `M${sourceX},${sourceY} C${sourceX + 30},${sourceY} ${targetX - 50},${targetY + 20} ${targetX},${targetY}`;
}
// W STATISTIC TO INTERPRET W
else if (source.id === "G" && target.id === "H") {
sourceX = source.x + 80;
sourceY = source.y - 15;
targetX = target.x - 80;
targetY = target.y;
// Create curved path
path = `M${sourceX},${sourceY} C${sourceX + 30},${sourceY} ${targetX - 40},${targetY} ${targetX},${targetY}`;
}
// W STATISTIC TO P-VALUE
else if (source.id === "G" && target.id === "I") {
sourceX = source.x + 80;
sourceY = source.y + 15;
targetX = target.x - 80;
targetY = target.y;
// Create curved path
path = `M${sourceX},${sourceY} C${sourceX + 30},${sourceY} ${targetX - 40},${targetY} ${targetX},${targetY}`;
}
// DEFAULT HORIZONTAL PATH
else {
sourceX = source.x + 80;
sourceY = source.y;
targetX = target.x - 80;
targetY = target.y;
path = `M${sourceX},${sourceY} L${targetX},${targetY}`;
}
return path;
})
.attr("stroke", "#666")
.attr("stroke-width", 2)
.attr("fill", "none")
.attr("marker-end", "url(#arrowhead)");
// Add edge labels for Yes/No - POSITIONED TO MATCH SCREENSHOT
svg.selectAll(".edgelabel")
.data(edges.filter(d => d.label !== ""))
.join("text")
.attr("class", "edgelabel")
.attr("text-anchor", "middle")
.attr("dominant-baseline", "middle")
.attr("x", d => {
const source = nodes.find(n => n.id === d.source);
if (d.label === "Yes") {
return source.x - 20; // Centered on the Yes path
} else if (d.label === "No") {
return source.x + 140; // Positioned on the No path - more space (moved right)
} else {
return source.x + 40;
}
})
.attr("y", d => {
const source = nodes.find(n => n.id === d.source);
if (d.label === "Yes") {
return source.y + 55; // Below the decision diamond
} else if (d.label === "No") {
return source.y; // Right of the decision diamond
} else {
return source.y - 10;
}
})
.attr("font-size", "14px")
.attr("font-weight", "bold")
.attr("fill", d => d.label === "Yes" ? "#5a9bd5" : (d.label === "No" ? "#ff9052" : "#333"))
.text(d => d.label);
// Draw nodes with fixed box sizes - after drawing paths to ensure nodes appear on top
const node = svg.selectAll(".node")
.data(nodes)
.join("g")
.attr("class", "node")
.attr("transform", d => `translate(${d.x},${d.y})`);
// Add node shapes (rectangles or diamonds) with consistent sizing
node.each(function(d) {
const elem = d3.select(this);
if (d.isDecision) {
// Diamond for decision node
elem.append("polygon")
.attr("points", "0,-30 60,0 0,30 -60,0")
.attr("fill", "#f8d56f")
.attr("stroke", "#d4a82e")
.attr("stroke-width", 2);
} else {
// Rectangle for regular node with fixed width
const boxWidth = d.id === "L" ? 180 : 160; // Extra width for "Fail to reject" box
elem.append("rect")
.attr("x", -boxWidth/2)
.attr("y", -25)
.attr("width", boxWidth)
.attr("height", 50)
.attr("rx", 5)
.attr("ry", 5)
.attr("fill", d => {
if (d.id === "K" || d.id === "L") return "#f0f0f0";
if (d.id === "A") return "#e2f0d9"; // Light green for data inputs
return "#b3deff";
})
.attr("stroke", d => {
if (d.id === "K" || d.id === "L") return "#999";
if (d.id === "A") return "#70ad47"; // Green border for data inputs
return "#4a98e0";
})
.attr("stroke-width", 2);
}
});
// Add node labels with better text wrapping
node.append("text")
.attr("text-anchor", "middle")
.attr("dominant-baseline", "middle")
.attr("font-size", "14px")
.attr("font-weight", d => (d.id === "J" ? "bold" : "normal"))
.attr("fill", "#333")
.each(function(d) {
const lines = d.label.split('\n');
const elem = d3.select(this);
if (lines.length === 1) {
elem.text(d.label);
} else {
lines.forEach((line, i) => {
const lineHeight = 16;
const yOffset = (i - (lines.length - 1) / 2) * lineHeight;
elem.append("tspan")
.attr("x", 0)
.attr("y", yOffset)
.text(line);
});
}
});
// Add interactivity
node.on("mouseover", function(event, d) {
d3.select(this).select("rect, polygon")
.transition()
.duration(200)
.attr("fill", d => {
if (d.isDecision) return "#ffc107";
if (d.id === "A") return "#b8e986"; // Brighter green on hover
if (d.id === "K" || d.id === "L") return "#e6e6e6";
return "#7fc9ff";
});
})
.on("mouseout", function(event, d) {
d3.select(this).select("rect, polygon")
.transition()
.duration(200)
.attr("fill", d => {
if (d.isDecision) return "#f8d56f";
if (d.id === "K" || d.id === "L") return "#f0f0f0";
if (d.id === "A") return "#e2f0d9";
return "#b3deff";
});
});
return svg.node();
}
Key Takeaways: Shapiro-Wilk Normality Test
Tip
- Purpose: Test whether a dataset follows a normal distribution
- When to use: Before applying statistical methods that assume normality
- Null hypothesis: The data is normally distributed (\(H_0\): data is normal)
- Alternative hypothesis: The data is not normally distributed (\(H_1\): data is not normal)
- Interpretation: If p < 0.05, the data significantly deviates from normality
- Most effective: For sample sizes between 3 and 5000
- Considered: One of the most powerful tests for detecting departures from normality
What is the Shapiro-Wilk Normality Test?
The Shapiro-Wilk test is a statistical test that assesses whether a dataset comes from a normally distributed population. Developed by Samuel Shapiro and Martin Wilk in 1965, it has become one of the most widely used and powerful tests for normality, particularly effective for small to medium-sized samples.
Tip
When to use the Shapiro-Wilk normality test:
- Before applying parametric statistical methods that assume normality
- When evaluating the distribution of residuals in regression analysis
- When checking if a transformation has successfully normalized your data
- As part of the data exploration and validation process
- To decide between parametric and non-parametric statistical approaches
This online calculator allows you to quickly perform a Shapiro-Wilk normality test, visualize your data distribution, and interpret the results with confidence.
#| '!! shinylive warning !!': |
#| shinylive does not work in self-contained HTML documents.
#| Please set `embed-resources: false` in your metadata.
#| standalone: true
#| viewerHeight: 1300
library(shiny)
library(bslib)
library(ggplot2)
library(bsicons)
library(vroom)
library(shinyjs)
ui <- page_sidebar(
title = "Shapiro-Wilk Normality Test",
useShinyjs(), # Enable shinyjs for dynamic UI updates
sidebar = sidebar(
width = 400,
card(
card_header("Data Input"),
accordion(
accordion_panel(
"Manual Input",
textAreaInput("data_input", "Enter your data (one value per row):", rows = 8,
placeholder = "Paste values here..."),
div(
actionLink("use_example", "Use example data", style = "color:#0275d8;"),
tags$span(bs_icon("file-earmark-text"), style = "margin-left: 5px; color: #0275d8;")
)
),
accordion_panel(
"File Upload",
fileInput("file_upload", "Upload CSV or TXT file:",
accept = c("text/csv", "text/plain", ".csv", ".txt")),
checkboxInput("header", "File has header", TRUE),
conditionalPanel(
condition = "output.file_uploaded",
div(
selectInput("selected_var", "Select variable:", choices = NULL),
actionButton("clear_file", "Clear File", class = "btn-danger btn-sm")
)
)
),
id = "input_method",
open = 1
),
# Advanced Options accordion
accordion(
accordion_panel(
"Advanced Options",
card(
card_header("Significance Level:"),
card_body(
sliderInput("alpha", NULL, min = 0.01, max = 0.10, value = 0.05, step = 0.01)
)
),
card(
card_header("Plot Options:"),
card_body(
checkboxInput("show_density", "Show density curve", TRUE),
checkboxInput("show_normal", "Show normal curve", TRUE),
sliderInput("bins", "Number of histogram bins:", min = 5, max = 50, value = 20)
)
)
),
open = FALSE
),
actionButton("run_test", "Run Test", class = "btn btn-primary")
),
hr(),
card(
card_header("Interpretation"),
card_body(
div(class = "alert alert-info",
tags$ul(
tags$li(tags$b("Null hypothesis (H₀):"), " The data is normally distributed."),
tags$li(tags$b("Alternative hypothesis (H₁):"), " The data is not normally distributed."),
tags$li("If p-value ≥ 0.05, there is not enough evidence to reject normality."),
tags$li("If p-value < 0.05, the data significantly deviates from a normal distribution.")
)
)
)
)
),
layout_column_wrap(
width = 1,
card(
card_header("Test Results"),
card_body(
navset_tab(
nav_panel("Results", uiOutput("error_message"), verbatimTextOutput("test_results")),
nav_panel("Explanation", div(style = "font-size: 0.9rem;",
p("The Shapiro-Wilk test is one of the most powerful tests for normality."),
tags$ul(
tags$li("It works by comparing your data's distribution to a normal distribution."),
tags$li("The test statistic W ranges from 0 to 1. Values close to 1 indicate normality."),
tags$li("Best for sample sizes between 3 and 5000. For very large samples, even minor deviations from normality may be detected."),
tags$li("Visual inspection (histograms and Q-Q plots) should always supplement this test.")
)
))
)
)
),
card(
card_header("Visual Assessment"),
card_body(
navset_tab(
nav_panel("Histogram",
navset_tab(
nav_panel("Plot", plotOutput("histogram")),
nav_panel("Explanation", div(style = "font-size: 0.9rem;",
p("The histogram helps visualize the shape of your data distribution:"),
tags$ul(
tags$li("For normal data, the histogram should appear approximately bell-shaped and symmetric."),
tags$li("The red curve shows the kernel density estimate of your data."),
tags$li("The blue dashed curve shows a normal distribution with the same mean and standard deviation as your data."),
tags$li("Compare these curves to assess normality visually.")
)
))
)
),
nav_panel("Q-Q Plot",
navset_tab(
nav_panel("Plot", plotOutput("qqplot")),
nav_panel("Explanation", div(style = "font-size: 0.9rem;",
p("The Q-Q (Quantile-Quantile) plot compares your data's quantiles against theoretical quantiles from a normal distribution:"),
tags$ul(
tags$li("If points closely follow the diagonal reference line, the data is approximately normal."),
tags$li("Systematic deviations from the line indicate non-normality."),
tags$li("Curves at the ends suggest heavy or light tails."),
tags$li("S-shaped patterns indicate skewness.")
)
))
)
)
)
)
)
)
)
server <- function(input, output, session) {
# Example data
example_data <- "8.44\n7.16\n16.94\n9.59\n13.25\n12.94\n11\n5.61\n10.6\n12.81"
# Track input method
input_method <- reactiveVal("manual")
# Function to clear file inputs
clear_file_inputs <- function() {
updateSelectInput(session, "selected_var", choices = NULL)
reset("file_upload")
}
# Function to clear text inputs
clear_text_inputs <- function() {
updateTextAreaInput(session, "data_input", value = "")
}
# When example data is used, clear file inputs and set text inputs
observeEvent(input$use_example, {
input_method("manual")
clear_file_inputs()
updateTextAreaInput(session, "data_input", value = example_data)
})
# When file is uploaded, clear text inputs and set file method
observeEvent(input$file_upload, {
if (!is.null(input$file_upload)) {
input_method("file")
clear_text_inputs()
}
})
# When clear file button is clicked, clear file and set manual method
observeEvent(input$clear_file, {
input_method("manual")
clear_file_inputs()
})
# When text input changes, clear file inputs if it has content
observeEvent(input$data_input, {
if (!is.null(input$data_input) && nchar(input$data_input) > 0) {
input_method("manual")
clear_file_inputs()
}
}, ignoreInit = TRUE)
# Process uploaded file
file_data <- reactive({
req(input$file_upload)
tryCatch({
vroom::vroom(input$file_upload$datapath, delim = NULL, col_names = input$header, show_col_types = FALSE)
}, error = function(e) {
showNotification(paste("File read error:", e$message), type = "error")
NULL
})
})
# Update variable selection dropdown with numeric columns from uploaded file
observe({
df <- file_data()
if (!is.null(df)) {
num_vars <- names(df)[sapply(df, is.numeric)]
updateSelectInput(session, "selected_var", choices = num_vars)
}
})
output$file_uploaded <- reactive({
!is.null(input$file_upload)
})
outputOptions(output, "file_uploaded", suspendWhenHidden = FALSE)
# Function to parse text input
parse_text_input <- function(text) {
if (is.null(text) || text == "") return(NULL)
input_lines <- strsplit(text, "\\r?\\n")[[1]]
input_lines <- input_lines[input_lines != ""]
numeric_values <- suppressWarnings(as.numeric(input_lines))
if (all(is.na(numeric_values))) return(NULL)
return(na.omit(numeric_values))
}
# Get data values based on input method
data_values <- reactive({
if (input_method() == "file" && !is.null(file_data()) && !is.null(input$selected_var)) {
df <- file_data()
return(na.omit(df[[input$selected_var]]))
} else {
return(parse_text_input(input$data_input))
}
})
# Validate input data
validate_data <- reactive({
values <- data_values()
if (is.null(values)) {
return("Error: Please check your input. Make sure all values are numeric.")
}
if (length(values) < 3) {
return("Error: At least 3 values are required for the Shapiro-Wilk test.")
}
if (length(values) > 5000) {
return("Warning: The Shapiro-Wilk test is less reliable for very large samples (n > 5000). Consider using visual inspection methods and other normality tests.")
}
if (length(unique(values)) == 1) {
return("Error: All values are identical. The Shapiro-Wilk test requires variation in the data.")
}
return(NULL)
})
# Display error messages
output$error_message <- renderUI({
error <- validate_data()
if (!is.null(error) && input$run_test > 0) {
if (startsWith(error, "Warning")) {
div(class = "alert alert-warning", error)
} else {
div(class = "alert alert-danger", error)
}
}
})
# Run the Shapiro-Wilk test
test_result <- eventReactive(input$run_test, {
error <- validate_data()
if (!is.null(error) && startsWith(error, "Error")) return(NULL)
values <- data_values()
shapiro.test(values)
})
# Display test results
output$test_results <- renderPrint({
if (is.null(test_result())) return(NULL)
result <- test_result()
values <- data_values()
cat("Shapiro-Wilk Normality Test Results:\n")
cat("====================================\n")
cat("W statistic:", round(result$statistic, 4), "\n")
cat("p-value:", format.pval(result$p.value, digits = 4), "\n\n")
cat("Data Summary:\n")
cat("-------------\n")
cat("Sample size:", length(values), "\n")
cat("Mean:", round(mean(values), 4), "\n")
cat("Median:", round(median(values), 4), "\n")
cat("Standard deviation:", round(sd(values), 4), "\n")
cat("Skewness:", round(e1071::skewness(values), 4), "\n")
cat("Kurtosis:", round(e1071::kurtosis(values), 4), "\n\n")
cat("Test Interpretation:\n")
cat("--------------------\n")
if (result$p.value < input$alpha) {
cat("The p-value (", format.pval(result$p.value, digits = 4),
") is less than the significance level (", input$alpha, ").\n", sep = "")
cat("We reject the null hypothesis. There is significant evidence\n")
cat("to suggest the data does not follow a normal distribution.")
} else {
cat("The p-value (", format.pval(result$p.value, digits = 4),
") is greater than or equal to the significance level (", input$alpha, ").\n", sep = "")
cat("We fail to reject the null hypothesis. There is not enough evidence\n")
cat("to suggest the data deviates from a normal distribution.")
}
})
# Generate histogram
output$histogram <- renderPlot({
req(input$run_test > 0)
error <- validate_data()
if (!is.null(error) && startsWith(error, "Error")) return(NULL)
values <- data_values()
p <- ggplot(data.frame(x = values), aes(x = x)) +
geom_histogram(aes(y = ..density..), bins = input$bins,
fill = "#5dade2", color = "#2874a6", alpha = 0.7) +
labs(title = "Distribution of Data",
subtitle = paste("Shapiro-Wilk test: W =", round(test_result()$statistic, 4),
", p =", format.pval(test_result()$p.value, digits = 4)),
x = "Value", y = "Density") +
theme_minimal(base_size = 14)
if (input$show_density) {
p <- p + geom_density(color = "#c0392b", linewidth = 1.2)
}
if (input$show_normal) {
p <- p + stat_function(fun = dnorm, args = list(mean = mean(values), sd = sd(values)),
color = "#2471a3", linewidth = 1.2, linetype = "dashed")
}
p
})
# Generate Q-Q plot
output$qqplot <- renderPlot({
req(input$run_test > 0)
error <- validate_data()
if (!is.null(error) && startsWith(error, "Error")) return(NULL)
values <- data_values()
ggplot(data.frame(x = values), aes(sample = x)) +
stat_qq() +
stat_qq_line(color = "#c0392b") +
labs(title = "Normal Q-Q Plot",
subtitle = paste("Shapiro-Wilk test: W =", round(test_result()$statistic, 4),
", p =", format.pval(test_result()$p.value, digits = 4)),
x = "Theoretical Quantiles",
y = "Sample Quantiles") +
theme_minimal(base_size = 14)
})
}
shinyApp(ui = ui, server = server)
How the Shapiro-Wilk Test Works
The Shapiro-Wilk test evaluates the correlation between your data and the corresponding normal scores. It compares the ordered sample values with the expected ordered values from a normal distribution.
Mathematical Procedure
Order the data: Sort the \(n\) observations from smallest to largest: \(x_{(1)} \leq x_{(2)} \leq ... \leq x_{(n)}\)
Calculate the test statistic \(W\):
\[W = \frac{(\sum_{i=1}^{n} a_i x_{(i)})^2}{\sum_{i=1}^{n} (x_i - \bar{x})^2}\]
Where:
- \(x_{(i)}\) are the ordered sample values
- \(a_i\) are weights derived from the means, variances, and covariances of the order statistics from a normal distribution
- \(\bar{x}\) is the sample mean
Interpret the \(W\) statistic:
- \(W\) ranges from 0 to 1
- Values close to 1 indicate normality
- Smaller values indicate departure from normality
Calculate p-value by comparing the \(W\) statistic to its sampling distribution
Make a decision:
- If p < \(\alpha\) (typically 0.05): Reject the null hypothesis (data is not normal)
- If p ≥ \(\alpha\): Fail to reject the null hypothesis (data may be normal)
Shapiro-Wilk vs. Other Normality Tests
The Shapiro-Wilk test is generally considered superior to many other normality tests:
Test | Strengths | Limitations |
---|---|---|
Shapiro-Wilk | Most powerful for many distributions; works well for small samples | Complex calculation; maximum sample size limitations (though extended versions exist) |
Kolmogorov-Smirnov | Simple concept; handles large samples | Less powerful; critical values depend on distribution being tested |
Anderson-Darling | Sensitive to deviations in the tails | Complex; less intuitive interpretation |
D’Agostino-Pearson | Based on skewness and kurtosis; handles large samples | Less power for some types of non-normality |
Jarque-Bera | Popular in economics; based on skewness and kurtosis | Requires large samples for good performance |
Important Considerations
- Sample size matters:
- For very small samples (n < 3), the test has very low power
- For very large samples (n > 5000), even minor deviations from normality may be detected as significant
- Most effective for sample sizes between 3 and 5000
- Visual inspection is crucial:
- Always supplement the test with visual methods (histograms, Q-Q plots)
- Statistical significance doesn’t always indicate practical significance
- Transformation options:
- If data is not normal, consider transformations (log, square root, Box-Cox)
- Retest after transformation to verify improvement
- Central Limit Theorem:
- Remember that for large sample sizes, the sampling distribution of the mean approaches normality regardless of the original distribution
- For large samples, non-normality may not be a practical concern for many statistical procedures
Example 1: Testing Normality of Student Exam Scores
An educator wants to determine if the distribution of final exam scores follows a normal distribution before performing parametric analyses.
Data (exam scores out of 100): 78, 85, 92, 65, 70, 88, 76, 84, 90, 64, 71, 68, 82, 79, 91, 75, 83, 77, 86, 73, 80, 89, 66, 72, 81
Analysis Steps:
- Run Shapiro-Wilk test:
- W = 0.9728, p = 0.7156
- Calculate descriptive statistics:
- Mean = 78.92
- Median = 79.00
- Standard deviation = 8.54
- Skewness = -0.0631 (very slight negative skew)
- Kurtosis = -0.7813 (platykurtic - slightly flatter than normal)
- Visual assessment:
- Histogram shows roughly bell-shaped distribution
- Q-Q plot points follow closely along the reference line
- No substantial deviations from normality
Results:
- W = 0.9728, p = 0.7156
- Interpretation: Since p > 0.05, we fail to reject the null hypothesis. There is insufficient evidence to claim that the exam scores deviate from a normal distribution.
How to Report: “The Shapiro-Wilk test was conducted to evaluate the normality of exam scores. Results indicated that the scores (M = 78.92, SD = 8.54) were approximately normally distributed, W(25) = 0.9728, p = 0.716. Visual inspection of the histogram and Q-Q plot confirmed this conclusion.”
Example 2: Checking Normality of Reaction Times
A psychologist wants to check if reaction time data from an experiment follows a normal distribution.
Data Summary:
- Sample size: 30 participants
- Shapiro-Wilk test: W = 0.8651, p = 0.0012
- Mean reaction time: 342 ms
- Median reaction time: 328 ms
- Skewness: 1.25 (positive skew)
Results:
- W = 0.8651, p = 0.0012
- Interpretation: Since p < 0.05, we reject the null hypothesis. There is significant evidence that the reaction time data deviates from a normal distribution.
How to Report: “The Shapiro-Wilk test indicated that reaction times were not normally distributed, W(30) = 0.8651, p = 0.0012. Visual inspection revealed a positive skew (skewness = 1.25), with more observations clustered around faster reaction times and a tail extending toward slower times. Therefore, non-parametric statistical methods would be more appropriate for analyzing this data.”
How to Report Shapiro-Wilk Test Results
When reporting the results of a Shapiro-Wilk test in academic papers or research reports, include the following elements:
[variable]. Results indicated
"The Shapiro-Wilk test was used to assess the normality of [was/was not] normally distributed, W([sample size]) = [W statistic], p = [p-value]." that the data
For example:
"The Shapiro-Wilk test was used to assess the normality of blood pressure measurements. Results indicated that the data was normally distributed, W(45) = 0.976, p = 0.462."
Additional information to consider including:
- Descriptive statistics (mean, median, standard deviation)
- Skewness and kurtosis values
- Brief description of visual assessment (histogram shape, Q-Q plot)
- Any transformations attempted if non-normality was detected
APA Style Reporting
For APA style papers (7th edition), report the Shapiro-Wilk test results as follows:
[variable] was normally distributed.
We conducted a Shapiro-Wilk test to examine whether [variable] [was/was not] approximately normally distributed,
Results indicated that [sample size]) = [W statistic], p = [p-value]. W(
Reporting in Tables
When reporting multiple Shapiro-Wilk test results in a table, include these columns:
- Variable tested
- Sample size
- W statistic
- p-value
- Skewness
- Kurtosis
- Normality conclusion (Yes/No based on significance level)
Test Your Understanding
- What does the Shapiro-Wilk test primarily assess?
- Whether two samples come from the same distribution
- Whether a sample comes from a normal distribution
- Whether a sample has equal variance with another sample
- Whether a sample has outliers
- What is the null hypothesis in the Shapiro-Wilk test?
- The data is not normally distributed
- The data is normally distributed
- The mean of the data equals a specific value
- The variance of the data equals a specific value
- What range of values can the Shapiro-Wilk W statistic take?
- 0 to infinity
- -1 to +1
- 0 to 1
- Negative infinity to positive infinity
- A researcher finds W = 0.92, p = 0.08 when testing a dataset. What can they conclude?
- The data is significantly different from a normal distribution
- The data is significantly normal
- There is not enough evidence to conclude the data deviates from normality
- The sample size is too small for the test to be valid
- Which of the following sample sizes is most appropriate for the Shapiro-Wilk test?
- n = 2
- n = 20
- n = 10,000
- n = 1,000,000
Answers: 1-B, 2-B, 3-C, 4-C, 5-B
Common Questions About the Shapiro-Wilk Test
What sample size is appropriate for the Shapiro-Wilk test?
The Shapiro-Wilk test works best for sample sizes between 3 and 5000. With very small samples (n < 3), the test has low power to detect non-normality. With very large samples (n > 5000), the test becomes overly sensitive and may flag even minor, practically insignificant deviations from normality as statistically significant.
What does it mean if my Shapiro-Wilk test is significant (p < 0.05)?
A significant result (p < 0.05) indicates that your data significantly deviates from a normal distribution. This suggests you should either transform your data to achieve normality or consider using non-parametric statistical methods that don’t assume normality.
What should I do if my data is not normally distributed?
If your data is not normally distributed, you have several options:
- Transform the data using methods like log, square root, or Box-Cox transformations
- Use non-parametric tests that don’t assume normality (e.g., Mann-Whitney U instead of t-test)
- Continue with parametric tests if your sample size is large enough (n > 30) to rely on the Central Limit Theorem
- Use robust statistical methods that are less sensitive to violations of normality
Can I rely solely on the Shapiro-Wilk test to assess normality?
No, it’s best to use a combination of approaches to assess normality. The Shapiro-Wilk test should be supplemented with:
- Visual methods like histograms, boxplots, and Q-Q plots
- Descriptive statistics like skewness and kurtosis
- Practical knowledge of your data and measurement process
With large samples, even minor deviations from normality will be flagged as significant by the test, though they may not be practically important.
Why is the Shapiro-Wilk test considered more powerful than other normality tests?
The Shapiro-Wilk test is considered more powerful because it has higher ability to detect departures from normality across a wide range of non-normal distributions compared to alternatives like Kolmogorov-Smirnov, Lilliefors, or D’Agostino-Pearson tests. It’s particularly sensitive to both skewness and kurtosis issues. Simulation studies have consistently shown it has superior power properties, especially for small to medium sample sizes.
How do outliers affect the Shapiro-Wilk test results?
Outliers can strongly influence the Shapiro-Wilk test, often leading to rejection of normality. Before concluding your data is non-normal, examine it for outliers. If outliers are detected:
- Verify they’re not data entry errors
- Consider if they represent a valid part of the population
- Try running the test with and without outliers to assess their impact
- Consider robust statistical methods designed to handle outliers
Examples of When to Use the Shapiro-Wilk Test
- Before t-tests or ANOVA: To verify the normality assumption before applying these parametric methods
- Regression analysis: To check if residuals are normally distributed
- Time series analysis: To examine if the error terms follow a normal distribution
- Quality control: To verify if process measurements follow a normal distribution
- Financial analysis: To test normality of returns before applying certain models
- Clinical trials: To check distribution of outcome measures
- Educational testing: To examine the distribution of test scores
- Psychological assessment: To validate normality of psychometric measurements
- Environmental monitoring: To analyze distribution of pollution or environmental indicators
- After data transformation: To verify if transformations successfully normalized the data
References
- Shapiro, S. S., & Wilk, M. B. (1965). An analysis of variance test for normality (complete samples). Biometrika, 52(3/4), 591-611.
- Razali, N. M., & Wah, Y. B. (2011). Power comparisons of Shapiro-Wilk, Kolmogorov-Smirnov, Lilliefors and Anderson-Darling tests. Journal of Statistical Modeling and Analytics, 2(1), 21-33.
- Royston, P. (1992). Approximating the Shapiro-Wilk W-test for non-normality. Statistics and Computing, 2(3), 117-119.
- Ghasemi, A., & Zahediasl, S. (2012). Normality tests for statistical analysis: a guide for non-statisticians. International Journal of Endocrinology and Metabolism, 10(2), 486-489.
- Yap, B. W., & Sim, C. H. (2011). Comparisons of various types of normality tests. Journal of Statistical Computation and Simulation, 81(12), 2141-2155.
- Henderson, A. R. (2006). Testing experimental data for univariate normality. Clinica Chimica Acta, 366(1-2), 112-129.
Reuse
Citation
BibTeX citation:
@online{kassambara2025,
author = {Kassambara, Alboukadel},
title = {Shapiro-Wilk {Normality} {Test} {Calculator} \textbar{}
{Check} {If} {Data} {Is} {Normal}},
date = {2025-04-09},
url = {https://www.datanovia.com/apps/statfusion/analysis/inferential/goodness-fit/normality/shapiro-wilk-normality-test.html},
langid = {en}
}
For attribution, please cite this work as:
Kassambara, Alboukadel. 2025. “Shapiro-Wilk Normality Test
Calculator | Check If Data Is Normal.” April 9, 2025. https://www.datanovia.com/apps/statfusion/analysis/inferential/goodness-fit/normality/shapiro-wilk-normality-test.html.