File size: 8,408 Bytes
c2a7494 fdce7f7 c2a7494 7b72661 c2a7494 7b72661 c2a7494 5f4b467 c2a7494 7b72661 b9b50e9 7b72661 b9b50e9 7b72661 b9b50e9 7b72661 b9b50e9 7b72661 b9b50e9 7b72661 b9b50e9 c2a7494 fdce7f7 2c8188d 7b72661 fdce7f7 b9b50e9 7b72661 fdce7f7 7b72661 fdce7f7 c10b3f9 fdce7f7 7b72661 c10b3f9 fdce7f7 7b72661 1326591 c10b3f9 7b72661 c10b3f9 c2a7494 7b72661 5f4b467 c10b3f9 5f4b467 7b72661 c10b3f9 7b72661 5f4b467 c2a7494 7b72661 b9b50e9 7b72661 c2a7494 7b72661 b9b50e9 7b72661 5f4b467 7b72661 c10b3f9 b9b50e9 7b72661 c10b3f9 7b72661 c10b3f9 b9b50e9 5f4b467 7b72661 5f4b467 7b72661 b9b50e9 7b72661 b9b50e9 7b72661 c10b3f9 b9b50e9 7b72661 b9b50e9 7b72661 b9b50e9 7b72661 b9b50e9 5f4b467 c2a7494 b9b50e9 c10b3f9 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 | import pandas as pd
import gradio as gr
import plotly.express as px
# --- 1. Helper Functions ---
def categorize_rank(r):
"""Categorizes the rank into SEO buckets."""
if pd.isna(r) or r == 0 or r == "NA":
return "Not Talking (Intent Not Correct)"
elif r <= 3: return "01–03"
elif r <= 10: return "04–10"
elif r <= 20: return "11–20"
elif r <= 30: return "21–30"
elif r <= 40: return "31–40"
elif r <= 50: return "41–50"
elif r <= 100: return "51–100"
else: return "100+"
def clean_and_process_sheet(df):
"""
Takes a raw dataframe from a single sheet, cleans columns,
identifies dates, and adds Rank Range columns.
"""
# Clean Headers
df.columns = df.columns.map(lambda x: str(x).strip())
non_date_cols = ["Keyword", "Avg. monthly searches"]
available_non_date = [c for c in non_date_cols if c in df.columns]
# Identify Date Columns
potential_date_cols = [col for col in df.columns if col not in non_date_cols and "Unnamed" not in col]
rename_dict = {}
for col in potential_date_cols:
try:
cleaned = str(col).replace("_", " ").replace(".", " ").strip()
d = pd.to_datetime(cleaned, errors="coerce", dayfirst=True)
if pd.notna(d):
rename_dict[col] = d.strftime("%d_%b_%Y")
except: pass
df.rename(columns=rename_dict, inplace=True)
date_cols = list(rename_dict.values())
# Build Processed DataFrame
processed_df = df[available_non_date].copy()
for col in date_cols:
range_col_name = f"{col}_Rank Range"
raw_vals = pd.to_numeric(df[col], errors="coerce")
processed_df[f"{col}_Rank_Numeric"] = raw_vals.fillna(0).astype(int)
processed_df[range_col_name] = raw_vals.apply(categorize_rank)
return processed_df
def build_chart_and_filters(df):
"""
Generates the Plotly figure and filter choices for a specific dataframe.
"""
if df is None or df.empty:
return None, [], []
# 1. Prepare Data for Chart
viz_data = []
# Identify all date columns by looking for "_Rank Range"
range_cols = [c for c in df.columns if "_Rank Range" in c]
for range_col in range_cols:
# Extract date from column name (e.g., "27_Oct_2025_Rank Range" -> "27_Oct_2025")
month_name = range_col.replace("_Rank Range", "")
counts = df[range_col].value_counts().reset_index()
counts.columns = ['Range', 'Keyword Count']
counts['Month'] = month_name
viz_data.append(counts)
full_viz_df = pd.concat(viz_data) if viz_data else pd.DataFrame()
# 2. Define Sorting Order
correct_order = [
"01–03", "04–10", "11–20", "21–30", "31–40",
"41–50", "51–100", "100+", "Not Talking (Intent Not Correct)"
]
fig = None
unique_months = []
if not full_viz_df.empty:
unique_months = list(full_viz_df['Month'].unique())
fig = px.bar(
full_viz_df,
x='Range',
y='Keyword Count',
color='Month',
barmode='group', # Side-by-side bars
text_auto=True,
title="Keyword Performance Distribution",
category_orders={"Range": correct_order}
)
return fig, unique_months, correct_order
# --- 2. Gradio Event Functions ---
def process_upload_initial(file):
"""
Reads ALL sheets, stores them in State, and renders the FIRST sheet.
"""
if file is None:
return None, None, gr.Dropdown(choices=[]), None, None, gr.Dropdown(choices=[]), gr.Dropdown(choices=[])
excel = pd.ExcelFile(file.name)
all_sheets_data = {}
# Process every sheet and store in dictionary
for sheet_name in excel.sheet_names:
raw_df = pd.read_excel(file.name, sheet_name=sheet_name)
all_sheets_data[sheet_name] = clean_and_process_sheet(raw_df)
sheet_names = list(all_sheets_data.keys())
first_sheet_name = sheet_names[0]
first_df = all_sheets_data[first_sheet_name]
# Generate View for First Sheet
fig, months, ranges = build_chart_and_filters(first_df)
return (
all_sheets_data, # State: All Data
first_df, # State: Current Sheet Data
gr.Dropdown(choices=sheet_names, value=first_sheet_name), # Sheet Selector
fig, # Chart
first_df, # Table
gr.Dropdown(choices=months, value=months[0] if months else None), # Month Filter
gr.Dropdown(choices=ranges, value=ranges[0] if ranges else None) # Range Filter
)
def change_sheet(selected_sheet, all_data):
"""
Switches the view when a new sheet is selected from dropdown.
"""
if not selected_sheet or not all_data:
return None, None, None, gr.Dropdown(choices=[]), gr.Dropdown(choices=[])
new_df = all_data[selected_sheet]
fig, months, ranges = build_chart_and_filters(new_df)
return (
new_df, # Update Current Sheet State
fig, # Update Chart
new_df, # Update Table
gr.Dropdown(choices=months, value=months[0] if months else None), # Update Month Choices
gr.Dropdown(choices=ranges, value=ranges[0] if ranges else None) # Update Range Choices
)
def filter_table(current_df, selected_month, selected_range):
"""
Filters the CURRENTLY active sheet's dataframe.
"""
if current_df is None or current_df.empty: return None
target_col = f"{selected_month}_Rank Range"
if target_col in current_df.columns:
filtered_df = current_df[current_df[target_col] == selected_range]
return filtered_df
return current_df
def reset_table(current_df):
return current_df
# --- 3. UI Layout ---
with gr.Blocks(theme=gr.themes.Soft()) as demo:
# STATES
all_sheets_state = gr.State({}) # Stores DICTIONARY of all sheets {name: df}
current_sheet_state = gr.State(pd.DataFrame()) # Stores currently visible sheet DF
gr.Markdown("# 🚀 SEO Multi-Sheet Dashboard")
# TOP ROW: Upload & Sheet Selection
with gr.Row():
file_input = gr.File(label="Upload Excel (Multiple Sheets Supported)", file_types=[".xlsx"])
with gr.Column():
process_btn = gr.Button("📊 Load File", variant="primary")
# NEW: Sheet Selector Dropdown
sheet_dropdown = gr.Dropdown(label="📑 Select Sheet to Analyze", choices=[], interactive=True)
# VISUALIZATION
plot_output = gr.Plot(label="Keyword Distribution")
# FILTER ROW
gr.Markdown("### 🔍 Filter Data")
with gr.Row():
month_dropdown = gr.Dropdown(label="Select Month", choices=[])
range_dropdown = gr.Dropdown(label="Select Rank Range", choices=[])
filter_btn = gr.Button("Apply Filter")
reset_btn = gr.Button("Show All Rows")
table_output = gr.DataFrame(interactive=False)
# --- EVENTS ---
# 1. Upload & Process (Loads all sheets, defaults to first)
process_btn.click(
fn=process_upload_initial,
inputs=file_input,
outputs=[
all_sheets_state, # Save all sheets
current_sheet_state, # Save current sheet
sheet_dropdown, # Update dropdown options
plot_output, # Show chart
table_output, # Show table
month_dropdown, # Update filters
range_dropdown
]
)
# 2. Change Sheet (User selects "RD Monthly" etc.)
sheet_dropdown.change(
fn=change_sheet,
inputs=[sheet_dropdown, all_sheets_state],
outputs=[
current_sheet_state,
plot_output,
table_output,
month_dropdown,
range_dropdown
]
)
# 3. Filter Data (Applied to current sheet)
filter_btn.click(
fn=filter_table,
inputs=[current_sheet_state, month_dropdown, range_dropdown],
outputs=table_output
)
# 4. Reset Data
reset_btn.click(
fn=reset_table,
inputs=current_sheet_state,
outputs=table_output
)
if __name__ == "__main__":
demo.launch() |