Stats & Product Analytics for Analysts
Back to Stats You Can Actually Use

Spotting the Shape

Histograms tell stories. Read four common distribution shapes correctly and pick the right summary stats for each.

6 minutesReading
🎯 Free Guest Mode: You are learning for free. Sign in to save your completion progress and quiz answers.

Every metric you ever analyze has an underlying distribution. Most analysts never bother to look - and then they're confused when their "average" doesn't match what users actually experience. This lesson is about training your eye.

The four shapes you'll see most

Normal (bell-shaped) - symmetric, single hump. Most data clustered around the mean, tapering off in both directions. Mean = median = mode. Examples: adult height, IQ scores, measurement errors.

Log-normal - right-skewed with a long tail of high values. Mean > median. Mean is dragged up by the tail. Examples: income, house prices, time-to-complete a task, customer lifetime value.

Long-tailed / power-law - extreme right-skew. A tiny fraction of observations account for most of the mass. Examples: city populations, website traffic per page, revenue per customer in B2B.

Bimodal - two distinct humps. Usually a sign you're mixing two populations. Examples: order values from a freemium product (free users near zero, paid users much higher), session durations (bouncers vs engaged users).

Spotting shapes & log transforms in Python

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

# Generate right-skewed Customer Lifetime Value (LTV) data
np.random.seed(42)
raw_ltv = np.random.lognormal(mean=4.5, sigma=1.2, size=5000)
df = pd.DataFrame({"ltv": raw_ltv})

# Log-transformation to normalize skewed distributions
df["log_ltv"] = np.log1p(df["ltv"])

print("--- Raw LTV vs Log-Transformed LTV ---")
print(f"Raw LTV: Mean = ${df['ltv'].mean():.2f}, Median = ${df['ltv'].median():.2f}")
print(f"Log LTV: Mean = {df['log_ltv'].mean():.2f}, Std = {df['log_ltv'].std():.2f}")

# Plotting distribution comparison
fig, axes = plt.subplots(1, 2, figsize=(10, 4))
sns.histplot(df["ltv"], kde=True, ax=axes[0], color="crimson")
axes[0].set_title("Raw LTV (Right-Skewed / Log-Normal)")
axes[0].set_xlabel("LTV ($)")

sns.histplot(df["log_ltv"], kde=True, ax=axes[1], color="teal")
axes[1].set_title("log1p(LTV) (Transformed to Bell Shape)")
axes[1].set_xlabel("Log(LTV + 1)")
plt.tight_layout()
# plt.show()

Why the shape changes which stats you trust

ShapeUseAvoid
NormalMean + stdNothing - both work
Log-normalMedian + percentiles, or log-transform then use meanRaw mean (it lies)
Long-tailedPercentiles only (p50, p90, p99)Mean, std (both useless)
BimodalSplit groups, analyze separatelyAny single summary statistic

Takeaway

Before you summarize any metric:

  1. Plot its histogram (sns.histplot(df['col'], kde=True)).
  2. Identify the shape.
  3. Pick the summary statistic that fits the shape.

Two lines of Python save you from a meeting where someone says "but the average is fine?!" and you have to explain that the average is meaningless because the distribution is bimodal.

Loading progress...