02-01: Frequency Distributions¶
A frequency distribution collapses a long list of raw values into a compact table of classes and counts. It is the first real summary of a data set, and it is the input to almost every graph in 02-02.
Categorical Frequency Distribution¶
For qualitative data, the classes are simply the categories.
| Blood type | Frequency f |
Relative frequency f/n |
Percent |
|---|---|---|---|
| A | 20 | 0.400 | 40.0% |
| B | 5 | 0.100 | 10.0% |
| AB | 5 | 0.100 | 10.0% |
| O | 20 | 0.400 | 40.0% |
| Total | 50 | 1.000 | 100% |
The relative frequencies must sum to 1 (allowing for rounding). If they do not, a category is missing or double-counted.
Grouped Frequency Distribution¶
For quantitative data with many distinct values, group into classes (bins).
Building one by hand¶
Given n values, range R = max − min, and a target number of classes k:
Step 1 Choose k, the number of classes (5 ≤ k ≤ 20; often k ≈ √n)
Step 2 Range R = max − min
Step 3 Class width w = ROUNDUP(R / k) (always round UP)
Step 4 Start the first class at (or just below) the minimum
Step 5 Add w repeatedly to get the lower class limits
Step 6 Upper limits = next lower limit − 1 unit of measurement
Step 7 Tally the values into classes and total the frequencies
Sturges' rule is a common alternative for step 1:
Limits, boundaries, midpoint, width¶
| Term | Definition | Example (class 20–29, data in whole units) |
|---|---|---|
| Lower class limit | Smallest value that fits the class | 20 |
| Upper class limit | Largest value that fits the class | 29 |
| Class boundaries | Limits pushed out by half a unit, removing the gap | 19.5 – 29.5 |
Class midpoint Xm |
(lower limit + upper limit) / 2 |
24.5 |
Class width w |
Difference between two consecutive lower limits | 10 |
Warning
Class width is not upper − lower of the same class (that gives 9, not 10). Use the distance between consecutive lower limits, or the difference of the boundaries.
Rules for good classes:
- Classes must be mutually exclusive — no value fits two classes.
- Classes must be exhaustive — every value fits somewhere.
- Classes must be continuous — no gaps, even for empty classes.
- Classes should have equal width (except sometimes an open-ended first/last class).
Cumulative Frequency¶
Cumulative frequency answers "how many observations are at or below this point?" It is built on class boundaries and is what an ogive plots.
| Class | f |
Boundaries | Cumulative f |
Cumulative % |
|---|---|---|---|---|
| 20–29 | 3 | 19.5–29.5 | 3 | 12% |
| 30–39 | 8 | 29.5–39.5 | 11 | 44% |
| 40–49 | 9 | 39.5–49.5 | 20 | 80% |
| 50–59 | 5 | 49.5–59.5 | 25 | 100% |
| Total | 25 |
A cumulative relative frequency of 0.80 at 49.5 means 80% of the data lies at or below 49.5.
Worked Example¶
Twenty-five service times (minutes), rounded to whole minutes:
n = 25
min = 23, max = 58
R = 58 − 23 = 35
k = 4 (chosen; √25 = 5 would also be defensible)
w = ROUNDUP(35 / 4) = ROUNDUP(8.75) = 9 → round up to a clean 10
Starting at 20 with width 10:
| Class | Boundaries | Midpoint | f |
f/n |
Cum f |
Cum % |
|---|---|---|---|---|---|---|
| 20–29 | 19.5–29.5 | 24.5 | 3 | 0.12 | 3 | 12% |
| 30–39 | 29.5–39.5 | 34.5 | 8 | 0.32 | 11 | 44% |
| 40–49 | 39.5–49.5 | 44.5 | 9 | 0.36 | 20 | 80% |
| 50–59 | 49.5–59.5 | 54.5 | 5 | 0.20 | 25 | 100% |
| Total | 25 | 1.00 |
Reading the table: 32% of service times fall in the 30–39 minute class; 80% are 49.5 minutes or less.
Excel¶
Categorical — PivotTable (the fastest route)¶
Select the data ▸ Insert ▸ PivotTable → drag the category to Rows and the same field to Values (set to Count). To get percentages, right-click a value ▸ Show Values As ▸ % of Grand Total**.
Categorical — formulas¶
=COUNTIF($B$2:$B$51, "A") ' frequency of category A
=COUNTIF($B$2:$B$51,"A")/COUNTA($B$2:$B$51) ' relative frequency
=UNIQUE($B$2:$B$51) ' the class list itself (365)
Grouped quantitative — formulas¶
' Set-up cells
=MIN(A2:A26) ' F1 -> 23
=MAX(A2:A26) ' F2 -> 58
=F2-F1 ' F3 -> range = 35
=ROUNDUP(F3/4, 0) ' F4 -> raw class width
' With lower limits in D2:D5 (20, 30, 40, 50) and upper limits in E2:E5:
=COUNTIFS($A$2:$A$26,">="&D2, $A$2:$A$26,"<="&E2) ' frequency
=(D2+E2)/2 ' class midpoint
=D2-0.5 ' lower boundary
=E2+0.5 ' upper boundary
=F2/SUM($F$2:$F$5) ' relative frequency
=SUM($F$2:F2) ' cumulative frequency
' One-shot alternative with a bin array (365 dynamic array):
=FREQUENCY(A2:A26, {29;39;49;59}) ' counts <=29, 30-39, 40-49, 50-59
Note
FREQUENCY bins are "less than or equal to" the bin value, so enter the upper limits (29, 39, 49, 59) — not the boundaries. In older Excel, select the whole output range first and confirm with Ctrl+Shift+Enter.
Grouped quantitative — Analysis ToolPak¶
Data ▸ Data Analysis ▸ Histogram → Input Range = the data, Bin Range = the upper limits, tick Cumulative Percentage and Chart Output. You get the frequency table, cumulative percentages, and the histogram in one step.
R¶
service <- c(23,27,31,33,34,35,36,38,39,40,41,42,43,44,45,
46,47,48,51,52,54,57,58,29,37)
# ── Categorical frequency ──────────────────────────────────────────
blood <- c("A","O","B","A","AB","O","O","A")
table(blood) # frequencies
prop.table(table(blood)) # relative frequencies
round(100 * prop.table(table(blood)), 1) # percentages
# ── Class construction ─────────────────────────────────────────────
n <- length(service); n
rng <- diff(range(service)); rng # 35
k <- ceiling(1 + 3.322 * log10(n)); k # Sturges -> 6
ceiling(rng / 4) # 9 -> round up to a clean 10
w <- 10
breaks <- seq(19.5, 59.5, by = w) # class BOUNDARIES
# ── Grouped frequency distribution ─────────────────────────────────
classes <- cut(service, breaks = breaks, right = TRUE)
f <- table(classes)
dist <- data.frame(
class = names(f),
midpoint = head(breaks, -1) + w / 2,
f = as.integer(f),
rel_f = round(as.numeric(prop.table(f)), 3),
cum_f = cumsum(as.integer(f)),
cum_pct = round(100 * cumsum(as.numeric(prop.table(f))), 1)
)
dist
# class midpoint f rel_f cum_f cum_pct
# 1 (19.5,29.5] 24.5 3 0.12 3 12.0
# 2 (29.5,39.5] 34.5 8 0.32 11 44.0
# 3 (39.5,49.5] 44.5 9 0.36 20 80.0
# 4 (49.5,59.5] 54.5 5 0.20 25 100.0
# hist() computes the same counts
h <- hist(service, breaks = breaks, plot = FALSE)
h$counts; h$mids
Python¶
import numpy as np
import pandas as pd
service = np.array([23,27,31,33,34,35,36,38,39,40,41,42,43,44,45,
46,47,48,51,52,54,57,58,29,37])
# ── Categorical frequency ──────────────────────────────────────────
blood = pd.Series(["A","O","B","A","AB","O","O","A"])
blood.value_counts() # frequencies
blood.value_counts(normalize=True) # relative frequencies
# ── Class construction ─────────────────────────────────────────────
n = service.size
rng = service.max() - service.min() # 35
k = int(np.ceil(1 + 3.322 * np.log10(n))) # Sturges -> 6
int(np.ceil(rng / 4)) # 9 -> round up to a clean 10
w = 10
edges = np.arange(19.5, 19.5 + 5 * w, w) # class BOUNDARIES
# ── Grouped frequency distribution ─────────────────────────────────
classes = pd.cut(service, bins=edges, right=True)
f = classes.value_counts().sort_index()
dist = pd.DataFrame({
"f": f.values,
"midpoint": edges[:-1] + w / 2,
"rel_f": (f / n).round(3).values,
"cum_f": f.cumsum().values,
"cum_pct": (100 * f.cumsum() / n).round(1).values,
}, index=f.index)
print(dist)
# np.histogram gives the same counts
counts, bin_edges = np.histogram(service, bins=edges)
Quick Reference¶
| Task | Excel | R | Python |
|---|---|---|---|
| Category frequencies | PivotTable / COUNTIF |
table(x) |
s.value_counts() |
| Relative frequencies | COUNTIF/COUNTA |
prop.table(table(x)) |
value_counts(normalize=True) |
| Number of classes | =ROUNDUP(1+3.322*LOG10(n),0) |
ceiling(1+3.322*log10(n)) |
ceil(1+3.322*log10(n)) |
| Class width | =ROUNDUP((MAX-MIN)/k, 0) |
ceiling(diff(range(x))/k) |
ceil(np.ptp(x)/k) |
| Grouped counts | FREQUENCY / COUNTIFS |
table(cut(x, breaks)) |
pd.cut(x, bins).value_counts() |
| Cumulative frequency | SUM($F$2:F2) |
cumsum(f) |
f.cumsum() |
| Full table + chart | Data Analysis ▸ Histogram | hist(x, breaks) |
np.histogram(x, bins) |
Common Mistakes¶
- Rounding class width down — the last class then cannot hold the maximum value.
- Using limits where boundaries are required (cumulative plots and ogives always use boundaries).
- Overlapping classes (
20–30,30–40) so a value of 30 belongs to two classes. - Dropping an empty class in the middle to "tidy" the table — it breaks the continuity rule and distorts the histogram.
- Reading
FREQUENCY's bin array as boundaries rather than upper limits.
Exercises: 02-01: Exercises — Frequency Distributions
⬅️ Previous: 01-02: Data Representation and Organization ➡️ Next: 02-02: Graphical Displays