0%
DQDigital Qasas Courses/Medical Statistics with R and Python
Sign inAll courses
  • Why R and Python, and running them in your browser
  • Data frames, import, and tidy medical data
  • Tables and summaries
  • Variables, types, and the question behind the data
  • Displaying data: frequency distributions, histograms, and shape
  • Means, standard deviations and standard errors
  • The normal distribution
  • Confidence interval for a mean
  • Using P-values and confidence intervals
  • Comparison of two means
  • Analysis of variance
  • Linear regression and correlation
  • Multiple regression and diagnostics
  • Transformations
  • Risk, odds, and how to compare them
  • Proportions and the binomial distribution
  • Two proportions: risk ratio, odds ratio, risk difference, and confidence intervals
  • Chi-squared: 2x2 tables, larger tables, trend, and exact tests
  • Confounding and stratification
  • Logistic regression
  • Matched studies
  • Rates and the Poisson distribution
  • Comparing rates and Poisson regression
  • Standardization: direct, indirect, and the SMR
  • Kaplan-Meier survival curves and the log-rank test
  • Cox proportional-hazards regression
  • Likelihood and the generalized linear model
  • Building models, checking assumptions, and clustered data
  • Systematic reviews and meta-analysis
  • Bayesian statistics
  • Linking analysis to study design
  • Sample size and power
  • Measurement error and its consequences
  • Capstone: from a messy dataset to a reported result
1. Foundations: variables and displaying data

Displaying data: frequency distributions, histograms, and shape

Last updated 30 June 2026

Before you fit a single model, look at the data. A frequency table and a couple of plots tell you the shape of a variable, flag the impossible value typed in by mistake, and point you toward the right analysis. This lesson covers the everyday displays for one variable at a time: tables, histograms, bar charts, pie charts, and the cumulative curve you read percentiles off. Lesson 1.1 sorted variables into numerical and categorical. The display you choose follows directly from that split.

Counting categories

For a categorical variable the work is counting. You tally how many observations fall in each category, and those counts are the frequencies. Divide each by the total and you get relative frequencies, reported as proportions or percentages. Suppose an antenatal clinic in Johor records the booking diagnosis for 200 women.

DiagnosisNo. of womenPercentage
Iron-deficiency anaemia8442.0
Gestational hypertension4824.0
Gestational diabetes4020.0
Thalassaemia trait2814.0
Total200100.0

That table is already a complete summary. A plot just makes the comparison faster to read.

Bar charts and pie charts

A bar chart draws one bar per category, with bar length proportional to the frequency. Gaps sit between the bars because the categories are separate, not a continuous scale. A pie chart splits a circle into wedges whose areas match the frequencies. Both show the same numbers.

Reach for the bar chart by default. The eye compares lengths along a common baseline far better than it compares wedge angles, so a bar chart lets a reader rank categories and judge differences at a glance. That is the standard criticism of pie charts: angles and areas are hard to compare, and once you pass three or four slices the chart turns into a guessing game. A pie chart works only when you have a handful of categories and the single point is that they make up a whole. When the message is which category is largest, use bars.

Key term

A frequency is the count of observations in a category or interval. A relative frequency is that count as a proportion or percentage of the total. The percentages across all categories add to 100.

Frequency distributions for numerical data

A numerical variable usually has too many distinct values to count one by one. With more than about 20 observations, group the values into intervals and count how many fall in each. The result is a frequency distribution. Take haemoglobin measured to one decimal place in 40 antenatal women, in g/dl. Group into 1 g/dl bands.

Haemoglobin (g/dl)No. of womenPercentage
9.0 to 9.9410.0
10.0 to 10.9820.0
11.0 to 11.91332.5
12.0 to 12.9922.5
13.0 to 13.9615.0
Total40100.0

Choosing the intervals

