Saturday, April 5, 2014
Saturday, March 15, 2014
Horse Racing: Introductory Analysis
I want to start getting my head around the basic information that could influence outcomes.
I have these race-level fields sloppily copy-pasted from the R terminal. I've bolded the fields that scream to me as being relevant.
"Track" "Date" "Race Number" "Race Type"
[5] "HorseType" "ClaimPriceHigh" "ClaimPriceLow" "Race Length"
[9] "Record Time" "Record Date" "Purse" "Plus"
[13] "Available Money" "Valueist" "Value2nd" "Value3rd"
[17] "Weather" "Track" "Time" "Start"
[21] "2FTime1" "2FTime2" "3FTime1" "3FTime2"
[25] "3FTime3" "4FTime1" "4FTime2" "4FTime3"
[29] "4FTime4" "t_Final" "2SplT1" "2SplT2"
[33] "3SplT1" "3SplT2" "3SplT3" "4SplT1"
[37] "4SplT2" "4SplT3" "4SplT4" "Run-up"
[41] "WPS_Pool" "ID"
Horse Type:
Of the 41 unique strings with horse type information, here's those that occur more than 10x.
Most horses are three or more years old. Most are fillies. This information might need to be coerced into similar groups to be useful but until I know what all of this means, I don't want to lose information.
I've only seen fillies run since I've been out so I'm assuming this is the norm. The highest frequency entry at the top is vague about whether they are male/female so I'm going to assume female unless otherwise specified.
For Thoroughbred Three Year Old and Upward
59
For Thoroughbred Three Year Old and Upward Fillies and Mares
27
For Thoroughbred Three Year Old Fillies
23
For Thoroughbred Four Year Old and Upward
23
For Thoroughbred Two Year Old
21
For Thoroughbred Three Year Old
20
For Thoroughbred Two Year Old Fillies
16
For Thoroughbred Three Year Old and Upward (NW2 L)
15
For Thoroughbred Three Year Old and Upward Fillies and Mares (NW2 L)
14
For Thoroughbred Three Year Old and Upward Fillies and Mares (NW2 L X)
12
For Thoroughbred Four Year Old and Upward Fillies and Mares
11
For Claim Price:
As an incentive against running superior horses, horses can be bought at the "claim price" after each race (at least, that's my understanding). This keeps a competitive~ish market. I'm not sure if this name is a relic of days past or not and it warrants further investigation. Most of the races are under 10k with a decent showing from 35-40k.
Race Length:
Win, Place, Show Values:
So that's a start. More to come.
I have these race-level fields sloppily copy-pasted from the R terminal. I've bolded the fields that scream to me as being relevant.
"Track" "Date" "Race Number" "Race Type"
[5] "HorseType" "ClaimPriceHigh" "ClaimPriceLow" "Race Length"
[9] "Record Time" "Record Date" "Purse" "Plus"
[13] "Available Money" "Valueist" "Value2nd" "Value3rd"
[17] "Weather" "Track" "Time" "Start"
[21] "2FTime1" "2FTime2" "3FTime1" "3FTime2"
[25] "3FTime3" "4FTime1" "4FTime2" "4FTime3"
[29] "4FTime4" "t_Final" "2SplT1" "2SplT2"
[33] "3SplT1" "3SplT2" "3SplT3" "4SplT1"
[37] "4SplT2" "4SplT3" "4SplT4" "Run-up"
[41] "WPS_Pool" "ID"
Horse Type:
Of the 41 unique strings with horse type information, here's those that occur more than 10x.
Most horses are three or more years old. Most are fillies. This information might need to be coerced into similar groups to be useful but until I know what all of this means, I don't want to lose information.
I've only seen fillies run since I've been out so I'm assuming this is the norm. The highest frequency entry at the top is vague about whether they are male/female so I'm going to assume female unless otherwise specified.
For Thoroughbred Three Year Old and Upward
59
For Thoroughbred Three Year Old and Upward Fillies and Mares
27
For Thoroughbred Three Year Old Fillies
23
For Thoroughbred Four Year Old and Upward
23
For Thoroughbred Two Year Old
21
For Thoroughbred Three Year Old
20
For Thoroughbred Two Year Old Fillies
16
For Thoroughbred Three Year Old and Upward (NW2 L)
15
For Thoroughbred Three Year Old and Upward Fillies and Mares (NW2 L)
14
For Thoroughbred Three Year Old and Upward Fillies and Mares (NW2 L X)
12
For Thoroughbred Four Year Old and Upward Fillies and Mares
11
For Claim Price:
As an incentive against running superior horses, horses can be bought at the "claim price" after each race (at least, that's my understanding). This keeps a competitive~ish market. I'm not sure if this name is a relic of days past or not and it warrants further investigation. Most of the races are under 10k with a decent showing from 35-40k.
Race Length:
For conversion purposes, one mile is eight furlongs. Most races are significantly shorter than a mile. I'd guess that the shorter races have more variable outcomes as it's easy to see that the odds out horses lose their pace in the longer races but compete well in the shorter runs.
Six Furlongs On The All Weather Track
111
One Mile On The All Weather Track
71
Five And One Half Furlongs On The All Weather Track
68
One Mile On The Turf
47
One And One Sixteenth Miles On The Turf
30
One And One Sixteenth Miles On The All Weather Track
17
Five Furlongs On The All Weather Track
12
111
One Mile On The All Weather Track
71
Five And One Half Furlongs On The All Weather Track
68
One Mile On The Turf
47
One And One Sixteenth Miles On The Turf
30
One And One Sixteenth Miles On The All Weather Track
17
Five Furlongs On The All Weather Track
12
Win, Place, Show Values:
A more relevant range of interest occurs from 20k down (as the majority of races win values lie in that range). For this price range, win amounts look linear with a favorable slope for 3rd. As the price range goes up, the results skew nonlinear with 3rd offering less % value. Incentive for a conservative strategy, maybe? Worth noting even if I don't have the racing strategies down yet.
Start:
90% of races are flagged as having "a good start for all". The horse in PP1 starts worst most frequently at 2% but this isn't really fair given that the outside PP changes so I would have to compare race group by race group and I don't want to do that right now.
So that's a start. More to come.
The Bukowski Sequence:
Like any other 17 year old guy, Bukowski was a really fun read. Different era, different perspective. Slums, women, booze, fights, all make for a potent youth catnip. It got me back into reading so I have been grateful for that. More recently, and partially due to a location change, I've chased down another Bukowski fixture: the horses. As I like uncertainty and probalistic estimates, I figured I'd throw my hat into the ring.
First step was to acquire the dataset. Copy, pastes and the following script helped out with that:
First step was to acquire the dataset. Copy, pastes and the following script helped out with that:
Monday, December 9, 2013
The Benefits of Aggressive Driving: First Result
I've included the ability to change lanes. The aggressive car checks how much room it has in front of it in the current lane. If an obstruction is present within a distance, it checks the next lane for obstructions. If none exists in the second lane, it'll change lanes and hopefully improve its situation and elapsed course time.
I ran this program 500 times with the following conditions:
1. the top speed of the aggressive car is 33% faster than the other cars.
2. the acceleration of the aggressive car is twice that of the other cars.
3. the aggressive car will change lanes to improve its situation.
4. the number of cars in the simulation are 22, which makes the density look something like this.
The differences in elapsed time between the aggressive car and a test car starting at the same speed and position are in the histogram below.
According to this simulation, an aggressive driver usually benefits from his strategy but the degree of the benefit varies substantially. By percentage, the aggressive car finishes the course on average 22% faster than the test car.
Next step is to vary the number of obstacle cars and see how that shifts the distribution.
Thursday, December 5, 2013
The Benefits of Aggressive Driving: Simulation Employing Python OOP
Two weeks ago on the way home from work, an excessively aggressive driver was dodging through traffic behind me. It was night, but like all obnoxious drivers, the headlights were of the luminous, distracting blue-white ilk. Jumping lanes, aggressive acceleration, higher top speed. At the next light, we were lined up with three or so cars in front of both of us, on a two lane road.
On the green, I accelerated gently and kept my pace at the speed of traffic. The blue-white headlight car ,jumped on the bumper of the car ahead, accelerating aggressively and quickly jumping lanes (to no advantage). At the next red light, he was only one car ahead despite his strategy.
Got me thinking. Does an aggressive driving strategy pay off on surface streets?
Using the pygame module (the Python equivalent of Java's processing), I've modeled a surface street as six stoplight objects spread at random distances apart. The function that turns the crank here is screen.get_at, which finds the RGB color scheme at a specified (x,y) location on the grid. Each car object (white rectangles) looks at its current location and ahead of it to find potential obstacles and modifies its speed to avoid crashes.
"Stoplights" are implemented through the redzones, which slow the speed of the car. If the car reaches the end of the redzone, it stops. The green or red lines to the left of the lane indicate the light status.
The cars with the small blue rectangles are the "racing" cars. They start at the same location and speed, but have different initialization values for acceleration and max speed. At the end of the course, the time elapsed from start to finish is recorded.
The results of five simulations:
(slow left lane car, fast right lane car)
[279, 282], [257, 290], [203, 316], [208, 298], [239, 291]
how much faster?
[1% faster, 12% faster, 56% faster, 43% faster, 22% faster]
I still need to include the ability of the car to change lanes. Comparing a stupid driver with a stupid and aggressive driver isn't very interesting.
The end questions I want to answer:
1. How much does varying traffic volume, speed differential between fast and slow cars and the length of red lights affect the course time?
2. Establish a metric that weighs the value of quickly completing the course against moderate acceleration and top speed, and find a strategy for optimizing that metric.
Here is the code. I'll clean it up later.
Tuesday, December 3, 2013
Postmortem on General Moly Speculation
Relevant summary:
- General Moly (GMO) is a development stage mining company. They want to dig up molybdenum. They have the land to do so and had the money, until...
- The financing required for mine construction fell through due to an unfortunately timed detaining of a Chinese bank chairman.
- The stock price fell accordingly.
My assumption was that if GMO was able to secure financing elsewhere, the stock price would rebound. Futures for GMO expiring in September and December amounted to a bet on whether GMO would be able to secure financing. To date, GMO has not secured financing, and the futures expired worthless.
Mt. Hope, the focal point of GMO, has proven and probable reserves of ~1.5 billion pounds according to GMO's website (and a health dose of copper). At current prices of $10/lb, GMO is sitting on $15B in molybdenum that isn't going anywhere, for better and for worse.
Thursday, November 21, 2013
Aggregating Aggregation: "Exception Handling or: How I learned to Stop Worrying and Let R Handle Imperfections"
The code for parsing Indeed.com by keyword and area in the previous post has some primitive features. It looks for exact patterns, and based on the usual syntax of Indeed HTML, subtracts a fixed number of characters to make a usable link which I can re-paste.
tryCatch takes your code and runs it. If an error or warning occurs, it redirects to the warning/error "handler", below the tryCatch{}. In redirecting, it doesn't stop the main loop with an error so my problem seems avoided although I don't understand what happens entirely. I ran through 5 pages of Indeed postings for analyst, and it spit out 50 instances of the expected output (as shown in the previous post).
Now I need to figure out what kind of output I want this to give, how I can speed up the execution, and automate a list of user inputs for both the keywords searched and multiple search terms, ie, "analyst","physics",... rather than the single "analyst".
but the legs are operational!
I think the error I have experienced comes from this fixed number subtraction taking away/leaving a relevant/irrelevant character. It works for most links, but not all. Ideally, I would just skip the exceptions and let the loop continue through.
R does this through tryCatch(), which has a confusing documentation. Reading through this posting from the Working With Data blog helped me in writing a function that loads as many pages of Indeed as specified.
tryCatch takes your code and runs it. If an error or warning occurs, it redirects to the warning/error "handler", below the tryCatch{}. In redirecting, it doesn't stop the main loop with an error so my problem seems avoided although I don't understand what happens entirely. I ran through 5 pages of Indeed postings for analyst, and it spit out 50 instances of the expected output (as shown in the previous post).
Now I need to figure out what kind of output I want this to give, how I can speed up the execution, and automate a list of user inputs for both the keywords searched and multiple search terms, ie, "analyst","physics",... rather than the single "analyst".
but the legs are operational!
Wednesday, November 20, 2013
Aggregating Aggregation: Finding Relevant Jobs
The job hunting process is like climbing a mountain. Each time the mountain crests, you assume it is the top only to be disappointed when there is another ascent. C'est la vie. To that end, I'm trying to implement some robotic legs to do my grunt work to the next ascent.
Example inputs as "entry level analyst"==search terms, "sacramento ca"==geographic area, 100==radius, 1==the number of pages to search. The bug appears on the fifth third party scrape, with values==defs. Debugging the function takes me to the line that scrapes the HTML but I'm still not sure where the error comes up. Obviously a work in progress, but if I can automate looking through 60 pages of Indeed.com a day, I can save myself a nice chunk of time.
This is a function that takes the argument that specify a search to Indeed.com, read the page, find the job links, and scrapes the third party redirect postings for relevant keywords. The non-function implementation works well but this bugs out quickly.
Example inputs as "entry level analyst"==search terms, "sacramento ca"==geographic area, 100==radius, 1==the number of pages to search. The bug appears on the fifth third party scrape, with values==defs. Debugging the function takes me to the line that scrapes the HTML but I'm still not sure where the error comes up. Obviously a work in progress, but if I can automate looking through 60 pages of Indeed.com a day, I can save myself a nice chunk of time.
Wednesday, October 23, 2013
Predictive Modeling of S.F. Crime: First Backtest
I've incorporated multiple simulations per day to produce distributions for crime volume, turning this
"X crimes will happen tomorrow according to my model" to
"X-Y crimes happen 50% of the time according to my model".
Here's a short backtest using the 150 days beginning on Jan. 1st, 2013 to illustrate what's going on. The percentile lines correspond to the 95th, 80th, 20th and 5th percentiles for each day in the simulation. The points are the realized volumes.
I've recalibrated the means used by the model to the 2003 data and ran the model forward 5 years to see how the model accounts for historical data.
A "good" performance:
1. ~60% of volumes should lie between the 20th and 80th percentile.
2. ~15% of volumes should lie in both the 5th to 20th and 80th to 95th range (~30% combined).
3. 10% should lie above and below the 95th and 5th, respectively.
What happened?
1. 50% of historical volumes were within the model-produced 20th and 60th percentiles. Low.
2. 19% were within the model-produced 5th and 20th percentile. High.
3. 13% were within the model-produced 80th and 95th percentile. Low.
4. ~17% were above/below forecast 95th/5th.
What does that mean?
1. As suspected, my model underestimates the frequency of abnormally high/low volume days (ie, fat-tailed events).
2. Judging from the 5th-20th and 80th-95th results, the forecasts produce higher volume days more often than lower volume days, which is inconsistent with past observations.
Now what?
The underlying issue is that my model produces volumes by combining random draws from probability distributions. Going back to Probability 101, generating a 95th percentile event will require multiple high draws from the twelve distributions. The benefit of my approach is that I can tinker with means by category (customize) without much problem and independently. The downside is that the combination of draws decreases the consistency of my forecasts with historical observation.
A way around this could be to initiate one draw which will inform the draws from each other category. Rather than combining a number of independent, individual draws to produce a volume, a percentile could be chosen, passed to the categorical distributions and inform that choice. That would reduce the problem of combined probabilities and better account for high-volume events.
But I'm not being paid and this model is jumping into overfit territory quickly, so I won't.
Monday, October 7, 2013
Predictive Modeling of S.F. Crime: Adding Monthly Tendencies
I have been developing a model for forecasting crime volume in San Francisco, informed through publicly available historical data going back ten years. Volumes are generated through a sum of choices from normal distributions by crime category, as informed from the past data. By shifting the mean of this distribution, I have accounted for day of the week trends (Friday being a higher crime day than Tuesday) and a long-term linear decrease.
The last tweak is to account for monthly shifts. Here's a plot lacking severely in elegance to give a quick idea as to the magnitudes by year. Each year, I took the mean of the twelve months and differenced the actual number by month from the mean.
The clearest trend is that February and December reside below the mean. To include this numerically, I've taken an average of the percentage away from the mean by year. A quick example will make this more clear.
In '03, 13482 crimes were reported in January. The mean of that year was 12741. 13482-12741 ~ 740. As a percentage of the mean, 740/12741 ~5.8%. Doing this for each January, year by year, and averaging those percentages yields 3.5%. Doing this for each month, the list looks like
January ~ 3.5% ~ 0.00115
February ~ -6.2% ~ -0.00222
March ~ 3.6% ~ 0.001185
April ~ -0.6% ~ -0.000218
May ~ 1.6% ~ 0.0000538
June ~ -3.8% ~ -0.001285
July ~ 0.02% ~ 0.0000711
August ~ 3.5% ~ 0.001142
September~ 1.6% ~ 0.000533
October ~ 5.2% ~ 0.00167
November ~ -3.0% ~ -0.001011
December ~ -5.6% ~ -0.00183
As a sanity check, the sum of these should be close to zero. Taking the full, "unapproximated" values in R yields a number raised to the -17th so ~0. To include these percentages as a modifier to a daily volume forecast, I need to translate these numbers into a daily shift. This percentage does not need to be geometric like the long-term decrease because the mean is effectively being reset at each run. On an annual basis, this monthly tweak should redistribute when the crime volume occurs, but not change the total volume.
The second number trailing the percent is the daily shift that accounts for the monthly shift, by dividing the percentage by the number of days in that month and ignoring leap years.
My model is a simple conditional tweak to the means of normal distributions informed by past crimes. To put this new shift in, the past mean calculation,
Mean = Historical Mean * [ (Geometric Average Decrease)^(Number of Forecast Days) + (Day of Week Shift)]
will include a new component so that
Mean = His Mean * [(GeoAvgDec)^(NumOfDays) + (DoW Mod) + (Month of Year Mod)]
The result will be a redistribution of forecast volume which will stay consistent with annual expectations.
Above is the new method including the monthly modifier and below is the previous forecast using only a day of the week shift and a long-term shift. By inspection, it is seen that this model still does not forecast extreme events but inclusion of this new modifier has spread the distribution out rather than clustering so heavily around the downward linear trend.
The next step is to account for the fat tail events, and keeping in mind the longer term goal of putting forecasts in the context of Monte Carlo simulations.
The last tweak is to account for monthly shifts. Here's a plot lacking severely in elegance to give a quick idea as to the magnitudes by year. Each year, I took the mean of the twelve months and differenced the actual number by month from the mean.
The clearest trend is that February and December reside below the mean. To include this numerically, I've taken an average of the percentage away from the mean by year. A quick example will make this more clear.
In '03, 13482 crimes were reported in January. The mean of that year was 12741. 13482-12741 ~ 740. As a percentage of the mean, 740/12741 ~5.8%. Doing this for each January, year by year, and averaging those percentages yields 3.5%. Doing this for each month, the list looks like
January ~ 3.5% ~ 0.00115
February ~ -6.2% ~ -0.00222
March ~ 3.6% ~ 0.001185
April ~ -0.6% ~ -0.000218
May ~ 1.6% ~ 0.0000538
June ~ -3.8% ~ -0.001285
July ~ 0.02% ~ 0.0000711
August ~ 3.5% ~ 0.001142
September~ 1.6% ~ 0.000533
October ~ 5.2% ~ 0.00167
November ~ -3.0% ~ -0.001011
December ~ -5.6% ~ -0.00183
As a sanity check, the sum of these should be close to zero. Taking the full, "unapproximated" values in R yields a number raised to the -17th so ~0. To include these percentages as a modifier to a daily volume forecast, I need to translate these numbers into a daily shift. This percentage does not need to be geometric like the long-term decrease because the mean is effectively being reset at each run. On an annual basis, this monthly tweak should redistribute when the crime volume occurs, but not change the total volume.
The second number trailing the percent is the daily shift that accounts for the monthly shift, by dividing the percentage by the number of days in that month and ignoring leap years.
My model is a simple conditional tweak to the means of normal distributions informed by past crimes. To put this new shift in, the past mean calculation,
Mean = Historical Mean * [ (Geometric Average Decrease)^(Number of Forecast Days) + (Day of Week Shift)]
will include a new component so that
Mean = His Mean * [(GeoAvgDec)^(NumOfDays) + (DoW Mod) + (Month of Year Mod)]
The result will be a redistribution of forecast volume which will stay consistent with annual expectations.
Above is the new method including the monthly modifier and below is the previous forecast using only a day of the week shift and a long-term shift. By inspection, it is seen that this model still does not forecast extreme events but inclusion of this new modifier has spread the distribution out rather than clustering so heavily around the downward linear trend.
The next step is to account for the fat tail events, and keeping in mind the longer term goal of putting forecasts in the context of Monte Carlo simulations.
Sunday, September 29, 2013
Predictive Modeling of S.F.P.D. Crime: Falling Short
Turning the crank in Python and R, here's my first long term forecast incorporating the tailoring detailed in the last few posts. I didn't spend much time on the plot, hence the funky axis units. The left of the red line is the actual daily volumes from 2003-2013, while after the red line is the forecast volumes.
So far, a long-term decreasing trend and a day of the week modifier has been included. By inspection, the long-term decrease seems to be doing what it needs to because there is a clear decreasing trend. What about the day of the week modifier?
Percentile results show that the forecast volumes are lower and more tightly distributed than historical volumes. Forecasts are in italics and historical results are in regular font.
Wednesday
Min. 1st Qu. Median Mean 3rd Qu. Max.
202.0 339.0 372.0 377.6 414.0 666.0
Min. 1st Qu. Median Mean 3rd Qu. Max.
277.0 326.2 344.0 344.7 362.0 422.0
Thursday
Min. 1st Qu. Median Mean 3rd Qu. Max.
197.0 352.0 379.0 382.9 413.0 576.0
Min. 1st Qu. Median Mean 3rd Qu. Max.
249 311 331 331 349 406
Friday
Min. 1st Qu. Median Mean 3rd Qu. Max.
6.0 350.0 388.0 388.8 429.0 552.0
Min. 1st Qu. Median Mean 3rd Qu. Max.
292.0 337.0 353.0 356.0 371.8 427.0
Saturday
Min. 1st Qu. Median Mean 3rd Qu. Max.
149.0 325.0 363.0 361.6 393.0 566.0
Min. 1st Qu. Median Mean 3rd Qu. Max.
272.0 316.0 334.0 335.4 355.0 394.0
Sunday
Min. 1st Qu. Median Mean 3rd Qu. Max.
2.0 313.0 340.0 341.3 369.0 588.0
Min. 1st Qu. Median Mean 3rd Qu. Max.
237.0 291.8 308.0 309.6 326.2 376.0
Monday
Min. 1st Qu. Median Mean 3rd Qu. Max.
190.0 329.0 362.0 362.6 395.0 522.0
Min. 1st Qu. Median Mean 3rd Qu. Max.
256.0 298.0 317.5 318.5 338.0 395.0
Tuesday
Min. 1st Qu. Median Mean 3rd Qu. Max.
210.0 347.0 376.0 379.4 409.0 553.0
Min. 1st Qu. Median Mean 3rd Qu. Max.
274.0 311.8 328.0 333.9 352.0 421.0
In both cases, Friday is the peak day for historical and forecast crime while Sunday is the minimum. At first glance, the modifier looks like it has successfully shifted the daily volumes to better fit historical patterns.
Takeaways:
- The forecast volumes hug the mean more closely than historical volumes.
More technically, switching from a normal distribution to a more fat tailed distribution is probably warranted. A fatter tail in the probability distribution will pull more "extreme" values more often, and so forecast volumes will spread away from the mean. When I incorporate the Monte Carlo aspect, this should lead to more accurate percentile results. - The mean of the forecast volumes are lower than the historical means.
I have added a decreasing trend, so volumes would be expected to be lower. Additionally, the historical means include the last ten years of crime and so reflect the higher volumes early on in the sample. Comparing instead with a more recent set like the three year period from 2009-2012. Turning the crank on that, the means increase so something seems amiss as that is not consistent from inspection.
Thursday, September 26, 2013
Predictive Modeling of S.F.P.D. Crime: Daily and Long-Term Tailoring
My last post established that a linear model of crime volume from 2003 to 2013 has a slope of -25 (incorrectly labeled 23 in the image from the last post), and that there is variation in crime volume associated with the day of the week. This post details how I intend to include this into the model.
My approach is to shift the means of the normal distributions that I am pulling from by applying a number of modifiers, which will incorporate the specific trends I have mentioned.
As a demonstration, assume that the daily decrease in crime is ~1%, Wednesday has 5% lesscrime than a normal day and Tuesday has 5% more crime than a normal day. Tailoring the model to include these tendencies requires shifting the means of these normal distributions. As a concrete example, let's consider thefts. The mean is ~89 per day.
Through the week, the mean of the distribution selected will be changed like this:
The term inside of the brackets is the percentage modifier. The first term accounts for 1% decrease, compounding daily, hence the geometric term. The second term accounts for the day of the week. I'll explain the origin of each later.
Long-Term Decrease:
Sequentially taking monthly percentage changes in crime volume from 2003 to 2013 produces a series of 120 "returns".The geometric mean comes to -0.212%. Bringing this to our example of an ~89 mean, day one will have 89 crimes. Day two will have 89*(1+Geometric Mean=-0.00212) = 88.81. Day three will have day two's mean, 88.81*(1+Geometric Mean = -0.00212)=88.62. This can be simplified by 89(1+GeoMean)^(Day), where "Day" is the number of the days from the start. Over a year period, the last number for Day would be 365.
The long-term decrease will be accounted for by a term of (0.997877)^(Day of Forecast).
Day of Week Modifier:
This is a lesson in documenting code. I don't remember how I got to these results, but here's what I have noted. Listed in order from Monday to Sunday, -3.3%, 0.4%, 3.5%, 0.3%, 6.6%, 0.4%, -7.8%. The numbers work, in the sense that they sum pretty close to 0 so that they will shift the distributions without affecting the totals. This is important because historical volume informs my model, and I want to stay true to that while tailoring to make forecasts account for the day of the week.
Edit:
Alright, I went back. Here's how I got those numbers.
Each year, take the number of crimes occurring on each day of the week and find the mean. For example, Monday through Friday have 100 crimes each day and Saturday, Sunday have 200 each. The mean is 128. Daily volume can be represented as a percentage above/below the mean. Saturday, Sunday are 56% above it while Monday through Friday are -21%. Summing these percentages gives zero, meaning that the predicted means overly an annual period should be consistent with historical annual means.
Daily Modifier Shift:
Monday, -0.034
Tuesday, 0.002
Wednesday, 0.033
Thursday, 0.001
Friday, 0.067
Saturday, 0.006
Sunday, -0.074
My approach is to shift the means of the normal distributions that I am pulling from by applying a number of modifiers, which will incorporate the specific trends I have mentioned.
As a demonstration, assume that the daily decrease in crime is ~1%, Wednesday has 5% lesscrime than a normal day and Tuesday has 5% more crime than a normal day. Tailoring the model to include these tendencies requires shifting the means of these normal distributions. As a concrete example, let's consider thefts. The mean is ~89 per day.
Through the week, the mean of the distribution selected will be changed like this:
| Monday | Tuesday | Wednesday |
|---|---|---|
| Mean=89 | Mean=89[(1-0.01)^1+(0.05)] | Mean=89[(1-0.01)^2+(-0.05)] |
The term inside of the brackets is the percentage modifier. The first term accounts for 1% decrease, compounding daily, hence the geometric term. The second term accounts for the day of the week. I'll explain the origin of each later.
Long-Term Decrease:
Sequentially taking monthly percentage changes in crime volume from 2003 to 2013 produces a series of 120 "returns".The geometric mean comes to -0.212%. Bringing this to our example of an ~89 mean, day one will have 89 crimes. Day two will have 89*(1+Geometric Mean=-0.00212) = 88.81. Day three will have day two's mean, 88.81*(1+Geometric Mean = -0.00212)=88.62. This can be simplified by 89(1+GeoMean)^(Day), where "Day" is the number of the days from the start. Over a year period, the last number for Day would be 365.
The long-term decrease will be accounted for by a term of (0.997877)^(Day of Forecast).
Day of Week Modifier:
This is a lesson in documenting code. I don't remember how I got to these results, but here's what I have noted. Listed in order from Monday to Sunday, -3.3%, 0.4%, 3.5%, 0.3%, 6.6%, 0.4%, -7.8%. The numbers work, in the sense that they sum pretty close to 0 so that they will shift the distributions without affecting the totals. This is important because historical volume informs my model, and I want to stay true to that while tailoring to make forecasts account for the day of the week.
Edit:
Alright, I went back. Here's how I got those numbers.
Each year, take the number of crimes occurring on each day of the week and find the mean. For example, Monday through Friday have 100 crimes each day and Saturday, Sunday have 200 each. The mean is 128. Daily volume can be represented as a percentage above/below the mean. Saturday, Sunday are 56% above it while Monday through Friday are -21%. Summing these percentages gives zero, meaning that the predicted means overly an annual period should be consistent with historical annual means.
Daily Modifier Shift:
Monday, -0.034
Tuesday, 0.002
Wednesday, 0.033
Thursday, 0.001
Friday, 0.067
Saturday, 0.006
Sunday, -0.074
Tuesday, September 17, 2013
Predictive Modeling of S.F. Crime: Trends
So far, my model generates crime predictions through statistical applications of historical data. Calling it predictive is a stretch because it assumes a stationary, homogeneous environment for crime. Some examples are that daily forecasts do not account for day of the week volume differences or long term crime trends. I explored that a little in the data today and produced some charts to that end.
Day of Week Trends:
Below is a barplot that takes crime volume by day, by year from 2003 to 2013. That is, the first seven bars correspond to the number of crimes on Friday, Monday, Saturday, Sunday, Thursday, Tuesday and Wednesday occurring in 2003.

More demonstrative is to take the mean number of crimes for the days of the week in each year and calculate the distance from that mean. Let's say Friday has 1000 crimes, Monday-Thursday have 500 crimes, Saturday and Sunday have 700 crimes each. The total # is 4400 crimes in the week. The mean is 628. Put another way, 628 crimes occurring each day of the week would reach that [cough, rounding] same number (7*628 ~ 4400). The barplot below takes the distance by day from the weekly mean for each year. We see that Friday and Wednesday frequently lie above the mean, while Sunday is uniformly below the mean.
Takeaways:
1. A long-term decreasing trend would be a useful inclusion to multi-year forecasts. This trend is not strictly decreasing and deviates significantly on a monthly basis.
2. Daily differences matter. Friday has more crime and Sunday has less crime. Including this to differentiate a Friday forecast from a Sunday forecast can solve the ambiguous and interchangeable format of current forecasts.
Long-Term Trends:
Monthly volume was compiled into a list. The result is the plot below, and a best fit trendline that shows crime over the last 11 years has been approximately decreasing at a rate of 23 crimes per month. Significant variance exists on a monthly basis, but there is definitely a long-term decreasing trend which could be included in a model forecasting many years out.
Day of Week Trends:
Below is a barplot that takes crime volume by day, by year from 2003 to 2013. That is, the first seven bars correspond to the number of crimes on Friday, Monday, Saturday, Sunday, Thursday, Tuesday and Wednesday occurring in 2003.

More demonstrative is to take the mean number of crimes for the days of the week in each year and calculate the distance from that mean. Let's say Friday has 1000 crimes, Monday-Thursday have 500 crimes, Saturday and Sunday have 700 crimes each. The total # is 4400 crimes in the week. The mean is 628. Put another way, 628 crimes occurring each day of the week would reach that [cough, rounding] same number (7*628 ~ 4400). The barplot below takes the distance by day from the weekly mean for each year. We see that Friday and Wednesday frequently lie above the mean, while Sunday is uniformly below the mean.
Takeaways:
1. A long-term decreasing trend would be a useful inclusion to multi-year forecasts. This trend is not strictly decreasing and deviates significantly on a monthly basis.
2. Daily differences matter. Friday has more crime and Sunday has less crime. Including this to differentiate a Friday forecast from a Sunday forecast can solve the ambiguous and interchangeable format of current forecasts.
Sunday, September 15, 2013
Predictive Modeling of S.F. Crime: Introduction
My last few posts have approached S.F. reported crime data from a descriptive perspective. My goal was to familiarize the unknown by employing data descriptively and presenting it in simple and intuitive ways. Methods keeping to this theme include:
- natural frequencies, for example, 1 in 5 reported crimes in the Tenderloin were drug related.
- visualizations, such as barplots and wordclouds to compare relative magnitudes of natural frequencies and maps of San Francisco with the density of various crimes plotted over it (heatmap, of sorts).
- brief summaries of stand-out traits in each district. The Tenderloin has the highest density of crime by area. Violent and drug-related crime are far more frequent than in other districts, and as a consequence, the arrest rate of 70% is double or triple that of the other districts.
The last few weeks I have switched focus from descriptive analysis to predictive analysis. Instead of summarizing what has happened, I want to use relevant historical data to generate forecasts for reported criminal activity.
I'm keeping it simple to start. I organized the data into daily cuts. From there, the crime/day was calculated for high volume crimes individually (crimes occurring more than 2500 times a year such as theft, robbery, burglary, assault, missing persons, non-criminal, other offenses, drugs, warrants, vehicle thefts) and for low volume crimes combined.
The data set of 223 days allows for a histogram of 223 individual values. For the high volume crimes (mean of more than ten a day), their distribution looks normal for a first run approximation. One such example:
Approximating each high volume crime and the grouped together low volume crimes with a normal distribution gives a mean and standard deviation. A daily crime volume forecast can be produced by pulling randomly from each distribution, giving a # of each crime/day. Summing these, a daily volume is produced. Repeating this process 365 times yields the following barplot. The red line is the annual mean and the blue lines show a standard deviation on either side of the mean.
I'm keeping it simple to start. I organized the data into daily cuts. From there, the crime/day was calculated for high volume crimes individually (crimes occurring more than 2500 times a year such as theft, robbery, burglary, assault, missing persons, non-criminal, other offenses, drugs, warrants, vehicle thefts) and for low volume crimes combined.
The data set of 223 days allows for a histogram of 223 individual values. For the high volume crimes (mean of more than ten a day), their distribution looks normal for a first run approximation. One such example:
Approximating each high volume crime and the grouped together low volume crimes with a normal distribution gives a mean and standard deviation. A daily crime volume forecast can be produced by pulling randomly from each distribution, giving a # of each crime/day. Summing these, a daily volume is produced. Repeating this process 365 times yields the following barplot. The red line is the annual mean and the blue lines show a standard deviation on either side of the mean.
This is a first run and has little utility for decision making. There is no accounting for daily, monthly or annual trends, such as higher crime volume on Friday. It does not distribute crime by police district, which would be relevant to staffing. More importantly, each day is a random selection from a normal distribution. Another annual generation would likely not resemble this one in anything but the mean and variance.
Where can this be improved?
The short answer is everywhere. The main inclusion needed is realizing daily forecasts in the context of Monte Carlo simulations. With many daily forecasts, I can replace a one number estimate with percentiles. As an example, my day one crime volume was 339. If I ran my annual simulation 1000 times, I could instead offer an estimate through percentiles such as, "95% of outcomes fell below 400 crimes/day".
This still does not improve the model, except in improving the clarity of results. To make this model useful, I need to include how these crimes are distributed by hourly blocks and police districts. Further, pulling from a stationary mean and variance make this a time independent model. While that might be the best approximation, I am not well enough informed to make that assumption. Finally, long-term or daily trends can be included on further studying the data.
Tuesday, September 3, 2013
SFPD Project, Part 2 by name and not proportion
Here's a wordcloud that pulls from the descriptions of the 75k+ crimes reported in San Francisco so far. The larger the word, the higher frequency it is mentioned. Theft
The quick conclusion is that the largest problem, by frequency, has to do with theft from autos or property. Of the 20k larceny/theft reported, only 1.2k had any sort of resolution. Reinforcing that notion is the incidence per 1000 crimes of each category. Larceny/theft is always the largest proportion of crime,
with the exception of the Tenderloin,
where drugs are a contender for most frequently occurring crime. An incredible 1 of 5 reported crimes involve drugs in the Tenderloin. For context, the average is 1 of 20 in the other 9 districts. Parsing for drug descriptions across the city showed that the occurrence of type of drug is fairly equal (don't quote me because that is off memory of something I ran a few weeks ago), but in the Tenderloin it was significant leaning towards "harder" drugs (non-marijuana, that is). Not surprisingly, the frequency of assaults and other non-theft crimes increases in the Tenderloin. Come to think of it, jacking people in the ghetto doesn't make a ton of sense.
The other exception is Bayview, where nobody drives with a license plate (ie, Other Offenses). Also not a wonderful area, in terms of % of violent crime.
Here's the last chart I liked. Pretty self-explanatory. The Tenderloin has an abnormally high percentage of arrests. That comes with the drugs. An anecdote from the incoming PD officer this research was intended for went along the line of that the officers from the TLoin station walk out into the street, and before they even get into the cars they have to arrest someone.
If exciting to you meanst putting the bad guys away, assaults (40% arrested), drugs (90% arrested) and warrants (93% arrested) are the ticket. The Loin, Mission are the heavy hitters in that department, followed by Bayview, Northern and Southern. It looks like Richmond, Park, Taraval and to a lesser extent, Central, are going to involve driving around and listening to people bitch about what was stolen from them and vague descriptions who might have done it when you know you will never actually find the person who did it. But there won't be as many crackheads, so they have that going for them.
Also funny is how the hood has a disproportionately low amount of burglaries compared to the rest of the crime occurring. Park, Richmond and Taraval lead the way at about 7-8 %. Can't blame the burglars for stupidity, anyway. Worth reinforcing that since this is a frequency of occurence rather than raw buglarly numbers, those districts haven't had the most burglaries (Northern and Southern are ahead, but not by as much as might be guessed without the numbers) because more crimes are committed elsewhere. Anyway, burglars know where to shop.
Monday, August 26, 2013
SFPD Project
SFPD has publicly available data on all reported crime including descriptions/location/time/etc. I've spent the last few weeks parsing it and trying to package it in useful ways to an incoming PD officer. Here's a few plots.
Subscribe to:
Posts (Atom)



























