How to plot pseudo-3d bar chart in matplotlib? - python

I'd like to prepare some statistics for my boss. The flat style of matplotlib bar chart would make them look cheap for those used to Excel charts, although for clarity, using styles like this probably should be avoided.
I'm not that far away, but I don't get how to give the right thickness of the bars:
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
row = [0, 0, 0, 22, 0, 0, 4, 16, 2, 0, 4, 4, 12, 26]
length = len(row)
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
x = np.arange(length)
y = np.zeros(14)
z = np.array(row)
width = 0.8
ax.bar3d(x, y, [0]*length, 0.5, 0.001, z)
ax.set_xticks(x + width/2)
ax.set_xticklabels(titles[2:], rotation=90)
ax.set_yticks(y)
ax.set_zlabel('count')
plt.show()
Result:

The thickness of the bars are set by the dx, dy arguments in ax.bar3d for which you have the values 0.5, 0.001. The issue, as I'm sure you noticed is that changing dy will change the length of the bar (in your case the untitled axis), but matplotlib helpfully rescales the y axis so the data fills it. This makes it look strange (I am assuming this is the problem, sorry if it isn't).
To remedy this you could set the y limits using ax.set_ylim(0, 0.002) (basically your y values go from 0->0.001). If you change either dy or the value of y given to bar3d which is currently 0, then you will need to update the limits accordingly.
Example:
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
row = [0, 0, 0, 22, 0, 0, 4, 16, 2, 0, 4, 4, 12, 26]
length = len(row)
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.bar3d(range(length), [0]*length, [0]*length, 0.5, 0.001, row)
ax.set_ylim(-0.005, 0.005)
plt.show()

Related

Matplotlib colorbar: some ticks appear without labels

I'm using plr.scatter and logariphmic scale, and i'm trying to add some specific tick values to the colorbar, but it seems to work really arbitrary. See the example:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
import matplotlib
from matplotlib.ticker import LogFormatter
x, y = np.meshgrid(np.linspace(0, 1, 30), np.linspace(0, 1, 30))
z = x**2 + 15*y**3 + 1.5
plt.figure(figsize=(9, 4.5))
plt.scatter(x, y, c=z, cmap=cm.jet, norm=matplotlib.colors.LogNorm(), vmin=1, vmax=20)
formatter = LogFormatter(10, labelOnlyBase=False)
cbar = plt.colorbar(ticks=[1, 2, 5, 10, 15, 20], format=formatter)
This code produced all the required major ticks, plus some minor ticks, but only labeled 1 and 10, while I need all numbers to be seen in colorbar. At first I though it was due to the fact that 1 and 10 are integer powers of 10, and other number are not, but...
...if I change the log base to 2, we can see tick labels at 1 and 2, which are powers of 2, but we also see labels at 5, 10 and 20, which are not. 15 did not appear this time too, but if I try adding 17 it works (not shown on the picture, but it does)
formatter = LogFormatter(2, labelOnlyBase=False)
What is this sorcery and how do I make matplotlib add exactly the labels I want to the ticks? I can do it manually by using
cbar.ax.set_yticklabels(['1', '2', '5', '10', '15', '20'])
but it seems redundant. Is there a better way?
You can format any axis ticks with formatter. Below is the example .
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
import matplotlib
from matplotlib.colors import LogNorm
x, y = np.meshgrid(np.linspace(0, 1, 30), np.linspace(0, 1, 30))
z = x**2 + 15*y**3 + 1.5
f, ax = plt.subplots(figsize=(9, 4.5))
p = plt.scatter(x, y, c=z, cmap=cm.jet, norm=LogNorm(vmin=1, vmax=20) )
v1 = np.linspace(z.min(), z.max(), 8, endpoint=True)
cbar=plt.colorbar(ticks=v1)
cbar.ax.set_yticklabels(["{:4.2f}".format(i) for i in v1]) # add the labels
LogFormatter and its subclasses use the minor_thresholds parameter to decide when to hide non-decade tick labels to prevent overcrowding. By default this will hide nearly all non-decade labels, but you can increase it to allow more labels to appear.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
from matplotlib.ticker import LogFormatter
from matplotlib.colors import LogNorm
x, y = np.meshgrid(np.linspace(0, 1, 30), np.linspace(0, 1, 30))
z = x**2 + 15*y**3 + 1.5
plt.figure(figsize=(9, 4.5))
cnorm = LogNorm(vmin=1, vmax=20)
plt.scatter(x, y, c=z, cmap=cm.jet, norm=cnorm)
# define minor_thresholds to be >= the range of the color scale
decades = np.ceil(np.log10(cnorm.vmax / cnorm.vmin))
formatter = LogFormatter(10, minor_thresholds=(decades, decades))
cbar = plt.colorbar(ticks=[1, 2, 5, 10, 15, 20], format=formatter)