The grouping interval is a judgement call, and the table's usefulness depends on it. A few working rules:

  1. Find the lowest and highest values first, so you know the span you have to cover.
  2. Aim for 5 to 20 groups. Fewer observations want fewer groups. Too wide and you lose the shape; too narrow and single counts make the table jumpy and hard to read.
  3. Start each group on a round number, and keep every interval the same width when you can.
  4. Leave no gaps, and make the boundaries unambiguous. Labels like 9.0 to 9.9 and 10.0 to 10.9 are clear; labels like 9 to 10 and 10 to 11 leave a reader guessing where a value of exactly 10.0 belongs.

The histogram and why area matters

A histogram plots a frequency distribution: one rectangle per interval, drawn with no gaps because the scale underneath is continuous. When all intervals share the same width, the bar heights are the frequencies and the picture is honest. The shape is the same whether you plot counts or percentages.

Unequal widths are where people slip. If you merge the top two haemoglobin bands into one group of 12.0 to 13.9 with 15 women, that interval is twice as wide as the rest. Draw a bar of height 15 and it looks far taller than the truth. The fix is the rule that holds for every histogram: the area of each bar, not its height, represents the frequency. For a double-width interval you halve the height, so a frequency of 15 becomes a bar of height 7.5 spread across two units. Height then reads as frequency per unit of the scale, which is what keeps the comparison fair.

Watch out

Software draws equal-width bins by default and gets the heights right. The moment you hand-merge intervals, or plot pre-grouped data with one wide tail bin, you have to convert to frequency per unit (frequency divided by width) yourself. Plotting raw counts as heights over unequal widths is one of the most common misleading charts in clinical reports.

The shape of a distribution

Once the histogram is drawn, read its shape. Shape decides which summary and which test belong later, so name it early.

  • Symmetric. The two tails mirror each other and a single peak sits in the middle. Adult height and haemoglobin in a healthy population look like this. Symmetric data suit the mean and standard deviation.
  • Positively (right) skewed. A long tail stretches to the right toward high values. Hospital length of stay, parasite counts, and many lab assays behave this way: most patients cluster low, a few run far higher. The mean is dragged above the median.
  • Negatively (left) skewed. The long tail points left toward low values. Gestational age at birth is a clinical example, since most deliveries cluster near term and a smaller group of preterm births trails to the left.
  • Bimodal. Two separate peaks. This usually means the data mix two groups. A hormone level pooled across men and women, or a measurement taken on two different machines, can show two humps. A bimodal histogram is a prompt to ask what subgroups you have combined.

Skew is not a defect to hide. It is information. A right-skewed assay often becomes symmetric after a log transformation, which is exactly why you check the shape before choosing a method (module 6 returns to this).

Worked example 1: a histogram and a bar chart

Here is the full picture for the antenatal clinic: a histogram for the numerical variable (haemoglobin) and a bar chart for the categorical one (booking diagnosis), in both languages. The haemoglobin values are entered inline so the block runs on its own.

Draw a histogram of haemoglobin with 1 g/dl bands, then a bar chart of the diagnosis counts. Print the grouped counts to check the table.

hb <- c(11.2, 10.5, 12.1, 9.8, 13.0, 11.6, 10.9, 12.4, 11.0, 13.5,
        10.2, 11.8, 9.4, 12.9, 11.3, 10.7, 12.2, 11.5, 13.2, 10.4,
        11.9, 12.6, 11.1, 10.8, 12.0, 11.7, 9.9, 13.1, 11.4, 10.6,
        12.3, 11.2, 12.7, 10.3, 11.6, 12.1, 9.7, 13.4, 11.0, 13.7)
dx <- c(Anaemia = 84, Hypertension = 48, Diabetes = 40, Thalassaemia = 28)
hist(hb, breaks = seq(9, 14, 1))  # add labels
barplot(dx)                       # add a y-axis label
hist(hb, breaks = seq(9, 14, 1),
     xlab = "Haemoglobin (g/dl)", ylab = "No. of women", main = "")
