I have the following code:
import pandas as pd
from matplotlib import pyplot as plt
%matplotlib inline
hours = list(range(25)) # [0, 1, 2, ... 22, 23, 24]
labels = [f'{h:02d}:00' for h in hours] # ["00:00", "01:00", ... "23:00", "24:00"]
load = [2000, 2000, 0, 0, 0, 0, 0, 2000, 2000, 2000, 2000, 2000,0,0, 0, 0, 0, 2000, 2000,2000, 2000, 2000, 0,0,0, 0]
temperature = [21, 21.6, 22, 21.3, 20.8, 20.4, 20.1, 20, 20.6, 21.1, 21.5, 21.8, 22, 21.4, 20.9, 20.5, 20.2, 20, 20.7, 21.2, 21.6, 21.9, 22, 21.4, 21]
plt.figure(linewidth=1, figsize=(9, 5))
ax = plt.gca()
ax.plot(hours, load[0:25], color="goldenrod",drawstyle="steps-post", linewidth=3)
ax.plot(hours, load[0:25], color="gold",drawstyle="steps-post", linewidth=3, alpha=.8, label = 'Electrical power') # <- drawstyle argument.
ax.set_xlabel("Time of day", fontsize=16, labelpad=8)
ax.set_ylabel("Electrical power in W", fontsize=14, labelpad=8)
ax.set_xlim(0, 24)
ax.set_ylim(0, 3000)
plt.xticks(hours, labels=labels, rotation=90)
plt.grid(axis='y', alpha=.4)
ax.tick_params(axis='both', which='major', labelsize=14)
ax2 = ax.twinx()
ax2.plot(hours, temperature, color="red", linewidth=3, label = 'Temperature')
ax2.set_ylabel("Temperature in °C", fontsize=14, labelpad=8)
ax2.set_ylim(20, 22.5)
ax2.tick_params(axis='both', which='major', labelsize=14)
fig = plt.gcf()
fig.legend(loc='center left', bbox_to_anchor=(0.25, 1.03), fontsize=14, ncol=3)
fig.tight_layout()
ax.patch.set_visible(False)
fig.savefig('ControlStrategy_Conventional.png', edgecolor='black', dpi=400, bbox_inches='tight')
plt.show()
I would like to change the axis. So the temperature should be displayed in the left axis and the load on the right axis. I tried to change it but the resulting plot looked weird. Can anyone tell me what to do? I'd appreciate every comment.
You can first create the twin-axis and then freely select what to plot on what axis. Further, for clarity and readability, I prefer to stick with one interface (object oriented or pyplot) and not mix them:
hours = list(range(25))
labels = [f'{h:02d}' for h in hours[::2]]
fig,ax = plt.subplots(figsize=(9, 5), linewidth=1)
ax2 = ax.twinx()
ax.plot(hours, temperature, color="red", linewidth=3, label = 'Temperature')
ax2.plot(hours, load[0:25], color="gold",drawstyle="steps-post", linewidth=3, alpha=.8, label = 'Electrical power')
ax2.fill_between(hours, load[0:25], step="post", color="yellow")
ax.set_zorder(1)
ax.patch.set_visible(False)
ax.set_xlabel("Time of day", fontsize=16, labelpad=8)
ax.set_xlim(0, 24)
ax.set_xticks(hours[::2])
ax.set_xticklabels(labels=labels)
ax.tick_params(axis='both', which='major', labelsize=14)
ax.grid(axis='y', alpha=.4)
ax.set_ylabel("Temperature in °C", fontsize=14, labelpad=8)
ax.set_ylim(20, 22.5)
ax2.set_ylabel("Electrical power in W", fontsize=14, labelpad=8)
ax2.set_ylim(0, 3000)
fig.legend(loc='center left', bbox_to_anchor=(0.25, 1.03), fontsize=14, ncol=3)
fig.tight_layout()
Related
I have the following code for a Matplotlib plot:
import pandas as pd
from matplotlib import pyplot as plt
columns = ['Price']
price_values = [[4.2],
[4.1],
[4],
[3.8],
[3.9],
[4.2],
[4.5],
[4.8],
[5.2],
[5.2],
[5.2],
[5.6],
[5.2],
[5.1],
[5.3],
[6],
[6.2],
[6.3],
[6.2],
[6],
[5.5] ,
[5.2],
[4.8],
[4.6]]
price_data = pd.DataFrame(price_values, index=range(0, 24), columns=columns)
fig = plt.figure(linewidth=1, figsize=(9, 5))
ax=plt.gca()
for column,color in zip(price_data.columns,['gold']):
ax.fill_between(
x=price_data.index,
y1=price_data[column],
y2=0,
label=column,
color=color,
alpha=.5,
step='post',
linewidth=5,
)
ax.set_facecolor("white")
ax.set_xlabel("Time of day", fontsize = 14, labelpad=8)
ax.set_ylabel("Price [Cent/kWh]", fontsize = 14,labelpad=8)
ax.set_xlim(0, 23)
ax.set_ylim(0, 8)
plt.xticks(price_data.index, labels=[f'{h:02d}:00' for h in price_data.index], rotation=90)
plt.tight_layout()
hours = list(range(25))
labels = [f'{h:02d}:00' for h in hours]
ax.tick_params(axis='both', which='major', labelsize=14)
ax.legend(loc='center left', bbox_to_anchor=(0.15, 1.07), fontsize = 14, ncol=3)
plt.savefig('Diagramm.png', edgecolor='black', dpi=400, bbox_inches='tight')
plt.show()
Now I would like to remove the area under the curve, sucht that I can only see the curve. I tried to use
plt.bar(fill=False)
but I get the error "TypeError: bar() missing 2 required positional arguments: 'x' and 'height'". Any suggestions how I can do that
Using fill_between and later remove the area under the curve seems like a pretty convoluted way to plot your data. But you could just set y2=price_data[column]:
price_data = pd.DataFrame(price_values, index=range(0, 24), columns=columns)
fig = plt.figure(linewidth=1, figsize=(9, 5))
ax=plt.gca()
for column,color in zip(price_data.columns,['gold']):
ax.fill_between(
x=price_data.index,
y1=price_data[column],
y2=price_data[column],
label=column,
color=color,
alpha=.5,
step='post',
linewidth=5,
)
ax.set_facecolor("white")
ax.set_xlabel("Time of day", fontsize = 14, labelpad=8)
ax.set_ylabel("Price [Cent/kWh]", fontsize = 14,labelpad=8)
ax.set_xlim(0, 23)
ax.set_ylim(0, 8)
plt.xticks(price_data.index, labels=[f'{h:02d}:00' for h in price_data.index], rotation=90)
plt.tight_layout()
hours = list(range(25))
labels = [f'{h:02d}:00' for h in hours]
ax.tick_params(axis='both', which='major', labelsize=14)
ax.legend(loc='center left', bbox_to_anchor=(0.15, 1.07), fontsize = 14, ncol=3)
plt.savefig('Diagramm.png', edgecolor='black', dpi=400, bbox_inches='tight')
plt.show()
Output:
Edit: #JohanC rightfully noted that the last value barely appears on the plot. One way to avoid this would be to replace your loop with the following:
price_data.plot(ax=ax, color="gold", drawstyle="steps-mid", linewidth=2)
Note that your solution is missing the last price value, the one between 23 and 24 h. You'll need to repeat the last value for this to work. To draw a step plot, the easiest way is ax.step.
The following example code changes the values for the first and the last value to make them stand out more.
from matplotlib import pyplot as plt
import pandas as pd
columns = ['Price']
price_values = [[1.2], [4.1], [4], [3.8], [3.9], [4.2], [4.5], [4.8], [5.2], [5.2], [5.2], [5.6], [5.2], [5.1], [5.3], [6], [6.2], [6.3], [6.2], [6], [5.5], [5.2], [4.8], [1.6]]
price_data = pd.DataFrame(price_values, index=range(0, 24), columns=columns)
fig, ax = plt.subplots(figsize=(9, 5))
for column, color in zip(price_data.columns, ['gold']):
ax.step(x=range(len(price_data) + 1), y=list(price_data[column]) + list(price_data[column][-1:]),
where='post', color=color, linewidth=5, label=column)
ax.set_xlabel("Time of day", fontsize=14, labelpad=8)
ax.set_ylabel("Price [Cent/kWh]", fontsize=14, labelpad=8)
ax.set_xlim(0, 24)
ax.set_ylim(0, 8)
xs = range(len(price_data) + 1)
ax.set_xticks(xs, labels=[f'{h:02d}:00' for h in xs], rotation=90)
ax.tick_params(axis='both', which='major', labelsize=14)
ax.legend(loc='lower left', bbox_to_anchor=(0.15, 1.01), fontsize=14, ncol=3)
plt.tight_layout()
plt.savefig('Diagramm.png', edgecolor='black', dpi=400, bbox_inches='tight')
plt.show()
Alternatively, you could use Seaborn's histplot, which has a step option (element='step', fill=False), but that works easiest if you'd let seaborn do the counting for the histogram. You could use sns.histplot's weights= parameter to fill in the values, e.g.
sns.histplot(x=price_data.index, weights=price_data[column].values, bins=len(price_data), binrange=(0, 24),
element='step', fill=False, color=color, linewidth=5, label=column, ax=ax)
The following code returns the nice graph provided here:
.
However, when I add a line code such as ax.set_ylim(ymax=14) I get the following graph:
.
Clearly there is something wrong. Could anyone help me figure out what?
Thanks!
fig, ax = plt.subplots(figsize=(15, 10))
# Set bins for histograms:
bins = np.arange(0, 40+0.5, 1)
# set interval on this bins for curve fitting:
xplot = np.linspace(min(bins), max(bins), 100)
# -- make a histogram
ax.grid(axis='y', zorder=0)
plt.axvline(x=af_farm_w_speedb.mean(), color='black', linestyle='dashed', linewidth=2, label = "Mean speed at control")
plt.axvline(x=af_farm_w_speedw.mean(), color='red', linestyle='dashed', linewidth=2, label = "Mean speed at treatment")
ax.hist([af_farm_w_speedb, af_farm_w_speedw], bins=bins, density=True, alpha = 1, align='left', zorder=1, rwidth=0.8, color=['lightsteelblue','grey'], label = ['Records at control', 'Records at treatment'])
ax.tick_params(axis='both', which='major', labelsize=30)
ax.set_yticklabels([0, 2, 4, 6, 8, 10, 12])
ax.set_title('Post-farm wind speed distribution', fontsize=35)
(scale, a, shape, c) = stats.exponweib.fit(af_farm_w_speedw, f0=1, floc=0)
ax.plot(xplot, stats.exponweib.pdf(xplot, *stats.exponweib.fit(af_farm_w_speedb, 1, 1, scale=1, loc=0)), zorder=3, color = "black", linewidth=1.6, label="Weibull fit at control")
ax.plot(xplot, stats.exponweib.pdf(xplot, *stats.exponweib.fit(af_farm_w_speedw, 1, 1, scale=1, loc=0)), zorder=3, color = "red", linewidth=1.6, label="Weibull fit at treatment")
fig.text(0.6, 0.55, "Mean speed at treatment: {:.4g}".format(af_farm_w_speedw.mean()), fontsize=18)
fig.text(0.6, 0.6, "Mean speed at control: {:.4g}".format(af_farm_w_speedb.mean()), fontsize=18)
ax.legend(prop=dict(size=18))
I would like to have a step-wise area plot in matplotib with pandas. I adjusted the code for a step-wise line plot but I get an error message. Here is the current code:
import pandas as pd
from matplotlib import pyplot as plt
%matplotlib inline
columns = ['Conventional control', 'Optimized control']
power_values = [[0.73,1.28],
[0.21, 0.21],
[0.18, 0.18],
[0.16, 1.00],
[0.57, 0.76],
[1.63, 1.62],
[3.28, 2.77],
[3.92, 0.47],
[3.29, 0.51],
[2.01, 3.64],
[1.72, 4.45],
[2.2, 0.59],
[2.33, 4.34],
[2.01, 2.05],
[1.39, 1.68],
[2.06, 0.55],
[3.07, 0.61],
[4.07, 0.61],
[3.66, 0.59],
[2.67, 0.59] ,
[1.54, 1.65],
[1.37, 1.55],
[1.36, 0.95],
[1.1, 1.70],
[0,0]]
wind_data = pd.DataFrame(power_values, index=range(0, 25), columns=columns)
fig = plt.figure(linewidth=1, figsize=(9, 5))
ax = wind_data.plot.area(ax=plt.gca(), color =["saddlebrown", "limegreen"], stacked=False, drawstyle="steps-post" )
ax.set_facecolor("white")
ax.set_xlabel("Time of day", fontsize = 14, labelpad=8)
ax.set_ylabel("Electrical power in kW", fontsize = 14,labelpad=8)
ax.set_xlim(0, 24)
ax.set_ylim(0, 5)
plt.xticks(wind_data.index, labels=[f'{h:02d}:00' for h in wind_data.index], rotation=90)
plt.grid(axis='y', alpha=.4)
plt.tight_layout()
hours = list(range(25)) # [0, 1, 2, ... 22, 23, 24]
labels = [f'{h:02d}:00' for h in hours] # ["00:00", "01:00", ... "23:00", "24:00"]
ax.tick_params(axis='both', which='major', labelsize=14)
ax.legend(loc='center left', bbox_to_anchor=(0.15, 1.07), fontsize = 14, ncol=3)
plt.savefig('CS_Cost_PerTimeslot.png', edgecolor='black', dpi=400, bbox_inches='tight')
plt.show()
If I do not use the argument "drawstyle="steps-post" I get just an normal area plot. But I would like to have a step-wise area plot. When using this attribut (as with the line plot) I get the error message:"
AttributeError: 'PolyCollection' object has no property 'drawstyle'
". I'd be very happy if someone could help me on that. Maybe there is also another way how to tell matpoltlib not to linearly interpolate the lines between the data points.
I think the simplest way to solve your problem is to use the pyplot fill_between command directly. That way you get superb control over all the plotting elements you might want. Slightly less user friendly than the DataFrame.plot api, but still good.
Replace the line
ax = wind_data.plot.area(ax=plt.gca(), color =["saddlebrown", "limegreen"], stacked=False,drawstyle="steps-post")
with
ax=plt.gca()
for column,color in zip(wind_data.columns,['saddlebrown','limegreen']):
ax.fill_between(
x=wind_data.index,
y1=wind_data[column],
y2=0,
label=column,
color=color,
alpha=.5,
step='post',
linewidth=2,
)
and you're good.
Is there any simpler way to have this line (with two annotations) using matplotlib?
The idea is just draw a line showing a interval ([0,T]) and some points with annotations. This code is too big for such a small thing.
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(111)
plt.ylim(-.3,.3)
plt.xlim(0, 1)
xmin, xmax = ax.get_xlim()
# removing the default axis on all sides:
for side in ['bottom','right','top','left']:
ax.spines[side].set_visible(False)
# removing the axis ticks
plt.xticks([])
plt.yticks([])
ax.xaxis.set_ticks_position('none')
ax.yaxis.set_ticks_position('none')
# draw x and y axis
ax.arrow(xmin, 0, xmax-xmin, 0, fc='k', ec='k',
length_includes_head= True, clip_on = False)
esp=0.05
ax.text(0.5, esp, r'$\Delta t$', ha='center')
ax.text(0.45, 0, r'$|$', ha='center')
ax.text(0.45, -esp, r'$t_i$', ha='center')
ax.text(0.55, 0, r'$|$', ha='center')
ax.text(0.55, -esp, r'$t_{i+1}$', ha='center')
ax.text(0, 0, r'$|$', ha='center')
ax.text(0, -esp, r'$0$', ha='center')
ax.text(1, 0, r'$|$', ha='center')
ax.text(1, -esp, r'$T$', ha='center')
plt.show()
One could use the xaxis as the line to draw the intervals. That would allow to use its ticks and ticklabels for the annotations.
import matplotlib.pyplot as plt
fig,ax = plt.subplots()
plt.subplots_adjust(bottom=0.5, top=0.7)
for side in ['right','top','left']:
ax.spines[side].set_visible(False)
ax.set_yticks([])
ticks=[0,.45,.55,1]
ticklabels=[0,r'$t_i$', r'$t_{i+1}$',r'$T$']
ax.set_xticks(ticks)
ax.set_xticklabels(ticklabels)
ax.tick_params(direction='inout', length=10,pad=7)
ax.text(0.5, 0.2, r'$\Delta t$', ha='center')
plt.show()
I found similar solution using the x-axis but it is a little more compact
plt.ylim(0,.3)
plt.xticks([0, 0.45, 0.55, 1],
('$0$', '$t_i$', '$t_{i+1}$', '$T$'),
size=20,
verticalalignment='top')
plt.tick_params(axis='x',
direction='inout',
length=20,
pad=15,
bottom=True,
top=False)
Another approach is drawing the ticks as scatterplot and annotating each, i.e.
for x, label in [(0, '$0$'), (0.45, '$t_i$'), (0.55, '$t_{i+1}$'), (1, '$T$')]:
plt.scatter(x, 0, s=50, marker='|')
plt.annotate(label, [x,-0.05], size=20)
Below, I plot the following Figure in Python:
As you can see the plot on the right is much more "smooth" than the one on the left. That's because the scaling of x-axis on both plot is different. More observations on the left than on the right (about three times more). Hence how can I "squeeze" horizontally the right plot such that I get somewhat an approximative look to the one of the left? Below is my code (I use Pandas):
fig, axes = plt.subplots(1, 2, sharey=True, figsize=(30, 15))
# plot the same data on both axes
#gs = gridspec.GridSpec(1, 2, width_ratios=[3, 1])
ax1 = df1.plot(ax=axes[0], grid='off', legend=False,
xticks=[-250, -200, -150, -100, -50,
0, 25], lw=2, colormap='jet',
fontsize=20)
ax2 = df2.plot(ax=axes[1], grid='off', legend=False,
xticks=[-5, 0, 20, 40, 60, 80], lw=2,
colormap='jet', fontsize=20)
# zoom-in / limit the view to different portions of the data
# hide the spines between ax and ax2
ax1.set_ylabel('Treatment-Control Ratio', fontsize=20)
ax1.axhline(y=1, color='r', linewidth=1.5)
ax2.axhline(y=1, color='r', linewidth=1.5)
ax1.axvline(x=0, color='r', linewidth=1.5, linestyle='--')
ax2.axvline(x=0, color='r', linewidth=1.5, linestyle='--')
ax1.set_xlabel('Event Time - 1 Minute', fontsize=20)
ax2.set_xlabel('Event Time - 1 Minute', fontsize=20)
ax1.spines['right'].set_visible(False)
ax2.spines['left'].set_visible(False)
ax1.yaxis.tick_left()
ax2.yaxis.set_major_locator(plt.NullLocator())
ax1.tick_params(labeltop='off') # don't put tick labels at the top
plt.subplots_adjust(wspace=0.11)
plt.tight_layout()
With the help of #cphlewis and #gboffi I fixed the issue with the code below:
fig, axes = plt.subplots(figsize=(30, 15))
# plot the same data on both axes
gs = gridspec.GridSpec(1, 2, width_ratios=[3, 1.2])
ax1 = plt.subplot(gs[0])
ax2 = plt.subplot(gs[1], sharey=ax1)
df_wpc.loc[-260:25].plot(ax=ax1, grid='off', legend=False,
xticks=[-250, -200, -150, -100, -50,
0, 25], lw=2, colormap='jet',
fontsize=20)
df_pc_et.loc[-5:91].plot(ax=ax2, grid='off', legend=False,
xticks=[-5, 0, 20, 40, 60, 80], lw=2,
colormap='jet', fontsize=20)
ax1.set_ylabel('Treatment-Control Ratio', fontsize=20)
ax1.axhline(y=1, color='r', linewidth=1.8)
ax2.axhline(y=1, color='r', linewidth=1.8)
ax1.axvline(x=0, color='r', linewidth=1.8, linestyle='--')
ax2.axvline(x=0, color='r', linewidth=1.8, linestyle='--')
ax1.set_xlabel('Event Time - 1 Minute', fontsize=20)
ax2.set_xlabel('Event Time - 1 Minute', fontsize=20)
ax1.spines['right'].set_visible(False)
ax2.spines['left'].set_visible(False)
ax1.yaxis.tick_left()
ax2.yaxis.set_major_locator(plt.NullLocator())
ax1.tick_params(labeltop='off') # don't put tick labels at the top
plt.subplots_adjust(wspace=0.7)
plt.tight_layout()