LeonceNsh commited on
Commit
9dc78cb
Β·
verified Β·
1 Parent(s): 40947f4

Update gradio_app.py

Browse files
Files changed (1) hide show
  1. gradio_app.py +249 -71
gradio_app.py CHANGED
@@ -4,12 +4,16 @@ import plotly.express as px
4
  import plotly.graph_objects as go
5
  import json
6
 
7
- # Color palette for accessibility
8
  colorblind_palette = [
9
- "#377eb8", # blue
10
- "#ff7f00", # orange
11
- "#a65628", # brown
12
- "#999999", # gray
 
 
 
 
13
  ]
14
 
15
  # Cache for data (simple in-memory cache)
@@ -57,32 +61,112 @@ def get_col_prefix(authority):
57
  else:
58
  raise ValueError(f"Unknown authority {authority}")
59
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  def get_aa_count_chart(df):
61
  """Get a chart that shows detentions by arresting authority as a count."""
62
  if df.empty:
63
  return go.Figure().add_annotation(text="Error loading data", showarrow=False)
64
 
65
- df = df.rename(columns={"ice_all": "ICE", "cbp_all": "CBP", "total_all": "Total"})
 
 
 
 
 
66
 
67
- df_melted = df.melt(
 
68
  id_vars="date",
69
- value_vars=["ICE", "CBP", "Total"],
70
  var_name="Arresting Authority",
71
  value_name="count",
72
  )
73
 
 
74
  fig = px.line(
75
  df_melted,
76
  x="date",
77
  y="count",
78
  color="Arresting Authority",
79
  color_discrete_sequence=colorblind_palette,
 
80
  )
81
 
82
- fig.update_layout(
83
- xaxis_title="Date",
84
- yaxis_title="Detainees",
85
- title="ICE Detainees by Date and Arresting Authority",
 
 
 
 
 
 
 
 
 
 
 
 
86
  )
87
 
88
  return fig
@@ -92,28 +176,56 @@ def get_aa_pct_chart(df):
92
  if df.empty:
93
  return go.Figure().add_annotation(text="Error loading data", showarrow=False)
94
 
95
- df["ICE"] = (df.ice_all / df.total_all * 100).round()
96
- df["CBP"] = (df.cbp_all / df.total_all * 100).round()
 
 
97
 
98
- df_melted = df.melt(
 
99
  id_vars="date",
100
- value_vars=["ICE", "CBP"],
101
  var_name="Arresting Authority",
102
  value_name="percent",
103
  )
104
 
 
105
  fig = px.line(
106
  df_melted,
107
  x="date",
108
  y="percent",
109
  color="Arresting Authority",
110
  color_discrete_sequence=colorblind_palette,
 
111
  )
112
 
113
- fig.update_layout(
114
- xaxis_title="Date",
115
- yaxis_title="Percent",
116
- title="ICE Detainees by Date* and Arresting Authority",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
  )
118
 
119
  return fig
@@ -124,39 +236,56 @@ def get_criminality_count_chart(df, authority):
124
  return go.Figure().add_annotation(text="Error loading data", showarrow=False)
125
 
126
  prefix = get_col_prefix(authority)
127
- df = df.rename(
 
 
 
128
  columns={
129
- f"{prefix}_all": "Total",
130
- f"{prefix}_conv": "Convicted Criminal",
131
  f"{prefix}_pend": "Pending Criminal Charges",
132
- f"{prefix}_other": "Other Immigration Violator",
133
  }
134
  )
135
 
136
- df_melted = df.melt(
 
137
  id_vars="date",
138
  value_vars=[
139
- "Convicted Criminal",
140
- "Pending Criminal Charges",
141
- "Other Immigration Violator",
142
- "Total",
143
  ],
144
  var_name="Criminal Status",
145
  value_name="count",
146
  )
147
 
 
148
  fig = px.line(
149
  df_melted,
150
  x="date",
151
  y="count",
152
  color="Criminal Status",
153
  color_discrete_sequence=colorblind_palette,
 
154
  )
155
 
156
- fig.update_layout(
157
- xaxis_title="Date",
158
- yaxis_title="Detainees",
159
- title="ICE Detainees by Date*, Criminality** and Arresting Authority",
 
 
 
 
 
 
 
 
 
 
 
 
 
160
  )
161
 
162
  return fig
