Creating a colorbar in matplotlib given the code below - matplotlib

I'm having a really hard time creating a colourbar given the code below. I had the idea of colouring the lines one by one as my code adds the plot lines for each cycle iteration. What matters is that for each main cycle iteration (i in stim_strength_list) a new line is added for each of the subplots. And the colour values for such line get continuously updated.
Now the issue of such a code is that I'm having an extremely hard time creating a colourbar sitting to the right of all the subplot. Most importantly because I have no ScalarMappable object that can be fed into the plt.colorbar() function. And I don't know of any other function that could get the job done properly.
I see a lot of tutorials on-line where no parameters are given at all to the function and everything works just fine but I don't know why it isn't the case for me. I just began programming recently so I'm really sure that there is something really simple flyin over my head, but despite having searched all over stack overflow I haven't managed to find anything that could fix my specific problem.
stim_strength_list = [int(x)*10 for x in range(0,81)]
stp_list = [int(x) for x in range(51,71)]
area_list = ['V1', 'TO', 'AT', 'PFL', 'PML', 'M1L', 'A1', 'AB', 'PB', 'PFI', 'PMI', 'M1I']
# CREATE THE FIGURE
fig, axis = plt.subplots(2, 6, sharey=True, sharex=True, squeeze=True, figsize=(30, 22.5))
fig.suptitle("Activation plots for each area at different levels of strength, spiking architecture", fontsize=25.0, y=0.99)
# COLOURS
red = 0.0
blue = 1.0
for i in stim_strength_list:
# CREATING VALUES TO BE PLOTTED
a = df_s.drop(df_s[df_s['stim_strength'] != i].index)
b = a.groupby('stp').mean()
for j in area_list: # here a create an empty list for each of the 12 areas
globals()[j]=[] # i.e.: V1 = [], TO = [], AT = [], ...
for j in stp_list: # here I cycle through the time steps..
for k in area_list:
globals()[k].append(b.loc[j, k]) # to append values to each area called upon
# PLOTTING
for row in range(0, 2): # given the plt.subplots() methods refers to the subplots via a matrix coordinate system...
for col in range (0, 6): # ...I use this nested loop to plot values in each list in its respective subplot
if row == 0:
axis[row, col].plot(stp_list, globals()[area_list[col]], color=(red, 0, blue), linewidth=1)
else: # about the col + 6 indexing, if I'm in the second row of the subplots I just add 6 to the current value of col to fetch the appropriate brain area this is just
axis[row, col].plot(stp_list, globals()[area_list[col + 6]], color=(red, 0, blue), linewidth=1)
blue -= 1/len(stim_strength_list) # this is to make it that the first line is fully blue...
red += 1/len(stim_strength_list) # ... and the final one is fully red
# TITLING
for row in range(0, 2): #same logic as above, I did an external nested loop simply because the titling would be done for len(stp_list)*12 times
for col in range (0, 6):
if row == 0:
axis[row, col].set_title(area_list[col], fontsize=25)
else:
axis[row, col].set_title(area_list[col + 6], fontsize=25)
# LABELLING
axis[0, 0].set_ylabel('Amount of cells', fontsize=20)
axis[1, 0].set_ylabel('Amount of cells', fontsize=20)
for i in range(0, 6):
axis[1, i].set_xlabel('Time step', fontsize=20)
fig.tight_layout()
plt.show()
This is the code which creates everything. What is missing is the dataframe from which data is taken but i think that you can get an idea by looking at the result.
Final plot
What I would love to achieve is to have a colorbar which displays the colors employed to color the different lines within each subplot. Such transition is determined by progressively increasing and decreasing the red and blue values within the color attribute in .plot() method.
If I just add plt.colorbar() I will get the following error:
Traceback (most recent call last):
  at block x, line y
  at /opt/python/envs/default/lib/python3.8/site-packages/matplotlib/pyplot.py, line 2084, in colorbar(mappable, cax, ax, **kw)
RuntimeError: No mappable was found to use for colorbar creation. First define a mappable such as an image (with imshow) or a contour set (with contourf).
What would you do with the code available to achieve the result I described above?

Related

Matplotlib: Adding a point to an existing scatterplot that has already been displayed (without needing to recreate)

