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()
Related
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 am working on an energy balance and I am currently creating a barplot. I want to compare the generation and consumption of electricity over the years. Therefore I have 2 stacked bars for each year. This is how far I got:
import matplotlib.pyplot as plt
import numpy as np
generation_2020 = [20, 30, 40, 20, 10]
consumption_2020 = [50,50]
import_2020 = 20
if import_2020 >0:
generation_2020 = np.append(generation_2020, import_2020)
else:
consumption_2020 = np.insert(consumption_2020, 0, import_2020*-1, axis=0)
generation_2025 = np.array([20, 20, 20, 20, 10])
consumption_2025 = np.array([50,50])
import_2025 = -10
if import_2025 >0:
generation_2025 = np.append(generation_2025, import_2025)
else:
consumption_2025 = np.insert(consumption_2025, 0, import_2025*-1, axis=0)
data = np.array([generation_2020, generation_2025])
data2 = np.array([consumption_2020, consumption_2025])
label_data = ['renewables','gas','cole', 'storage', 'nuclear', 'import', 'consumption', 'storage']
x2 = ['2020', '2025']
x3 = ['Erzeugung' , '\n\n\n\n\n\n2020', 'Verbrauch', 'Erzeugung' , '\n\n\n\n\n\n2025', 'Verbrauch']
x_pos = np.arange(len(x2))
width = 0.2
x = list()
for i in x_pos:
x.extend([i-width, i, i+width])
fig, ax = plt.subplots()
for i in range(data.shape[1]):
bottom = np.sum(data[:, 0:i], axis=1)
ax.bar(x_pos - width, data[:, i], bottom=bottom, width=width, label=f"label {i}")
for i in range(data2.shape[1]):
bottom = np.sum(data2[:, 0:i], axis=1)
ax.bar(x_pos+ width, data2[:, i], bottom=bottom, width=width, label=f"label {i}")
ax.set_ylabel('[TWh]')
ax.set_xticks(x)
ax.set_xticklabels(x3)
ax.tick_params(axis='x', which='both',length=0, labelsize = 10)
for label in ax.get_xmajorticklabels():
if 'u' in label.get_text(): label.set_rotation(90)
ax.margins(y=0.1) # some extra padding to place the bar labels
plt.legend(label_data, bbox_to_anchor=(1, 1))
fig.tight_layout()
plt.show()
This works if both import_2020 and import_2025 are either both negative or both positive. If one is negative and the other is positive it doesnt.
This is the error message I get:
for i in range(data.shape[1]):
IndexError: tuple index out of range
The problem is that generation_2020 and generation_2025 dont have the same length. One has 5 entries and the other 6. Does someone have an Idea how to solve this?
I am trying to make a normalized histogram in matplotlib, however I want it normalized such that the total area will be 1000. Is there a way to do this?
I know to get it normalized to 1, you just have to include density=True,stacked=True in the argument of plt.hist(). An equivalent solution would be to do this and multiply the height of each column by 1000, if that would be more doable than changing what the histogram is normalized to.
Thank you very much in advance!
The following approach uses np.histogram to calculate the counts for each histogram bin. Using 1000 / total_count / bin_width as normalization factor, the total area will be 1000. On the contrary, to get the sum of all bar heights to be 1000, a factor of 1000 / total_count would be needed.
plt.bar is used to display the end result.
The example code calculates the same combined histogram with density=True, to compare it with the new histogram summing to 1000.
import matplotlib.pyplot as plt
import numpy as np
data = [np.random.randn(100) * 5 + 10, np.random.randn(300) * 4 + 14, np.random.randn(100) * 3 + 17]
fig, (ax1, ax2) = plt.subplots(ncols=2, figsize=(12, 4))
ax1.hist(data, stacked=True, density=True)
ax1.set_title('Histogram with density=True')
xmin = min([min(d) for d in data])
xmax = max([max(d) for d in data])
bins = np.linspace(xmin, xmax, 11)
bin_width = bins[1] - bins[0]
counts = [np.histogram(d, bins=bins)[0] for d in data]
total_count = sum([sum(c) for c in counts])
# factor = 1000 / total_count # to sum to 1000
factor = 1000 / total_count / bin_width # for an area of 1000
thousands = [c * factor for c in counts]
bottom = 0
for t in thousands:
ax2.bar(bins[:-1], t, bottom=bottom, width=bin_width, align='edge')
bottom += t
ax2.set_title('Histogram with total area of 1000')
plt.show()
An easy way to do this is to set up a second y-axis whose tick labels are the original multiplied by 1000, then hide the original axis' ticks:
import matplotlib.pyplot as plt
import numpy as np
data = [np.random.randn(5000)]
fig, ax1 = plt.subplots()
ax2 = ax1.twinx()
#hist returns a tuple that contains a list of y values at its 0 index:
y,_,_ = ax1.hist(data, density=True, bins=10, edgecolor = 'black')
#find max y value of histogram and multiply by 1000:
max_y = np.round(y.max(),1)*1000
#set up the second y-axis ticks as increments of max_y:
ax2.set_ylim(0,max_y)
ax2.set_yticks(np.linspace(0, max_y, 9))
#hide original y-axis ticks:
ax1.axes.yaxis.set_ticks([])
plt.show()
I find the code example for drawing scatter pie chat
In this example, the size of each pie slices is identical across all three scatters. I would like to know if it is possible to make each pie chart unique (different number of slices and different pie proportions)
Yes, it's totally possible. Here's a function that plot a pie chart at given position with a given size:
def draw_pie(dist,
xpos,
ypos,
size,
ax=None):
if ax is None:
fig, ax = plt.subplots(figsize=(10,8))
# for incremental pie slices
cumsum = np.cumsum(dist)
cumsum = cumsum/ cumsum[-1]
pie = [0] + cumsum.tolist()
for r1, r2 in zip(pie[:-1], pie[1:]):
angles = np.linspace(2 * np.pi * r1, 2 * np.pi * r2)
x = [0] + np.cos(angles).tolist()
y = [0] + np.sin(angles).tolist()
xy = np.column_stack([x, y])
ax.scatter([xpos], [ypos], marker=xy, s=size)
return ax
Using that function, we can draw, say three pie charts:
fig, ax = plt.subplots(figsize=(10,8))
draw_pie([1,2,1],1,1,10000,ax=ax)
draw_pie([2,2,2,2], 2, 1, 20000, ax=ax)
draw_pie([1,1,1,1,1], 1.5,1.5, 30000, ax=ax)
plt.xlim(0.6,2.5)
plt.ylim(0.8, 1.8)
plt.show()
gives:
you could implement it like this:
import numpy as np
import matplotlib.pyplot as plt
def drawPieMarker(xs, ys, ratios, sizes, colors):
assert sum(ratios) <= 1, 'sum of ratios needs to be < 1'
markers = []
previous = 0
# calculate the points of the pie pieces
for color, ratio in zip(colors, ratios):
this = 2 * np.pi * ratio + previous
x = [0] + np.cos(np.linspace(previous, this, 10)).tolist() + [0]
y = [0] + np.sin(np.linspace(previous, this, 10)).tolist() + [0]
xy = np.column_stack([x, y])
previous = this
markers.append({'marker':xy, 's':np.abs(xy).max()**2*np.array(sizes), 'facecolor':color})
# scatter each of the pie pieces to create pies
for marker in markers:
ax.scatter(xs, ys, **marker)
fig, ax = plt.subplots()
drawPieMarker(xs=np.random.rand(3),
ys=np.random.rand(3),
ratios=[.3, .2, .5],
sizes=[80, 60, 100],
colors=['cyan', 'orange', 'teal'])
drawPieMarker(xs=np.random.rand(2),
ys=np.random.rand(2),
ratios=[.33, .66],
sizes=[100, 120],
colors=['blue', 'yellow'])
drawPieMarker(xs=np.random.rand(2),
ys=np.random.rand(2),
ratios=[.33, .25],
sizes=[50, 75],
colors=['maroon', 'brown'])
plt.show()
I want to separate or increase the distance of my table and my graph so they don't layover. I thought of increasing the size to right and put the table there but I can't seem to make it work, and I can't find a way to offset the table by 1 line.
Graph
global dataread
global top4
global iV
top4mod = [] #holder for table, combines amplitude and frequency (bin*3.90Hz)
plt.plot(x1, fy1, '-') #plot x-y
plt.axis([0, 500, 0, 1.2]) #range for x-y plot
plt.xlabel('Hz')
columns = ('Frequency','Hz')
rows = ['# %d' % p for p in (1,2,3,4)] #top4
colors = 'C0'
print(len(rows))
print(len(str(top4)))
print(top4)
iV=[d*bins for d in iV] # convert bins into frequency
i=0;
FirstCol = [4, 3, 2, 1]
while i < 4:
Table.append([iV[i]] + [top4[i]])#[FirstCol[i]]
i = i+1
cell_text = []
n_rows = len(Table)
index = np.arange(len(columns)) + 1 #0.3 orginal
bar_width = 0.4
y_offset = np.array([0.0] * len(columns))
for row in range(n_rows):
#plt.bar(index, Table[row], bar_width, bottom=y_offset, color='C0') #dont use this
y_offset = y_offset + Table[row]
cell_text.append(['%1.1f' % p for p in y_offset])
the_table = plt.table(cellText=Table,rowLabels=rows, colLabels=columns,loc='bottom')
#plt.figure(figsize=(7,8))
# Adjust layout to make room for the table:
plt.subplots_adjust(bottom=0.2) #left=0.2, bottom=0.2
plt.show() #display plot
Using bbox
You can set the position of the table using the bbox argument. It expects either a bbox instance or a 4-tuple of values (left, bottom, width, height), which are in axes coordinates. E.g.
plt.table(..., bbox=[0.0,-0.5,1,0.3])
produces a table that is as wide as the axes (left=0, width=1) but positionned below the axes (bottom=-0.5, height=0.3).
import numpy as np
import matplotlib.pyplot as plt
data = np.random.rand(4,2)
columns = ('Frequency','Hz')
rows = ['# %d' % p for p in (1,2,3,4)]
plt.plot(data[:,0], data[:,1], '-') #plot x-y
plt.axis([0, 1, 0, 1.2]) #range for x-y plot
plt.xlabel('Hz')
the_table = plt.table(cellText=data,rowLabels=rows, colLabels=columns,
loc='bottom', bbox=[0.0,-0.45,1,.28])
plt.subplots_adjust(bottom=0.3)
plt.show()
Create dedicated axes
You can also create an axes (tabax) to put the table into. You would then set the loc to "center", turn the axis spines off and only use a very small subplots_adjust bottom parameter.
import numpy as np
import matplotlib.pyplot as plt
data = np.random.rand(4,2)
columns = ('Frequency','Hz')
rows = ['# %d' % p for p in (1,2,3,4)]
fig, (ax, tabax) = plt.subplots(nrows=2)
ax.plot(data[:,0], data[:,1], '-') #plot x-y
ax.axis([0, 1, 0, 1.2]) #range for x-y plot
ax.set_xlabel('Hz')
tabax.axis("off")
the_table = tabax.table(cellText=data,rowLabels=rows, colLabels=columns,
loc='center')
plt.subplots_adjust(bottom=0.05)
plt.show()