@@ -172,48 +301,84 @@ def get_criminality_pct_chart(df, authority):
172
  pend_col = f"{prefix}_pend"
173
  other_col = f"{prefix}_other"
174
 
175
- df["Convicted Criminal"] = (df[conv_col] / df[all_col] * 100).round()
176
- df["Pending Criminal Charges"] = (df[pend_col] / df[all_col] * 100).round()
177
- df["Other Immigration Violator"] = (df[other_col] / df[all_col] * 100).round()
 
 
178
 
179
- df_melted = df.melt(
 
180
  id_vars="date",
181
  value_vars=[
182
- "Convicted Criminal",
183
- "Pending Criminal Charges",
184
- "Other Immigration Violator",
185
  ],
186
  var_name="Criminal Status",
187
  value_name="percent",
188
  )
189
 
 
190
  fig = px.line(
191
  df_melted,
192
  x="date",
193
  y="percent",
194
  color="Criminal Status",
195
  color_discrete_sequence=colorblind_palette,
 
196
  )
197
 
198
- fig.update_layout(
199
- xaxis_title="Date",
200
- yaxis_title="Percent",
201
- title="ICE Detainees by Date, Criminality, and Arresting Authority",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
202
  )
203
 
204
  return fig
205
 
206
  def get_footnote_text(dataset):
207
  """Get footnote text for the dataset."""
208
- abbreviations = 'ICE: Immigration and Customs Enforcement. \nCBP: Customs and Border Protection\n.'
209
- date_footnote = "\n*Dates < 11/15/2021 date ICE posted the data; \ndates > 11/15/2021 refer to the date the information was current as of."
210
-
211
- if dataset == "Arresting Authority":
212
- return f"{abbreviations}\n{date_footnote}"
213
- elif dataset == "Criminality":
214
- criminality_footnote = "**\nICE classifies an individual as a convicted criminal if they have been convicted of any criminal violation. Violations can range from serious felonies all the way down to a purely immigration violation (such as illegal entry which is a petty offense under the U.S. Code), or a violation which results in only in a fine such as not keeping a dog on a leash, fishing without a permit, driving a vehicle with a tail light out, etc."
215
- return f"{abbreviations}\n{date_footnote}\n{criminality_footnote}"
216
- return ""
 
 
 
 
 
 
 
217
 
218
  def update_chart(dataset, display, authority):
219
  """Update the chart based on user selections."""
@@ -273,26 +438,37 @@ def create_app():
273
  gr.Markdown("*Interactive visualization of ICE detention statistics*")
274
 
275
  with gr.Tabs():
276
- with gr.TabItem("Graphs"):
277
  with gr.Row():
278
- dataset = gr.Dropdown(
279
- choices=["Arresting Authority", "Criminality"],
280
- value="Arresting Authority",
281
- label="Dataset"
282
- )
283
- display = gr.Dropdown(
284
- choices=["Count", "Percent"],
285
- value="Count",
286
- label="Display"
287
- )
288
- authority = gr.Dropdown(
289
- choices=["All", "ICE", "CBP"],
290
- value="All",
291
- label="Arresting Authority",
292
- visible=False
293
- )
 
 
 
 
 
 
 
 
 
 
 
 
294
 
295
- chart = gr.Plot()
296
  footnote = gr.Markdown()
297
 
298
  # Update authority dropdown visibility based on dataset
@@ -320,6 +496,8 @@ def create_app():
320
  outputs=[chart, footnote]
321
  )
322
 
 
 
323
 
324
  return app
325
 
 
4
  import plotly.graph_objects as go
5
  import json
6
 
7
+ # Color-blind friendly palette (Okabe-Ito palette)
8
  colorblind_palette = [
9
+ "#0173b2", # blue
10
+ "#de8f05", # orange
11
+ "#029e73", # green
12
+ "#d55e00", # red-orange
13
+ "#cc78bc", # pink
14
+ "#ca9161", # brown
15
+ "#fbafe4", # light pink
16
+ "#949494", # gray
17
  ]
18
 
19
  # Cache for data (simple in-memory cache)
 
61
  else:
62
  raise ValueError(f"Unknown authority {authority}")
63
 
