Three-dimensional bar chart of a bell-shaped distribution, illustrating how z-scoring is used to standardize data

How to Standardize Data with Z-Scoring

Data transformation is one of those steps that quietly shapes the quality of downstream analysis. When features operate on different scales, even well-designed models can behave in unexpected ways. Z-scoring, also known as standardization, is a simple and widely used approach that brings numerical features onto a common scale while preserving their underlying structure. This article shows how to standardize data in Python and why the scaler should be fit on the training data only.

What Z-Scoring does

Z-score standardization transforms each data point into a standardized value that reflects its distance from the mean of the feature, measured in standard deviations. A positive z-score indicates a value above the mean, while a negative z-score indicates a value below it. After transformation, the feature has a mean of zero and a standard deviation of one on the sample the scaler was fitted on. Applied to new data with those stored parameters, the values are recentred on the training mean rather than on their own, so they will generally not have mean zero and unit spread.

This transformation does not change the shape of the distribution. Correlations and the ordering of observations remain intact, and differences between values are preserved up to a constant factor of one divided by the standard deviation. Ratios between values are not, because subtracting the mean moves the origin. What changes is the center and the scale, which makes features comparable in spread.

Figure 1 follows a resting heart rate through this transformation. Drag the marker along the scale, or jump to the reference points, and the panel below recomputes the z-score one step at a time. The reading keeps its place among the others. Only the axis beneath it changes from beats per minute to standard deviations.

Figure 1

From raw values to z-scores

A z-score re-expresses a value as its distance from the mean, counted in standard deviations. Below is a resting heart rate. Standardizing leaves the reading itself alone and changes only the scale you describe it on.

84 bpm
What it means

A resting heart rate of 84 bpm sits 1.00 standard deviation above the mean of 72 bpm. On the standardized scale its value is +1.00.

Here the mean is μ = 72 bpm and the standard deviation is σ = 12 bpm (population standard deviation, dividing by n, as scikit-learn’s StandardScaler does).

Selected value 84 bpm standardizes to a z-score of positive 1.00.
Figure 1. The two axes describe the same ten heart-rate readings. The top axis reads them in beats per minute, and the bottom axis reads them in standard deviations from the mean, where 0 marks the mean and each unit is one standard deviation. Because the transformation only relabels the axis, the order of the readings never changes.

The Z-Score formula

The z-score for a single observation is defined as:
z = (x − μ) / σ

where x is the original value, μ is the mean of the feature, and σ is the standard deviation. The arithmetic is trivial. What it does to a modeling workflow is not.

Throughout this article, σ is the population standard deviation, which divides by n and matches the convention used by scikit-learn’s StandardScaler. A sample standard deviation divides by n minus 1 and returns a larger value, though the difference is negligible unless the dataset is small.

A practical best practice is to compute μ and σ using only the training data, then apply the same parameters to validation and test sets. This avoids leaking information across data splits and keeps model evaluation honest.

Figure 2 sets this correct workflow beside a common mistake. Use the toggle to fit the scaler on the training data alone, then on the training and test data together. The second option lets the test set influence the mean and standard deviation. That changes the z-scores it receives, and leaks information into the evaluation.

Figure 2

Fit the scaler on the training data only

The mean and standard deviation should be learned from the training set, then reused to standardize the test set. Switch the workflow below to see what changes when test data is allowed to influence those statistics.

Workflow
Correct workflow. The scaler learns μ and σ from the training set, then applies them to the test set. The test values are scored with statistics they did not help create, so the evaluation reflects genuinely unseen data.
Scaler statistics

Fitted on: the training set only

μ = 50.0 years

σ = 8.0 years

Test set, standardized

56 years → +0.75

62 years → +1.50

68 years → +2.25

Correct workflow selected. The scaler is fitted on the training set only, giving mu 50.0 and sigma 8.0 years.
Figure 2. The mean and standard deviation are model parameters that must be estimated from training data alone. When the test set is added to the fit, μ shifts from 50.0 to 54.5 years and σ from 8.0 to 9.1 years, so the same three test ages receive different z-scores. The evaluation no longer measures performance on data the preprocessing has never seen.

Handling different feature scales

Many real datasets mix variables with very different units. Age might range from tens to hundreds, while laboratory values or financial ratios may be fractions or large integers. Without standardization, features with larger numeric ranges tend to dominate optimization and distance calculations. Z-scoring places all features on a comparable scale, so that no single variable has a wider spread than the rest simply because of its units. That puts the spreads on an equal footing, though a standardized feature can still dominate a distance or an optimization.

