Introduction to Dataset

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.

Findings

Baltimore Crime Incidents by Quarter

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.

Frequency of Crime Types in Baltimore

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.

Baltimore Crime Incidents by Month & Year

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.

Crime Types by Incident Count & Average Age

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.

Crime Incident Count by Hour

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.