This dataset comes from the National Instant Criminal Background Check System (NICS). It covers the different types of firearm transactions reported to the NICS for review. There are several types of transaction types, but for my analysis I focused on pre-pawn transactions (those requesting a background check before allowing someone to pawn a gun) and return to seller transactions (those who wish to obtain a firearm from a third party seller that deos not have an official firearms liscense. )
First, I wanted to investigate if there were any inconsistencies throughout the months when it came to requests for background checks for those looking to pawn handguns. I thought that there may be some months that had significantly more requests than others. However, it appears the monthly trend across the years does not deviate much. The significant changes can be seen when just looking at the years. What sticks out most are the years 2000-2002. The only months prevalent for these years are October, November, and December. This combined with the overall huge spike could indicate that around November of 2000 is when a policy was either introduced or heavily enforced regarding the necessity for background checks before pawning handguns.
import os
os.environ['OT_QPA_PLATFORM_PLUGIN_PATH'] = '/Applications/anaconda3/plugins/platforms'
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import warnings
from matplotlib.ticker import FuncFormatter
warnings.filterwarnings("ignore")
path = "/Users/cailynwilliams/Downloads/"
filename = "firearm.csv"
df = pd.read_csv(path + filename, usecols = ['state','prepawn_handgun', 'month'])
# check for NA values and replace them
df.isna().sum()
df.prepawn_handgun.fillna(0, inplace = True )
# format the month and year properly
df['month'] = pd.to_datetime(df['month'], format = '%Y-%m')
df['year'] = df.month.dt.year
# isolate the actual month from "month" variable
df['Month'] = df.month.dt.month
guns_df = df.groupby(['Month', 'year'])['prepawn_handgun'].sum().reset_index(name = 'TotalPrepawnedHandguns')
# multi line plot
fig = plt.figure(figsize = (18,10))
ax = fig.add_subplot(1,1,1)
my_colors = {(1,) : 'blue',
(2,) : 'red',
(3,) : 'green',
(4,) : 'pink',
(5,) : 'purple',
(6,) : 'orange',
(7,) : 'yellow',
(8,) : 'coral',
(9,) : 'salmon',
(10,) : 'deeppink',
(11,) : 'darkorchid',
(12,) : 'springgreen',
}
for key, grp in guns_df.groupby(['Month']):
grp.plot(ax=ax, kind='line',x = 'year', y='TotalPrepawnedHandguns', color = my_colors[key], label = key, marker = 8)
plt.title('Total Requests for Background Checks for Prepawned Handguns', fontsize = 20)
ax.set_xlabel('Year', fontsize = 18)
ax.set_ylabel('Total Requests', fontsize = 18, labelpad = 20)
ax.tick_params(axis = 'x', labelsize = 14, rotation = 0)
ax.tick_params(axis = 'y', labelsize = 14, rotation = 0)
Next, I wanted to investigate any potential trends in the requests for background checks to return handguns to sellers. The most apparent thing demonstrated by the stacked barplot is that from the years 1998-2013 there were no reports. This indicates that 2014 marked either the introduction or enforcement of policy regarding background checks in this instance. Next it can be observed that 2021 had the most request for background checks. However, like in the case of the scatterplot, the background checks per month seem to be pretty evenly distributed.
df = pd.read_csv(path + filename, usecols = ['state','return_to_seller_handgun', 'month'])
df.isna().sum()
df.return_to_seller_handgun.fillna(0, inplace = True)
# format the month and year properly
df['month'] = pd.to_datetime(df['month'], format = '%Y-%m')
df['year'] = df.month.dt.year
# isolate the actual month from "month" variable
df['Month'] = df.month.dt.month
stacked_df = df.groupby(['Month', 'year'])['return_to_seller_handgun'].sum().reset_index(name = 'TotalReturnHandguns')
stacked_df = stacked_df.pivot(index='year', columns='Month',values='TotalReturnHandguns')
fig = plt.figure(figsize=(18,10))
ax = fig.add_subplot(1,1,1)
stacked_df.plot(kind='bar', stacked=True, ax=ax)
plt.ylabel('Total Background Check Requests (to return handgun)', fontsize = 18, labelpad = 10)
plt.title('Total Background Checks Requested to Return Handgun to Seller \n Stacked Bar Plot', fontsize = 18)
ax.set_xlabel('Year', fontsize = 18)
plt.show()
It struck me that there was a lack of seasonal patterns/trends of background checks for handguns in my previous observations, so I decided to utilize a piechart to demonstrate this finding. I divided the year up into quarters. As it is seen, there is a fairly even division among the requests for background checks to return handguns to sellers among the quarters. Once again this affirms that there are no seasonal patterns here.
# donut chart
df['MonthName'] = df['month'].dt.strftime('%B')
df['Quarter'] = 'Quarter' + df.month.dt.quarter.astype('string')
pie_df = df.groupby(['Quarter', 'MonthName','Month'])['return_to_seller_handgun'].sum().reset_index(name='TotalRequests')
pie_df.sort_values(by=['Month'], inplace=True)
pie_df.reset_index(inplace = True, drop =True)
del pie_df['Month']
num_out_colors = len(pie_df.Quarter.unique())
outside_color_ref_number = np.arange(num_out_colors)*4
fig = plt.figure(figsize=(10,10))
ax = fig.add_subplot(1,1,1)
colormap = plt.get_cmap("tab20c")
outer_colors = colormap(outside_color_ref_number)
all_reqs = pie_df.TotalRequests.sum()
pie_df.groupby(['Quarter'])['TotalRequests'].sum().plot(
kind='pie', radius=1, colors=outer_colors, pctdistance=0.85, labeldistance = 1.1,
wedgeprops = {'linewidth':3},textprops={'fontsize':18},
autopct = lambda p: '{:.2f}%\n({:.0f})'.format(p,(p/100)*all_reqs),
startangle=90)
hole = plt.Circle((0,0), radius = 0.4, fc = 'white')
fig1 = plt.gcf()
fig1.gca().add_artist(hole)
ax.yaxis.set_visible(False)
plt.title('Total Requests for Background Checks \n to Return Handgun to Seller By Quarter ', fontsize = 18)
ax.text(0,0, 'Total Requests\n' + str(all_reqs), size = 18, ha = 'center', va = 'center')
ax.axis('equal')
plt.tight_layout()
plt.show()
Just to make sure, I wanted to display the months along with the quarter to also show how this lack of variance also applied to monthly trends. As shown, the average requests for each month hardly deviate from one another. This could indicate that becuase this is NICS data, those who have an official Federal Fireamrm Liscence are consistent with their requests for background checks as there is a lot at stake if they don’t comply. It also indicates that Firearms in America are in a someone consistent demand, so the requests for background checks reflect that nature.
# nested donut chart
num_inside_colors = len(pie_df.MonthName.unique())
all_color_ref_number = np.arange(num_out_colors + num_inside_colors)
inside_color_ref_num = []
for each in all_color_ref_number:
if each not in outside_color_ref_number:
inside_color_ref_num.append(each)
inner_colors = colormap(inside_color_ref_num)
fig = plt.figure(figsize=(10,10))
ax = fig.add_subplot(1,1,1)
pie_df.groupby(['Quarter'])['TotalRequests'].sum().plot(
kind='pie', radius=1, colors=outer_colors, pctdistance=0.85, labeldistance = 1.1,
wedgeprops = {'linewidth':3},textprops={'fontsize':18},
autopct = lambda p: '{:.2f}%\n({:.0f})'.format(p,(p/100)*all_reqs),
startangle=90)
pie_df.TotalRequests.plot(
kind='pie', radius=0.65, colors=inner_colors, pctdistance=0.55, labeldistance = 0.8,
wedgeprops = {'linewidth':3},textprops={'fontsize':12.5},
labels = pie_df.MonthName,
autopct = '%1.2f%%',
startangle=90)
hole = plt.Circle((0,0), radius = 0.3, fc = 'white')
fig1 = plt.gcf()
fig1.gca().add_artist(hole)
ax.yaxis.set_visible(False)
plt.title('Total Requests for Background Checks\n to Return to Seller by Quarter and Month', fontsize = 18)
ax.text(0,0, 'Total Requests\n' + str(all_reqs), size = 18, ha = 'center', va = 'center')
ax.axis('equal')
plt.tight_layout()
plt.show()
For this bumpchart, I wanted to analyze the years after 2013 for background check requests to return handguns to sellers. Here, it can be seen that 2014 was consistently low for background check requests. February of 2018 did not have as many background check requests, but that changed in september when the amount of requests peaked. 2016 and 2019 both appear to have relatively consistent downward trends.
# create bump chart dataframe (10 year period for Return to Seller handgun)
# drop years until 2014
bump_df = df.groupby(['year', 'MonthName'])['return_to_seller_handgun'].sum().reset_index(name = 'TotalRequests')
bump_df = bump_df.pivot(index='year', columns = 'MonthName', values='TotalRequests')
month_order = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']
bump_df = bump_df.reindex(columns=month_order)
bump_df = bump_df.dropna()
bump_df = bump_df.drop(bump_df.index[0:15])
bump_df_ranked = bump_df.rank(0,ascending=False, method='average')
bump_df_ranked = bump_df_ranked.T
fig = plt.figure(figsize=(18,10))
ax = fig.add_subplot(1,1,1)
bump_df_ranked.plot(kind='line', ax=ax, marker='o', markeredgewidth=1, linewidth=6,
markersize=18,
markerfacecolor='white')
ax.invert_yaxis()
num_rows = bump_df_ranked.shape[0]
num_cols = bump_df_ranked.shape[1]
plt.ylabel('Monthly Ranking', fontsize=18, labelpad=10)
plt.title('Ranking of Total Background Checks Requested to Return to Seller\n By Month and Year Bump Chart', fontsize=16.7,pad=15)
plt.xticks(np.arange(num_rows),month_order, fontsize=13.5)
plt.yticks(range(1, num_cols+1,1), fontsize=13.5)
ax.set_xlabel('Month', fontsize=18)
handles, labels = ax.get_legend_handles_labels()
handles = [ handles[8],handles[7],handles[6],handles[5],handles[4],handles[3],handles[2],handles[1],handles[0]]
labels = [ labels[8],labels[7],labels[6],labels[5],labels[4],labels[3],labels[2],labels[1],labels[0]]
ax.legend(handles,labels, bbox_to_anchor=(1.01,1.01),fontsize=14)
plt.show()