R ggplot2 Histogram
A histogram shows the distribution of a single numeric variable by grouping values into bins and displaying how many observations fall in each bin. Unlike a bar chart (which compares categories), a histogram reveals the shape of your data — whether it is symmetric, skewed, peaked, or spread out.
Basic Histogram
library(ggplot2)
scores <- data.frame(
score = c(45,52,58,60,62,65,67,68,70,72,72,74,75,
76,78,78,80,82,84,85,88,90,92,95,98)
)
ggplot(scores, aes(x=score)) +
geom_histogram(binwidth=10, fill="steelblue", color="white") +
labs(title="Exam Score Distribution",
x="Score", y="Number of Students") +
theme_minimal()
Choosing the Right Number of Bins
# binwidth: width of each bin (in data units) geom_histogram(binwidth=5) # narrow bins — more detail geom_histogram(binwidth=10) # medium bins — balanced geom_histogram(binwidth=20) # wide bins — more smoothed # bins: total number of bins geom_histogram(bins=10) geom_histogram(bins=20) # Sturges rule: bins ≈ log2(n) + 1 (ggplot2 default: 30)
Effect of bin width: binwidth=5 (narrow): ▄▆█▇▆▄▃▂ more detailed, noisier binwidth=10 (medium): ▄██▇▄▂ good balance binwidth=20 (wide): ▄█▆▃ smoother, less detail
Filled by Group (Multiple Distributions)
class_scores <- data.frame(
score = c(45,60,72,68,80,75,88,92, 55,70,65,78,82,90,85,95),
class = rep(c("Class A","Class B"), each=8)
)
ggplot(class_scores, aes(x=score, fill=class)) +
geom_histogram(binwidth=10, alpha=0.6, position="identity") +
scale_fill_manual(values=c("Class A"="steelblue","Class B"="tomato")) +
labs(title="Score Distribution by Class",
x="Score", y="Count", fill="Class") +
theme_minimal()
Density Histogram
# y-axis shows density (area = 1) instead of count
ggplot(scores, aes(x=score)) +
geom_histogram(aes(y=after_stat(density)),
binwidth=10, fill="steelblue", color="white", alpha=0.7) +
geom_density(color="red", linewidth=1.2) + # overlay density curve
labs(title="Score Distribution with Density Curve",
x="Score", y="Density") +
theme_minimal()
Adding Reference Lines
mean_score <- mean(scores$score)
median_score <- median(scores$score)
ggplot(scores, aes(x=score)) +
geom_histogram(binwidth=10, fill="steelblue", color="white", alpha=0.8) +
geom_vline(xintercept=mean_score, color="red", linetype="dashed", linewidth=1) +
geom_vline(xintercept=median_score, color="orange", linetype="dashed", linewidth=1) +
annotate("text", x=mean_score+1, y=5.5, label=paste("Mean:",round(mean_score,1)),
color="red", hjust=0, size=3) +
annotate("text", x=median_score+1, y=5, label=paste("Median:",round(median_score,1)),
color="orange", hjust=0, size=3) +
labs(title="Score Distribution with Mean and Median") +
theme_minimal()
Reading Distribution Shapes
Shape Description What It Means ───────────────────────────────────────────────────────────────────── Bell curve Symmetric, single peak Normal distribution Right-skewed Long tail on right Most values low, few high Left-skewed Long tail on left Most values high, few low Bimodal Two peaks Two subgroups in data Uniform Flat, roughly equal bars Equal probability across range
Right-skewed (income data): │███ │██████ │█████████▄▃▂▁▁ └──────────────── Low High Left-skewed (exam scores near ceiling): │ ▁▂▄ │ ▃████████ │ ▁▂▄████████ └──────────────── Low High
Faceted Histograms
ggplot(class_scores, aes(x=score, fill=class)) +
geom_histogram(binwidth=10, color="white", show.legend=FALSE) +
facet_wrap(~class, ncol=1) +
scale_fill_manual(values=c("Class A"="steelblue","Class B"="tomato")) +
labs(title="Score Distribution by Class", x="Score", y="Count") +
theme_minimal()
Histograms answer the question "how is my data distributed?" before any other analysis begins. Always examine your data's distribution first — it tells you whether to use parametric tests, whether outliers exist, and whether transformations are needed before modeling.
