Data Science for Fun and Motorsport

Posted on Jan 1, 2022

One of my favorite things about motorsport is telemetry. It’s a wealth of information that, with a human eye, is overwhelming. Data science or, more specifically, data visualization really breaks down data to something instantly digestible for consideration, analysis, and action.

Table Of Contents

Start your engines

Telemetry, and electronics in general, are vital to motorsport as we know it. Through data transmission and collection we can audit adherence to rule sets, learn about and diagnose issues while the vehicle is still out on the track, and iterate on vehicle performance with assistance from data analysis. Another component, which also strikes me as very interesting, is the ability for telemetry and data analysis to enrich the spectator experience.

All of these components are, perhaps, of equal interest to me however they all have a commonality that may not be obvious at first glace: financial cost. Motorsport, itself, is a costly endeavor and the telemetry systems and associated sensors and communications equipment are part and parcel with building and running the rest of the vehicle. As a surrogate I’ve taken to Forza Horizon 5, a newly released game for Xbox One, Xbox Series S/X, and PC. Forza Horizon is definitely a more “arcade” entry into racing games however it does use the same system as Forza Motorsport 7 for sending vehicle telemetry out to a listener. This is probably more appropriate as I used an Xbox One controller as opposed to a race wheel which, in my case, allows for more realistic input.

💡 At no point is this a flex on my skills in Forza Horizon 5.

This is done over UDP and there are some specifications with regards to how the data is delivered but I skipped over creating my own listener and opted to use forza telemetry by Austin Baccus to record the data to CSV. This is a Windows-only electron application (by the looks of it–had some issues building dotnet 6 on my M1 MBA that I chose to not troubleshoot) that also provides a HUD. Very cool.

Fields

There is a wealth of information delivered by the Forza game engine to the telemetry endpoint. It is potentially overwhelming and this is very specifically simulation–a professional vehicle would have many more sensors being recorded if not also being transmitted back to the garage via wireless link.

IsRaceOn
TimestampMS
EngineMaxRpm
EngineIdleRpm
CurrentEngineRpm
AccelerationX
AccelerationY
AccelerationZ
VelocityX
VelocityY
VelocityZ
AngularVelocityX
AngularVelocityY
AngularVelocityZ
Yaw
Pitch
Roll
NormalizedSuspensionTravelFrontLeft
NormalizedSuspensionTravelFrontRight
NormalizedSuspensionTravelRearLeft
NormalizedSuspensionTravelRearRight
TireSlipRatioFrontLeft
TireSlipRatioFrontRight
TireSlipRatioRearLeft
TireSlipRatioRearRight
WheelRotationSpeedFrontLeft
WheelRotationSpeedFrontRight
WheelRotationSpeedRearLeft
WheelRotationSpeedRearRight
WheelOnRumbleStripFrontLeft
WheelOnRumbleStripFrontRight
WheelOnRumbleStripRearLeft
WheelOnRumbleStripRearRight
WheelInPuddleDepthFrontLeft
WheelInPuddleDepthFrontRight
WheelInPuddleDepthRearLeft
WheelInPuddleDepthRearRight
SurfaceRumbleFrontLeft
SurfaceRumbleFrontRight
SurfaceRumbleRearLeft
SurfaceRumbleRearRight
TireSlipAngleFrontLeft
TireSlipAngleFrontRight
TireSlipAngleRearLeft
TireSlipAngleRearRight
TireCombinedSlipFrontLeft
TireCombinedSlipFrontRight
TireCombinedSlipRearLeft
TireCombinedSlipRearRight
SuspensionTravelMetersFrontLeft
SuspensionTravelMetersFrontRight
SuspensionTravelMetersRearLeft
SuspensionTravelMetersRearRight
CarOrdinal
CarClass
CarPerformanceIndex
DrivetrainType
NumCylinders
PositionX
PositionY
PositionZ
Speed
Power
Torque
TireTempFl
TireTempFr
TireTempRl
TireTempRr
Boost
Fuel
Distance
BestLapTime
LastLapTime
CurrentLapTime
CurrentRaceTime
Lap
RacePosition
Accelerator
Brake
Clutch
Handbrake
Gear
Steer
NormalDrivingLine
NormalAiBrakeDifference

