Central tendency
central tendency mean median mode statistics explained
| Situation | Best Measure |
|---|---|
| Symmetric, no outliers | Mean |
| Skewed data or outliers present | Median |
| Categorical / nominal data | Mode |
| Ordinal data (ranked) | Median or Mode |
| Measure | Description |
|---|---|
| Geometric Mean | nth root of the product of n values; used for growth rates |
| Harmonic Mean | Reciprocal of the average of reciprocals; used for rates/speeds |
| Weighted Mean | Mean where each value has a different importance (weight) |
| Trimmed Mean | Mean calculated after removing a percentage of extreme values |
| Midrange | (Maximum + Minimum) / 2 |
Create a study guide on measures of central tendency
mkdir -p /home/daytona/workspace/central-tendency-guide
~/central-tendency-guide/generate_guide.py
from reportlab.lib.pagesizes import letter
from reportlab.lib import colors
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import inch
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, PageBreak, KeepTogether
)
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
from reportlab.platypus import ListFlowable, ListItem
OUTPUT = "/home/daytona/workspace/central-tendency-guide/Central_Tendency_Study_Guide.pdf"
# ── Colour palette ──────────────────────────────────────────────────────────
DARK_BLUE = colors.HexColor("#1A3A5C")
MID_BLUE = colors.HexColor("#2E6DA4")
LIGHT_BLUE = colors.HexColor("#D6E8F7")
ACCENT = colors.HexColor("#E8A020")
LIGHT_GREY = colors.HexColor("#F4F6F8")
MED_GREY = colors.HexColor("#CCCCCC")
TEXT_DARK = colors.HexColor("#1C1C1C")
WHITE = colors.white
# ── Styles ───────────────────────────────────────────────────────────────────
base = getSampleStyleSheet()
def make_style(name, parent="Normal", **kw):
s = ParagraphStyle(name, parent=base[parent], **kw)
return s
cover_title = make_style("CoverTitle",
fontSize=32, leading=40, textColor=WHITE,
alignment=TA_CENTER, fontName="Helvetica-Bold")
cover_sub = make_style("CoverSub",
fontSize=14, leading=20, textColor=LIGHT_BLUE,
alignment=TA_CENTER, fontName="Helvetica")
cover_meta = make_style("CoverMeta",
fontSize=11, leading=16, textColor=WHITE,
alignment=TA_CENTER, fontName="Helvetica")
h1 = make_style("H1",
fontSize=18, leading=24, textColor=WHITE,
fontName="Helvetica-Bold", spaceAfter=6)
h2 = make_style("H2",
fontSize=14, leading=18, textColor=DARK_BLUE,
fontName="Helvetica-Bold", spaceBefore=12, spaceAfter=4)
h3 = make_style("H3",
fontSize=12, leading=16, textColor=MID_BLUE,
fontName="Helvetica-Bold", spaceBefore=8, spaceAfter=3)
body = make_style("Body",
fontSize=10.5, leading=16, textColor=TEXT_DARK,
fontName="Helvetica", spaceAfter=6, alignment=TA_JUSTIFY)
bullet_style = make_style("Bullet",
fontSize=10.5, leading=15, textColor=TEXT_DARK,
fontName="Helvetica", leftIndent=16, spaceAfter=4)
formula_style = make_style("Formula",
fontSize=11, leading=16, textColor=DARK_BLUE,
fontName="Helvetica-Bold", alignment=TA_CENTER,
spaceBefore=6, spaceAfter=6,
backColor=LIGHT_BLUE, borderPad=8)
note_style = make_style("Note",
fontSize=10, leading=14, textColor=colors.HexColor("#7B5000"),
fontName="Helvetica-Oblique", leftIndent=12,
backColor=colors.HexColor("#FFF8E1"), borderPad=6)
caption = make_style("Caption",
fontSize=9, leading=12, textColor=colors.grey,
fontName="Helvetica-Oblique", alignment=TA_CENTER)
toc_item = make_style("TOCItem",
fontSize=11, leading=18, textColor=DARK_BLUE,
fontName="Helvetica", leftIndent=20)
toc_head = make_style("TOCHead",
fontSize=13, leading=20, textColor=DARK_BLUE,
fontName="Helvetica-Bold", spaceAfter=8)
review_q = make_style("ReviewQ",
fontSize=10.5, leading=15, textColor=TEXT_DARK,
fontName="Helvetica-Bold", leftIndent=10, spaceAfter=4)
answer_style = make_style("Answer",
fontSize=10, leading=14, textColor=colors.HexColor("#2E6DA4"),
fontName="Helvetica-Oblique", leftIndent=24, spaceAfter=8)
# ── Helper builders ───────────────────────────────────────────────────────────
def section_header(title):
"""Dark-blue full-width banner with white title text."""
data = [[Paragraph(title, h1)]]
t = Table(data, colWidths=[6.5*inch])
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), DARK_BLUE),
("TOPPADDING", (0,0), (-1,-1), 10),
("BOTTOMPADDING", (0,0), (-1,-1), 10),
("LEFTPADDING", (0,0), (-1,-1), 14),
("RIGHTPADDING", (0,0), (-1,-1), 14),
("ROUNDEDCORNERS", [6]),
]))
return [t, Spacer(1, 10)]
def definition_box(term, definition):
"""Accent-bordered definition card."""
data = [
[Paragraph(f"<b>{term}</b>", make_style("DefTerm",
fontSize=11, leading=14, textColor=ACCENT,
fontName="Helvetica-Bold"))],
[Paragraph(definition, body)],
]
t = Table(data, colWidths=[6.3*inch])
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), DARK_BLUE),
("BACKGROUND", (0,1), (-1,1), LIGHT_GREY),
("TOPPADDING", (0,0), (-1,-1), 8),
("BOTTOMPADDING", (0,0), (-1,-1), 8),
("LEFTPADDING", (0,0), (-1,-1), 12),
("RIGHTPADDING", (0,0), (-1,-1), 12),
("LINEBELOW", (0,-1), (-1,-1), 0.5, MED_GREY),
]))
return [t, Spacer(1, 8)]
def formula_box(text):
data = [[Paragraph(text, formula_style)]]
t = Table(data, colWidths=[6.3*inch])
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), LIGHT_BLUE),
("TOPPADDING", (0,0), (-1,-1), 10),
("BOTTOMPADDING", (0,0), (-1,-1), 10),
("LEFTPADDING", (0,0), (-1,-1), 14),
("RIGHTPADDING", (0,0), (-1,-1), 14),
("BOX", (0,0), (-1,-1), 1.5, MID_BLUE),
("ROUNDEDCORNERS", [4]),
]))
return [t, Spacer(1, 8)]
def note_box(text):
data = [[Paragraph(f"<b>Note:</b> {text}", note_style)]]
t = Table(data, colWidths=[6.3*inch])
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), colors.HexColor("#FFF8E1")),
("TOPPADDING", (0,0), (-1,-1), 8),
("BOTTOMPADDING", (0,0), (-1,-1), 8),
("LEFTPADDING", (0,0), (-1,-1), 12),
("RIGHTPADDING", (0,0), (-1,-1), 12),
("LINEBEFORE", (0,0), (0,-1), 4, ACCENT),
]))
return [t, Spacer(1, 8)]
def styled_table(header_row, data_rows, col_widths=None):
if col_widths is None:
n = len(header_row)
col_widths = [6.3*inch/n] * n
all_rows = [header_row] + data_rows
t = Table(all_rows, colWidths=col_widths)
style = [
("BACKGROUND", (0,0), (-1,0), MID_BLUE),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,0), 10),
("ALIGN", (0,0), (-1,-1), "CENTER"),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("FONTNAME", (0,1), (-1,-1), "Helvetica"),
("FONTSIZE", (0,1), (-1,-1), 9.5),
("ROWBACKGROUNDS",(0,1), (-1,-1), [WHITE, LIGHT_GREY]),
("GRID", (0,0), (-1,-1), 0.5, MED_GREY),
("TOPPADDING", (0,0), (-1,-1), 7),
("BOTTOMPADDING", (0,0), (-1,-1), 7),
("LEFTPADDING", (0,0), (-1,-1), 8),
("RIGHTPADDING", (0,0), (-1,-1), 8),
]
t.setStyle(TableStyle(style))
return [t, Spacer(1, 10)]
# ── Document builder ─────────────────────────────────────────────────────────
def build():
doc = SimpleDocTemplate(
OUTPUT,
pagesize=letter,
leftMargin=1*inch, rightMargin=1*inch,
topMargin=0.85*inch, bottomMargin=0.85*inch,
title="Measures of Central Tendency – Study Guide",
author="Orris AI"
)
story = []
# ── COVER PAGE ────────────────────────────────────────────────────────────
cover_bg_data = [
[Paragraph("STATISTICS", make_style("CLabel",
fontSize=12, textColor=ACCENT, fontName="Helvetica-Bold",
alignment=TA_CENTER, spaceBefore=0))],
[Paragraph("Measures of<br/>Central Tendency", cover_title)],
[Spacer(1, 8)],
[Paragraph("Undergraduate Study Guide", cover_sub)],
[Spacer(1, 20)],
[HRFlowable(width=3*inch, thickness=2, color=ACCENT, spaceAfter=20)],
[Paragraph("Topics covered: Mean · Median · Mode · Weighted Mean<br/>"
"Geometric & Harmonic Mean · Skewness & Outlier Effects<br/>"
"Choosing the Right Measure · Practice Problems", cover_meta)],
]
cover_table = Table(cover_bg_data, colWidths=[6.5*inch])
cover_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), DARK_BLUE),
("TOPPADDING", (0,0), (-1,-1), 18),
("BOTTOMPADDING", (0,0), (-1,-1), 18),
("LEFTPADDING", (0,0), (-1,-1), 30),
("RIGHTPADDING", (0,0), (-1,-1), 30),
("ROUNDEDCORNERS", [10]),
]))
story.append(Spacer(1, 1.2*inch))
story.append(cover_table)
story.append(Spacer(1, 0.5*inch))
story.append(Paragraph("Prepared by Orris AI | July 2026", caption))
story.append(PageBreak())
# ── TABLE OF CONTENTS ─────────────────────────────────────────────────────
story.append(Paragraph("Table of Contents", toc_head))
story.append(HRFlowable(width=6.5*inch, thickness=1, color=ACCENT, spaceAfter=10))
toc_entries = [
("1.", "Introduction to Central Tendency"),
("2.", "The Arithmetic Mean"),
("3.", "The Median"),
("4.", "The Mode"),
("5.", "Weighted Mean"),
("6.", "Geometric and Harmonic Mean"),
("7.", "Skewness and Outlier Effects"),
("8.", "Choosing the Right Measure"),
("9.", "Summary Comparison Table"),
("10.", "Practice Problems"),
("11.", "Answer Key"),
]
for num, title in toc_entries:
story.append(Paragraph(f"{num} {title}", toc_item))
story.append(PageBreak())
# ── SECTION 1: INTRODUCTION ───────────────────────────────────────────────
story += section_header("1. Introduction to Central Tendency")
story.append(Paragraph(
"In statistics, a <b>measure of central tendency</b> is a single value that attempts "
"to describe a dataset by identifying its central position. These measures summarise "
"an entire distribution into one representative number, making it easier to compare "
"datasets and communicate findings. Central tendency is always interpreted alongside "
"<b>dispersion</b> (e.g. variance, standard deviation) for a complete picture.",
body))
story.append(Spacer(1, 6))
story += definition_box("Central Tendency",
"A statistical measure that identifies the center, or typical value, "
"of a dataset or probability distribution.")
story.append(Paragraph("<b>Why it matters:</b>", h3))
items = [
"Summarises large datasets into a single interpretable number.",
"Allows comparison between different datasets or groups.",
"Provides the foundation for more advanced statistical analyses.",
"Guides practical decisions in business, medicine, social sciences, and engineering.",
]
for item in items:
story.append(Paragraph(f"• {item}", bullet_style))
story.append(Spacer(1, 6))
story.append(Paragraph("<b>The Three Primary Measures:</b>", h3))
primary_data = [
[Paragraph("<b>Measure</b>", base["Normal"]),
Paragraph("<b>Description</b>", base["Normal"]),
Paragraph("<b>Data Type</b>", base["Normal"])],
["Mean", "Arithmetic average of all values", "Interval / Ratio"],
["Median", "Middle value of an ordered dataset", "Ordinal and above"],
["Mode", "Most frequently occurring value(s)", "All types (incl. Nominal)"],
]
story += styled_table(
primary_data[0], primary_data[1:],
col_widths=[1.4*inch, 3.4*inch, 1.5*inch])
story.append(PageBreak())
# ── SECTION 2: MEAN ───────────────────────────────────────────────────────
story += section_header("2. The Arithmetic Mean")
story.append(Paragraph(
"The arithmetic mean (commonly called the <b>average</b>) is computed by summing all "
"observations and dividing by the count. It uses every data point and is the most "
"widely used measure of central tendency for interval and ratio data.",
body))
story.append(Paragraph("<b>Population Mean:</b>", h3))
story += formula_box("mu = (x1 + x2 + ... + xN) / N [Population mean - uses all N values]")
story.append(Paragraph("<b>Sample Mean:</b>", h3))
story += formula_box("x-bar = (x1 + x2 + ... + xn) / n [Sample mean - uses n observations]")
story.append(Paragraph("<b>Worked Example</b>", h3))
story.append(Paragraph(
"A student scores 72, 85, 90, 68, and 95 on five exams. Find the mean.",
body))
ex_data = [
[Paragraph("<b>Step</b>", base["Normal"]),
Paragraph("<b>Operation</b>", base["Normal"]),
Paragraph("<b>Result</b>", base["Normal"])],
["1 - Sum values", "72 + 85 + 90 + 68 + 95", "410"],
["2 - Count values", "n = 5", "5"],
["3 - Divide", "410 / 5", "x-bar = 82"],
]
story += styled_table(ex_data[0], ex_data[1:], col_widths=[1.8*inch, 2.8*inch, 1.7*inch])
story.append(Paragraph("<b>Key Properties</b>", h3))
props = [
"Uses <b>every observation</b> in the dataset.",
"The sum of deviations from the mean always equals zero: sum(xi - x-bar) = 0.",
"Minimises the sum of <b>squared deviations</b> (least-squares property).",
"Sensitive to <b>outliers</b>: extreme values pull the mean toward them.",
"Cannot be used for <b>nominal or ordinal</b> data.",
]
for p in props:
story.append(Paragraph(f"• {p}", bullet_style))
story += note_box(
"When data contain outliers or are heavily skewed, the mean may be misleading. "
"In such cases, the median is often preferred.")
story.append(PageBreak())
# ── SECTION 3: MEDIAN ────────────────────────────────────────────────────
story += section_header("3. The Median")
story.append(Paragraph(
"The <b>median</b> is the middle value in an ordered dataset. It divides the "
"distribution into two equal halves — 50% of values fall below and 50% above.",
body))
story.append(Paragraph("<b>Finding the Median</b>", h3))
med_steps = [
[Paragraph("<b>Dataset Size</b>", base["Normal"]),
Paragraph("<b>Procedure</b>", base["Normal"])],
["Odd (n is odd)", "Median = value at position (n+1)/2 after sorting"],
["Even (n is even)","Median = average of values at positions n/2 and (n/2)+1"],
]
story += styled_table(med_steps[0], med_steps[1:], col_widths=[2.0*inch, 4.3*inch])
story.append(Paragraph("<b>Worked Example - Odd n</b>", h3))
story.append(Paragraph("Data: 15, 22, 8, 31, 17 (n = 5)", body))
story.append(Paragraph("Step 1 - Sort: <b>8, 15, 17, 22, 31</b>", bullet_style))
story.append(Paragraph("Step 2 - Middle position: (5+1)/2 = 3rd value", bullet_style))
story.append(Paragraph("Median = <b>17</b>", bullet_style))
story.append(Spacer(1, 6))
story.append(Paragraph("<b>Worked Example - Even n</b>", h3))
story.append(Paragraph("Data: 15, 22, 8, 31 (n = 4)", body))
story.append(Paragraph("Step 1 - Sort: <b>8, 15, 22, 31</b>", bullet_style))
story.append(Paragraph("Step 2 - Average of 2nd and 3rd values: (15 + 22) / 2", bullet_style))
story.append(Paragraph("Median = <b>18.5</b>", bullet_style))
story.append(Spacer(1, 6))
story.append(Paragraph("<b>Key Properties</b>", h3))
props = [
"<b>Resistant to outliers</b>: not influenced by extreme values.",
"Appropriate for <b>ordinal, interval, and ratio</b> data.",
"Best used when data are <b>skewed</b> or contain outliers.",
"Used in reporting <b>income, housing prices, and hospital wait times</b>.",
]
for p in props:
story.append(Paragraph(f"• {p}", bullet_style))
story += note_box(
"For grouped frequency data, the median can be estimated using the formula: "
"Median = L + [(n/2 - F) / f] x h, where L = lower class boundary, "
"F = cumulative frequency before the median class, f = frequency of median class, "
"h = class width.")
story.append(PageBreak())
# ── SECTION 4: MODE ──────────────────────────────────────────────────────
story += section_header("4. The Mode")
story.append(Paragraph(
"The <b>mode</b> is the value (or values) that appear most frequently in a dataset. "
"It is the only measure of central tendency applicable to <b>nominal (categorical) data</b>.",
body))
story.append(Paragraph("<b>Types of Mode</b>", h3))
mode_data = [
[Paragraph("<b>Type</b>", base["Normal"]),
Paragraph("<b>Description</b>", base["Normal"]),
Paragraph("<b>Example</b>", base["Normal"])],
["Unimodal", "One mode", "2, 3, 3, 4, 5 → Mode = 3"],
["Bimodal", "Two modes", "1, 2, 2, 3, 4, 4 → Mode = 2 and 4"],
["Multimodal", "More than two modes","1,1,2,2,3,3 → Mode = 1, 2, and 3"],
["No mode", "All values equal freq","1, 2, 3, 4, 5 → No mode"],
]
story += styled_table(mode_data[0], mode_data[1:],
col_widths=[1.4*inch, 2.0*inch, 3.1*inch])
story.append(Paragraph("<b>Key Properties</b>", h3))
props = [
"Applicable to <b>all data types</b> including nominal and categorical.",
"A dataset may have <b>zero, one, or multiple modes</b>.",
"<b>Not necessarily unique</b> - bimodal or multimodal distributions are common.",
"Less useful for continuous data where values rarely repeat exactly.",
"Useful in <b>market research</b> (most popular product), <b>manufacturing</b> (most common defect).",
]
for p in props:
story.append(Paragraph(f"• {p}", bullet_style))
story.append(PageBreak())
# ── SECTION 5: WEIGHTED MEAN ──────────────────────────────────────────────
story += section_header("5. Weighted Mean")
story.append(Paragraph(
"The <b>weighted mean</b> is used when different observations carry different levels "
"of importance (weights). It is the standard method for computing GPA and weighted "
"portfolio returns.", body))
story += formula_box("Weighted Mean = sum(wi * xi) / sum(wi) where wi = weight of observation xi")
story.append(Paragraph("<b>Worked Example - GPA Calculation</b>", h3))
gpa_data = [
[Paragraph("<b>Course</b>", base["Normal"]),
Paragraph("<b>Grade (xi)</b>", base["Normal"]),
Paragraph("<b>Credit Hours (wi)</b>", base["Normal"]),
Paragraph("<b>wi x xi</b>", base["Normal"])],
["Statistics", "A (4.0)", "3", "12.0"],
["Calculus", "B (3.0)", "4", "12.0"],
["English", "A (4.0)", "2", "8.0"],
["History", "C (2.0)", "3", "6.0"],
["TOTAL", "", "12", "38.0"],
]
story += styled_table(gpa_data[0], gpa_data[1:],
col_widths=[1.8*inch, 1.5*inch, 1.7*inch, 1.3*inch])
story.append(Paragraph("GPA = 38.0 / 12 = <b>3.17</b>", body))
story.append(Spacer(1, 6))
story += note_box(
"When all weights are equal (wi = 1 for all i), the weighted mean reduces to the "
"simple arithmetic mean.")
story.append(PageBreak())
# ── SECTION 6: GEOMETRIC & HARMONIC MEAN ──────────────────────────────────
story += section_header("6. Geometric and Harmonic Mean")
story.append(Paragraph("<b>6.1 Geometric Mean</b>", h2))
story.append(Paragraph(
"The geometric mean is the nth root of the product of n values. It is used for "
"data that represents <b>growth rates, ratios, or compounding quantities</b> "
"(e.g. investment returns, population growth rates).",
body))
story += formula_box("GM = (x1 * x2 * ... * xn)^(1/n) or equivalently GM = exp[mean(ln xi)]")
story.append(Paragraph("<b>Example:</b> Annual returns of 10%, 20%, and -5%", body))
story.append(Paragraph(
"Values: 1.10, 1.20, 0.95 (expressed as growth multipliers)", bullet_style))
story.append(Paragraph(
"GM = (1.10 x 1.20 x 0.95)^(1/3) = (1.254)^(0.333) = <b>1.0782</b> → 7.82% avg annual return",
bullet_style))
story.append(Spacer(1, 8))
story.append(Paragraph("<b>6.2 Harmonic Mean</b>", h2))
story.append(Paragraph(
"The harmonic mean is the reciprocal of the arithmetic mean of reciprocals. "
"It is most appropriate for <b>rates</b> (speed, price per unit, efficiency).",
body))
story += formula_box("HM = n / sum(1/xi) = n / (1/x1 + 1/x2 + ... + 1/xn)")
story.append(Paragraph(
"<b>Example:</b> A car travels 60 km at 30 km/h and 60 km at 60 km/h. "
"Average speed = HM(30, 60) = 2 / (1/30 + 1/60) = 2 / (0.05) = <b>40 km/h</b>",
body))
story.append(Spacer(1, 6))
story.append(Paragraph("<b>Inequality of Means (Always true for positive data):</b>", h3))
story += formula_box("HM <= GM <= AM (equality holds only when all values are equal)")
story.append(PageBreak())
# ── SECTION 7: SKEWNESS & OUTLIERS ────────────────────────────────────────
story += section_header("7. Skewness and Outlier Effects")
story.append(Paragraph(
"The shape of a distribution determines which measure of central tendency is most "
"representative. <b>Skewness</b> describes the asymmetry of a distribution.",
body))
skew_data = [
[Paragraph("<b>Distribution</b>", base["Normal"]),
Paragraph("<b>Shape</b>", base["Normal"]),
Paragraph("<b>Relationship</b>", base["Normal"]),
Paragraph("<b>Best Measure</b>", base["Normal"])],
["Symmetric (Normal)", "Bell-shaped, balanced",
"Mean = Median = Mode", "Mean"],
["Right-skewed (+)", "Long right tail",
"Mode < Median < Mean", "Median"],
["Left-skewed (-)", "Long left tail",
"Mean < Median < Mode", "Median"],
]
story += styled_table(skew_data[0], skew_data[1:],
col_widths=[1.6*inch, 1.8*inch, 2.0*inch, 1.3*inch])
story.append(Paragraph("<b>Outlier Effect on the Mean</b>", h3))
story.append(Paragraph(
"Consider five employee salaries: $32k, $35k, $33k, $31k, $200k", body))
salary_data = [
[Paragraph("<b>Measure</b>", base["Normal"]),
Paragraph("<b>Value</b>", base["Normal"]),
Paragraph("<b>Representative?</b>", base["Normal"])],
["Mean", "$66,200", "No - inflated by $200k outlier"],
["Median", "$33,000", "Yes - reflects typical salary"],
["Mode", "No mode", "N/A"],
]
story += styled_table(salary_data[0], salary_data[1:],
col_widths=[1.4*inch, 1.5*inch, 3.4*inch])
story += note_box(
"This is why government agencies and economists report <b>median household income</b> "
"rather than mean income - the mean is distorted by very high earners at the top.")
story.append(PageBreak())
# ── SECTION 8: CHOOSING THE RIGHT MEASURE ────────────────────────────────
story += section_header("8. Choosing the Right Measure")
choice_data = [
[Paragraph("<b>Situation</b>", base["Normal"]),
Paragraph("<b>Recommended Measure</b>", base["Normal"]),
Paragraph("<b>Reason</b>", base["Normal"])],
["Symmetric distribution, no outliers", "Mean",
"Uses all data, most efficient"],
["Skewed distribution", "Median",
"Not affected by extreme values"],
["Outliers present", "Median",
"Outliers distort the mean"],
["Nominal / categorical data", "Mode",
"Mean and median are undefined"],
["Ordinal data (rankings)", "Median or Mode",
"Mean requires equal intervals"],
["Growth rates / percentages", "Geometric Mean",
"Accounts for compounding"],
["Averaging rates (speed, price)", "Harmonic Mean",
"Correct averaging of rates"],
["Unequal importance / weights", "Weighted Mean",
"Reflects varying contribution"],
]
story += styled_table(choice_data[0], choice_data[1:],
col_widths=[2.0*inch, 2.0*inch, 2.5*inch])
story.append(PageBreak())
# ── SECTION 9: SUMMARY TABLE ──────────────────────────────────────────────
story += section_header("9. Summary Comparison Table")
summary_data = [
[Paragraph("<b>Feature</b>", base["Normal"]),
Paragraph("<b>Mean</b>", base["Normal"]),
Paragraph("<b>Median</b>", base["Normal"]),
Paragraph("<b>Mode</b>", base["Normal"])],
["Formula", "Sum / n", "Middle value", "Most frequent"],
["Uses all data", "Yes", "No", "No"],
["Outlier effect","High", "Low", "None"],
["Data types", "Interval/Ratio", "Ordinal+", "Any"],
["Unique value", "Yes", "Yes", "Not always"],
["Skewed data", "Not ideal", "Preferred", "Depends"],
["Normal dist.", "Best", "Equally valid", "Equally valid"],
["Use case", "Test scores, temp","Income, prices","Survey responses"],
]
story += styled_table(summary_data[0], summary_data[1:],
col_widths=[1.7*inch, 1.6*inch, 1.6*inch, 1.4*inch])
story.append(PageBreak())
# ── SECTION 10: PRACTICE PROBLEMS ────────────────────────────────────────
story += section_header("10. Practice Problems")
problems = [
("Q1.",
"A dataset contains the values: 12, 18, 15, 22, 18, 30, 18, 25, 10. "
"Calculate the mean, median, and mode.",
"Mean = (12+18+15+22+18+30+18+25+10)/9 = 168/9 = 18.67. "
"Sorted: 10,12,15,18,18,18,22,25,30 — Median = 18 (5th value). Mode = 18."),
("Q2.",
"A mutual fund had annual returns of 8%, 12%, -4%, 15%, and 9% over five years. "
"Calculate the geometric mean annual return.",
"Convert to multipliers: 1.08, 1.12, 0.96, 1.15, 1.09. "
"GM = (1.08 x 1.12 x 0.96 x 1.15 x 1.09)^(1/5) = (1.4617)^0.2 = 1.0789 → 7.89% p.a."),
("Q3.",
"A student earned the following grades: Biology B+(3.3, 4cr), Chemistry A(4.0, 5cr), "
"Math A-(3.7, 3cr), English B(3.0, 3cr). Calculate the GPA.",
"Weighted sum = 3.3x4 + 4.0x5 + 3.7x3 + 3.0x3 = 13.2+20+11.1+9 = 53.3. "
"Total credits = 15. GPA = 53.3/15 = 3.55."),
("Q4.",
"Household incomes in a neighbourhood (in $1000s): 45, 52, 48, 61, 55, 49, 53, 210. "
"Which measure of central tendency best represents a typical household? Explain.",
"The median (52.5) is best. The value of $210k is an outlier that pulls the mean to $71.6k, "
"which is unrepresentative of typical households. The median is resistant to this outlier."),
("Q5.",
"A car travels 120 km at 60 km/h and then 120 km at 40 km/h. "
"What is the average speed for the entire trip?",
"Use harmonic mean: HM = 2 / (1/60 + 1/40) = 2 / (0.0167+0.025) = 2/0.0417 = 48 km/h. "
"(Do NOT use arithmetic mean: (60+40)/2 = 50 km/h would be incorrect.)"),
]
for num, question, answer in problems:
story.append(Paragraph(f"<b>{num}</b> {question}", review_q))
story.append(Spacer(1, 4))
answer_data = [[Paragraph(f"Working space:", make_style("WS",
fontSize=9, textColor=colors.grey, fontName="Helvetica-Oblique"))]]
for _ in range(3):
answer_data.append([Paragraph("", body)])
ws_table = Table(answer_data, colWidths=[6.3*inch])
ws_table.setStyle(TableStyle([
("BOX", (0,0), (-1,-1), 0.5, MED_GREY),
("TOPPADDING", (0,0), (-1,-1), 8),
("BOTTOMPADDING", (0,0), (-1,-1), 18),
("LEFTPADDING", (0,0), (-1,-1), 10),
("BACKGROUND", (0,0), (-1,-1), LIGHT_GREY),
("LINEBELOW", (0,1), (-1,-1), 0.3, MED_GREY),
]))
story.append(ws_table)
story.append(Spacer(1, 14))
story.append(PageBreak())
# ── SECTION 11: ANSWER KEY ────────────────────────────────────────────────
story += section_header("11. Answer Key")
story.append(Paragraph(
"Attempt all problems before consulting this section.", note_style))
story.append(Spacer(1, 10))
for num, question, answer in problems:
story.append(Paragraph(f"<b>{num}</b> {question}", review_q))
story.append(Paragraph(f"Answer: {answer}", answer_style))
story.append(HRFlowable(width=6.3*inch, thickness=0.5, color=MED_GREY, spaceAfter=6))
story.append(Spacer(1, 20))
story.append(HRFlowable(width=6.5*inch, thickness=1.5, color=ACCENT))
story.append(Spacer(1, 10))
story.append(Paragraph(
"End of Study Guide | Measures of Central Tendency | Prepared by Orris AI, July 2026",
caption))
doc.build(story)
print(f"PDF created: {OUTPUT}")
if __name__ == "__main__":
build()
python /home/daytona/workspace/central-tendency-guide/generate_guide.py
ls -lh /home/daytona/workspace/central-tendency-guide/
Central Tendency Study Guide
PDF Document · PDF
| Section | Content |
|---|---|
| 1. Introduction | What central tendency is, why it matters, data types |
| 2. Arithmetic Mean | Formula (population & sample), worked example, properties |
| 3. Median | Odd/even n procedures, two worked examples, grouped data formula |
| 4. Mode | Unimodal/bimodal/multimodal, key properties and use cases |
| 5. Weighted Mean | Formula, full GPA calculation example |
| 6. Geometric & Harmonic Mean | Formulas, worked examples, the HM ≤ GM ≤ AM inequality |
| 7. Skewness & Outliers | How distribution shape affects the best measure, salary outlier example |
| 8. Choosing the Right Measure | Decision table covering 8 common scenarios |
| 9. Summary Comparison Table | Side-by-side comparison of Mean, Median, and Mode |
| 10. Practice Problems | 5 problems with workspace lines for writing in answers |
| 11. Answer Key | Full worked solutions for all 5 problems |