Реформа полиции - огромная тема в наши дни, особенно когда мы приближаемся к президентским выборам с двумя кандидатами, которые, похоже, придерживаются совершенно разных идеологий по этому вопросу. Тема, которую потенциально можно было бы рассмотреть при рассмотрении реформы полиции, - это Терри Стопс. В 1968 году Верховный суд постановил, что, если у офицера есть разумные подозрения, что лицо совершило, совершает или собирается совершить преступление, он не нарушает запрет Четвертой поправки на необоснованные обыски и выемки, если они решат прекратить, расследовать и даже обыскивать подозреваемых, чтобы определить, вооружены ли они. Как вы понимаете, расовое профилирование потенциально может стать огромной проблемой и уже давно является предметом обсуждения. Вместо того, чтобы сосредотачиваться на расовой принадлежности, моей целью было спрогнозировать максимальное количество арестов в результате действий Терри Стопса на момент возникновения первоначальных подозрений. Хотя расовая принадлежность не будет основной темой этой статьи, в результате работы модели будут освещены некоторые демографические статистические данные.

В моем процессе использовался итеративный подход к построению модели классификации на Python для прогнозирования арестов в результате Терри Стопса, с использованием данных Терри Стопа в Сиэтле, штат Вашингтон, с отчетами, начиная с 2015 года. Описание данных взято непосредственно из https: //www.seattle.gov/police/information-and-data/terry-stops и гласит:« [Данные включают] контакты между полицией и гражданскими лицами, которые включают остановку и ограниченное задержание человека (Терри Стоп). В соответствии с Terry v. Ohio, 392 U.S. 1 (1968), такие контакты разрешены в соответствии с законодательством и политикой в ​​целях расследования, основанного на разумных подозрениях сотрудника, в отношении того, занимается ли данное лицо преступной деятельностью, участвовал или собирается участвовать. Во время остановки Терри офицер может разработать вероятную причину для осуществления ареста, но вероятная причина не требуется для первоначальной остановки, и остановка, основанная на вероятной причине для ареста, не попадает в категорию Терри. останавливаться."

В этом проекте использовался подход OSEMN, описанный ниже:

Получить данные

Данные для этого проекта были получены непосредственно из https://catalog.data.gov/dataset/terry-stops, загружены в виде файла csv и импортированы в фреймворк pandas. Описания столбцов доступны в репозитории github для этого проекта в виде файла md.

df = pd.read_csv('data/Terry_Stops.csv')

Очистить данные

Очистка данных включала удаление повторяющихся записей, заполнение отсутствующих значений, определение того, следует ли сохранять или заменять значения заполнителей, и преобразование в соответствующие типы данных. Ниже приведены несколько примеров кода:

#Change Stop Resolution to binary: arrested or not
df['Stop Resolution'] = df['Stop Resolution'].replace('Arrest', 1.0)
df['Stop Resolution'] = df['Stop Resolution'].map(lambda x: 0.0 if (x != 1.0) else 1.0)
#Split time from date and keep date in df
df['Reported Date'] = df['Reported Date'].str.split('T').str[0]
#Convert to datetime data type
df['Reported Date'] = pd.to_datetime(df['Reported Date'])
#Split date into multiple columns
df['Reported Year'] = df['Reported Date'].dt.year
df['Reported Month'] = df['Reported Date'].dt.month
df['Reported Day'] = df['Reported Date'].dt.day

Изучить данные

Этот шаг включал построение графика всех функций в зависимости от целевой переменной, Stop Resolution, для выполнения дополнительных необходимых функций, одной категориальной переменной горячего кодирования и получения общего понимания того, какие функции следует включить в наше моделирование. Пример разрешения ударов и стопов приведен ниже. Удар - это адрес, связанный с базовым событием автоматизированной диспетчеризации (CAD), который был преобразован в двоичный столбец, независимо от того, сообщалось об ударе или нет.

Данные модели

Подход к моделированию заключался в использовании нескольких различных алгоритмов классификации и сравнении оценок, затем реализации алгоритма Boruta для выбора функций и использования GridSearchCV с настройкой гиперпараметров для увеличения нашей оценки. Основное внимание в этом проекте уделялось оценке отзыва / чувствительности (измерение доли правильно идентифицированных положительных результатов) с конечной целью прогнозирования арестов при минимизации ложноотрицательных результатов.

Используются следующие алгоритмы: логистическая регрессия, полиномиальный байесовский анализ, деревья решений, случайный лес, сбалансированный случайный лес, сбор с деревьями решений, сбалансированный сбор с деревьями решений, AdaBoost и XGBoost. Чтобы упростить этот процесс, я создал функцию, которая будет строить модель классификации для каждого алгоритма, возвращать отчет о классификации, распечатывать поезд и точность набора тестов и строить матрицу путаницы, чтобы просмотреть наши общие истинные положительные результаты, истинные отрицательные результаты, ложные положительные результаты и ложные негативы. Функция также создала фрейм данных pandas с результатами модели.