I realize now that this list may be difficult to scroll down so this will likely cause me to improve on the theme of this website by creating an expandable code fence of some type. But the impression holds true: it’s a lot of data and it’s coming in at, from my estimation, with one full record (every one of those fields) approximately every 116 milliseconds. That’s a significant amount of data for a human to go through. It would be a bit of a chore to go through the significance of every field at this moment so I will do so as they come up.

time = ford_falcon['TimestampMS']
difference = time.diff(periods=-1)
print (np.mean(difference))

That’s the strictly operational code that I used to find the difference and that bring about the discussion of how this data is structured and manipulated. Python is all the rage in data science for a reason–structures and packages exist to make this process as capable as it can be. If you thought I was going to say “painless” you were right, I was thinking that, but I didn’t want to lie. The learning curve is significant to someone who is inexperienced with Python and even greater to someone who is inexperienced with Python and data visualization (e.g. me).

Pandas 🐼

pandas is a widely used data analysis library and can be considered standard at this point. It provides functions and structures that will be leveraged heavily throughout this process, most specifically the DataFrame, however there is no tremendous need to jump over and read their documentation–I plan on easing into every concept as it comes up through this journey. Matplotlib is another library that makes this all possible and is similarly standard in this realm. We will also be using NumPy and SciPy for some other functionality. If you would want to break off and read the documentation now I fear that you may get caught up in quite the tangled web–these libraries contain a large amount of functionality and it’s not always obvious how they’ll behave or how that functionality can be applied.

Let me see what spring is like on Jupyter

Jupyter Notebooks are a relatively new concept that’s, if you can dig it, a shareable shell. Jupyter Notebooks can be saved, shared, and messed around with. I’ll have a notebook up on Github afterwards. It is a great way to learn new languages or packages. This is a good example of one of those times. Homebrew conquers all things and it’s straightforward to install.

brew install jupyterlab

I had some issues with jupyterlab on my M1 MacBook but on Linux it works like gangbusters. Since I’m not on my Linux box and I still want to work on my laptop. Jupyter Notebook isn’t particularly suited for that, though. SSH has the ability to forward a part and, as Jupyter Notebook likes to run only available to localhost. There are ways to get around this but it isn’t necessary for this.

Google’s Colaboratory

Google Colab is a product that leverages GCP to provide Jupyter (or Jupyter-comaptible–there’s a lot of nuance there with regards to IPython and various other back ends and functionality) runtimes and can load Jupyter Notebooks straight up while also being able to have some local storage or connect to your GCP storage or Google Drive. I’ve migrated my Jupyter Notebook over to Colab and all I got was this lousy blog post section.

Migration Blues

I didn’t start this project on Google Colab because, as is my wont, I jumped in to the deep end and pip3 didn’t find the package that I was wanting to work with, specifically. In looking for the quickest way forward I set up Jupyter Notebook at home and made progress only to find that Python, and specifically matplotlib, examples vary greatly and lack context. I like context. Context helps one make better decisions. So that’s why there’s an entire section here on moving from Jupyter to Colab.

Moving the files was a simple matter. Create a folder in your Google Drive and drop everything in there together. That’s my CSVs and the Jupyter .ipynb file. Sign up for a free Google Colab account (or spend $9.99/mo for more premium access–free should be fine for what we’re doing here but for machine learning it may be worth your while to jump to that paid tier. I have some experience there and I’ll say that it may not necessarily be faster for all workloads but it will not dominate the use of your machine while it’s running so you are free to do other things and, as always, YMMV but I have some posts on that coming up).

Once your .ipynb is on Google Drive and you’re signed up for Google Colab you can actually just double-click it from your Drive window. Obviously if you’re starting from scratch you won’t need to do that and can just create a new Notebook in Colab. To get access to your files from Google Drive you’ll need to add a mount cell at the top like this:

from google.colab import drive
drive.mount('/content/drive')

This is in-built and will provide a link that you must visit to sign-in and receive a code to authorize the access of Colab to your Google Drive. If you’re very concerned about the contents of your Drive then there are a number of other ways to get your files in there (pulling them from Git being one of them) but just be aware of that as we navigate through directory structures. By clicking the “Refresh” button on the directory view on the left you’ll see a folder called drive come up in the root. So now I’m going to go through and change all of my file paths to /content//drive/MyDrive/ForzaData from what they were before,

import pandas as pd
from matplotlib import pyplot as plt
from matplotlib.pyplot import figure

