Assignment 3

Objectives of This Analysis

  • First, I decided it would be interesting to look at the initial charges, themselves, to see if different groups tend to be charged with higher or lower class felonies from the beginning. Such differences might reflect biases in policing or in the early stages of prosecution, or could just be the result of different classes of people committing different crimes at different rates.
  • My main goal was to analyze the generosity or fairness of the Cook County Attorney's office over the years. I created a dependent variable I called felony_class_difference and set equal to the change in felony class from arraignment to disposition for each defendant. I think this is an imperfect measure of fairness, but it is the best I could find from the data as it exists today. I then looked at how the independent variables race, gender and age impact the felony_class_difference to see if there appears to be any systematic bias for or against any particular groups.

Part 1: Reading in and exploring the initiation data

In [1]:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from matplotlib.pyplot import figure
%matplotlib inline
import warnings
sns.set(style="darkgrid")
warnings.filterwarnings('ignore')
import datetime
In [2]:
initiation = pd.read_csv('Initiation.csv', low_memory=False)
In [3]:
initiation.head()
Out[3]:
CASE_ID CASE_PARTICIPANT_ID PRIMARY_CHARGE CHARGE_ID CHARGE_VERSION_ID CHAPTER ACT SECTION CLASS AOIC ... INCIDENT_END_DATE ARREST_DATE LAW_ENFORCEMENT_AGENCY UNIT INCIDENT_CITY RECEIVED_DATE ARRAIGNMENT_DATE UPDATED_OFFENSE_CATEGORY CHARGE_OFFENSE_TITLE CHARGE_COUNT
0 89636659845 135342759273 False 563965111550 487780942354 720 5 9-1(a)(2) M 0735100 ... NaN 5/22/2011 6:51:00 PM PROMIS Data Conversion NaN NaN 5/24/2011 12:00:00 AM 7/11/2011 12:00:00 AM Homicide FIRST DEGREE MURDER 2
1 89636659845 135342759273 False 563966831069 487782428385 720 5 9-1(a)(3) M 0735200 ... NaN 5/22/2011 6:51:00 PM PROMIS Data Conversion NaN NaN 5/24/2011 12:00:00 AM 7/11/2011 12:00:00 AM Homicide FIRST DEGREE MURDER 3
2 89636659845 135342759273 False 563968304943 487783772890 720 5 9-1(a)(3) M 0735200 ... NaN 5/22/2011 6:51:00 PM PROMIS Data Conversion NaN NaN 5/24/2011 12:00:00 AM 7/11/2011 12:00:00 AM Homicide FIRST DEGREE MURDER 4
3 89636659845 135342759273 False 563968796235 487784197470 720 5 9-1(a)(3) M 0735200 ... NaN 5/22/2011 6:51:00 PM PROMIS Data Conversion NaN NaN 5/24/2011 12:00:00 AM 7/11/2011 12:00:00 AM Homicide FIRST DEGREE MURDER 5
4 89636659845 135342759273 False 563969696935 487784975868 720 5 9-1(a)(3) M 0735200 ... NaN 5/22/2011 6:51:00 PM PROMIS Data Conversion NaN NaN 5/24/2011 12:00:00 AM 7/11/2011 12:00:00 AM Homicide FIRST DEGREE MURDER 6

5 rows × 26 columns

In [4]:
initiation.shape
Out[4]:
(839181, 26)
In [5]:
sns.heatmap(initiation.isnull(),yticklabels=False,cbar=False,cmap='viridis')
Out[5]:
<matplotlib.axes._subplots.AxesSubplot at 0x1a19e96198>
In [6]:
initiation.drop(['INCIDENT_END_DATE','UNIT'], axis=1, inplace=True)
In [7]:
initiation.dropna(inplace=True)
In [8]:
sns.heatmap(initiation.isnull(),yticklabels=False,cbar=False,cmap='viridis')
Out[8]:
<matplotlib.axes._subplots.AxesSubplot at 0x1a1a1d9438>
In [9]:
initiation['CLASS'].value_counts()
Out[9]:
4    231874
2    169426
3    105495
X     77998
1     60931
M     26081
A       470
B        19
C        16
Z         6
P         1
Name: CLASS, dtype: int64
In [10]:
initiation.columns
Out[10]:
Index(['CASE_ID', 'CASE_PARTICIPANT_ID', 'PRIMARY_CHARGE', 'CHARGE_ID',
       'CHARGE_VERSION_ID', 'CHAPTER', 'ACT', 'SECTION', 'CLASS', 'AOIC',
       'EVENT', 'EVENT_DATE', 'AGE_AT_INCIDENT', 'GENDER', 'RACE',
       'INCIDENT_BEGIN_DATE', 'ARREST_DATE', 'LAW_ENFORCEMENT_AGENCY',
       'INCIDENT_CITY', 'RECEIVED_DATE', 'ARRAIGNMENT_DATE',
       'UPDATED_OFFENSE_CATEGORY', 'CHARGE_OFFENSE_TITLE', 'CHARGE_COUNT'],
      dtype='object')
In [11]:
initiation_filtered = initiation[['CASE_PARTICIPANT_ID', 'CHARGE_ID', 'CHARGE_OFFENSE_TITLE', 'CLASS','AGE_AT_INCIDENT', 'GENDER',
            'RACE', 'LAW_ENFORCEMENT_AGENCY', 'ARRAIGNMENT_DATE', 'UPDATED_OFFENSE_CATEGORY']]
