matplotlib - dashed line between points if one condition is met - python-3.x

I am using matplotlib to draw a plot. What I want to achieve is to connect points if one condition is met. For instance, if I have a dataframe like the following:
import os
import pandas as pd
import numpy as np
from matplotlib import pyplot as plt
df=pd.DataFrame({'dates': [2001, 2002, 2003, 2004, 2005, 2006], 'census_people': [306,327,352,478,250, 566], 'census_houses': [150,200,249,263, 180, 475]}) #I changed the dates from strings to ints
I could create plots like this use the following codes:
plt.plot('dates','census_houses',data=df[df['dates'] < 2004] ,marker='o',color='orange', linewidth=2)
plt.plot('dates','census_houses',data=df[df['dates'] > 2002] ,marker='o',color='orange', linewidth=2, linestyle = '--')
The plot is like the following:
However, what I truely want is, for instance, use the dashed line to connect points if the census_houses is bigger than 250. How to achieve this using matplotlib? Any suggestions and insights are welcomed! Thank you~

This effect can be achieved by applying clipping paths. In this example I suppose the full line completely draws over the dashed line, so only clipping of the full line is needed.
In the example, the special value for the y-axis is set to 220, different colors and very thick lines are used, to better see what is happening. The parameters for Rectangle((x, y), width, height) are setting y to the desired cut-off value, x is some position far left, width makes sure that x + width is far right and height is a large positive number to clip above the line, negative to clip below the line.
This post has more information about clipping paths.
import pandas as pd
from matplotlib import pyplot as plt
from matplotlib.patches import Rectangle
def do_clipping(patches, special_y, keep_below=True, ax=None):
ax = ax or plt.gca()
xmin, xmax = plt.xlim()
ymin, ymax = plt.ylim()
height = ymax - ymin
if keep_below:
height = -height
clip_rect = Rectangle((xmin, special_y), xmax - xmin, height,
transform=ax.transData)
for p in patches:
p.set_clip_path(clip_rect)
df = pd.DataFrame({'dates': [2001, 2002, 2003, 2004, 2005, 2006],
'census_houses': [150, 200, 249, 263, 180, 475]})
plt.plot('dates', 'census_houses', data=df, color='limegreen', linewidth=10, linestyle='--')
plot_patches = plt.plot('dates', 'census_houses', data=df, color='crimson', linewidth=10)
do_clipping(plot_patches, 220)
plt.show()

Related

problem on filing up the colour between two index values

I have a timeseries data timeseries.txt. First I select a index value (here 50) and put a red line mark on that selected index value. And I want to highlight portion before(idx-20) and after(idx+20) the red line index value on the timeseries.
I wrote this code however i am able to put the red line mark on the timeseries but while using fill_betweenx it doesnot work. I hope experts may help me overcoming this problem.Thanks.
import matplotlib.pyplot as plt
import numpy as np
input_data=np.loadtxt("timeseries.txt")
time=np.arange(len(input_data))
plt.plot(time,input_data)
idx = [50]
mark = [time[i] for i in idx]
plt.plot(idx,[input_data[i] for i in mark], marker="|",color='red',markerfacecolor='none',mew=0.4,ms=30,alpha=2.0)
plt.fill_betweenx(idx-20,idx+20 alpha=0.25,color='lightsteelblue')
plt.show()
If you are looking for just a semi-transparent rectangle, you can use patches.Rectangle to draw one. Refer here. I have updated your code to add a rectangle. See if this meets your requirement. I have used a sine wave as I didn't have your data.
import matplotlib.pyplot as plt
import numpy as np
## Create sine wave
x = np.arange(100)
input_data=np.sin(2*np.pi*3*x/100)
time=np.arange(len(input_data))
plt.plot(time,input_data)
idx = [50]
mark = [time[i] for i in idx]
plt.plot(idx,[input_data[i] for i in mark], marker="|", color='red', markerfacecolor='none', mew=0.4,ms=30,alpha=2.0)
#plt.fill_betweenx(mark,idx-20,0, alpha=0.25,color='lightsteelblue')
# Create a Rectangle patch
import matplotlib.patches as patches
from matplotlib.patches import Rectangle
plt.gca().add_patch(Rectangle((idx[0]-20, -0.15), 40, .3, facecolor = 'lightsteelblue',fill=True,alpha=0.25, lw=0))
plt.show()
EDIT
Please refer to the Rectangle documentation provided earlier in the response. You will need to adjust the start coordinates (x,y) and the height and width to see how big/small you need the Rectangle. For eg: changing the rectangle code like this...
plt.gca().add_patch(Rectangle((idx[0]-10, -0.40), 20, 0.8, facecolor = 'lightsteelblue',fill=True,alpha=0.25, lw=0))
will give you this plot.

Is it possible to set (in `matplotlib`) `ax.grid` in such a way that lines will go just to bars instead of going by the whole chart?

