Related
I'm trying to fix the width of one of the subplots to a fixed value. The issue comes from the fact that I'm combining ax.imshow() and ax.plot in the same column of plots and I'd like my plots to have the same width so that they line up on both sides, since they should share the x axis. to complicate things, the ax.imshow() also has a colorbar, which I introduced with ax.figure.colorbar() and that needs to be ignored when talking about the width of the plot. So far the only way I managed to make it work is by inserting values for ax.set_aspect() manually, but it takes a long time to get it right by trial and error.
I won't bore you with the details, especially because the code is a bit too long, but I'll include it here for completeness, hoping that it won't cause too much confusion. You won't be able to run it yourself unfortunately, since I'm only including the plotting part of the code.
Thanks in advance for your help.
df_spec = pd.read_csv(path + spec_name, header=spec_header, skipfooter=3)
df_spec['EPOCH_yyyy-mm-ddThh:mm:ss.sssZ'] = pd.to_datetime(df_spec['EPOCH_yyyy-mm-ddThh:mm:ss.sssZ'], format = '%Y-%m-%dT%H:%M:%S.%fZ')
dates_list = df_spec['EPOCH_yyyy-mm-ddThh:mm:ss.sssZ'].to_list()
x_lims_ = [dates_list[0].timestamp(), dates_list[-1].timestamp()]
x_lims = list(map(dt.datetime.fromtimestamp, x_lims_))
x_lims = mdates.date2num(x_lims)
y_lims0 = np.linspace(1075, 13825, 100)
y_lims1 = np.linspace(20, 1040, 100)
y_lims2 = np.linspace(4, 245, 100)
#plt.FuncFormatter
def fake_log(x, pos):
'The two args are the value and tick position'
return round(10**x) #r'$10^{%.2f}$' % (x)
df_spec = df_spec.set_index('EPOCH_yyyy-mm-ddThh:mm:ss.sssZ')
df_spec = df_spec[df_spec.columns[::-1]].T
df_spec0 = df_spec.iloc[353:-1, :]
df_spec1 = df_spec.iloc[95:353, :]
df_spec2 = df_spec.iloc[0:95, :]
date_format = mdates.DateFormatter('%H:%M:%S')
locator = mdates.HourLocator([4, 5, 6, 7, 8, 9])
fig, ax = plt.subplots(6, 1, figsize=(15,20))
for a in ax:
a.set_anchor('W')
for a in ax:
a.patch.set_facecolor('black')
im = ax[0].imshow(df_spec0, norm=colors.LogNorm(), cmap=cm.inferno, extent = [x_lims[0], x_lims[1], np.log10(y_lims0[0]), np.log10(y_lims0[-1])], aspect='auto')
ax[0].figure.colorbar(im, ax=ax[0])
ax[0].xaxis.set(major_formatter=date_format, major_locator=locator)
ax[0].tick_params(bottom = False)
ax[0].set_xticks([])
ax[0].yaxis.set_major_formatter(fake_log)
ax[0].yaxis.set_major_locator(MaxNLocator(5))
ax[0].set_yticks(np.log10(np.array([2000, 5000, 10000])))
im = ax[1].imshow(df_spec1, norm=colors.LogNorm(), cmap=cm.inferno, extent = [x_lims[0], x_lims[1], np.log10(y_lims1[0]), np.log10(y_lims1[-1])], aspect='auto')
ax[1].figure.colorbar(im, ax=ax[1])
ax[1].xaxis.set(major_formatter=date_format, major_locator=locator)
ax[1].tick_params(bottom = False)
ax[1].set_xticks([])
ax[1].yaxis.set_major_formatter(fake_log)
ax[1].yaxis.set_major_locator(MaxNLocator(5))
ax[1].set_yticks(np.log10(np.array([20, 50, 100, 200, 500, 1000])))
im = ax[2].imshow(df_spec2, norm=colors.LogNorm(), cmap=cm.inferno, extent = [x_lims[0], x_lims[1], np.log10(y_lims2[0]), np.log10(y_lims2[-1])], aspect='auto')
ax[2].figure.colorbar(im, ax=ax[2])
ax[2].xaxis.set(major_formatter=date_format, major_locator=locator)
ax[2].tick_params(bottom = False)
ax[2].set_xticks([])
ax[2].yaxis.set_major_formatter(fake_log)
ax[2].yaxis.set_major_locator(MaxNLocator(5))
ax[2].set_yticks(np.log10(np.array([10, 20, 50, 100, 200])))
#second set of images
df1 = pd.read_csv(path + f1_name, header=f1_header, skipfooter=3)
df1['EPOCH_yyyy-mm-ddThh:mm:ss.sssZ'] = pd.to_datetime(df1['EPOCH_yyyy-mm-ddThh:mm:ss.sssZ'], format = '%Y-%m-%dT%H:%M:%S.%fZ')
df2 = pd.read_csv(path + f2_name, header=f2_header, skipfooter=3)
df2['EPOCH_yyyy-mm-ddThh:mm:ss.sssZ'] = pd.to_datetime(df2['EPOCH_yyyy-mm-ddThh:mm:ss.sssZ'], format = '%Y-%m-%dT%H:%M:%S.%fZ')
df3 = pd.read_csv(path + f3_name, header=f3_header, skipfooter=3)
df3['EPOCH_yyyy-mm-ddThh:mm:ss.sssZ'] = pd.to_datetime(df3['EPOCH_yyyy-mm-ddThh:mm:ss.sssZ'], format = '%Y-%m-%dT%H:%M:%S.%fZ')
i1, i2, i3 = 1, 1, 1
energies1 = [27, 40, 66, 108, 181, 310, 517]
energies2 = list(reversed(list(np.array([6, 7, 9, 13, 18, 27, 41, 65, 103, 164, 264, 426, 689, 1113])*1e-3)))
energies3 = list(reversed([0.137, 0.2, 0.29, 0.43, 0.64, 0.92, 1.34, 1.95, 2.85, 4.16, 6.08, 6.87, 12.97, 18.9, 27]))
#fig, ax = plt.subplots(3, 1, figsize=(10,10))
while i1 < 8 :
ax[5].plot(df1.iloc[:, 0], df1.iloc[:, i1], c=cm.rainbow([50*i1]))
ax[5].vlines(arrival_time(energies1[i1-1]), 0,0.3, color=cm.rainbow([50*i1]), label = str(energies1[i1-1]) + ' keV')
ax[5].set_xlim(df3.iloc[0, 0], df3.iloc[-1, 0])
ax[5].set_yscale("log")
plt.gcf().autofmt_xdate()
i1 += 1
while i2 < 15 :
ax[3].plot(df2.iloc[:, 0], df2.iloc[:, i2], c=cm.rainbow([20*i2]), label = str(round(energies2[i2-1]*1e3)) + ' eV')
ax[3].vlines(arrival_time(energies2[i2-1]), 0,4e8, color=cm.rainbow([20*i2]))
ax[3].set_xlim(df3.iloc[0, 0], df3.iloc[-1, 0])
ax[3].set_yscale("log")
i2 += 1
while i3 < 15 :
ax[4].plot(df3.iloc[:, 0], df3.iloc[:, i3], c=cm.rainbow([20*i3]), label = str(round(energies3[i3-1]*1e3)) + ' eV')
ax[4].vlines(arrival_time(energies3[i3-1]), 0,1e5, color=cm.rainbow([20*i3]))
ax[4].set_xlim(df3.iloc[0, 0], df3.iloc[-1, 0])
ax[4].set_yscale("log")
i3 += 1
fig.tight_layout()
# bigger aspect ratio means shorter plot (and viceversa)
ax[3].set_aspect(0.00574) # 2002-04-25: 0.00646
ax[4].set_aspect(0.004899) # 2002-04-25: 0.00684
ax[5].set_aspect(0.006865) # 2002-04-25: 0.01415
ax[4].set_ylim(None, 8e4)
ax[3].set_ylim(top=3e8)
ax[5].set_ylim(top=0.2)
ax[3].legend(loc='upper left', bbox_to_anchor=(1, 1),fontsize = 11)
ax[4].legend(loc='upper left', bbox_to_anchor=(1, 1),fontsize = 11)
ax[5].legend(loc='upper left', bbox_to_anchor=(1, 1),fontsize = 11)
ax[5].set_xlabel('UT', fontsize = 20)
ax[1].set_ylabel('Frequency [kHz]', fontsize = 20)
ax[4].set_ylabel('Eletrons $cm^{-2} ster^{-1} s^{-1} eV^{-1}$', fontsize = 20)
plt.show()
plt.savefig(path + "Spec_Plot_2002_04_25", bbox_inches='tight')
You may want to try layout='compressed':
fig, axs = plt.subplots(2, 1, sharex=True, figsize=(10, 4),
layout='compressed')
x = np.arange(10)
y = np.random.randn(10)
axs[0].plot(x, y)
im = axs[1].imshow(np.random.randn(10, 10), extent=[0, 10, 0, 10])
fig.colorbar(im, ax=axs[1], shrink=0.5)
plt.show()
I am trying to create a 3D barplot using matplotlib in python, and apply a colormap which is tied some data (4th dimension) which is not explicitly plotted. I think what makes this even more complicated is that I want this 4th dimension to be a range of values as opposed to a single value.
So far I have managed to create the 3D bar plot with a colormap tied to the z-dimension thanks primarily to this post how to plot gradient fill on the 3d bars in matplotlib. The code can be found below.
import numpy as np
import glob,os
from matplotlib import pyplot as plt
import matplotlib.colors as cl
import matplotlib.cm as cm
from mpl_toolkits.mplot3d import Axes3D
os.chdir('./')
# axis details for the bar plot
x = ['1', '2', '3', '4', '5'] # labels
x_tick_locks = np.arange(0.1, len(x) + 0.1, 1)
x_axis = np.arange(len(x))
y = ['A', 'B']
y_tick_locks = np.arange(-0.1, len(y) - 0.1, 1)
y_axis = np.arange(len(y))
x_axis, y_axis = np.meshgrid(x_axis, y_axis)
x_axis = x_axis.flatten()
y_axis = y_axis.flatten()
x_data_final = np.ones(len(x) * len(y)) * 0.5
y_data_final = np.ones(len(x) * len(y)) * 0.5
z_axis = np.zeros(len(x)*len(y))
z_data_final = [[30, 10, 15, 20, 25], [10, 15, 15, 28, 40]]
values_min = [[5, 1, 6, 8, 3], [2, 1, 3, 9, 4]]
values_max = [[20, 45, 11, 60, 30], [11, 28, 6, 30, 40]]
cmap_max = max(values_max)
cmap_min = min(values_min)
############################### FOR 3D SCALED GRADIENT BARS ###############################
def make_bar(ax, x0=0, y0=0, width = 0.5, height=1 , cmap="plasma",
norm=cl.Normalize(vmin=0, vmax=1), **kwargs ):
# Make data
u = np.linspace(0, 2*np.pi, 4+1)+np.pi/4.
v_ = np.linspace(np.pi/4., 3./4*np.pi, 100)
v = np.linspace(0, np.pi, len(v_)+2 )
v[0] = 0 ; v[-1] = np.pi; v[1:-1] = v_
#print(u)
x = np.outer(np.cos(u), np.sin(v))
y = np.outer(np.sin(u), np.sin(v))
z = np.outer(np.ones(np.size(u)), np.cos(v))
xthr = np.sin(np.pi/4.)**2 ; zthr = np.sin(np.pi/4.)
x[x > xthr] = xthr; x[x < -xthr] = -xthr
y[y > xthr] = xthr; y[y < -xthr] = -xthr
z[z > zthr] = zthr ; z[z < -zthr] = -zthr
x *= 1./xthr*width; y *= 1./xthr*width
z += zthr
z *= height/(2.*zthr)
#translate
x += x0; y += y0
#plot
ax.plot_surface(x, y, z, cmap=cmap, norm=norm, **kwargs)
def make_bars(ax, x, y, height, width=1):
widths = np.array(width)*np.ones_like(x)
x = np.array(x).flatten()
y = np.array(y).flatten()
h = np.array(height).flatten()
w = np.array(widths).flatten()
norm = cl.Normalize(vmin=0, vmax=h.max())
for i in range(len(x.flatten())):
make_bar(ax, x0=x[i], y0=y[i], width = w[i] , height=h[i], norm=norm)
############################### FOR 3D SCALED GRADIENT BARS ###############################
# Creating graph surface
fig = plt.figure(figsize=(9,6))
ax = fig.add_subplot(111, projection= Axes3D.name)
ax.azim = 50
ax.dist = 10
ax.elev = 30
ax.invert_xaxis()
ax.set_box_aspect((1, 0.5, 1))
ax.zaxis.labelpad=7
ax.text(0.9, 2.2, 0, 'Group', 'x')
ax.text(-2, 0.7, 0, 'Class', 'y')
ax.set_xticks(x_tick_locks)
ax.set_xticklabels(x, ha='left')
ax.tick_params(axis='x', which='major', pad=-2)
ax.set_yticks(y_tick_locks)
ax.set_yticklabels(y, ha='right', rotation=30)
ax.tick_params(axis='y', which='major', pad=-5)
ax.set_zlabel('Number')
make_bars(ax, x_axis, y_axis, z_data_final, width=0.2, )
fig.colorbar(plt.cm.ScalarMappable(cmap = 'plasma'), ax = ax, shrink=0.8)
#plt.tight_layout() # doesn't seem to work properly for 3d plots?
plt.show()
As I mentioned, I don't want the colormap to be tied to the z-axis but rather a 4th dimension, which is a range. In other words, I want the colours of the colormap to range from cmap_min to cmap_max (so min is 1 and max is 60), then for the bar plot with a z_data_final entry of 30 for example, its colours should correspond with the range of 5 to 20.
Some other posts seem to provide a solution for a single 4th dimensional value, i.e. (python) plot 3d surface with colormap as 4th dimension, function of x,y,z or How to make a 4d plot using Python with matplotlib however I wasn't able to find anything specific to bar plots with a range of values as your 4th dimensional data.
I would appreciate any guidance in this matter, thanks in advance.
This is the 3D bar plot with colormap tied to the z-dimension
I have got a matplotlib question about xticks. I wanted to hide all those values that do not occur. I actually did it, but for the second set of values (red chart). I found how to hide for a specific data frame but not for 2 or more.
This is my code:
plt.subplots(figsize=(2, 1), dpi=400)
width = 0.005
xlim = np.arange(0, 1, 0.01)
ylim = np.arange(0, 0.1, 0.001)
plt.xticks(density_2.index.unique(), rotation=90, fontsize=1.5)
plt.yticks(density_2.unique(), fontsize=2)
plt.bar(density_1.index, density_1, width, color='Green', label=condition_1,alpha=0.5)
plt.bar(density_2.index, density_2, width, color='Red', label=condition_2,alpha=0.5)
plt.legend(loc="upper right", fontsize=2)
plt.show()
Link where I saw the solution: show dates in xticks only where value exist in plot chart and hide unnecessary interpolated xtick labels
Thank you very much in advance!
You need to find the intersection of the two lists of density_1's and density_2's ticks, as reported here.
Working example:
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
N = 150
values_1 = np.random.randint(low = 5, high = 75, size = N)/100
density_1 = pd.DataFrame({'density_1': values_1})
density_1 = density_1.value_counts().sort_index(ascending = True)
density_1.index = sorted(list(set(values_1)), reverse = False)
values_2 = np.random.randint(low = 35, high = 100, size = N)/100
density_2 = pd.DataFrame({'density_2': values_2})
density_2 = density_2.value_counts().sort_index(ascending = True)
density_2.index = sorted(list(set(values_2)), reverse = False)
width = 0.005
condition_1 = 'Adele'
condition_2 = 'Extremoduro'
fig, ax = plt.subplots(figsize = (10, 5))
ax.bar(density_1.index, density_1, width, color = 'Green', label = condition_1, alpha = 0.5)
ax.bar(density_2.index, density_2, width, color = 'Red', label = condition_2, alpha = 0.5)
ax.legend(loc = 'upper right')
ax.set_xticks(list(set(density_1.index.unique()) & set(density_2.index.unique())), rotation = 90)
plt.show()
In the line:
list(set(density_1.index.unique()) & set(density_2.index.unique()))
you can select ticks which blongs to both density_1 and density_2.
Zoom in:
Here is a code for stacked bar using matplotlib (C0 and C2 are predefined arrays)
N = 1
width = 0.1
ind = [i+1 for i in range(N)]
colorsArr = plt.cm.BuPu(np.linspace(0, 0.5, len(C2)))
p = numpy.empty(len(C2), dtype=object)
plt.figure(figsize=(11,11))
prevBar = 0
for index in range(len(C2)):
plt.bar(ind, C2[index], width, bottom=prevBar, color=colorsArr[index], label=C0[index])
prevBar = prevBar + C2[index]
# positions of the x-axis ticks (center of the bars as bar labels)
tick_pos = [i+(width/2) for i in ind]
plt.ylabel('Home Category')
plt.title('Affinity - Retail Details(Home category)')
# set the x ticks with names
plt.xticks(tick_pos, ['All Transactions'])
plt.yticks(np.arange(0,70000,3000))
plt.legend(title="Line" )
plt.show()
As a result I get the following plot. I want the plot be more narrow and start from offset - How can I do it?
Leaving aside the issue of whether this is really the best way to visualize your data, you can do what you want by passing ind = [0]*N as your x-axis, centering the single bar with align='center' and then using plt.xlim(-width*2, width*2) to pad either side of the bar's width by as much as you want (adjust the factor of 2):
import numpy as np
import matplotlib.pyplot as plt
N = 1
C2 = [1400, 5000, 5400, 6000, 12000]
C0 = ['label%d' % (e+1) for e in range(len(C2))]
width = 0.1
ind = [0]*N
colorsArr = plt.cm.BuPu(np.linspace(0, 0.5, len(C2)))
p = np.empty(len(C2), dtype=object)
plt.figure(figsize=(11,11))
prevBar = 0
for index in range(len(C2)):
plt.bar(ind, C2[index], width, bottom=prevBar, color=colorsArr[index], label=C0[index], align='center')
prevBar = prevBar + C2[index]
# positions of the x-axis ticks (center of the bars as bar labels)
tick_pos = [0]
plt.ylabel('Home Category')
plt.title('Affinity - Retail Details(Home category)')
# set the x ticks with names
plt.xticks(tick_pos, ['All Transactions'])
plt.yticks(np.arange(0,70000,3000))
plt.legend(title="Line" )
plt.xlim(-width*2, width*2)
plt.show()
import matplotlib.pyplot as plt
gridnumber = range(1,4)
b1 = plt.bar(gridnumber, [0.2, 0.3, 0.1], width=0.4,
label="Bar 1", align="center")
b2 = plt.bar(gridnumber, [0.3, 0.2, 0.2], color="red", width=0.4,
label="Bar 2", align="center")
plt.ylim([0,0.5])
plt.xlim([0,4])
plt.xticks(gridnumber)
plt.legend()
plt.show()
Currently b1 and b2 overlap each other. How do I plot them separately like so:
There is an example in the matplotlib site. Basically, you just shift the x values by width. Here is the relevant bit:
import numpy as np
import matplotlib.pyplot as plt
N = 5
menMeans = (20, 35, 30, 35, 27)
menStd = (2, 3, 4, 1, 2)
ind = np.arange(N) # the x locations for the groups
width = 0.35 # the width of the bars
fig = plt.figure()
ax = fig.add_subplot(111)
rects1 = ax.bar(ind, menMeans, width, color='royalblue', yerr=menStd)
womenMeans = (25, 32, 34, 20, 25)
womenStd = (3, 5, 2, 3, 3)
rects2 = ax.bar(ind+width, womenMeans, width, color='seagreen', yerr=womenStd)
# add some
ax.set_ylabel('Scores')
ax.set_title('Scores by group and gender')
ax.set_xticks(ind + width / 2)
ax.set_xticklabels( ('G1', 'G2', 'G3', 'G4', 'G5') )
ax.legend( (rects1[0], rects2[0]), ('Men', 'Women') )
plt.show()
Below answer will explain each and every line of code in the simplest manner possible:
# Numbers of pairs of bars you want
N = 3
# Data on X-axis
# Specify the values of blue bars (height)
blue_bar = (23, 25, 17)
# Specify the values of orange bars (height)
orange_bar = (19, 18, 14)
# Position of bars on x-axis
ind = np.arange(N)
# Figure size
plt.figure(figsize=(10,5))
# Width of a bar
width = 0.3
# Plotting
plt.bar(ind, blue_bar , width, label='Blue bar label')
plt.bar(ind + width, orange_bar, width, label='Orange bar label')
plt.xlabel('Here goes x-axis label')
plt.ylabel('Here goes y-axis label')
plt.title('Here goes title of the plot')
# xticks()
# First argument - A list of positions at which ticks should be placed
# Second argument - A list of labels to place at the given locations
plt.xticks(ind + width / 2, ('Xtick1', 'Xtick3', 'Xtick3'))
# Finding the best position for legends and putting it
plt.legend(loc='best')
plt.show()
Sometimes could be tricky to find the right bar width. I usually use this np.diff to find the right dimension.
import numpy as np
import matplotlib.pyplot as plt
#The data
womenMeans = (25, 32, 34, 20, 25)
menMeans = (20, 35, 30, 35, 27)
indices = [5.5,6,7,8.5,8.9]
#Calculate optimal width
width = np.min(np.diff(indices))/3
fig = plt.figure()
ax = fig.add_subplot(111)
# matplotlib 3.0 you have to use align
ax.bar(indices-width,womenMeans,width,color='b',label='-Ymin',align='edge')
ax.bar(indices,menMeans,width,color='r',label='Ymax',align='edge')
ax.set_xlabel('Test histogram')
plt.show()
# matplotlib 2.0 (you could avoid using align)
# ax.bar(indices-width,womenMeans,width,color='b',label='-Ymin')
# ax.bar(indices,menMeans,width,color='r',label='Ymax')
This is the result:
What if my indices on my x axis are nominal values like names:
#
import numpy as np
import matplotlib.pyplot as plt
# The data
womenMeans = (25, 32, 34, 20, 25)
menMeans = (20, 35, 30, 35, 27)
indices = range(len(womenMeans))
names = ['Asian','European','North Amercian','African','Austrailian','Martian']
# Calculate optimal width
width = np.min(np.diff(indices))/3.
fig = plt.figure()
ax = fig.add_subplot(111)
ax.bar(indices-width/2.,womenMeans,width,color='b',label='-Ymin')
ax.bar(indices+width/2.,menMeans,width,color='r',label='Ymax')
#tiks = ax.get_xticks().tolist()
ax.axes.set_xticklabels(names)
ax.set_xlabel('Test histogram')
plt.show()
Here are two examples of creating a side-by-side bar chart when you have more than two "categories" in a group.
Manual Method
Manually set the position and width of each bar.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import ticker
coins = ['penny', 'nickle', 'dime', 'quarter']
worth = np.array([.01, .05, .10, .25])
# Coin values times *n* coins
# This controls how many bars we get in each group
values = [worth*i for i in range(1,6)]
n = len(values) # Number of bars to plot
w = .15 # With of each column
x = np.arange(0, len(coins)) # Center position of group on x axis
for i, value in enumerate(values):
position = x + (w*(1-n)/2) + i*w
plt.bar(position, value, width=w, label=f'{i+1}x')
plt.xticks(x, coins);
plt.ylabel('Monetary Value')
plt.gca().yaxis.set_major_formatter(ticker.FormatStrFormatter('$%.2f'))
plt.legend()
Pandas Method
If you put the data into a pandas DataFrame, pandas will do the hard stuff for you.
import pandas as pd
coins = ['penny', 'nickle', 'dime', 'quarter']
worth = [0.01, 0.05, 0.10, 0.25]
df = pd.DataFrame(worth, columns=['1x'], index=coins)
df['2x'] = df['1x'] * 2
df['3x'] = df['1x'] * 3
df['4x'] = df['1x'] * 4
df['5x'] = df['1x'] * 5
from matplotlib import ticker
import matplotlib.pyplot as plt
df.plot(kind='bar')
plt.ylabel('Monetary Value')
plt.gca().yaxis.set_major_formatter(ticker.FormatStrFormatter('$%.2f'))
plt.gca().xaxis.set_tick_params(rotation=0)
Pandas creates a similar figure...