barplot(dx, ylab = "No. of women", xlab = "Booking diagnosis")
table(cut(hb, breaks = seq(9, 14, 1), right = FALSE))
#  [9,10) [10,11) [11,12) [12,13) [13,14)
#       4       8      13       9       6

Draw a histogram of haemoglobin with 1 g/dl bands, then a bar chart of the diagnosis counts. Print the grouped counts to check the table.

import numpy as np
import matplotlib.pyplot as plt
hb = np.array([11.2, 10.5, 12.1, 9.8, 13.0, 11.6, 10.9, 12.4, 11.0, 13.5,
               10.2, 11.8, 9.4, 12.9, 11.3, 10.7, 12.2, 11.5, 13.2, 10.4,
               11.9, 12.6, 11.1, 10.8, 12.0, 11.7, 9.9, 13.1, 11.4, 10.6,
               12.3, 11.2, 12.7, 10.3, 11.6, 12.1, 9.7, 13.4, 11.0, 13.7])
labels = ["Anaemia", "Hypertension", "Diabetes", "Thalassaemia"]
counts = [84, 48, 40, 28]
plt.hist(hb, bins=np.arange(9, 15, 1))  # add labels
plt.show()
bins = np.arange(9, 15, 1)
plt.hist(hb, bins=bins, edgecolor="white")
plt.xlabel("Haemoglobin (g/dl)"); plt.ylabel("No. of women"); plt.show()

plt.figure()
plt.bar(labels, counts)
plt.ylabel("No. of women"); plt.xlabel("Booking diagnosis"); plt.show()

grouped, _ = np.histogram(hb, bins=bins)
print(grouped)
# [ 4  8 13  9  6]

Cumulative frequency, quantiles, and percentiles

A frequency distribution tells you how many fall in each band. A cumulative frequency distribution starts at the lowest value and adds up as you move right, so each point answers how many observations sit at or below this value. Plotted, it climbs as a step function from 0% to 100%, steep where values pile up and flat where they are sparse.

The payoff is reading percentiles straight off the curve. The kth percentile is the value below which k% of the data lie. Draw a horizontal line at k% across to the curve, then drop down to the value. Three percentiles get their own names. The median is the 50th percentile, the middle value with half the data on each side. The lower quartile is the 25th percentile and the upper quartile the 75th; together with the median they cut the data into four equal groups. The gap between the quartiles is the interquartile range, the spread of the middle half.

From the haemoglobin curve, the 50% line crosses near 11.55 g/dl, so about half the women sit below that. The 25% line lands near 10.8 and the 75% near 12.3. You can read any percentile the same way: the 10th for a low cut-off, the 90th for a high one. The cumulative curve shows the whole shape without needing to pick bin widths, which makes it a clean way to compare two distributions on one set of axes.

Key term

A percentile (or centile) is a value below which a stated percentage of observations fall. The median, lower quartile, and upper quartile are the 50th, 25th, and 75th percentiles. Quantiles is the general word for these equal-sized cut points.

Worked example 2: reading a percentile off the curve

Compute the quartiles for the haemoglobin data and draw the cumulative curve with the median marked, so you can see the 50% line meet the step.

Report the 25th, 50th, and 75th percentiles, then plot the cumulative distribution with the median line.

hb <- c(11.2, 10.5, 12.1, 9.8, 13.0, 11.6, 10.9, 12.4, 11.0, 13.5,
        10.2, 11.8, 9.4, 12.9, 11.3, 10.7, 12.2, 11.5, 13.2, 10.4,
        11.9, 12.6, 11.1, 10.8, 12.0, 11.7, 9.9, 13.1, 11.4, 10.6,
        12.3, 11.2, 12.7, 10.3, 11.6, 12.1, 9.7, 13.4, 11.0, 13.7)
