Skip to content

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%
Relative frequency  =  f / n
Percentage          =  (f / n) × 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:

k = 1 + 3.322 × log10(n)          (round to a whole number)

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:

  1. Classes must be mutually exclusive — no value fits two classes.
  2. Classes must be exhaustive — every value fits somewhere.
  3. Classes must be continuous — no gaps, even for empty classes.
  4. 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:

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
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 ▸ HistogramInput 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