3

I'm trying to plot one set of data with two different y-axes using Plotly. This seems like it should have an easy answer, but I've been unsuccessful so far...

I'm analyzing some Strava data and have a plot of average speed vs. distance. I want another y-axis on the right to show the equivalent pace (1/speed). I've read and googled but have only found examples of how to plot multiple series on multiple y-axes, not one series on two y-axes. I've found some clunky workarounds suggesting plotting two traces and manually setting yranges, etc. but this seems like something that should be easy to do.

For something concrete to work with, here's an example of the desired result using matplotlib. Note we only plot the data once, but use functions to set the ticks on the secondary y-axis. Does anything like this exist in plotly?

import matplotlib.pyplot as plt

xx = [1, 2, 3, 4]
y1 = [8, 7, 6, 5]
y2 = [60. / y for y in y1]  # min/mile

fig, ax = plt.subplots()

ax.plot(xx, y1)
ax.set_ylabel('speed (miles/hr)')
ax.set_xlabel('distance (miles)')


def spd2pac(y):
    return 60. / y


def pac2spd(y):
    return 60. / y


secax = ax.secondary_yaxis('right', functions=(spd2pac, pac2spd))
secax.set_ylabel('pace (min/mile)')
plt.show()

desired result with matplotlib

user7828298
  • 95
  • 2
  • 7
  • 1
    I tried playing around with your example (i.e using None as traces, etc.) but didn't have much luck. However, if simply the values are important to you and not the second y axis, you can add your variable `y2` for the `text` parameter as follows `go.Scatter(x=xx, y=y1, text=y2)`. This will show you the pace in min/mi when the point is hovered on. – Jacob K Aug 26 '20 at 21:29
  • Doing this in Plotly is probably tricky as the second y-axis isn't linear. One workaround would be to add the integer paces `8, 9, ... , 12` as annotations, placing them where they should be located on the plot – Derek O Aug 27 '20 at 16:25

1 Answers1

1

The main difficulty is that the second y-axis isn't linear. As far as I know, Plotly doesn't have a way to add a second y-axis and specify a non-linear scale (that isn't a log base 10 scale).

One workaround would be to use annotations, placing the integer paces [8,9,10,11,12] where they would go on a linear scale (e.g. the pace of 8 min/mile should be located at y-coordinate 60/8 = 7.5, ..., 12 min/mile should be located at y-coordinate 60/12 = 5), giving the appearance of tick marks on the secondary y-axis. You can add the secondary y-axis title as an annotation as well to avoid overlapping the annotation tick marks.

However, this is purely visual, and if you want to include the pace information, you can add them as part of the hover template as suggested by @Jacob K

import plotly.graph_objects as go
from plotly.subplots import make_subplots

xx = [1, 2, 3, 4]
y1 = [8, 7, 6, 5]
y2 = [60. / y for y in y1]  # min/mile

fig = make_subplots(specs=[[{"secondary_y": True}]])

fig.add_trace(
    go.Scatter(
        x=xx, 
        y=y1, 
        marker_color="Blue",
        name="speed"
    )
)

fig.add_trace(
    go.Scatter(
        x=None, 
        y=None, 
    ),
    secondary_y=True
)

paces = [8, 9, 10, 11, 12]
y_coordinate = [60. / y for y in paces]

# hardcoded the x value
yaxes_dict = [dict(x=0.955, y=y_coordinate[idx], xref="paper", yref="y", text=str(y_val), showarrow=False) for idx, y_val in enumerate(paces)]
yaxes_title = [dict(x=0.97, y=6.5, xref="paper", yref="y", text='pace (min/mile)', textangle=-90, showarrow=False)]
fig.update_layout(annotations=yaxes_dict + yaxes_title)

fig['layout']['yaxis2'].update(ticks='', showticklabels=False)
fig['layout']['yaxis'].update(title='speed (miles/hour)')

fig.show()

enter image description here

Derek O
  • 16,770
  • 4
  • 24
  • 43