🚀 Supercharge your YouTube channel's growth with AI.
Try YTGrowAI FreeRead Excel Files in Python using Pandas: A Complete Guide
Excel files are everywhere in the real world. Sales reports, financial models, inventory lists, survey data, employee records. If you work with data long enough, someone will hand you an .xlsx file and ask you to do something with it. I have gotten this request from junior engineers, product managers, data analysts, and executives more times than I can count. The answer in Python almost always involves pandas, and specifically the read_excel function.
This article covers everything you need to know about reading Excel files with pandas. I start with the basics and work through real scenarios including multiple sheets, specific columns, handling missing data, working with large files, and some edge cases that trip people up. By the end you will be able to load any Excel file confidently and do it the right way.
Why pandas for Excel files
Python has several libraries for working with Excel files. Openpyxl and xlrd give you fine-grained control over cells, formatting, and formulas. They are powerful but require more code for everyday data tasks.
Pandas takes a different approach. It treats an Excel sheet as a table of data, much like a database table or a CSV file. The read_excel function loads that data into a DataFrame, which is pandas’ primary data structure. Once you have a DataFrame, you have access to the full ecosystem of pandas tools for filtering, grouping, merging, and analyzing data.
This matters because most of the time you receive an Excel file, you do not care about the cell colors or formula references. You care about the data inside. Pandas gives you a direct path from Excel file to analyzed data without extra steps.
What you need to read Excel files in Python
Two Python packages handle Excel reading: pandas and openpyxl. Pandas does the data manipulation. Openpyxl handles the Excel file format itself. You need both installed.
Install them with pip:
pip install pandas openpyxl
If you work with older .xls files, you also need the xlrd package:
pip install xlrd
Most Excel files today use the .xlsx format, which is based on XML and was introduced with Excel 2007. Openpyxl works natively with this format. Once both packages are installed, reading Excel files works exactly like reading CSV files or any other data source in pandas.
Reading your first Excel file
The basic call to load an Excel file looks like this:
import pandas as pd
df = pd.read_excel('sales_data.xlsx')
print(df.head())
This loads the first sheet from the file and returns a pandas DataFrame. The head() method shows the first five rows by default, which is useful for quickly inspecting what you loaded.
Pandas automatically detects the header row and uses those values as column names. It also figures out data types by default, though you can override this behavior when needed.
If your file is not in the same directory as your Python script, provide the full path:
df = pd.read_excel('/home/user/data/sales_data.xlsx')
On Windows, the path uses backslashes and it is good practice to use a raw string to avoid escaping issues:
df = pd.read_excel(r'C:\Users\user\data\sales_data.xlsx')
The r prefix tells Python to treat the string as raw, which means backslashes are treated literally instead of as escape characters.
Specifying which sheet to read
Excel files can contain multiple sheets. A workbook might have sheets for each month, each department, or each data category. By default, read_excel loads the first sheet. You can specify a different sheet using the sheet_name parameter.
Load a sheet by its name, which is useful when you know exactly which sheet you want:
df = pd.read_excel('report.xlsx', sheet_name='Q4 Sales')
Load a sheet by its numeric position, where 0 is the first sheet, 1 is the second, and so on:
df = pd.read_excel('report.xlsx', sheet_name=2)
You can also load all sheets at once by setting sheet_name to None. This returns a dictionary instead of a single DataFrame:
all_sheets = pd.read_excel('report.xlsx', sheet_name=None)
print(all_sheets.keys())
This returns a dictionary where each key is a sheet name and each value is the corresponding DataFrame. You can then iterate over the dictionary to process each sheet:
for sheet_name, data in all_sheets.items():
print(f"Sheet: {sheet_name}, Rows: {len(data)}, Columns: {len(data.columns)}")
This approach is useful when you need to process every sheet in a multi-sheet workbook, for example when each sheet represents a different month of sales data and you need to combine them.
Handling headers and index columns
The header parameter controls which row pandas treats as the column names. By default it is row 0. If your file has a title row or blank rows above the actual header, skip them:
df = pd.read_excel('data.xlsx', header=2)
This uses the third row (index 2) as the header row. All rows above it are ignored.
Sometimes your data has no header row at all. In that case, set header=None and optionally provide your own column names using the names parameter:
df = pd.read_excel('data.xlsx', header=None, names=['ID', 'Name', 'Revenue', 'Date', 'Status'])
This creates generic column labels (0, 1, 2, …) and then replaces them with the names you provided. This is the cleanest approach when your Excel file has no header row.
You can also tell pandas which column to use as the row index using the index_col parameter. The index is the leftmost identifier for each row:
df = pd.read_excel('data.xlsx', index_col=0)
This uses the first column as the index. The index appears as the leftmost column in the DataFrame and is useful when you want to label rows uniquely, for example using a customer ID or product code.
You can combine header and index_col when your file has both a named header row and a named index column:
df = pd.read_excel('data.xlsx', header=0, index_col=0)
Selecting specific columns to read
Reading an entire Excel file into memory works fine for small to medium files. When you are working with large spreadsheets that have dozens of columns, you can save memory and speed up loading by loading only the columns you actually need.
The usecols parameter accepts column names, column indices, or a callable function.
Load specific columns by their names:
df = pd.read_excel('data.xlsx', usecols=['Name', 'Revenue', 'Date', 'Region'])
Load specific columns by their numeric positions (0-indexed):
df = pd.read_excel('data.xlsx', usecols=[0, 2, 4, 7])
This loads the first, third, fifth, and eighth columns. This approach is useful when the column names are long or when you are working with files where the column order is consistent but names might vary.
Load columns based on a condition using a callable function. The callable receives the column name and returns True if the column should be included:
df = pd.read_excel('data.xlsx', usecols=lambda x: x in ['Name', 'Revenue', 'Date'])
The callable approach is handy when you want to filter columns programmatically without hardcoding names, for example when building a reusable function that handles different file layouts.
You can also load a range of columns using Excel-style column letters:
df = pd.read_excel('data.xlsx', usecols='A:D')
This loads columns A through D, which corresponds to the first four columns. You can also specify non-contiguous ranges:
df = pd.read_excel('data.xlsx', usecols='A:B,D:F')
This loads columns A through B and D through F, skipping column C.
Controlling data types
Pandas infers data types by default by sampling values from each column. Sometimes it gets this right and sometimes it does not. Numeric columns might be read as strings, or date columns might be read as objects. You can override the inferred types using the dtype parameter.
Specify types for specific columns using a dictionary:
df = pd.read_excel('data.xlsx', dtype={'ID': str, 'Revenue': float, 'Quantity': int, 'Discount': float})
Converting the ID column to a string prevents pandas from dropping leading zeros, which commonly happen with postal codes, phone numbers, product codes, and credit card numbers. This is one of the most common data quality issues I see when people first start working with Excel data in pandas.
You can also control how pandas parses dates using the parse_dates parameter. By default, pandas tries to detect date columns automatically, but this detection is not always reliable:
df = pd.read_excel('data.xlsx', parse_dates=['Date', 'ShipDate', 'DueDate'])
If a date column is not being recognized as a date, you can specify the date format explicitly using the date_format parameter. Knowing the format in advance also speeds up parsing:
df = pd.read_excel('data.xlsx', parse_dates=['Date'], date_format='%d/%m/%Y')
Pandas also supports parsing dates from multiple columns into a single datetime column. For example, if your Excel file has separate Year, Month, and Day columns:
df = pd.read_excel('data.xlsx', parse_dates={'Date': ['Year', 'Month', 'Day']})
This combines those three columns into a single Date column.
Skipping rows and limiting rows loaded
The skiprows parameter lets you skip rows at the top of the file. This is useful when Excel files have title blocks, metadata, or blank rows before the actual data starts.
Skip a specific number of rows from the top:
df = pd.read_excel('data.xlsx', skiprows=3)
This skips the first three rows. The row at index 3 becomes the header row.
Skip specific rows using a list of row numbers to skip:
df = pd.read_excel('data.xlsx', skiprows=[1, 3, 5])
This skips rows 1, 3, and 5. This is useful when problematic rows are not at the start of the file.
Limit how many rows are loaded using nrows. This is particularly useful for previewing a file without loading the entire thing, which matters when you are working with very large files during development:
df = pd.read_excel('data.xlsx', nrows=100)
This loads only the first 100 rows. You can combine it with skiprows to load rows from a specific position:
df = pd.read_excel('data.xlsx', skiprows=5, nrows=100)
This skips the first 5 rows and then loads the next 100. Useful when the data starts after a known header block.
Skip rows from the bottom of the file using skipfooter:
df = pd.read_excel('data.xlsx', skipfooter=2)
This skips the last 2 rows, which is useful when Excel files have footnotes or summary rows that you do not want in your analysis.
Handling missing values while reading
Excel files often have empty cells representing missing data. This happens when someone leaves a cell blank because the value is unknown or not applicable. Pandas represents these as NaN (Not a Number) by default. You can control what values are treated as missing using the na_values parameter.
Treat specific strings as missing values:
df = pd.read_excel('data.xlsx', na_values=['N/A', 'NULL', '-', 'NaN', ''])
This treats any of those strings as missing data. Blank cells are already treated as NaN by default.
You can specify different missing value markers for each column using a dictionary. This is useful when different columns use different conventions:
df = pd.read_excel('data.xlsx', na_values={'Revenue': ['N/A', ''], 'Name': ['UNKNOWN', ''], 'Rating': ['N/A', 'NA', 'n/a']})
This approach handles the reality that different teams often use different placeholder values for missing data in the same spreadsheet.
To keep certain values as strings instead of converting them to missing data, use keep_default_na=False. By default, pandas treats many common placeholder values as NaN. Setting this to False means only blank cells are treated as missing:
df = pd.read_excel('data.xlsx', keep_default_na=False)
This is useful when the value “NA” or “N/A” is a legitimate data value in your dataset and not a missing value indicator.
After loading, you can check for missing values using:
print(df.isnull().sum())
This shows the count of missing values per column. You can then decide how to handle them using pandas methods like fillna() to replace missing values or dropna() to remove rows with missing data.
Reading from URLs and byte streams
You do not need a local file to use read_excel. You can load Excel files directly from a URL, which is useful when working with hosted datasets or APIs that return Excel files:
url = 'https://example.com/data/sales_report.xlsx'
df = pd.read_excel(url)
Pandas downloads the file over HTTPS and reads it directly. This works with any publicly accessible URL pointing to an actual Excel file.
When you need more control over the HTTP request, such as adding authentication headers or custom user agents, you can download the file first using the requests library and then pass it to pandas:
import requests
from io import BytesIO
url = 'https://example.com/data/sales_report.xlsx'
headers = {'Authorization': 'Bearer YOUR_TOKEN_HERE'}
response = requests.get(url, headers=headers)
df = pd.read_excel(BytesIO(response.content))
This approach gives you full control over the HTTP request while still using pandas for the actual data loading.
You can also read from in-memory byte streams, which is useful when working with cloud storage or when the file has been retrieved from a database:
from io import BytesIO
with open('data.xlsx', 'rb') as f:
content = f.read()
df = pd.read_excel(BytesIO(content))
This reads the file into memory as bytes and then passes the byte stream to pandas.
Reading Excel files with multi-level headers
Some Excel files have hierarchical headers with merged cells spanning multiple rows. Financial reports often use this format where the top row has broad categories like “Sales” and “Expenses” with subcategories below them.
Pandas can handle this using the header parameter as a list of row numbers:
df = pd.read_excel('financials.xlsx', header=[0, 1])
This creates a MultiIndex for the columns with two levels. The first level corresponds to row 0 and the second to row 1. You can then access columns using tuple notation:
print(df[('Sales', 'Q1')])
print(df[('Sales', 'Q2')])
If you find multi-level column headers difficult to work with, you can flatten them after loading:
df.columns = ['_'.join(col).strip() for col in df.columns.values]
This converts column names like (‘Sales’, ‘Q1’) to ‘Sales_Q1’.
Performance tips for large Excel files
Large Excel files can be slow to load. Here are the main ways to speed things up.
Use engine='openpyxl' explicitly. While it is the default for .xlsx files, being explicit prevents any engine negotiation overhead and makes the intent clear.
Read only the data you need. Use usecols to drop columns you will not use. Use nrows to load a subset during development and testing. Only load the full file once your code is working correctly.
Skip unnecessary rows with skiprows. If a file has a large title block at the top, skip it instead of loading it and then filtering it out in pandas.
Consider converting the Excel file to a CSV first for one-time analysis tasks. CSVs load significantly faster because they are plain text without the XML structure of Excel files:
df = pd.read_csv('data.csv')
If you work with the same Excel file repeatedly, load it once and save it as a pickle file. Pickle preserves data types and loads much faster on subsequent reads:
df.to_pickle('data.pkl')
df = pd.read_pickle('data.pkl')
Pickle files are Python-specific but they preserve complex data types like datetime columns and categorical data without the conversion issues that can happen with CSV files.
For very large files, consider using the chunksize parameter to load data in batches:
for chunk in pd.read_excel('large_file.xlsx', chunksize=10000):
process(chunk)
This loads 10,000 rows at a time, processes them, and then loads the next batch. This keeps memory usage flat even for extremely large files.
Common errors and how to fix them
FileNotFoundError means the path is wrong or the file does not exist at that location. Check that the file exists using your operating system’s file browser and that you have read permissions for it. This is one of the most common errors when starting out.
XLRDError: Unsupported format, or unsupported file extension usually means you are trying to read an .xlsx file without openpyxl installed. Install it with pip install openpyxl. This error also appears when the file is corrupted or is not actually an Excel file despite having an .xlsx extension.
ValueError: No objects to concatenate can happen when reading an empty sheet or a sheet that only has formatting but no data. Always check if the sheet contains data before processing it.
PermissionError on Windows often means the Excel file is open in Excel itself. Windows locks files that are open in other applications. Close the file in Excel and try again.
Merged cells in Excel files can cause unexpected behavior. Merged cells typically show a value in the top-left cell and empty cells for the rest of the merged region. Pandas reads merged cells as empty in all cells except the top-left, which can create confusion when you expect a value in every row. You may need to forward-fill merged values after loading:
df = df.ffill()
This fills each empty cell with the value from the cell above it.
Reading older Excel formats
If you need to read .xls files from older Excel versions, install the xlrd library:
pip install xlrd
Reading an .xls file works exactly the same as reading an .xlsx file:
df = pd.read_excel('old_report.xls')
Note that xlrd only works with the older .xls format. For modern .xlsx files, always use openpyxl. The two formats are fundamentally different and using the wrong library for each format will fail.
Writing DataFrames back to Excel
While this article focuses on reading, you will often need to write processed data back out to Excel. Pandas supports this with the to_excel method.
Write a single DataFrame to Excel:
df.to_excel('output.xlsx', sheet_name='Results', index=False)
The index=False parameter prevents pandas from writing the row index as a separate column in the Excel file. Without this, your first data column gets shifted to the right.
Write a DataFrame with a named index column:
df.to_excel('output.xlsx', sheet_name='Results', index=True, index_label='ID')
Write to an Excel file with multiple sheets using ExcelWriter:
with pd.ExcelWriter('combined.xlsx') as writer:
df_sales.to_excel(writer, sheet_name='Sales', index=False)
df_expenses.to_excel(writer, sheet_name='Expenses', index=False)
df_summary.to_excel(writer, sheet_name='Summary', index=False)
This creates a single Excel file with three separate sheets. The with statement ensures the file is properly closed after writing.
Real example: Loading and analyzing a sales report
Let me walk through a complete example using a realistic sales report scenario.
import pandas as pd
# Load the sales sheet, skip the title row at the top
df = pd.read_excel('sales_report.xlsx', sheet_name='Sales', header=1)
# Show the shape to understand data dimensions
print(f"Loaded {df.shape[0]} rows and {df.shape[1]} columns")
print(f"Columns: {list(df.columns)}")
# Check data types of each column
print(df.dtypes)
# Select only the columns we actually need
df = pd.read_excel('sales_report.xlsx', sheet_name='Sales', header=1,
usecols=['Date', 'Product', 'Region', 'Revenue', 'Units'])
# Convert date column to datetime
df['Date'] = pd.to_datetime(df['Date'])
# Filter to a specific region
df_north = df[df['Region'] == 'North']
# Calculate summary statistics by product
product_summary = df_north.groupby('Product').agg({
'Revenue': 'sum',
'Units': 'sum'
}).round(2)
print(product_summary.sort_values('Revenue', ascending=False))
# Calculate month-over-month growth
df_north['Month'] = df_north['Date'].dt.to_period('M')
monthly = df_north.groupby('Month')['Revenue'].sum()
monthly_pct_change = monthly.pct_change() * 100
print(monthly_pct_change.round(2))
# Save filtered results to a new Excel file
df_north.to_excel('north_sales.xlsx', sheet_name='North Sales', index=False)
This example demonstrates the full workflow: loading a specific sheet, skipping a title row, selecting only needed columns, converting types, filtering data, running groupby analysis, calculating growth rates, and writing results to a new file.
Comparing read_excel to alternatives
Pandas read_excel is not the only way to read Excel files in Python. Here is how it compares to other common options.
openpyxl gives you lower-level access to Excel files. You can read and write individual cells, modify formatting, access formulas, and work with charts. It does not return DataFrames though, so you end up writing significantly more code for data analysis tasks. Use openpyxl when you need to preserve formatting or work with formulas.
xlrd is specifically designed for reading older .xls files. It is no longer maintained for new Excel formats, so use it only when you specifically need to read legacy .xls files.
pyexcel provides a unified interface across multiple Excel formats and can handle both reading and writing with a consistent API. It is useful when you need to work with various Excel formats without worrying about the underlying library differences.
sqlalchemy with pandas can read Excel files using SQL queries if you are already working within a database workflow. This is less common but useful in ETL pipelines.
For data analysis and manipulation, pandas read_excel is the right tool. The DataFrame output integrates directly with the entire pandas and broader Python data science ecosystem.
Putting it together
Reading Excel files is one of those fundamental skills that pays off immediately and repeatedly. The read_excel function in pandas handles most of the complexity for you, from detecting headers to inferring types to handling missing values.
The key parameters to remember are sheet_name for multi-sheet workbooks, usecols for filtering columns, header for controlling the header row, dtype for data type specification, na_values for handling missing data, skiprows and nrows for controlling what gets loaded, and index_col for setting the row index. These give you enough flexibility to handle almost any Excel file format you encounter.
Once you have the data in a DataFrame, you have the full power of pandas at your disposal for filtering, grouping, merging, and analyzing. You can pivot the data, merge it with other sources, run calculations, and export results back to Excel or any other format.


