How to add ticks for minutes and hours to a y-axis of logarithmic seconds?

Viewed 147

My code right now looks like this:

import math 
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from matplotlib.pyplot import figure

rows = np.around(np.logspace(math.log10(50),math.log10(10000),8))
rows = rows.astype(int)

# Plot runtimes
sns.set_style("whitegrid")
plt.rcParams["font.family"] = "Times New Roman"
figure(num=None,figsize=(9, 6), dpi=160, facecolor='w', edgecolor='k')
runtime = np.zeros((3,len(rows)))

runtime[0]=[1,1,3,34,104,445,2000,8000]
runtime[1]=[4,20,100,5000,26000,200000,'nan','nan']
runtime[2]=[0.5,0.5,3,30,80,300,1300,5000]

extraticks=[60]
extravalue=[60]

plt.plot(rows,runtime[0], label="A", marker='o')
plt.plot(rows,runtime[1], label="B", marker='o')
plt.plot(rows,runtime[2], label="C", marker='o')

plt.xscale("log"), plt.yscale("log")
plt.xticks(rows, labels=rows)
#plt.yticks(list(plt.yticks()[0])+extraticks,list(plt.yticks()[0])+extravalue)

plt.xlabel("Sample size")
plt.ylabel("Runtime [s]")
plt.legend()

Which gives me the following results (where I painted extra lines to show the desired outcome):

enter image description here

How would I go about adding those extra lines and ticks? I tried something like this as you can see in the commented line, but it messed up my previously working ticks and labels.

2 Answers

An idea is to use the minor y ticks, both for labels and for extra grid lines. A special formatter displays the labels as minutes, hours, days. (matplotlib 3.4 is needed to directly set the formatter, older versions need a FunctionFormatter to set the custom formatter). Optionally, a different color and fontsize can be used for these new ticks.

To leave out the overlapping y tick label at 105, a special formatter can test for that power and set an empty label. The corresponding grid line will still be drawn as there still will be a tick position.

import matplotlib.pyplot as plt
from matplotlib.ticker import FixedLocator, ScalarFormatter
import seaborn as sns
import numpy as np

def show_pow_10(x, pos):
    power = np.log10(x)
    return '' if power == 5 else f'$10^{power:.0f}$'

def show_day_hour_min(x, pos):
    if x < 60:
        return f'{x:.0f} s'
    else:
        x = np.round(x / 60)
        if x < 60:
            return f'{x:.0f} min'
        else:
            x = np.round(x / 60)
            return f'{x:.0f} h'

rows = np.around(np.logspace(np.log10(50), np.log10(10000), 8))
rows = rows.astype(int)

# Plot runtimes
sns.set_style("whitegrid")
plt.rcParams["font.family"] = "Times New Roman"
plt.figure(num=None, figsize=(9, 6), dpi=160, facecolor='w', edgecolor='k')
ax = plt.gca()
runtime = np.zeros((3, len(rows)))

runtime[0] = [1, 1, 3, 34, 104, 445, 2000, 8000]
runtime[1] = [4, 20, 100, 5000, 26000, 200000, np.nan, np.nan]
runtime[2] = [0.5, 0.5, 3, 30, 80, 300, 1300, 5000]

# extraticks = [60, 60 * 60, 24 * 60 * 60]
extraticks = [60, 10*60, 60 * 60, 10*60*60, 24 * 60 * 60]

ax.plot(rows, runtime[0], label="A", marker='o')
ax.plot(rows, runtime[1], label="B", marker='o')
ax.plot(rows, runtime[2], label="C", marker='o')

ax.set_xscale("log")
ax.set_yscale("log")
ax.xaxis.set_major_locator(FixedLocator(rows))
ax.xaxis.set_major_formatter(ScalarFormatter())
ax.yaxis.set_major_formatter(show_pow_10)
ax.set_yticks(extraticks, minor=True)
ax.tick_params(axis='y', which='minor', labelcolor='dodgerblue', labelsize=9)
ax.yaxis.set_minor_locator(FixedLocator(extraticks))
ax.yaxis.set_minor_formatter(show_day_hour_min)

ax.set_xlabel("Sample size")
ax.set_ylabel("Runtime [s]")
ax.grid(True, which='minor', axis='y', color='dodgerblue', ls='--', lw=0.5, alpha=0.6)
ax.legend()
plt.show()

using minor ticks to indicate minute, hour, day

Here's my shot...crude but effective:

import math 
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from matplotlib.pyplot import figure

rows = np.around(np.logspace(math.log10(50),math.log10(10000),8))
rows = rows.astype(int)

# Plot runtimes
sns.set_style("whitegrid")
plt.rcParams["font.family"] = "Times New Roman"
figure(num=None,figsize=(9, 6), dpi=160, facecolor='w', edgecolor='k')
runtime = np.zeros((3,len(rows)))
hours = np.zeros((3,len(rows)))


runtime[0]=[1,1,3,34,104,445,2000,8000]
runtime[1]=[4,20,100,5000,26000,200000,'nan','nan']
runtime[2]=[0.5,0.5,3,30,80,300,1300,5000]
hours[1]=[60,60,60,60,60,60,60,60]


extraticks=[60]
extravalue=[60]

plt.plot(rows,runtime[0], label="A", marker='o')
plt.plot(rows,runtime[1], label="B", marker='o')
plt.plot(rows,runtime[2], label="C", marker='o')
plt.axhline(60, color='r', linestyle='dashed')
plt.text(30, 50, '1 hr', size=12, color='r')

plt.xscale("log"), plt.yscale("log")
plt.xticks(rows, labels=rows)
#plt.yticks(list(plt.yticks()[0])+extraticks,list(plt.yticks()[0])+extravalue)

plt.xlabel("Sample size")
plt.ylabel("Runtime [s]")
plt.legend()
plt.show()

enter image description here

Related