quantile(hb, probs = c(0.25, 0.5, 0.75))  # the quartiles
# then plot ecdf(hb)
round(quantile(hb, probs = c(0.25, 0.5, 0.75)), 2)
#   25%   50%   75%
# 10.78 11.55 12.32
plot(ecdf(hb), xlab = "Haemoglobin (g/dl)",
     ylab = "Cumulative proportion", main = "")
abline(h = 0.5, lty = 2)   # the 50% line meets the curve at the median

Report the 25th, 50th, and 75th percentiles, then plot the cumulative distribution with the median line.

import numpy as np
import matplotlib.pyplot as plt
hb = np.array([11.2, 10.5, 12.1, 9.8, 13.0, 11.6, 10.9, 12.4, 11.0, 13.5,
               10.2, 11.8, 9.4, 12.9, 11.3, 10.7, 12.2, 11.5, 13.2, 10.4,
               11.9, 12.6, 11.1, 10.8, 12.0, 11.7, 9.9, 13.1, 11.4, 10.6,
               12.3, 11.2, 12.7, 10.3, 11.6, 12.1, 9.7, 13.4, 11.0, 13.7])
print(np.quantile(hb, [0.25, 0.5, 0.75]))  # the quartiles
# then build and plot the cumulative curve
q = np.quantile(hb, [0.25, 0.5, 0.75])
print(np.round(q, 2))
# [10.78 11.55 12.32]
x = np.sort(hb)
y = np.arange(1, len(x) + 1) / len(x)
plt.step(x, y, where="post")
plt.axhline(0.5, linestyle="--")   # 50% line meets the curve at the median
plt.xlabel("Haemoglobin (g/dl)"); plt.ylabel("Cumulative proportion"); plt.show()

The box plot in one line

A box plot packs those same quantiles into a compact shape. The box runs from the lower quartile to the upper quartile, a line inside marks the median, and the whiskers reach out to the extremes. Read the spread from the box length and the skew from where the median sits inside the box and how long each whisker is. A median shoved toward the bottom of the box with a long upper whisker says right-skewed. Box plots shine when you compare several groups side by side. Module 2 builds the summary statistics behind them.

Draw a horizontal box plot of the haemoglobin values.

hb <- c(11.2, 10.5, 12.1, 9.8, 13.0, 11.6, 10.9, 12.4, 11.0, 13.5,
        10.2, 11.8, 9.4, 12.9, 11.3, 10.7, 12.2, 11.5, 13.2, 10.4,
        11.9, 12.6, 11.1, 10.8, 12.0, 11.7, 9.9, 13.1, 11.4, 10.6,
        12.3, 11.2, 12.7, 10.3, 11.6, 12.1, 9.7, 13.4, 11.0, 13.7)
boxplot(hb)  # make it horizontal and label it
boxplot(hb, horizontal = TRUE, xlab = "Haemoglobin (g/dl)")
summary(hb)   # min, quartiles, median, max behind the box

Draw a horizontal box plot of the haemoglobin values.

import numpy as np
import matplotlib.pyplot as plt
hb = np.array([11.2, 10.5, 12.1, 9.8, 13.0, 11.6, 10.9, 12.4, 11.0, 13.5,
               10.2, 11.8, 9.4, 12.9, 11.3, 10.7, 12.2, 11.5, 13.2, 10.4,
               11.9, 12.6, 11.1, 10.8, 12.0, 11.7, 9.9, 13.1, 11.4, 10.6,
               12.3, 11.2, 12.7, 10.3, 11.6, 12.1, 9.7, 13.4, 11.0, 13.7])
plt.boxplot(hb)  # make it horizontal and label it
plt.boxplot(hb, vert=False)
plt.xlabel("Haemoglobin (g/dl)"); plt.show()
print(np.round(np.quantile(hb, [0, 0.25, 0.5, 0.75, 1]), 2))

Matching the display to the variable

The whole chapter reduces to one habit: let the variable type pick the plot.