Is it possible to set ax.grid in such a way that lines will go just to bars?
Below the regular output("before") and expected("after"):
My code:
fig, ax = plt.subplots(figsize=(15,6))
ax.set_axisbelow(True)
ax = data_test.bar(fontsize=15, zorder=1, color=(174/255, 199/255, 232/255)) # 'zorder' is bar layaut order
for p in ax.patches:
ax.annotate(s=p.get_height(),
xy=(p.get_x()+p.get_width()/2., p.get_height()),
ha='center',
va='center',
xytext=(0, 10),
textcoords='offset points')
ax.spines["right"].set_visible(False)
ax.spines["left"].set_visible(False)
ax.spines["top"].set_visible(False)
ax.spines["bottom"].set_visible(False)
ax.set_xticklabels(
data_test.index,
rotation=34.56789,
fontsize='xx-large'
) # We will set xticklabels in angle to be easier to read)
# The labels are centred horizontally, so when we rotate them 34.56789°
ax.grid(axis='y', zorder=0) # 'zorder' is bar layaut order
plt.ylim([4500, 5300])
plt.show()
You could draw horizontal lines instead of using grid lines.
You forgot to add test data, making it quite unclear of what type data_test could be.
The code below supposes data_test is a pandas dataframe, and that data_test.plot.bar() is called to draw a bar plot. Note that since matplotlib 3.4 you can use ax.bar_label to label bars.
from matplotlib import pyplot as plt
import pandas as pd
import numpy as np
data_test = pd.DataFrame({'height': np.random.randint(1000, 2000, 7).cumsum()},
index=['Alkaid', 'Mizar', 'Alioth', 'Megrez', 'Phecda', 'Merak', 'Dubhe'])
fig, ax = plt.subplots(figsize=(15, 6))
ax.set_axisbelow(True)
data_test.plot.bar(fontsize=15, zorder=1, color=(174 / 255, 199 / 255, 232 / 255), ax=ax)
for container in ax.containers:
ax.bar_label(container, fmt='%.0f', fontsize=15)
for spine in ax.spines.values():
spine.set_visible(False)
ax.set_xticklabels(data_test.index, rotation=34.56789, fontsize='xx-large')
ax.tick_params(length=0) # remove tick marks
xmin, xmax = ax.get_xlim()
ticks = ax.get_yticks()
tick_extends = [xmax] * len(ticks)
# loop through the bars and the ticks; shorten the lines whenever a bar crosses it
for bar in ax.patches:
for j, tick in enumerate(ticks):
if tick <= bar.get_height():
tick_extends[j] = min(tick_extends[j], bar.get_x())
ax.hlines(ticks, xmin, tick_extends, color='grey', lw=0.8, ls=':', zorder=0)
plt.tight_layout()
plt.show()

Seaborn, how to gradient color distplot depending on the x-axis value

I'd like to gradient-color the plot line in the Seaborn's distplot, depending on the x-axis value. For example if the value is 1, then the colour is blue, when 1.1 then it's blue and goes toward green, and so on, and so on. For example like on the plot-draft below:
The problem is, that I don't how to set colour map manually in Seaborn or how to force x-dependend coloring of the plot's curve.
Note that distplot has been deprecated. In the current seaborn version, kdeplot draws a kde curve.
You can grab the generated line with ax.get_lines(). And then create a multicolored line similar to this tutorial example.
Here is some code to demonstrate the idea (currently it would also still work with distplot):
from matplotlib import pyplot as plt
from matplotlib.collections import LineCollection
import seaborn as sns
import numpy as np
np.random.seed(1234)
data = np.random.uniform(-1, 1.1, (5, 1000)).cumsum(axis=1).ravel()
ax = sns.kdeplot(x=data)
x, y = ax.get_lines()[0].get_data()
segments = np.array([x[:-1], y[:-1], x[1:], y[1:]]).T.reshape(-1, 2, 2)
norm = plt.Normalize(x.min(), x.max())
lc = LineCollection(segments, cmap='turbo_r', norm=norm)
lc.set_array(x[:-1])
lc.set_linewidth(2)
ax.get_lines()[0].remove()
line = ax.add_collection(lc)
ax.fill_between(x, y, color='purple', alpha=0.1, hatch='xx')
ax.margins(x=0)
ax.set_ylim(ymin=0)
plt.show()

How to retrieve a gridline axis tick positions?