# read csv file
amg_one = pd.read_csv('/content/drive/MyDrive/ForzaData/amg_one.csv')
z_car = pd.read_csv('/content/drive/MyDrive/ForzaData/240z_2.csv')
ford_falcon = pd.read_csv('/content/drive/MyDrive/ForzaData/ford_falcon.csv')
# display DataFrame
print(amg_one)

That should work swimmingly. If it doesn’t, definitely use the “refresh” button on your directory view to see what the pathname should be but once Drive is hooked up it should be cruise control to read and write to it. I’ll also say that writing to Drive is slow so use the scratch disk in your instance if you want to write out a lot of operations. pd.read_csv takes in a CSV from disk and converts it directly to a DataFrame. In online examples you’ll usually see a DataFrame object represented as df. You can think of a df as a spreadsheet or a database table–“records” are rows and “fields” are columns. Records are all related to each other in some way and will tend to keep that relationship. The DataFrame (I won’t be doing the code block for it the whole time–it’ll be a lot–it’s just a normal word now) will be the primary structure to interact with the data that you have. It doesn’t work quite like a two-dimensional array but we’ll go through some of those operations as they come up.

The only other issue I had was that, seeming in this version of the libraries installed, the command ax.set_box_aspect(aspect = (1,1,1)) does not exist. That being said the plot still worked as expected (it’s a 3D plot–we’ll get there).

Graph it up, B

In this instance I have telemetry from three vehicles. I definitely want to filter the data so I get exactly what I’m looking for so I run the following as Forza sets the IsRaceOn field to 1 during a race.

race_amg_one = amg_one.loc[(amg_one['IsRaceOn'] == 1)]
race_240z = z_car.loc[(z_car['IsRaceOn'] == 1)]
race_falcon = ford_falcon.loc[(ford_falcon['IsRaceOn'] == 1)]

This is a good time to bring up the issue of data sanitation. Forza doesn’t really do any organization for you and the best you get is lap counts and IsRaceOn. I took a few captures but the Ford Falcon FPV capture was the latest one so it was the one where I’d already made the mistakes of starting the recording too early and getting strange data. The lessons learned there were:

  1. Dirty data can make this task more difficult so having the cleanest data possible is ideal but sanitation is necessary to ensure accuracy
  2. Start recording right as you hit “Start Race” in Forza Horizon. You have a couple of seconds between hitting that and the race starting.
  3. Make sure your telemetry is actually recording. I had some issues with forza-telemetry only successfully recording the first session after opening it. I’ll poke around and see if I can find out what the problem is and how to remediate it but just know to check so you don’t need to repeat your data capture session.
  4. Anomalous data in an arcade-style game is to be expected. It comes from the strangest places.

ford_falcon.loc is a method that will help select records. All DataFrame objects have this method and it’s one of the bet ways to cherry pick or slice up data. By passing it the boolean expression ford_falcon['IsRaceOn'] == 1 the loc function will evaluate that for every record and return a DataFrame where the condition is True.

Let’s start with setting up a very basic pyplot object. Recall that we imported from matplotlib import pyplot as plt. That’s a very particular Python convention–aliasing your imports. Pyplot also uses that plt as somewhat of a singleton which is also a very Pythonic convention. This was all a bit confusing to me, initially. These aliases are also very standardized and readily used in examples without any context so just keep your wits about you and note the conventions.

fig, ax = plt.subplots()
ax.plot(race_falcon['CurrentRaceTime'],race_falcon['AccelerationX'], color="orange", label="Ford Falcon FPV")
ax.set_xlabel("Race Time")
ax.set_ylabel("Acceleration X")
ax.legend()

“This is the plot of X-axis acceleration versus race time of the Ford Falcon FPV in Forza Horizon 5.”

I realize now that yours may not be formatted quite like this but, in running through the notebook I changed the style at some point. As plt is a singleton the style gets changed across the board until it’s changed back. I will discuss styles in a later post. But you’ll notice some things here that aren’t really desirable. The two that jump out at me are the unit of measure in the Y-axis and the race time being presented in what looks to be seconds. These are both obstacles that can be readily overcome.

Ticking away the moments that make up a dull race

Every index on an axis is called a “tick” in the parlance of matplotlib. There are definite ways to format a tick and, in this case, the conversion of seconds into the format of minutes:seconds would be ideal. We can start that process by defining a function that will perform that operation.

