R: R Descriptive Statistics

Last updated: 2026-08-26

In the previous 22 lessons, we learned about R’s “tools”—vectors, data frames, and ggplot2. Now we’ll move on to R’s true “value”—statistical analysis. In this lesson, we’ll start with the most basic concept: “descriptive statistics.” We’ll use R to calculate key metrics such as the mean, median, and standard deviation, and “summarize” the data into a few numbers.

After completing this lesson, you'll be able to generate a "statistical report" for any dataset using just three lines of R code.

1. What You'll Learn



2. The Story of a Student’s Report Card

(1) Challenge: How do you "summarize" 1,000 students?

At the end of the semester, Professor Bob needs to generate grade reports for 1,000 students. Each student is taking 5 courses, and for each course, he needs to calculate:

Manual calculations? Excel formulas? That takes 2 hours; using R summary()——

(2) Solution using R

R
# 1. One line summary shows 5 key metrics
summary(scores$math)
#   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
#   45.0    72.0    82.0    81.5    92.0    99.0

# 2. One line skimr View the full report
library(skimr)
skim(scores$math)
# ── Variable type: numeric ──
#   min  max mean sd   p25  p50  p75  hist
#   45  99  81.5 12  72  82  92  ▇▇▇▇▇

# 3. One line dplyr Grouped Statistics
scores |> group_by(class) |> summarise(
  Average Score = mean(math),
  Median = median(math),
  Standard Deviation = sd(math),
  Highest = max(math),
  Lowest = min(math),
  Number of people = n()
)

3 lines of code → a complete grade report.



3. Three Major Categories of Descriptive Statistics

(1) Overview of the Three Major Categories

100%
graph TB
    A[Descriptive Statistics] --> B[Central Tendency<br/>Central Tendency]
    A --> C[Degree of dispersion<br/>Dispersion]
    A --> D[Distribution Patterns<br/>Shape]
    B --> E[mean Median mode]
    C --> F[sd var range IQR]
    D --> G[Skewness skewness<br/>Fengdu kurtosis]
    
    style A fill:#fff3cd
    style B fill:#d4edda
    style C fill:#cce5ff
    style D fill:#f8d7da

(2) Quick Reference Table

Category Function Meaning
Central Tendency mean() Arithmetic Mean
Central Tendency median() Median (50th percentile)
Centralized mode() Mode (requires DescTools package)
Discrete sd() Standard Deviation
Discrete var() Variance
Discrete range() Extreme Values (min/max)
Discrete IQR() Interquartile Range (Q3-Q1)
Discrete mad() Median Absolute Deviation
Distribution skewness() Skewness (requires e1071)
Distribution kurtosis() Kurtosis (requires e1071)


4. Central Tendency

(1) mean() Mean

R
x <- c(85, 90, 78, 92, 88)

mean(x)              # [1] 86.6
mean(x, na.rm = TRUE) # Skip NA
mean(x, trim = 0.1)   # Truncated Mean (Remove the top and bottom 10%)
⚠️ Note: The mean is highly susceptible to outliers. If the data contains a single outlier of 10,000, the mean will be skewed upward. Use the median for skewed distributions.

(2) median() Median

R
median(c(85, 90, 78, 92, 88))  # [1] 88
median(c(85, 90, 78, 92, 10000))  # [1] 90  ← Not affected by 10000 Impact
💡 Tip: The median is more robust than the mean. In real-world projects, give priority to the median (especially for skewed distributions such as income and housing prices).

(3) Mode (Special Functions)

R
# R does not have a built-in mode(), use the DescTools package
install.packages("DescTools")
library(DescTools)

Mode(c(1, 2, 2, 3, 3, 3, 4))  # [1] 3


5. Degree of Dispersion

(1) sd() / var() Standard Deviation / Variance

R
x <- c(85, 90, 78, 92, 88)