64
+ def format_number(num):
65
+ """Format large numbers with proper separators."""
66
+ if num >= 1000000:
67
+ return f"{num/1000000:.1f}M"
68
+ elif num >= 1000:
69
+ return f"{num/1000:.0f}K"
70
+ else:
71
+ return f"{num:,.0f}"
72
+
73
+ def apply_chart_styling(fig, title, x_title, y_title, show_legend=True):
74
+ """Apply consistent styling to all charts."""
75
+ fig.update_layout(
76
+ title={
77
+ 'text': title,
78
+ 'x': 0.5,
79
+ 'xanchor': 'center',
80
+ 'font': {'size': 18, 'family': 'Arial, sans-serif', 'color': '#2c3e50'}
81
+ },
82
+ xaxis={
83
+ 'title': {'text': x_title, 'font': {'size': 14, 'family': 'Arial, sans-serif'}},
84
+ 'tickfont': {'size': 12, 'family': 'Arial, sans-serif'},
85
+ 'gridcolor': '#e8e8e8',
86
+ 'gridwidth': 0.5
87
+ },
88
+ yaxis={
89
+ 'title': {'text': y_title, 'font': {'size': 14, 'family': 'Arial, sans-serif'}},
90
+ 'tickfont': {'size': 12, 'family': 'Arial, sans-serif'},
91
+ 'gridcolor': '#e8e8e8',
92
+ 'gridwidth': 0.5,
93
+ 'tickformat': ',.0f' if 'Percent' not in y_title else '.0f'
94
+ },
95
+ legend={
96
+ 'orientation': 'h',
97
+ 'yanchor': 'bottom',
98
+ 'y': 1.02,
99
+ 'xanchor': 'center',
100
+ 'x': 0.5,
101
+ 'font': {'size': 12, 'family': 'Arial, sans-serif'},
102
+ 'bgcolor': 'rgba(255,255,255,0.8)',
103
+ 'bordercolor': '#bdc3c7',
104
+ 'borderwidth': 1
105
+ } if show_legend else {'showlegend': False},
106
+ plot_bgcolor='white',
107
+ paper_bgcolor='white',
108
+ margin={'t': 80, 'r': 40, 'b': 60, 'l': 80},
109
+ hovermode='x unified'
110
+ )
111
+
112
+ # Update line properties for better accessibility
113
+ fig.update_traces(
114
+ line={'width': 3},
115
+ marker={'size': 6},
116
+ hovertemplate='<b>%{fullData.name}</b><br>' +
117
+ 'Date: %{x}<br>' +
118
+ 'Value: %{y:,.0f}<br>' +
119
+ '<extra></extra>'
120
+ )
121
+
122
+ return fig
123
+
124
  def get_aa_count_chart(df):
125
  """Get a chart that shows detentions by arresting authority as a count."""
126
  if df.empty:
127
  return go.Figure().add_annotation(text="Error loading data", showarrow=False)
128
 
129
+ # Rename columns for better display
130
+ df_chart = df.copy()
131
+ df_chart = df_chart.rename(columns={
132
+ "ice_all": "ICE Detainees",
133
+ "cbp_all": "CBP Detainees"
134
+ })
135
 
136
+ # Create melted dataframe
137
+ df_melted = df_chart.melt(
138
  id_vars="date",
139
+ value_vars=["ICE Detainees", "CBP Detainees"],
140
  var_name="Arresting Authority",
141
  value_name="count",
142
  )
143
 
144
+ # Create line chart
145
  fig = px.line(
146
  df_melted,
147
  x="date",
148
  y="count",
149
  color="Arresting Authority",
150
  color_discrete_sequence=colorblind_palette,
151
+ markers=True
152
  )
153
 
154
+ # Apply styling
155
+ fig = apply_chart_styling(
156
+ fig,
157
+ title="ICE Detention Population by Arresting Authority",
158
+ x_title="Date",
159
+ y_title="Number of Detainees"
160
+ )
161
+
162
+ # Add data source annotation
163
+ fig.add_annotation(
164
+ text="Source: TRAC Immigration Reports",
165
+ xref="paper", yref="paper",
166
+ x=1, y=-0.1,
167
+ showarrow=False,
168
+ font={'size': 10, 'color': '#7f8c8d'},
169
+ xanchor='right'
170
  )
171
 
172
  return fig
 
176
  if df.empty:
177
  return go.Figure().add_annotation(text="Error loading data", showarrow=False)
178
 
