
This project uses Tkinter and Pandas to prepare a Google Analytics 4 report exported as a CSV file.
The user selects the Analytics CSV through a Tkinter file browser. The application detects the report header, creates a DataFrame, removes empty data, optionally removes query strings from page paths, creates Python-friendly column names and saves the cleaned DataFrame as another CSV file.
GA4 report
|
v
Download CSV
|
v
Tkinter file browser
|
v
Detect header row
|
v
pd.read_csv()
|
v
Clean DataFrame
|
v
Save cleaned CSV
In Google Analytics 4, open the Pages and screens report and set the required date range and report configuration before exporting the data.
Download the report as a CSV file. The exact columns in the downloaded file depend on the dimension, metrics and report configuration you selected.
A page report can contain columns such as:
Page path and screen class
Views
Active users
Views per active user
Average engagement time per active user
Event count
Key events
Total revenue
Do not build the Python program around one fixed list of Analytics metrics. A report can contain a different set of columns.
The older version of this tutorial used:
df=pd.read_csv(
file,
skiprows=6
)
That depended on the format of an older Analytics export.
A more reusable approach is:
Analytics CSV
|
v
Scan first rows
|
v
Find page-dimension header
|
v
Use that row as CSV header
This lets the application handle leading report information without assuming that the header is always on a particular physical line.
The application looks for common Pages and screens dimensions:
PAGE_DIMENSIONS=[
'Page path and screen class',
'Page path + query string and screen class',
'Page title and screen class',
'Page title and screen name'
]
The first part of the CSV file is scanned:
def detect_header_row(file_path):
with open(
file_path,
'r',
encoding='utf-8-sig',
newline=''
) as file:
reader=csv.reader(file)
for row_number,row in enumerate(reader):
values=[
value.strip()
for value in row
]
if any(
name in values
for name in PAGE_DIMENSIONS
):
return row_number
If no expected page dimension is found, the application reports that the selected file does not look like the expected GA4 page report.
After detecting the header row:
header_row=detect_header_row(
file_path
)
df=pd.read_csv(
file_path,
skiprows=header_row
)
The selected header row becomes the DataFrame column header.
The application preserves two DataFrames:
source_df
cleaned_df
source_df represents the imported Analytics report. cleaned_df is created when the cleaning operation runs.
Instead of assuming that the page field is called simply Page, the application detects which supported GA4 dimension is present.
def find_page_column(columns):
for name in PAGE_DIMENSIONS:
if name in columns:
return name
return None
This makes the program less dependent on one report configuration.
Remove columns that contain no data:
cleaned_df=cleaned_df.dropna(
axis=1,
how='all'
)
Remove completely empty rows:
cleaned_df=cleaned_df.dropna(
how='all'
)
Rows without a page dimension are also removed:
cleaned_df=cleaned_df.dropna(
subset=[page_column]
)
The page values are stripped of surrounding spaces:
cleaned_df[page_column]=cleaned_df[
page_column
].astype('string').str.strip()
A URL such as:
/python/list.php?utm_source=newsletter
can represent the same underlying page as:
/python/list.php
The old tutorial removed every row containing ?. That can discard valid traffic.
The revised application can instead remove only the query-string part:
cleaned_df[page_column]=cleaned_df[
page_column
].str.replace(
r'\?.*$',
'',
regex=True
)
Fragments can be removed in the same way:
.str.replace(
r'#.*$',
'',
regex=True
)
The older tutorial forced the DataFrame into exactly seven names:
df.columns=[
'Page',
'p_view',
'u_view',
'avg',
'entrance',
'b_rate',
'b_exit'
]
This no longer fits GA4 reports.
The revised application converts whatever columns were actually exported into lower-case underscore names.
Examples:
Views
-> views
Active users
-> active_users
Average engagement time per active user
-> average_engagement_time_per_active_user
The detected page dimension is given the simpler name:
page
The application displays:
For example:
Rows: 1250
Columns: 8
Page field: page
COLUMNS
page
views
active_users
views_per_active_user
average_engagement_time_per_active_user
event_count
key_events
total_revenue
Use the Tkinter Save As dialog:
file_path=filedialog.asksaveasfilename(
defaultextension='.csv',
filetypes=[
('CSV file','*.csv')
]
)
Save the cleaned DataFrame using to_csv():
cleaned_df.to_csv(
file_path,
index=False
)
The application stores:
source_df
cleaned_df
The original imported report remains in source_df. Cleaning creates another DataFrame.
GA4 CSV
|
v
source_df
|
+----------------+
| |
v |
clean_data() |
| |
v |
cleaned_df |
|
Show Source --------+
This makes it easy to compare the report before and after cleaning.
Removing query strings can turn:
/python/list.php?a=1
/python/list.php?a=2
/python/list.php?a=3
into:
/python/list.php
/python/list.php
/python/list.php
It may be tempting to immediately group these rows and sum every metric.
However, Analytics metrics do not all behave the same way. A metric such as page views can often be summed across rows, while user-based or average metrics should not automatically be added together.
This page therefore prepares the DataFrame but leaves aggregation to a later analysis step where the correct metric-specific calculation can be chosen.
After saving the cleaned CSV, continue with the practical analysis project:
Pandas GroupBy and Pivot AnalysisYou can also choose specific DataFrame columns:
Select DataFrame ColumnsOr search the DataFrame through a Tkinter interface:
Search DataFrame RecordsIt is designed primarily for CSV exports containing a GA4 page dimension such as Page path and screen class or Page path + query string and screen class.
A fixed skip value depends on one export format. Detecting a known GA4 page dimension makes the import less dependent on a specific number of leading rows.
A question mark normally introduces a query string. Removing the complete row can discard useful Analytics data, so the revised example removes only the query-string portion of a page path.
Spaces and punctuation are converted to lower-case underscore names so the columns are easier to reference in later Python and Pandas operations.
GA4 provides several possible page dimensions. Renaming the detected dimension to a consistent page field makes later processing simpler.
No. The rows remain separate because different GA4 metrics require different aggregation rules.
No. The downloaded report remains unchanged. A separate cleaned DataFrame is created and can be saved as a new CSV file.
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.