Plotly: Markers disappear when (n) points are plotted - python

Okay, so my initial idea is to make a line plot in plotly and color the line with one color after certain threshold t, and another color before the threshold. It works for a 23 or less points, but it works with no more, using this method:
import numpy as np
import plotly.graph_objects as go
X = [j for j in range(0, 100)]
Y = [j for j in range(100000, 200000, 1000)]
X = X[:23]
Y = Y[:23]
X = np.array(X)
Y = np.array(Y)
t = 4
x = X[X <= t] # Include the threshold
y = Y[X <= t]
bx = X[X >= t]
by = Y[X >= t]
fig = go.Figure()
fig.add_trace(go.Scatter(x=x, y=y, line=dict(width=4, color='grey'), name="useless data"))
fig.add_trace(go.Scatter(x=bx, y=by, line=dict(width=4, color='blue'), name="useful data"))
fig.update_layout(xaxis_title="x axis", yaxis_title="y axis")
fig.show()
So this works normally, and if you run it, you will see that 4 is included in the blue points. But now, please remove the lines where only 23 values are taken (X = X[:23], Y = Y[:23]). You will see that 4 is no longer part of the blue points, moreover, the points themselves disappear from the graph in the blue line, you can hover and see data, but you can't see the actual points! If anyone knows why this happens, is it an actual bug or it is normal behaviour and there is something I am missing? Thank you in advance!

Bug? Not necessarily. Weird behaviour? Perhaps...
In any case, the solution in your case is:
fig.data[1].mode = 'lines+markers'
What you seem to be struggling with here is caused by two things:
When hovering over a point where there is one trace represented by a line, and one trace represented by a marker, plotly will display the information for the marker even though the line is placed on top.
For an increasing length of a go.Scatter() trace, plotly will stop showing markers for after a certain threshold.
And this is perhaps the arguably weird part; that the exact threshold does not seem to be determined by the length of the trace alone. We'll take a look at that in the end.
Details:
1. Hover behavior
Just run your code as it is, and hover over 4:
Now deselect useless data by clicking the name in the legend and you'll get:
If you zoom in a bit, you'll see that the data is actually there, it just won't show on hover when both traces are activated:
So, what to do about it?
Just include:
fig.data[1].mode = 'lines+markers'
And get:
2. Marker trigger threshold for go.Scatter
In your case, this threshold seems to be a trace with length = 23 since you're seeing the exact behaviour you're describing. So, what's weird about this? The next part:
3. Varying marker trigger threshold
First of all, *why is there a threshold? Probably because a trace with too many markers arguably looks weird:
You found the threshold to be 24. But in a figure built only with go.Figure(go.Scatter(x = x, y = y)), the threshold is 20:
pts = 20
x = np.arange(1,pts)
y = np.arange(1,pts)
fig = go.Figure(go.Scatter(x = x, y = y)).show()
pts = 21
x = np.arange(1,pts)
y = np.arange(1,pts)
fig = go.Figure(go.Scatter(x = x, y = y)).show()
And I'm not sure why. But I think that would make for a good question on its own.

The reason:
This is a 'feature' of Plotly Scatter plots. When (a number) of points are plotted, the underlying Plotly logic converts the 'scatter' plot to a 'line' plot, for rendering efficiency and plot cleanliness. Thus, the markers are converted to a solid line.
The fix:
Simply add mode='lines+markers' to the trace.
Full working example:
This is your source code, with the minor fix mentioned above:
import numpy as np
import plotly.graph_objects as go
X = [j for j in range(0, 100)]
Y = [j for j in range(100000, 200000, 1000)]
#X = X[:23]
#Y = Y[:23]
X = np.array(X)
Y = np.array(Y)
t = 4
x = X[X <= t] # Include the threshold
y = Y[X <= t]
bx = X[X >= t]
by = Y[X >= t]
fig = go.Figure()
fig.add_trace(go.Scatter(x=x, y=y, mode='lines+markers', line=dict(width=1, color='grey'), name="useless data"))
fig.add_trace(go.Scatter(x=bx, y=by, mode='lines+markers', line=dict(width=1, color='blue'), name="useful data"))
fig.update_layout(xaxis_title="x axis", yaxis_title="y axis")
fig.show()
Output:

Related

Making parts of a line graph a different colour depending on their y value in Matplotlib