sd(x)    # [1] 5.32  ← Standard Deviation
var(x)   # [1] 28.3  <- Variance (sd^2)

Standard Deviation = the "average distance" of data points from the mean. The larger the value, the more scattered the data is.

(2) Extreme Values of range()

R
x <- c(85, 90, 78, 92, 88)

range(x)              # [1] 78 92  ← Minimum and Maximum
diff(range(x))        # [1] 14   ← Range

(3) IQR() Interquartile Range

R
x <- c(85, 90, 78, 92, 88, 70, 95, 65, 88, 92)

IQR(x)                # [1] 11.5  ← Q3 - Q1
quantile(x, c(0.25, 0.5, 0.75))
#   25%   50%   75% 
# 78.25  88.0  89.75
💡 Tip: The IQR is a robust discrete measure—it is not affected by outliers and is often used to identify anomalies (Q1 - 1.5*IQR ~ Q3 + 1.5*IQR).

(4) Absolute deviation of the median in mad()

R
mad(c(85, 90, 78, 92, 10000))  # Extremely Robust Discrete Measures
# [1] 4.45


6. Quantiles

(1) quantile() Quantile

R
x <- 1:100

# Default 0%, 25%, 50%, 75%, 100%
quantile(x)
#   0%  25%  50%  75% 100% 
#  1.0 25.75 50.5 75.25 100.0

# Custom Percentiles
quantile(x, probs = c(0.1, 0.5, 0.9))
#  10%  50%  90% 
# 10.9 50.5 90.1

(2) Common Percentiles

Percentile Function Meaning
Q0 (0%) min(x) Minimum
Q1 (25%) quantile(x, 0.25) Lower quartile
Q2 (50%) median(x) Median
Q3 (75%) quantile(x, 0.75) Upper quartile
Q4 (100%) max(x) Maximum


7. summary()—A Single Line of Summary Statistics

R
x <- c(85, 90, 78, 92, 88, NA, 75, 95)

summary(x)
#    Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
#   75.00   81.50   88.00   87.62   90.50   95.00       1

6 Key Metrics + NA Count—All in One Line!

R
# Data Frame
df <- data.frame(
  age = c(20, 25, 30, 35),
  score = c(85, 90, 78, 92)
)
summary(df)
#       age            score     
#  Min.   :20.00   Min.   :78.00  
#  1st Qu.:23.75   1st Qu.:83.25  
#  Median :27.50   Median :87.50  
#  Mean   :27.50   Mean   :86.25  
#  3rd Qu.:31.25   3rd Qu.:90.50  
#  Max.   :35.00   Max.   :92.00


8. skimr: Advanced Comprehensive Statistics

R
install.packages("skimr")
library(skimr)

x <- c(85, 90, 78, 92, 88, 70, 95, 65, 88, 92)

skim(x)
# ── Data Summary ────────────────────────
# Values                           x
# Number of rows                   10
# Number of distinct                8
# Mean                            84.2
# Standard deviation             10.13
# Min                              65
# Max                              95
# Median                          87.5
# ... (More Metrics)

skim() Provides over 20 metrics—10 times more detailed than summary().



9. Descriptive Statistics by Group

(1) dplyr + group_by + summarise

R
library(dplyr)

# Simulation 3 Cls × 5 Student Grades
scores <- tibble(
  class = rep(c("1Cls", "2Cls", "3Cls"), each = 5),
  student = paste0("S", 1:15),
  math = c(85, 78, 92, 65, 88,    # 1 Cls
           90, 75, 80, 95, 70,    # 2 Cls
           82, 88, 76, 91, 85),   # 3 Cls
  english = c(78, 85, 88, 70, 92,
              82, 80, 90, 88, 75,
              88, 90, 78, 92, 85)
)

