Statistical Data Analysis and Inference › Descriptive Statistics for Financial Returns
Quantitative FinanceAll chapters Statistical Data Analysis and Inference › Descriptive Statistics for Financial ReturnsOne section · Sections in this chapter2 / 9 ↓
Reading tools Definitions & inline links
Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.
Article links
Make inline references clickable
Descriptive statistics summarize the essential features of a dataset. For financial returns, we focus on four moment-based summaries: mean, volatility, skewness, and kurtosis. Together they describe complementary aspects of a return distribution.
Returns: The Foundation of Financial Analysis
Raw prices are difficult to compare across assets and time periods. A $10 move in a $100 stock is very different from a $10 move in a $1000 stock. The first represents a 10% change, while the second is only 1%. Returns normalize price changes, making relative moves comparable. Returns are often closer to stationary than price levels, but the transformation does not guarantee stationarity; that property must be checked for the series, horizon, and sample in question.
The choice to work with returns rather than prices can help with a basic statistical requirement: stationarity. Price levels often contain trends, so their distribution changes over time. Return means, variances, and dependence can also change across regimes, however. Model-based inference and forecasting therefore require checks for structural breaks, serial dependence, and time-varying volatility rather than an automatic assumption that returns are stationary.
Simple returns measure the percentage change in price:
where:
- : simple return at time (expressed as a decimal, e.g., 0.05 for 5%)
- : price at time
- : price at time
Log returns (continuously compounded returns) are defined as:
where is the log return at time . Here we use strictly positive prices for log returns; the simple-return formula requires a nonzero lagged price. The logarithm turns the price ratio into an additive measure.
Log returns are additive over time and approximately equal to simple returns for small values. Their additivity is convenient for time aggregation, while simple returns remain natural for portfolio accounting and cross-sectional aggregation. The appropriate convention depends on the analysis.
The distinction between simple and log returns deserves careful attention because it affects how we aggregate returns over time. With simple returns, compounding over multiple periods requires multiplication: a 10% gain followed by a 10% loss does not return you to your starting point (you end up at 99% of your original value). Log returns, however, are additive. The two-day log return is simply the sum of the individual daily log returns. Both definitions are widely used; the appropriate choice depends on whether the task emphasizes time aggregation, portfolio accounting, or another convention.
Let's calculate returns from price data:
import numpy as np
# Simulated daily closing prices
np.random.seed(42)
prices = 100 * np.cumprod(1 + np.random.normal(0.0005, 0.02, 252))
# Calculate simple and log returns
simple_returns = np.diff(prices) / prices[:-1]
log_returns = np.diff(np.log(prices))This seeded price path is positive. The normal simple-return generator is a fixed numerical illustration, not a globally positive price model: a normal draw can be below -1. The later log-return intervals illustrate inference conditional on a real-valued i.i.d. log-return model with the required population moments, not a guarantee about every possible path of this generator.
Number of daily returns: 251 Simple return (first day): -0.0023 Log return (first day): -0.0023 Difference: 0.000003
For small percentage moves, simple and log returns are nearly identical. The difference becomes material for larger moves: a 20% simple gain corresponds to a log return of about 18.2%, while a 20% simple loss corresponds to about -22.3%. The appropriate convention depends on the calculation rather than on a universal frequency threshold.
The First Moment: Mean Return
The arithmetic mean of returns measures the average performance over the sample period. This is the most intuitive statistic: add up all the returns and divide by how many there are. It summarizes the sample's first moment, but it need not equal an observed return, the most common return, or a value representative of a skewed or multimodal distribution.
where:
- : sample mean return
- : number of observations in the sample
- : individual return observation at time
The summation notation indicates that we add up all returns from the first observation () through the last (). The factor in front converts this sum into an average. This formula produces a single measure of central tendency, but observations need not cluster around it.
Using the common 252-trading-day convention, we annualize daily mean log returns by multiplying by 252:
Log returns add over time. With a common daily mean log return of 0.04%, the expected sum over 252 trading days is 252 × 0.04% = 10.08% in log-return units. This is not automatically expected compounded simple growth. For simple returns, multiplying the daily mean by 252 defines a linear annualized arithmetic mean, not the compounded annual return. Small daily values alone do not guarantee that compounding over a year is negligible.
# Calculate mean returns
mean_daily = np.mean(log_returns)
mean_annual = mean_daily * 252Mean daily log return: 0.000199 (0.0199%) Annualized mean log return: 0.0500 (5.00%)
The sample mean estimates the population mean under a specified return model; it is not automatically an investor's forward expected reward. Historical sample means are unstable, and a few extreme days can shift the estimate significantly. This instability arises because the mean is computed by adding all observations, giving equal weight to every day including the outliers. A single day with a 10% move (positive or negative) can shift the annual mean estimate by several percentage points.
The Second Moment: Volatility
Volatility measures the dispersion of returns around the mean. It quantifies risk in the most commonly used sense: how much do returns deviate from what we expect? A high-volatility asset tends to have larger deviations around its mean, while a low-volatility asset tends to have smaller deviations. Dispersion alone does not determine whether returns are forecastable. This concept of risk as variability is central to modern portfolio theory.
For at least two observations, the sample variance is:
where:
- : sample variance
- : degrees of freedom; for i.i.d. observations with a common finite variance, Bessel's correction gives the usual unbiased sample-variance estimator
- : deviation of each return from the sample mean
- : number of observations in the sample
The logic behind variance is straightforward. First, we calculate how far each return deviates from the mean by computing . Some deviations are positive (returns above average) and some are negative (returns below average). If we simply averaged these deviations, the positives and negatives would cancel out, giving us zero, which is not useful as a dispersion measure. By squaring each deviation, we ensure all terms are nonnegative, and larger deviations receive disproportionately more weight.
For i.i.d. observations with a common finite variance, we use in the denominator rather than to obtain the usual unbiased estimator of that population variance. This adjustment, known as Bessel's correction, accounts for the fact that we estimated the mean from the same data. When we calculate deviations from the sample mean, the deviations are constrained to sum to zero, removing one algebraic degree of freedom. Under those sampling conditions, dividing by accounts for that lost degree of freedom and makes the sample variance unbiased for the common population variance.
Volatility is the square root of variance, annualized by multiplying by :
where:
- : annualized volatility
- : daily volatility (standard deviation of daily returns)
- : scaling factor based on approximately 252 trading days per year (the square root arises because variance scales linearly with time, so standard deviation scales with the square root)
The square root scaling deserves explanation because it differs from how we annualize means. When we sum independent random variables, their variances add. If daily variance is , then annual variance (summing 252 independent daily variances) is . Taking the square root to convert back to standard deviation gives us . This is why volatility scales with the square root of time rather than linearly.
# Calculate volatility
std_daily = np.std(log_returns, ddof=1) # ddof=1 for sample std
std_annual = std_daily * np.sqrt(252)Daily volatility: 0.019319 (1.9319%) Annualized volatility: 0.3067 (30.67%)
Volatility is central to finance: it appears in option pricing formulas, portfolio optimization, and risk metrics like Value at Risk. It is often more forecastable than mean return, but it remains time-varying and can shift sharply across regimes. Historical volatility is therefore an estimate with a horizon and model attached, not a stable constant.
The Third Moment: Skewness
Skewness measures one form of asymmetry in a distribution. Mean and variance describe center and spread but do not describe asymmetry, even though both are also defined for many asymmetric distributions. The third standardized moment records whether large standardized deviations contribute more on the positive or negative side.
Define the sample central moments with denominator as
The moment coefficient of sample skewness used by scipy.stats.skew when bias=True is
where:
- : second central sample moment using denominator
- : third central sample moment using denominator
- : dimensionless moment coefficient; SciPy can apply a finite-sample bias correction with
bias=False
The coefficient requires . A constant sample has zero dispersion, so its standardized skewness is undefined.
Understanding this formula requires recognizing the role of each component. Subtracting the mean centers each return, and dividing by expresses the deviation on a dimensionless scale. Cubing those standardized deviations preserves their signs and gives large deviations more influence.
The key step is raising these standardized returns to the third power. Unlike squaring (which makes everything positive), cubing preserves the sign: negative values remain negative after cubing, while positive values remain positive. Cubing also amplifies extreme values more than moderate ones. A z-score of 3 contributes 27 to the sum, while a z-score of 2 contributes only 8. This makes skewness heavily influenced by the distribution's tails, especially which tail extends farther.
The sign of the third standardized moment is interpreted as follows, with the tail descriptions serving as heuristics rather than complete tail orderings:
- Zero skewness: The third central moment is zero. Symmetric distributions with positive variance and a finite third moment have zero skewness, but zero skewness does not imply symmetry.
- Negative skewness: The third standardized moment is negative; this often accompanies a more influential left tail.
- Positive skewness: The third standardized moment is positive; this often accompanies a more influential right tail.
from scipy import stats
skewness = stats.skew(log_returns)Skewness: 0.2353 Interpretation: Sample skewness is within the chosen ±0.5 threshold



