Lecture 6 – Data 100, Summer 2021

Notebook by Joseph Gonzalez (Spring 2020)

Introduction

In this lecture we examine the process of data cleaning and Exploratory Data Analysis (EDA). Often you will acquire or even be given a collection of data in order to conduct some analysis or answer some questions. The first step in using that data is to ensure that it is in the correct form (cleaned) and that you understand its properties and limitations (EDA). Often as you explore data through EDA you will identify additional transformations that may be required before the data is ready for analysis.

In this notebook we obtain crime data from the city of Berkeley's public records. Ultimately, our goal might be to understand policing patterns but before we get there we must first clean and understand the data.

Getting the Data

To begin this analysis we want to get data about crimes in Berkeley. Remarkably, the city of Berkeley maintains an Open Data Portal for citizens to access data about the city. We will be examining the:

  1. Call Data
  2. Stop Data (NEW)

Fortunately, this data is also relatively well document with detailed descriptions of what it contains. Here are summaries of the fields in the data:

Calls Data

Stop Data

Most data has bad documentation:

Unfortunately, data is seldom well documented and when it is you may not be able to trust the documentation. It is therefore critical that when we download the data we investigate the fields and verify that it reflects the assumptions made in the documentation.

Reproducible Data Science

In the interest of reproducible data science we will download the data programatically. We have defined some helper functions in the ds100_utils.py file. I can then reuse these helper functions in many different notebooks.

Occasionally, you will want to modify code that you have imported. To reimport those modifications you can either use the python importlib library:

from importlib import reload
reload(utils)

or use iPython magic which will intelligently import code when files change:

%load_ext autoreload
%autoreload 2

Downloading the Data

Notice that because I record how I got the data in the notebook, others can reproduce this experiment. However, it is worth noting that the data can change. We will want to pay attention to file timestamps.

Exploring the data

Now that we have obtained the data we want to understand its:

Structure

Before we even begin to load the data it often helps to understand a little about the high-level structure:

  1. How much data do I have?
  2. How is it formatted?

How big is the data?

I often like to start my analysis by getting a rough estimate of the size of the data. This will help inform the tools I use and how I view the data. If it is relatively small I might use a text editor or a spreadsheet to look at the data. If it is larger, I might jump to more programmatic exploration or even used distributed computing tools.

However here we will use python tools to probe the file.

All the files are relatively small and we could comfortable examine them in a text editors. (Personally, I like sublime or emacs but others may have a different view.).

In listing the files I noticed that the names suggest that they are all text file formats:

We will dive into the formats in a moment. However because these are text data I might also want to investigate the number of lines which often correspond to records.

What is the file format? (Can we trust extensions?)

We already noticed that the files end in csv and json which suggests that these are comma separated and javascript object files respectively. However, we can't always rely on the naming as this is only a convention. For example, here we picked the name of the file when downloading based on some hints in the URL.

Often files will have incorrect extensions or no extension at all.

Let's assume that these are text files (and do not contain binary encoded data) so we can print a "few lines" to get a better understanding of the file.

Notice that I used the repr function to return the raw string with special characters. This is helpful in deducing the file format.

What are some observations about Calls data?

  1. It appears to be in comma separated value (CSV) format.
  2. First line contains the column headings.
  3. There are lots of new-line \n characters:
    • at the ends of lines (delimiting records?)
    • within records as part of addresses.
  4. There are "quoted" strings in the Block_Location column:
    "2500 LE CONTE AVE
    Berkeley, CA
    (37.876965, -122.260544)"
    these are going to be difficult. What are the implications on our earlier line count calculations?

What are some observations about Stops data?

This appears to be a fairly standard JSON file. We notice that the file appears to contain a description of itself in a field called "meta" (which is presumably short for meta-data). We will come back to this meta data in a moment but first let's quickly discuss the JSON file format.

A quick note on JSON

JSON (JavaScript Object Notation) is a common format for exchanging complex structured and semi-structured data.

{
    "field1": "value1",
    "field2": ["list", "of", "values"],
    "myfield3": {"is_recursive": true, "a null value": null}
}

A few key points:

Loading the Data

We will now attempt to load the data into python. We will be using the Pandas dataframe library for basic tabular data analysis. Fortunately, the Pandas library has some relatively sophisticated functions for loading data.

Loading the Calls Data

Because the file appears to be a relatively well formatted CSV we will attempt to load it directly and allow the Pandas Library to deduce column headers. (Always check that first row and column look correct after loading.)

How many records did we get?

Preliminary observations on the data?

  1. EVENTDT -- Contain the incorrect time stamp
  2. EVENTTM -- Contains the time in 24 hour format (What timezone?)
  3. CVDOW -- Appears to be some encoding of the day of the week (see data documentation).
  4. InDbDate -- Appears to be correctly formatted and appears pretty consistent in time.
  5. Block_Location -- Errr, what a mess! newline characters, and Geocoordinates all merged!! Fortunately, this field was "quoted" otherwise we would have had trouble parsing the file. (why?)
  6. BLKADDR -- This appears to be the address in Block Location.
  7. City and State seem redundant given this is supposed to be the city of Berkeley dataset.

Checking that the City and State fields are all Berkeley CA

We notice that there are city and state columns. Since this is supposed to be data for the city of Berkeley these columns appear to be redundant. Let's quickly compute the number of occurences of unique values for these two columns.

Decoding day of the week

According to the documentation CVDOW=0 is Sunday, CVDOW=1 is Monday, ..., Therefore we can make a series to decode the day of the week for each record and join that series with the calls data.

Cleaning Block Location

The block location contains the lat/lon coordinates and I might want to use these to analyze the location of each request. Let's try to extract the GPS coordinates using regular expressions (we will cover regular expressions in future lectures):

