DEV Community

YASHWANTH CHIKKI HD
YASHWANTH CHIKKI HD

Posted on Edited on

hi

import streamlit as st
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import plotly.express as px

-----------------------------

Load and clean data

-----------------------------

df = pd.read_excel("data.xlsx")

actual_column_map = {
"Advisor IP Name": "Advisor_not_used",
"Advisor_IP_ID":"Advisor_IP_ID_not_used",
"clients with individual relationship": "Clients_individual",
"a part of a team": "Clients_team_not_used",
"Total AUM": "Total_AUM",
"MD count": "MD_count",
"Client Count": "Client_count",
"Total RK Assets":"Total_RK_Assets",
"Total FMF Assets":"Total_FMF_Assets" ,

"Total external assets (Nexus)": "External_assets",
"Advisor Tenure" :"Advisor_Tenure",

"Recency - Last client onboarded (in days)": "Recency_days",
"Frequency_2024_N": "Frequency_2024",
"Frequency_2025_N": "Frequency_2025",
"Products per client (only Core clients)": "Products_per_client",
"Attrition_rate_N":"Attrition_rate_N"
}
data = df.rename(columns=actual_column_map)

-----------------------------

Derived columns

-----------------------------

data["Total_assets_not_used"] = data["Total_AUM"] + data["External_assets"]

data["Pct_external"] = np.where(
data["Total_assets_not_used"] == 0,
0,
data["External_assets"] / data["Total_assets_not_used"]
)

data["Ind_ratio"] = np.where(
data["Client_count"] == 0,
0,
data["Clients_individual"] / data["Client_count"]

)

-----------------------------

Sidebar controls

-----------------------------

st.sidebar.header("Ranking Controls")

core_weights = {
"Advisor_Tenure": st.sidebar.slider("Weight: Advisor_Tenure", 0.0, 100.0, 6.67),
"MD_count": st.sidebar.slider("Weight: MD count", 0.0, 100.0, 6.67),
"Client_count": st.sidebar.slider("Weight: Client Count", 0.0, 100.0, 6.67),
"Ind_ratio": st.sidebar.slider("Weight: percentage ofClients with 1 on 1 relation", 0.0, 100.0, 6.67),
"Total_RK_Assets": st.sidebar.slider("Weight: Total RK Assets", 0.0, 100.0, 6.67),
"Total_AUM": st.sidebar.slider("Weight: Total AUM", 0.0, 100.0, 6.67),
"Total_FMF_Assets": st.sidebar.slider("Weight: Total FMF Assets", 0.0, 100.0, 6.67),
"Recency_days": st.sidebar.slider("Weight: Recency", 0.0, 100.0, 6.67),
"Frequency_2024": st.sidebar.slider("Weight: New Clients 2024", 0.0, 100.0, 6.67),
"Frequency_2025": st.sidebar.slider("Weight: New Clients 2025", 0.0, 100.0, 6.67),
"External_assets": st.sidebar.slider("Weight: External Assets", 0.0, 100.0, 6.67),
"Pct_external": st.sidebar.slider("Weight: percentage share of external assets", 0.0, 100.0, 6.67),
"Products_per_client": st.sidebar.slider("Weight: Products_per_client", 0.0, 100.0, 6.67),

"NPS_N": st.sidebar.slider("Weight: NPS", 0.0, 100.0, 6.67),
"Attrition_rate_N": st.sidebar.slider("Weight: Attrition_rate_N", 0.0, 100.0, 6.67),

}

Normalize weights

total_wt = sum(core_weights.values())
if total_wt == 0: total_wt = 1
norm_weights = {k: v / total_wt for k, v in core_weights.items()}

-----------------------------

Extra Filters

-----------------------------

rank_df = data.copy()

st.sidebar.header("Extra Filters")
toggle_val = st.sidebar.radio("Concierge_Flag", ("All", "1 only", "0 only"))

if toggle_val == "1 only":
rank_df = rank_df[rank_df["Concierge_Flag"] == 1]
elif toggle_val == "0 only":
rank_df = rank_df[rank_df["Concierge_Flag"] == 0]

from sklearn.preprocessing import MinMaxScaler

Identify numeric columns to scale (excluding 'Advisor' and non-numeric ones)

numeric_cols = rank_df.select_dtypes(include=[np.number]).columns

rank_df["NPS_N"] = pd.to_numeric(rank_df["NPS_N"], errors="coerce").fillna(0)

