https://opendata.cityofnewyork.us/
While studying in an immersive Data Science bootcamp program, one of my projects I worked on had to specifically use classification variables. An example of a classification variable is hot dog vs not hot dog. This is different from continuous variables such as predicting the sale price of your car based on make, model, year, etc. The sales price is an integer and it is continuous. In my project I took a look at 311 Service Requests in NYC made specifically on March 11, 2019, which was the start of my Data Science bootcamp. I feature engineered a new feature named Resolution Outcome based on contents of the resolution description. An example of a negative (0) resolution was “The NYPD responded but upon arrival those responsible were gone.” An example of a positive (1) resolution was “The DOT responded to the complaint and took action to fix the condition.”
Other features found in the data were:
Agency: NYPD, Dept of Transportation, Dept of Health & Mental Hygiene, Dept of Sanitation, Dept of Housing Preservation and Development, Dept of Parks and Recreation, etc
Borough: Brooklyn, Queens, Manhattan, Bronx, Staten Island
Location: Longitude/Latitude, Cross Streets, Intersections
Created/Closed Date
Complaint Type: Heat/Hot Water, Rodent, Noise, Street Condition, Illegal Parking, Unsanitary Condition, Blocked Driveway are just a few examples.
Resolution Description: Described what was done in response to the 311 service request.
I created several machine learning models, such as logistic regression, decision trees, random forest, K nearest neighbor and XGBoost, to predict what was the most influential feature that determined whether a person would have a positive or negative resolution outcome to their 311 service request. You can check out my github for the project here or my medium blog about it here.
That was then, this is now
There were several next steps that I wanted to take following my project. The fact that this project only looks at a single day of data greatly limits its scope. The most frequent complaint of HEAT/HOT WATER is probably highly correlated with the month and winter season. It might be a safe assumption to say that the number one complaint may be completely different if this data took a look at a day in the middle of summer. Would the number one complaint be Noise?
So my first step is to write out a function to get multiple days worth of data from NYC’s OpenData. OpenData has an api that returns 1,000 results from your query. My original criteria was 311 Service requests made on March 11th. Using python and pandas, this is how I retrieved this information:
url = "https://data.cityofnewyork.us/resource/fhrw-4uyv.json?$where=created_date%20between%20%272019-03-11T00:00:00%27%20and%20%272019-03-12T00:00:00%27"
data = pd.read_json(url)
The created date is between “2019-03-11T00:00:00,” which is midnight of March 11th to “2019-03-12T00:00:00,” which is midnight of March 12th. This creates a pandas dataframe named data with the first 1,000 results. One of the features from the data is the created_date which included the date and the time of the 311 service request.
data.created_date[999]
Returns a string of “2019-03-11T07:50:45.000“. Which means the first 1,000 results are 311 service requests made from midnight to 7:50am. To get the next set of 1,000 results, I would then need to change my created date to no longer start from midnight of March 11th. I would need to change it to start at 7:50 am. My new url would be created from “2019-03-11T07:50:45” and still end at midnight of the 12th.
To make this more dynamic, I created a function that would take input for the start_date and the end_date. I will be able to specify if I want data from just one day, or perhaps two days, or even a whole month. I would retrieve that data 1,000 results at a time by dynamically changing the created_date on the url to the last date found in data.created_date[999].
def three_one_one(start_date, end_date):
url = "https://data.cityofnewyork.us/resource/fhrw-4uyv.json?$where=created_date%20between%20%272019-" + start_date + "T00:00:00%27%20and%20%272019-" + end_date + "T00:00:00%27"
data = pd.read_json(url)
last_date = data.created_date[999]
while last_date:
new_url = "https://data.cityofnewyork.us/resource/fhrw-4uyv.json?$where=created_date%20between%20%27" + last_date + "%27%20and%20%272019-" + end_date + "T00:00:00%27"
I set last_date as the variable name for data.created_date[999]. In new_url I have replaced start_date with last_date, therefore when I retrieve the new_url, it will start at 7:50am from the previous example.
data1 = pd.read_json(new_url)
print("data1 shape:" data1.shape)
data = pd.concat([data, data1], join="inner", axis=0)
I save the data from the new_url to a dataframe named data1. data1.shape tells me how many rows and features I have in my dataframe. In this case, there are 1,000 rows and 37 features. This data1 will always have 1,000 rows because that is the maximum amount of information OpenData allows me to retrieve at a time.
data = pd.concat([data, data1], join="inner", axis=0)
print("concat data shape:", data.shape)
Concat joins both dataframes, data and data1, together and saves the combined dataframe as data. An “inner” join returns all rows from both the participating tables where the key record of one table is equal to the key records of another table. Now when I check how many rows and features I have in my data dataframe, there should now be 2,000 rows and 37 features. The number of rows in data.shape will keep increasing as we run this function again to keep adding more data. While data1.shape will always max out at 1,000. data.shape is useful to verify that the function is working and that we are increasing the amount of data we are adding to our dataframe.
data.to_csv('311_' + start_date + '_to_' + end_date + 'function.csv', index=False)
print("saved to csv")
time.sleep(3)
data.to_csv saves our dataframe as a csv file and time.sleep(3) forces a pause before the function runs again. I put a pause there so opendata does not block me for making too many requests in a very, very short time period.
new_last_date = data1.created_date[999]
last_date = new_last_date
Finally, we take the 1,000th entry from data1 which is the last date in the dataframe, and use it as the new “start date” to be able to run the function again and again, until our inputted end_date.
Here is the function in its entirety:
def three_one_one(start_date, end_date):
url = "https://data.cityofnewyork.us/resource/fhrw-4uyv.json?$where=created_date%20between%20%272019-" + start_date + "T00:00:00%27%20and%20%272019-" + end_date + "T00:00:00%27"
data = pd.read_json(url)
print("this is the first url:", url)
print("this is the start date:", start_date)
print("data shape is:", data.shape)
last_date = data.created_date[999]
while last_date:
new_url = "https://data.cityofnewyork.us/resource/fhrw-4uyv.json?$where=created_date%20between%20%27" + last_date + "%27%20and%20%272019-" + end_date + "T00:00:00%27"
print("this is the new url:", new_url)
print("this is the last_date:", last_date)
data1 = pd.read_json(new_url)
print("data1 shape:", data1.shape)
data = pd.concat([data, data1], join="inner", axis=0)
print("concat data shape:", data.shape)
data.to_csv('311_' + start_date + '_to_' + end_date + 'function.csv', index=False)
time.sleep(3)
new_last_date = data1.created_date[999]
print("this is the new last date:", new_last_date)
last_date = new_last_date
else:
print("else statement, done!")
return data.tail(10)
My original project looked at one day in March. Now, I wanted to look at a different month, specifically, in a different season. Since it is the most recent, I wanted to take a look at a week in October, or perhaps, the entire month. I was debating whether or not I should feature engineer resolution_outcome again, but with all this new data, I can begin by simply exploring what the data presents. I did notice that some of the features have changed since March. There is now a feature named open_data_channel_type which states whether the 311 service request was made through the phone, online or mobile. Do the residents of Brooklyn use mobile more than other boroughs? There is also a resolution_action_updated_date which would also bring interesting time insights. At the very least we can see how fast a status can be “closed.”
That’s it for now….more to come in the next few weeks.








