I'm making a program which takes a random list of data and will plot it.
I want the colour of the graph to change if it goes above a certain value.
https://matplotlib.org/gallery/lines_bars_and_markers/multicolored_line.html
Matplotlib has an entry on doing just this but it seems to require using a function as input for the graph not using lists.
Does anyone know how to either convert this to work for lists or another way of doing so?
Here's my code so far (without my horrific failed attempts to colour code them)
from matplotlib import pyplot as plt
import random
import sys
import numpy as np
#setting the max and min values where I want the colour to change
A_min = 2
B_max = 28
#makes lists for later
A_min_lin = []
B_max_lin = []
#simulating a corruption of the data where it returns all zeros
sim_crpt = random.randint(0,10)
print(sim_crpt)
randomy = []
if sim_crpt == 0:
randomy = []
#making the empty lists for corrupted data
for i in range(0,20):
randomy.append(0)
print(randomy)
else:
#making a random set of values for the y axis
for i in range(0,20):
n = random.randint(0,30)
randomy.append(n)
print(randomy)
#making an x axis for time
time = t = np.arange(0, 20, 1)
#Making a list to plot a straight line showing where the maximum and minimum values
for i in range(0, len(time)):
A_min_lin.append(A_min)
B_max_lin.append(B_max)
#Testing to see if more than 5 y values are zero to return if it's corrupted
tracker = 0
for i in (randomy):
if i == 0:
tracker += 1
if tracker > 5:
sys.exit("Error, no data")
#ploting and showing the different graphs
plt.plot(time,randomy)
plt.plot(time,A_min_lin)
plt.plot(time,B_max_lin)
plt.legend(['Data', 'Minimum for linear', "Maximum for linear"])
plt.show
You can use np.interp to generate the fine-grain data to plot:
# fine grain time
new_time = np.linspace(time.min(), time.max(), 1000)
# interpolate the y values
new_randomy = np.interp(new_time, time, randomy)
# this is copied from the link with few modification
points = np.array([new_time, new_randomy]).T.reshape(-1, 1, 2)
segments = np.concatenate([points[:-1], points[1:]], axis=1)
fig, axs = plt.subplots()
norm = plt.Normalize(new_randomy.min(), new_randomy.max())
lc = LineCollection(segments, cmap='viridis', norm=norm)
# Set the values used for colormapping
lc.set_array(new_randomy[1:])
lc.set_linewidth(2)
line = axs.add_collection(lc)
fig.colorbar(line, ax=axs)
# set the limits
axs.set_xlim(new_time.min(), new_time.max())
axs.set_ylim(new_randomy.min(), new_randomy.max())
plt.show()
Output:

Plotly: How to add annotations to different intervals of the y-axis?

I'm trying to add annoation to y axis based on different inverval of y value
if y > 0, I want to give the annotation of Flexion
if y < 0, I want to give the annotation of Extension
I tried to use multicategory to specify the annotation
my code is show below
import plotly.graph_objects as go
import numpy as np
x = np.arange(-10,10,1)
y = np.arange(-10,10,1)
y_annotation = [ 'Flexion' if data > 0 else 'Extension' for data in y ]
fig = go.Figure( data= go.Scatter(x=x,y=[y_annotation,y]) )
fig.show()
This will produce
but I don't want the lines to seperate the Flexision and Extension
and this method will give detailed y values on the y axis, which is also I don't want to have
I'm wondering if there's another way to add annotation to y axis based on different interval?
Thanks !
If you're happy with the setup above besides the lines and detailed y-axis, then you can drop the multi index approach and just set up annotations at the appropriate positions using fig.add_annotation()
The following figure is produced with the snippet below that:
makes room for your annotations on the left side using fig.update_layout(margin=dict(l=150)),
stores interval names and data in a dict, and
calculates the middle values of each specified interval, and
places the annotations to the left of the y-axis using xref="paper", and
does not mess up the values of the y-axis tickmarks.
Plot
Complete code:
import plotly.graph_objects as go
import numpy as np
x = np.arange(-10,10,1)
y = np.arange(-10,10,1)
y_annotation = [ 'Flexion' if data > 0 else 'Extension' for data in y ]
intervals = {'Flexion':[0,10],
'Extension':[0, -10]}
# plotly setup
fig = go.Figure( data= go.Scatter(x=x,y=y) )
# make room for annotations
fig.update_layout(margin=dict(l=150))
for k in intervals.keys():
fig.add_annotation(dict(font=dict(color="green",size=14),
#x=x_loc,
x=-0.16,
y=(intervals[k][0]+intervals[k][1])/2,
showarrow=False,
text="<i>"+k+"</i>",
textangle=0,
xref="paper",
yref="y"
))
fig.show()

