def app(): # year-end summary fig = plot_year_summary() f_year = go.FigureWidget(fig) st.plotly_chart(f_year) ##### Comparison controling for offense types ##### st.header("By actor analysis with controlled offense types") st.write( "While the 'By Actor' page provides a useful year-end summary, it does not provide a fair comparison of the actors. \ In particular, the differences among actors may stem from the fact that some actors may have handled more cases with more severe charges." ) st.write( "We compared the bail type and bail amount set by the actors while controlling for the difference in the charges. \ We selected size actors (Bernard, Rainey, Rigmaiden-DeLeon, Stack, E-Filing Judge, and O'Brien) that handled more than 1000 cases in the year 2020. \ We then conducted a matched study where we sampled cases with the same charges that were handled by the six actors." ) st.write( "The following results were obtained from the 3264 cases (544 per magistrate) that were sampled. Ideally, there shouldn't be any noticeable difference across actors." ) st.write( "Note that due to the sampling nature of the matched study, the matched dataset will vary across samples. However, the general trends observed below were consistent across multiple samples." ) # bail type st.subheader("Percentage of bail type for each magistrate") #st.write("**<font color='red'>Question for PBF</font>**: Would you prefer the pie chart or the bar chart?", unsafe_allow_html=True) image = Image.open('figures/magistrate_matched_type.png') st.image(image, use_column_width=True) #image = Image.open('figures/magistrate_matched_type_bar.png') #st.image(image) st.write( "When we control for the offense types, all actors set monetary bail to 31%-44% of their cases." ) # bail amount st.subheader("Monetary bail amount set by each magistrate") st.write( "In the box plot, the colored bars represent the 25% to 75% range of the bail amount set by the magistrate.", unsafe_allow_html=True) _, col, _ = st.beta_columns([1, 5, 1]) image = Image.open('figures/magistrate_matched_amount.png') col.image(image, use_column_width=True) """ _, col, _ = st.beta_columns([1, 10, 1]) image = Image.open('figures/magistrate_matched_amount_countplot.png') col.image(image, use_column_width = True) """ st.write( "Even when we control for offense types, we see a difference in the monetary bail amount across actors.\ While the median bail amounts are similar, comparing the colored boxes (which indicates the 25% - 75% range of bail amounts) show that Bernard, Rainey, and Rigmaiden-DeLeon tend to set higher bail amounts than the others." )
def app(): st.image('figures/PBF_logo_edit.png', use_column_width=True) # year-end summary fig = plot_year_summary() f_year = go.FigureWidget(fig) st.plotly_chart(f_year) n_cases = load_data() # What is this app st.title('Bail in Philadelphia') st.write( """This dashboard provides a summary of the bail situation in Philadelphia **from January 2020 through March 2021**. A summary of how people in Philadelphia have been impacted by **monetary bail** is displayed at the top of each page. You can also explore how bail breaks down... - ...by year: how have **bail types and bail amounts set** in 2021 compared to those set in 2020? - ...by actor: how has bail depended on the magistrate or other **person setting bail**? - ...by price: how much bail have Philadelphians across the city **paid**? - ...by demographics: how has bail differed between **races, genders, and age groups**? Use the navigation panel on the left to explore.""") st.write( f"Information from the **{n_cases:,d} cases** used to create this dashboard was gathered from [Philadelphia Municipal Court docket sheets.](https://ujsportal.pacourts.us/DocketSheets/MC.aspx#)" ) # What is PBF st.header('The Philadelphia Bail Fund') st.write( "The Philadelphia Bail Fund is a revolving fund that posts bail for people who are indigent and cannot afford bail.\ Our goal is to keep families and communities together and vigorously advocate for the end to cash bail in Philadelphia. " ) st.subheader("Get involved") # learn more pbf_link = 'Learn more at [phillybailfund.org](http://phillybailfund.org)' st.markdown(pbf_link, unsafe_allow_html=True) # donate donation_link = 'Take action via [donation](https://www.phillybailfund.org/donate)' st.markdown(donation_link, unsafe_allow_html=True) # contact us st.markdown( 'Contact us at <a href="mailto:[email protected]">[email protected]</a>', unsafe_allow_html=True) # What is Code for Philly st.header('Code for Philly') st.write( "This dashboard was created by [Code for Philly](https://codeforphilly.org/), a Code For America brigade, in collaboration with the Philadelphia Bail Fund.\ We're part of a national alliance of community organizers, developers, and designers that are putting technology to work in service of our local communities." )
def app(): # ---------------------------------- # Year-end summary (included on every page) # ---------------------------------- fig = plot_year_summary() f_year = go.FigureWidget(fig) st.plotly_chart(f_year) # ---------------------------------- # Description # ---------------------------------- st.title('Breakdown by Year') st.subheader("Bail Types") st.write("""During a defendant's arraignment (a hearing held shortly after they are arrested), one of several [types of bail](https://www.pacodeandbulletin.gov/Display/pacode?file=/secure/pacode/data/234/chapter5/s524.html) may be set: - **monetary**, where a bail amount is set and the defendant is held in jail until a portion (typically 10%) is paid (\"posted\"), - **ROR** (“released on own recognizance”), where a defendant must agree to show up to all future court proceedings, - **unsecured**, where the defendant is liable for a set bail amount if they do not show up to future court proceedings, - a **nonmonetary** bail condition, or - the defendant may be **denied** bail.""") st.write("Monetary bail is the most frequently set bail type.") # ---------------------------------- # Preprocessing # ---------------------------------- bail_types = ['Denied', 'Monetary', 'Nonmonetary', 'ROR', 'Unsecured'] years = [2020, 2021] df_month, df_by_numbers = load_data() df_year = df_month.groupby(['bail_year', 'bail_type'])['count'].sum() # TODO: update so that df is sorted according to bail_types, to match bail type colors/orders accross pages! Currently, bail_types must be set to match the groupby order by hand. arr_year_total = np.array([df_year[val] for val in years]) yearly_sums = [df_year[x].sum() for x in years] arr_year_pct = np.array([100*df_year[val]/yearly_sums[i] for i, val in enumerate(years)]) # ---------------------------------- # Interactive figure: Bail Type Percentages # ---------------------------------- fig = go.Figure() for j, bailType in enumerate(bail_types): bail_pct = arr_year_pct[:, j] fig.add_trace(go.Bar( x=[0,1], y=bail_pct, orientation="v", text="", textposition="inside", name=bailType, hoverinfo="text", hovertext=[f"{bailType}: {bailPct:.1f}% of {year} total" for bailPct, year in zip(bail_pct, years)] )) fig.update_layout( barmode='stack', legend={'traceorder': 'normal'}, legend_title="Bail Types", title="Breakdown of Bail Types Set: Percentages", xaxis_title="Year", yaxis_title="Percent", xaxis_tickvals=[0, 1], xaxis_ticktext=["2020","2021 YTD"] ) f_pct = go.FigureWidget(fig) st.plotly_chart(f_pct) # ---------------------------------- # Interactive figure: Bail Type Totals # ---------------------------------- fig = go.Figure() for j, bailType in enumerate(bail_types): bail_total = arr_year_total[:,j] fig.add_trace(go.Bar( x=[0,1], y=bail_total, orientation="v", text="", textposition="inside", name=bailType, hoverinfo="text", hovertext=[f"{bailType}: {bailCount:,d} people in {year}" for bailCount, year in zip(bail_total, years)] )) fig.update_layout( barmode='stack', legend={'traceorder': 'normal'}, legend_title="Bail Types", title="Breakdown of Bail Types Set: Totals", xaxis_title="Year", yaxis_title="Number of People", xaxis_tickvals=[0, 1], xaxis_ticktext=["2020","2021 YTD"] ) f_total = go.FigureWidget(fig) st.plotly_chart(f_total) # ---------------------------------- # Summary table # ---------------------------------- st.subheader("Monetary Bail") st.write("""In 2020 cases where monetary bail was set, bail was posted in only 44% of cases, meaning that **a majority of people were not able to pay the amount required to be released from jail**.""") st.table(df_by_numbers)
def app(): # ---------------------------------- # Year-end summary (included on every page) # ---------------------------------- fig = plot_year_summary() f_year = go.FigureWidget(fig) st.plotly_chart(f_year) # ---------------------------------- # Description # ---------------------------------- st.title('Breakdown by Demographics') # ---------------------------------- # Preprocessing # ---------------------------------- years = [2020, 2021] bail_types = ['Denied', 'Monetary', 'Nonmonetary', 'ROR', 'Unsecured'] races = ['Black', 'White', 'Other'] sexes = ['Male', 'Female'] age_groups = ['minor', '18 to 25', '26 to 64', '65+'] df_race_bail_set, df_race_bail_type, df_race_bail_type_count = load_race_data( ) df_sex_bail_set, df_sex_bail_type, df_sex_bail_type_count = load_sex_data() df_age_bail_set, df_age_bail_type, df_age_bail_type_count = load_age_data() arr_race_pct_2020 = np.array( [df_race_bail_type[2020][race] for race in races], dtype=object) arr_race_pct_2021 = np.array( [df_race_bail_type[2021][race] for race in races], dtype=object) arr_race_count_2020 = np.array( [df_race_bail_type_count[2020][race] for race in races], dtype=object) arr_race_count_2021 = np.array( [df_race_bail_type_count[2021][race] for race in races], dtype=object) arr_sex_pct_2020 = np.array([df_sex_bail_type[2020][sex] for sex in sexes], dtype=object) arr_sex_pct_2021 = np.array([df_sex_bail_type[2021][sex] for sex in sexes], dtype=object) arr_sex_count_2020 = np.array( [df_sex_bail_type_count[2020][sex] for sex in sexes], dtype=object) arr_sex_count_2021 = np.array( [df_sex_bail_type_count[2021][sex] for sex in sexes], dtype=object) arr_age_pct_2020 = np.array( [df_age_bail_type[2020][age] for age in age_groups], dtype=object) arr_age_pct_2021 = np.array( [df_age_bail_type[2021][age] for age in age_groups], dtype=object) arr_age_count_2020 = np.array( [df_age_bail_type_count[2020][age] for age in age_groups], dtype=object) arr_age_count_2021 = np.array( [df_age_bail_type_count[2021][age] for age in age_groups], dtype=object) # ---------------------------------- # Interactive figure: Bail Type Percentages by Race # ---------------------------------- fig = go.Figure() for j, bailType in enumerate(bail_types): bail_pct = arr_race_pct_2020[:, j] bail_count = arr_race_count_2020[:, j] fig.add_trace( go.Bar( x=list(range(len(races))), y=bail_pct, orientation="v", text="", textposition="inside", name=bailType, hoverinfo="text", hovertext=[ f"{bailType}: {bailPct:.1f}% of 2020 total, {race} ({count:,d} people)" for bailPct, race, count in zip(bail_pct, races, bail_count) ])) for j, bailType in enumerate(bail_types): bail_pct = arr_race_pct_2021[:, j] bail_count = arr_race_count_2020[:, j] fig.add_trace( go.Bar( x=list(range(len(races))), y=bail_pct, orientation="v", text="", textposition="inside", name=bailType, hoverinfo="text", hovertext=[ f"{bailType}: {bailPct:.1f}% of 2021 total, {race} ({count:,d} people)" for bailPct, race, count in zip(bail_pct, races, bail_count) ], visible=False)) fig.update_layout(barmode='stack', legend={'traceorder': 'normal'}, legend_title="Bail Types", title="Breakdown of Bail Types Set, by Race", xaxis_title="Race", yaxis_title="Percent", xaxis_tickvals=list(range(len(races))), xaxis_ticktext=races) fig.update_layout(updatemenus=[ dict(active=0, x=-0.35, xanchor='left', y=0.9, yanchor='top', buttons=list([ dict(label="2020", method="update", args=[{ "visible": [ True, True, True, True, True, False, False, False, False, False ] }, { "title": "Bail type by race in 2020" }]), dict(label="2021", method="update", args=[{ "visible": [ False, False, False, False, False, True, True, True, True, True ] }, { "title": "Bail type by race in 2021" }]) ])) ]) fig.update_layout(annotations=[ dict(text="Select year", x=-0.35, xref="paper", y=0.98, yref="paper", align="left", showarrow=False) ]) f_pct = go.FigureWidget(fig) st.plotly_chart(f_pct) st.write( "In 2020, monetary bail was set more frequently for people identified by the court system as Black than those identified as non-Black (White or Other)." ) st.write( "Note: While additional race categories beyond \"White\" and \"Black\" are recognized by the Pennsylvania court system, these are grouped together as \"Other\" out of anonymization concerns. The Philadelphia Bail Fund has observed that the Philadelphia court system appears to record most non-Black and non-Asian people, such as Latinx and Indigenous people, as White." ) st.write( "**<font color='red'>Question for PBF</font>**: what disclaimer language would you like to include here? The above was informed by the language in the July 2020 report.", unsafe_allow_html=True) # ---------------------------------- # Interactive figure: Bail Type Percentages by Sex # ---------------------------------- fig = go.Figure() for j, bailType in enumerate(bail_types): bail_pct = arr_sex_pct_2020[:, j] bail_count = arr_sex_count_2020[:, j] fig.add_trace( go.Bar( x=list(range(len(sexes))), y=bail_pct, orientation="v", text="", textposition="inside", name=bailType, hoverinfo="text", hovertext=[ f"{bailType}: {bailPct:.1f}% of 2020 total, {sex} ({count:,d} people)" for bailPct, sex, count in zip(bail_pct, sexes, bail_count) ])) for j, bailType in enumerate(bail_types): bail_pct = arr_sex_pct_2021[:, j] bail_count = arr_sex_count_2021[:, j] fig.add_trace( go.Bar( x=list(range(len(sexes))), y=bail_pct, orientation="v", text="", textposition="inside", name=bailType, hoverinfo="text", hovertext=[ f"{bailType}: {bailPct:.1f}% of 2021 total for {sex} ({count:,d} people)" for bailPct, sex, count in zip(bail_pct, sexes, bail_count) ], visible=False)) fig.update_layout(barmode='stack', legend={'traceorder': 'normal'}, legend_title="Bail Types", title="Breakdown of Bail Types Set, by Sex", xaxis_title="Sex", yaxis_title="Percent", xaxis_tickvals=list(range(len(sexes))), xaxis_ticktext=sexes) fig.update_layout(updatemenus=[ dict(active=0, x=-0.35, xanchor='left', y=0.9, yanchor='top', buttons=list([ dict(label="2020", method="update", args=[{ "visible": [ True, True, True, True, True, False, False, False, False, False ] }, { "title": "Bail type by sex in 2020" }]), dict(label="2021", method="update", args=[{ "visible": [ False, False, False, False, False, True, True, True, True, True ] }, { "title": "Bail type by sex in 2021" }]) ])) ]) fig.update_layout(annotations=[ dict(text="Select year", x=-0.35, xref="paper", y=0.98, yref="paper", align="left", showarrow=False) ]) f_pct_sex = go.FigureWidget(fig) st.plotly_chart(f_pct_sex) st.write( "In 2020, monetary bail was set more frequently for people identified by the court system as male than those identified as female." ) # ---------------------------------- # Interactive figure: Bail Type Percentages by Age # ---------------------------------- fig = go.Figure() for j, bailType in enumerate(bail_types): bail_pct = arr_age_pct_2020[:, j] bail_count = arr_age_count_2020[:, j] fig.add_trace( go.Bar( x=list(range(len(age_groups))), y=bail_pct, orientation="v", text="", textposition="inside", name=bailType, hoverinfo="text", hovertext=[ f"{bailType}: {bailPct:.1f}% of 2020 total, {age} ({count:,d} people)" for bailPct, age, count in zip(bail_pct, age_groups, bail_count) ])) for j, bailType in enumerate(bail_types): bail_pct = arr_age_pct_2021[:, j] bail_count = arr_age_count_2021[:, j] fig.add_trace( go.Bar( x=list(range(len(age_groups))), y=bail_pct, orientation="v", text="", textposition="inside", name=bailType, hoverinfo="text", hovertext=[ f"{bailType}: {bailPct:.1f}% of 2021 total for {age} ({count:,d} people)" for bailPct, age, count in zip(bail_pct, age_groups, bail_count) ], visible=False)) fig.update_layout(barmode='stack', legend={'traceorder': 'normal'}, legend_title="Bail Types", title="Breakdown of Bail Types Set, by Age", xaxis_title="Age Group", yaxis_title="Percent", xaxis_tickvals=list(range(len(age_groups))), xaxis_ticktext=age_groups) fig.update_layout(updatemenus=[ dict(active=0, x=-0.35, xanchor='left', y=0.9, yanchor='top', buttons=list([ dict(label="2020", method="update", args=[{ "visible": [ True, True, True, True, True, False, False, False, False, False ] }, { "title": "Bail type by age in 2020" }]), dict(label="2021", method="update", args=[{ "visible": [ False, False, False, False, False, True, True, True, True, True ] }, { "title": "Bail type by age in 2021" }]) ])) ]) fig.update_layout(annotations=[ dict(text="Select year", x=-0.35, xref="paper", y=0.98, yref="paper", align="left", showarrow=False) ]) f_pct_age = go.FigureWidget(fig) st.plotly_chart(f_pct_age) st.write( "In 2020, the frequency of monetary bail being set decreased with increasing age group." )
def app(): # year-end summary fig = plot_year_summary() f_year = go.FigureWidget(fig) st.plotly_chart(f_year) st.title('Breakdown by Race') # ---------------------------------------- # Aggregate race statistics # ---------------------------------------- st.header("Year-end Summary") st.image(Image.open('figures/race_aggregate_frequency.png'), width=500) st.write( "In the following analysis, only defendants who have been labeled as White or Black are considered, due to sample size concerns.\ Note: the Philadelphia Bail Fund has observed that the Philadelphia court system appears to record most non-Black and non-Asian people, such as Latinx and Indigenous people, as White.\ Thus, while the label \"White\" is maintained in the charts, this group is referred to as \"non-Black\" in the text." ) st.write( "**<font color='red'>Question for PBF</font>**: what disclaimer language would you like to include here? The above was informed by the language in the July 2020 report.", unsafe_allow_html=True) st.write( "Given the low frequency of nonmonetary and nominal bail (<1% of all cases), cases where these bail types were set are excluded from consideration in this section, for ease of interpretation." ) st.subheader("Bail type frequency") st.write( "Relative to non-Black defendants, Black defendants had monetary bail set more frequently, ROR bail set less frequently, and were more frequently denied bail." ) st.image(Image.open('figures/race_aggregate_type.png'), width=int(1.5 * imgWidth)) st.subheader("Bail amount frequency") st.write( "When monetary bail was set, non-Black defendants were more frequently assigned a bail amount between $10,000 and $25,000, and Black defendants were more frequently assigned a bail amount between $100,000 and $500,000." ) st.image(Image.open('figures/race_aggregate_set.png'), width=imgWidth) st.subheader("Bail posted frequency") st.write( "Overall, slightly less than half of Black and non-Black defendants posted bail (44% and 44% respsectively), and defendants posted bail at similar rates for each range of bail amounts set, independent of race." ) st.image(Image.open('figures/race_aggregate_bailPosted.png'), width=imgWidth) """ st.image(Image.open('figures/bail_paid_race.png'), width=imgWidth) st.image(Image.open('figures/bail_paid_race_1k to 5k.png'), width=imgWidth) st.image(Image.open('figures/bail_paid_race_5k to 10k.png'), width=imgWidth) st.image(Image.open('figures/bail_paid_race_10k to 25k.png'), width=imgWidth) st.image(Image.open('figures/bail_paid_race_25k to 50k.png'), width=imgWidth) st.image(Image.open('figures/bail_paid_race_50k to 100k.png'), width=imgWidth) st.image(Image.open('figures/bail_paid_race_100k to 500k.png'), width=imgWidth) st.image(Image.open('figures/bail_paid_race_>=500k.png'), width=imgWidth) st.write("Overall, 52.5% of White defendants paid bail while the percentage of Black defendants who were able to pay bail was 50.6%.") st.write("However, the assigned bail amount had an impact. For bail amounts under $50K, a higher percentage of Black defendants were able to pay bail compared to White defandents. This trend reversed when the bail amount was over $50K.") """ # ---------------------------------------- # Matched analysis # ---------------------------------------- st.header("Controlling for offense types") st.write( """While the above figures provide a useful year-end summary, they include variation in bail types and amounts that may be attributed to factors other than race, such as offense types. To control for offense types, we conducted a matched study where we sampled cases with identical lists of charges from cases with Black and non-Black defendants. The following results were obtained from the 11026 cases (5328 for Black defendants, 5328 for White defendants) that were sampled. In this matched sample, bail types and amounts were set at similar rates for Black and non-Black defendants, indicating that variations in these metrics between cases with the same charged offenses may be largely attributed to factors other than the defendant's race (such as the magistrate assigned to the case). """ ) st.image(Image.open('figures/race_matched_type.png'), width=int(1.5 * imgWidth)) st.image(Image.open('figures/race_matched_set.png'), width=imgWidth)
def app(): # year-end summary fig = plot_year_summary() f_year = go.FigureWidget(fig) st.plotly_chart(f_year) st.title('Breakdown by Price') # prepare data df_month = load_data() month_data = df_month['bail_paid'] df_year = df_month.groupby(['bail_year'])['bail_paid'].sum() bail_paid_2020 = df_year[2020] bail_paid_2021 = df_year[2021] months = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ] # plot figure fig = go.Figure() ### Add traces for yearly summary # 2020 fig.add_trace( go.Bar(x=[0], y=[bail_paid_2020], orientation="v", text=["$" + f'{bail_paid_2020:,.0f}'], textposition="inside", hoverinfo="text", hovertext=["2020 <br>$" + f'{bail_paid_2020:,.0f}'], showlegend=False)) # 2021 fig.add_trace( go.Bar(x=[1], y=[bail_paid_2021], orientation="v", text=["$" + f'{bail_paid_2021:,.0f}'], textposition="inside", hoverinfo="text", hovertext=["2021 <br>$" + f'{bail_paid_2021:,.0f}'], showlegend=False)) ### add traces for monthly summary # 2020 data hovertext_2020 = [ m + " 2020 <br>" + "$" + f'{v:,.0f}' for (m, v) in zip(months, month_data[:12]) ] fig.add_trace( go.Bar(y=month_data[:12], name="2020", hoverinfo="text", hovertext=hovertext_2020, visible=False)) # 2021 data hovertext_2021 = [ m + " 2021 <br>" + "$" + f'{v:,.0f}' for (m, v) in zip(months, month_data[12:]) ] fig.add_trace( go.Bar(y=month_data[12:], name="2021", hoverinfo="text", hovertext=hovertext_2021, visible=False)) fig.update_layout(title="Bail paid by year", xaxis_title="year", yaxis_title="bail amount paid ($)", xaxis_tickvals=[0, 1], xaxis_ticktext=["2020", "2021 YTD"]) # update fig.update_layout(annotations=[ dict(text="Summary", x=-0.28, xref="paper", y=1.1, yref="paper", align="left", showarrow=False) ]) fig.update_layout(updatemenus=[ dict( active=0, x=-0.3, xanchor='left', y=1, yanchor='top', buttons=list([ dict(label="by year", method="update", args=[{ "visible": [True, True, False, False] }, { "title": "Bail paid by year", 'xaxis': { 'title': 'year', 'tickvals': [0, 1], 'ticktext': ["2020", "2021 YTD"] } }]), dict(label="by month", method="update", args=[{ "visible": [False, False, True, True] }, { "title": "Bail paid by month", 'xaxis': { 'title': 'month', 'tickvals': list(range(12)), 'ticktext': months } }]) ]), ) ]) f2 = go.FigureWidget(fig) st.plotly_chart(f2)
def app(): # ---------------------------------- # Year-end summary (included on every page) # ---------------------------------- fig = plot_year_summary() f_year = go.FigureWidget(fig) st.plotly_chart(f_year) # ---------------------------------- # Introduction # ---------------------------------- st.title('Total price paid by Philadelphians') st.write( """When monetary bail is set, a defendent is detained in jail while awaiting trial unless they pay a portion (typically 10%) of the bail amount set. Depending on how much bail was set, the amount that a defendant must pay can be thousands of dollars. This can be financially difficult for many Philadelphia families. """) # ---------------------------------- # Interactive figure: Total price paid # ---------------------------------- st.subheader('How much have Philadelphians paid in bail?') st.write( "In 2020, Philadelphians paid **tens of millions of dollars** in bail to be released from jail while they await their trials." ) minCount = 10 #100 # prepare data df_month = load_data_paid() month_data = df_month['bail_paid'] df_year = df_month.groupby(['bail_year'])['bail_paid'].sum() bail_paid_2020 = df_year[2020] bail_paid_2021 = df_year[2021] months = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ] # Plot figure fig = go.Figure() ### Add traces for yearly summary # 2020 fig.add_trace( go.Bar(x=[0], y=[bail_paid_2020], orientation="v", text=["$" + f'{bail_paid_2020:,.0f}'], textposition="inside", hoverinfo="text", hovertext=["2020 <br>$" + f'{bail_paid_2020:,.0f}'], showlegend=False)) # 2021 fig.add_trace( go.Bar(x=[1], y=[bail_paid_2021], orientation="v", text=["$" + f'{bail_paid_2021:,.0f}'], textposition="inside", hoverinfo="text", hovertext=["2021 <br>$" + f'{bail_paid_2021:,.0f}'], showlegend=False)) ### add traces for monthly summary # 2020 data hovertext_2020 = [ m + " 2020 <br>" + "$" + f'{v:,.0f}' for (m, v) in zip(months, month_data[:12]) ] fig.add_trace( go.Bar(y=month_data[:12], name="2020", hoverinfo="text", hovertext=hovertext_2020, visible=False)) # 2021 data hovertext_2021 = [ m + " 2021 <br>" + "$" + f'{v:,.0f}' for (m, v) in zip(months, month_data[12:]) ] fig.add_trace( go.Bar(y=month_data[12:], name="2021", hoverinfo="text", hovertext=hovertext_2021, visible=False)) fig.update_layout(margin={'t': 25}, xaxis_title="Year", yaxis_title="Bail amount paid ($)", xaxis_tickvals=[0, 1], xaxis_ticktext=["2020", "2021 YTD"]) fig.update_layout(annotations=[ dict(text="Select data to view:", x=-0.5, y=1.00, xref="paper", yref="paper", align="left", showarrow=False) ]) fig.update_layout(updatemenus=[ dict( active=0, x=-0.5, xanchor='left', y=0.92, yanchor='top', buttons=list([ dict(label="by year", method="update", args=[{ "visible": [True, True, False, False] }, { 'xaxis': { 'title': 'year', 'tickvals': [0, 1], 'ticktext': ["2020", "2021 YTD"] } }]), dict(label="by month", method="update", args=[{ "visible": [False, False, True, True] }, { 'xaxis': { 'title': 'month', 'tickvals': list(range(12)), 'ticktext': months } }]) ]), ) ]) f2 = go.FigureWidget(fig) st.plotly_chart(f2) # ---------------------------------- # Interactive figure: Neighborhood maps # ---------------------------------- st.subheader('Breakdown by Neighborhood') st.write( 'This section provides an interactive breakdown of case counts, total amounts of bail set and paid, and bail payment rate by Philadelphia zip code, in tandem with income, poverty, and unemployment data from the American Community Survey (ACS) collected by the U.S. Census Bureau.' ) st.write( 'The plot on the left shows bail-related summary for each zipcode, while the plot on the right shows income, poverty level, and unemployment rate for each zipcode. One can see that the neighborhoods that are strongly affected by the bail system often correspond to neighborhoods with low income, high unemployment, and high poverty.' ) st.markdown(f""" Source Data: [Median Income]({INCOME}), [Poverty Level]({POVERTY}), [Unemployment Rate]({UNEMPLOYMENT}) """) st.header('Interactive Maps') st.write( f'Use the dropdown menus to select a given bail and census metric associated with each map.\ Hover over an area to view the corresponding metric value and zip code number.\ For bail metrics other than case counts, only zip codes with at least {minCount} cases are shown.' ) st.write( 'Zip codes with the highest case counts, highest total bail amounts set and posted, and lowest bail posting rates tend to have lower median incomes and higher poverty and unemployment rates.' ) col1, col2 = st.beta_columns(2) # ------------------------------------ # Process bail data # ------------------------------------ #df = preprocess() df = load_data() # Create data assoc. w/ each metric (over all bail types) and put in dict case_counts = pd.DataFrame( df['zip'].value_counts().reset_index().rename(columns={ 'index': 'zip', 'zip': 'count' })) bail_amounts = df.groupby('zip').sum()[['bail_amount']].reset_index() bail_paid = df.groupby('zip').sum()[['bail_paid']].reset_index() df_monetary = df[df['bail_type'] == 'Monetary'][['zip', 'bail_paid']] bail_paid_pct = ( df_monetary[df_monetary['bail_paid'] > 0]['zip'].value_counts().divide( df_monetary['zip'].value_counts()).mul(100).round( 1).reset_index().rename(columns={ 'index': 'zip', 'zip': 'pct' })) #public_defender = (df[df['attorney_type'] == 'Public']['zipcode_clean'].value_counts() # .divide(df['zipcode_clean'].value_counts()) # .mul(100).round(1) # .reset_index().rename(columns={'index': 'zipcode', 'zip': 'pct'})) # Select only zip codes with at least minCount cases to show in bail metrics map minZips = case_counts[case_counts['count'] >= minCount]['zip'].to_list() #case_counts = case_counts[case_counts['zip'].isin(minZips)] bail_amounts = bail_amounts[bail_amounts['zip'].isin(minZips)] bail_paid = bail_paid[bail_paid['zip'].isin(minZips)] bail_paid_pct = bail_paid_pct[bail_paid_pct['zip'].isin(minZips)] #public_defender = public_defender[public_defender['zip'].isin(minZips)] cases_dfs = { 'Case Count': case_counts, 'Total Bail Set ($)': bail_amounts, 'Total Bail Posted ($)': bail_paid, 'Bail Posting Rate': bail_paid_pct } # Geo data # Approximate Philly lat/long philly = (40.0, -75.13) # Open geojson of philly zip code borders zips_geo = 'data/external/zipcodes_poly.geojson' with open(zips_geo) as f: zips_data = json.load(f) # ------------------------------------ # Interactive map for bail metrics # ------------------------------------ # Make dropdown for bail metric metric = col1.selectbox('Bail Metric', (tuple(cases_dfs.keys()))) # Get data for the selected metric data = cases_dfs[metric] z = data[data.columns[1]] locations = data[data.columns[0]] # Set up figure object (choropleth map) with our geo data map_fig = go.FigureWidget( go.Choroplethmapbox( geojson=zips_data, # geojson data z=z, # what colors will rep. in map from our data locations=locations, # zip codes in our data featureidkey="properties.CODE", # key index in geojson for zip colorscale='YlOrRd')) map_fig.update_layout(mapbox_style="carto-positron", mapbox_zoom=8.8, mapbox_center={ "lat": philly[0], "lon": philly[1] }) map_fig.update_layout(margin={ "r": 0, "t": 0, "l": 0, "b": 0 }, height=450, width=350) col1.plotly_chart(map_fig) # Interactive map for ACS metrics # Get ACS data acs_df = preprocess_acs() # Dropdown for ACS metrics acs_metric = col2.selectbox('Census Metric', ('Households Median Income', 'Percent Below Poverty', 'Unemployment Rate')) # Set up figure object (choropleth map) with geo data, make sure z gets the selected metric acs_map_fig = go.FigureWidget( go.Choroplethmapbox( geojson=zips_data, # geojson data z=acs_df['_'.join([ w.lower() for w in acs_metric.split(' ') ])], # what colors will rep. in map from our data locations=acs_df['zipcode'], # zip codes in our data featureidkey="properties.CODE", # key index in geojson for zip colorscale='YlOrRd')) acs_map_fig.update_layout(mapbox_style="carto-positron", mapbox_zoom=8.8, mapbox_center={ "lat": philly[0], "lon": philly[1] }) acs_map_fig.update_layout(margin={ "r": 0, "t": 0, "l": 0, "b": 0 }, height=450, width=350) col2.plotly_chart(acs_map_fig) col11, col21 = st.beta_columns(2) zip_input = col11.text_input('Enter your zip code:') if zip_input: try: st.header('Bail Summary for ' + zip_input) temp_df = pd.DataFrame({ 'Metric': [ '<b>Case Count</b>', '<b>Total Bail Set</b>', '<b>Total Bail Posted</b>', '<b>Bail Posting Rate</b>', '<b>Household Median Income</b>', '<b>Percent Below Poverty</b>', '<b>Unemployment Rate</b>' ], 'Value': [ case_counts[case_counts['zip'] == int( zip_input)]['count'].values[0], f"${bail_amounts[bail_amounts['zip']==int(zip_input)]['bail_amount'].values[0]:,.0f}", f"${bail_paid[bail_paid['zip']==int(zip_input)]['bail_paid'].values[0]:,.0f}", f"{bail_paid_pct[bail_paid_pct['zip']==int(zip_input)]['pct'].values[0]:.0f}%", f"${acs_df[acs_df['zipcode']==int(zip_input)]['households_median_income'].values[0]:,.0f}", f"{acs_df[acs_df['zipcode']==int(zip_input)]['percent_below_poverty'].values[0]:.0f}%", f"{acs_df[acs_df['zipcode']==int(zip_input)]['unemployment_rate'].values[0]:.0f}%" ] }) tbl_fig = go.Figure(data=[ go.Table(columnwidth=[400, 200], header=dict(values=['', ''], fill_color='white', align='left'), cells=dict( values=[temp_df['Metric'], temp_df['Value']], fill_color='lavender', font=dict(color='black', family='Arial', size=24), align=['left', 'right'])) ]) tbl_fig.update_layout(margin={ "r": 0, "t": 0, "l": 0, "b": 0 }, width=600) st.plotly_chart(tbl_fig) except IndexError: st.write("No information available for this zip code")
def app(): # ---------------------------------- # Year-end summary (included on every page) # ---------------------------------- fig = plot_year_summary() f_year = go.FigureWidget(fig) st.plotly_chart(f_year) # ---------------------------------- # Description # ---------------------------------- st.title('Breakdown by Year') st.subheader( 'How have bail types set in 2021 compared to those set in 2020?') st.write( """During a defendant's arraignment (a hearing held shortly after they are arrested), one of several [types of bail](https://www.pacodeandbulletin.gov/Display/pacode?file=/secure/pacode/data/234/chapter5/s524.html) may be set: - **monetary**, where a bail amount is set and the defendant is held in jail until a portion (typically 10%) is paid (\"posted\"), - **ROR** (“released on own recognizance”), where the defendant must agree to show up to all future court proceedings, - **unsecured**, where the defendant is liable for a set bail amount if they do not show up to future court proceedings, - a **nonmonetary** bail condition, or - the defendant may be **denied** bail.""") st.write( "In 2020 and so far in 2021, **monetary bail has been the most frequently set bail type**." ) # ---------------------------------- # Preprocessing # ---------------------------------- bail_types = ['Denied', 'Monetary', 'Nonmonetary', 'ROR', 'Unsecured'] years = [2020, 2021] df_month, df_by_numbers = load_data() df_year = df_month.groupby(['bail_year', 'bail_type'])['count'].sum() df_monetary = df_month[df_month['bail_type'] == 'Monetary'] # TODO: update so that df is sorted according to bail_types, to match bail type colors/orders accross pages! Currently, bail_types must be set to match the groupby order by hand. arr_year_total = np.array([df_year[val] for val in years]) yearly_sums = [df_year[x].sum() for x in years] arr_year_pct = np.array( [100 * df_year[val] / yearly_sums[i] for i, val in enumerate(years)]) gb_month = df_month.groupby(['bail_year', 'bail_month'])['count'].sum() df_monetary['pct'] = df_monetary.apply(lambda row: 100 * row[ 'count'] / gb_month[row['bail_year']][row['bail_month']], axis=1) # ---------------------------------- # Interactive figure: bail type percentages/counts, yearly comparison # ---------------------------------- fig = go.Figure() # Plot percentages for j, bailType in enumerate(bail_types): bail_pct = arr_year_pct[:, j] fig.add_trace( go.Bar(x=[0, 1], y=bail_pct, orientation="v", text="", textposition="inside", name=bailType, hoverinfo="text", hovertext=[ f"{bailType}: {bailPct:.1f}% of {year} total" for bailPct, year in zip(bail_pct, years) ], visible=True)) # Plot counts for j, bailType in enumerate(bail_types): bail_total = arr_year_total[:, j] fig.add_trace( go.Bar(x=[0, 1], y=bail_total, orientation="v", text="", textposition="inside", name=bailType, hoverinfo="text", hovertext=[ f"{bailType}: {bailCount:,d} people in {year}" for bailCount, year in zip(bail_total, years) ], visible=False)) # Formatting fig.update_layout(margin={'t': 25}, barmode='stack', legend={'traceorder': 'normal'}, legend_title="Bail Types", yaxis_title="Percentage of cases", xaxis_title="Year", xaxis_tickvals=[0, 1], xaxis_ticktext=["2020, total", "2021, YTD"]) fig.update_layout(annotations=[ dict(text="Select data to view:", x=-0.6, y=1.00, xref="paper", yref="paper", align="left", showarrow=False) ]) fig.update_layout(updatemenus=[ dict( active=0, x=-0.6, y=0.92, xanchor='left', yanchor='top', buttons=list([ dict(label="Percentage of cases", method="update", args=[{ "visible": [ True, True, True, True, True, False, False, False, False, False ] }, { 'yaxis': { 'title': 'Percentage of cases' } }]), dict(label="Number of people", method="update", args=[{ "visible": [ False, False, False, False, False, True, True, True, True, True ] }, { 'yaxis': { 'title': 'Number of people' } }]) ]), ) ]) f_pct = go.FigureWidget(fig) st.plotly_chart(f_pct) # ---------------------------------- # Interactive figure: monetary bail percentages/counts, monthly comparison # ---------------------------------- st.subheader('Monetary bail cases over time') st.write( """So far in 2021, the percentage of cases each month for which monetary bail is set is slightly higher than for the same month in 2020.""" ) months = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ] fig = go.Figure() # As percentage of total cases for i, year in enumerate(years): month_data = df_monetary[df_monetary['bail_year'] == year]['pct'] fig.add_trace( go.Bar(y=month_data, name=year, hoverinfo="text", hovertext=[ f"{m} {year}: {bailPct:.1f}% of cases" for m, bailPct in zip(months, month_data) ], visible=True)) # As total counts for i, year in enumerate(years): month_data = df_monetary[df_monetary['bail_year'] == year]['count'] fig.add_trace( go.Bar(y=month_data, name=year, hoverinfo="text", hovertext=[ f"{m} {year}: {bailCount:,d} people" for m, bailCount in zip(months, month_data) ], visible=False)) # Layout fig.update_layout( margin={'t': 25}, #title="Monetary bail cases per month", legend_title="Year", yaxis_title="Percentage of cases", xaxis_title="Month", xaxis_tickvals=list(range(12)), xaxis_ticktext=months) fig.update_layout(annotations=[ dict(text="Select data to view:", x=-0.6, y=1.00, xref="paper", yref="paper", align="left", showarrow=False) ]) fig.update_layout(updatemenus=[ dict( active=0, x=-0.6, y=0.92, xanchor='left', yanchor='top', buttons=list([ dict(label="Percentage of cases", method="update", args=[{ "visible": [True, True, False, False] }, { 'yaxis': { 'title': 'Percentage of cases' } }]), dict(label="Number of people", method="update", args=[{ "visible": [False, False, True, True] }, { 'yaxis': { 'title': 'Number of people' } }]) ]), ) ]) f_total = go.FigureWidget(fig) st.plotly_chart(f_total) # ---------------------------------- # Summary table # ---------------------------------- st.write( """In 2020 and 2021, the median payment of $0 means that over half of people did not post bail: **a majority of people have not been able to pay the amount required to be released from jail**.""" ) #bail was posted in only 44% of monetary bail cases st.table(df_by_numbers)
def app(): # year-end summary fig = plot_year_summary() f_year = go.FigureWidget(fig) st.plotly_chart(f_year) st.title('Breakdown by Actor') st.write( 'This section provides a summary of how bail type and bail amount depend on the person setting the bail (hereby referred to as the actor).' ) # ---------------------------------- # interactive summary plots # ---------------------------------- st.subheader("Comparison of bail type") # load data for interactive plot df_magistrate_2020, df_magistrate_2021 = load_data() # prepare data bail_type = ["Monetary", "ROR", "Unsecured", "Nonmonetary", "Denied"] bail_type_count = [ "Monetary_count", "ROR_count", "Unsecured_count", "Nonmonetary_count", "Denied_count" ] data_2020 = np.array(df_magistrate_2020[bail_type]).transpose() count_2020 = np.array(df_magistrate_2020[bail_type_count]).transpose() names_2020 = list(df_magistrate_2020['magistrate'].values) total_2020 = df_magistrate_2020['Total'].astype(int) bail_set_2020 = df_magistrate_2020['bail_amount'] data_2021 = np.array(df_magistrate_2021[bail_type]).transpose() count_2021 = np.array(df_magistrate_2021[bail_type_count]).transpose() names_2021 = list(df_magistrate_2021['magistrate'].values) total_2021 = df_magistrate_2021['Total'].astype(int) bail_set_2021 = df_magistrate_2021['bail_amount'] # Initialize figure fig = go.Figure() ##### add traces for 2020 for i in range(5): # text text = [str(item) + "%" if item > 6 else "" for item in data_2020[i]] # hover text # include monetary bail if i == 0: hovertext = [ "name: " + name + "<br>" + "percentage: " + str(perct) + "%" + "<br>" + "case count: " + str(case) + " / " + str(total) + "<br>" + "total monetary bail amount set: " + str(amount) for name, perct, case, total, amount in zip( names_2020, data_2020[i], count_2020[i], total_2020, bail_set_2020) ] else: hovertext = [ "name: " + name + "<br>" + "percentage: " + str(perct) + "%" + "<br>" + "case count: " + str(case) + " / " + str(total) for name, perct, case, total in zip(names_2020, data_2020[i], count_2020[i], total_2020) ] fig.add_trace( go.Bar(y=names_2020, x=data_2020[i], text=text, textposition="inside", name=bail_type[i], hoverinfo='text', hovertext=hovertext, orientation='h')) ##### add traces for 2021 for i in range(5): # text text = [str(item) + "%" if item > 6 else "" for item in data_2021[i]] # hover text # include monetary bail if i == 0: hovertext = [ "name: " + name + "<br>" + "percentage: " + str(perct) + "%" + "<br>" + "case count: " + str(case) + " / " + str(total) + "<br>" + "total monetary bail amount set: " + str(amount) for name, perct, case, total, amount in zip( names_2021, data_2021[i], count_2021[i], total_2021, bail_set_2021) ] else: hovertext = [ "name: " + name + "<br>" + "percentage: " + str(perct) + "%" + "<br>" + "case count: " + str(case) + " / " + str(total) for name, perct, case, total in zip(names_2021, data_2021[i], count_2021[i], total_2021) ] fig.add_trace( go.Bar( y=names_2021, x=data_2021[i], text=text, textposition="inside", name=bail_type[i], hoverinfo='text', hovertext=hovertext, orientation='h', visible=False # hide in initial plot )) fig.update_layout(barmode='stack', legend={'traceorder': 'normal'}, xaxis_title="percentage", yaxis_title="magistrate", legend_title="bail type") # update fig.update_layout(updatemenus=[ dict( active=0, x=-0.35, xanchor='left', y=0.9, yanchor='top', buttons=list([ dict(label="2020", method="update", args=[{ "visible": [ True, True, True, True, True, False, False, False, False, False ] }, { "title": "Bail type by actor in 2020" }]), dict(label="2021", method="update", args=[{ "visible": [ False, False, False, False, False, True, True, True, True, True ] }, { "title": "Bail type by actor in 2021" }]) ]), ) ]) fig.update_layout(annotations=[ dict(text="Select year", x=-0.35, xref="paper", y=0.98, yref="paper", align="left", showarrow=False) ]) # Update plot sizing fig.update_layout(margin=dict(t=100, b=0, l=0, r=0), title="Bail type by actor in 2020") f2 = go.FigureWidget(fig) st.plotly_chart(f2) st.write( "The above figure summarizes the percentage of bail types set by each actors. \ For the year 2020, we selected 10 actors that handled the highest number of cases. \ For the year 2021, we summarize the data upto March 31, 2021. \ Hover your mouse over the figure for further details.") st.write( "**<font color='red'>Note to PBF</font>**: For every case in which bail was denied, there was no magistrate information found. Is this correct? ", unsafe_allow_html=True) ##### 2020 Summary plots ##### # Explain selection of magistrates (Those who handled more than 500 cases) # Some timeline (Many were involved consistently throughout the year. Some were seasonal) # Explain 'Others': Those who set fewer than 500 cases. Total number of those labeled as "Others" # number of cases """ st.header("1. Year-End Summary by Actor") st.subheader("Number of cases handled by each magistrate") _, col, _ = st.beta_columns([1,2,1]) image = Image.open('figures/magistrate_case_count.png') col.image(image) st.write("In the year 2020, bail was set by 37 different people. We provide a summary of the bail type and bail amount for cases handled by the nine magistrates who handled more than 300 cases in the year 2020.") # bail type st.subheader("Percentage of bail type for each magistrate") """ """ st.write("**<font color='red'>Question for PBF</font>**: Would you prefer the following pie chart or the stacked bar chart?", unsafe_allow_html=True) image = Image.open('figures/magistrate_type_summary.png') st.image(image, use_column_width=True) image = Image.open('figures/magistrate_type_summary_bar.png') st.image(image) """ # bail amount st.subheader("Comparison of bail amount") df_monetary = pd.read_csv("data/cleaned/app_magistrate_amount.csv", index_col=0) df_monetary_2020 = df_monetary[df_monetary["year"] == 2020] df_monetary_2021 = df_monetary[df_monetary["year"] == 2021] names_2020 = df_monetary_2020.groupby( "magistrate")["bail_amount"].median().sort_values() names_2021 = df_monetary_2021.groupby( "magistrate")["bail_amount"].median().sort_values() fig = go.Figure() # add traces for 2020 for name in names_2020.index: data = df_monetary_2020[df_monetary_2020["magistrate"] == name].bail_amount fig.add_trace(go.Box(x=data, name=name.split(',')[0], hoverinfo='skip')) # add traces for 2021 for name in names_2021.index: data = df_monetary_2021[df_monetary_2021["magistrate"] == name].bail_amount fig.add_trace( go.Box(x=data, name=name.split(',')[0], hoverinfo='skip', visible=False)) # update fig.update_layout(updatemenus=[ dict( active=0, x=-0.5, xanchor='left', y=0.9, yanchor='top', buttons=list([ dict(label="2020", method="update", args=[{ "visible": [True] * len(names_2020) + [False] * len(names_2021) }, { "title": "Bail amount by actor in 2020" }]), dict(label="2021", method="update", args=[{ "visible": [False] * len(names_2020) + [True] * len(names_2021) }, { "title": "Bail amount by actor in 2021" }]) ]), ) ]) fig.update_layout(xaxis_range=[0, 300000], showlegend=False, title="Bail amount by actor in 2020", xaxis_title="amount", yaxis_title="actor", annotations=[ dict(text="Select year", x=-0.5, xref="paper", y=0.98, yref="paper", align="left", showarrow=False) ]) f3 = go.FigureWidget(fig) st.plotly_chart(f3) fig.update_layout(xaxis_range=[0, 300000], showlegend=False, title="Bail amount by actor in 2020", xaxis_title="amount", yaxis_title="actor") #image = Image.open('figures/magistrate_amount_summary.png') #_, col, _ = st.beta_columns([1, 5, 1]) #col.image(image, use_column_width = True) st.write( "The above box plot compares the monetary bail amount set by different actors for the year 2020. \ For each actor, the vertical line in the colored box represents the median bail amount set by that actor. \ The colored boxes represent the 25% to 75% range of the bail amount set by the magistrate. The dots represent outliers." ) st.write( "On average (median), the bail amount set by Connor, Bernard, and Rigmaiden-DeLeon ($50k) is higher than the bail amount set by others ($25k). \ Moreover, Connor and Bernard seem to have set a wider range of bail amounts than other actors. " ) ##### Comparison controling for offense types ##### st.header("Analysis with controlled offense types") st.write( "While the above analysis provides a useful year-end summary, it does not provide a fair comparison of the actors. \ In particular, the differences among actors may stem from the fact that some actors may have handled more cases with more severe charges." ) st.write( "We compared the bail type and bail amount set by the actors while controlling for the difference in the charges. \ We selected size actors (Bernard, Rainey, Rigmaiden-DeLeon, Stack, E-Filing Judge, and O'Brien) that handled more than 1000 cases in the year 2020. \ We then conducted a matched study where we sampled cases with the same charges that were handled by the six actors." ) st.write( "The following results were obtained from the 3264 cases (544 per magistrate) that were sampled. Ideally, there shouldn't be any noticeable difference across actors." ) st.write( "Note that due to the sampling nature of the matched study, the matched dataset will vary across samples. However, the general trends observed below were consistent across multiple samples." ) # bail type st.subheader("Percentage of bail type for each magistrate") #st.write("**<font color='red'>Question for PBF</font>**: Would you prefer the pie chart or the bar chart?", unsafe_allow_html=True) image = Image.open('figures/magistrate_matched_type.png') st.image(image, use_column_width=True) #image = Image.open('figures/magistrate_matched_type_bar.png') #st.image(image) st.write( "When we control for the offense types, all actors set monetary bail to 31%-44% of their cases." ) # bail amount st.subheader("Monetary bail amount set by each magistrate") st.write( "In the box plot, the colored bars represent the 25% to 75% range of the bail amount set by the magistrate.", unsafe_allow_html=True) _, col, _ = st.beta_columns([1, 5, 1]) image = Image.open('figures/magistrate_matched_amount.png') col.image(image, use_column_width=True) """ _, col, _ = st.beta_columns([1, 10, 1]) image = Image.open('figures/magistrate_matched_amount_countplot.png') col.image(image, use_column_width = True) """ st.write( "Even when we control for offense types, we see a difference in the monetary bail amount across actors.\ While the median bail amounts are similar, comparing the colored boxes (which indicates the 25% - 75% range of bail amounts) show that Bernard, Rainey, and Rigmaiden-DeLeon tend to set higher bail amounts than the others." ) st.write( "**<font color='red'>Note to PBF</font>**: The dashboard mockup contained two extrafigures: `DAO bail request breakdown` and `DAO vs magistrates`. However, we currently don't have the data.", unsafe_allow_html=True)
def app(): # year-end summary fig = plot_year_summary() f_year = go.FigureWidget(fig) st.plotly_chart(f_year) st.title('Year-end Summary') st.write('This section provides a general year-end summary of bail in Philadelphia in 2020, including trends and aggregate-level information for case counts, bail types, and monetary bail set and posted.') # ---------------------------------------------------- # Summary numbers # ---------------------------------------------------- # Get bail data #df = preprocess() st.subheader('Yearly totals') st.write("Use the slider to change the range of dates over which the sum is calculated.") df = load_data() # Get range of dates and create slider to select date range (workaround since Streamlit doesn't have a date range slider) # Try.. except block is another workzround. Streamlit caching doesn't work with datetime module try: df['bail_date'] = df['bail_date'].map(datetime.datetime.date) except: pass all_dates = sorted(df['bail_date'].unique()) start_date = df['bail_date'].min() end_date = df['bail_date'].max() # Slider date_range = st.slider('Date Range', 1, (end_date-start_date).days + 1, (1,(end_date-start_date).days + 1), 1) st.write(all_dates[date_range[0]-1].strftime('%b %d, %Y'), '-', all_dates[date_range[1]-1].strftime('%b %d, %Y')) # Get data based on selected date range df_selected = df[(df['bail_date'] >= all_dates[date_range[0]-1])&(df['bail_date'] <= all_dates[date_range[1]-1])] df_bail = df_selected['bail_type'].value_counts() df_monetary = df_selected[df_selected['bail_type'] == "Monetary"] series_monetary = df_monetary['bail_set_bin'].value_counts() df_defender = df_selected['attorney_type'].value_counts() # Card for Case Count cases = go.Indicator( mode = 'number', value = len(df_selected), domain = {'row': 0, 'column': 0 }, title = {'text': 'Total Cases'}) # Card for Monetary Bail Frequency frequency = go.Indicator( mode = 'number', value = len(df_selected[df_selected['bail_type'] == 'Monetary']) / len(df_selected[df_selected['bail_type'].notnull()]) * 100., number = {'suffix': '%'}, domain = {'row': 0, 'column': 1 }, title = {'text': 'Monetary Bail Frequency'}) # Card for Total Bail Amt amount = go.Indicator( mode = 'number', value = df_selected[df_selected['bail_type'] == 'Monetary']['bail_amount'].sum(), number = {'prefix': '$'}, domain = {'row': 1, 'column': 0 }, title = {'text': 'Total Bail Set'}) # Card for Total Bail Paid paid = go.Indicator( mode = 'number', value = df_selected[df_selected['bail_type'] == 'Monetary']['bail_paid'].sum(), number = {'prefix': '$'}, domain = {'row': 1, 'column': 1 }, title = {'text': 'Total Bail Paid'}) # Set up figure as 2x2 grid of the cards in the order specified card_fig = go.FigureWidget() card_fig.add_trace(cases) card_fig.add_trace(frequency) card_fig.add_trace(amount) card_fig.add_trace(paid) card_fig.update_layout( grid = {'rows': 2, 'columns': 2, 'pattern': "independent"}) st.plotly_chart(card_fig) # ---------------------------------------------------- # Summary charts # ---------------------------------------------------- #st.header('Bail type and monetary bail summary') st.subheader('Bail type') st.write("""During a defendant's arraignment (a hearing held shortly after they are arrested), one of several [types of bail](https://www.pacodeandbulletin.gov/Display/pacode?file=/secure/pacode/data/234/chapter5/s524.html) may be set: - **monetary**, where a bail amount is set and the defendant is held in jail until a portion (typically 10%) is paid (\"posted\"), - **unsecured**, where the defendant is liable for a set bail amount if they do not show up to future court proceedings, - **ROR** (“released on own recognizance”), where a defendant must agree to show up to all future court proceedings, - **nonmonetary** bail condition, or - the defendant may be **denied** bail.""") # By Bail Type """ pie1_fig = go.FigureWidget() pie1_fig.add_trace(go.Pie(labels=df_bail.index.tolist(), values=df_bail.values.tolist())) pie1_fig.update_traces(hole=.4, hoverinfo="label+percent+value") pie1_fig.update_layout(showlegend=True, title_text='Bail Type', title_x=0.45) pie1_fig.update_layout(margin={"r":0,"t":100,"l":0,"b":0}, height=400, width=400) st.plotly_chart(pie1_fig) """ st.image(Image.open('figures/aggregate_bail_type.png'), width=600) st.write("The most frequently set bail type in 2020 was monetary bail. Nonmonetary bail was set in under 1% of cases.") st.subheader('Monetary bail set') st.write("For cases where monetary bail was set, the median bail set was $40,000. A bail amount of less than $10,000 was set in around 19 percent of cases, and a bail amount of at least $100,000 was set in more than 25 percent of cases.") st.image(Image.open('figures/aggregate_bailSetBin.png'), width=400) st.write("While the maximum bail set was $5M, bail of at least $500k was set in only 5 percent of cases. Of the specific values of bail that were set below $500k, the most frequently set bail amount was $50,000.") st.image(Image.open('figures/aggregate_bailSet500k.png'), width=400) st.subheader('Monetary bail posted') st.write("In more than half (56%) of cases where monetary bail was set, bail was not posted, meaning that the defendant was not released from jail. Out of the cases where bail was at least $100,000, less than 30% of defendants posted bail. Though infrequently set, bail amounts below $1000 were also infrequently posted.") st.write("**<font color='red'>Question for PBF</font>**: do these observations (in particular, low payments of bail set below $1000) match your experience?", unsafe_allow_html=True) st.image(Image.open('figures/aggregate_bailPostedBin.png'), width=400) st.write("When bail was posted, the median and most frequently paid amount was $2,500 (corresponding to 10% of bail set at $25,000). ") st.image(Image.open('figures/aggregate_bailPosted.png'), width=400) """ # By Bail Set pie2_fig = go.FigureWidget() pie2_fig.add_trace(go.Pie(labels=series_monetary.index.tolist(), values=series_monetary.values.tolist())) pie2_fig.update_traces(hole=.4, hoverinfo="label+percent+value") pie2_fig.update_layout(showlegend=True, title_text='Bail Set', title_x=0.45) pie2_fig.update_layout(margin={"r":0,"t":100,"l":0,"b":0}, height=400, width=40) st.plotly_chart(pie2_fig) """ st.subheader('Attorney types') # By Atty Type st.write("Public defenders, representing defendants who cannot afford to hire a lawyer, were appointed in more than two thirds of cases.") st.image(Image.open('figures/aggregate_attorney_type.png'), width=600) """ pie3_fig = go.FigureWidget() pie3_fig.add_trace(go.Pie(labels=df_defender.index.tolist(), values=df_defender.values.tolist())) pie3_fig.update_traces(hole=.4, hoverinfo="label+percent+value") pie3_fig.update_layout(showlegend=True, title_text='Attorney Type', title_x=0.45) pie3_fig.update_layout(margin={"r":0,"t":100,"l":0,"b":0}, height=400, width=400) st.plotly_chart(pie3_fig) """ # TODO: fix these figures such that the same colors/order are used for each bail type st.subheader('Charged offenses and bail type') st.write("The frequency of bail types set was dependent on the types of charges associated with each case.\ For cases involving a charge of assault, monetary bail was most frequently set.\ For cases involving a drug-related charge, monetary bail and ROR were set at similar rates.\ For cases involving a charge of DUI, ROR bail was most frequently set.") st.write("**<font color='red'>Question for PBF</font>**: are there any specific charges you'd be interested in knowing this (or bail amounts/bail posted) for?", unsafe_allow_html=True), st.image(Image.open('figures/aggregate_bailType_byOffense.png'), use_column_width=True) # ---------------------------------------------------- # Moving average plots # ---------------------------------------------------- st.subheader('Bail trends over the year') st.write("Use the dropdown menu to view trends in the mean of different bail metrics. Use the slider to change the number of days over which the moving average is calculated.") st.write("Mean bail amount trended slightly upward over the course of the year. \ Monetary bail case counts dropped in March, following a decrease in total arrests as a COVID-19 mitigation measure, but returned to pre-pandemic levels by October.\ Monetary bail frequency held steady for much of the year, with a slight upward trend starting in September.") # Make data for each metric + data to initialize the chart ma_dfs = {'Bail Amount': df.groupby('bail_date').mean()['bail_amount'], 'Monetary Bail Cases': df[df['bail_type'] == 'Monetary'].groupby('bail_date').size(), 'Monetary Bail Frequency': df[df['bail_type'].notnull()].groupby('bail_date').size() } # Dropdown for metric metric = st.selectbox('Metric', ('Bail Amount', 'Monetary Bail Cases', 'Monetary Bail Frequency')) # Slider for window size window = st.slider('Window Size (days)', 1, 60, 5, 1) # Initialize figure ma_fig = go.FigureWidget() ma_fig.layout.title.text = 'Mean '+ metric + ' ' + str(window) + '-Day Moving Average' ma_fig.layout.title.x = 0.5 ma_fig.layout.xaxis.title = 'Date' ma_fig.layout.yaxis.title = metric # Add traces and finalize figure, make sure to get data from selected metric and window size if metric != 'Monetary Bail Frequency': tmp = ma_dfs[metric].rolling(window=window, min_periods=1).mean() else: tmp_denom = ma_dfs[metric].rolling(window=window, min_periods=1).sum() tmp_num = ma_dfs['Monetary Bail Cases'].rolling(window=window, min_periods=1).sum() tmp = tmp_num.div(tmp_denom) ma_fig.add_trace(go.Scatter(x=tmp.index, y=tmp.values, mode='lines+markers', name='lines+markers')) st.plotly_chart(ma_fig)