I am trying to retrieve the yaxis and xaxis tick positions from a cartopy geoaxes.
As far as I understand, a common matplotlib's Axes has the internal method: 'axes.get_xticks' and 'axes.get_yticks'.
Nevertheless, a cartopy's gridline from a geoaxes does not. How could I retrieve them?
Also, when I try to retrieve the ticks from a geoaxes using the common format (i.e.: "axes.get_yticks"), I end up with strange coordinates.
Here is an example.
import pandas as pd
pd.set_option('display.width', 50000)
pd.set_option('display.max_rows', 50000)
pd.set_option('display.max_columns', 5000)
import numpy as np
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import cartopy.feature as cfeature
from matplotlib.offsetbox import AnchoredText
def main(projection = ccrs.Mercator(), drawlicense=True):
fig = plt.figure(figsize=(9,7))
ax = plt.axes(projection=projection)
# Put a background image on for nice sea rendering.
ax.stock_img()
# Create a feature for States/Admin 1 regions at 1:50m from Natural Earth
states_provinces = cfeature.NaturalEarthFeature(
category='cultural',
name='admin_1_states_provinces_lines',
scale='50m',
facecolor='none')
SOURCE = 'Natural Earth'
LICENSE = 'public domain'
ax.add_feature(cfeature.LAND)
ax.add_feature(cfeature.COASTLINE)
ax.add_feature(states_provinces, edgecolor='gray')
# Add a text annotation for the license information to the
# the bottom right corner.
if drawlicense:
text = AnchoredText(r'$\mathcircled{{c}}$ {}; license: {}'
''.format(SOURCE, LICENSE),
loc='right',
bbox_transform=ax.transAxes,
bbox_to_anchor=(1.01, -0.02),
prop={'size': 8},
frameon=False)
ax.add_artist(text)
plt.show()
return ax
ax = main()
Gridliner = ax.gridlines(draw_labels=True)
In this above case, if I try to retrieve the yticks from the geoaxes "ax", I end up with an array of strange values as:
In: ax.get_yticks()
Out:
array([-20000000., -15000000., -10000000., -5000000., 0.,
5000000., 10000000., 15000000., 20000000.])
Notice that the values are not in degrees, though the figure and also the selected cartopy's projection states degree coordinates.
Therefore, what am I doing wrong? How can I get the respective degree coordinates of the map?
Sincerely,
The cartopy axes does not actually show normal matplotlib ticks. Instead you can use ax.gridlines to obtain a set of linecollections that show the grid. The returned cartopy.mpl.gridliner.Gridliner can be used to query the positions of the lines.
Note that projections are not necessarily separable in x and y, hence the gridlines could potentially be curves.
In the following we take the first points of those lines.
# create grid
gridliner = ax.gridlines(draw_labels=True)
# we need to draw the figure, such that the gridlines are populated
fig.canvas.draw()
ysegs = gridliner.yline_artists[0].get_segments()
yticks = [yseg[0,1] for yseg in ysegs]
xsegs = gridliner.xline_artists[0].get_segments()
xticks = [xseg[0,0] for xseg in xsegs]
print(xticks)
print(yticks)
This prints two lists with the first gridline point's coordinates:
[-180.0, -120.0, -60.0, 0.0, 60.0, 120.0]
[-80.0, -60.0, -40.0, -20.0, 0.0, 20.0, 40.0, 60.0, 80.0, 100.0]

How to draw Scatter plot on top of background using Basemap Python

I am trying to plot a scatter plot on a background using basemap. But it's overwriting the background. How do I retain the background?
I am using this code
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
m = Basemap(projection='merc',llcrnrlat=-80,urcrnrlat=80,llcrnrlon=-180,urcrnrlon=180,lat_ts=20,resolution='c')
m.bluemarble()
x, y = m(list(longitude), list(latitude))
plt.scatter(x,y,1,marker='o',color='Red')
plt.show()
But as soon as I run the scatter plot, its overwriting background image. How can I overlay the scatter plot on the image.
This is how to plot a series of points on top of a raster map. Note that the bluemarble image is huge, so a full scale (1.0 or default) plot of it should be avoided. The code is based on yours.
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
# make up some data for scatter plot
lats = np.random.randint(-75, 75, size=20)
lons = np.random.randint(-179, 179, size=20)
fig = plt.gcf()
fig.set_size_inches(8, 6.5)
m = Basemap(projection='merc', \
llcrnrlat=-80, urcrnrlat=80, \
llcrnrlon=-180, urcrnrlon=180, \
lat_ts=20, \
resolution='c')
m.bluemarble(scale=0.2) # full scale will be overkill
m.drawcoastlines(color='white', linewidth=0.2) # add coastlines
x, y = m(lons, lats) # transform coordinates
plt.scatter(x, y, 10, marker='o', color='Red')
plt.show()
The resulting plot:
I realize it's an old question but in case anyone comes here with the same problem as I did.
The trick is to give a higher zorder for the scatter plot than for the .bluemarble().
m.scatter(x, y, 10, marker='o', color='Red', zorder=3)
More info here: https://matplotlib.org/3.1.0/gallery/misc/zorder_demo.html
I'm not entirely sure what you mean by "overwriting the background". When you use plt.scatter(), it will plot the points over the map, so it will display the points over the background.
Just based off the code provided, I think you're issue here is m(list(longitude), list(latitude)).
If you have multiple points in a list, you want to loop over them.
lats = [32, 38, 35]
lons = [-98, -79, -94]
x, y = m(lons, lats)
for i in range(len(lats)):
plt.scatter(x, y, marker = 'o')
If it's only one single point,
lat, lon = 32, -92
x, y = m(lon, lat)
plt.scatter(x, y, marker = 'o')
The styling of the points can be found in the matplotlib documentation.

Resources