Skip to main content
Workforce LibreTexts

14: Root Mean Squared Error

  • Page ID
    50859
  • \( \newcommand{\vecs}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \)

    \( \newcommand{\vecd}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash {#1}}} \)

    \( \newcommand{\dsum}{\displaystyle\sum\limits} \)

    \( \newcommand{\dint}{\displaystyle\int\limits} \)

    \( \newcommand{\dlim}{\displaystyle\lim\limits} \)

    \( \newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\)

    ( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\)

    \( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\)

    \( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\)

    \( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\)

    \( \newcommand{\Span}{\mathrm{span}}\)

    \( \newcommand{\id}{\mathrm{id}}\)

    \( \newcommand{\Span}{\mathrm{span}}\)

    \( \newcommand{\kernel}{\mathrm{null}\,}\)

    \( \newcommand{\range}{\mathrm{range}\,}\)

    \( \newcommand{\RealPart}{\mathrm{Re}}\)

    \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\)

    \( \newcommand{\Argument}{\mathrm{Arg}}\)

    \( \newcommand{\norm}[1]{\| #1 \|}\)

    \( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\)

    \( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\AA}{\unicode[.8,0]{x212B}}\)

    \( \newcommand{\vectorA}[1]{\vec{#1}}      % arrow\)

    \( \newcommand{\vectorAt}[1]{\vec{\text{#1}}}      % arrow\)

    \( \newcommand{\vectorB}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \)

    \( \newcommand{\vectorC}[1]{\textbf{#1}} \)

    \( \newcommand{\vectorD}[1]{\overrightarrow{#1}} \)

    \( \newcommand{\vectorDt}[1]{\overrightarrow{\text{#1}}} \)

    \( \newcommand{\vectE}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash{\mathbf {#1}}}} \)

    \( \newcommand{\vecs}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \)

    \(\newcommand{\longvect}{\overrightarrow}\)

    \( \newcommand{\vecd}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash {#1}}} \)

    \(\newcommand{\ket}[1]{\left| #1 \right>}\)
    \(\newcommand{\bra}[1]{\left< #1 \right|}\)
    \(\newcommand{\braket}[2]{\left< #1 \vphantom{#2} \right| \left. #2 \vphantom{#1} \right>}\)
    \(\newcommand{\braopket}[3]{\left< #1 \vphantom{#2}\vphantom{#3} \right| #2 \vphantom{#1}\vphantom{#3} \left| #3 \vphantom{#1}\vphantom{#2} \right>}\)
    \(\newcommand{\qmvec}[1]{\mathbf{\vec{#1}}}\)
    \(\newcommand{\op}[1]{\hat{\mathbf{#1}}}\)
    \(\newcommand{\expect}[1]{\langle #1 \rangle}\)
    \(\newcommand{\dfn}[1]{\emph{\textbf{#1}}}\)

    \(\newcommand{\avec}{\mathbf a}\) \(\newcommand{\bvec}{\mathbf b}\) \(\newcommand{\cvec}{\mathbf c}\) \(\newcommand{\dvec}{\mathbf d}\) \(\newcommand{\dtil}{\widetilde{\mathbf d}}\) \(\newcommand{\evec}{\mathbf e}\) \(\newcommand{\fvec}{\mathbf f}\) \(\newcommand{\nvec}{\mathbf n}\) \(\newcommand{\pvec}{\mathbf p}\) \(\newcommand{\qvec}{\mathbf q}\) \(\newcommand{\svec}{\mathbf s}\) \(\newcommand{\tvec}{\mathbf t}\) \(\newcommand{\uvec}{\mathbf u}\) \(\newcommand{\vvec}{\mathbf v}\) \(\newcommand{\wvec}{\mathbf w}\) \(\newcommand{\xvec}{\mathbf x}\) \(\newcommand{\yvec}{\mathbf y}\) \(\newcommand{\zvec}{\mathbf z}\) \(\newcommand{\rvec}{\mathbf r}\) \(\newcommand{\mvec}{\mathbf m}\) \(\newcommand{\zerovec}{\mathbf 0}\) \(\newcommand{\onevec}{\mathbf 1}\) \(\newcommand{\real}{\mathbb R}\) \(\newcommand{\twovec}[2]{\left[\begin{array}{r}#1 \\ #2 \end{array}\right]}\) \(\newcommand{\ctwovec}[2]{\left[\begin{array}{c}#1 \\ #2 \end{array}\right]}\) \(\newcommand{\threevec}[3]{\left[\begin{array}{r}#1 \\ #2 \\ #3 \end{array}\right]}\) \(\newcommand{\cthreevec}[3]{\left[\begin{array}{c}#1 \\ #2 \\ #3 \end{array}\right]}\) \(\newcommand{\fourvec}[4]{\left[\begin{array}{r}#1 \\ #2 \\ #3 \\ #4 \end{array}\right]}\) \(\newcommand{\cfourvec}[4]{\left[\begin{array}{c}#1 \\ #2 \\ #3 \\ #4 \end{array}\right]}\) \(\newcommand{\fivevec}[5]{\left[\begin{array}{r}#1 \\ #2 \\ #3 \\ #4 \\ #5 \\ \end{array}\right]}\) \(\newcommand{\cfivevec}[5]{\left[\begin{array}{c}#1 \\ #2 \\ #3 \\ #4 \\ #5 \\ \end{array}\right]}\) \(\newcommand{\mattwo}[4]{\left[\begin{array}{rr}#1 \amp #2 \\ #3 \amp #4 \\ \end{array}\right]}\) \(\newcommand{\laspan}[1]{\text{Span}\{#1\}}\) \(\newcommand{\bcal}{\cal B}\) \(\newcommand{\ccal}{\cal C}\) \(\newcommand{\scal}{\cal S}\) \(\newcommand{\wcal}{\cal W}\) \(\newcommand{\ecal}{\cal E}\) \(\newcommand{\coords}[2]{\left\{#1\right\}_{#2}}\) \(\newcommand{\gray}[1]{\color{gray}{#1}}\) \(\newcommand{\lgray}[1]{\color{lightgray}{#1}}\) \(\newcommand{\rank}{\operatorname{rank}}\) \(\newcommand{\row}{\text{Row}}\) \(\newcommand{\col}{\text{Col}}\) \(\renewcommand{\row}{\text{Row}}\) \(\newcommand{\nul}{\text{Nul}}\) \(\newcommand{\var}{\text{Var}}\) \(\newcommand{\corr}{\text{corr}}\) \(\newcommand{\len}[1]{\left|#1\right|}\) \(\newcommand{\bbar}{\overline{\bvec}}\) \(\newcommand{\bhat}{\widehat{\bvec}}\) \(\newcommand{\bperp}{\bvec^\perp}\) \(\newcommand{\xhat}{\widehat{\xvec}}\) \(\newcommand{\vhat}{\widehat{\vvec}}\) \(\newcommand{\uhat}{\widehat{\uvec}}\) \(\newcommand{\what}{\widehat{\wvec}}\) \(\newcommand{\Sighat}{\widehat{\Sigma}}\) \(\newcommand{\lt}{<}\) \(\newcommand{\gt}{>}\) \(\newcommand{\amp}{&}\) \(\definecolor{fillinmathshade}{gray}{0.9}\)

    In the previous chapter, we learned how a regression line models the relationship between two variables and how the correlation coefficient describes the strength of that relationship. In this chapter, we will evaluate how well a regression line predicts new values. We will measure the prediction errors and find ways to measure how close the predicted values are to the actual data.

    Review: Find the Regression Line

    We revisit the cars dataset to work with weight and mpg and find the regression line.

    We read in the data and show the scatterplot of weight vs mpg.

    import pandas as pd
    import numpy as np
    import matplotlib.pyplot as plt
    
    url = 'https://raw.githubusercontent.com/DeAnzaDataScience/CIS11/refs/heads/main/datasets_notes/cars.csv'
    cars = pd.read_csv(url)
    cars.head()
    Output?
      mpg cylinders displacement horsepower weight acceleration model_year origin car_name
    0 18.0 8 307.0 130.0 3504.0 12.0 70 1 chevrolet chevelle malibu
    1 15.0 8 350.0 165.0 3693.0 11.5 70 1 buick skylark 320
    2 18.0 8 318.0 150.0 3436.0 11.0 70 1 plymouth satellite
    3 16.0 8 304.0 150.0 3433.0 12.0 70 1 amc rebel sst
    4 17.0 8 302.0 140.0 3449.0 10.5 70 1 ford torino
    plt.figure(figsize=(4,4))
    plt.scatter(cars["weight"], cars["mpg"])
    plt.xlabel("Weight (lbs)")
    plt.ylabel("Miles per Gallon")
    plt.grid()
    plt.show()
    Output?

    Next we bring in the standard_units and correlation functions from the previous chapter.

    def standard_units(array):
        return (array - np.mean(array))/np.std(array)
    
    # correlation function uses standard_units function to calculate the correlation coefficent
    def correlation(array1, array2):
        return np.mean(standard_units(array1) * standard_units(array2))
    Output?

    Then we bring in the slope and intercept functions from the previous chapter.

    We use these functions to find the slope and intercept of the regression line.

    # slope function uses correlation function to calculate the slope of the regression line
    def slope(x, y):
        return correlation(x, y) * np.std(y) / np.std(x)
    
    # intercept function uses slope function to calculate the y-intercept of the regression line
    def intercept(x, y):
        return np.mean(y) - slope(x, y) * np.mean(x)
    
    # find the slope and intercept of the regression line for weight and mpg
    reg_line_slope = slope(cars['weight'], cars['mpg'])
    print("slope:", round(reg_line_slope, 2))
    reg_line_intercept = intercept(cars['weight'], cars['mpg'])
    print("intercept:", round(reg_line_intercept, 2))
    Output?
    slope: -0.01
    intercept: 46.22
    

    We recall what the slope of the regression line means:

    For every 1-pound increase in weight, the predicted MPG decreases by about 0.01, or, for every 100-pound increase in weight, the predicted MPG decreases by about 1 mile per gallon.

    We plot the prediction line in the scatterplot by using the equation for a line: \(\hat{y} = \text{slope} \times x + \text{intercept}\), where \(\hat{y}\) represents the predicted values.

    plt.figure(figsize=(4,4))
    plt.scatter(cars['weight'], cars['mpg'])
    plt.plot(cars['weight'], reg_line_slope * cars['weight'] + reg_line_intercept, color='red')
    plt.xlabel('Weight (lbs)')
    plt.ylabel('Miles per Gallon')
    plt.grid()
    plt.show()
    Output?

    We recall that the regression line is the best fit line, so it doesn't necessarily pass through all the data points.

    In the plot above, the blue points are the actual y values or the actual MPG. The red regression line represents predicted values \(\hat{y}\) or the predicted MPG for each weight. We see that the red line passes through some of the blue points. At these points the predicted MPG matches the actual MPG exactly. But we also see many blue points that lie above or below the red regression line, which means the predicted values are different from the actual MPG values. The farther a blue point is from the regression line, the larger the prediction error. We want to measure how accurate the predictions are.

    The Residuals

    We measure the prediction error by comparing the actual MPG with the predicted MPG. This difference is called the residual.

    The residual is the vertical distance between a blue point (the actual value) and the red regression line (the predicted value). If the blue point lies on the regression line, then the residual is 0 because the prediction is exactly correct. The farther a blue point is above or below the regression line, the larger the residual and the larger the prediction error.

    Working with the existing scatterplot and regression line, we add code to select 4 cars (or 4 rows in the DataFrame), plot them, and plot their residuals:

    plt.figure(figsize=(6,6))
    
    # scatter plot and regression line
    plt.scatter(cars['weight'], cars['mpg'], alpha=0.8, label='All cars')
    plt.plot(cars['weight'], reg_line_slope * cars['weight'] + reg_line_intercept, color='red')
    
    # Note that you don't have to study this block of code.
    # It is to demo the residuals for 4 selected cars.
    # choose 4 cars to highlight
    selected = cars.iloc[[24,315,137,250]]
    # plot selected cars
    plt.scatter(selected['weight'], selected['mpg'], color='orange', edgecolor='k', s=50, zorder=5, label='Selected cars')
    
    # plot vertical differences for selected points
    for i, row in selected.iterrows():
        x = row['weight']
        y_actual = row['mpg']
        y_pred = reg_line_slope * x + reg_line_intercept
        plt.plot([x, x], [y_actual, y_pred], color='red', linestyle='--', linewidth=2)
    
    # show labels
    plt.xlabel('Weight (lbs)')
    plt.ylabel('Miles per Gallon')
    plt.grid()
    plt.legend()
    plt.show()
    Output?

    The vertical red dashed lines represent the residuals.

    We observe that:

    • one of the points is on the regression line and its residual is 0
    • one of the points is below the regression line and its residual is negative.
    • two of the points are above the regression line and their residuals are positive. Of these two points, the one farther away from the regression line has the larger residual.

    Residual Plots

    While we can plot the residual for each data point with a red dashed line, it's easy to imagine that all the red lines from all the data points could overlap and create a cluttered plot, making it difficult to make good observations of them.

    Instead, we can draw residual plots which display all the residuals in a more organized way. A residual plot is a scatterplot of the predictor on the x-axis, and the residuals on the y-axis. The plot also contains a horizontal line at residual = 0 to represent a perfectly accurate prediction. Points above this line have positive residuals and points below this line have negative residuals.

    First we find the predicted MPG, then we find the residuals.

    # find predicted MPG
    y_pred = reg_line_slope * cars['weight'] + reg_line_intercept
    # find residuals
    residuals = cars['mpg'] - y_pred
    Output?

    Plot weight vs MPG and weight vs residuals side-by-side.

    plt.figure(figsize=(8, 4))
    # plot weight vs mpg
    plt.subplot(1, 2, 1)
    plt.scatter(cars['weight'], cars['mpg'], alpha=0.6)
    plt.plot(cars['weight'], y_pred, color='red', linewidth=2)
    plt.xlabel('Weight')
    plt.ylabel('MPG')
    plt.title('Weight vs MPG')
    plt.grid()
    
    # plot weight vs residuals
    plt.subplot(1, 2, 2)
    plt.scatter(cars['weight'], residuals, color='green', alpha=0.6)
    plt.axhline(0, color='red', linewidth=2)
    plt.xlabel('Weight')
    plt.ylabel('Residual')
    plt.title('Weight vs Residuals')
    plt.grid()
    
    plt.tight_layout()
    plt.show()
    Output?

    Every point in the original scatterplot has a corresponding point in the residual plot. The x-coordinate stays the same, while the y-coordinate changes from the actual MPG to the residual.

    Looking at the Weight vs MPG plot, we observe that:

    • At the two ends of the weight range, around 2000 lb and 5000 lb, many of the actual MPG values are above the regression line. This is reflected in the residual plot, with many residuals lying above the zero line.
    • In the middle of the weight range, between about 2500 lb to 4000 lbs, many of the actual MPG values are below the regression line. This is reflected in the residual plot, with many of the residuals lying below the zero line.

    When the residuals form a curved pattern instead of being randomly scattered around the zero line, it suggests that the relationship between the variables is curved rather than linear. A residual plot can help us detect when a linear regression model may not be a good fit for the data.

    We also observed from the Weight vs MPG plot that:

    • From about 4000 lb to 5000 lb, all the data points tend to be closer to the regression line. This is reflected in the residual plot, where the data are clustered more closely for the same weight range.
    • Below 4000 lb quite a few data points are farther away from the linear regression line. This is reflected in the residual plot, where the data points are spread out more.

    Residual plots can be used to detect uneven spread of prediction errors, such as when the residuals are larger for a specific range of x-values.

    MSE and RMSE

    A residual plot helps us see the pattern and spread of the prediction errors. However, it can still be difficult to judge the overall accuracy of the regression model by looking at many residuals.

    To summarize all the residuals with a single number, we use the Mean Squared Error (MSE) and the Root Mean Squared Error (RMSE).

    MSE

    As each residual is the prediction error for one data point, a summary of all the residuals, or their average, gives us a measure of the overall prediction error.

    We simply can't calculate the average of the residuals by summing all the residuals and dividing by the number of residuals. This is because the regression line passes through the center of the data points, so the positive and negative residuals effectively cancel each other out. As a result, the average residual is 0 even when many predictions are far from the actual data.

    For example, if two residuals are +5 and -5, then

    \[\frac{-5 + 5}{2} = 0 \nonumber\]

    The result shows no prediction error, even though both predictions are actually off by 5 units.

    Instead we'll use the same method as the calculation of the standard deviation:

    • square the error values to remove the negative sign
    • find the average of all the squares

    This average is the Mean Squared Error or the MSE. A smaller MSE means the predictions are generally closer to the actual value.

    MSE is useful because it summarizes the prediction errors in a single number, and it is the value used by many regression models to find the best regression line.

    However, because the errors are squared, the units are also squared. For example, if the MPG prediction errors are measured in miles per gallon, then MSE is measured in \(\text{miles per gallon}^2\) Since people normally think of prediction errors in miles per gallon rather than square miles per gallon, MSE is not easy to interpret.

    RMSE

    Just as with the standard deviation calculation, we can take the square root of the MSE. This converts the squared units back to the original units of the data, making the result easier to interpret. The result is called the Root Mean Squared Error or RMSE. Its long name describes how it is calculated: square the residuals, then find their mean, and finally take the square root.

    rmse = np.sqrt(np.mean(residuals**2))
    print("RMSE:", round(rmse, 2))
    actual_mpg_mean = np.mean(cars["mpg"])
    print("Average actual MPG:", round(actual_mpg_mean, 2))
    print("RMSE as a percentage of average MPG:", round(rmse / actual_mpg_mean * 100), "%")
    Output?
    RMSE: 4.32
    Average actual MPG: 23.45
    RMSE as a percentage of average MPG: 18 %
    

    The RMSE is 4.32. This means that the regression model's predictions differ from the actual MPG values by about 4.32 miles per gallon, on average. A smaller RMSE indicates that the predictions are closer to the actual values and that the regression model is more accurate.

    The average MPG of all the cars in the dataset is 23.45 MPG. The RMSE is about 18%, or about one-fifth of the average MPG. This gives us a sense of the size of the prediction error relative to a typical MPG value in the dataset.

    The RMSE is most useful for comparing different regression models on the same dataset. In general the model with the smaller RMSE makes more accurate predictions.

    Summary

    In this chapter, we review how a linear regression model predicts values using a regression line, then we learn how to measure the accuracy of those predictions. We find the residual, which is the difference between the actual and predicted values, and use residual plots to visualize prediction errors. Then we learn how the Mean Squared Error (MSE) and Root Mean Squared Error (RMSE) summarize the overall prediction error. Together, these tools help us evaluate and compare the accuracy of regression models.


    This page titled 14: Root Mean Squared Error was last modified on Sat, 26 Sep 2026 08:15:44 GMT and is shared under a CC BY 4.0 license and was authored, remixed, and/or curated by Clare Nguyen.