matplotlib

How to disable scientific labels on log plot for Matplotlib?


How to get rid of all the 3x10^-6 notations, that messes up the x-axis labels?enter image description here

The code:

    ticks = [0.00005, 0.0001, 0.0002, 0.0005]
    ax.set_title(f'Period {period} days')
    ax.set_xlabel('Volatility (EWMA)')
    ax.set_xscale('log')
    ax.set_ylabel('mean[S_365/S_0/RF_0]')
    ax.grid(True)

    # Force fixed ticks
    ax.set_xticks(ticks)
    ax.set_xlim([ticks[0], ticks[-1]])
    ax.xaxis.set_major_formatter(FuncFormatter(lambda x, _: f'{x:.4f}'))

I tried lots of other variants, all I can find on the internet, nothing works.

Reproducible example:

import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.ticker import FuncFormatter
np.random.seed(0)

def make_data():
  data = []
  for period_d in [182, 365]:
    # Log-spaced ema_var_d_t between 0.00005 and 0.0005 (as before)
    ema_vars = 10 ** np.random.uniform(np.log10(0.00005), np.log10(0.0005), 500)

    # Increase standard deviation of lr_t2 for meaningful spread
    for ema_var in ema_vars:
      lr_t2 = np.random.normal(0, 0.2)  # Increased from 0.05 to 0.2
      lr_rf_1y_t = np.log(1.02)
      data.append({
        'period_d': period_d,
        'lr_t2': lr_t2,
        'lr_rf_1y_t': lr_rf_1y_t,
        'ema_var_d_t': ema_var
      })

  df = pd.DataFrame(data)
  df['nlr_t2'] = df['lr_t2'] - df['lr_rf_1y_t']
  df['nr_t2'] = np.exp(df['nlr_t2'])

  return df

df = make_data()

def plot_all_periods(df, window = 5):
  unique_periods = df['period_d'].unique()
  n = len(unique_periods)
  cols = 3
  rows = (n + cols - 1) // cols

  fig, axes = plt.subplots(rows, cols, figsize=(5 * cols, 4 * rows), squeeze=False)

  for idx, period in enumerate(unique_periods):
    ax = axes[idx // cols][idx % cols]

    df_period = df[df['period_d'] == period].sort_values('ema_var_d_t').reset_index(drop=True)

    result_x = []
    result_y = []

    for i in range(len(df_period)):
      left = i - window
      right = i + window

      if left < 0 or right >= len(df_period):
        continue

      avg = np.mean(df_period['nr_t2'].iloc[left:right+1])

      result_x.append(df_period['ema_var_d_t'].iloc[i])
      result_y.append(avg)

    ax.plot(result_x, result_y, linewidth=2)

    ticks = [0.00005, 0.0001, 0.0002, 0.0005]
    ax.set_title(f'Period {period} days')
    ax.set_xlabel('Volatility (EWMA)')
    ax.set_xscale('log')
    ax.set_ylabel('mean[S_365/S_0/RF_0]')
    ax.grid(True)

    # Force fixed ticks
    ax.set_xticks(ticks)
    ax.set_xlim([ticks[0], ticks[-1]])
    ax.xaxis.set_major_formatter(FuncFormatter(lambda x, _: f'{x:.4f}'))

  # Hide empty subplots
  for idx in range(n, rows * cols):
    fig.delaxes(axes[idx // cols][idx % cols])

  plt.tight_layout()
  plt.show()

plot_all_periods(df)

Solution

  • This is hinted at by @JohanC's comment, but you should make sure to turn off the minor tick locator with a NullLocator. Running your MWE, but with the additional imports:

    from matplotlib.ticker import FuncFormatter, FixedLocator, NullLocator
    

    and then replacing:

    # Force fixed ticks
    ax.set_xticks(ticks)
    ax.set_xlim([ticks[0], ticks[-1]])
    ax.xaxis.set_major_formatter(FuncFormatter(lambda x, _: f'{x:.4f}'))
    

    with:

    # Force fixed ticks
    ax.xaxis.set_major_locator(FixedLocator(ticks))
    ax.xaxis.set_minor_locator(NullLocator())  # set minor ticks to use a NullLocator
    ax.set_xlim([ticks[0], ticks[-1]])
    ax.xaxis.set_major_formatter(FuncFormatter(lambda x, _: f'{x:.4f}'))
    

    seems to get rid of the labels that you don't want.