def racetime_fmt(x, y):
    minutes, seconds = divmod(x, 60)
    return "{:.0f}".format(minutes) + ':' + "{:.0f}".format(seconds)

Python is a very pithy language so this may be difficult to interpret but I’ll do my best to explain it. The name of the function is racetime_fmt and the parameters, x and y, are the x axis tick value or the y axis tick value. I’m specifically wanting to format the x axis so I just ignored y for the time being. When you see minutes, seconds that’s very strange (but it’s already appeared) and that’s when functions can return multiple values, or even objects, and is also extremely Pythonic. divmod returns two values, the integer quotient and then the modulo (I need to look up that term to see if it’s correct) or remainder. Dividing x, which is measured in seconds, by 60 will give us the number of minutes and then the remained will be the number of seconds left. The actual string is constructed in the return statement by providing a string literal that’s the format that we want and applying it to the literal that is passed as a parameter (in this case the minutes and seconds values returned by divmod).

There’s significant documentation on Python strings and their formatting however it’s simplest to point out that this will give us integers with, very specifically, 0 digits after the decimal in a floating point number. We apply this formatter to the x axis with a simple declaration.

fig, ax = plt.subplots()
ax.plot(race_falcon['CurrentRaceTime'],race_falcon['AccelerationX'], color="orange", label="Ford Falcon FPV")
ax.set_xlabel("Race Time")
ax.set_ylabel("Acceleration X")
def racetime_fmt(x, y):
    minutes, seconds = divmod(x, 60)
    return "{:.0f}".format(minutes) + ':' + "{:.0f}".format(seconds)
ax.xaxis.set_major_formatter(tick.FuncFormatter(racetime_fmt))
ax.legend()

That function is a “functional formatter” for the tick object and will be applied to “major ticks” as seen by the function name. There are minor ticks as well but they aren’t being addressed currently.

“Formatted the x-axis to reflect standard minute and second notation.”

Nuthin but a g thang

The second grievance is that the measurements on y for acceleration are odd. We, as humans, tend to measure lateral acceleration–especially in vehicles–in the unit g. A g is the equivalent of the acceleration of planet Earth on objects on its surface. It’s literally one Earth “gravity” and you can think of the force on an object to be it’s weight on the surface of the earth if you’d like to. The acceleration of the Earth on objects is, for our purposes, 9.81 meters per second per second as 1 g. This means that 1 g of lateral acceleration means it feels like you would be pushed up against the side of the car with the same amount of force as your weight. DataFrames are very cool in that they can take what would ordinarily be operations on a singular thing and perform them on a row or column quickly.

race_falcon['AccelerationX'] / 9.81

0      -0.000418
1       0.000232
2      -0.002438
3      -0.001309
4       0.004816
          ...   
2061   -0.579115
2062   -0.361294
2063   -0.224737
2064   -0.083242
2065    0.000677
Name: AccelerationX, Length: 2066, dtype: float64

This looks for normal for a measurement in g. What else can we see about this column?

print ( abs(race_falcon['AccelerationX']).min() / 9.81 )
print ( abs(race_falcon['AccelerationX']).max() / 9.81 )
print ( abs(race_falcon['AccelerationX']).mean() / 9.81 )

4.253134556574923e-07
5.471163608562692
1.021939719205032

Note that I used abs to get the absolute value of the values in the column because Acceleration is a vector. According to the Forza documentation it’s lateral to the car (i.e from driver’s side to passenger’s side) with negative values being towards the left and positive values being towards the right. Using abs makes certain that we examine the magnitude of these values and not their directionality. 5 g’s of lateral acceleration is the anomalous value I mentioned before and, through analysis, I learned where it came from. You won’t ever get 5 g’s of turning in a Class A Ford Falcon FPV under normal circumstances–that’s Formula 1 territory. The takeaway here is that this raw data needs to be converted from meters per second per second to g to make sense (in the way I’m thinking about it). It’s actually trivially easy to make this happen, thankfully.

import matplotlib.ticker as tick #this is a new import to allow us to access the ticks on the axis
fig, ax = plt.subplots()
ax.plot(race_falcon['CurrentRaceTime'],race_falcon['AccelerationX'] / 9.81, color="orange", label="Ford Falcon FPV")
ax.set_xlabel("Race Time (M:S)")
ax.set_ylabel("Acceleration X (g)")

