Search code examples
pythonmatplotlibformattingaxis-labels

Matplotlib apply xaxis and yaxis number formatting


  1. Why is my xaxis not formatted as a date? I expect it to because I am setting the dataframe index to a datetime index.
  2. How do I make all subplots share the same xaxis? The reason I am currently using add_subplot instead of plt.subplots is because I couldn't get plt.subplots to work for what I want to do -- which is to make nrows and ncols dynamic parameters so I can output charts with any shape I want: (4,1) shape, (2,2), (1,4), etc.
  3. How do I apply a specific number format to each yaxis? Below, I am attempting to lookup the plot string in d (dict) to return the format string and then apply that format to the yaxis formatting but it doesn't seem to be working.

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.ticker import FuncFormatter
plt.style.use('ggplot')

df = pd.DataFrame({'Clicks': {0: 334, 1: 554, 2: 433, 3: 5353, 4: 433},
                   'Impressions': {0: 3242, 1: 43345, 2: 3456, 3: 34543, 4: 3453},
                   'Day': {0: '12/1/2015', 1: '12/2/2015', 2: '12/3/2015', 3: '12/4/2015', 4: '12/5/2015'},
                   'Conv': {0: 23, 1: 23, 2: 45, 3: 56, 4: 45},
                   'Cost': {0: 414.16, 1: 686.96, 2: 536.91, 3: 637.72, 4: 536.91}},
                  columns=['Day', 'Impressions', 'Clicks', 'Cost', 'Conv'])

df['Day'] = pd.to_datetime(df['Day'])
df = df.set_index('Day').resample('d', how='sum')

window = 2
nrows = 2
ncols = 2

plots = ['Impressions', 'Clicks', 'Cost', 'Conv']
d = {'Impressions':'{:,.0f}', 'Clicks': '{:,.0f}', 'Cost':'${:,.2f}', 'Conv': '{:,.0f}'}

fig = plt.figure(figsize=(8,6))
for i, plot in enumerate(plots):
  ax = fig.add_subplot(nrows, ncols, i+1)
  ax.plot(df.index, df[plot])
  ma = pd.rolling_mean(df[plot], window)
  ax.plot(df.index, ma)
  mstd = pd.rolling_std(df[plot], window)
  ax.fill_between(df.index, ma - 2*mstd, ma + 2*mstd, color='b', alpha=0.1)
  ax.set_title(plot)
  ax.get_yaxis().set_major_formatter(FuncFormatter(lambda x, p: d[plot].format(x)))
  plt.tight_layout()

plt.show()

Plot example

Here's the df:

            Impressions  Clicks    Cost  Conv
Day                                          
2015-12-01         3242     334  414.16    23
2015-12-02        43345     554  686.96    23
2015-12-03         3456     433  536.91    45
2015-12-04        34543    5353  637.72    56
2015-12-05         3453     433  536.91    45

Solution

  • Why is my xaxis not formatted as a date?

    You need to set DateFormatter (or similar) as major_formatter - see code below.

    How do I make all subplots share the same xaxis?

    Add sharex=True parameter to you subplots call. You can use axes from .subplots() if you flatten them like it is shown in the code below.

    How do I apply a specific number format to each yaxis?

    Your FuncFormatter needs to return a formatted string from given tick_value and position like in the code below:

    fig, axes = plt.subplots(2, 2, figsize=(8,6), sharex=True)
    
    for ax, plot in zip(axes.flat, plots):
        ax.plot(df.index, df[plot])
        ma = pd.rolling_mean(df[plot], window)
        ax.plot(df.index, ma)
        mstd = pd.rolling_std(df[plot], window)
        ax.fill_between(df.index, ma - 2*mstd, ma + 2*mstd, color='b', alpha=0.1)
        ax.set_title(plot)
        ax.yaxis.set_major_formatter(FuncFormatter(lambda x, p: '{:.0f}'.format(x)))
        ax.xaxis.set_major_formatter(DateFormatter('%d-%H:%M')) # or '%d.%m.%y'
    
    fig.autofmt_xdate()  # This will rotate the xticklabels by 30 degrees so that all dates are readable.
    fig.tight_layout()  # no need to call this inside the loop.
    

    This will produce a plot like this:

    enter image description here