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"))
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: