Spaces:
Sleeping
Sleeping
Sync from GitHub via hub-sync
Browse files- README.md +1 -1
- app.py +5 -16
- src/calcs.py +4 -1
- src/visuals.py +14 -3
README.md
CHANGED
|
@@ -17,7 +17,7 @@ An interactive Shiny app for exploring AI exposure and employment trends across
|
|
| 17 |
- **Occupation View**: Select an occupation by SSYK level and year to see employment statistics and % changes (1, 3, 5-year)
|
| 18 |
- **AI Exposure**: Ranked bar chart of AI sub-domain exposure scores with percentile rankings and exposure levels
|
| 19 |
- **Employment by Age Group**: Line chart of annual employment % change by age group over a custom year range
|
| 20 |
-
- **Comparison View**: Benchmark multiple occupations side by side with an employment
|
| 21 |
- **Download**: Filter and export the full dataset as CSV, Parquet, or Excel
|
| 22 |
|
| 23 |
## Data Sources
|
|
|
|
| 17 |
- **Occupation View**: Select an occupation by SSYK level and year to see employment statistics and % changes (1, 3, 5-year)
|
| 18 |
- **AI Exposure**: Ranked bar chart of AI sub-domain exposure scores with percentile rankings and exposure levels
|
| 19 |
- **Employment by Age Group**: Line chart of annual employment % change by age group over a custom year range
|
| 20 |
+
- **Comparison View**: Benchmark multiple occupations side by side with an annual employment % change chart, summary table, and AI exposure radar chart
|
| 21 |
- **Download**: Filter and export the full dataset as CSV, Parquet, or Excel
|
| 22 |
|
| 23 |
## Data Sources
|
app.py
CHANGED
|
@@ -19,7 +19,6 @@ from src.setup import (
|
|
| 19 |
build_choices_by_level,
|
| 20 |
download_extension,
|
| 21 |
download_media_type,
|
| 22 |
-
empty_figure,
|
| 23 |
export_filtered_data,
|
| 24 |
lf,
|
| 25 |
)
|
|
@@ -82,8 +81,7 @@ def comparison_data():
|
|
| 82 |
"""Return total employment per year/occupation for the comparison view."""
|
| 83 |
occs = list(input.comp_occs())
|
| 84 |
ages = list(input.comp_age())
|
| 85 |
-
|
| 86 |
-
return pl.DataFrame()
|
| 87 |
return calcs.get_comparison_employment(lf, occs, ages)
|
| 88 |
|
| 89 |
|
|
@@ -91,8 +89,7 @@ def comparison_data():
|
|
| 91 |
def comp_radar_data():
|
| 92 |
"""Return mean AI percentile scores per occupation for the radar chart."""
|
| 93 |
occs = list(input.comp_occs())
|
| 94 |
-
|
| 95 |
-
return pl.DataFrame()
|
| 96 |
return calcs.get_comp_radar(lf, occs, int(input.comp_year()))
|
| 97 |
|
| 98 |
|
|
@@ -203,8 +200,6 @@ with ui.navset_pill(id="tab"):
|
|
| 203 |
@render.ui
|
| 204 |
def comparison_summary():
|
| 205 |
df = comparison_data()
|
| 206 |
-
if df.is_empty():
|
| 207 |
-
return ui.markdown("*Select occupations to generate a summary...*")
|
| 208 |
|
| 209 |
latest_yr = df["year"].max()
|
| 210 |
summary_rows = []
|
|
@@ -243,24 +238,18 @@ with ui.navset_pill(id="tab"):
|
|
| 243 |
|
| 244 |
with ui.layout_columns(col_widths=[6, 6], gap="1rem"):
|
| 245 |
with ui.card(full_screen=True):
|
| 246 |
-
ui.card_header("Employment
|
| 247 |
|
| 248 |
@render_plotly
|
| 249 |
def comparison_employment_plot():
|
| 250 |
-
|
| 251 |
-
if df.is_empty():
|
| 252 |
-
return empty_figure("Select occupations to compare", {"text": "#666"})
|
| 253 |
-
return visuals.build_comparison_employment_plot(df.to_pandas())
|
| 254 |
|
| 255 |
with ui.card(full_screen=True):
|
| 256 |
ui.card_header("Radar Comparison (AI Exposure Percentiles)")
|
| 257 |
|
| 258 |
@render_plotly
|
| 259 |
def comp_radar_plot():
|
| 260 |
-
|
| 261 |
-
if df.is_empty():
|
| 262 |
-
return empty_figure("Select occupations to compare", {"text": "#666"})
|
| 263 |
-
return visuals.build_comp_radar_plot(df.to_pandas(), METRICS)
|
| 264 |
|
| 265 |
with ui.nav_panel(title="3. Download"):
|
| 266 |
ui.p(
|
|
|
|
| 19 |
build_choices_by_level,
|
| 20 |
download_extension,
|
| 21 |
download_media_type,
|
|
|
|
| 22 |
export_filtered_data,
|
| 23 |
lf,
|
| 24 |
)
|
|
|
|
| 81 |
"""Return total employment per year/occupation for the comparison view."""
|
| 82 |
occs = list(input.comp_occs())
|
| 83 |
ages = list(input.comp_age())
|
| 84 |
+
req(occs, ages)
|
|
|
|
| 85 |
return calcs.get_comparison_employment(lf, occs, ages)
|
| 86 |
|
| 87 |
|
|
|
|
| 89 |
def comp_radar_data():
|
| 90 |
"""Return mean AI percentile scores per occupation for the radar chart."""
|
| 91 |
occs = list(input.comp_occs())
|
| 92 |
+
req(occs)
|
|
|
|
| 93 |
return calcs.get_comp_radar(lf, occs, int(input.comp_year()))
|
| 94 |
|
| 95 |
|
|
|
|
| 200 |
@render.ui
|
| 201 |
def comparison_summary():
|
| 202 |
df = comparison_data()
|
|
|
|
|
|
|
| 203 |
|
| 204 |
latest_yr = df["year"].max()
|
| 205 |
summary_rows = []
|
|
|
|
| 238 |
|
| 239 |
with ui.layout_columns(col_widths=[6, 6], gap="1rem"):
|
| 240 |
with ui.card(full_screen=True):
|
| 241 |
+
ui.card_header("Annual Employment Change (Selected Occupations)")
|
| 242 |
|
| 243 |
@render_plotly
|
| 244 |
def comparison_employment_plot():
|
| 245 |
+
return visuals.build_comparison_employment_plot(comparison_data().to_pandas())
|
|
|
|
|
|
|
|
|
|
| 246 |
|
| 247 |
with ui.card(full_screen=True):
|
| 248 |
ui.card_header("Radar Comparison (AI Exposure Percentiles)")
|
| 249 |
|
| 250 |
@render_plotly
|
| 251 |
def comp_radar_plot():
|
| 252 |
+
return visuals.build_comp_radar_plot(comp_radar_data().to_pandas(), METRICS)
|
|
|
|
|
|
|
|
|
|
| 253 |
|
| 254 |
with ui.nav_panel(title="3. Download"):
|
| 255 |
ui.p(
|
src/calcs.py
CHANGED
|
@@ -139,7 +139,10 @@ def get_comparison_employment(
|
|
| 139 |
& pl.col("age_group").is_in(age_groups),
|
| 140 |
)
|
| 141 |
.group_by(["year", "occupation"])
|
| 142 |
-
.agg(
|
|
|
|
|
|
|
|
|
|
| 143 |
.sort(["occupation", "year"])
|
| 144 |
.collect()
|
| 145 |
)
|
|
|
|
| 139 |
& pl.col("age_group").is_in(age_groups),
|
| 140 |
)
|
| 141 |
.group_by(["year", "occupation"])
|
| 142 |
+
.agg([
|
| 143 |
+
pl.col("count").sum(),
|
| 144 |
+
pl.col("pct_chg_1y").mean(),
|
| 145 |
+
])
|
| 146 |
.sort(["occupation", "year"])
|
| 147 |
.collect()
|
| 148 |
)
|
src/visuals.py
CHANGED
|
@@ -138,21 +138,32 @@ def build_age_chart(df: pd.DataFrame, occupation: str) -> go.Figure:
|
|
| 138 |
|
| 139 |
|
| 140 |
def build_comparison_employment_plot(df: pd.DataFrame) -> go.Figure:
|
| 141 |
-
"""Build a line chart comparing employment
|
| 142 |
if df.empty:
|
| 143 |
return go.Figure()
|
| 144 |
|
| 145 |
fig = px.line(
|
| 146 |
df,
|
| 147 |
x="year",
|
| 148 |
-
y="
|
| 149 |
color="occupation",
|
| 150 |
markers=True,
|
| 151 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 152 |
)
|
|
|
|
| 153 |
fig.update_layout(
|
| 154 |
**_BASE_LAYOUT,
|
| 155 |
legend={"orientation": "h", "yanchor": "bottom", "y": -0.25, "xanchor": "center", "x": 0.5, "title": None},
|
|
|
|
| 156 |
)
|
| 157 |
fig.update_xaxes(gridcolor=_C_GRID, zeroline=False, dtick=1)
|
| 158 |
fig.update_yaxes(gridcolor=_C_GRID, zeroline=False)
|
|
|
|
| 138 |
|
| 139 |
|
| 140 |
def build_comparison_employment_plot(df: pd.DataFrame) -> go.Figure:
|
| 141 |
+
"""Build a line chart comparing 1-yr employment % change across selected occupations."""
|
| 142 |
if df.empty:
|
| 143 |
return go.Figure()
|
| 144 |
|
| 145 |
fig = px.line(
|
| 146 |
df,
|
| 147 |
x="year",
|
| 148 |
+
y="pct_chg_1y",
|
| 149 |
color="occupation",
|
| 150 |
markers=True,
|
| 151 |
+
custom_data=["count"],
|
| 152 |
+
labels={"pct_chg_1y": "Employment Change (%)", "year": "Year"},
|
| 153 |
+
)
|
| 154 |
+
fig.update_traces(
|
| 155 |
+
hovertemplate=(
|
| 156 |
+
"<b>%{fullData.name}</b><br>"
|
| 157 |
+
"Year: %{x}<br>"
|
| 158 |
+
"Change: %{y:.1f}%<br>"
|
| 159 |
+
"Employment: %{customdata[0]:,}<extra></extra>"
|
| 160 |
+
),
|
| 161 |
)
|
| 162 |
+
fig.add_hline(y=0, line_color="grey", line_width=1)
|
| 163 |
fig.update_layout(
|
| 164 |
**_BASE_LAYOUT,
|
| 165 |
legend={"orientation": "h", "yanchor": "bottom", "y": -0.25, "xanchor": "center", "x": 0.5, "title": None},
|
| 166 |
+
yaxis={"ticksuffix": "%"},
|
| 167 |
)
|
| 168 |
fig.update_xaxes(gridcolor=_C_GRID, zeroline=False, dtick=1)
|
| 169 |
fig.update_yaxes(gridcolor=_C_GRID, zeroline=False)
|