Data Science for Fun and Motorsport
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.
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:
- Dirty data can make this task more difficult so having the cleanest data possible is ideal but sanitation is necessary to ensure accuracy
- Start recording right as you hit “Start Race” in Forza Horizon. You have a couple of seconds between hitting that and the race starting.
- 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. - 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()
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.
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.
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.
./content/posts/data-science-for-fun.md 1:1 suggestion You averaged 2.19 complex marktoso.Kiss words per sentence 1:1 suggestion Try to keep the Flesch-Kincaid marktoso.Readability grade level (9.81) below 8. 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:79 suggestion Try to keep sentences short (< Microsoft.SentenceLength 30 words). 14:120 warning Try to avoid using Microsoft.We first-person plural like 'we'. 14:274 suggestion Consider using 'help' instead Microsoft.ComplexWords of 'assistance'. 14:313 suggestion Consider using 'part' instead Microsoft.ComplexWords of 'component'. 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:187 suggestion Consider using 'try' instead Microsoft.ComplexWords of 'endeavor'. 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:740 suggestion Verify your use of 'allows' Microsoft.Vocab with the A-Z word list. 16:821 warning Use first person (such as Microsoft.FirstPerson 'my') sparingly. 18:1 suggestion Try to keep sentences short (< Microsoft.SentenceLength 30 words). 18:6 suggestion 'is done' looks like passive Microsoft.Passive voice. 18:19 suggestion 'UDP' has no definition. Microsoft.Acronyms 18:86 suggestion 'is delivered' looks like Microsoft.Passive passive voice. 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:271 suggestion Try to keep sentences short (< Microsoft.SentenceLength 30 words). 18:373 warning Use first person (such as Microsoft.FirstPerson 'my') sparingly. 18:379 suggestion 'MBA' has no definition. Microsoft.Acronyms 18:387 warning Use first person (such as ' I Microsoft.FirstPerson ') sparingly. 18:438 suggestion 'HUD' has no definition. Microsoft.Acronyms 18:443 warning Consider removing 'Very'. Microsoft.Adverbs 21:96 error Use 'it's' instead of 'It is'. Microsoft.Contractions 21:96 suggestion Try to keep sentences short (< Microsoft.SentenceLength 30 words). 21:102 warning Consider removing Microsoft.Adverbs 'potentially'. 21:139 warning Consider removing 'very'. Microsoft.Adverbs 21:223 suggestion 'being recorded' looks like Microsoft.Passive passive voice. 21:250 suggestion 'being transmitted' looks like Microsoft.Passive passive voice. 109:1 suggestion Try to keep sentences short (< Microsoft.SentenceLength 30 words). 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:182 suggestion Try to keep sentences short (< Microsoft.SentenceLength 30 words). 109:260 warning Use first person (such as Microsoft.FirstPerson 'my') sparingly. 109:324 suggestion Consider using 'about' instead Microsoft.ComplexWords of 'approximately'. 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:126 suggestion 'is structured' looks like Microsoft.Passive passive voice. 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:443 suggestion 'is inexperienced' looks like Microsoft.Passive passive voice. 115:504 suggestion 'is inexperienced' looks like Microsoft.Passive passive voice. 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:85 suggestion 'be considered' looks like Microsoft.Passive passive voice. 118:123 suggestion Try to keep sentences short (< Microsoft.SentenceLength 30 words). 118:170 suggestion 'be leveraged' looks like Microsoft.Passive passive voice. 118:183 warning Consider removing 'heavily'. Microsoft.Adverbs 118:358 suggestion Consider using 'idea' instead Microsoft.ComplexWords of 'concept'. 118:530 warning Try to avoid using Microsoft.We first-person plural like 'We'. 118:642 suggestion Try to keep sentences short (< Microsoft.SentenceLength 30 words). 118:703 warning Use first person (such as ' I Microsoft.FirstPerson ') sparingly. 118:897 suggestion 'be applied' looks like Microsoft.Passive passive voice. 120:8 warning Use first person (such as Microsoft.FirstPerson 'me') sparingly. 121:64 suggestion Consider using 'idea' instead Microsoft.ComplexWords of 'concept'. 121:140 suggestion 'be saved' looks like passive Microsoft.Passive voice. 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'. 127:4 suggestion 'Google's Colaboratory' Microsoft.Headings should use sentence-style capitalization. 128:2 suggestion Try to keep sentences short (< Microsoft.SentenceLength 30 words). 128:79 suggestion 'GCP' has no definition. Microsoft.Acronyms 128:86 suggestion Consider using 'give' or Microsoft.ComplexWords 'offer' instead of 'provide'. 128:347 suggestion 'GCP' has no definition. Microsoft.Acronyms 128:390 warning Use first person (such as Microsoft.FirstPerson 'my') sparingly. 128:431 warning Use first person (such as ' I Microsoft.FirstPerson ') sparingly. 130:5 suggestion 'Migration Blues' should use Microsoft.Headings sentence-style capitalization. 131:1 warning Use first person (such as 'I Microsoft.FirstPerson ') sparingly. 131:1 suggestion Try to keep sentences short (< Microsoft.SentenceLength 30 words). 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:179 suggestion Try to keep sentences short (< Microsoft.SentenceLength 30 words). 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:161 suggestion Try to keep sentences short (< Microsoft.SentenceLength 30 words). 133:274 warning Try to avoid using Microsoft.We first-person plural like 'we'. 133:370 suggestion Try to keep sentences short (< Microsoft.SentenceLength 30 words). 133:477 error Use 'won't' instead of 'will Microsoft.Contractions not'. 133:589 suggestion 'YMMV' has no definition. Microsoft.Acronyms 133:597 warning Use first person (such as ' I Microsoft.FirstPerson ') sparingly. 140:27 suggestion Consider using 'give' or Microsoft.ComplexWords 'offer' instead of 'provide'. 140:95 suggestion Consider using 'allow' instead Microsoft.ComplexWords of 'authorize'. 140:147 suggestion Try to keep sentences short (< Microsoft.SentenceLength 30 words). 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:30 suggestion Try to keep sentences short (< Microsoft.SentenceLength 30 words). 153:154 suggestion 'is hooked' looks like passive Microsoft.Passive voice. 153:711 suggestion Try to keep sentences short (< Microsoft.SentenceLength 30 words). 153:809 suggestion Don't use language (such as Microsoft.Accessibility 'normal') that defines people by their disability. 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:161 suggestion 'being said' looks like Microsoft.Passive passive voice. 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:18 suggestion Try to keep sentences short (< Microsoft.SentenceLength 30 words). 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. 164:161 suggestion Try to keep sentences short (< Microsoft.SentenceLength 30 words). 164:203 suggestion 'FPV' has no definition. Microsoft.Acronyms 165:126 suggestion Verify your use of 'ensure' Microsoft.Vocab with the A-Z word list. 167:186 warning Use first person (such as ' I Microsoft.FirstPerson ') sparingly. 168:49 suggestion 'be expected' looks like Microsoft.Passive passive voice. 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 180:80 suggestion 'FPV' has no definition. Microsoft.Acronyms 182:1 warning Use first person (such as 'I Microsoft.FirstPerson ') sparingly. 182:34 suggestion 'be formatted' looks like Microsoft.Passive passive voice. 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:412 suggestion 'being presented' looks like Microsoft.Passive passive voice. 182:494 warning Consider removing 'readily'. Microsoft.Adverbs 185:24 suggestion 'is called' looks like passive Microsoft.Passive voice. 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:106 suggestion Try to keep sentences short (< Microsoft.SentenceLength 30 words). 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:445 suggestion Consider using 'many' instead Microsoft.ComplexWords of 'multiple'. 191:491 warning Consider removing 'extremely'. Microsoft.Adverbs 191:668 suggestion 'is measured' looks like Microsoft.Passive passive voice. 191:708 warning Try to avoid using Microsoft.We first-person plural like 'us'. 191:809 suggestion 'is constructed' looks like Microsoft.Passive passive voice. 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'. 191:947 suggestion 'is passed' looks like passive Microsoft.Passive voice. 193:1 suggestion Try to keep sentences short (< Microsoft.SentenceLength 30 words). 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'. 205:74 suggestion 'be applied' looks like Microsoft.Passive passive voice. 205:178 suggestion 'being addressed' looks like Microsoft.Passive passive voice. 210:80 warning Try to avoid using Microsoft.We first-person plural like 'We'. 210:187 suggestion Consider using 'equal' instead Microsoft.ComplexWords of 'equivalent'. 210:261 suggestion Try to keep sentences short (< Microsoft.SentenceLength 30 words). 210:453 warning Try to avoid using Microsoft.We first-person plural like 'our'. 210:511 suggestion Try to keep sentences short (< Microsoft.SentenceLength 30 words). 210:585 suggestion 'be pushed' looks like passive Microsoft.Passive voice. 210:598 suggestion Verify your use of 'against' Microsoft.Vocab with the A-Z word list. 210:687 warning Consider removing 'very'. Microsoft.Adverbs 210:811 warning Consider removing 'quickly'. Microsoft.Adverbs 227:16 suggestion Don't use language (such as Microsoft.Accessibility 'normal') that defines people by their disability. 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:112 suggestion Try to keep sentences short (< Microsoft.SentenceLength 30 words). 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:595 suggestion 'FPV' has no definition. Microsoft.Acronyms 237:605 suggestion Don't use language (such as Microsoft.Accessibility 'normal') that defines people by their disability. 237:655 suggestion Try to keep sentences short (< Microsoft.SentenceLength 30 words). 237:704 suggestion 'be converted' looks like Microsoft.Passive passive voice. 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:1 suggestion 'be pissed' looks like passive Microsoft.Passive voice. 252:1 suggestion Try to keep sentences short (< Microsoft.SentenceLength 30 words). 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:40 suggestion 'was checked' looks like Microsoft.Passive passive voice. 258:146 suggestion 'was checked' looks like Microsoft.Passive passive voice. 258:184 suggestion Verify your use of 'as well Microsoft.Vocab as' with the A-Z word list. 258:210 warning Use first person (such as ' I Microsoft.FirstPerson ') sparingly. 258:284 suggestion 'was put' looks like passive Microsoft.Passive voice.✖ 9 errors, 105 warnings and 84 suggestions in 1 file.