In [12]:
initiation_filtered.shape
Out[12]:
(672317, 10)
In [13]:
initiation_filtered.iloc[:,-2] = initiation_filtered.iloc[:, -2].apply(pd.to_datetime, errors='coerce')
initiation_filtered['arraignment_year'] = initiation_filtered.ARRAIGNMENT_DATE.dt.year
initiation_filtered.drop('ARRAIGNMENT_DATE', axis=1, inplace=True)
initiation_filtered.head()
Out[13]:
CASE_PARTICIPANT_ID CHARGE_ID CHARGE_OFFENSE_TITLE CLASS AGE_AT_INCIDENT GENDER RACE LAW_ENFORCEMENT_AGENCY UPDATED_OFFENSE_CATEGORY arraignment_year
116 459015687748 555449231356 [POSSESSION OF CONTROLLED SUBSTANCE WITH INTEN... X 34.0 Male Black CHICAGO PD Narcotics 2011.0
117 459015687748 556334538337 ARMED HABITUAL CRIMINAL X 34.0 Male Black CHICAGO PD Narcotics 2011.0
118 459015687748 556335439038 ARMED VIOLENCE X 34.0 Male Black CHICAGO PD Narcotics 2011.0
119 459015687748 555347861578 UNLAWFUL USE OR POSSESSION OF A WEAPON BY A FELON 2 34.0 Male Black CHICAGO PD Narcotics 2011.0
120 459015687748 556336339739 AGGRAVATED UNLAWFUL USE OF WEAPON 2 34.0 Male Black CHICAGO PD Narcotics 2011.0
In [14]:
initiation_filtered.rename(columns={'CASE_PARTICIPANT_ID':'participant_id', 'CHARGE_ID':'charge_id','CHARGE_OFFENSE_TITLE':'charge_offence_title', 
                                    'CLASS':'felony_class_initiation', 'AGE_AT_INCIDENT':'age','GENDER':'gender','RACE':'race', 
                                    'LAW_ENFORCEMENT_AGENCY':'law_enforcement_agency', 'UPDATED_OFFENSE_CATEGORY':'updated_offense_category'}, inplace=True)
In [15]:
initiation_filtered.head()
Out[15]:
participant_id charge_id charge_offence_title felony_class_initiation age gender race law_enforcement_agency updated_offense_category arraignment_year
116 459015687748 555449231356 [POSSESSION OF CONTROLLED SUBSTANCE WITH INTEN... X 34.0 Male Black CHICAGO PD Narcotics 2011.0
117 459015687748 556334538337 ARMED HABITUAL CRIMINAL X 34.0 Male Black CHICAGO PD Narcotics 2011.0
118 459015687748 556335439038 ARMED VIOLENCE X 34.0 Male Black CHICAGO PD Narcotics 2011.0
119 459015687748 555347861578 UNLAWFUL USE OR POSSESSION OF A WEAPON BY A FELON 2 34.0 Male Black CHICAGO PD Narcotics 2011.0
120 459015687748 556336339739 AGGRAVATED UNLAWFUL USE OF WEAPON 2 34.0 Male Black CHICAGO PD Narcotics 2011.0
In [16]:
initiation_filtered = initiation_filtered[initiation_filtered['felony_class_initiation'].isin(['X','1','2','3','4'])]
In [17]:
initiation_filtered['felony_class_initiation'] = initiation_filtered['felony_class_initiation'].map({'X': 0,'1': 1,'2':2,'3':3, '4':4})
In [18]:
initiation_filtered.race.value_counts()
Out[18]:
Black                               434531
White [Hispanic or Latino]          116875
White                                78699
HISPANIC                              6653
Asian                                 4496
White/Black [Hispanic or Latino]      3232
Unknown                                853
American Indian                        306
Biracial                                73
ASIAN                                    6
Name: race, dtype: int64
In [19]:
initiation_filtered['race'] = initiation_filtered['race'].str.lower()
In [20]:
initiation_filtered.race.value_counts()
Out[20]:
black                               434531
white [hispanic or latino]          116875
white                                78699
hispanic                              6653
asian                                 4502
white/black [hispanic or latino]      3232
unknown                                853
american indian                        306
biracial                                73
Name: race, dtype: int64
In [21]:
initiation_filtered = initiation_filtered[initiation_filtered['race'].isin(['black',
                    'white [hispanic or latino] ', 'white', 'hispanic','asian',
                    'white/black [hispanic or latino]'])]
In [22]:
initiation_filtered.gender.value_counts()
Out[22]:
Male                          471365
Female                         56239
Unknown                            6
Male name, no gender given         5
Unknown Gender                     2
Name: gender, dtype: int64
In [23]:
initiation_filtered = initiation_filtered[initiation_filtered['gender'].isin(['Male', 'Female'])]

Data Visualization

In [24]:
ax = sns.countplot(initiation_filtered['race'])

ax.set_xticklabels(ax.get_xticklabels(), rotation=40, ha="right")

ax.set_title('Breakdown by Race of People Charged With Felonies in Cook County from 2011 to 2018')
plt.show()

Observation: Black people comprised more than 2/3 of all people charged with felonies during this time period.

In [25]:
ax = sns.countplot(initiation_filtered['gender'])

ax.set_xticklabels(ax.get_xticklabels(), rotation=40, ha="right")

ax.set_title('Breakdown by Gender of People Charged With Felonies in Cook County from 2011 to 2018')
plt.show()

Observation: Male felony defendants outnumber female by about nine to one.

Note: I included only Male and Female gender because the data for other categories of gender is insufficient.

In [26]:
initiation_filtered[['age','arraignment_year']]  = initiation_filtered[['age','arraignment_year']].fillna(0.0).astype(int)
In [27]:
initiation_filtered.arraignment_year.value_counts()
Out[27]:
2012    70439
2013    69370
2018    64869
2014    64215
2017    63898
2015    62610
2016    62520
2011    60096
2019     9534
2010       25
2106        6
2007        6
2001        4
2103        3
0           3
2008        2
2108        1
2117        1
1985        1
2100        1
Name: arraignment_year, dtype: int64

I decided to work with data between 2011 and 2018 only because the data for the other years is incomplete.

In [28]:
initiation_filtered = initiation_filtered[(initiation_filtered['arraignment_year'] > 2010) & (initiation_filtered['arraignment_year'] < 2019)]
In [29]:
initiation_filtered.arraignment_year.value_counts()
Out[29]:
2012    70439
2013    69370
2018    64869
2014    64215
2017    63898
2015    62610
2016    62520
2011    60096
Name: arraignment_year, dtype: int64
In [30]:
multi_plot = initiation_filtered.groupby(['arraignment_year', 'race']).felony_class_initiation.mean()
multi_plot = multi_plot.unstack()
multi_plot
Out[30]:
race asian black hispanic white white/black [hispanic or latino]
arraignment_year
2011 2.587336 2.508401 3.224401 2.668670 2.439678
2012 2.582367 2.516357 3.472013 2.840857 2.626039
2013 2.445000 2.373637 3.539157 2.779796 2.287982
2014 2.219259 2.363948 3.525786 2.806799 2.478261
2015 2.296562 2.429692 3.651189 2.759598 2.723039
2016 2.596262 2.442053 3.617702 2.731924 2.219858
2017 2.685921 2.434579 3.646199 2.637439 2.529680
2018 2.642105 2.519948 3.688889 2.790099 2.591479
In [31]:
multi_plot.loc[:, :].plot()
plt.legend(bbox_to_anchor=(1.75, 1), loc='upper right',  ncol=1)


plt.plot(multi_plot['black'], marker='', color='black', linewidth=3, alpha=0.7)
plt.plot(multi_plot['white'], marker='', color='white', linewidth=3, alpha=1)
plt.plot(multi_plot['hispanic'], marker='', color='green', linewidth=2, alpha=1)

ax = plt.gca()
leg = ax.get_legend()
leg.legendHandles[1].set_color('black')
leg.legendHandles[3].set_color('white')

plt.ylabel('Felony class')
plt.title('Initial felony class for each race by year')
plt.show()

Observations:

  • The average felony class for people in the Hispanic class was much lower compared to the average of all other races.

  • People in the Black, Asian, and White/Black [Hispanic or Lation} categories were on average charged with the more serious felonies, although there were years where people in the latter two categories moved into the middle range occupied by people in the White and White [Hispanic or Latino] categories.

In [32]:
by_gender = initiation_filtered.groupby(['arraignment_year', 'gender']).felony_class_initiation.mean()
by_gender = by_gender.unstack()
by_gender
Out[32]:
gender Female Male
arraignment_year
2011 2.766941 2.524340
2012 2.889406 2.555367
2013 2.698672 2.417336
2014 2.725339 2.410282
2015 2.698135 2.471406
2016 2.704640 2.468313
2017 2.636337 2.452456
2018 2.784964 2.541569
In [33]:
by_gender.loc[:, :].plot()
plt.legend(bbox_to_anchor=(1, 1), loc='upper right',  ncol=1)
plt.ylabel('Felony class')
plt.title('Initial felony class for each gender by year')
plt.show()

Observation: The average felony class for Female is much lower compared to the average of the Male gender. Note: I did not include defendants from other gender classes because their numbers were too small to be statistically meaningful.

Part2: Exploring changes in felony class as defendants move from initiation through disposition. This entailed merging two data sets.

In [34]:
disposition = pd.read_csv('Dispositions.csv', low_memory=False)
In [35]:
disposition.head()
Out[35]:
CASE_ID CASE_PARTICIPANT_ID PRIMARY_CHARGE CHARGE_ID CHARGE_VERSION_ID OFFENSE_TITLE CHAPTER ACT SECTION CLASS ... INCIDENT_BEGIN_DATE INCIDENT_END_DATE ARREST_DATE LAW_ENFORCEMENT_AGENCY UNIT INCIDENT_CITY RECEIVED_DATE ARRAIGNMENT_DATE UPDATED_OFFENSE_CATEGORY CHARGE_COUNT
0 61601638368 113580977306 False 82263540230 88097103639 PROMIS Conversion 38 - 9-1(a)(2) X ... 8/9/1984 12:00:00 AM NaN 8/15/1984 12:00:00 AM CHICAGO POLICE DEPT NaN NaN 8/15/1984 12:00:00 AM 9/21/1984 12:00:00 AM Homicide 2
1 61601638368 113580977306 False 82273775466 94780777269 PROMIS Conversion 38-33A-2\I NaN NaN X ... 8/9/1984 12:00:00 AM NaN 8/15/1984 12:00:00 AM CHICAGO POLICE DEPT NaN NaN 8/15/1984 12:00:00 AM 9/21/1984 12:00:00 AM Homicide 3
2 61601638368 113580977306 False 82273857348 99965682145 PROMIS Conversion 38 - 9-1(a)(3) X ... 8/9/1984 12:00:00 AM NaN 8/15/1984 12:00:00 AM CHICAGO POLICE DEPT NaN NaN 8/15/1984 12:00:00 AM 9/21/1984 12:00:00 AM Homicide 4
3 61601638368 113580977306 False 82273939230 99960728707 PROMIS Conversion 38 - 9-1(a)(3) X ... 8/9/1984 12:00:00 AM NaN 8/15/1984 12:00:00 AM CHICAGO POLICE DEPT NaN NaN 8/15/1984 12:00:00 AM 9/21/1984 12:00:00 AM Homicide 5
4 61601638368 113580977306 False 82274021112 99960940998 PROMIS Conversion 38 - 9-1(a)(3) X ... 8/9/1984 12:00:00 AM NaN 8/15/1984 12:00:00 AM CHICAGO POLICE DEPT NaN NaN 8/15/1984 12:00:00 AM 9/21/1984 12:00:00 AM Homicide 6

5 rows × 31 columns

In [36]:
disposition.shape
Out[36]:
(754945, 31)
In [37]:
disposition.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 754945 entries, 0 to 754944
Data columns (total 31 columns):
CASE_ID                      754945 non-null int64
CASE_PARTICIPANT_ID          754945 non-null int64
PRIMARY_CHARGE               754945 non-null bool
CHARGE_ID                    754945 non-null int64
CHARGE_VERSION_ID            754945 non-null int64
OFFENSE_TITLE                754945 non-null object
CHAPTER                      754945 non-null object
ACT                          731840 non-null object
SECTION                      731840 non-null object
CLASS                        754786 non-null object
AOIC                         754764 non-null object
DISPO_DATE                   754945 non-null object
CHARGE_DISPOSITION           754945 non-null object
CHARGE_DISPOSITION_REASON    198178 non-null object
JUDGE                        692654 non-null object
COURT_NAME                   749526 non-null object
COURT_FACILITY               746748 non-null object
AGE_AT_INCIDENT              742462 non-null float64
GENDER                       752118 non-null object
RACE                         750971 non-null object
OFFENSE_TYPE                 754945 non-null object
INCIDENT_BEGIN_DATE          747988 non-null object
INCIDENT_END_DATE            87140 non-null object
ARREST_DATE                  739345 non-null object
LAW_ENFORCEMENT_AGENCY       753919 non-null object
UNIT                         232865 non-null object
INCIDENT_CITY                684307 non-null object
RECEIVED_DATE                754945 non-null object
ARRAIGNMENT_DATE             621943 non-null object
UPDATED_OFFENSE_CATEGORY     754945 non-null object
CHARGE_COUNT                 754945 non-null int64
dtypes: bool(1), float64(1), int64(5), object(24)
memory usage: 173.5+ MB
In [38]:
merged = pd.merge(initiation, disposition, on=['CASE_PARTICIPANT_ID', 'CHARGE_ID'])
In [39]:
merged.shape
Out[39]:
(533522, 53)
In [40]:
merged.head()
Out[40]:
CASE_ID_x CASE_PARTICIPANT_ID PRIMARY_CHARGE_x CHARGE_ID CHARGE_VERSION_ID_x CHAPTER_x ACT_x SECTION_x CLASS_x AOIC_x ... INCIDENT_BEGIN_DATE_y INCIDENT_END_DATE ARREST_DATE_y LAW_ENFORCEMENT_AGENCY_y UNIT INCIDENT_CITY_y RECEIVED_DATE_y ARRAIGNMENT_DATE_y UPDATED_OFFENSE_CATEGORY_y CHARGE_COUNT_y
0 157303445894 459015687748 False 555449231356 480313281047 720 570 401(a)(2)(A) X 5095130 ... 12/15/2010 12:00:00 AM NaN 12/15/2010 8:40:00 PM CHICAGO PD NaN Chicago 1/31/2011 12:00:00 AM 1/31/2011 12:00:00 AM Narcotics 2
1 157303445894 459015687748 False 556334538337 481092315287 720 5 24-1.7(a) X 0013855 ... 12/15/2010 12:00:00 AM NaN 12/15/2010 8:40:00 PM CHICAGO PD NaN Chicago 1/31/2011 12:00:00 AM 1/31/2011 12:00:00 AM Narcotics 3
2 157303445894 459015687748 False 556335439038 481093093684 720 5 33A-2(a) X 0012372 ... 12/15/2010 12:00:00 AM NaN 12/15/2010 8:40:00 PM CHICAGO PD NaN Chicago 1/31/2011 12:00:00 AM 1/31/2011 12:00:00 AM Narcotics 4
3 157303445894 459015687748 False 555347861578 480222703899 720 5 24-1.1(a) 2 0012309 ... 12/15/2010 12:00:00 AM NaN 12/15/2010 8:40:00 PM CHICAGO PD NaN Chicago 1/31/2011 12:00:00 AM 1/31/2011 12:00:00 AM Narcotics 5
4 157303445894 459015687748 False 556336339739 481093872082 720 5 24-1.6(a)(1) 2 0012476 ... 12/15/2010 12:00:00 AM NaN 12/15/2010 8:40:00 PM CHICAGO PD NaN Chicago 1/31/2011 12:00:00 AM 1/31/2011 12:00:00 AM Narcotics 6

5 rows × 53 columns

In [41]:
merged_2 = merged[['CHARGE_ID', 'UPDATED_OFFENSE_CATEGORY_x', 
                   'AGE_AT_INCIDENT_x','GENDER_x','RACE_x',
                   'ARREST_DATE_x','ARRAIGNMENT_DATE_x', 
                   'DISPO_DATE','LAW_ENFORCEMENT_AGENCY_x',
                   'CLASS_x', 'CLASS_y', 'CHARGE_DISPOSITION']]
In [42]:
merged_2.iloc[:,5:8].head(1)
Out[42]:
ARREST_DATE_x ARRAIGNMENT_DATE_x DISPO_DATE
0 12/15/2010 8:40:00 PM 1/31/2011 12:00:00 AM 3/26/2012 12:00:00 AM
In [43]:
merged_2.iloc[:,5:8] = merged_2.iloc[:, 5:8].apply(pd.to_datetime, errors='coerce')
merged_2['arrest_year'] = merged_2.ARREST_DATE_x.dt.year
merged_2['arrest_month'] = merged_2.ARREST_DATE_x.dt.month
merged_2['arraignment_year'] = merged_2.ARRAIGNMENT_DATE_x.dt.year
merged_2['arraignment_month'] = merged_2.ARRAIGNMENT_DATE_x.dt.month
merged_2['disposition_year'] = merged_2.DISPO_DATE.dt.year
merged_2['disposition_month'] = merged_2.DISPO_DATE.dt.month
In [44]:
merged_2.rename(columns={'UPDATED_OFFENSE_CATEGORY_x':'type_of_crime','AGE_AT_INCIDENT_x':'age',
                        'GENDER_x':'gender','RACE_x':'race', 'LAW_ENFORCEMENT_AGENCY_x':'law_enforcement_agency',
                        'CHARGE_DISPOSITION':'charge_disposition', 'CLASS_x':'felony_class_initiation',
                        'CLASS_y':'felony_class_disposition', 'ARREST_DATE_x':'arrest_date',
                        'ARRAIGNMENT_DATE_x':'arraignment_date','DISPO_DATE':'disposition_date'}, inplace=True)
In [45]:
merged_2.head()
Out[45]:
CHARGE_ID type_of_crime age gender race arrest_date arraignment_date disposition_date law_enforcement_agency felony_class_initiation felony_class_disposition charge_disposition arrest_year arrest_month arraignment_year arraignment_month disposition_year disposition_month
0 555449231356 Narcotics 34.0 Male Black 2010-12-15 20:40:00 2011-01-31 2012-03-26 CHICAGO PD X X Finding Not Not Guilty 2010 12 2011.0 1.0 2012.0 3.0
1 556334538337 Narcotics 34.0 Male Black 2010-12-15 20:40:00 2011-01-31 2012-03-26 CHICAGO PD X X Finding Not Not Guilty 2010 12 2011.0 1.0 2012.0 3.0
2 556335439038 Narcotics 34.0 Male Black 2010-12-15 20:40:00 2011-01-31 2012-03-26 CHICAGO PD X X Finding Guilty 2010 12 2011.0 1.0 2012.0 3.0
3 555347861578 Narcotics 34.0 Male Black 2010-12-15 20:40:00 2011-01-31 2012-03-26 CHICAGO PD 2 2 Finding Not Not Guilty 2010 12 2011.0 1.0 2012.0 3.0
4 556336339739 Narcotics 34.0 Male Black 2010-12-15 20:40:00 2011-01-31 2012-03-26 CHICAGO PD 2 2 Finding Guilty 2010 12 2011.0 1.0 2012.0 3.0
In [46]:
merged_3 = merged_2[merged_2['felony_class_initiation'].isin(['X','1','2','3','4']) 
                    & merged_2['felony_class_disposition'].isin(['X','1','2','3','4'])]
In [47]:
merged_3 = merged_3[merged_3['gender'].isin(['Male','Female'])]
In [48]:
merged_3.shape
Out[48]:
(511726, 18)

In order to get a quantitative measure of changes in felony class as a defendant moved from initiation to disposition, I had to first convert the most serious class from the non-numeric X to the numeric 0. The other four felony classes were already in numeric form, ranging from 1 (the most serious after X) to 4 (the least serious). I then defined felony_class_difference as the change in felonly class value from initiation to dispositon. For example, a defendant moving from Class 0 to Class 3 would have a felony_class_difference of 3, while one moving from 2 to 3 would have felony_class_difference of 1.
Note: there were some defendents whose felony class actually became more severe (e.g. moved from a 1 to a 0 or a 3 to 2). I did not include these in the analysis because their numbers were small and not very statistically meaningful. However, they might be worth investigating in a later analysis.

In [49]:
merged_3['felony_class_initiation'] = merged_3['felony_class_initiation'].map({'X': 0,'1': 1,'2':2,'3':3, '4':4})
merged_3['felony_class_disposition'] = merged_3['felony_class_disposition'].map({'X': 0, '1': 1,'2':2,'3':3, '4':4})
In [50]:
merged_3['felony_class_difference'] = merged_3['felony_class_disposition'] - merged_3['felony_class_initiation']
In [51]:
merged_3['felony_class_difference'].value_counts()
Out[51]:
 0    491818
 3      7052
 1      5813
 2      5081
 4      1859
-1        79
-2        16
-3         6
-4         2
Name: felony_class_difference, dtype: int64
In [52]:
merged_3 = merged_3[merged_3['felony_class_difference'].isin([0,1,2,3,4])]
In [53]:
merged_3.head()
Out[53]:
CHARGE_ID type_of_crime age gender race arrest_date arraignment_date disposition_date law_enforcement_agency felony_class_initiation felony_class_disposition charge_disposition arrest_year arrest_month arraignment_year arraignment_month disposition_year disposition_month felony_class_difference
0 555449231356 Narcotics 34.0 Male Black 2010-12-15 20:40:00 2011-01-31 2012-03-26 CHICAGO PD 0 0 Finding Not Not Guilty 2010 12 2011.0 1.0 2012.0 3.0 0
1 556334538337 Narcotics 34.0 Male Black 2010-12-15 20:40:00 2011-01-31 2012-03-26 CHICAGO PD 0 0 Finding Not Not Guilty 2010 12 2011.0 1.0 2012.0 3.0 0
2 556335439038 Narcotics 34.0 Male Black 2010-12-15 20:40:00 2011-01-31 2012-03-26 CHICAGO PD 0 0 Finding Guilty 2010 12 2011.0 1.0 2012.0 3.0 0
3 555347861578 Narcotics 34.0 Male Black 2010-12-15 20:40:00 2011-01-31 2012-03-26 CHICAGO PD 2 2 Finding Not Not Guilty 2010 12 2011.0 1.0 2012.0 3.0 0
4 556336339739 Narcotics 34.0 Male Black 2010-12-15 20:40:00 2011-01-31 2012-03-26 CHICAGO PD 2 2 Finding Guilty 2010 12 2011.0 1.0 2012.0 3.0 0
In [54]:
merged_3.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 511623 entries, 0 to 533521
Data columns (total 19 columns):
CHARGE_ID                   511623 non-null int64
type_of_crime               511623 non-null object
age                         511623 non-null float64
gender                      511623 non-null object
race                        511623 non-null object
arrest_date                 511623 non-null datetime64[ns]
arraignment_date            511620 non-null datetime64[ns]
disposition_date            511620 non-null datetime64[ns]
law_enforcement_agency      511623 non-null object
felony_class_initiation     511623 non-null int64
felony_class_disposition    511623 non-null int64
charge_disposition          511623 non-null object
arrest_year                 511623 non-null int64
arrest_month                511623 non-null int64
arraignment_year            511620 non-null float64
arraignment_month           511620 non-null float64
disposition_year            511620 non-null float64
disposition_month           511620 non-null float64
felony_class_difference     511623 non-null int64
dtypes: datetime64[ns](3), float64(5), int64(6), object(5)
memory usage: 78.1+ MB
In [55]:
merged_3[['age','arraignment_year','disposition_year', 'arrest_year']]  = merged_3[['age','arraignment_year',
                                             'disposition_year', 'arrest_year']].fillna(0.0).astype(int)
In [56]:
merged_3 = merged_3[(merged_3.arraignment_year < 2019) & (merged_3.arraignment_year > 2010) ]
In [57]:
merged_3.arraignment_year.value_counts()
Out[57]:
2012    80496
2013    77175
2014    69931
2011    68333
2015    67146
2016    63834
2017    56430
2018    27963
Name: arraignment_year, dtype: int64
In [58]:
merged_3.race.value_counts()
Out[58]:
Black                               345126
White [Hispanic or Latino]           90957
White                                62637
HISPANIC                              5562
Asian                                 3396
White/Black [Hispanic or Latino]      2571
Unknown                                712
American Indian                        275
Biracial                                66
ASIAN                                    6
Name: race, dtype: int64
In [59]:
merged_3['race'] = merged_3['race'].str.lower()
In [60]:
merged_3.race.value_counts()
Out[60]:
black                               345126
white [hispanic or latino]           90957
white                                62637
hispanic                              5562
asian                                 3402
white/black [hispanic or latino]      2571
unknown                                712
american indian                        275
biracial                                66
Name: race, dtype: int64
In [61]:
merged_3 = merged_3[merged_3['race'].isin(['black',
                    'white [hispanic or latino]',
                    'white', 'hispanic','asian',
                    'white/black [hispanic or latino]'])]
In [62]:
grouped_by_race = merged_3.groupby(['arraignment_year', 'race']).felony_class_difference.mean()
grouped_by_race = grouped_by_race.unstack()
grouped_by_race
Out[62]:
race asian black hispanic white white [hispanic or latino] white/black [hispanic or latino]
arraignment_year
2011 0.015113 0.079843 0.017949 0.026043 0.029219 0.068182
2012 0.020566 0.077486 0.001932 0.021830 0.023990 0.069164
2013 0.030992 0.096725 0.012277 0.037012 0.027868 0.097187
2014 0.030449 0.116887 0.005900 0.035701 0.032773 0.064407
2015 0.030744 0.142322 0.012563 0.045153 0.039002 0.083333
2016 0.041126 0.134739 0.008734 0.049277 0.048006 0.078035
2017 0.050505 0.138108 0.007843 0.048788 0.056300 0.111446
2018 0.068702 0.128518 0.000000 0.034443 0.059218 0.080357
In [63]:
grouped_by_race.loc[:, :].plot()
plt.legend(bbox_to_anchor=(1.75, 1), loc='upper right',  ncol=1)


plt.plot(grouped_by_race['black'], marker='', color='black', linewidth=3, alpha=0.7)
plt.plot(grouped_by_race['white'], marker='', color='white', linewidth=3, alpha=1)
plt.plot(grouped_by_race['hispanic'], marker='', color='green', linewidth=2, alpha=1)

ax = plt.gca()
leg = ax.get_legend()
leg.legendHandles[1].set_color('black')
leg.legendHandles[3].set_color('white')

plt.ylabel('Felony class drop')
plt.title('Average felony class drop by year for every race')
plt.show()

Observations: Upon initial examination, one might think the system is biased towards Black people and against Hispanic people since the data shows Blacks and White/Black [Hispanic or Latino] having the largest average reductions in felony class from initiation to disposition and Hispanics having the least. However, this may in part be explained by the observation made in Part 1 above that Hispanics had much lower average felonly class averages at initiation than Black or White/Black [Hispanic or Latino} people. Since Hispanic defendants started with lesser charges, there was less room for them to move down during the plea bargain process. Asians were the one class that started out with among the most severe average charges yet had among the lowest average felony_class_drop of any of the racial groups. This does not prove bias, since it's possible that the circumstances of their individual cases could have warranted this, but it does suggest that further investigation may be warranted.

In [64]:
grouped_by_gender = merged_3.groupby(['arraignment_year', 'gender']).felony_class_difference.mean()
grouped_by_gender = grouped_by_gender.unstack()
In [65]:
grouped_by_gender.loc[:, :].plot()
plt.legend(bbox_to_anchor=(1, 1), loc='upper right',  ncol=1)
plt.ylabel('Felony class drop')
plt.title('Average felony class drop for each gender by year')
plt.show()

Observations: In recent years except for 2018, Female defendants have enjoyed larger average felony_class_drops than their Male counterparts, and this is despite the observation made in Part 1 that Females began with much less serious initial charges. Again, this does not prove bias, but it does warrant further investigation.

I created 5 age categories in order to create heatmap. I mapped each age category to a number from 0 to 6, e.g. 0 = teenager (13-18), 1 = young adult (18-25), 2 = adult (25-40), etc.

In [66]:
merged_3.loc[merged_3['age'] <= 18, 'age'] = 0
merged_3.loc[(merged_3['age'] > 18) & (merged_3['age'] <= 25), 'age'] = 1
merged_3.loc[(merged_3['age'] > 25) & (merged_3['age'] <= 40), 'age'] = 2
merged_3.loc[(merged_3['age'] > 40) & (merged_3['age'] <= 55), 'age'] = 3
merged_3.loc[(merged_3['age'] > 55) & (merged_3['age'] <= 70), 'age'] = 4
merged_3.loc[merged_3['age'] > 70, 'age'] = 5
In [67]:
grouped_by_age = merged_3.groupby(['arraignment_year', 'age']).felony_class_difference.mean()
grouped_by_age = grouped_by_age.unstack()
grouped_by_age
Out[67]:
age 0 1 2 3 4 5
arraignment_year
2011 0.053636 0.059574 0.060310 0.078270 0.052632 0.000000
2012 0.051674 0.056595 0.051032 0.083463 0.060284 0.000000
2013 0.071000 0.072409 0.066203 0.101502 0.090263 0.023438
2014 0.083896 0.086009 0.076539 0.121615 0.103290 0.000000
2015 0.068809 0.092985 0.101746 0.150611 0.172367 0.025806
2016 0.107406 0.087014 0.092549 0.172261 0.150034 0.065934
2017 0.069473 0.084065 0.097985 0.192342 0.235955 0.082051
2018 0.062745 0.073789 0.091295 0.184997 0.202733 0.000000

Seems like the most of the felony class drop occurs between the age 40 and 70, especially in recent years.

In [68]:
ax = plt.axes()
sns.heatmap(grouped_by_age,cmap="YlGnBu")

ax.set_title('Average felony class drop for each age rank by year')
plt.show()

Part 3: Analyzing types of crimes

In [83]:
merged_3.head()
Out[83]:
CHARGE_ID type_of_crime age gender race arrest_date arraignment_date disposition_date law_enforcement_agency felony_class_initiation felony_class_disposition charge_disposition arrest_year arrest_month arraignment_year arraignment_month disposition_year disposition_month felony_class_difference
0 555449231356 Narcotics 2 Male black 2010-12-15 20:40:00 2011-01-31 2012-03-26 CHICAGO PD 0 0 Finding Not Not Guilty 2010 12 2011 1.0 2012 3.0 0
1 556334538337 Narcotics 2 Male black 2010-12-15 20:40:00 2011-01-31 2012-03-26 CHICAGO PD 0 0 Finding Not Not Guilty 2010 12 2011 1.0 2012 3.0 0
2 556335439038 Narcotics 2 Male black 2010-12-15 20:40:00 2011-01-31 2012-03-26 CHICAGO PD 0 0 Finding Guilty 2010 12 2011 1.0 2012 3.0 0
3 555347861578 Narcotics 2 Male black 2010-12-15 20:40:00 2011-01-31 2012-03-26 CHICAGO PD 2 2 Finding Not Not Guilty 2010 12 2011 1.0 2012 3.0 0
4 556336339739 Narcotics 2 Male black 2010-12-15 20:40:00 2011-01-31 2012-03-26 CHICAGO PD 2 2 Finding Guilty 2010 12 2011 1.0 2012 3.0 0
In [84]:
merged_3['type_of_crime'].nunique()
Out[84]:
78
In [85]:
f, ax = plt.subplots(figsize=(18,5))

ax = merged_3['type_of_crime'].value_counts().plot(kind='bar')
plt.title('Breakdown by Type of Crime in Cook County from 2011 to 2018')
plt.xlabel('Type of Crime')
plt.ylabel('Number of Crime')
Out[85]:
Text(0, 0.5, 'Number of Crime')
In [86]:
merged_4 = merged_3
In [87]:
merged_4['felony_class_difference'] = merged_3.felony_class_difference.astype('object')
In [88]:
merged_4 = merged_3[merged_3['felony_class_difference'].isin([1,2,3,4])]
In [89]:
felony_charge_drop_by_race = pd.crosstab(index=merged_4['race'], columns=merged_4['felony_class_difference'], normalize='index')
felony_charge_drop_by_race
Out[89]:
felony_class_difference 1 2 3 4
race
asian 0.548387 0.209677 0.161290 0.080645
black 0.246428 0.259128 0.391073 0.103370
hispanic 0.638889 0.250000 0.083333 0.027778
white 0.552053 0.258065 0.157625 0.032258
white [hispanic or latino] 0.499725 0.235036 0.206480 0.058759
white/black [hispanic or latino] 0.400000 0.170000 0.350000 0.080000
In [76]:
felony_charge_drop_by_race.plot(kind="bar", figsize=(12,8), stacked=True)
plt.title('The Percentage of Each Felony Charge Drop by Race for 2011-2018')
plt.show()

Observations: Even if we mentioned in Part 2 that Hispanic people had the lowest initial felony classes, we notice from the plot above, they are still getting pretty significant felony drop. Black and White/Black[Hispanic or Latino] have been getting the highest felony drops among the rest.

In [90]:
by_crime = merged_3[merged_3['type_of_crime'].isin(['UUW - Unlawful Use of Weapon',
                                        'Narcotics', 'Aggravated DUI', 'Sex Crimes',
                                        'Driving With Suspended Or Revoked License'])]
In [91]:
by_crime = merged_3[merged_3['type_of_crime'].isin(['UUW - Unlawful Use of Weapon',
                                        'Narcotics', 'Aggravated DUI', 'Sex Crimes',
                                        'Driving With Suspended Or Revoked License'])]

Below I made the table using the highest crime types' values and showed their percentage by each race.

In [92]:
crime_table = pd.crosstab(index=by_crime['race'], columns=by_crime['type_of_crime'],normalize='index')
crime_table
Out[92]:
type_of_crime Aggravated DUI Driving With Suspended Or Revoked License Narcotics Sex Crimes UUW - Unlawful Use of Weapon
race
asian 0.182365 0.035404 0.308617 0.334669 0.138945
black 0.072519 0.072285 0.342414 0.048312 0.464469
hispanic 0.700340 0.149264 0.038505 0.027860 0.084032
white 0.286668 0.082454 0.337330 0.179335 0.114213
white [hispanic or latino] 0.344645 0.090591 0.146692 0.170394 0.247679
white/black [hispanic or latino] 0.133847 0.054660 0.280308 0.112824 0.418360
In [93]:
crime_table.plot(kind="bar", figsize=(12,8), stacked=True)
plt.title('The Percentage of Each Types of Crime by Race for 2011-2018')
plt.show()

Observations: It seems there is a similar pattern between Black and White/Black[Hispanic or Latino] peoples' crime types. Both races lead on UUW following by narcotics. Asian people have the highest record on Sex Crimes.

Idea for Further Improvements to Analysis

The aspect of the work done by the Cook County Attorney's Office that most impacts a defendant is what sentence that person ultimately receives at disposition. It would be very interesting to repeat the above analysis but replacing felony_class_difference as the dependent variable with something like "actual_sentence_versus_expectations", which I would define as follows:

  1. find the average sentence given in Cook County for all defendants convicted of a given crime during the period (from 2011 to 2018, say) and call this the "expected_sentence".
  2. add a column to the database that shows what sentence each defendant that plead guilty actually received at disposition. Call this the "actual_sentence". I am sure this data exists but wasn't sure where to find it.
  3. for each defendant who took a plea bargain during the period, subtract the actual_sentence they received at disposition from the expected_sentence for the most serious crime they were charged with at initiation to get the actual_sentence_versus_expectations for that defendant.

After seeing the data, I may want to make some further analyses related to the above. For example, for defendants with multiple charges against them, it might also be interesting to add up the expected sentences for all the charges against them at initiation to get a "total_expected_sentence" at initiation, then compare this to the actual_sentence in the same manner. It might also be necessary to make some adjustments to the data to avoid distortions. For example, if a defendant is sentenced to multiple lifetimes, I might treat that as one lifetime of say 60 years or something since, in terms of what it actually means to a defendant, one lifetime is no different than 10 or a thousand.

  1. I would then look at how the various independent variables (race, gender, age) affect the actual_sentence_versus_expectations.