<jupyter_start><jupyter_text>Table of ContentsReplication of McMillen et al. (2017)Load and prepare dataSchoolsRoutesAdd treatment indicator from schoolsBlocks with running variable for Figure 3PrepareYearly crime countsReplication of descriptivesFigure A.2OriginalReplicationTable 1OriginalReplicationFigure 3OriginalReplicationReplication of main resultsTable 10 (column 3 and 7)Reduced estimation datasetAdditional figures and descriptives for websiteLoad dataBlocks with dummiesRoutesHourly crime countsPlot blocks with dummiesPlot violent crime trendsHourly countsViolent crimes split by FBI CodeIllustration of difference in differences (DiD)Illustration of block dummiesAverage area per block **Description**: This notebook creates all figures and displays the results used on the website [Chicago's Safe Passage Program to Prevent Crime: Is It Worth the Dime?](https://binste.github.io/chicago_safepassage_evaluation/). Some of the figures, descriptive statistics and the main results are a replication of the census block results from [McMillen et al. (2017)](https://ignaciomsarmiento.github.io/assets/Safe_Passage_WP.pdf). Therefore, the first part of the notebook will make direct comparisons to the relevant parts of the beforementioned paper. The second part produces additional figures for the website which are unrelated to McMillen et al. (2017).**Usage**: Run all cells (Cell -> Run All) to get a correct output of the figures.<jupyter_code>%matplotlib inline

import calendar
import pickle
import sys
from pathlib import Path

import altair as alt
import geopandas as gpd
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import ipywidgets as widgets
from altair import datum

sys.path.append('../..')
from src.analysis.figures import format_chart, save_to_jekyll
from src.prepare_data.crime_database import load_relevant_crimes

plt.style.use('seaborn-white')
plt.rcParams['figure.figsize'] = (13, 8)
plt.rcParams['font.size'] = 17
plt.rcParams['axes.labelsize'] = 17
plt.rcParams['axes.labelweight'] = 'bold'
plt.rcParams['xtick.labelsize'] = 17
plt.rcParams['ytick.labelsize'] = 17
plt.rcParams['legend.fontsize'] = 17
# for the notebook only (not for JupyterLab) run this command once per session
alt.renderers.enable('notebook')<jupyter_output><empty_output><jupyter_text>Set path to data folder<jupyter_code>project_folder = Path('../..')
data_path = project_folder / 'data'<jupyter_output><empty_output><jupyter_text>Replication of McMillen et al. (2017)Only main results of census block specification with regards to violent and property crime.  Load and prepare data  Schools<jupyter_code>with (data_path / 'processed/schools_blocks.pkl').open('rb') as f:
    schools = pickle.load(f)
schools['treated'] = schools['treated_foia']
schools = schools.drop('treated_foia', axis='columns')<jupyter_output><empty_output><jupyter_text>Routes<jupyter_code>with (data_path / 'processed/routes.pkl').open('rb') as f:
    routes = pickle.load(f)<jupyter_output><empty_output><jupyter_text>Add treatment indicator from schools Add earlier school years<jupyter_code>school_years = ['SY0910', 'SY1011', 'SY1112', 'SY1213']
earlier_routes = []
routes_1314 = routes.query('school_year == "SY1314"')
for sy in school_years:
    temp_routes = routes_1314.copy()
    temp_routes['school_year'] = sy
    earlier_routes.append(temp_routes)

routes = pd.concat([routes] + earlier_routes, ignore_index=True)
assert not routes.duplicated(subset=['school_year', 'route_number']).any()
routes = routes.merge(
    schools[['school_name', 'school_year', 'treated']],
    how='left',
    on=['school_name', 'school_year'], validate='m:1')<jupyter_output><empty_output><jupyter_text>Blocks with running variable for Figure 3<jupyter_code>with (data_path / 'processed/figures/blocks_fig_3.pkl').open('rb') as f:
    blocks_fig_3 = pickle.load(f)
blocks_fig_3 = blocks_fig_3.query('run_var != 3')
blocks_fig_3.head()<jupyter_output><empty_output><jupyter_text>Prepare First, devide all crime counts by number of weekdays for respective school year. These have to be first calculated:<jupyter_code>def count_weekdays(year, month):
    """Counts number of weekdays per given month.
    
    Parameters
    ----------
    year : int
        Year of month
    
    month : int
    
    Returns
    -------
    int
        Number of weekdays in given month
    """
    weekday_count = 0
    cal = calendar.Calendar()

    for day, wday_number in cal.itermonthdays2(year, month):
        # day == 0 would mean that it is not a day of
        # this month
        # wday_number in (5, 6) -> weekends
        if day > 0 and wday_number <= 4:
            weekday_count += 1
    return weekday_count
sy_range = {
    sy: [(int(f'20{sy[2:4]}'), [9, 10, 11, 12]), (int(f'20{sy[4:]}'), [1, 2, 3, 4, 5, 6])]
    for sy in [
        'SY0607', 'SY0708', 'SY0809', 'SY0910', 'SY1011', 'SY1112',
        'SY1213', 'SY1314', 'SY1415', 'SY1516'
    ]
}
# Add SY0506 separate, as I only use data starting from January 2001
sy_range['SY0506'] = [(2006, [1, 2, 3, 4, 5, 6])]
sy_range
sy_weekdays = {}
for sy, year_months in sy_range.items():
    sy_counter = 0
    for year, months in year_months:
        for month in months:
            sy_counter += count_weekdays(year, month)
    sy_weekdays[sy] = sy_counter
sy_weekdays
blocks_fig_3['violent_count_per_wday'] = blocks_fig_3.apply(
    lambda row: row['violent_count'] / sy_weekdays[row['school_year']], axis=1)
blocks_fig_3['property_count_per_wday'] = blocks_fig_3.apply(
    lambda row: row['property_count'] / sy_weekdays[row['school_year']], axis=1)
blocks_fig_3.query('violent_count != 0').head()
blocks_fig_3 = blocks_fig_3.groupby(['run_var', 'block_status']).agg({
    'violent_count':
    'mean',
    'property_count':
    'mean',
    'violent_count_per_wday':
    'mean',
    'property_count_per_wday':
    'mean'
}).reset_index()
blocks_fig_3.head()<jupyter_output><empty_output><jupyter_text>Yearly crime counts<jupyter_code>with (data_path / 'processed/figures/violent_yearly_counts.pkl').open('rb') as f:
    yearly_crime_counts = pickle.load(f)
yearly_crime_counts['violent'] = yearly_crime_counts['violent'].map({0: 'Property', 1: 'Violent'})<jupyter_output><empty_output><jupyter_text>Replication of descriptives  Figure A.2 Original![figure_a2](../../reports/figures/mcmillen_fig_a2.png)*Source: McMillen et al. (2017)*  Replication<jupyter_code>yearly_violent_trend = alt.Chart(yearly_crime_counts).mark_line().encode(
    alt.X('Date:O', axis=alt.Axis(title='Year', labelAngle=0)),
    alt.Y('count:Q', axis=alt.Axis(title='Number of crimes (in thousands)')),
    alt.Color('violent:N',
              legend=alt.Legend(title='Crime category')))
tooltips = yearly_violent_trend.mark_point().encode(
    opacity=alt.value(0),
    tooltip='count'
)
yearly_violent_trend = format_chart(yearly_violent_trend + tooltips)
yearly_violent_trend
save_to_jekyll(yearly_violent_trend, 'yearly_violent_trend')<jupyter_output><empty_output><jupyter_text>Table 1 Original![table_1](../../reports/figures/mcmillen_tab_1.png)*Source: McMillen et al. (2017)*  Replication<jupyter_code>table_1 = schools.groupby('school_year')['treated'].sum().diff().rename(
    'schools_added').reset_index()
table_1.loc[table_1['school_year'] == 'SY0910', 'schools_added'] = schools.loc[
    schools['school_year'] == 'SY0910', 'treated'].sum()
table_1_routes = routes.groupby('school_year')['treated'].sum().diff().rename(
    'routes_added').reset_index()
table_1_routes.loc[table_1_routes['school_year'] == 'SY0910',
                   'routes_added'] = routes.loc[routes['school_year'] ==
                                                'SY0910', 'treated'].sum()
table_1 = pd.merge(table_1_routes, table_1, how='inner', on='school_year')
table_1 = pd.concat(
    [
        table_1,
        pd.DataFrame.from_dict([{
            'school_year': 'total',
            'routes_added': table_1['routes_added'].sum(),
            'schools_added': table_1['schools_added'].sum()
        }])
    ],
    sort=True,
    ignore_index=True)
table_1 = table_1[['school_year', 'routes_added', 'schools_added']]
table_1<jupyter_output><empty_output><jupyter_text>Minor differences exist to the original table showed in a previous section my McMillen et al. (2017), as I did not split up certain schools which are coded as one entry into multiple entries. Example `Aldridge/Carver/CICS-Bond/CICS-Hawkins/Dubois` could be split up into the individual schools. However, due to how the rest of the analysis is implemented, this should not make any difference. For an exact replication, see the section "Implementation of Safe Passage program" in the Appendix.  Figure 3 Original >*"Given the phased way the program was implemented, we normalize to a common start and show the averages for the five pre-program years and for three post-program years. Figure 3a and 3b show that the program was indeed implemented in areas with higher crime incidents but there are no obvious differences in trends before the program implementation. Furthermore, control cells show no significant differences in levels or trends. What is more, after the implementation in the program there’s a drop in the average number of crimes when compared to control areas."* - McMillen et al. (2017), p. 10 **Assumption**: For figure, drop all blocks which switch status.Furthermore, I could not identify on which level of aggregation the crime counts on the y-axis where exactly. ![Figure 3](../../reports/figures/mcmillen_fig_3.png)*Source: McMillen et al. (2017)*  Replication<jupyter_code>text_labels = [f'{i}\n school\n years\n prior' for i in range(2, 6)][::-1]
text_labels = text_labels + [
    '1\n school\n year\n prior', '1st\n school\n year\n after', '2nd\n school\n year\n after',
    '3rd\n school\n year\n after'
]
def add_block_group(before, after, ax, block_group, color, count_col):
    """Helper function for plotting the difference in difference graphs.
    It adds the pre and post implementation lines for a given block group.
    
    Parameters
    ----------
    before : pd.DataFrame
        Contains data on years before the implementation
        of the program
        
    after : pd.DataFrame
        Contains data on years after the implementation
        of the program
        
    ax : matplotlib.axes._subplots.AxesSubplot
        Ax to which lines should be added
        
    block_group : str
        Name of block group
        
    color : str
        Name of color to use for lines
        
    count_col : str
        Name of column which contains y values
        
    Returns
    -------
    Nothing    
    """
    before[before['block_status'] == block_group].plot(
        'run_var',
        count_col,
        ax=ax,
        color=color,
        label=block_group,
        alpha=0.5,
        linewidth=3)
    after[after['block_status'] == block_group].plot(
        'run_var',
        count_col,
        ax=ax,
        color=color,
        label='',
        alpha=0.5,
        linewidth=3)
def plot_did(count_col, f_name, ylim):
    """Plots a difference in difference graph
    and saves it.
    
    Parameters
    ----------
    count_col : str
        Name of column which contains y values
        
    f_name : str
        File name without ending 
        (used for saving the figure)
        
    ylim : tuple of floats, (lower limit, upper limit)
        Contains the lower and upper y limits
        
    Returns
    -------
    Nothing
    """
    fig, ax = plt.subplots()
    before = blocks_fig_3.query('run_var < 0')
    after = blocks_fig_3.query('run_var >= 0')

    add_block_group(before, after, ax, 'Treated', 'blue', count_col)
    add_block_group(before, after, ax, 'One over', 'orange', count_col)
    add_block_group(before, after, ax, 'Two over', 'red', count_col)
    add_block_group(before, after, ax, 'Three over', 'turquoise', count_col)

    plt.xticks(range(-5, 4), text_labels)
    ax.tick_params(axis='x', which='major', length=6)

    ax.set_xlabel('')
    ax.set_ylabel('Average crime count per weekday per block')
    ax.legend(title='Block status')
    ax.set_ylim(*ylim)
    
    ax.vlines(-0.5, 0, ylim[1], linestyles='dotted', alpha=0.5,
        linewidth=3)
    plt.figtext(0.66, 0.65, 'Implementation of program', rotation=90)
    ax.grid(True)
    plt.tight_layout()
    plt.savefig(f'../../docs/figures/{f_name}.png', dpi=800, transparent=True)<jupyter_output><empty_output><jupyter_text>Violent crime count (Figure 3a):<jupyter_code>plot_did('violent_count_per_wday', 'didfigureviolent', (0, 0.0024))<jupyter_output><empty_output><jupyter_text>Property crime count (Figure 3b):<jupyter_code>plot_did('property_count_per_wday', 'didfigureproperty', (0, 0.008))<jupyter_output><empty_output><jupyter_text>Replication of main results  Table 10 (column 3 and 7)These results are better visualized and compared in the Appendix.![table_10](../../reports/figures/mcmillen_tab_10.png) The poisson regression is run in `0.0-binste-estimation-poisson.ipynb`. The estimation notebook can NOT be run in mybinder.org due to resource constraints.<jupyter_code>results_violent = pd.read_csv('../../models/summary_poisson_violent.csv')
results_violent
results_property = pd.read_csv('../../models/summary_poisson_property.csv')
results_property<jupyter_output><empty_output><jupyter_text>Reduced estimation datasetBasis is the dataset used to obtain the results in the previous section with the additional restriction of only 5 pre-implementation school years and 3 after-implementation school years per block. Figure 3 in McMillen et al. (2017) seem to use this data and it is not clear to me if they also used it for the regressions.<jupyter_code>results_violent_reduced = pd.read_csv('../../models/summary_poisson_violent_reduced.csv')
results_violent_reduced
results_property_reduced = pd.read_csv('../../models/summary_poisson_property_reduced.csv')
results_property_reduced<jupyter_output><empty_output><jupyter_text>Additional figures and descriptives for websiteThe figures and descriptives in the following are for the website only and do not directly belong to the replication.Care with this setting. `max_rows=None` should only be used when exporting to html. Don't display large figures in this notebook!<jupyter_code>alt.data_transformers.enable('default', max_rows=None)<jupyter_output><empty_output><jupyter_text>Load data  Blocks with dummiesThis dataset contains an observation per block per school year<jupyter_code>with (data_path / 'processed/figures/blocks_with_dummies.pkl').open('rb') as f:
    blocks_dummies = pickle.load(f)
blocks_full = blocks_dummies.copy()
blocks_dummies = blocks_dummies[['tract_bloc', 'school_year', 'info']]
assert not blocks_dummies.duplicated(subset=['tract_bloc', 'school_year']).any()
blocks_dummies.head()<jupyter_output><empty_output><jupyter_text>Routes<jupyter_code>with (data_path / 'processed/routes.pkl').open('rb') as f:
    routes = pickle.load(f)
routes.head()<jupyter_output><empty_output><jupyter_text>Hourly crime counts<jupyter_code>with (data_path / 'processed/figures/violent_hourly_counts.pkl').open('rb') as f:
    hourly_counts = pickle.load(f)<jupyter_output><empty_output><jupyter_text>Plot blocks with dummies<jupyter_code>dummy_trend = blocks_dummies.groupby('school_year')['info'].apply(
    lambda x: x.value_counts()).reset_index().rename(
        {
            'level_1': 'block_status',
            'info': 'count'
        }, axis='columns').query('block_status != "-"')

dummy_trend['block_status'] = dummy_trend['block_status'].map({
    'treated':
    'Treated',
    'one over':
    'One over',
    'two over':
    'Two over',
    'three over':
    'Three over'
})
dummy_trend.head()
def add_block_group_2(dummy_trend, ax, block_group, color):
    """Helper function for plotting the trends of the block status
    
    Parameters
    ----------
    dummy_trend : pd.DataFrame
        Contains number of blocks for each block group
        for each school year
        
    ax : matplotlib.axes._subplots.AxesSubplot
        Ax to which lines should be added
    
    block_group : str
        Name of block group which should be plotted
        
    color : str
        Name of color which should be used for the lines
        
    Returns
    -------
    Nothing
    """
    ax.plot(
        sorted(dummy_trend['school_year'].unique()),
        dummy_trend.loc[dummy_trend['block_status'] == block_group, 'count']
        .values,
        label=block_group,
        color=color,
        alpha=0.5,
        linewidth=3)
fig, ax = plt.subplots()
add_block_group_2(dummy_trend, ax, 'Treated', 'blue')
add_block_group_2(dummy_trend, ax, 'One over', 'orange')
add_block_group_2(dummy_trend, ax, 'Two over', 'red')
add_block_group_2(dummy_trend, ax, 'Three over', 'turquoise')

ax.grid(True)
ax.set_ylim(0, 4500)
ax.set_xlabel('')
ax.set_ylabel('Number of blocks')

ax.tick_params(axis='x', length=6, which='major')
ax.legend(title='Block status', loc='upper left')
plt.tight_layout()
plt.savefig('../../docs/figures/blocktrend.png', dpi=800, transparent=True);<jupyter_output><empty_output><jupyter_text>Plot violent crime trends  Hourly counts Color scheme will be reversed by `save_to_jekyll` function.<jupyter_code>def plot_violent_trends():
    """Plots the heatmaps of the violent crimes
    
    Parameters
    ----------
    Nothing
    
    Returns
    -------
    alt.vconcat Chart
    """
    single = alt.selection_single(on='click', encodings=['x'])

    month = alt.Chart().mark_rect().encode(
        x=alt.X(
            'Date:O', timeUnit='year', axis=alt.Axis(title='Year',
                                                     labelAngle=-60)),
        y=alt.Y('Date:O', timeUnit='month', axis=alt.Axis(title='Month')),
        color=alt.condition(
            single,
            alt.Color(
                'mean(count):Q',
                legend=alt.Legend(title="Mean violent crime count"),
                scale=alt.Scale(scheme='redyellowgreen')),
            alt.value('lightgray'))).properties(selection=single)

    hours = alt.Chart().mark_rect().encode(
        x=alt.X(
            'Date:O',
            timeUnit='hours',
            axis=alt.Axis(title='Hour of Day', labelAngle=-60)),
        y=alt.Y('Date:O', timeUnit='month', axis=alt.Axis(title='Month')),
        color=alt.Color(
            'mean(count):Q',
            legend=alt.Legend(title="Mean violent crime count"),
            scale=alt.Scale(scheme='redyellowgreen')),
    ).transform_filter(single)

    return alt.vconcat(
        month,
        hours,
        data=hourly_counts.query('violent == 1'),
        resolve=alt.Resolve(
            scale=alt.LegendResolveMap(color=alt.ResolveMode('independent'))))<jupyter_output><empty_output><jupyter_text>We won't display the figure as it is too large for the notebook. We will only use this one for exporting it for the website. On the website you can find it as the two interactive heatmaps of the mean violent crime count (first page).<jupyter_code>violent_trend = plot_violent_trends()
save_to_jekyll(violent_trend, 'violent_trend', reverse_colors=True)<jupyter_output><empty_output><jupyter_text>Violent crimes split by FBI Code<jupyter_code>with (data_path / 'processed/figures/yearly_violent_FBI.pkl').open('rb') as f:
    yearly_violent_FBI = pickle.load(f)
yearly_violent_FBI.head()
yearly_violent_FBI['FBI Code'] = yearly_violent_FBI['FBI Code'].map({
    '01A': 'Homicide 1st & 2nd degree',
    '02': 'Criminal sexual assault',
    '03': 'Robbery',
    '04A': 'Aggravated assault',
    '04B': 'Aggravated battery'
})
violent_FBI = format_chart(
    alt.Chart(yearly_violent_FBI).mark_bar().encode(
        alt.X('Date:O', axis=alt.Axis(title='Year', labelAngle=0)), 
        alt.Y('count', axis=alt.Axis(title='Number of crimes')),
        alt.Color(
            'FBI Code:N',
            title='UCR categorization'
            ),
        tooltip='count'))
violent_FBI
save_to_jekyll(violent_FBI, 'violent_FBI')<jupyter_output><empty_output><jupyter_text>Illustration of difference in differences (DiD)<jupyter_code>x_labels = ['Before implementation', 'After implementation']
fig, ax = plt.subplots()

# fig.subplots_adjust(left=0.1)
ax.plot(x_labels, [5, 3], color='blue', label='Treated block', alpha=0.5,
        linewidth=3)
ax.plot(x_labels, [2, 1], color='red', label='Control block', alpha=0.5,
        linewidth=3)

# Add estimated counterfactual
ax.plot(x_labels, [5, 4], color='red', linestyle='dashed', label='Assumed counterfactual', alpha=0.5,
        linewidth=3)

# Add treatment effect
ax.vlines('After implementation', 3, 4, colors='black', alpha=0.5,
        linewidth=3)
plt.figtext(0.915, 0.633, 'Effect\n of program', rotation=90, horizontalalignment='center')

# Add share which is attributable to other factors
ax.vlines('After implementation', 4, 5, colors='black', linestyles='dotted', alpha=0.5,
        linewidth=3)
plt.figtext(0.91, 0.856, 'Other factors', rotation=90)

ax.legend(loc='lower left')
ax.set_ylim((0.5, 5.5))
ax.set_ylabel('Average monthly crime count')
ax.yaxis.grid(True)
ax.tick_params(axis='x', which='major', pad=15)
plt.tight_layout()
plt.savefig('../../docs/figures/didfigure.png', dpi=800, transparent=True);<jupyter_output><empty_output><jupyter_text>Illustration of block dummiesUse Bogan HS for the school year 2013-2014 as an example. Find the corresponding blocks<jupyter_code>ill_blocks = blocks_full[blocks_full['school_year'] == 'SY1314'].cx[-87.825:-87.615, 41.6425:41.8525]
ill_route = routes[(routes['route_number'] == 75) & (routes['school_year'] == 'SY1314')]
ill_blocks['info'] = ill_blocks['info'].map({
    'treated':
    'Treated',
    'one over':
    'One over',
    'two over':
    'Two over',
    'three over':
    'Three over',
    '-':
    '-'
})
ill_blocks = ill_blocks.query('info != "-"')
ill_route.head()
fig, ax = plt.subplots()
ill_blocks.query('info == "Treated"').plot(ax=ax, color='blue', alpha=0.5, edgecolor='grey')
ill_blocks.query('info == "One over"').plot(ax=ax, color='orange', alpha=0.5, edgecolor='grey')
ill_blocks.query('info == "Two over"').plot(ax=ax, color='red', alpha=0.5, edgecolor='grey')
ill_blocks.query('info == "Three over"').plot(ax=ax, color='turquoise', alpha=0.5, edgecolor='grey')
ill_route.plot(ax=ax, color='black', linewidth=3)

# Plot empty points to create legend
plt.scatter([], [], color='blue', alpha=0.5, label='Treated block')
plt.scatter([], [], color='orange', alpha=0.5, label='One block over')
plt.scatter([], [], color='red', alpha=0.5, label='Two blocks over')
plt.scatter([], [], color='turquoise', alpha=0.5, label='Three blocks over')
plt.plot([], [], color='black', linewidth=3, label='Safe Passage route')

ax.legend(loc='upper right', bbox_to_anchor=(1.38, 1))
ax.set_xlim(-87.730, -87.714)
ax.set_ylim(41.742, 41.753)
plt.tight_layout()
plt.savefig('../../docs/figures/blockgroups.png', dpi=800, transparent=True);<jupyter_output><empty_output><jupyter_text>Average area per blockFollows https://gis.stackexchange.com/questions/218450/getting-polygon-areas-using-geopandas -> gives area in square meter.<jupyter_code>avg_area_df = blocks_full[~blocks_full['school_year'].isin(
    ['SY0506', 'SY0607', 'SY0708', 'SY0809'])].to_crs({
        'init': 'epsg:3857'
    })

avg_area = avg_area_df['geometry'].area.mean()
avg_area
for x in ['three over', 'two over', 'one over', 'treated']:
    print(f'Avg area for {x} block:',
          avg_area_df.query('info == @x')['geometry'].area.mean())<jupyter_output>Avg area for three over block: 23891.17317558407
Avg area for two over block: 22398.82184940946
Avg area for one over block: 21561.475598147517
Avg area for treated block: 23409.490151683178
{'fraction_non_alphanumeric': 0.13199915736254478, 'fraction_numerical': 0.024436486201811672, 'mean_word_length': 4.187062937062937, 'pattern_counts': {'":': 0, '<': 106, '<?xml version=': 0, '>': 110, 'https://': 3, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '26', 'dir': 'jupyter-structured-clean-dedup', 'id': '457168', 'license': 'MIT', 'n_tokens_mistral': 8206, 'n_tokens_neox': 7676, 'n_words': 1898, 'path': 'notebooks/5_analysis/1.0-binste-analyze-crime-results-census-block-level.ipynb', 'repo_name': 'binste/chicago_safepassage_evaluation', 'size': '23735'}