179
+ # Calculate percentages
180
+ df_chart = df.copy()
181
+ df_chart["ICE (%)"] = (df_chart.ice_all / df_chart.total_all * 100).round(1)
182
+ df_chart["CBP (%)"] = (df_chart.cbp_all / df_chart.total_all * 100).round(1)
183
 
184
+ # Create melted dataframe
185
+ df_melted = df_chart.melt(
186
  id_vars="date",
187
+ value_vars=["ICE (%)", "CBP (%)"],
188
  var_name="Arresting Authority",
189
  value_name="percent",
190
  )
191
 
192
+ # Create line chart
193
  fig = px.line(
194
  df_melted,
195
  x="date",
196
  y="percent",
197
  color="Arresting Authority",
198
  color_discrete_sequence=colorblind_palette,
199
+ markers=True
200
  )
201
 
202
+ # Apply styling
203
+ fig = apply_chart_styling(
204
+ fig,
205
+ title="ICE Detention Population by Arresting Authority (Percentage)",
206
+ x_title="Date",
207
+ y_title="Percentage of Total Detainees (%)"
208
+ )
209
+
210
+ # Update y-axis for percentage
211
+ fig.update_yaxis(ticksuffix="%", range=[0, 100])
212
+
213
+ # Update hover template for percentages
214
+ fig.update_traces(
215
+ hovertemplate='<b>%{fullData.name}</b><br>' +
216
+ 'Date: %{x}<br>' +
217
+ 'Percentage: %{y:.1f}%<br>' +
218
+ '<extra></extra>'
219
+ )
220
+
221
+ # Add data source annotation
222
+ fig.add_annotation(
223
+ text="Source: TRAC Immigration Reports",
224
+ xref="paper", yref="paper",
225
+ x=1, y=-0.1,
226
+ showarrow=False,
227
+ font={'size': 10, 'color': '#7f8c8d'},
228
+ xanchor='right'
229
  )
230
 
231
  return fig
 
236
  return go.Figure().add_annotation(text="Error loading data", showarrow=False)
237
 
238
  prefix = get_col_prefix(authority)
239
+
240
+ # Rename columns for better display
241
+ df_chart = df.copy()
242
+ df_chart = df_chart.rename(
243
  columns={
244
+ f"{prefix}_conv": "Convicted of Crime",
 
245
  f"{prefix}_pend": "Pending Criminal Charges",
246
+ f"{prefix}_other": "Immigration Violations Only",
247
  }
248
  )
249
 
250
+ # Create melted dataframe
251
+ df_melted = df_chart.melt(
252
  id_vars="date",
253
  value_vars=[
254
+ "Convicted of Crime",
255
+ "Pending Criminal Charges",
256
+ "Immigration Violations Only"
 
257
  ],
258
  var_name="Criminal Status",
259
  value_name="count",
260
  )
261
 
262
+ # Create line chart
263
  fig = px.line(
264
  df_melted,
265
  x="date",
266
  y="count",
267
  color="Criminal Status",
268
  color_discrete_sequence=colorblind_palette,
269
+ markers=True
270
  )
271
 
272
+ # Apply styling
273
+ authority_text = "All Authorities" if authority == "All" else f"{authority} Detainees"
274
+ fig = apply_chart_styling(
275
+ fig,
276
+ title=f"ICE Detention Population by Criminal Status ({authority_text})",
277
+ x_title="Date",
278
+ y_title="Number of Detainees"
279
+ )
280
+
281
+ # Add data source annotation
282
+ fig.add_annotation(
283
+ text="Source: TRAC Immigration Reports",
284
+ xref="paper", yref="paper",
285
+ x=1, y=-0.1,
286
+ showarrow=False,
287
+ font={'size': 10, 'color': '#7f8c8d'},
288
+ xanchor='right'
289
  )
290
 
291
  return fig
 
301
  pend_col = f"{prefix}_pend"
302
  other_col = f"{prefix}_other"
303
 
304
+ # Calculate percentages
305
+ df_chart = df.copy()
306
+ df_chart["Convicted of Crime (%)"] = (df_chart[conv_col] / df_chart[all_col] * 100).round(1)
307
+ df_chart["Pending Criminal Charges (%)"] = (df_chart[pend_col] / df_chart[all_col] * 100).round(1)
308
+ df_chart["Immigration Violations Only (%)"] = (df_chart[other_col] / df_chart[all_col] * 100).round(1)
309
 
