Understanding BIXI Commuters: An Analysis of Montreal’s Bike Share System in Python
By: Gregoire C-M
Tags: Python, Bikeshare, Linear Regression, Data Science, Data Visualization

The visualizations above were created using Folium. Folium allows the user to create geographical visualizations, it can be installed using pip. The visualizations above were created using the following code. It should be noted that the data frame passed to the function contains the net influx or out flux associated to each station for a specific time slot.

import folium

def densityMap(stations):



#generate a new map

Montreal = [45.508154, -73.587450]

map = folium.Map(location = Montreal,

zoom_start = 12,

tiles = “CartoDB positron”)



#calculate stations radius

stations[‘radius’] = pd.Series( index=data.index)

stations[‘radius’] = np.abs(stations[‘net_departures’])

stations[‘radius’] = stations[‘radius’].astype(float) #set stations color

stations[‘color’] = ‘#E80018’ # red

stations.loc[stations[‘net_departures’].between(-10000,0), ‘color’] = ‘#00E85C’ # green



lat = stations[‘latitude’].values

lon = stations[‘longitude’].values

name = stations[‘name’].values

rad = stations[‘radius’].values

color = stations[‘color’].values

net_dep = stations[‘net_departures’]



#populate map

for _lat, _lon, _rad, _color, _name, _nd in zip(lat, lon, rad, color, name, net_dep):

folium.Circle(location = [_lat,_lon],

radius = _rad/5,

color = _color,

tooltip = _name + “ / net. dep:” +str(_nd),

fill = True).add_to(map)



#save map

f = ‘maps/map_density.html’

map.save(f)

Correlation between biking habits and temperature

Now that its been established that bikers commute to and from work using BIXI, lets look at other predicting factors. Montreal is a city that knows a high range of temperatures, it can easily get as hot as 30 °C in the summer and as low as -30 °C in the winter. The bikes from BIXI are taken off the streets in November and brought back in April. It is worth asking how well does temperature predicts the usage of the network.

Each dot is the count of trips on a given day in 2018

Looking at the plot above, it can be seen that daily ridership increases as temperature gets warmer, the number of predicted trips triples between 0 and 20 °C from 10,000 to 30,000 trips. It could be be argued that the third degree polynomial regression provides the best fit fort this situation as one would expect the daily ridership to remain low as the temperature is around the freezing point. The daily ridership would then increase rapidly as the temperature gets warm but then stagnate and even decrease as it gets too hot. Looking at the plot it seems like the number of daily trips starts to decrease around 22 °C. This decrease could also be due to people being on vacation during the hotter months.

From the R squared score it can be seen that 70% of the variability is explained by the temperature with polynomial regression of order three, other factors to consider could be the weekend vs the week. I have also tried to correlate the distance from subway stations but the results were inconclusive.

Variance r2 score linear: 0.63

Variance r2 score poly2: 0.65

Variance r2 score poly3: 0.70

A regression can be performed in python using the library Sklearn. The average daily temperature used for this analysis came from an open data set from the Government of Canada. Here is the code that was used to perform this linear regression.

import matplotlib.pyplot as plt

from sklearn.linear_model import LinearRegression

from sklearn.preprocessing import PolynomialFeatures

from sklearn.metrics import r2_score def scatterPlot(dataDay):



X = dataDay[[‘Mean Temp (°C)’]].values

X_1 = dataDay[[‘Mean Temp (°C)’]].values

X_2 = dataDay[[‘Mean Temp (°C)’]].values

X_3 = dataDay[[‘Mean Temp (°C)’]].values

Y = dataDay[[‘departures_cnt’]].values



#Linear regression

linear_regressor = LinearRegression()

linear_regressor.fit(X, Y)

Y_pred_linear = linear_regressor.predict(X)

print(‘Variance score linear: %.2f’ % r2_score(Y, Y_pred_linear))



#polynmial degree 2 regression

polynomial_feat = PolynomialFeatures(degree=2)

x_poly_2 = polynomial_feat.fit_transform(X)

polynomial_regressor = LinearRegression()

polynomial_regressor.fit(x_poly_2, Y)



Y_pred_poly_2 = polynomial_regressor.predict(x_poly_2)

print(‘Variance score poly2: %.2f’ % r2_score(Y, Y_pred_poly_2))



#polynmial degree 3 regression

polynomial_feat_3 = PolynomialFeatures(degree=3)

x_poly_3 = polynomial_feat_3.fit_transform(X)

polynomial_regressor_3 = LinearRegression()

polynomial_regressor_3.fit(x_poly_3, Y)



Y_pred_poly_3 = polynomial_regressor_3.predict(x_poly_3)

print(‘Variance score poly3: %.2f’ % r2_score(Y, Y_pred_poly_3)) #Ploting the data

plt.figure(figsize=(20, 10))

plt.title(‘Daily Bike Ridership With Regards to Temperature’)

plt.scatter(X_1,Y,c=’blue’,marker=’o’)

