Home > Software engineering >  How do you adjust bar width to match the secondary x-axis?
How do you adjust bar width to match the secondary x-axis?

Time:12-04

How can I set the bar width to match a specified number of days?

I want to have 4 bars, one for each week of the month. Depending on the month, the number of days in the week are different. Here is how I calculate the number of days in each week:

from calendar import monthrange

def weekly_breakdown(year=2021, month=11):
    total_days = monthrange(year, month)[1]
    
    if total_days % 4 == 0: # 28 / 4 = 7
        days_per_week = (7, 7, 7, 7)
    
    elif total_days % 4 == 2: # 30 / 4 = 7.5
        days_per_week = (8, 7, 8, 7)
    
    elif total_days % 4 == 3: # 31 / 4 = 7.75
        days_per_week = (8, 8, 8, 7)
    
    # days_per_week = 'Week 1', 'Week 2', 'Week 3', 'Week 4'
    return days_per_week

Here is the part of my script that actually creates the plot. In this example, I'm using November 2021 (30 days), so I want the first bars width to span 01-08 (8 days), the second bar to span 09-15 (7 days), the third bar to span 16-23 (8 days), and the last bar to span 24-30 (7 days).

import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.dates import DateFormatter, DayLocator


# Generate data for bars.
df1 = pd.DataFrame(
    [100, 1650, 2000, 3500],
    index=['Week 1', 'Week 2', 'Week 3', 'Week 4'],
    columns=['Amount']
)

df2 = pd.DataFrame(
    [750, 1500, 2250, 3000],
    index=['Week 1', 'Week 2', 'Week 3', 'Week 4'],
    columns=['Amount']
)

# Generate line data.
days = pd.date_range(start="2021-11-01", end="2021-11-30").to_pydatetime().tolist()
daily_df = pd.DataFrame(
    list(range(100, 3100, 100)), 
    index=days, 
    columns=['Cumulative']
)

fig, ax1 = plt.subplots(figsize=(8, 6), nrows=1, ncols=1)

# Share x-axis between bar and line plot.
ax2 = ax1.twiny()

df1['Amount'].plot(kind='bar', ax=ax1, color='red')
df2['Amount'].plot(kind='bar', ax=ax1, color='white', edgecolor='black', alpha=0.5)
daily_df['Cumulative'].plot(x='Adjusted Date', kind='line', ax=ax2, marker='o')

ax1.xaxis.tick_bottom()
ax1.tick_params(axis='x', rotation=0, length=0, pad=30)
ax1.set_ylabel('Dollars')

ax2.xaxis.tick_bottom()
ax2.tick_params(axis='x', which='major', length=3)
ax2.tick_params(axis='x', which='minor', length=3)
ax2.set_xlim([daily_df.index[0], daily_df.index[-1]])
ax2.xaxis.set_major_locator(DayLocator(interval=1))
ax2.xaxis.set_major_formatter(DateFormatter("%d"))

enter image description here

Bonus question: Where can I find documentation on how to create plots using df.plot(kind='')? I've search the matplotlib documentation with no luck.

CodePudding user response:

It seems that the width argument for the df.plot('kind'=bar) function from pandas only admits a single value rather than a list of widths. However, you can achieve what you want by using the bar function from matplolib directly. It allows you to pass a list of widths to the function.

You can find the code below:

import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.dates import DateFormatter, DayLocator
import numpy as np

# Generate data for bars.
df1 = pd.DataFrame(
    [100, 1650, 2000, 3500],
    index=['Week 1', 'Week 2', 'Week 3', 'Week 4'],
    columns=['Amount']
)

df2 = pd.DataFrame(
    [750, 1500, 2250, 3000],
    index=['Week 1', 'Week 2', 'Week 3', 'Week 4'],
    columns=['Amount']
)

# Generate line data.
days = pd.date_range(start="2021-11-01", end="2021-11-30").to_pydatetime().tolist()
daily_df = pd.DataFrame(
    list(range(100, 3100, 100)), 
    index=days, 
    columns=['Cumulative']
)

fig, ax1 = plt.subplots(figsize=(8, 6), nrows=1, ncols=1)

# Share x-axis between bar and line plot.
ax2 = ax1.twiny()
days_per_week=weekly_breakdown(2021,11)
N_days=np.sum(days_per_week)
widths=np.array(days_per_week)-1
index_bars=[1 sum(widths[0:i] 1) for i in range(len(widths))]
ax1.bar(index_bars,df1['Amount'],color='red', width=widths,align='edge')
ax1.bar(index_bars,df2['Amount'],color='white',edgecolor='black', alpha=0.5,width=widths,align='edge')
ax1.plot(np.arange(N_days) 1,daily_df['Cumulative'],marker='o')
ax1.set_xlim([1,N_days])
ax1.set_xticks(np.arange(N_days) 1)

ax2.xaxis.tick_bottom()
ax2.tick_params(axis='x', rotation=0, length=0, pad=30)
ax2.set_xticks([1./(len(df1.index))*i (1./(2*len(df1.index))) for i in range(len(df1.index))])
ax2.set_xticklabels(df1.index)

And the output gives:

enter image description here

  • Related