Plotting randomly stacked cubes in 3D- mplot3d?

I am exploring random stackings of cubes.
I started with 2D and can generate random packings within a rectangle like this:
Now I have the code to generalize the stacking to 3D, but I am struggling to generalize the visualization. An example data set is, filling a 3x3x3 cube with 1x1x1 and 2x2x2 cubes,
#the coordinates of a corner vertex of the 19 1x1x1 cubes
x1 = [1, 0, 2, 0, 0, 0, 2, 1, 0, 1, 2, 2, 0, 0, 0, 2, 0, 1, 1]
y1 = [1, 1, 0, 2, 0, 0, 2, 2, 2, 0, 1, 0, 1, 2, 1, 0, 0, 0, 0]
z1 = [2, 1, 1, 0, 1, 2, 2, 2, 2, 1, 2, 0, 0, 1, 2, 2, 0, 0, 2]
#the coordinates of a corner vertex of the 1 2x2x2 cube
x2 = [1]
y2 = [1]
z2 = [0]
# I believe the random filling is working because
# the total volumes equal: 19 + 2**3 = 3**3
#I would like to start with the lists
X = [x1,x2]
Y = [y1,y2]
Z = [z1,z2]
sizes = [1,2]
#because I want to generalize the visualization to n sizes
So far, all I have the knowledge to do is plot a 3D scatter of the data
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
for ii in range(len(sizes)):
ax.scatter(X[ii],Y[ii],Z[ii])
plt.show()
I would like to make a plot more like this, except with variable sizes.
Any help would be greatly appreciated! I have a lot to learn about matplotlib/pyplot and so on.
I have made a little bit of progress:
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle, PathPatch
from mpl_toolkits.mplot3d import Axes3D
import mpl_toolkits.mplot3d.art3d as art3d
def cube(a,b,c,l):
for zz in [c,c+l]:
for i in ["x","y","z"]:
side = Rectangle((a, b), l,l)
ax.add_patch(side)
art3d.pathpatch_2d_to_3d(side, z=zz, zdir=i)
fig = plt.figure()
ax=fig.gca(projection='3d')
cube(0,0,0,1)
ax.set_xlim3d(-2, 2)
ax.set_ylim3d(-2, 2)
ax.set_zlim3d(-2, 2)
plt.show()
This plots a single cube.
EDIT:
More progress, I am now very close
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle, PathPatch
from mpl_toolkits.mplot3d import Axes3D
import mpl_toolkits.mplot3d.art3d as art3d
cmap = plt.get_cmap('spring') #define the colors of the plot
colors = [cmap(i) for i in np.linspace(0.1, 0.9, n+1)]
def cube(a,b,c,l): #plots a cube of side l at (a,b,c)
for ll in [0,l]:
for i in range(3):
dire= ["x","y","z"]
xdire = [b,a,a]
ydire = [c,c,b]
zdire = [a,b,c]
side = Rectangle((xdire[i], ydire[i]),facecolors[np.where(sizes == l)[0]],edgecolor='black')
ax.add_patch(side)
art3d.pathpatch_2d_to_3d(side, z=zdire[i]+ll, zdir=dire[i])
def plotter3D(X,Y,Z,sizes): #run cube(a,b,c,l) over the whole data set
for iX in range(len(X)):
x = X[iX]
y = Y[iX]
z = Z[iX]
for ix in range(len(x)):
cube(x[ix],y[ix],z[ix],sizes[iX])
fig = plt.figure() #open a figure
ax=fig.gca(projection='3d') #make it 3d
plotter3D(X,Y,Z,sizes) #generate the cubes from the data set
ax.set_xlim3d(0, length) #set the plot ranges
ax.set_ylim3d(0, width)
ax.set_zlim3d(0, height)
plt.show()
This generates the desired output, although it seems to be see-through in some places when viewed from certain angles. You can see this in the small cube-- dead center at coordinates (1.5,2,3) Any idea how to fix this?
Another edit:
The solution outined above has two problems: (1) I can't get equal aspect ratios for the three axes, and (2) The cubes are see-through from certain angles. Here's what the output looks like for a larger system

How to add counts of points as a label in a sparse scatter plot

I have sparse scatter plot to visualize the comparison of predicted vs actual values. The range of the values are 1-4 and there are no decimal points.
I have tried plotly so far with hte following code (but I can also use a matplotlib solution):
my_scatter = go.Scatter(
x = y_actual, y = y_pred, mode = 'markers',
marker = dict(color = 'rgb(240, 189, 89)', opacity=0.5)
)
This prints the graph nicely (see below). I use opacity to see the density at each point. I.e. if two points lie on top of each other, the point will be shown in darker color. However, this is not explanatory enough. Is it possible to add the counts at each point as a label? There are some overlaps at certain intersections. I want to display how many points intersects. Can this be done automatically using matplotlib or plotly?
This answer uses matplotlib.
To answer the initial question first: You need to find out how often the data produces a point at a given coordinate to be able to annotate the points. If all values are integers this can easily be done using a 2d histogram. Out of the hstogram one would then select only those bins where the count value is nonzero and annotate the respective values in a loop:
x = [3, 0, 1, 2, 2, 0, 1, 3, 3, 3, 4, 1, 4, 3, 0]
y = [1, 0, 4, 3, 2, 1, 4, 0, 3, 0, 4, 2, 3, 3, 1]
import matplotlib.pyplot as plt
import numpy as np
x = np.array(x)
y = np.array(y)
hist, xbins,ybins = np.histogram2d(y,x, bins=range(6))
X,Y = np.meshgrid(xbins[:-1], ybins[:-1])
X = X[hist != 0]; Y = Y[hist != 0]
Z = hist[hist != 0]
fig, ax = plt.subplots()
ax.scatter(x,y, s=49, alpha=0.4)
for i in range(len(Z)):
ax.annotate(str(int(Z[i])), xy=(X[i],Y[i]), xytext=(4,0),
textcoords="offset points" )
plt.show()
You may then decide not to plot all points but the result from the histogramming which offers the chance to change the color and size of the scatter points,
ax.scatter(X,Y, s=(Z*20)**1.4, c = Z/Z.max(), cmap="winter_r", alpha=0.4)
Since all values are integers, you may also opt for an image plot,
fig, ax = plt.subplots()
ax.imshow(hist, cmap="PuRd")
for i in range(len(Z)):
ax.annotate(str(int(Z[i])), xy=(X[i],Y[i]), xytext=(0,0), color="w",
ha="center", va="center", textcoords="offset points" )
Without the necesity to calculate the number of occurances, another option is to use a hexbin plot. This gives slightly inaccurate positions of the dots, du to the hexagonal binning, but I still wanted to mention this option.
import matplotlib.pyplot as plt
import matplotlib.colors
import numpy as np
x = np.array(x)
y = np.array(y)
fig, ax = plt.subplots()
cmap = plt.cm.PuRd
cmaplist = [cmap(i) for i in range(cmap.N)]
cmaplist[0] = (1.0,1.0,1.0,1.0)
cmap = matplotlib.colors.LinearSegmentedColormap.from_list('mcm',cmaplist, cmap.N)
ax.hexbin(x,y, gridsize=20, cmap=cmap, linewidth=0 )
plt.show()

How to plot text below xticks (using x-coords) in matplotlib when x-axis are dates?