Variable typeTablePlot
Categorical (nominal or ordinal)Frequency table with percentagesBar chart (pie only for a few parts of a whole)
Numerical (discrete or continuous)Grouped frequency distributionHistogram or box plot; cumulative curve for percentiles

Use a histogram on a numerical variable and a bar chart on a categorical one. The visible gap between bars in a bar chart is the signal that the categories do not sit on a continuous scale, which is exactly why a histogram has no gaps.

Common mistakes

  • Plotting unequal-width bins as raw heights. A merged or wide tail interval drawn at its raw count exaggerates that group. Convert to frequency per unit (count divided by width) so area, not height, carries the frequency.
  • Using a histogram for a categorical variable. A histogram needs an ordered continuous axis. For diagnosis, blood group, or district, you want a bar chart with gaps. Bars touching falsely suggest the categories flow into one another.
  • Ambiguous interval labels. Bands written 9 to 10, 10 to 11 leave it unclear where exactly 10.0 lands. Use 9.0 to 9.9 style labels, or state that intervals are closed on the left, so every value has one home.
  • Too many or too few bins. Two bins hide the shape; thirty bins on 40 points turn the histogram into noise. Aim for 5 to 20 and check that the shape is stable if you nudge the bin width.
  • Trusting a pie chart with many slices. Past three or four wedges, readers cannot rank them. Switch to a bar chart whenever the point is comparison rather than parts of a whole.

Tips

  • Plot every new variable before you summarise it. A histogram catches the impossible value (a haemoglobin of 1.2, a negative age) that a mean would quietly absorb.
  • Try two bin widths. If the shape holds, trust it. If it flips between one peak and two, look harder for subgroups before reporting.
  • Read skew straight off the picture: a long right tail means report the median and quartiles, not the mean, and consider a log scale (module 6).
  • A bimodal histogram is a question, not an answer. Split by sex, site, or machine and the two peaks often separate.
  • Always label axes and units, and never start a frequency axis above zero. A truncated axis can make a tiny difference look like a cliff.

A district report groups gestational age into five bands of 1 week each plus a final band "32 weeks or less" that is 6 weeks wide and holds 30 births. The chart draws every bar at its raw count. What is wrong?

  • The wide band is six times the width of the others, so plotting its raw count of 30 makes the preterm group look far larger than it is; its height should be the count divided by its width.
  • Nothing is wrong, because the band still holds a real count of 30 births.
  • Gestational age is categorical, so the chart should have been a pie chart instead.
In a histogram, area represents frequency. A band six times as wide drawn at full height has six times the area it should, so it overstates the preterm group. The fix is frequency per unit of width: 30 divided by 6 gives a height of 5, comparable to the single-week bars. Gestational age is numerical, so a histogram is the right plot; the error is the unequal width, not the chart type.

You draw the cumulative frequency curve of birthweight for 500 babies. Where do you read the median?

  • Find 50% on the vertical axis, go across to the curve, then drop down to the birthweight on the horizontal axis.
  • Find the birthweight halfway between the lightest and heaviest baby on the horizontal axis.
  • Take the height of the curve at the midpoint of the birthweight range.
The cumulative curve maps each birthweight to the percentage of babies at or below it. The median is the 50th percentile, so you enter at 50% on the vertical axis and read off the matching weight. The midpoint of the weight range is the middle of the scale, not the middle of the data, and it ignores how the babies are distributed. The same method gives any percentile: enter at k% and read across then down.
← PreviousVariables, types, and the question behind the dataNext →Means, standard deviations and standard errors
On this page
  • Counting categories
  • Bar charts and pie charts
  • Frequency distributions for numerical data
  • Choosing the intervals
  • The histogram and why area matters
  • The shape of a distribution
  • Worked example 1: a histogram and a bar chart
  • Cumulative frequency, quantiles, and percentiles
  • Worked example 2: reading a percentile off the curve
  • The box plot in one line
  • Matching the display to the variable
  • Common mistakes
  • Tips