Cycling gear ratio calculator for R (or why you should just go buy a SRAM Eagle 1×12 group right now)

Don’t care about R functions? Fine then. Jump past all the code to my analysis of single ring cyclocross setups or to my analysis of SRAM’s new 1×2 Eagle group.

It took the end of a bottle of Jägermeister Winterkräuter to finish this off, but here it is. In the spirit of the incogitable Sheldon Brown (whose name will come up anytime I need an authoritative word) I offer a simple gear ratio calculator function for the R programming environment. I also include two case studies at the end that look at 1) single vs. double ring cyclocross setups and 2) SRAMs new Eagle 12-speed group.

This probably belongs in a package with other semi-useless stuff like the running pace calculator functions so it can achieve cult status among R fortunes and the like. But for now, it’s just a bunch of code on a blog site that nobody reads anyway…where it can do no harm.

Okay, enough self-deprecation (for now). Here’s what I’ve got.

Included here is a trilogy of functions that start simple, progress through humble wanderings, and finish in a spectacular crescendo (you were forewarned about the Jägermeister):

  1. The getCogs function will retrieve standard cassette tooth counts for a variety of normal cassettes. Normal is subjective, of course, but if you have something that isn’t on the list, you’re weird.
  2. The wheelSize function (shockingly) calculates wheel sizes. Yes, you need to know the ISO measurement for your wheels. Before you complain about that, check here. Or for a less snarky link, Sheldon Brown. Or measuring tape.
  3. The getGears function, wait for it, calculates the gear ratios and kicks them out as tables of (somewhat)useful numbers.

With the three functions, you should be able to progress from blissfully ignorant cyclist (like still riding for fun, or whatever) to deeply concerned, humourless, opinionated, saddles-must-match-the-bar-tape, and what-idiot-would-run-a-39T-for-cross kind of cyclist, which is sure to make you friends on the next group ride.

Before we begin, to use the getCogs function, you’ll need to download an R object (since I haven’t packaged this up yet). Grab the cassette key here and unzip it into your R working directory.

The functions

First, use getCogs to retrieve standard cassette tooth counts for 10-, 11-, and (seriously) 12-speed cassettes from Shimano, SRAM, and Campy. Got something else? Sorry, you’ll have to enter it manually…or just go buy a SRAM Eagle group…but we’ll get to that later. Every time you use this function, please consider the painstaking data entry I had to do to make this table.

# Function to generate standard cassette spreads
# Requires the "CassetteKey.RData" file in the working directory
getCogs <- function(discipline, brand, speeds, small, large){
  # discipline (character) = one of "road" or "mountain"
  # brand      (character) = one of "Shimano", "SRAM", or "Campagnolo" (or "Campy" of course)
  # speeds     (numeric) = number of speeds; only supports 10 or 11 or 12 for now
  # small      (numeric) = size of the smallest cog
  # large      (numeric) = size of the largest cog
  output <- c()
  if(brand=="Campy"|brand=="campy"){brand <- "Campagnolo"}
  cogs <- as.numeric(cassetteKey[cassetteKey$use==tolower(discipline) & 
                                 cassetteKey$brand==tolower(brand) & 
                                 cassetteKey$speeds==speeds & 
                                 cassetteKey$small==small & 
                                 cassetteKey$large==large, -1:-5])
  output <- c(output, cogs[!])
  if(length(output)==0){print("No matching cassettes found. Sorry.")} else {output}

getCogs(discipline="Road", brand="SRAM", speeds=11, small=11, large=28)
[1] 11 12 13 14 15 16 17 19 22 25 28

getCogs("Road", "campy", 10, 13, 29)  # Of course Campy has a 13.
[1] 13 14 15 16 17 19 21 23 26 29

getCogs("Mountain", "Shimano", 12, 10, 50)  # You wish.
[1] "No matching cassettes found. Sorry."

Second, use wheelSize to calculate the circumference of your wheel + tire in millimetres. To do this, you’ll need to know the ISO bead seat diameter of your rims and the size of the tire that you’re running. The rim diameter must be entered in mm while the size of the tire can be specified in inches as with most mountain bike tires (e.g. 1.8 or 2.3) or in “c” as with most road and cyclocross tires (e.g. 23c or 32c). For the triathletes in the crowd, the most accurate way is to mark your floor with a complete wheel rotation and measure between the marks.

# Function to calculate wheel size
wheelSize <- function(rimSize, tireSize, tireUnits){
  # rimSize   (numeric) = ISO rim size, see wheelKey table or measure in mm
  # tireSize  (numeric) = size of the tire
  # tireUnits (character) = units of tireSize, either "inches" or "mm"
  tireSize <- ifelse(tolower(tireUnits)=="inches", tireSize*25.4, tireSize)
  diam <- rimSize + tireSize*2
  round(pi * diam)

# For a typical 26" mountain bike setup with 2.1 tires
wheelSize(rimSize=622, tireSize=2.1, tireUnits="inches")
[1] 2091

# For a typical 700c road setup with 23c tires
[1] 2099

Third, use getGears to generate tables of gear ratios, gear inches, development metres, and a variety of speeds at given RPMs. I have not included Sheldon Brown’s Gain Ratio because 1) it belongs to him, and 2) I’m not sure I fully get it yet. But I’ll keep thinking about it. If you want other speeds (like miles/hr) at different RPMs (like 90, where we all live), you can easily add them yourself to the function below.