How to set fixed spaces between ticks in maptlotlib

I am preparing a graph of latency percentile results. This is my pd.DataFrame looks like:
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
%matplotlib inline
result = pd.DataFrame(np.random.randint(133000, size=(5,3)), columns=list('ABC'), index=[99.0, 99.9, 99.99, 99.999, 99.9999])
I am using this function (commented lines are different pyplot methods I have already tried to achieve my goal):
def plot_latency_time_bar(result):
ind = np.arange(4)
means = []
stds = []
for index, row in result.iterrows():
means.append(np.mean([row[0]//1000, row[1]//1000, row[2]//1000]))
stds.append(np .std([row[0]//1000, row[1]//1000, row[2]//1000]))
plt.bar(result.index.values, means, 0.2, yerr=stds, align='center')
plt.xlabel('Percentile')
plt.ylabel('Latency')
plt.xticks(result.index.values)
# plt.xticks(ind, ('99.0', '99.9', '99.99', '99.999', '99.99999'))
# plt.autoscale(enable=False, axis='x', tight=False)
# plt.axis('auto')
# plt.margins(0.8, 0)
# plt.semilogx(basex=5)
plt.legend(bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0.)
fig = plt.gcf()
fig.set_size_inches(15.5, 10.5)
And here is the figure:
As you can see bars for all percentiles above 99.0 overlaps and are completely unreadable. I would like to set some fixed space between ticks to have a same space between all of them.
Since you're using pandas, you can do all this from within that library:
means = df.mean(axis=1)/1000
stds = df.std(axis=1)/1000
means.plot.bar(yerr=stds, fc='b')
# Make some room for the x-axis tick labels
plt.subplots_adjust(bottom=0.2)
plt.show()
Not wishing to take anything away from xnx's answer (which is the most elegant way to do things given that you're working in pandas, and therefore likely the best answer for you) but the key insight you're missing is that, in matplotlib, the x positions of the data you're plotting and the x tick labels are independent things. If you say:
nominalX = np.arange( 1, 6 ) ** 2
y = np.arange( 1, 6 ) ** 4
positionalX = np.arange(len(y))
plt.bar( positionalX, y ) # graph y against the numbers 1..n
plt.gca().set(xticks=positionalX + 0.4, xticklabels=nominalX) # ...but superficially label the X values as something else
then that's different from tying positions to your nominal X values:
plt.bar( nominalX, y )
Note that I added 0.4 to the x position of the ticks, because that's half the default width of the bars bar( ..., width=0.8 )—so the ticks end up in the middle of the bar.

Trying to set y axis labels and ticks aligned to 2D faces

This is my plot:
If I were to draw your attention to the axis labelled 'B' you'll see that everything is not as it should be.
The plots was produced using this:
def newPoly3D(self):
from matplotlib.cm import autumn
# This passes a pandas dataframe of shape (data on rows x 4 columns)
df = self.loadData()
fig = plt.figure(figsize=(10,10))
ax = fig.gca(projection='3d')
vels = [1.42,1.11,0.81,0.50]
which_joints = df.columns
L = len(which_joints)
dmin,dmax = df.min().min(),df.max().max()
dix = df.index.values
offset=-5
for i,j in enumerate(which_joints):
ax.add_collection3d(plt.fill_between(dix,df[j],
dmin,
lw=1.5,
alpha=0.3/float(i+1.),
facecolor=autumn(i/float(L))),
zs=vels[i],
zdir='y')
ax.grid(False)
ax.set_xlabel('A')
ax.set_xlim([0,df.index[-1]])
ax.set_xticks([])
ax.xaxis.set_ticklabels([])
ax.set_axis_off
ax.set_ylabel('B')
ax.set_ylim([0.4, max(vels)+0.075])
ax.set_yticks(vels)
ax.tick_params(direction='out', pad=10)
ax.set_zlabel('C')
ax.set_zlim([dmin,dmax])
ax.xaxis.labelpad = -10
ax.yaxis.labelpad = 15
ax.zaxis.labelpad = 15
# Note the inversion of the axis
plt.gca().invert_yaxis()
First I want to align the ticks on the yaxis (labelled B) with each coloured face. As you can see they are now offset slightly down.
Second I want to align the yaxis tick labels with the above, as you cans see they are currently very much offset downwards. I do not know why.
EDIT:
Here is some example data; each column represents one coloured face on the above plot.
-13.216256 -7.851065 -9.965357 -25.502654
-13.216253 -7.851063 -9.965355 -25.502653
-13.216247 -7.851060 -9.965350 -25.502651
-13.216236 -7.851052 -9.965342 -25.502647
-13.216214 -7.851038 -9.965324 -25.502639
-13.216169 -7.851008 -9.965289 -25.502623
-13.216079 -7.850949 -9.965219 -25.502592
-13.215900 -7.850830 -9.965078 -25.502529
Here we are again, with a simpler plot, reproduced with this data:
k = 10
df = pd.DataFrame(np.array([range(k),
[x + 1 for x in range(k)],
[x + 4 for x in range(k)],
[x + 9 for x in range(k)]]).T,columns=list('abcd'))
If you want to try this with the above function, comment out the df line in the function and change its argument as so def newPoly3D(df): so that you can pass the the test df above.

Creating a hexagonal grid (u-matrix) in Python using a Regularpolycollection

I am trying to create a hexagonal grid to use with a u-matrix in Python (3.4) using a RegularPolyCollection (see code below) and have run into two problems:
The hexagonal grid is not tight. When I plot it there are empty spaces between the hexagons. I can fix this by resizing the window, but since this is not reproducible and I want all of my plots to have the same size, this is not satisfactory. But even if it were, I run into the second problem.
Either the top or right hexagons don't fit in the figure and are cropped.
I have tried a lot of things (changing figure size, subplot_adjust(), different areas, different values of d, etc.) and I am starting to get crazy! It feels like the solution should be simple, but I simply cannot find it!
import SOM
import matplotlib.pyplot as plt
from matplotlib.collections import RegularPolyCollection
import numpy as np
import matplotlib.cm as cm
from mpl_toolkits.axes_grid1 import make_axes_locatable
m = 3 # The height
n = 3 # The width
# Some maths regarding hexagon geometry
d = 10
s = d/(2*np.cos(np.pi/3))
h = s*(1+2*np.sin(np.pi/3))
r = d/2
area = 3*np.sqrt(3)*s**2/2
# The center coordinates of the hexagons are calculated.
x1 = np.array([d*x for x in range(2*n-1)])
x2 = x1 + r
x3 = x2 + r
y = np.array([h*x for x in range(2*m-1)])
c = []
for i in range(2*m-1):
if i%4 == 0:
c += [[x,y[i]] for x in x1]
if (i-1)%2 == 0:
c += [[x,y[i]] for x in x2]
if (i-2)%4 == 0:
c += [[x,y[i]] for x in x3]
c = np.array(c)
# The color of the hexagons
d_matrix = np.zeros(3*3)
# Creating the figure
fig = plt.figure(figsize=(5, 5), dpi=100)
ax = fig.add_subplot(111)
# The collection
coll = RegularPolyCollection(
numsides=6, # a hexagon
rotation=0,
sizes=(area,),
edgecolors = (0, 0, 0, 1),
array= d_matrix,
cmap = cm.gray_r,
offsets = c,
transOffset = ax.transData,
)
ax.add_collection(coll, autolim=True)
ax.axis('off')
ax.autoscale_view()
plt.show()
See this topic
Also you need to add scale on axis like
ax.axis([xmin, xmax, ymin, ymax])
The hexalattice module of python (pip install hexalattice) gives solution to both you concerns:
Grid tightness: You have full control over the hexagon border gap via the 'plotting_gap' argument.
The grid plotting takes into account the grid final size, and adds sufficient margins to avoid the crop.
Here is a code example that demonstrates the control of the gap, and correctly fits the grid into the plotting window:
from hexalattice.hexalattice import *
create_hex_grid(nx=5, ny=5, do_plot=True) # Create 5x5 grid with no gaps
create_hex_grid(nx=5, ny=5, do_plot=True, plotting_gap=0.2)
See this answer for additional usage examples, more images and links
Disclosure: the hexalattice module was written by me

Categories