Pandas Percentage Change
PCT_change() works nearly identical to .diff() within Python Pandas. The only difference is that we will get a decimal change instead of subtracting the two values. While the Pandas documentation calls this “Percentage Change” it really is the decimal representation of it and we need to multiply our value by 100 to get a true percentage change.
Don’t worry though as it can be done in a simple line of code. We will be showing that in our first example. Throughout the lesson though, we will continue to use the default decimal representation to save time and simplify code.
Now if you want to skip the text tutorial and jump right into our YouTube video on Pandas pct_change(), we have one embedded below. Otherwise let’s start our lesson on Pandas Percentage Change.
Tutorial Prep
Before we jump into the tutorial let’s import in Pandas and NumPy. We will be using NumPy for null values a bit later in the tutorial.
import pandas as pd import numpy as np
For the first half of the lesson we will be using a dataframe that is based around YouTube views. Pass in a dictionary with a list inside of views of videos and we are good to go.
df_YouTube = pd.DataFrame({ 'YouTube Views': [125, 800, 335, 400, 1500] })

Example 1a - Default periods
For our first example we will create a new column called views change. We will use .pct_change() with no parameters passed in. You’ll see that we get both positive and negative values of the decimal percentage change.
One thing to note is that we technically cannot find the percentage change of the first row (0 index). This is due to there being no data above. A bit later in the tutorial we will go over a few different approaches on how we can fill the missing value.
df_YouTube['Views_Change'] = df_YouTube['YouTube Views'].pct_change()

Example 1b - Show percentage sign instead of decimal
Now if we wanted to specifically have this in percentage form, we can take the column we created above and multiple it by 100. You could also technically do this from the start, but we elected to have two columns to showcase the difference.
df_YouTube['Views_Change_100'] = df_YouTube['Views_Change'] * 100

Example 2 - Periods 2
Now let’s add in our first parameter. Periods allows you to define how many rows (or columns) we look at for the calculation. By default this is 1, which typically looks at the difference between the current row and the one above it. With us setting it to two, we look 2 rows above.
Since we are now looking two rows above, the first two rows in our dataframe will have missing values.
df_YouTube['Views_Change_2_Periods'] = df_YouTube['YouTube Views'].pct_change(periods=2)

Example 3 - ABS Difference
Sometimes we only want to see the positive percentage change. To do that we will attach .abs() at the end which stands for absolute value. This will take any negative value and turn it positive.
df_YouTube['Views_Absolute_Difference'] = df_YouTube['YouTube Views'].pct_change().abs()

Example 4 - Diff multiple rows and columns
While every example covered so far looks at one specific column, we can also find the difference of multiple at the same time.
Let’s look at the example of how many cards are sold monthly of two legendary athletes: Don Bradman and Nolan Ryan.
monthly_card_sales = pd.DataFrame({ 'Donald Bradman': [28, 46, 33], 'Nolan Ryan': [511, 702, 611] })
Since we are looking at the entire dataframe in this example, we can attatch .pct_change() to the dataframe and see the results.
While this is convenient, the better approach is to create new columns with the percentage change.
monthly_card_sales.pct_change()

Since we are creating multiple columns, ensure that you use double brackets.
monthly_card_sales[['Don_Bradman_diff', 'Nolan_Ryan_diff']] = monthly_card_sales.pct_change()

Example 5 - Difference across columns
Every example in this lesson so far has been through finding the percentage change across rows, let’s change it up and look at columns.Â
Let’s create a dataset which looks at the first 3 quarters of merchant signups across the years 2023-2025.
df_merchants = pd.DataFrame({ 'Q1': [182, 270, 330], 'Q2': [211, 220, 380], 'Q3': [250, 230, 390] }, index=[2023, 2024, 2025]) df_merchants.index.name = 'Year'

Since we are looking at finding the percentage change now between columns, we have to specify axis=1. By default the axis is 0 which looks at rows.
df_merchants.pct_change(axis=1)

Example 6 - Time series analysis with date index
In the diff() tutorial, I showcased this time series example. Finding the diff() is much more popular in this scenario as it helps with differencing. Anyways it’s still worth showing the time series with the percentage change.
dates = pd.date_range(start='2025-04-19', periods=7, freq='D') temps = [30, 32, 31, 35, 36, 34, 33] df_temps = pd.DataFrame({ 'Date': dates, 'Temperature': temps }) df_temps.set_index('Date', inplace=True) df_temps.sort_index()

df_temps['Temp_Change'] = df_temps['Temperature'].pct_change()

Example 7 - Dealing with null values prior to diff
When working with real world data, it’s not uncommon to have null values in your dataset. While I personally reccomend that you fill them in before using pct_change() you still have the option to fill them in on the same line.
The benefit here is that you do not modify the null values within your dataframe, but we still take the percentage change as if you already did.
data = { 'Time': ['08:00', '08:15', '08:30', '08:45', '09:00', '09:15'], 'Passengers': [120, 125, np.nan, 130, 128, np.nan] } nan_df = pd.DataFrame(data)
We can use fillna() to populate a number that we want to utilize.
nan_df['diff'] = nan_df['Passengers'].fillna(100).pct_change()

If you want to keep the null values and not fill them in, use the parameter fill_method=None.
nan_df['diff'] = nan_df['Passengers'].pct_change(fill_method=None)

Example 8 - Different ways to fill in the first value
All tutorial we have talked about null value appearing in the first row of the dataframe. There are countless ways in which we can fill it in. Here are 3 of the more common approaches.
Backward Fill uses next available value. If the next row isn’t null it’ll populate it’s valueÂ
df_temps['Filled_bfill'] = df_temps['Temp_Change'].bfill()
Fillna allows you to fill the null value with a Specific Value. In this case we use 0.
df_temps['Filled_zero'] = df_temps['Temp_Change'].fillna(0)
Fillna also allows you to fill with a Custom Value. Let’s use the average of the column to populate the null value.
mean_change = df_temps['Temp_Change'].mean() df_temps['Filled_mean'] = df_temps['Temp_Change'].fillna(mean_change)

Example 9 - Groupby
data = { 'Date': pd.date_range(start='2025-01-01', periods=12, freq='ME'), 'Event': ['5K', '10K', 'Half', 'Marathon'] * 3, 'Time': [25, 55, 110, 240, 24, 54, 108, 238, 23, 52, 107, 237] } df_running = pd.DataFrame(data) df_running.sort_values(by=['Event', 'Date'], inplace=True) df_running.reset_index(drop=True, inplace=True)

df_running['Time_Change'] = df_running.groupby('Event')['Time'].pct_change()

Ryan is a Data Scientist at a fintech company, where he focuses on fraud prevention in underwriting and risk. Before that, he worked as a Data Analyst at a tax software company. He holds a degree in Electrical Engineering from UCF.