# Class Statistics
class_stats <- scores |>
  group_by(class) |>
  summarise(
    Number of people = n(),
    Mathematical Average = mean(math),
    Median in Mathematics = median(math),
    Mathematical Standard Deviation = sd(math),
    Top in Math = max(math),
    Lowest in Math = min(math),
    MathematicsIQR = IQR(math),
    Average English Score = mean(english)
  )

print(class_stats)
# A tibble: 3 × 9
#   class  Number of people Mathematical Average Median in Mathematics Mathematical Standard Deviation Top in Math Lowest in Math MathematicsIQR Average English Score
#   <chr> <int>    <dbl>    <dbl>      <dbl>    <dbl>    <dbl>   <dbl>    <dbl>
# 1 1Cls       5     81.6       85       11.4       92       65    17       82.6
# 2 2Cls       5     82        80        10.5       95       70    20       83
# 3 3Cls       5     84.4       85        6.02      91       76    12      86.6

(2) Multi-Indicator Breakdown (across)

R
# Calculate statistics for all numeric columns at once
scores |>
  group_by(class) |>
  summarise(across(where(is.numeric), list(
    mean = mean,
    sd = sd,
    median = median
  )))

(3) Old syntax for tapply (for reference)

R
# Use tapply
tapply(scores$math, scores$class, mean)
# 1Cls 2Cls 3Cls 
# 81.6 82.0 84.4


10. Hands-On Exercise: Comprehensive Grade Report for 1,000 Students

Below is an example of a complete workflow that ties together all the descriptive statistics covered in this lesson.

▶ Example: Comprehensive Report on 1,000 Students and 5 Courses

R 📖 Display only
# ============================================
# 1000 Student 5 Course Comprehensive Grade Report
# Features: Complete Descriptive Statistics + Grouping + Ranking
# ============================================

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

# 1. Prepare data
set.seed(42)
n <- 1000
students <- tibble(
  id = 1:n,
  class = sample(c("1Cls", "2Cls", "3Cls", "4Cls", "5Cls"), n, replace = TRUE),
  gender = sample(c("M", "F"), n, replace = TRUE),
  math = round(rnorm(n, 80, 12)),
  english = round(rnorm(n, 78, 15)),
  physics = round(rnorm(n, 75, 14)),
  chemistry = round(rnorm(n, 76, 13)),
  biology = round(rnorm(n, 80, 10))
)

cat("=== Data Volume:", nrow(students), "rows ===\n")

# 2. University-wide Comprehensive Statistics
cat("\n=== University-wide Comprehensive Statistics ===\n")
overall <- students |>
  summarise(across(c(math, english, physics, chemistry, biology),
                   list(mean = mean, sd = sd, median = median),
                   .names = "{.col}_{.fn}"))
print(overall)

# 3. Statistics by Class
cat("\n=== Math Statistics for Each Class ===\n")
class_stats <- students |>
  group_by(class) |>
  summarise(
    Number of people = n(),
    Average = round(mean(math), 2),
    Median = median(math),
    Standard Deviation = round(sd(math), 2),
    Highest = max(math),
    Lowest = min(math),
    Q1 = quantile(math, 0.25),
    Q3 = quantile(math, 0.75),
    IQR = round(IQR(math), 2)
  ) |>
  arrange(desc(Average))
print(class_stats)

# 4. Grouped by gender
cat("\n=== All genders 5 Class Average ===\n")
gender_stats <- students |>
  group_by(gender) |>
  summarise(across(c(math, english, physics, chemistry, biology),
                   mean, .names = "{.col}_Average"))
print(gender_stats)

# 5. Each Student's Total Score and Rank
cat("\n=== Top 10 Students (Total Score) ===\n")
students <- students |>
  mutate(total = math + english + physics + chemistry + biology,
         average = round(total / 5, 2))

top10 <- students |>
  arrange(desc(total)) |>
  head(10) |>
  select(id, class, gender, total, average)
print(top10)