Supporting distance-based methods

Algorithms that rely on distances or similarities are particularly sensitive to scale. Methods such as k-nearest neighbors, k-means clustering, and hierarchical clustering implicitly treat one unit of any feature as comparable to one unit of any other. Z-score standardization gives every feature the same spread, so no feature dominates a distance simply because it was measured on a larger scale. Equal spread is not the same as equal influence, and whether an equal-spread feature space is the intended one remains a modeling assumption.

Enabling meaningful comparisons

Standardized values support comparisons across features, and sometimes across datasets. A z-score of 2 carries the same interpretation regardless of the original unit and it represents a value two standard deviations above the mean. That interpretation holds in standard deviations only. Equal z-scores mean equal distance from each feature’s own mean. Whether they also mean equal rarity, or the same percentile, depends on the two distributions sharing a shape. Comparing across datasets asks for more still, namely that both means and standard deviations were estimated on comparable populations. That common scale is useful for downstream model development.

Practical considerations

Z-scoring assumes that the mean and standard deviation are meaningful summaries of the data. For heavily skewed distributions or features with extreme outliers, robust alternatives such as median-based scaling may be more appropriate. In practice, inspecting distributions before standardization often reveals whether z-scoring is a good fit. Another important detail is interpretability. While models may perform better on standardized data, coefficients are expressed per standard deviation of their feature and often need to be reported in the original units. Keeping track of scaling parameters makes it easy to reverse the transformation when needed.

A constant feature is a special case. Its standard deviation is zero, so the z-score formula would divide by zero. scikit-learn’s StandardScaler guards against this by treating the scale as 1, which returns a column of zeros for that feature.

Figure 3 shows what standardizing does to distributions of different shapes. Switch between a roughly symmetric feature, a right-skewed feature, and a feature with a single outlier. In every case the mean moves to 0 and the standard deviation to 1, yet the shape of the distribution and the order of the values stay the same. A skewed feature stays skewed, and an outlier stays extreme, which is exactly why the shape of a feature tells you how well its mean and standard deviation summarize it.

Figure 3

What standardization changes and what it keeps

Standardizing moves the mean to 0 and makes one unit equal to one standard deviation. The shape of the distribution and the order of the values do not change. Switch between distribution shapes to see what survives the transformation.

Distribution shape
Inspect a value Select a point to read its value
Raw mean (μ)
214.3 mg/dL
Raw SD (σ)
39.6 mg/dL
Standardized mean
0.00
Standardized SD
1.00
What changes. The mean moves to 0 and the standard deviation becomes 1. On the lower axis, every value is now measured in standard deviations.
What stays the same. The order of the values and the shape of the distribution are unchanged. Standardizing changes the units while the structure of the data stays intact.
This roughly symmetric shape stays symmetric. Standardizing rewrites the axis and leaves the pattern of the data untouched.
Showing a roughly symmetric distribution. Raw mean 214.3 mg/dL, raw standard deviation 39.6 mg/dL. After standardizing, the mean is 0 and the standard deviation is 1.
Figure 3. The same points are shown against two axes. The upper axis reads cholesterol in mg/dL, and the lower axis reads the identical positions in standard deviations from the mean. Because standardizing only relabels the axis, the shape and the ordering are preserved. A skewed feature stays skewed and an outlier stays extreme, which is why the mean and standard deviation, and therefore z-scores, respond to unusual values. The three shapes are illustrative distributions drawn for this figure, standing in for the cholesterol column of the heart disease dataset.

Example: Standardizing continuous variables

This notebook focuses on a small set of continuous variables from a cardiovascular dataset and prepares them for further analyses. This notebook is intentionally lightweight. The goal is to keep the data and plots easy to inspect, and to make it straightforward to extend with correlation heatmaps or pairwise scatterplots later.

Notebook Overview:

  • Load a local CSV (heart.csv) into a pandas DataFrame (this load is not among the code blocks below, which begin from a DataFrame named df that already holds the file)
  • Select a few continuous features of interest
  • Visualize feature distributions before and after standardization

Data sources

Heart Failure Prediction Dataset gathers 11 routinely collected patient features and asks whether heart disease is present. It is aimed at early detection and risk stratification. It merges five well-known heart disease cohorts into one harmonized table. Removing duplicates leaves 918 unique patient observations. The variables span demographics, clinical readings, electrocardiogram (ECG), exercise and laboratory results. Research on cardiovascular risk modeling and clinical decision support leans on it heavily.
Further information about the dataset can be found here: https://www.kaggle.com/datasets/fedesoriano/heart-failure-prediction