I'm trying to add a point to a scatterplot that I've already displayed. For simplicity, I'd rather not recreate the plot shown previously, but rather display a previous plot and not have it cleared.
For example: Here is my first plot...
fig, ax = plt.subplots(1,1)
ax.scatter(owners.Income, owners.Lot_Size, marker='o', label='Owner', color='C1')
ax.scatter(nonowners.Income, nonowners.Lot_Size, marker='D', label='Nonowner', color='C0')
plt.xlabel('Income') # set x-axis label
plt.ylabel('Lot_Size') # set y-axis label
# iterate through each row and add the number of each observation to the scatter plot
for index, row in train_data.iterrows(): # look through each row of our data
ax.annotate(index, (row.Income + 1, row.Lot_Size)) # add the number of the observation next to point
handles, labels = ax.get_legend_handles_labels() # add a legend
ax.legend(handles, labels, loc=4)
ax.set_xlim(40, 115) # set the x range
plt.show()
Then, what I'd like to do in the next code cell is to display a new point on this existing figure/ax....
new_household = pd.DataFrame([{'Income': 60, 'Lot_Size': 20}])
new_household
ax.scatter(new_household.Income, new_household.Lot_Size, marker='*', label='New household', color='black', s=150)
plt.show() # nothing displays, since the previous figure is now cleared
The second plot doesn't display because the previous figure has been cleared by the plt.show function.
How can I show the first scatterplot, but keep the figure and ax's so that I can continue add to the plot in later cells (without having to recreate the complete plot each time)?

Dynamically scaling axes during a matplotlib ArtistAnimation

It appears to be impossible to change the y and x axis view limits during an ArtistAnimation, and have the frames replayed with different axis limits.
The limits seem to fixed to those set last before the animation function is called.
In the code below, I have two plotting stages. The input data in the second plot is a much smaller subset of the data in the 1st frame. The data in the 1st stage has a much wider range.
So, I need to "zoom in" when displaying the second plot (otherwise the plot would be very tiny if the axis limits remain the same).
The two plots are overlaid on two different images (that are of the same size, but different content).
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import matplotlib.image as mpimg
import random
# sample 640x480 image. Actual frame loops through
# many different images, but of same size
image = mpimg.imread('image_demo.png')
fig = plt.figure()
plt.axis('off')
ax = fig.gca()
artists = []
def plot_stage_1():
# both x, y axis limits automatically set to 0 - 100
# when we call ax.imshow with this extent
im_extent = (0, 100, 0, 100) # (xmin, xmax, ymin, ymax)
im = ax.imshow(image, extent=im_extent, animated=True)
# y axis is a list of 100 random numbers between 0 and 100
p, = ax.plot(range(100), random.choices(range(100), k=100))
# Text label at 90, 90
t = ax.text(im_extent[1]*0.9, im_extent[3]*0.9, "Frame 1")
artists.append([im, t, p])
def plot_stage_2():
# axes remain at the the 0 - 100 limit from the previous
# imshow extent so both the background image and plot are tiny
im_extent = (0, 10, 0, 10)
# so let's update the x, y axis limits
ax.set_xlim(im_extent[0], im_extent[1])
ax.set_ylim(im_extent[0], im_extent[3])
im = ax.imshow(image, extent=im_extent, animated=True)
p, = ax.plot(range(10), random.choices(range(10), k=10))
# Text label at 9, 9
t = ax.text(im_extent[1]*0.9, im_extent[3]*0.9, "Frame 2")
artists.append([im, t, p])
plot_stage_1()
plot_stage_2()
# clear white space around plot
fig.subplots_adjust(left=0, bottom=0, right=1, top=1, wspace=None, hspace=None)
# set figure size
fig.set_size_inches(6.67, 5.0, True)
anim = animation.ArtistAnimation(fig, artists, interval=2000, repeat=False, blit=False)
plt.show()
If I call just one of the two functions above, the plot is fine. However, if I call both, the axis limits in both frames will be 0 - 10, 0 - 10. So frame 1 will be super zoomed in.
Also calling ax.set_xlim(0, 100), ax.set_ylim(0, 100) in plot_stage_1() doesn't help. The last set_xlim(), set_ylim() calls fix the axis limits throughout all frames in the animation.
I could keep the axis bounds fixed and apply a scaling function to the input data.
However, I'm curious to know whether I can simply change the axis limits -- my code will be better this way, because the actual code is complicated with multiple stages, zooming plots across many different ranges.
Or perhaps I have to rejig my code to use FuncAnimation, instead of ArtistAnimation?
FuncAnimation appears to result in the expected behavior. So I'm changing my code to use that instead of ArtistAnimation.
Still curious to know though, whether this can at all be done using ArtistAnimation.

Pandas / Plot.plt Line Graph, X Values horizontal

