import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
# Consistent color theme used across every chart on this page
PALETTE = ["#457b9d", "#2a9d8f", "#e9c46a", "#e76f51", "#6d597a", "#f4a261"]
GRAY = "#b8bec6"
# the same work arrangement always gets the same color
ARRANGEMENT_COLORS = {"remote": PALETTE[0], "hybrid": PALETTE[1], "onsite": PALETTE[2], "unknown": PALETTE[3]}
ARRANGEMENT_LABELS = {"remote": "Remote", "hybrid": "Hybrid", "onsite": "Onsite", "unknown": "Not stated"}
raw = pd.read_csv("data/processed/met_text_panel.csv")
def highest_degree(row):
# the highest degree level the posting text names, whatever the wording; None when there is no text
if not row["has_text"]:
return None
for col, label in [("degree_PhD", "PhD"), ("degree_Master", "Master's"), ("degree_Bachelor", "Bachelor's")]:
if row[col] in ("required", "preferred", "mentioned"):
return label
return "Not specified"
df = pd.DataFrame({
"job_id": raw["job_id"],
"title": raw["title"],
"state_clean": raw["state"].fillna("").replace({"": "Unknown"}),
"company_name_clean": raw["company_name_clean"],
"remote_status": raw["remote_status"].fillna("Unknown").str.lower(),
"salary_min_annual": raw["salary_min_annual"],
"salary_max_annual": raw["salary_max_annual"],
"experience_min_years": raw["experience_min_years"],
"has_text": raw["has_text"],
})
df["education_level"] = raw.apply(highest_degree, axis=1)
TOTAL = len(df)
print(f"Total postings in baseline: {TOTAL}")
SCOPE = "Analyst-to-Scientist pathway, NAICS 5182, US"
ROLE_LABELS = {"ml engineer": "ML Engineer", "bi analyst": "BI Analyst"}
def role_label(key):
return ROLE_LABELS.get(key, key.title())
def style(fig, title, subtitle, height=480, legend=False):
# chart name, then a gray line saying which postings the chart covers and how many
fig.update_layout(
title=dict(text=f"<b>{title}</b><br><span style='font-size:12px;color:gray'>{subtitle}</span>",
x=0, xanchor="left"),
template="simple_white", height=height, showlegend=legend,
font=dict(family="Segoe UI, Helvetica, Arial, sans-serif", size=13),
margin=dict(l=10, r=20, t=95, b=50),
hoverlabel=dict(bgcolor="white", font_size=13),
legend=dict(orientation="h", x=1, xanchor="right", y=1.02, yanchor="bottom"),
)
fig.update_xaxes(gridcolor="#eceff3")
fig.update_yaxes(gridcolor="#eceff3")
return fig
def show(fig):
fig.show(config={"displaylogo": False, "responsive": True, "modeBarButtonsToRemove": ["lasso2d", "select2d"]})
def hbar(labels, values, xtitle, title, subtitle, texts=None, custom=None, hover=None, color=None, money=False, height=480):
# horizontal bar chart; labels are listed bottom to top, so pass them sorted ascending
fig = go.Figure(go.Bar(
x=list(values), y=list(labels), orientation="h", marker_color=color or PALETTE[0],
text=list(texts) if texts is not None else list(values), textposition="outside", cliponaxis=False,
customdata=None if custom is None else list(custom),
hovertemplate=hover or "<b>%{y}</b><br>%{x:,.0f}<extra></extra>"))
fig.update_xaxes(title=xtitle, range=[0, max(values) * 1.18])
if money:
fig.update_xaxes(tickprefix="$", tickformat=",.0f")
return style(fig, title, subtitle, height)
def salary_bars(stats, keys, labels, colors, title, subtitle):
# average salary as bars, median as a diamond, sample size under each label
xs = [f"{labels[k]}<br>(n={int(stats.loc[k, 'count'])})" for k in keys]
label_colors = ["#1d2733" if colors[k] in (PALETTE[2], GRAY) else "white" for k in keys]
fig = go.Figure()
fig.add_trace(go.Bar(
x=xs, y=[stats.loc[k, "mean"] for k in keys], name="Average", marker_color=[colors[k] for k in keys],
text=[f"${stats.loc[k, 'mean']:,.0f}" for k in keys], textposition="inside", insidetextanchor="start",
textfont=dict(color=label_colors, size=14),
customdata=[[stats.loc[k, "median"]] for k in keys],
hovertemplate="<b>%{x}</b><br>Average: $%{y:,.0f}<br>Median: $%{customdata[0]:,.0f}<extra></extra>"))
fig.add_trace(go.Scatter(
x=xs, y=[stats.loc[k, "median"] for k in keys], mode="markers", name="Median",
marker=dict(symbol="diamond", size=12, color="black", line=dict(color="white", width=1)),
hovertemplate="Median: $%{y:,.0f}<extra></extra>"))
fig.update_yaxes(title="Estimated annual salary (USD)", tickprefix="$", tickformat=",.0f",
range=[0, stats["mean"].max() * 1.15])
style(fig, title, subtitle, height=520, legend=True)
fig.update_layout(legend=dict(orientation="h", x=0.5, xanchor="center", y=-0.2, yanchor="top"),
margin=dict(b=110))
return fig