I'm trying to plot arbitrary text below the x tick marks in a matplotlib figure (see example figure below). I'm using dates as the x-axis and, for instance, I want to display counts of some variable associated with each date.
In the example below I use the relative positions from 0-1 within the figure for the x-coordinate of where the text should be positioned. However I've just guessed these relative values (using trial and error) and so I would like to know how would one plot text below the x-ticks using the actual positions of the x-data instead of using these 0-1 relative scaling that's the default in figtext?
import numpy as np
import matplotlib.pyplot as plt
import datetime
x = [datetime.datetime(2010, 12, 1, 0, 0),
datetime.datetime(2011, 1, 1, 0, 0),
datetime.datetime(2011, 5, 1, 1, 0)]
y = [4, 9, 2]
fig, ax = plt.subplots()
ax.bar(x, y, width = 20)
xticks = ax.get_xticks()
xtick_rel_position = np.linspace(0.13, 0.81, len(xticks)) # <- these are just guessed
counts = np.random.randint(0, 25, len(xticks))
for i, xpos in enumerate(xtick_rel_position):
plt.figtext(xpos, 0.028, "Below tick\nlabel "+str(i),
size = 6, ha = 'center')
plt.figtext(xpos, 0.005, "Count: "+str(counts[i]),
size = 6, ha = 'center')
# For better aesthetics
ax.yaxis.set_visible(False)
plt.show()
I've tried including transform = ax.transAxes with the actual x coordinates from ax.get_xticks() but this doesn't change anything.
Use ax.text for text positions relative to axis positions rather than fig positions:
import matplotlib.pyplot as plt
import datetime
import numpy as np
x = [datetime.datetime(2010, 12, 1, 0, 0),
datetime.datetime(2011, 1, 1, 0, 0),
datetime.datetime(2011, 5, 1, 1, 0)]
y = [4, 9, 2]
fig, ax = plt.subplots()
ax.bar(x, y, width = 20, align='center')
counts = np.random.randint(0, 25, len(ax.get_xticks()))
for i, xpos in enumerate(ax.get_xticks()):
ax.text(xpos,-1, "Below tick\nlabel "+str(i),
size = 6, ha = 'center')
ax.text(xpos, -1.25, "Count: "+str(counts[i]),
size = 6, ha = 'center')
You can use the date2num function to do the conversion, and a DataFormatter to display the dates correctly. Finally get_xticks is used to get the locations of the ticks for the text to be accurately displayed underneath:
import numpy as np
import matplotlib.pyplot as plt
import datetime
import matplotlib
x = [datetime.datetime(2010, 12, 1, 0, 0),
datetime.datetime(2011, 1, 1, 0, 0),
datetime.datetime(2011, 5, 1, 1, 0)]
y = [4, 9, 2]
hfmt = matplotlib.dates.DateFormatter('%b %Y')
months = matplotlib.dates.MonthLocator(range(1, 13), bymonthday=1, interval=1)
xs = matplotlib.dates.date2num(x)
fig, ax = plt.subplots()
ax.bar(xs, y, width=20)
ax.xaxis.set_major_locator(months)
ax.xaxis.set_major_formatter(hfmt)
ax.yaxis.set_visible(False)
for i, x in enumerate(ax.get_xticks()):
plt.text(x, -1.3, "Below tick\nlabel {}\nCount x".format(i), size=7, ha='center')
fig.subplots_adjust(bottom=0.15) # Add space at bottom
plt.show()
This would display:

3D plot from a data set

I have a data set which looks like this:
Intensity = ( [1, 2, 3, 4], [6, 7, 9, 10] )
Xposition = (0.1, 0.2, 0.3, 0.4)
Yposition = (1E^-9, 1.2E^-9)
So, for each Yposition, we have an 1D array stored in Intensity, corresponding to each Xposition.
Now I want to plot Xposition (X-axis), Yposition (Y-axis) and Intensity along Z to generate a 3D plot. How can I do this using matplotlib?
There are nice tutorials on matplotlib page. Looking at two examples and slightly tweaking the code:
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
x = (0.1,0.2,0.3,0.4)
y = (10**-9, 1.2*10**-9)
x,y = np.meshgrid(x,y)
z = ( [1,2,3,4], [6,7,9,10] )
ax.scatter(x, y, z)
plt.show()

Categories