Import dependencies

The imports below cover three needs:

  • Data handling: pandas for loading and slicing the dataset
  • Preprocessing utility: StandardScaler for a quick, consistent standardization of selected columns
  • Visualization: matplotlib for figure setup and seaborn for histogram and kernel density estimate (KDE) plots

If this notebook is being run in a fresh environment, make sure the standard scientific Python stack is available (pandas, matplotlib, seaborn, scikit-learn).

Python
import sys
import os
import seaborn as sns
import matplotlib.pyplot as plt
from sklearn.preprocessing import StandardScaler
import pandas as pd

Define variables of interest and apply a simple filter

A focused variable list keeps exploratory plots readable and avoids mixing data types.

In this notebook:

  • continuous_variables defines the numeric columns to visualize.
  • A quick plausibility filter is applied to RestingBP to reduce the influence of extreme values that can dominate plots and obscure the central mass of the distribution.

This is a pragmatic cleaning step for visualization. Any downstream modeling workflow should document filters like this and revisit thresholds if domain requirements change.

Python
continuous_variables = ["Age", "RestingBP", "MaxHR"]
df = df[(df["RestingBP"] >= 50) & (df["RestingBP"] <= 200)]

Plot feature distributions before standardizing

Before changing any columns, it helps to look at the raw distributions.

This block:

  • Creates one histogram per variable (plus a KDE overlay)
  • Uses a consistent bin count so that differences are visually comparable
  • Produces a quick scan for issues like heavy tails, multi-modality, or suspicious spikes

The intent here is just to build intuition about what the data looks like prior to further analysis.

Python
# Set up the figure and axes
num_vars = len(continuous_variables)
fig, axes = plt.subplots(1, num_vars, figsize=(6 * num_vars, 4))

# Ensure axes is iterable even if there's only one plot
if num_vars == 1:
    axes = [axes]

# Plot histograms with KDE for each continuous variable
for ax, variable in zip(axes, continuous_variables):
    sns.histplot(data=df, x=variable, kde=True, bins=30, ax=ax)
    ax.set_title(f'Distribution of {variable}')
    ax.set_xlabel(variable)
    ax.set_ylabel('Frequency')

plt.tight_layout()
plt.show()
Figure 4

Raw distributions before standardizing

Histograms Non-standardized Z-Scoring
Figure 4. One histogram per continuous variable, drawn with 30 bins and a KDE overlay by the code above, after the plausibility filter keeps RestingBP between 50 and 200. Reading the raw shapes comes first, because z-scoring summarizes a feature by its mean and standard deviation, and the shape of a feature tells you how well that summary fits. Age is broadly symmetric and centers in the mid-fifties, MaxHR spreads widely with a mild left tail, and RestingBP is heavily rounded, with readings piling up on 120, 130 and 140 and a thin right tail out to 200.

Apply standardization to the selected continuous variables

Here the notebook creates a copy of the DataFrame and applies StandardScaler to the columns in continuous_variables.

Implementation notes:

  • df_standardized = df.copy() keeps the original values available for reference.
  • Only the selected continuous columns are transformed, leaving the rest of the dataset untouched.
  • The fitted scaler is kept in the scaler object, which is what makes the transform reversible via scaler.inverse_transform. Do not carry its stored mean and standard deviation into a modeling workflow. They were fitted here on the full DataFrame. Reusing them would let test rows shape the scaling parameters, which is the leakage Figure 2 warns about. If this notebook is later extended with train/test splits, fit a fresh scaler on the training split alone.

This section is deliberately brief and procedural, focusing on what the code is doing rather than the general theory behind rescaling.

Python
# Create a scaler object
scaler = StandardScaler()

# Fit and transform the continuous variables
df_standardized = df.copy()
df_standardized[continuous_variables] = scaler.fit_transform(df[continuous_variables])

Verify the updated feature distributions

This block repeats the same histogram workflow, but uses the updated DataFrame (df_standardized).

Python
# Now plot the histograms as before but with the standardized data

# Set up the figure and axes
num_vars = len(continuous_variables)
fig, axes = plt.subplots(1, num_vars, figsize=(6 * num_vars, 4))

# Ensure axes is iterable even if there's only one plot
if num_vars == 1:
    axes = [axes]

# Plot histograms with KDE for each continuous variable in the standardized data
for ax, variable in zip(axes, continuous_variables):
    sns.histplot(data=df_standardized, x=variable, kde=True, bins=30, ax=ax)
    ax.set_title(f'Distribution of Standardized {variable}')
    ax.set_xlabel(variable)
    ax.set_ylabel('Frequency')