Some equity return samples exhibit negative skewness, but its sign and magnitude vary by asset, horizon, estimator, and market regime. A symmetric model cannot represent an asymmetric fitted loss distribution, so risk analysis should compare the relevant left-tail probabilities or quantiles directly rather than infer every tail probability from skewness alone.
The Fourth Moment: Kurtosis
Kurtosis is the fourth standardized moment and is especially sensitive to large standardized deviations. While skewness records one form of asymmetry, kurtosis summarizes how strongly those large deviations influence the fourth moment. It does not, by itself, rank tail probabilities against a normal distribution at every threshold.
Using the same central moments, the raw moment coefficient of kurtosis is
where:
- : fourth central sample moment using denominator
- : square of the second central sample moment
- : raw sample moment coefficient; finite-sample bias corrections use a different formula
Like skewness, this standardized coefficient requires positive dispersion: . Population kurtosis also requires a finite fourth moment.
The fourth power creates an even more extreme amplification of outliers than the cube used in skewness. A standardized return of 3 (three standard deviations from the mean) contributes 81 to the sum, while a standardized return of 4 contributes 256. A single extreme observation can therefore increase the kurtosis estimate dramatically. This sensitivity is relevant when studying extremes, but the fourth moment is not a complete measure of tail behavior and has considerable sampling variability.
A normal distribution has a kurtosis of 3. Excess kurtosis subtracts 3 to make the normal distribution the baseline:
where:
- : the raw kurtosis value computed from the sample
- : the kurtosis of the standard normal distribution, used as the reference point (subtracting it makes the normal distribution have excess kurtosis of zero)
By subtracting 3, we create a measure where zero is the normal-distribution benchmark. Positive excess kurtosis means a larger fourth standardized moment than the normal benchmark; it does not, by itself, guarantee larger tail probability at every threshold. scipy.stats.kurtosis reports excess kurtosis by default because its fisher argument defaults to True; other software may use a different convention.
The usual labels summarize the fourth-moment comparison, but they do not replace a direct tail-probability comparison:
- Zero: Same raw fourth standardized moment as the normal benchmark (mesokurtic)
- Positive: Larger fourth standardized moment than the normal benchmark (leptokurtic)
- Negative: Smaller fourth standardized moment than the normal benchmark (platykurtic)
kurtosis = stats.kurtosis(log_returns) # scipy returns excess kurtosis by defaultExcess kurtosis: 0.4562 Interpretation: Sample excess kurtosis is within the chosen ±1 descriptive threshold