Define columns to scale

columns_to_scale = [
"Advisor_Tenure","MD_count","Client_count","Ind_ratio","Total_RK_Assets", "Total_AUM","Total_FMF_Assets","Recency_days","Frequency_2024","Frequency_2025","External_assets","Pct_external","Products_per_client","NPS_N","Attrition_rate_N"

]

Define columns with negative correlation

negatively_correlated = ["Recency_days"]

Replace non-numeric values with 0 in all columns used for scoring

for col in core_weights.keys():
if col in rank_df.columns:
rank_df[col] = pd.to_numeric(rank_df[col], errors='coerce').fillna(0)

Apply Min-Max scaling only to selected columns

scaler = MinMaxScaler()
rank_df[columns_to_scale] = scaler.fit_transform(rank_df[columns_to_scale])

Reverse scaling for negatively correlated columns

for col in negatively_correlated:
if col in rank_df.columns:
rank_df[col] = 1 - rank_df[col]

Normalize weights to sum to 1

total_wt = sum(core_weights.values())
if total_wt == 0: total_wt = 1
norm_weights = {k: v / total_wt for k, v in core_weights.items()}

rank_df["count"] = rank_df["NPS_N"].apply(lambda x: 15 if x > 0 else 14)

Compute score using normalized weights and scaled features

rank_df["Score"] = rank_df.apply(
lambda row: sum(row[col] * norm_weights.get(col, 0) for col in norm_weights),
axis=1
)
rank_df["Final_Score"] = rank_df["Score"] / rank_df["count"]

data["Score"] = rank_df["Final_Score"]

Sort by Score

plot_df = data.sort_values(by="Score", ascending=False)

col1, col2, col3 = st.columns(3)

col1.metric("Total Advisors", len(plot_df))
col2.metric("Total Clients", int(plot_df["Client_count"].sum()))
col3.metric("Total Assets (M)", f"${plot_df['Total_assets_not_used'].sum()/1e6:.1f}M")

plot_df["Rank"] = plot_df["Score"].rank(method="min", ascending=False).astype(int)

display_columns = {
"Advisor_not_used": "Advisor Name",
"Advisor_IP_ID_not_used": "Advisor ID",
"Score": "Score",
"Client_count": "Client Count",
"Total_AUM": "Total AUM",
"External_assets": "External Assets",
"Rank": "Rank"
}

-----------------------------

Ranked Advisors Table

-----------------------------

st.subheader("Ranked Advisors")
st.dataframe(
plot_df.sort_values(by="Score", ascending=False)[list(display_columns.keys())]
.rename(columns=display_columns)
)

-----------------------------/////////////////////////////////////////////////////////////////////////////////////////////////////////////

Advisor Details

-----------------------------

st.subheader("Advisor Details")

Sort plot_df by Final_Score before populating the dropdown

sorted_plot_df = plot_df.sort_values(by="Score", ascending=False)

selected = st.selectbox("Select an advisor", sorted_plot_df["Advisor_not_used"])

if selected:
adv = plot_df[plot_df["Advisor_not_used"] == selected].iloc[0]
adv_scaled = rank_df[rank_df["Advisor_not_used"] == selected].iloc[0]

st.write(f"### {adv['Advisor_not_used']} - Final Score: {adv['Score']:.2f}")

import plotly.express as px

Feature contributions

contributions = {}
for feature in norm_weights:
if feature in adv_scaled:
value = adv_scaled[feature]
weight = norm_weights[feature]
contribution = value * weight
contributions[feature] = contribution

total_score = adv_scaled["Score"]
contribution_percentages = {
k: (v / total_score) * 100 for k, v in contributions.items()
}

contrib_df = pd.DataFrame({
"Feature": list(contribution_percentages.keys()),
"Contribution (%)": [round(v, 1) for v in contribution_percentages.values()]
}).sort_values(by="Contribution (%)", ascending=False)

Horizontal bar chart

bar_fig = px.bar(
contrib_df,
y="Feature",
x="Contribution (%)",
orientation='h',
title=f"Feature Contribution to Score for {adv['Advisor_not_used']}",
text=contrib_df["Contribution (%)"].apply(lambda x: f"{x}%"),
labels={"Contribution (%)": "Contribution to Score (%)"}
)
st.plotly_chart(bar_fig, use_container_width=True)

--- Pie Charts ---

col1, col2 = st.columns(2)