310
+ # Create melted dataframe
311
+ df_melted = df_chart.melt(
312
  id_vars="date",
313
  value_vars=[
314
+ "Convicted of Crime (%)",
315
+ "Pending Criminal Charges (%)",
316
+ "Immigration Violations Only (%)",
317
  ],
318
  var_name="Criminal Status",
319
  value_name="percent",
320
  )
321
 
322
+ # Create line chart
323
  fig = px.line(
324
  df_melted,
325
  x="date",
326
  y="percent",
327
  color="Criminal Status",
328
  color_discrete_sequence=colorblind_palette,
329
+ markers=True
330
  )
331
 
332
+ # Apply styling
333
+ authority_text = "All Authorities" if authority == "All" else f"{authority} Detainees"
334
+ fig = apply_chart_styling(
335
+ fig,
336
+ title=f"ICE Detention Population by Criminal Status - Percentage ({authority_text})",
337
+ x_title="Date",
338
+ y_title="Percentage of Total Detainees (%)"
339
+ )
340
+
341
+ # Update y-axis for percentage
342
+ fig.update_yaxis(ticksuffix="%", range=[0, 100])
343
+
344
+ # Update hover template for percentages
345
+ fig.update_traces(
346
+ hovertemplate='<b>%{fullData.name}</b><br>' +
347
+ 'Date: %{x}<br>' +
348
+ 'Percentage: %{y:.1f}%<br>' +
349
+ '<extra></extra>'
350
+ )
351
+
352
+ # Add data source annotation
353
+ fig.add_annotation(
354
+ text="Source: TRAC Immigration Reports",
355
+ xref="paper", yref="paper",
356
+ x=1, y=-0.1,
357
+ showarrow=False,
358
+ font={'size': 10, 'color': '#7f8c8d'},
359
+ xanchor='right'
360
  )
361
 
362
  return fig
363
 
364
  def get_footnote_text(dataset):
365
  """Get footnote text for the dataset."""
366
+ base_text = """
367
+ ### Definitions and Notes
368
+
369
+ **ICE**: Immigration and Customs Enforcement
370
+ **CBP**: Customs and Border Protection
371
+
372
+ **Data Coverage**: Dates before November 15, 2021 reflect when ICE posted the data; dates after November 15, 2021 refer to when the information was current as of that date.
373
+ """
374
+
375
+ if dataset == "Criminality":
376
+ criminality_note = """
377
+ **Criminal Classifications**: ICE classifies individuals as "convicted criminals" if they have been convicted of any criminal violation, ranging from serious felonies to minor infractions such as traffic violations, fishing without a permit, or immigration-related petty offenses.
378
+ """
379
+ return base_text + criminality_note
380
+
381
+ return base_text
382
 
383
  def update_chart(dataset, display, authority):
384
  """Update the chart based on user selections."""
 
438
  gr.Markdown("*Interactive visualization of ICE detention statistics*")
439
 
440
  with gr.Tabs():
441
+ with gr.TabItem("πŸ“Š Visualizations"):
442
  with gr.Row():
443
+ with gr.Column(scale=1):
444
+ dataset = gr.Dropdown(
445
+ choices=["Arresting Authority", "Criminality"],
446
+ value="Arresting Authority",
447
+ label="πŸ“‹ Dataset",
448
+ info="Choose the type of data to visualize"
449
+ )
450
+ with gr.Column(scale=1):
451
+ display = gr.Dropdown(
452
+ choices=["Count", "Percent"],
453
+ value="Count",
454
+ label="πŸ“ˆ Display Format",
455
+ info="Show absolute numbers or percentages"
456
+ )
457
+ with gr.Column(scale=1):
458
+ authority = gr.Dropdown(
459
+ choices=["All", "ICE", "CBP"],
460
+ value="All",
461
+ label="πŸ›οΈ Arresting Authority",
462
+ info="Filter by authority (Criminality data only)",
463
+ visible=False
464
+ )
465
+
466
+ chart = gr.Plot(
467
+ label="Detention Statistics",
468
+ show_label=False,
469
+ container=True
470
+ )
471
 
 
472
  footnote = gr.Markdown()
473
 
474
  # Update authority dropdown visibility based on dataset
 
496
  outputs=[chart, footnote]
497
  )
498
 
499
+ with gr.TabItem("πŸ“– About"):
500
+ gr.Markdown(get_data_info())
501
 
502
  return app
503