Not all the records have a lat and lon. What fraction do have coordinates?

The following block of code joins the extracted Latitude and Longitude fields with the calls data. Notice that we actually drop these fields before joining. This is to enable repeated invocation of this cell even after the join has been completed.

We can now look at a few of the records that were missing latitude and longitude entries:

Are there any patterns to the missing data?





Loading the stops.json Data

Python has relatively good support for JSON data since it closely matches the internal python object model. In the following cell we import the entire JSON datafile into a python dictionary.

The stops_json variable is now a dictionary encoding the data in the file:





We can now examine what keys are in the top level json object

We can list the keys to determine what data is stored in the object.

Observation

The JSON dictionary contains a meta key which likely refers to meta data (data about the data). Meta data often maintained with the data and can be a good source of additional information.





Digging into Meta Data

We can investigate the meta data further by examining the keys associated with the metadata.

The meta key contains another dictionary called view. This likely refers to meta-data about a particular "view" of some underlying database. We will learn more about views as we study SQL later in the class.

Notice that this a nested/recursive data structure. As we dig deeper we reveal more and more keys and the corresponding data:

meta
|-> data
    | ... (haven't explored yet)
|-> view
    | -> id
    | -> name
    | -> attribution 
    ...

There is a key called description in the view sub dictionary. This likely contains a description of the data:





Columns Meta data

Another potentially useful key in the meta data dictionary is the columns. This returns a list:

We can browse summary data in the list using python:

Observations?

  1. The above meta data tells us a lot about the columns in the data including column names, potential data anomalies, and a basic statistic.
  2. The old version of this data included descriptions which would be useful in loading and working with the data.
  3. JSON makes it easier (than CSV) to create "self-documented data".
  4. Self documenting data can be helpful since it maintains it's own description and these descriptions are more likely to be updated as data changes.

Examining the Data Field

We can look at a few entires in the data field

Building a Dataframe from JSON

In the following block of code we:

  1. Translate the JSON records into a dataframe
  2. Remove columns that have no metadata description. This would be a bad idea in general but here we remove these columns since the above analysis suggests that they are unlikely to contain useful information.
  3. Examine the top of the table

Too many columns. Let's ask pandas to show us more. Be careful, showing too much could break your notebook.

Preliminary Observations

What do we observe so far?

We observe:

  1. The Incident Number appears to have the year encoded in it - we could potentially use this as a validation check.
  2. The created_at and updated_at Fields look like they are in milliseconds since January 1, 1970.
  3. The CreateDatetime Field looks to be formatted in YYYY-MM-DDTHH:MM:SS. I am guessing T means "Time".
  4. The Age Field has variable size brackets: 18-29, 30-39, >40.
  5. The definition of CallType can be found in Berkeley Police DepartmentCall-Incident Types: 1194-Pedestrian Stop, 1196-Suspicious Vehicle Stop, T-Traffic Stop

Recall the description:

Stop Data





EDA

Now that we have loaded our various data files. Let's try to understand a bit more about the data by examining properties of individual fields.





Are Case Numbers unique?

Case numbers are probably used internally to track individual cases and my reference other data we don't have access to. However, it is possible that multiple calls could be associated with the same case. Let's see if the case numbers are all unique.

Are case numbers assigned consecutively.

I like to use interactive plotting tools so I can hover the mouse over the plot and read the values. The cufflinks library adds plotly support to Pandas.

What might we be observing?

It looks like there are three discrete regions - an initial value, a consecutive increasing range, a slight gap(!) and another consecutive increasing range.

Let's look at row 4121.

This record looks very anomalous as it is missing values for multiple important fields.





Examining the Date

Let's dig into the date in which events were recorded. Notice in this data we have several pieces of date/time information (this is not uncommon):

  1. EVENTDT: This contains the date the event took place. While it has time information the time appears to be 00:00:00.
  2. EVENTTM: This contains the time at which the event took place.
  3. InDbDate: This appears to be the date at which the data was entered in the database.

When Pandas loads more complex fields like dates it will often load them as strings:

We will want to convert these to dates. Pandas has a fairly sophisticated function pd.to_datetime which is capable of guessing reasonable conversions of dates to date objects.

We can verify that the translations worked by looking at a few dates:

We can also extract the time field:

To combine the correct date and correct time field we use the built-in python datetime combine function.

We now updated calls to contain this additional informations:

What time range does the data represent





Are there any other interesting temporal patterns

Do more calls occur on a particular day of the week?

How about temporal patterns within a day?

Observations?

In the above plot we see the standard pattern of limited activity early in the morning around here 6:00AM.

Stratified Analysis

To better understand the time of day a report occurs we could stratify the analysis by the day of the week. To do this we will use box plots.

Observations?

There are no very clear patterns here. However it does appear that weekends have more calls later into the night.





Examining the Event

We also have data about the different kinds of crimes being reported

The Offense Field

The Offense field appears to contain the specific crime being reported. As nominal data we might want to see a summary constructed by computing counts of each offense type:

Observations?

Car burglary and misdemeanor theft seem to be the most common crimes with many other types of crimes occurring rarely.





CVLEGEND

The CVLEGEND field provides the broad category of crime and is a good mechanism to group potentially similar crimes.

Notice that when we group by the crime time we see that larceny emerges as one of the top crimes. Larceny is essentially stealing -- taking someone else stuff without force.

Stratified Analysis of Time of Day by CVLEGEND

View the crime time periods broken down by crime type:

Examining Location information

Let's examine the geographic data (latitude and longitude). Recall that we had some missing values. Let's look at the behavior of these missing values according to crime type.

Observations?

There is a clear bias towards sex crimes and drug violations that is not present in the original data. Therefore we should be careful when dropping missing values!





Examine data geographically

Questions

  1. Why are all the calls located on the street and at often at intersections?