plt.xlabel(‘Mean Temperature (°C)’)

plt.ylabel(‘Number of Daily Trips’)

plt.plot(X_1, Y_pred_linear, color=’red’)



sort_axis = operator.itemgetter(0)

sorted_zip = sorted(zip(X_2,Y_pred_poly_2), key=sort_axis)

X_2, Y_pred_poly_2 = zip(*sorted_zip)

plt.plot(X_2, Y_pred_poly_2, color=’green’)



sort_axis = operator.itemgetter(0)

sorted_zip = sorted(zip(X_3,Y_pred_poly_3), key=sort_axis)

X_3, Y_pred_poly_3 = zip(*sorted_zip)

plt.plot(X_3, Y_pred_poly_3, color=’magenta’)



plt.plot(X_1, Y_pred_linear, ‘-r’, label=’degree=1')

plt.plot(X_2, Y_pred_poly_2, ‘-g’, label=’degree=2')

plt.plot(X_3, Y_pred_poly_3, ‘-m’, label=’degree=3')

plt.legend(loc=’upper left’)



plt.rcParams.update({‘font.size’: 22})

plt.show()

Cluster and graph analysis of the data

The last portion of this analysis is about estimating the traffic throughout the network. To do so spectral clusters were formed with the stations. Spectral clustering is a technique from graph theory. It seeks to identify groups of nodes, in this case stations, by using the edges connecting them, in this case trips. To do so it minimizes the number of trips between different clusters and maximize trips inside clusters. In the end it gives a idea of the geographical area riders stay within when taking a bike from a station.

From the map bellow it can be seen that 53% of trips in 2018 happened within the blue cluster showing that most of the traffic in the network is happening between the Plateau neighborhood and downtown. Moreover it can be seen that most of the clusters are delimited by geographical features, which are often highways or train tracks. For instance the split between the red and the blue clusters is mostly happening along a train track. Further the downtown area and the Old Port neighborhood are split between the blue and the purple cluster along the Ville-Marie highway.

Each dot on the map is a station and its color identifies to cluster to which it pertains. On the graph on the right the color of the nodes identifies the cluster and the vertices identify the percentage of the traffic that occurred within or between clusters in 2018. for Instance 5.02% or the traffic occurred from a red station to a blue station in 2018. Edges representing less than 0.5% of yearly traffic have been removed.

Looking at the graph bellow, one could hypothesized that to foster cycling in Montreal the city should invest in consolidating the network where bikers are already present, in this case the blue cluster and foster links to the other clusters. For instance, the city should ensure that safe bike paths are available in the blue cluster and that bikers can also efficiently and safely cross highways or train tracks that are cutting them from other adjacent clusters.

The graph analysis above was performed using the library folium and Diagraph from the library Graphviz. Both of which have to be installed using pip. Plotting of the map was similar to the one above. A Graphviz can quickly be generated using a Pandas data frame with the following code.

def create_graphviz(data):

#create the graph

G = Digraph(format=’png’)

G.attr(rankdir=’LR’, size=’10')

G.attr(‘node’, shape=’circle’)

nodelist = [] #prepare the data

data = data.drop([‘count’], axis=1)

data[‘from’] = pd.to_numeric(data[‘from’], downcast=’integer’)

data[‘to’] = pd.to_numeric(data[‘to’], downcast=’integer’)

data[‘from’] = data[‘from’].apply(str)

data[‘to’] = data[‘to’].apply(str)

#add the nodes and edges to the graph

for idx, row in data.iterrows():

node1, node2, weight = [str(i) for i in row] if node1 not in nodelist:

G.node(node1)

nodelist.append(node2)

if node2 not in nodelist:

G.node(node2)

nodelist.append(node2) #transform the edge label to XX,XX% format

percent = float(weight)

percent = percent*100

percent = round(percent, 2)

percent = str(percent)

G.edge(node1,node2, label = (“”+ percent +” %”)) #show graph

G.render(‘bixi_graph’, view=True)

Conclusion

To conclude, this exploratory analysis has been conducted using BIXI data from 2018 and May 2019. It has been seen the commuters use BIXI to go to and from work during the week and for leisure on weekends. Also, the use of the network is heavily influenced by the temperature as there are less users when the weather gets cold. Moreover, most users of the network remain around the Downtown, Plateau and surrounding areas. This could be due to the fact that geographical barriers such as highways and train tracks make their commute to nearby neighborhoods difficult.

Further analysis would also be useful to understand trends and patterns such as the specific paths users take to commute in the morning and at night. This would also provide great insights on bikers needs for a bike path network. Access to additional data such as stations capacity would allow for a better understanding of the network’s limitations and need for re balancing (bringing bikes to other stations by truck). As such, painting a complete picture of the situation, towards optimizing the BIXI and biking offer in Montreal.

Finally, I would like to thank other developers and writers who inspired my work with their own analysis more specifically Todd W. Schneider, Eden Au and Jon Nordby.