Many return samples have positive sample excess kurtosis, but the estimate depends on the asset, frequency, window, and estimator. A large estimate is evidence that the sample's fourth standardized moment differs from the normal benchmark. Establishing how often specified market moves occur requires a named dataset and horizon, with any model and dependence assumptions stated explicitly; the synthetic sample used in this chapter cannot establish those empirical frequencies.
Visualizing the Distribution
Let's visualize these statistics with a histogram and overlay a normal distribution for comparison:

Summary Statistics Table
Let's create a function that computes all descriptive statistics in one place:
def validate_return_sample(returns):
"""Require a finite, one-dimensional, nonconstant sample with n >= 2."""
values = np.asarray(returns, dtype=float)
if values.ndim != 1 or values.size < 2:
raise ValueError(
"returns must be a one-dimensional sample with at least two observations"
)
if not np.all(np.isfinite(values)):
raise ValueError("returns must contain only finite observations")
dispersion = np.std(values, ddof=1)
if not np.isfinite(dispersion) or dispersion <= 0:
raise ValueError(
"returns must have positive, numerically finite sample dispersion"
)
return values
def validate_periods_per_year(periods_per_year):
"""Require a positive, finite scalar annualization factor."""
factor = np.asarray(periods_per_year, dtype=float)
if factor.ndim != 0 or not np.isfinite(factor) or factor <= 0:
raise ValueError("periods_per_year must be a positive finite scalar")
return float(factor)
def compute_return_statistics(
returns, periods_per_year=252, return_convention="simple"
):
"""Summarize supplied returns; the convention labels them, not converts them."""
returns = validate_return_sample(returns)
periods_per_year = validate_periods_per_year(periods_per_year)
if return_convention not in {"simple", "log"}:
raise ValueError("return_convention must be 'simple' or 'log'")
std_return = np.std(returns, ddof=1)
result = {
"Count": len(returns),
"Mean (daily)": np.mean(returns),
"Mean (annualized, linear)": np.mean(returns) * periods_per_year,
"Std Dev (daily)": std_return,
"Std Dev (annualized)": std_return * np.sqrt(periods_per_year),
"Skewness": stats.skew(returns),
"Excess Kurtosis": stats.kurtosis(returns),
"Min": np.min(returns),
"Max": np.max(returns),
f"Sharpe Ratio ({return_convention} returns, zero reference)": (
np.mean(returns) * periods_per_year
)
/ (std_return * np.sqrt(periods_per_year)),
}
if not np.all(np.isfinite(list(result.values()))):
raise ValueError(
"the requested statistics are not numerically finite for these inputs"
)
return result
summary = compute_return_statistics(log_returns, return_convention="log")Return Statistics Summary ---------------------------------------- Count: 251 Mean (daily): 0.000199 Mean (annualized, linear): 0.050049 Std Dev (daily): 0.019319 Std Dev (annualized): 0.306673 Skewness: 0.235344 Excess Kurtosis: 0.456209 Min: -0.053290 Max: 0.074694 Sharpe Ratio (log returns, zero reference): 0.163199
The helper requires at least two finite, one-dimensional observations and positive sample dispersion. It raises a descriptive error for a constant sample because the standardized moments and ratio are undefined. return_convention labels the supplied data; it does not transform simple returns into log returns.
The ratio included above is explicitly based on log returns with a zero reference. The conventional ex-post Sharpe ratio uses arithmetic excess returns relative to a matching-period risk-free return; the worked example later uses simulated simple returns and a zero reference. Write the matching-period differential return as . The annualized ex-post ratio is:
where:
- : sample mean of matching-period differential returns
- : positive sample standard deviation of those differential returns
- : annualization factor for the stated daily-data convention
Subtract the reference before computing dispersion. With a constant daily reference , equals the portfolio's sample standard deviation and the linear annualized reference is . With a time-varying reference, the differential-return volatility generally differs from portfolio-return volatility. Square-root annualization assumes uncorrelated differential returns with a common variance; it is not an exact volatility formula for compounded annual wealth.
The Sharpe ratio addresses a fundamental question in investing: how much return are you earning per unit of risk taken? A strategy with high returns might simply be taking excessive risk. The Sharpe ratio normalizes performance by volatility, allowing comparisons across strategies when the return horizon and conventions match. A Sharpe ratio of 1.0 is dimensionless: excess return equals volatility numerically at the same horizon. Whether that value is attractive depends on the horizon, estimation method, peer set, costs, and constraints.