with col1:
fig1, ax1 = plt.subplots()
ax1.pie([adv["Total_AUM"], adv["External_assets"]],
labels=["Our AUM", "External"],
autopct="%1.1f%%", startangle=90, colors=["#4C72B0", "#55A868"])
ax1.set_title("Assets Split")
st.pyplot(fig1)

with col2:
fig2, ax2 = plt.subplots()
ax2.pie([adv["Clients_individual"], adv["Clients_team_not_used"]],
labels=["Individual", "Team"],
autopct="%1.1f%%", startangle=90, colors=["#FF6F61", "#6A5ACD"])
ax2.set_title("Client Split")
st.pyplot(fig2)

--- Additional Metrics ---

st.write("Additional Metrics:")
st.write(f"- External Assets Share: {adv['Pct_external']:.2%}")
st.write(f"- Total AUM: ${adv['Total_AUM']:.0f}")
st.write(f"- Products per Client: {round(adv['Products_per_client'], 2)}")
st.write(f"- Rank: {adv['Rank']}")

Enter fullscreen mode Exit fullscreen mode




--- Matplotlib Box Plot ---

st.subheader("Advisor Final Score Distribution (Box Plot)")
fig, ax = plt.subplots(figsize=(10, 4))
sns.boxplot(x=plot_df["Score"], ax=ax)
ax.set_title("Advisor Score Distribution")
ax.set_xlabel(" Score")
st.pyplot(fig)

--- Dynamic Percentage Inputs ---

st.subheader("Set Custom Class Thresholds (as Percentiles)")
pct_q1 = st.slider("Q1 upper bound (%)", min_value=0, max_value=100, value=25)
pct_q3 = st.slider("Q3 lower bound (%)", min_value=pct_q1, max_value=100, value=75)

--- Convert Percentiles to Score Thresholds ---

score_q1 = plot_df['Score'].quantile(pct_q1 / 100)
score_q3 = plot_df['Score'].quantile(pct_q3 / 100)

--- Classification Based on Thresholds ---

def classify_custom(score):
if score < score_q1:
return "Q1 (Lowest)"
elif score_q1 <= score <= score_q3:
return "Q2 (Middle)"
else:
return "Q3 (Highest)"

plot_df["Class"] = plot_df["Score"].apply(classify_custom)

--- Summary Stats by Class ---

summary = plot_df.groupby("Class").agg({
"Advisor_not_used": "count",
"Client_count": "sum",
"Total_AUM": "sum",
"Total_assets_not_used": "sum",
"External_assets": "mean",
"Products_per_client": "mean"
}).rename(columns={
"Advisor_not_used": "Advisor Count",
"Client_count": "Total Clients",
"Total_AUM": "Total AUM",
"Total_assets_not_used": "Total Assets",
"External_assets": "Avg External Assets",
"Products_per_client": "Avg Products per Client"
}).reset_index()

st.subheader("Class Summary")
st.dataframe(summary)

--- Advisor Lists by Class ---

for cls in ["Q1 (Lowest)", "Q2 (Middle)", "Q3 (Highest)"]:
st.markdown(f"### {cls} Advisors")
q_df = plot_df[plot_df["Class"] == cls]
st.write(q_df[[
"Advisor_not_used", "Score", "Client_count", "Total_AUM",
"Total_assets_not_used", "External_assets", "Products_per_client"
]].rename(columns={
"Advisor_not_used": "Advisor",
"Score": "Score",
"Total_assets_not_used": "Total Assets"
}))

--- Histogram of Recency ---

fig, ax = plt.subplots(figsize=(7, 4))
ax.hist(rank_df["Recency_days"], bins=10, color="#55A868", alpha=0.8, edgecolor='black')
ax.set_title("Distribution of Recency Across Advisors (Days)")
ax.set_xlabel("Days since last onboarding")
ax.set_ylabel("Number of Advisors")
st.pyplot(fig)

--- AUM-Based Filtering ---

st.subheader("Filter Advisors by AUM Range")

aum_min = st.number_input("Minimum AUM ($)", min_value=0, value=0, step=100000)
aum_max = st.number_input("Maximum AUM ($)", min_value=0, value=int(data["Total_AUM"].max()), step=100000)

apply_aum_filter = st.checkbox("Apply AUM Filter")

if apply_aum_filter:
plot_df = plot_df[
(plot_df["Total_AUM"] >= aum_min) &
(plot_df["Total_AUM"] <= aum_max)
]

Top comments (0)