# Function to generate gear ratio tables
getGears <- function(chainrings, cogs, wheelCirc){
  # chainrings  (integer) = teeth count of front chainring(s); may be a vector
  # cogs        (integer) = teeth count of rear cog(s); may be a vector
  # wheelCirc   (integer) = circumference of the drive wheel in mm
  # crankLength (integer) = length of the crank arms
  for(i in chainrings){
    if(i==chainrings[1]){output <- list()}
    geartab <- data.frame(chainring=i,cog=cogs)
    geartab$gearRatio   <- round(i/geartab$cog,2)
    geartab$gearInches  <- round(geartab$gearRatio*wheelCirc/pi/25.4,2)
    geartab$develMetres <- round(geartab$gearRatio*wheelCirc/1000,2)
    geartab$KPH.50RPM   <- ((geartab$develMetres/1000)*50)*60
    geartab$KPH.100RPM  <- ((geartab$develMetres/1000)*100)*60
    output[[paste0(i,"T")]] <- geartab

# Example for a typical road setup
# Standard 53-39 front
myRings <- c(39,53)
# 11-speed 12-28 on the rear
myCogs <- getCogs("Road","Shimano",11,12,28)
# Running 700x23c
myWheel <- wheelSize(622,23,"mm")

getGears(myRings, myCogs, myWheel)
  chainring cog gearRatio gearInches develMetres KPH.50RPM KPH.100RPM
1        39  12      3.25      85.49        6.82     20.46      40.92
2        39  13      3.00      78.91        6.30     18.90      37.80
3        39  14      2.79      73.39        5.86     17.58      35.16
4        39  15      2.60      68.39        5.46     16.38      32.76
5        39  16      2.44      64.18        5.12     15.36      30.72
6        39  17      2.29      60.24        4.81     14.43      28.86
7        39  19      2.05      53.92        4.30     12.90      25.80
8        39  21      1.86      48.93        3.90     11.70      23.40
9        39  23      1.70      44.72        3.57     10.71      21.42
10       39  25      1.56      41.03        3.27      9.81      19.62
11       39  28      1.39      36.56        2.92      8.76      17.52

  chainring cog gearRatio gearInches develMetres KPH.50RPM KPH.100RPM
1        53  12      4.42     116.27        9.28     27.84      55.68
2        53  13      4.08     107.32        8.56     25.68      51.36
3        53  14      3.79      99.69        7.96     23.88      47.76
4        53  15      3.53      92.85        7.41     22.23      44.46
5        53  16      3.31      87.07        6.95     20.85      41.70
6        53  17      3.12      82.07        6.55     19.65      39.30
7        53  19      2.79      73.39        5.86     17.58      35.16
8        53  21      2.52      66.29        5.29     15.87      31.74
9        53  23      2.30      60.50        4.83     14.49      28.98
10       53  25      2.12      55.77        4.45     13.35      26.70
11       53  28      1.89      49.72        3.97     11.91      23.82

Checking my output against Sheldon Brown’s calculator (the ultimate validation) yields very similar results. Phew!

If you want to visualise this, you can simply rbind the tables (and melt them if ggplot is your thing) to see the gear ratios (or whichever metric you like). I even did something fancy and shaded those nasty cross-chaining gears that you should never use (except to keep chain tension on rough descents, see #2 here).

Gear Ratio Example

Okay, great. So why do this? Why not just use Sheldon Brown’s spectacular online calculator? Well, I wanted more comprehensive and flexible outputs so I could look a bit deeper into a couple of the deep philosophical cycling questions of our time:

  1. Should I really be running a single ring for cyclocross?
  2. How excited should I be to move to SRAM’s new 1 x 12?

Is it hurting me to run a single ring for CX?

Maybe your calves have their own Instagram feed. Maybe you think “spinning” at more than 75 RPM is for 100lb girls on 29ers? Or maybe you’re like me and just love the simplicity, the purity, the silence, and the weight savings of a single front ring. Whatever the reason, you’ve probably had occasional misgivings about tossing your front derailleur. Well, stress no more.

This analysis is based on a 700x32C setup running an 11-28T cassette on either a single 38T front (here called CX1) or a typical 36-46T cross double (here called CX2). I chose to look at gear ratios as a straight comparison and speeds at 80RPM, which is about what I figure a CX rider would average.

CX Comparison

The comparison is pretty straightforward: you’re really only losing at the high gear (fast) end when you move to a single ring, which isn’t too surprising and probably not really detrimental. At 80 RPM, you can still put down 35kph with your single and you get about another 5kph for each 10RPM that you add. Might you lose in a pavement sprint at CX worlds? Sure. But then you should have attacked earlier. Or counted laps properly.

A larger concern here is working around the cross-chaining ranges in the small chainring. When coming out of slower corners (~5kph) you definitely want the low end of the small chainring (big rear) but you need to quickly accelerate in the straights to around 30kph in many cases, putting you in cross-chaining territory at 80-90RPM. In practice (on the rare occasions that I ride CX with a double) I notice the desire to shift the front up in the straights and drop the front again in the corners. With a single ring setup, you’ve got 9-22kph covered at 50RPM (fine for getting out of corners) and 14-35kph covered at 80RPM (fine for the straights).

Conclusion: Is your single ring hurting you? In the results, probably not. In the legs, of course it is. But so would your double. In cross, everything hurts, so stick with your single ring and keep your left hand free for the next beer hand-up.

Okay, so what about the new SRAM 12-speed?

First, it’s gold. I mean, LOOK AT THAT.


Photo: BikeRumor

Still not convinced? Fine, keep reading.

This comparison is based on a typical XC setup (29er with 2.1 tires) and compares between 2×10 (39-26 front, 11-36 rear), 1×11 (30 front, 10-42 rear), and the new Eagle 1×12 (38 front, 10-50 rear). You read that right: a 50T on the rear. To get a sense for overall riding conditions, I looked at differences in speeds at 50RPM (i.e. climbing and mashing cadence) to 90RPM (i.e. normal spinning cadence).

SRAM Comparison

For starters, the gear range on Eagle is impressive. There’s not much contest at the high gear (fast) end of things. At 90RPM, the larger front ring on the 1×12 provides an extra 3kph over 2×10 and an extra 10kph(!) over 1×11 (narrow that to 8kph if you run a 32T front). That should pretty much silence the “not enough fast gears” arguments against 1x mountain setups.

At the slow end, there’s not much in the way of sacrifice of climbing gears. While you might expect the double on the 2×10 to offer the best (easiest) climbing gear, that’s actually not the case, owing to the 36T being the largest ring on the 10-speed cassette. The honour of lowest gear actually goes to the 1×11 (30/42=0.71), then the 2×10 (26/36=0.72), and then the 1×12 (38/50=0.76). In reality on the bike, however, this means a difference of less than 0.5kph at 50RPM in the easiest gear, and you can make that up by dropping down to the 36T on the front of the 1×12. Though that 0.5kph will will cost you about 2.5kph at the fast end at 90RPM, so think about how bad you really want that ultra-granny gear.

Now, I’m not here to give free PR to SRAM. While definitely a SRAM guy, I of course don’t love ALL of their offerings (I’m sure if I thought hard I could come up with something… maybe). My first response to hearing of 1×12 was the same as everyone else’s: “Seriously?” However, the gear spread looks pretty good and the gain in top speed over 1×11 is pretty astonishing. That, combined with SRAM’s claims about increased durability and low friction (if they are to be believed), could make Eagle the holy grail group that we’ve all been waiting for.

That said, I haven’t pre-ordered my 1×12 setup yet because, while not actually plated in real gold, you wouldn’t know it from the price. Given that I’ve rocked 2×10 for this long (postdoc salary, remember), I think I can probably wait another season for the inevitable GX Eagle, which will no doubt cost about $1000 less and weigh 8 grams more. And by then maybe I’ll be ready for a Boost wheelset too. But in the meantime, I will pine for it.

Conclusion: The front derailleur is dead, and SRAM has killed it. So it goes.

P.S. – SRAM, if when you read this: when is Force CX12 coming out? Can I demo it? No? Okay then.

This entry was posted in R, Sports and tagged , , , , , , , , , , , , , , . Bookmark the permalink.

8 Responses to Cycling gear ratio calculator for R (or why you should just go buy a SRAM Eagle 1×12 group right now)

  1. tbulger says:

    Sheldon Brown would be proud of your work here.

    Liked by 1 person

  2. Kyle Goldstankowitz says:

    Any chance you could tell us what percent grade Nino Schurter could climb in the easiest Eagle gear on a normal dry day where traction is not a part of the problem?

    Liked by 1 person

  3. Maxime says:

    Thank for your awesome work ! Could you update the CassetteKey.RData folder please it is no longer available ?



  4. Maxime says:

    It is the first that I use R and even a programing software but when I paste :
    getCogs <- function("mountain", "SRAM", 12, 10, 50){
    I got this error :
    Erreur : unexpected string constant in "getCogs <- function("mountain""

    Could you help me please ?


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s