How can I pass an argument to show all of the x value so one could read it? As well, how can I show many lines of data, and with alegend?
plt. plot(a, b, linewidth=2.0 )
You could display only n-th x-ticks and make a plot bigger to accomodate more labels.
df = pd.DataFrame(data=np.random.rand(10), index=pd.Series(np.random.rand(10)).astype(str)+'_index')
0
0.007017115173211574_index 0.963285
0.434969747965131_index 0.547248
0.18021258326382017_index 0.719402
0.7815848046772174_index 0.061448
0.8856299613744312_index 0.771062
0.16840431221766328_index 0.524256
0.8662531211345982_index 0.528706
0.6389453277004077_index 0.287410
0.7444490769967744_index 0.513631
0.8965709043061524_index 0.892011
plt.subplots(figsize=(20,15)) # make plot bigger
plt.plot(df.index, df[0]*2, linewidth=.9) # plot several lines
plt.plot(df.index, df[0].rename('1'),linewidth=.9) # plot several lines
plt.xticks(df.index[::2]) # tick every 2nd label on x axis.
plt.legend() # show legend

Plot axvline from Point to Point in Matplotlib Python 3.6

I am reading Data from a Simulation out of an Excel File. Out of this Data I generated two DataFrames containing 200 values. Now i want to plot all the Values from DataFrame one in blue and all Values from DataFrame two in purple. Therefore I have following code:
df = pd.read_excel("###CENSORED####.xlsx", sheetname="Data")
unpatched = df["Unpatched"][:-800]
patched = df["Patched"][:-800]
x = range(0,len(unpatched))
fig = plt.figure(figsize=(10, 5))
plt.scatter(x, unpatched, zorder=10, )
plt.scatter(x, patched, c="purple",zorder=19,)
This results in following Graph:
But now i want to draw in some lines that visualize the difference between the blue and purple dots. I thought about an orange line going from blue dot at simulation-run x to the purple dot at simulation-run x. I've tried to "cheat" with following code, since I'm pretty new to matplotlib.
scale_factor = 300
for a in x:
plt.axvline(a, patched[a]/scale_factor, unpatched[a]/scale_factor, c="orange")
But this resulted in a inaccuracy as seen seen below:
So is there a smarter way to do this? I've realized that the axvline documentation only says that ymin, ymax can only be scalars. Can I somehow turn my given values into fitting scalars?

How might I plot multiple lines (same data, different styles) with a single plotting command in matplotlib?

In my example, I want to plot a "highlighting" line (a heavy black line with a narrower yellow line over it to create a black outline) behind the actual data (the blue 'Pitch Angle' line in my example).
My function highlight does this for elements in pandas Series x where h is True and also finally plots x. highlight returns unique identifiers for the highlighting lines created and the actual line styles used to allow the legend entries to be edited to produce what you see in the image, but as far as I can tell that has to be done after all plot commands, i.e. outside of highlight, assuming other things will be added to the Axes e.g. the 'Fine pitch' and '10 degrees' lines in my example.
def highlight(x, h, hlw=8, hc='#FFFBCC', hll='Highlighted', **kwargs):
"""
Plot data in Series x, highlighting the elements correponding to True in h.
"""
# For passing to extra the highlighter lines, drop any passed kwargs which
# would clobber their appearance. Keep a copy of the full set for the data.
# The extra lines are plotted first so they appear underneath the actual
# data.
fullkwargs = kwargs.copy()
for k in ['lw', 'linewidth', 'linestyle', 'ls', 'color', 'c', 'label']:
kwargs.pop(k, None)
# Generate a probably unique ID so that the legend entries of these lines
# can be identified by the caller.
import random
gid = random.random()
# Plot the highlighting lines. First, plot a heavier black line to provide
# a clear outline to the bright highlighted region.
x.where(h).plot(lw=hlw+2, c='k', label='_dont_put_me_in_legend', **kwargs)
# Plot the bright highlighter line on top of that. Give this line an id.
x.where(h).plot(lw=hlw, c=hc, label=hll, gid=gid, **kwargs)
# Plot the actual data.
a = x.plot(**fullkwargs)
# Generate the custom legend entry artists.
import matplotlib.lines as mlines
l1 = mlines.Line2D([], [], lw=hlw+2, c='k')
l2 = mlines.Line2D([], [], lw=hlw, c=hc)
# Return the unique identifier of this call, and the tuple of line styles to
# go to HandlerTuple (see
# http://matplotlib.org/users/legend_guide.html#legend-handlers). This
# makes it possible to update all "highlighter" legend entries with the
# correct color-on-black visual style produced here.
return {gid: (l1, l2)}
I manage to generate an accurate legend entry for the highlighting by making use of HandlerTuple when calling plt.legend() by looping over all legend entries and replacing legend keys where gid is one of the values returned by highlight.
handles,labels = a.get_legend_handles_labels()
newhandles = []
for h in handles:
if h.get_gid() in hi:
newhandles.append(hi[h.get_gid()])
else:
newhandles.append(h)
a.legend(newhandles, labels)
Is there a way in matplotlib to define a "compound" line style such that one plot command produces the desired appearance and has only one legend entry?