The goal of pct is to increase the accessibility and reproducibility of the data produced by the Propensity to Cycle Tool (PCT), a research project and web application hosted at For an overview of the data provided by the PCT, clicking on the previous link and trying it out is a great place to start. An academic paper on the PCT provides detail on the motivations for and methods underlying the project.

A major motivation behind the project was making transport evidence more accessible, encouraging evidence-based transport policies. The code base underlying the PCT is publicly available (see However, the code hosted there is not easy to run or reproduce, which is where this package comes in: it provides quick access to the data underlying the PCT and enables some of the key results to be reproduced quickly. It was developed primarily for educational purposes (including for upcoming PCT training courses) but it may be useful for people to build on the the methods, for example to create a scenario of cycling uptake in their town/city/region.

In summary, if you want to know how PCT works, be able to reproduce some of its results, and build scenarios of cycling uptake to inform transport policies enabling cycling in cities worldwide, this package is for you!


You can install the development version of the package as follows:


Load the package as follows:


Get PCT data

From feedback, we hear that the use of the data is critical in decision making. Therefore, one area where the package could be useful is making the data “easily” available to be processed.

  • get_pct: the basic function to obtain data available here.

The rest of these should be self explanatory.

  • get_pct_centroids
  • get_pct_lines
  • get_pct_rnet
  • get_pct_routes_fast
  • get_pct_routes_quiet
  • get_pct_zones
  • uptake_pct_godutch
  • uptake_pct_govtarget

For example, to get the centroids in West Yorkshire:

centroids = get_pct_centroids(region = "west-yorkshire")
#> Loading required package: sp
plot(centroids[, "geo_name"])

Likewise to download the desire lines for “west-yorkshire”:

lines = get_pct_lines(region = "west-yorkshire")
lines = lines[order(lines$all, decreasing = TRUE), c("all")]
plot(lines[1:10,], lwd = 4)

Estimate cycling uptake

An important part of the PCT is its ability to create model scenarios of cycling uptake. Key to the PCT uptake model is ‘distance decay’, meaning that short trips are more likely to be cycled than long trips. The functions uptake_pct_govtarget() and uptake_pct_godutch() implement uptake models used in the PCT, which use distance and hilliness per desire line as inputs and output the proportion of people who could be expected to cycle if that scenario were realised. The scenarios of cycling uptake produced by these functions are not predictions of what will happen, but illustrative snapshots of what could happen if overall propensity to cycle reached a certain level. The uptake levels produced by Go Dutch and Government Target scenarios (which represent increases in cycling, not final levels) are illustrated in the graph below (other scenarios could be produced, see the source code see how these models work):

The proportion of trips made by cycling along each origin-destination (OD) pair therefore depends on the trip distance and hilliness. The main input dataset into the PCT is OD data and, to convert each OD pair into a geographic desire line, geographic zone or centroids. Typical input data is provided in packaged datasets od_leeds and zones_leeds:

Reproduce PCT for Leeds

This example shows how scenarios of cycling uptake, and how ‘distance decay’ works (short trips are more likely to be cycled than long trips).

The input data looks like this (origin-destination data and geographic zone data):

The stplanr package can be used to convert the non-geographic OD data into geographic desire lines as follows:

We can convert these straight lines into routes with a routing service, e.g.:

We got useful information from this routing operation. We will add the desire line data onto vital data from the routes (from a cycling uptake perspective, distance and hilliness of routes):

routes_vital = sf::st_sf(
  sf::st_drop_geometry(desire_lines[c(1:3, 12)]),
  sf::st_drop_geometry(routes_fast[c("length", "av_incline")]),
  geometry = routes_fast$geometry

Now we estimate cycling uptake:

routes_vital$uptake = uptake_pct_govtarget(distance = routes_vital$length, gradient = routes_vital$av_incline)
#> Distance assumed in m, switching to km
routes_vital$bicycle_govtarget = routes_vital$bicycle +
  round(routes_vital$uptake * routes_vital$all)

Let’s see how many people started cycling:

sum(routes_vital$bicycle_govtarget) - sum(routes_vital$bicycle)
#> [1] 767

Nearly 1000 more people cycling to work, just in 10 desire is not bad! What % cycling is this, for those routes?

sum(routes_vital$bicycle_govtarget) / sum(routes_vital$all)
#> [1] 0.1152519
sum(routes_vital$bicycle) / sum(routes_vital$all)
#> [1] 0.03963324

It’s gone from 4% to 11%, a realistic increase if cycling were enabled by good infrastructure and policies.

Now: where to prioritise that infrastructure and those policies?

rnet = stplanr::overline2(routes_vital, attrib = c("bicycle", "bicycle_govtarget"))
lwd = rnet$bicycle_govtarget / mean(rnet$bicycle_govtarget)
plot(rnet["bicycle_govtarget"], lwd = lwd)

We can view the results in an interactive map and share with policy makers, stakeholders, and the public! E.g. (see interactive map here):

Current limitations

  • This package currently does not estimate cycling uptake associated with intrazonal flows and people with no fixed job data
  • This package currently does not estimate health benefits

Next steps and further resources (work in progress)

  • Add additional scenarios of cycling uptake from different places (e.g. goCambridge)
  • Add additional distance decay functions
  • Make it easy to use data from other cities around the world
  • Show how to create raster tiles of cycling uptake