# 6. Identify the outliers (IQR method)
cat("\n=== Students with Outliers in Math Scores ====\n")
math_q1 <- quantile(students$math, 0.25)
math_q3 <- quantile(students$math, 0.75)
math_iqr <- IQR(students$math)
lower <- math_q1 - 1.5 * math_iqr
upper <- math_q3 + 1.5 * math_iqr

outliers <- students |>
  filter(math < lower | math > upper) |>
  select(id, class, math)

cat("Normal Range:", lower, "-", upper, "\n")
cat("Number of outliers:", nrow(outliers), "\n")
print(head(outliers, 5))

# 7. Use summary() to view by subject
cat("\n=== Mathematics summary ===\n")
print(summary(students$math))

# 8. Use skimr for advanced report
library(skimr)
cat("\n=== skimr Report ===\n")
print(skim(students |> select(math, english, physics)))

# 9. Write a report
write_csv(students, "student_report.csv")
write_csv(class_stats, "class_stats.csv")
cat("\n=== The report has been generated ===\n")
73 logic lines (exceeds 40-line limit, display only)

Expected Output (Excerpt):

TEXT 📖 Display only
=== Math Statistics for Each Class ===
# A tibble: 5 × 9
  class  Number of people Average Median Standard Deviation Highest Lowest    Q1    Q3   IQR
  <chr> <int> <dbl> <dbl>  <dbl> <int> <int> <dbl> <dbl> <dbl>
1 5Cls    201  81.0    81   12.0   114    44  72    90    18
2 1Cls    195  80.6    81   11.6   115    47  73    88    15
3 3Cls    203  79.8    80   11.8   110    49  71    88    17
4 2Cls    201  79.4    80   12.1   109    44  70    88    18
5 4Cls    200  79.1    79   12.4   116    45  70    89    19

❓ FAQ

Q Mean or median?
A Use the mean for symmetrical data and the median for skewed data. The mean is generally used by default, but the median is used for skewed data such as income, housing prices, and wait times.
Q sd or var?
A Use sd consistently for all units
Q Which should I choose, summary() or skim()?
A summary is concise (6 metrics), while skim is detailed (20+ metrics).
Q How should NA be handled?
A Add na.rm = TRUE to all functions to skip NA. Otherwise, the results for vectors containing NA will all be NA.
Q How is the IQR used to identify outliers?
A Standard definition: Outliers = < Q1 - 1.5×IQR or > Q3 + 1.5×IQR. This method is more robust than the standard deviation rule (as it does not assume a normal distribution).
Q How do you calculate skewness and kurtosis?
A Use e1071::skewness() and e1071::kurtosis(). If skewness is > 0, the distribution is right-skewed; if < 0, it is left-skewed. If kurtosis is > 0, the distribution is more peaked than a normal distribution; if < 0, it is flatter.

📖 Summary


📝 Exercises

  1. Basic Problem: Construct a vector x <- c(85, 90, 78, 92, 88, NA, 75, 95), compute the values using mean(), median(), sd(), and var() (note na.rm = TRUE), and verify the 8 results.

  2. Basic Exercise: Use summary() and quantile(x, c(0, 0.25, 0.5, 0.75, 1)) to calculate the statistical metrics for the same vector, and compare the two results.

  3. Basic Problem: Create a data frame (3 classes × 5 students × 2 subjects: Math and English), and use dplyr::group_by + summarise + across to calculate the mean and standard deviation for all subjects in all classes in a single operation.

  4. Advanced Exercise: Simulate the grades of 1,000 students in 5 courses, use skimr::skim() to generate a complete report, identify outliers in math scores (using the IQR method), and calculate the percentage of students with outlier scores.

  5. Challenge: Complete workflow—simulate the grades for 1,000 students across 5 courses: ① Overall description ② Description grouped by class ③ Description grouped by gender ④ Ranking ⑤ Outlier detection ⑥ Generate a CSV report + a Markdown text report (use knitr::kable() to render tables). Save screenshots of the process.

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