
This project displays a Pandas DataFrame in a Tkinter Treeview and lets the user sort any column by clicking its heading.
Pandas sort_values() performs the actual sorting. Clicking the same heading again reverses the order from ascending to descending or from descending to ascending.
The application can load either an Excel file using read_excel() or a CSV file using read_csv().
Excel / CSV
|
v
Pandas DataFrame
|
v
Tkinter Treeview
|
v
Click column heading
|
v
df.sort_values()
|
v
Refresh Treeview rows
Show Table of Contents
The older example used a fixed local path:
df=pd.read_excel('D:\\student.xlsx')
A Tkinter file dialog makes the project more reusable because the user can select the data file while the program is running.
file_path=filedialog.askopenfilename(
title='Select Excel or CSV file',
filetypes=[
('Excel files','*.xlsx *.xls'),
('CSV files','*.csv'),
('All files','*.*')
]
)
The file extension decides which Pandas reader is used.
extension=os.path.splitext(
file_path
)[1].lower()
if extension=='.csv':
df=pd.read_csv(file_path)
else:
df=pd.read_excel(file_path)
The earlier my_disp() function created another Treeview every time the data was sorted.
The revised application creates one Treeview when the GUI starts:
tree=ttk.Treeview(
table_frame,
show='headings',
selectmode='browse'
)
Sorting then changes only the rows and heading labels. The widget itself is reused.
Create Treeview once
|
v
Load DataFrame
|
v
Insert rows
|
v
Sort DataFrame
|
v
Delete old rows
|
v
Insert sorted rows
Different Excel and CSV files can contain different headings, so the columns are created dynamically.
Instead of using DataFrame column names as Treeview's internal IDs, we create predictable IDs such as:
c0
c1
c2
c3
The original DataFrame names are still displayed as the headings.
column_ids=[
f'c{i}'
for i in range(
len(df.columns)
)
]
tree['columns']=column_ids
Each heading calls the sorting function with the corresponding DataFrame column name.
tree.heading(
column_id,
text=str(column_name),
command=lambda name=column_name:
sort_column(name)
)
Pandas sort_values() sorts the complete DataFrame using the selected column.
sorted_df=df.sort_values(
by=column_name,
ascending=ascending,
na_position='last',
kind='stable'
)
The options used here are:
by=column_name chooses the clicked column.ascending=True sorts from lower to higher values.ascending=False reverses the order.na_position='last' keeps missing values at the bottom.kind='stable' preserves the relative order of equal values when sorting one column.The old program used one global Boolean variable:
order=True
This meant clicking one column changed the next sort direction for every other column.
The revised application stores a separate next-sort direction for each heading:
sort_state={}
When a heading is clicked:
ascending=sort_state.get(
column_name,
True
)
sort_state[column_name]=not ascending
Therefore each column starts with ascending order independently.
After sorting, the clicked heading also shows the current direction:
Name (ASC)
or:
Name (DESC)
First delete the currently displayed Treeview items:
for item in tree.get_children():
tree.delete(item)
Then insert the rows from the newly sorted DataFrame.
for row in df.itertuples(
index=False,
name=None
):
values=[
'' if pd.isna(value)
else value
for value in row
]
tree.insert(
'',
tk.END,
values=values
)
The first cell is no longer used as the Treeview iid. This avoids duplicate-ID errors when several rows contain the same first-column value.

A vertical scrollbar is useful for many rows, while a horizontal scrollbar helps when the selected file contains many columns.
y_scroll=ttk.Scrollbar(
table_frame,
orient='vertical',
command=tree.yview
)
x_scroll=ttk.Scrollbar(
table_frame,
orient='horizontal',
command=tree.xview
)
tree.configure(
yscrollcommand=y_scroll.set,
xscrollcommand=x_scroll.set
)
The number of rows and columns is displayed using:
details_var.set(
f'Rows: {df.shape[0]} Columns: {df.shape[1]}'
)

The Select File Button can be used repeatedly. When another file is selected:
Assume the user clicks the Mark heading for the first time.
sort_state.get(
'Mark',
True
)
No previous value exists, so the default is True.
The DataFrame is therefore sorted in ascending order:
35
42
56
78
91
The next state is stored as:
sort_state['Mark']=False
Clicking Mark again produces:
91
78
56
42
35
If the user then clicks another column such as Name, that column independently starts with ascending order.
sort_values() changes row order but normally keeps the existing DataFrame index labels.
After sorting we use:
df=sorted_df.reset_index(
drop=True
)
This gives the sorted DataFrame a clean sequential index. The index is not displayed in Treeview.
DataFrames may contain missing values. The sorting operation uses:
na_position='last'
so missing values remain at the bottom for both ascending and descending views.
When the DataFrame is displayed, a missing value is converted to an empty Treeview cell:
'' if pd.isna(value)
else value
Column sorting is one part of a larger DataFrame viewer. You can continue by adding search controls:
Search DataFrame and Display Results in TreeviewYou can also let the user choose which DataFrame columns should be visible:
Select DataFrame Columns using CheckbuttonsFor SQLite data, continue with the database version:
SQLite DataFrame with Treeview Sorting and CSV ExportThe same column-sorting pattern is also used in the directory browser project:
Directory Browser with Treeview Column SortingClicking a Treeview heading calls a function that uses Pandas sort_values() on the corresponding DataFrame column and then refreshes the displayed rows.
A dictionary stores the next ascending or descending state for each DataFrame column independently.
Reusing one widget avoids creating overlapping Treeviews and scrollbars whenever the user sorts the data.
Yes. Excel files are read with read_excel() and CSV files with read_csv(). Both become Pandas DataFrames before being displayed.
Values in the first column may repeat. Letting Treeview create its own internal item identifiers prevents duplicate iid errors.
The example uses na_position='last' so missing values appear after the non-missing values and are displayed as blank Treeview cells.
Yes. A filtered DataFrame can be displayed with the same Treeview refresh method and then sorted using the selected column.
Author & Instructor at plus2net
I write and maintain practical tutorials on Python, PHP, SQL, JavaScript, HTML, jQuery, and web development at plus2net. The tutorials focus on clear explanations, working examples, and code that readers can test and adapt while learning.