def racetime_fmt(x, y):
    minutes, seconds = divmod(x, 60)
    return "{:.0f}".format(minutes) + ':' + "{:.0f}".format(seconds)

ax.xaxis.set_major_formatter(tick.FuncFormatter(racetime_fmt))
ax.legend()

Yes, I realize that 9.81 is a “magic number” and when you get into “code smells” and the like this is not a recommended practice but for the sake of brevity I just used it. This gets us a more readable plot. Go ahead and add those units in there, too, otherwise your Physics teacher will be pissed.

“Formatted the x-axis and changed the plot of the y-axis to reflect desired measurement.”

That’s just step 1 in plotting and data analysis but it’s already been a beefy post. Keep your eyes peeled for part 2.

Hi, this post was checked with vale which is a content-aware linter. It was checked using the Microsoft style as well as some rules that I made. A summary of those results is below. More details as to how this was put together check out this post. This post had: 9 errors, 104 warnings and 0 suggestions For details on the linting of this post
 ./content/posts/data-science-for-fun.md
 9:8      warning  Use first person (such as       Microsoft.FirstPerson  
                   'my') sparingly.                                       
 9:143    error    More than 3 commas!             marktoso.TresComas     
 9:183    warning  Consider removing 'really'.     Microsoft.Adverbs      
 14:67    warning  Try to avoid using              Microsoft.We           
                   first-person plural like 'we'.                         
 14:120   warning  Try to avoid using              Microsoft.We           
                   first-person plural like 'we'.                         
 14:343   warning  Use first person (such as       Microsoft.FirstPerson  
                   'me') sparingly.                                       
 14:349   warning  Consider removing 'very'.       Microsoft.Adverbs      
 16:1     warning  Consider using 'these' instead  Microsoft.Wordiness    
                   of 'All of these'.                                     
 16:60    warning  Use first person (such as       Microsoft.FirstPerson  
                   'me') sparingly.                                       
 16:663   warning  Use first person (such as ' I   Microsoft.FirstPerson  
                   ') sparingly.                                          
 16:731   warning  Use first person (such as       Microsoft.FirstPerson  
                   'my') sparingly.                                       
 16:821   warning  Use first person (such as       Microsoft.FirstPerson  
                   'my') sparingly.                                       
 18:102   warning  Use first person (such as ' I   Microsoft.FirstPerson  
                   ') sparingly.                                          
 18:127   warning  Use first person (such as       Microsoft.FirstPerson  
                   'my') sparingly.                                       
 18:373   warning  Use first person (such as       Microsoft.FirstPerson  
                   'my') sparingly.                                       
 18:387   warning  Use first person (such as ' I   Microsoft.FirstPerson  
                   ') sparingly.                                          
 18:443   warning  Consider removing 'Very'.       Microsoft.Adverbs      
 21:96    error    Use 'it's' instead of 'It is'.  Microsoft.Contractions 
 21:102   warning  Consider removing               Microsoft.Adverbs      
                   'potentially'.                                         
 21:139   warning  Consider removing 'very'.       Microsoft.Adverbs      
 109:1    warning  Use first person (such as 'I    Microsoft.FirstPerson  
                   ') sparingly.                                          
 109:88   warning  Use first person (such as       Microsoft.FirstPerson  
                   'me') sparingly.                                       
 109:260  warning  Use first person (such as       Microsoft.FirstPerson  
                   'my') sparingly.                                       
 109:519  warning  Use first person (such as ' I   Microsoft.FirstPerson  
                   ') sparingly.                                          
 115:12   warning  Consider removing 'strictly'.   Microsoft.Adverbs      
 115:42   warning  Use first person (such as ' I   Microsoft.FirstPerson  
                   ') sparingly.                                          
 115:300  warning  Use first person (such as ' I   Microsoft.FirstPerson  
                   ') sparingly.                                          
 115:371  warning  Use first person (such as ' I   Microsoft.FirstPerson  
                   ') sparingly.                                          
 115:559  error    Use 'for example' instead of    Microsoft.Foreign      
                   'e.g.'.                                                
 115:564  warning  Use first person (such as       Microsoft.FirstPerson  
                   'me') sparingly.                                       
 118:183  warning  Consider removing 'heavily'.    Microsoft.Adverbs      
 118:530  warning  Try to avoid using              Microsoft.We           
                   first-person plural like 'We'.                         
 118:703  warning  Use first person (such as ' I   Microsoft.FirstPerson  
                   ') sparingly.                                          
 120:8    warning  Use first person (such as       Microsoft.FirstPerson  
                   'me') sparingly.                                       
 121:216  warning  Prefer 'afterward' over         Microsoft.Terms        
                   'afterwards'.                                          
 121:228  error    Use 'it's' instead of 'It is'.  Microsoft.Contractions 
 125:1    warning  Use first person (such as 'I    Microsoft.FirstPerson  
                   ') sparingly.                                          
 125:40   warning  Use first person (such as       Microsoft.FirstPerson  
                   'my') sparingly.                                       
 125:100  warning  Use first person (such as       Microsoft.FirstPerson  
                   'I'm') sparingly.                                      
 125:113  warning  Use first person (such as       Microsoft.FirstPerson  
                   'my') sparingly.                                       
 125:129  warning  Use first person (such as ' I   Microsoft.FirstPerson  
                   ') sparingly.                                          
 125:154  warning  Use first person (such as       Microsoft.FirstPerson  
                   'my') sparingly.                                       
 125:230  warning  Consider using 'can' instead    Microsoft.Wordiness    
                   of 'has the ability to'.                               
 128:390  warning  Use first person (such as       Microsoft.FirstPerson  
                   'my') sparingly.                                       
 128:431  warning  Use first person (such as ' I   Microsoft.FirstPerson  
                   ') sparingly.                                          
 131:1    warning  Use first person (such as 'I    Microsoft.FirstPerson  
                   ') sparingly.                                          
 131:60   warning  Use first person (such as       Microsoft.FirstPerson  
                   'my') sparingly.                                       
 131:136  warning  Use first person (such as ' I   Microsoft.FirstPerson  
                   ') sparingly.                                          
 131:218  warning  Use first person (such as ' I   Microsoft.FirstPerson  
                   ') sparingly.                                          
 131:341  warning  Consider removing 'greatly'.    Microsoft.Adverbs      
 131:367  warning  Use first person (such as ' I   Microsoft.FirstPerson  
                   ') sparingly.                                          
 133:122  warning  Use first person (such as       Microsoft.FirstPerson  
                   'my') sparingly.                                       
 133:274  warning  Try to avoid using              Microsoft.We           
                   first-person plural like 'we'.                         
 133:477  error    Use 'won't' instead of 'will    Microsoft.Contractions 
                   not'.                                                  
 133:597  warning  Use first person (such as ' I   Microsoft.FirstPerson  
                   ') sparingly.                                          
 140:157  warning  Consider removing 'very'.       Microsoft.Adverbs      
 140:341  warning  Try to avoid using              Microsoft.We           
                   first-person plural like 'we'.                         
 140:513  warning  Use first person (such as       Microsoft.FirstPerson  
                   'I'm') sparingly.                                      
 140:548  warning  Consider using 'all' instead    Microsoft.Wordiness    
                   of 'all of'.                                           
 140:555  warning  Use first person (such as       Microsoft.FirstPerson  
                   'my') sparingly.                                       
 153:954  warning  Try to avoid using              Microsoft.We           
                   first-person plural like 'we'.                         
 155:21   warning  Use first person (such as ' I   Microsoft.FirstPerson  
                   ') sparingly.                                          
 155:140  error    Use 'doesn't' instead of 'does  Microsoft.Contractions 
                   not'.                                                  
 155:223  warning  Try to avoid using              Microsoft.We           
                   first-person plural like 'we'.                         
 158:17   warning  Use first person (such as ' I   Microsoft.FirstPerson  
                   ') sparingly.                                          
 158:95   warning  Use first person (such as ' I   Microsoft.FirstPerson  
                   ') sparingly.                                          
 158:115  warning  Use first person (such as       Microsoft.FirstPerson  
                   'I'm') sparingly.                                      
 158:133  warning  Use first person (such as ' I   Microsoft.FirstPerson  
                   ') sparingly.                                          
 164:77   warning  Consider removing 'really'.     Microsoft.Adverbs      
 164:160  warning  Use first person (such as ' I   Microsoft.FirstPerson  
                   ') sparingly.                                          
 167:186  warning  Use first person (such as ' I   Microsoft.FirstPerson  
                   ') sparingly.                                          
 172:1    warning  Try to avoid using              Microsoft.We           
                   first-person plural like                               
                   'Let's'.                                               
 172:31   warning  Consider removing 'very'.       Microsoft.Adverbs      
 172:69   warning  Try to avoid using              Microsoft.We           
                   first-person plural like 'we'.                         
 172:130  warning  Consider removing 'very'.       Microsoft.Adverbs      
 172:259  warning  Consider removing 'very'.       Microsoft.Adverbs      
 172:317  warning  Use first person (such as       Microsoft.FirstPerson  
                   'me') sparingly.                                       
 172:355  warning  Consider removing 'very'.       Microsoft.Adverbs      
 172:377  warning  Consider removing 'readily'.    Microsoft.Adverbs      
 182:1    warning  Use first person (such as 'I    Microsoft.FirstPerson  
                   ') sparingly.                                          
 182:99   warning  Use first person (such as ' I   Microsoft.FirstPerson  
                   ') sparingly.                                          
 182:223  warning  Use first person (such as ' I   Microsoft.FirstPerson  
                   ') sparingly.                                          
 182:310  warning  Consider removing 'really'.     Microsoft.Adverbs      
 182:353  warning  Use first person (such as       Microsoft.FirstPerson  
                   'me') sparingly.                                       
 182:494  warning  Consider removing 'readily'.    Microsoft.Adverbs      
 185:211  warning  Try to avoid using              Microsoft.We           
                   first-person plural like 'We'.                         
 191:13   warning  Consider removing 'very'.       Microsoft.Adverbs      
 191:83   warning  Use first person (such as       Microsoft.FirstPerson  
                   'my') sparingly.                                       
 191:238  warning  Use first person (such as       Microsoft.FirstPerson  
                   'I'm') sparingly.                                      
 191:288  warning  Use first person (such as ' I   Microsoft.FirstPerson  
                   ') sparingly.                                          
 191:367  warning  Consider removing 'very'.       Microsoft.Adverbs      
 191:491  warning  Consider removing 'extremely'.  Microsoft.Adverbs      
 191:708  warning  Try to avoid using              Microsoft.We           
                   first-person plural like 'us'.                         
 191:903  warning  Try to avoid using              Microsoft.We           
                   first-person plural like 'we'.                         
 191:942  error    Use 'that's' instead of 'that   Microsoft.Contractions 
                   is'.                                                   
 193:178  warning  Try to avoid using              Microsoft.We           
                   first-person plural like 'us'.                         
 193:196  warning  Consider removing 'very'.       Microsoft.Adverbs      
 193:272  warning  Try to avoid using              Microsoft.We           
                   first-person plural like 'We'.                         
 210:80   warning  Try to avoid using              Microsoft.We           
                   first-person plural like 'We'.                         
 210:453  warning  Try to avoid using              Microsoft.We           
                   first-person plural like                               
                   'our'.                                                 
 210:687  warning  Consider removing 'very'.       Microsoft.Adverbs      
 210:811  warning  Consider removing 'quickly'.    Microsoft.Adverbs      
 227:61   warning  Try to avoid using              Microsoft.We           
                   first-person plural like 'we'.                         
 237:10   warning  Use first person (such as ' I   Microsoft.FirstPerson  
                   ') sparingly.                                          
 237:338  warning  Try to avoid using              Microsoft.We           
                   first-person plural like 'we'.                         
 237:461  warning  Use first person (such as ' I   Microsoft.FirstPerson  
                   ') sparingly.                                          
 237:503  warning  Use first person (such as ' I   Microsoft.FirstPerson  
                   ') sparingly.                                          
 237:717  error    Don't spell out the number in   Microsoft.Units        
                   'from meters'.                                         
 237:782  warning  Use first person (such as       Microsoft.FirstPerson  
                   'I'm') sparingly.                                      
 237:856  warning  Consider removing               Microsoft.Adverbs      
                   'thankfully'.                                          
 252:100  error    Use 'isn't' instead of 'is      Microsoft.Contractions 
                   not'.                                                  
 252:157  warning  Use first person (such as ' I   Microsoft.FirstPerson  
                   ') sparingly.                                          
 252:184  warning  Try to avoid using              Microsoft.We           
                   first-person plural like 'us'.                         
 258:210  warning  Use first person (such as ' I   Microsoft.FirstPerson  
                   ') sparingly.                                          

9 errors, 105 warnings and 0 suggestions in 1 file.