plt.tight_layout()

# Save the figure as a PNG file
fig.savefig('z_histograms.png')

# Display the plot
plt.show()
Figure 5

The same shapes after standardizing

Histograms After Standardization Z-Score
Figure 5. The same three variables re-plotted from the standardized copy of the DataFrame, with the same 30 bins and KDE overlay. Every bar keeps its height and its neighbors, so the distributions are unchanged. What moves is the horizontal axis, from the original units to standard deviations, with 0 now marking each feature’s mean. That is what a linear rescaling does, and it is why a standardized feature keeps its shape: RestingBP keeps its rounding spikes and its right tail, which here reaches past z = 3.

We can confirm that the standardization did not change the fundamental shape of the variable distributions. However, the three standardized columns are now centered to a mean of 0 and a standard deviation of 1, while the rest of the DataFrame is unchanged.

Summary

Key Takeaways

Only a shift and a scale

Subtracting the mean and dividing by the standard deviation moves the center to 0 and the spread to 1. Nothing is reshaped: skew, multi-modality and outliers all survive, and the ordering of the observations is unchanged.

Fit on the training data only

The mean and standard deviation are estimated parameters. Fit them on the training split, then apply them to validation and test. Figure 2 shows the cost: adding the test set to the fit moves μ from 50.0 to 54.5 years and σ from 8.0 to 9.1.

Scale matters most to distance-based methods

k-nearest neighbors, k-means and hierarchical clustering compare distances across features, so a variable measured in hundreds can drown out one measured in fractions. Standardizing equalizes the spread of each feature, and equal spread still leaves each feature a different amount of influence on the fit.

An outlier stays an outlier

An extreme value still sits far from the mean afterwards, and it inflated the σ that every other value was divided by. For heavily skewed features, or features carrying extreme outliers, median-based scaling may be the better summary.

Same scale does not mean same rarity

A z of 2 means two standard deviations above the mean whatever the original unit. Two features share a percentile at the same z only when their distributions share a shape. Comparing z across datasets asks for more still, namely that both were fitted on comparable populations.

Keep the scaler to reverse the transform

A coefficient fitted on standardized data is expressed per standard deviation of its feature rather than per original unit. Holding the fitted μ and σ makes the transform reversible and keeps results reportable in years, mmHg or bpm.

Data & License

Dataset

Heart Failure Prediction Dataset by fedesoriano (Kaggle, 2021), combining five UCI heart-disease databases over 11 shared clinical features (918 records). Original clinical data © its creators — Andras Janosi, M.D. (Budapest); William Steinbrunn, M.D. (Zurich); Matthias Pfisterer, M.D. (Basel); and Robert Detrano, M.D., Ph.D. (Long Beach / Cleveland); donor David W. Aha (UCI). Source: kaggle.com/datasets/fedesoriano/heart-failure-prediction.License: Open Database License (ODbL) v1.0 for the database; the contents remain © the original authors named above. Used with attribution. ODbL 1.0

Article

© 2025 Philip Sarajlic. All rights reserved for the article’s original text and figures; the third-party data remains under the license shown above.

Code

Code examples in this article are licensed under the Common Public Attribution License Version 1.0 (CPAL-1.0), an OSI-approved copyleft license based on the Mozilla Public License 1.1. Initial Developer: Philip Sarajlic.

Attribution required by CPAL Exhibit B: © 2025 Philip Sarajlic · “Based on code by Philip Sarajlic” · philipsarajlic.com · no graphic image. This attribution must be displayed in Larger Works.

Modifications must be released in Source Code form under CPAL-1.0. Making the code usable by anyone other than you over a network is External Deployment under the license and is treated as distribution, so the Source Code must be made available to those users.

Full text: opensource.org/license/cpal-1-0 (SPDX identifier CPAL-1.0)

Scroll to Top

Free diagnostic

Would your model hold up to an external review?

Answer 39 questions in about nine minutes and get a clear picture of where your model stands.

You’ll receive a readiness score, a breakdown across 15 areas, your biggest evidence gaps, and the five questions an external reviewer would be most likely to ask first.

The assessment draws on guidance from:

  • TRIPOD+AI
  • PROBAST+AI
  • FDA GMLP
  • NIST AI RMF
  • SR 11-7

We’ll send you one email with your link, and your results when you finish. Your email address is carried through when you complete the assessment, so you won’t need to enter it again.

We don’t ask for your data, and there are no free-text fields in the assessment. See how we use your email address.