#Split features and target
X = df.drop(columns=['Stop Resolution'], axis=1)
y = df['Stop Resolution']
#Create function to easily build models and display results
def build_model(classifier, predictors, labels):
    '''Build classification model, returning classification report, train and test accuracy, and confusion matrix.
    
    Keyword arguments: 
    classifier -- classification algorithm
    predictors -- X, features
    labels -- y, target
    '''
    
    #Split the data into train and test sets
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=.20, random_state=123)
    
    #Scale the data
    scaler = MinMaxScaler()
    X_train = scaler.fit_transform(X_train)
    X_test = scaler.transform(X_test)
    
    #Create variables that can be called outside function
    build_model.X_train = X_train
    build_model.X_test = X_test
    build_model.y_train = y_train
    build_model.y_test = y_test
    
    #Fit model
    model = classifier.fit(X_train, y_train)
    score = np.mean(cross_val_score(classifier, X, y, cv=5, scoring='recall'))
    
    #Create predictions
    y_hat_train = model.predict(X_train)
    y_hat_test = model.predict(X_test)
    
    #Create variables that can be called outside function
    build_model.y_hat_train = y_hat_train
    build_model.y_hat_test = y_hat_test
    
    #Print classification report
    print('Train Classification Report\n', classification_report(y_train, y_hat_train))
    print('\n----------------------------------\n')
    print('Test Classification Report\n', classification_report(y_test, y_hat_test))
    print('\n----------------------------------\n')
    
    print('Train Accuracy:', round(accuracy_score(y_train, y_hat_train), 3))
    print('Test Accuracy:', round(accuracy_score(y_test, y_hat_test), 3))
    print('Cross Validation Recall', round(score, 3))
    print('\n----------------------------------\n')
    
    #Create a results dataframe
    results = pd.DataFrame([[str(model), round(accuracy_score(y_train, y_hat_train), 3), 
                             round(accuracy_score(y_test, y_hat_test), 3), round(score, 3)]], 
                          columns=['Model', 'Train_Accuracy', 'Test_Accuracy', 'Cross_Val_Recall'])
    build_model.results = results
    
    #Plot Confusion Matrices
    fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(14, 8))
    plot_confusion_matrix(classifier, X_train, y_train,
                     cmap=plt.cm.Blues, ax=axes[0])
    fig.suptitle('Train & Test Confusion Matrices', fontsize=16);
    
    plot_confusion_matrix(classifier, X_test, y_test,
                     cmap=plt.cm.Blues, ax=axes[1])
    plt.subplots_adjust(wspace=0.4)
    return model
#Create function to easily build models and display results
def build_model(classifier, predictors, labels):
    '''Build classification model, returning classification report, train and test accuracy, and confusion matrix.
    
    Keyword arguments: 
    classifier -- classification algorithm
    predictors -- X, features
    labels -- y, target
    '''
    
    #Split the data into train and test sets
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=.20, random_state=123)
    
    #Scale the data
    scaler = MinMaxScaler()
    X_train = scaler.fit_transform(X_train)
    X_test = scaler.transform(X_test)
    
    #Create variables that can be called outside function
    build_model.X_train = X_train
    build_model.X_test = X_test
    build_model.y_train = y_train
    build_model.y_test = y_test
    
    #Fit model
    model = classifier.fit(X_train, y_train)
    score = np.mean(cross_val_score(classifier, X, y, cv=5, scoring='recall'))
    
    #Create predictions
    y_hat_train = model.predict(X_train)
    y_hat_test = model.predict(X_test)
    
    #Create variables that can be called outside function
    build_model.y_hat_train = y_hat_train
    build_model.y_hat_test = y_hat_test
    
    #Print classification report
    print('Train Classification Report\n', classification_report(y_train, y_hat_train))
    print('\n----------------------------------\n')
    print('Test Classification Report\n', classification_report(y_test, y_hat_test))
    print('\n----------------------------------\n')
    
    print('Train Accuracy:', round(accuracy_score(y_train, y_hat_train), 3))
    print('Test Accuracy:', round(accuracy_score(y_test, y_hat_test), 3))
    print('Cross Validation Recall', round(score, 3))
    print('\n----------------------------------\n')
    
    #Create a results dataframe
    results = pd.DataFrame([[str(model), round(accuracy_score(y_train, y_hat_train), 3), 
                             round(accuracy_score(y_test, y_hat_test), 3), round(score, 3)]], 
                          columns=['Model', 'Train_Accuracy', 'Test_Accuracy', 'Cross_Val_Recall'])
    build_model.results = results
    
    #Plot Confusion Matrices
    fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(14, 8))
    plot_confusion_matrix(classifier, X_train, y_train,
                     cmap=plt.cm.Blues, ax=axes[0])
    fig.suptitle('Train & Test Confusion Matrices', fontsize=16);
    
    plot_confusion_matrix(classifier, X_test, y_test,
                     cmap=plt.cm.Blues, ax=axes[1])
    plt.subplots_adjust(wspace=0.4)
    return model

Интерпретация данных

