5

I am trying to create a figure containing several plots, and normalising them all so that their features can all be easily distinguished. I'm having some trouble wording what it is I'm trying to do, but the example code below should help clarify.

Example plot generated by the below code.

The code creates a figure with three lines on it. The black line's data varies between -1000 and 1000, so the scale is adjusted accordingly. This means that the variation in the green data, and even moreso in the red, is hard to see. I would ideally like to enlarge the green and red data so that their variation is clearer - but hopefully without just multiplying by a constant value.

Example of the result I'm hoping for - the different lines all have different orders of magnitude but have been fit to an arbitrary y-axis scale so their shape can be demonstrated.

import numpy as np
import matplotlib.pyplot as plt

time_arr = np.arange(0, 25, 1)

dist_arr = np.zeros(len(time_arr))
speed_arr = np.zeros(len(time_arr))
energy_arr = np.zeros(len(time_arr))

for i in range(len(time_arr)):
    dist_arr[i] = np.random.randint(-10, 10)
    speed_arr[i] = np.random.randint(-50, 50)
    energy_arr[i] = np.random.randint(-1000, 1000)


fig = plt.figure(figsize=(13,13))

plt.plot(time_arr, dist_arr, 'red', linestyle='-', label='dist (m)', linewidth=5)
plt.plot(time_arr, speed_arr, 'lime', linestyle='-', label='speed (m/s)', linewidth=5)
plt.plot(time_arr, energy_arr, 'black', linestyle='--', label='E_tot (J)', linewidth=5)

plt.xlabel('Time (s)', fontsize=25)
plt.ylabel('Various Params', fontsize=25)
plt.tick_params(axis='x', labelsize=20)
plt.tick_params(axis='y', labelsize=20)
plt.title('Various VS Time', fontsize = 32, y=1.008)
plt.legend(loc='best', fontsize=25)

plt.show()

I've tried playing around with things like plt.ylim([0, 100]) for each individual line plot, but that didn't seem to work out. Any assistance here would be fantastic, cheers.

EDIT:

Problem solved in the comments thanks to ImportanceOfBeingErnest's improved normalisation technique, combined with the accepted answer. Thanks!

Milo
  • 53
  • 1
  • 6
  • Which code produces this new figure that you show? In the code from the question there are no sin or cos curves. – ImportanceOfBeingErnest Apr 07 '17 at 20:58
  • @ImportanceOfBeingErnest sorry about that - I've edited the question so it shows up properly now. It's a lot more involved, which is why I was hoping my original miniature example code would be enough! – Milo Apr 07 '17 at 21:20
  • 2
    Your normalization function is not very useful. You could either normalize to the range between 0 and 1, `norm = lambda x: (x-x.min())/(x.max()-x.min())` or to the range around the mean, `norm = lambda x: 2*(x-x.mean())/(x.max()-x.min())` or similar. – ImportanceOfBeingErnest Apr 07 '17 at 21:49
  • Brilliant, this seems to have sorted the problem out. Thank you @ImportanceOfBeingErnest!! Is there a particular reason you've suggested using a lamda vs a regular function def here? – Milo Apr 08 '17 at 16:25
  • No, lambda is shorter and more clearly be written in comments section; that's all. – ImportanceOfBeingErnest Apr 08 '17 at 16:50

3 Answers3

5

you have the option of normalizing the data and also have multiple twin y axes. See below.

import numpy as np
import matplotlib.pyplot as plt

def norm(data):
    return (data)/(max(data)-min(data))

time_arr = np.arange(0, 25, 1)

dist_arr = np.zeros(len(time_arr))
speed_arr = np.zeros(len(time_arr))
energy_arr = np.zeros(len(time_arr))

for i in range(len(time_arr)):
    dist_arr[i] = np.random.randint(-10, 10)
    speed_arr[i] = np.random.randint(-50, 50)
    energy_arr[i] = np.random.randint(-1000, 1000)

# option 1
fig = plt.figure(figsize=(10,10))

plt.plot(time_arr, norm(dist_arr), 'red', linestyle='-', label='dist (m)', linewidth=5)
plt.plot(time_arr, norm(speed_arr), 'lime', linestyle='-', label='speed (m/s)', linewidth=5)
plt.plot(time_arr, norm(energy_arr), 'black', linestyle='--', label='E_tot (J)', linewidth=5)

plt.xlabel('Time (s)', fontsize=25)
plt.ylabel('Various Params', fontsize=25)
plt.tick_params(axis='x', labelsize=20)
plt.tick_params(axis='y', labelsize=20)
plt.title('Various VS Time', fontsize = 32, y=1.008)
plt.legend(loc='best', fontsize=25)

plt.show()

# option 2
fig, ax1 = plt.subplots(1,1,figsize=(12,9))

ax1.plot(time_arr, dist_arr, 'red', linestyle='-', label='dist (m)', linewidth=5)
ax1.set_xlabel('Time (s)', fontsize=25)
# Make the y-axis label, ticks and tick labels match the line color.
ax1.set_ylabel('Distance(m)', color='r',fontsize=25)
ax1.tick_params('y', colors='r', labelsize=20)

ax2 = ax1.twinx()
ax2.plot(time_arr, speed_arr, 'lime', linestyle='-', label='speed (m/s)', linewidth=5)
ax2.set_ylabel('Speed (m/s)', color='lime',fontsize=25)
ax2.tick_params('y', colors='lime', labelsize=20)

ax3 = ax1.twinx()
ax3.spines['right'].set_position(('axes', 1.25)) # move the right axis light bit to the right by 25 % of the axes
ax3.plot(time_arr, norm(energy_arr), 'black', linestyle='--', label='E_tot (J)', linewidth=5)
ax3.set_ylabel('E_tot (J)', color='black',fontsize=25)
ax3.tick_params('y', colors='black', labelsize=20)


fig.tight_layout()
plt.show()

results in

enter image description here enter image description here

plasmon360
  • 4,109
  • 1
  • 16
  • 19
  • I think this is exactly the sort of thing I'm looking for, but still seem to be having the same problem. I've edited my post with a further figure - the magenta and blue lines are sine curves, similar to the original target example I gave, but because of the scale of the axis and low amplitude of oscillation, this isn't visible at all. Any ideas? – Milo Apr 07 '17 at 20:44
0

I think you have two options:

  1. Sub-plots - see this demo. you could copy the "sharing both axes" case

  2. Twin y axis - see here. I think these plots are less clear as it is hard to see which scale to use for each curve.

I would recommend option 1

0

You should include (data -np.amin(x)) in the numerator to properly re-scale.

Koosul
  • 46
  • 1
  • 7