joseph-data commited on
Commit
aa1bc5b
·
verified ·
1 Parent(s): d2751bb

Sync from GitHub via hub-sync

Browse files
Files changed (4) hide show
  1. README.md +1 -1
  2. app.py +5 -16
  3. src/calcs.py +4 -1
  4. 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 trend 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
 
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
- if not occs or not ages:
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
- if not occs:
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 Trends (Selected Occupations)")
247
 
248
  @render_plotly
249
  def comparison_employment_plot():
250
- df = comparison_data()
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
- df = comp_radar_data()
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(pl.col("count").sum())
 
 
 
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 trends across selected occupations."""
142
  if df.empty:
143
  return go.Figure()
144
 
145
  fig = px.line(
146
  df,
147
  x="year",
148
- y="count",
149
  color="occupation",
150
  markers=True,
151
- labels={"count": "Total Employment", "year": "Year"},
 
 
 
 
 
 
 
 
 
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)