Используя функции, выбранные из алгоритма Борута, и подгоняя модель с помощью алгоритма XGBoost с настройкой гиперпараметров, результат модели имел оценку отзыва 95,5% при сохранении примерно 50% точности в целом как для обучающих, так и для тестовых данных. В конечном итоге моя цель состояла в том, чтобы сосредоточиться на отзыве и спрогнозировать максимальное количество арестов наряду с минимизацией ложноотрицательных результатов, в то же время поддерживая точность выше 50%. Этот результат потенциально может способствовать сокращению количества ненужных Терри Стопов.

Этот результат потенциально может способствовать сокращению количества ненужных Терри Стопсов.

Окончательные характеристики, определяющие результаты нашей модели, приведены ниже, причем Beat является нашей характеристикой, имеющей наивысшее значение. Интересен тот факт, что в большинстве случаев аресты не заканчиваются, если Computer Aided Dispatch не используется и адрес (такт), связанный с Computer Aided Dispatch, не сообщается. Это приводит к дальнейшему исследованию того, как сообщается об этих событиях и почему не используется CAD.

  • Бит (независимо от того, был ли зарегистрирован адрес, связанный с базовым событием Computer Aided Dispatch (CAD))
  • Тип начального вызова (независимо от того, был ли сообщен тип начального вызова)
  • Call Type_911 (звонок поступил в центр связи по номеру 911)
  • Офицер YOB
  • Отчетный год

Наконец, я построил функцию для тестирования модели:

def predict_arrests():
    '''Predict whether a stop resolution will result in an arrest using previously built classification model.
    
    Parameters: Input by User'''
    
    Beat = input("Was the address (beat) associated with the underlying Computer Aided Dispatch (CAD) event reported ? (1=yes, 0=no) ")
    Initial_Call_Type = input("Was an initial call type reported? (1=yes, 0=no) ")
    Call_Type_911 = input("Was the report of suspiscion a 911 call? (1=yes, 0=no) ")
    Call_Type_Other = input("Was the report of suspiscion an alternative to a 911 call or onsite viewing? (1=yes, 0=no) ")
    Officer_YOB = input("What year was the officer born? ")
    Reported_Year = input("What is the reported year? ")
    Reported_Minute = input("What is the reported minute? (1 to 59) ")
    
    features = np.array([[Beat, Initial_Call_Type, Call_Type_911, Call_Type_Other, Officer_YOB, Reported_Year, 
                        Reported_Minute]], dtype=float)
    
    result = xg_model.predict(features)
    if result == 1:
        answer = 'Yes'
    else:
        answer = 'No'
    
    print("\nShould officer conduct a Terry Stop? ")
    return answer

Один вопрос, который возник в результате этой модели, заключался в надежности процесса отчетности для Терри Стопса. Снова посмотрите график ниже, посвященный разрешению ударов и остановок.

Из 9 523 случаев, когда отчетность в той или иной форме отсутствовала, было произведено только 234 ареста. Поскольку в 97,5% этих случаев аресты не завершились, необходима дополнительная информация о том, почему были проведены эти Терри Стопы. Эта цитата взята непосредственно из http://www.seattle.gov/Documents/Departments/Tech/Privacy/SPD%20Computer%20Aided%20Dispatch%20Final%20SIR.pdf: Система CAD выполняет две основные функции. : инициировать и регистрировать соответствующий ответ полиции, а также задокументировать назначение и реакцию правильных полицейских ресурсов. CAD - это система учета в реальном времени ответов офицеров на обращения в службу поддержки, тем самым документируя действия SPD, связанные с каждым из этих запросов, в организованном и отчетном методе . Остается вопрос, почему информация об адресе не сообщается примерно в каждом пятом случае? CAD не используется?

В результате сомнительных сообщений я также хотел посмотреть, какова разбивка субъектной расы с случаями, когда отсутствует адресная информация.

Интересно отметить, что 53% этих случаев с незарегистрированной информацией - это белые субъекты, что на 15% меньше, чем по данным государственной переписи белого населения Сиэтла (68%). Кроме того, 26% этих случаев с незарегистрированной информацией относятся к темнокожим подданным, что на 19% выше, чем по данным государственной переписи чернокожего населения Сиэтла (7%).

Другой вопрос: есть ли офицеры с повторными случаями несообщаемой информации?

Интересно видеть, что есть несколько (19) офицеров, у которых есть более 50 случаев, когда информация не была передана в результате Терри Стопа.

Выводы о расовом неравенстве будут теоретическими только на основе этих данных, поэтому вместо того, чтобы давать теоретический вывод, я рекомендую, чтобы Департамент полиции Сиэтла уменьшил вопросы расового неравенства, улучшив отчетность в этих случаях, будь то требование к офицерам сообщать эту информацию или автоматизация обрабатывает дальше с помощью Computer Aided Dispatch. Дальнейшее расследование может включать изучение того, почему происходит так много событий, информация о которых не сообщается.

Полная информация об этом проекте доступна в репозитории GitHub: https://github.com/dbarth411/dsc-mod-3-project-v2-1-online-ds-sp-000. Я приветствую любые комментарии, вопросы и предложения / рекомендации, связанные с этим проектом.

В настоящее время я ищу аналитика данных или связанные должности, поэтому, если вы нанимаете или знаете какие-либо связи, пожалуйста, проверьте или поделитесь веб-сайтом моего портфолио.