The dataset used for this analysis contains information about reported crime incidents in Baltimore, Maryland. The dataset includes several categorical and numerical variables that provide details about each incident, including the date, time, crime description, crime code, weapon, age, gender, and more. Before creating the visualizations, I examined the structure of the dataset and focused on identifying patterns in reported crime based on the time of the incident, type of crime, and recorded age information. I also examined how crime incidents were distributed across different months, years, quarters, hours of the day, and days of the week. The variety of variables in the dataset allowed me to create visualizations that compare crime frequency, explore changes in crime activity over time, and examine differences in the recorded information across crime descriptions.
import pandas as pd;
import numpy as np;
import matplotlib.pyplot as plt;
import warnings;
warnings.filterwarnings("ignore");
path = "/Users/emimeco/Desktop/Data Visualizations/Python/";
filename = path + "BaltimoreCrimeData.csv";
df = pd.read_csv(filename, usecols = ['CrimeDateTime']);
df['CrimeDateTime'] = pd.to_datetime(df['CrimeDateTime'], format = '%m/%d/%Y %I:%M:%S %p');
df['Quarter'] = df['CrimeDateTime'].dt.quarter;
df['MonthNameAbbrev'] = df['CrimeDateTime'].dt.strftime('%b');
quarter_df = df.groupby(['Quarter'])['Quarter'].count().reset_index(name='Count');
quarter_df['Quarter'] = quarter_df['Quarter'].replace({1: 'Quarter 1', 2: 'Quarter 2', 3: 'Quarter 3', 4: 'Quarter 4'});
number_outside_colors = len(quarter_df.Quarter.unique());
outside_color_ref_number = np.arange(number_outside_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);
crime_count = quarter_df.Count;
ax.pie(crime_count, labels=quarter_df.Quarter, radius=1, colors=outer_colors, pctdistance=0.85, labeldistance=1.1, wedgeprops=dict(edgecolor='white'), textprops={'fontsize':18}, autopct=lambda p: '{:.2f}%\n({:.0f})'.format(p, p * sum(crime_count) / 100), startangle=90);
hole = plt.Circle((0,0), 0.3, fc='white');
fig1 = plt.gcf();
fig1.gca().add_artist(hole);
ax.yaxis.set_visible(False);
plt.title('Crime Incident Count by Quarter', fontsize=18);
ax.text(0, 0, 'Total Crimes\n' + str(crime_count.sum()), size=18, ha='center', va='center');
ax.axis('equal');
plt.tight_layout();
plt.show()
This donut chart shows the number and percentage of reported crime
incidents across the four quarters of the year. It compares crime
activity throughout the year and identify which quarters had the highest
and lowest number of incidents. The total number of incidents is also
displayed in the center of the chart.
df = pd.read_csv(filename, usecols = ['Description']);
crime_type_frequency = df.groupby(['Description'])['Description'].count().reset_index(name='Count');
crime_type_frequency = pd.DataFrame(crime_type_frequency);
crime_type_frequency = crime_type_frequency.sort_values('Count', ascending=True);
crime_type_frequency = crime_type_frequency.reset_index(drop=True);
colors = plt.colormaps['cividis'];
fig = plt.figure(figsize=(20,12));
ax1 = fig.add_subplot(1, 1, 1);
ax1.barh(crime_type_frequency.Description, crime_type_frequency.Count, color=colors(np.linspace(0, 1, len(crime_type_frequency))));
ax1.set_title('Frequency of Crime Types in Baltimore', size=20);
ax1.set_xlabel('Crime Incident Count', fontsize=16, labelpad=10);
ax1.set_ylabel('Types of Crime', fontsize=16, labelpad=20);
plt.xticks(fontsize=14);
plt.yticks(fontsize=14);
for row_counter, value_at_row_counter in enumerate(crime_type_frequency.Count):
ax1.text(value_at_row_counter+1000, row_counter, str(value_at_row_counter), color='black', size=12, fontweight='bold',ha='left', va='center');
plt.xlim(0,crime_type_frequency.Count.max()*1.1);
plt.show()
This horizontal bar chart displays the frequency of each reported crime
type in the dataset. The crime types are organized from the highest to
the lowest number of incidents to show how frequently different crimes
were reported and which crimes are most common in Baltimore.
df = pd.read_csv(filename, usecols = ['CrimeDateTime']);
df['CrimeDateTime'] = pd.to_datetime(df['CrimeDateTime'], format = '%m/%d/%Y %I:%M:%S %p');
df['Year'] = df['CrimeDateTime'].dt.year;
df['Month'] = df['CrimeDateTime'].dt.month;
df = df[ df['Month'] != 0];
x = df.groupby(['Year', 'Month'])['Year'].count().reset_index(name='count');
x = pd.DataFrame(x);
x = x.loc[ ~x['Year'].isin(range(2014)) ];
x = x.reset_index(drop=True);
plt.figure(figsize=(18,10));
plt.scatter(x['Month'], x['Year'], marker = '8', cmap = 'viridis', c=x['count'], s=x['count'] * 0.3, edgecolor='black');
plt.title('Baltimore Crime Incidents by Month & Year', fontsize=18);
plt.xlabel('Months of the Year', fontsize=14);
plt.ylabel('Year', fontsize=14, labelpad=15);
cbar = plt.colorbar();
cbar.set_label('Crime Incident Count', rotation=270, fontsize=14, color='black', labelpad=30);
my_x_ticks = [*range(x['Month'].min(), x['Month'].max()+1, 1)];
plt.xticks(my_x_ticks, fontsize=14, color='black');
my_y_ticks = [*range(x['Year'].min(), x['Year'].max()+1, 1)];
plt.yticks(my_y_ticks, fontsize=14, color='black');
plt.show()
This scatterplot chart compares crime incident counts by month and year.
Each circle represents the number of incidents recorded during a
different month and year. The size and color of each circle represents
the crime count, with larger circles indicating more incidents. This
shows changes in crime activity over time and which months typically
have higher incident counts.
df = pd.read_csv(filename, usecols = ['Description', 'Age']);
df = df[df['Age'].notna()];
CrimeAvgAge = df.groupby(['Description']).agg(AverageAge=('Age', 'mean'), Count=('Description', 'count')).round().reset_index();
CrimeAvgAge = CrimeAvgAge.sort_values('Count', ascending=False);
CrimeAvgAge = CrimeAvgAge.reset_index(drop=True);
fig = plt.figure(figsize = (22,18));
ax1 = fig.add_subplot(1, 1, 1);
ax2=ax1.twinx();
bar_width = 0.4;
x_pos = np.arange(13);
count_bars = ax1.bar(x_pos-(0.5*bar_width), CrimeAvgAge.Count, bar_width, color='gray', edgecolor='black', label='Crime Incident Count');
aver_age_bars = ax2.bar(x_pos+(0.5*bar_width), CrimeAvgAge.AverageAge, bar_width, color='green', edgecolor='black', label='Average Age');
ax1.set_xlabel('Types of Crime', fontsize=18);
ax1.set_ylabel('Crime Incident Count', fontsize=18, labelpad=20);
ax2.set_ylabel('Average Age', fontsize=18, rotation = 270, labelpad=20);
ax1.tick_params(axis='y', labelsize=14);
ax2.tick_params(axis='y', labelsize=14);
plt.title('Crime Types by Incident Count & Average Age', fontsize=18);
ax1.set_xticks(x_pos);
ax1.set_xticklabels(CrimeAvgAge.Description, fontsize=14, rotation=45);
count_color, count_label = ax1.get_legend_handles_labels();
fine_color, fine_label = ax2.get_legend_handles_labels();
legend = ax1.legend(count_color + fine_color, count_label + fine_label, loc='upper right', frameon=True, ncol=1, shadow=True, borderpad=1, fontsize=14);
ax2.set_ylim(0, CrimeAvgAge.AverageAge.max()*1.50);
plt.show()
This visualization compares the number of crime incidents and the
average recorded age associated with each crime description. It uses two
different axes to display both. This chart shows if there is a
correlation with the types of crime being committed and the ages of the
people committing them.
df = pd.read_csv(filename, usecols = ['CrimeDateTime']);
df['CrimeDateTime'] = pd.to_datetime(df['CrimeDateTime'], format = '%m/%d/%Y %I:%M:%S %p');
df['Hour'] = df['CrimeDateTime'].dt.hour;
df['Day'] = df['CrimeDateTime'].dt.day;
df['Month'] = df['CrimeDateTime'].dt.month;
df['Year'] = df['CrimeDateTime'].dt.year;
df['WeekDay'] = df['CrimeDateTime'].dt.strftime('%a');
df['MonthName'] = df['CrimeDateTime'].dt.strftime('%b');
crime_df = df.groupby(['Hour', 'WeekDay'])['Hour'].count().reset_index(name='count');
fig = plt.figure(figsize = (18,10));
ax = fig.add_subplot(1, 1, 1);
my_colors = {'Mon':'blue',
'Tue':'red',
'Wed':'green',
'Thu':'grey',
'Fri':'purple',
'Sat':'gold',
'Sun':'pink'};
for key, grp in crime_df.groupby('WeekDay'):
grp.plot(ax=ax, kind='line', x='Hour', y = 'count', color=my_colors[key], label=key, marker='8');
plt.title('Crime Incident Count by Hour', fontsize=18);
ax.set_xlabel('Hour (24 Hour Interval)', fontsize=18);
ax.set_ylabel('Crime Incident Count', fontsize=18, labelpad=20);
ax.tick_params(axis='x', labelsize=14, rotation=0);
ax.tick_params(axis='y', labelsize=14, rotation=0);
ax.set_xticks(np.arange(24));
handles, labels = ax.get_legend_handles_labels();
handles = [ handles[1], handles[5], handles[6], handles[4], handles[0], handles[2], handles[3] ];
labels = [ labels[1], labels[5], labels[6], labels[4], labels[0], labels[2], labels[3] ];
plt.legend(handles, labels, loc='lower right', fontsize=14, ncol=1);
plt.show()
This multiple line chart shows how crime incident counts change
throughout the 24 hours of the day for each day of the week. Each line
represents a different day and this identifies the hours when more
incidents are reported and how crime activity differs between weekdays
and weekends.