Matplotlib fill between multiple lines
If you start the plot in point (0, 0), and therefore do not need to consider the area of the polygon not in the first quadrant, then this should do the trick in this particular situation:
import matplotlib.pyplot as plt
import numpy as np
x = np.arange(0,10,0.1)
# The lines to plot
y1 = 4 - 2*x
y2 = 3 - 0.5*x
y3 = 1 -x
# The upper edge of polygon (min of lines y1 & y2)
y4 = np.minimum(y1, y2)
# Set y-limit, making neg y-values not show in plot
plt.ylim(0, 5)
# Plotting of lines
plt.plot(x, y1,
x, y2,
x, y3)
# Filling between line y3 and line y4
plt.fill_between(x, y3, y4, color='grey', alpha='0.5')
plt.show()
Fill area between multiple lines in plot
Here is an approach:
a = data.frame(time = c(1:100), x = rnorm(100))
b = data.frame(time = c(1:100), y = rnorm(100))
c = data.frame(time = c(1:100), z = rnorm(100))
calculate the pmin
and pmax
:
min_a <- pmin(a, b, c)
max_a <- pmax(a, b, c)
construct the polygon as usual:
polygon(c(c$time, rev(c$time)), c(max_a$x ,rev(min_a$x)), col = rgb(1, 0, 0,0.5) )
or using ggplot:
library(tidyverse)
data.frame(a, b, c) %>% #combine the three data frames
group_by(time) %>% # group by time for next step
mutate(max = max(x, y, z), # calculate max of x, y, z in each time
min = min(x, y, z)) %>% #same as above
select(-time.1, - time.2) %>% #discard redundant columns
gather(key, value, 2:4) %>% #convert to long format so you can color by key in the geom line call
ggplot()+
geom_ribbon(aes(x = time, ymin= min, ymax = max), fill= "red", alpha = 0.3)+
geom_line(aes(x = time, y = value, color = key))
Filling In the Area Between Two Lines with a Custom Color Gradient
You could draw a colored rectangle covering the curves. And use the polygon created by fill_between
to clip that rectangle:
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap
import numpy as np
x = np.linspace(0, 10, 200)
y1 = np.random.normal(0.02, 1, 200).cumsum() + 20
y2 = np.random.normal(0.05, 1, 200).cumsum() + 50
cm1 = LinearSegmentedColormap.from_list('Temperature Map', ['blue', 'red'])
polygon = plt.fill_between(x, y1, y2, lw=0, color='none')
xlim = (x.min(), x.max())
ylim = plt.ylim()
verts = np.vstack([p.vertices for p in polygon.get_paths()])
gradient = plt.imshow(np.linspace(0, 1, 256).reshape(-1, 1), cmap=cm1, aspect='auto', origin='lower',
extent=[verts[:, 0].min(), verts[:, 0].max(), verts[:, 1].min(), verts[:, 1].max()])
gradient.set_clip_path(polygon.get_paths()[0], transform=plt.gca().transData)
plt.xlim(xlim)
plt.ylim(ylim)
plt.show()
A more complicated alternative, would color such that the upper curve corresponds to red and the lower curve to blue:
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 10, 200)
y1 = np.random.normal(0.02, 1, 200).cumsum() + 20
y2 = np.random.normal(0.05, 1, 200).cumsum() + 50
polygon = plt.fill_between(x, y1, y2, lw=0, color='none')
ylim = plt.ylim()
verts = np.vstack([p.vertices for p in polygon.get_paths()])
ymin, ymax = verts[:, 1].min(), verts[:, 1].max()
gradient = plt.imshow(np.array([np.interp(np.linspace(ymin, ymax, 200), [y1i, y2i], np.arange(2))
for y1i, y2i in zip(y1, y2)]).T,
cmap='turbo', aspect='auto', origin='lower', extent=[x.min(), x.max(), ymin, ymax])
gradient.set_clip_path(polygon.get_paths()[0], transform=plt.gca().transData)
plt.ylim(ylim)
plt.show()
A variant could be to smooth out the color values in the horizontal direction (but still clip using the original curves):
from scipy.ndimage import gaussian_filter
gradient = plt.imshow(np.array([np.interp(np.linspace(ymin, ymax, 200), [y1i, y2i], np.arange(2))
for y1i, y2i in zip(gaussian_filter(y1, 4, mode='nearest'),
gaussian_filter(y2, 4, mode='nearest'))]).T,
cmap='turbo', aspect='auto', origin='lower', extent=[x.min(), x.max(), ymin, ymax])
Fill area between two lines with Python
Here it is:
import matplotlib.pyplot as plt
import numpy as np
a = [[12, 17, 26, 32, 34], [235.910888671875, 245.84429931640625, 211.8711395263672, 226.2964630126953, 222.0032501220703]]
b = [[12, 26, 34], [235.910888671875, 211.8711395263672, 222.0032501220703]]
plt.plot(a[0], a[1], c='r')
plt.plot(b[0], b[1], c='r')
plt.fill_between(a[0], a[1],np.min(a[1]))
plt.fill_between(b[0], b[1],np.min(a[1]),color='white')
plt.show()
How to fill area between two sets of data connected by line?
You have to use the ydata
as arguments for your fill_between
, not the curves.
Either use ydata
directly, or get them from your curve1/2
objects like ydata=curve1.get_ydata()
.
Here is an example adapted from the docs:
import matplotlib.pyplot as plt
import numpy as np
x = np.arange(-5, 5, 0.01)
y1 = -5*x*x + x + 10
y2 = 5*x*x + x
c1, = plt.plot(x, y1, color='black')
c2, = plt.plot(x, y2, color='black')
# If you want/have to get the data form the plots
# x = c1.get_xdata()
# y1 = c1.get_ydata()
# y2 = c2.get_ydata()
plt.fill_between(x, y1, y2, where=y2 >y1, facecolor='yellow', alpha=0.5)
plt.fill_between(x, y1, y2, where=y2 <=y1, facecolor='red', alpha=0.5)
plt.title('Fill Between')
plt.show()
In the end you get:
R - Fill area between lines based on top value
The reason you're getting the wrong shading is probably because the data is a bit on the coarse side. My advice would be to interpolate the data first. Assuming dat1
is from your example.
library(ggplot2)
# From long data to wide data
dat2 <- tidyr::pivot_wider(dat1, values_from = value, names_from = var)
# Setup interpolated data (tibble because we can then reference column x)
dat3 <- tibble::tibble(
x = seq(min(dat2$month), max(dat2$month), length.out = 1000),
rainfall = with(dat2, approx(month, rainfall, xout = x)$y),
evaporation = with(dat2, approx(month, evaporation, xout = x)$y)
)
Then, we need to find a way to identify groups, and here is a helper function for that. Group IDs are based on the runs in run length encoding.
# Make function to identify groups
rle_id <- function(x) {
x <- rle(x)
rep.int(seq_along(x$lengths), x$lengths)
}
And now we can plot it.
ggplot(dat3, aes(x)) +
geom_ribbon(aes(ymin = pmin(evaporation, rainfall),
ymax = pmax(evaporation, rainfall),
group = rle_id(sign(rainfall - evaporation)),
fill = as.factor(sign(rainfall - evaporation))))
Created on 2021-02-14 by the reprex package (v1.0.0)
Fill area between two lines drawn with plot()
how about ggplot geom_ribbon?
library(ggplot2)
set.seed(1)
df <- data.frame(
x = seq(1,100),
ymin = rnorm(100,10,3),
ymax = rnorm(100,22,2)
)
ggplot(df,aes(x=x))+
geom_line(aes(x,ymin),color="red")+
geom_ribbon(aes(ymin=ymin,ymax=ymax),fill="lightblue")+
geom_line(aes(x=x,y=ymax),color="black")
Fill area between 2 lines (when one is below another)
If you look at the comprehension you are using to calculate where to fill, you'll notice it only checks at the points listed in your y
and z
lists. However, there are regions in between those points that need to be filled as well.
This behavior is mentioned in the documentation:
Semantically,
where
is often used for y1 > y2 or similar. By default, the nodes of the polygon defining the filled region will only be placed at the positions in the x array. Such a polygon cannot describe the above semantics close to the intersection. The x-sections containing the intersecion are simply clipped
You need interpolate=True
:
Setting interpolate to True will calculate the actual intersection point and extend the filled region up to this point
plt.fill_between(
x,y,z,
where=[(y[i]<z[i]) for i in range(len(x))],
facecolor='r',
interpolate=True
)
Since you also asked for a way to avoid having a list of 5
, you may use axhline
instead, as well as switching your lists to numpy
arrays for easy comparison:
import matplotlib.pyplot as plt
import numpy as np
x = np.array([1,2,3,4,5,6,7,8,9,10])
y = np.array([4,9,1,3,6,2,4,7,6,3])
z = 5
plt.plot(x,y)
plt.axhline(y=z, color='orange')
plt.fill_between(x,y,z,where=y<z, facecolor='r', interpolate=True)
plt.show()
Plotly - Fill area between three lines - How do I remove this dark shadows
- have simulated data to be able to re-produce
- there were two issues with you code
- two traces were defined as
mode="text"
have changed tomode="lines"
- only want
fill="tonexty"
on percentile traces
- two traces were defined as
import plotly.graph_objects as go
import pandas as pd
import numpy as np
df = pd.DataFrame({"date": pd.date_range("30-mar-2022", "15-may-2022")}).assign(
val=lambda d: np.random.randint(1, 10, len(d))
)
df = df.join(
df["val"]
.rolling(5)
.agg(["median", lambda w: np.percentile(w, 25), lambda w: np.percentile(w, 75)])
).dropna()
df.columns = ["date", "val", "Median", "25 Percentile", "75 Percentile"]
fig = go.Figure(layout_yaxis_range=[0, 10])
fig.add_traces(
go.Scatter(
x=df["date"],
y=df["25 Percentile"],
name="25th Percentile",
mode="lines",
line=dict(color="blue"),
# fill='tonext',
# fillcolor='grey',
connectgaps=False,
)
)
fig.add_traces(
go.Scatter(
x=df["date"],
y=df["Median"],
name="Median",
mode="lines",
line=dict(color="green"),
fill="tonexty",
fillcolor="#eaecee",
connectgaps=False,
)
)
fig.add_traces(
go.Scatter(
x=df["date"],
y=df["75 Percentile"],
name="75th Percentile",
mode="lines",
line=dict(color="blue"),
fill="tonexty",
fillcolor="#eaecee",
connectgaps=False,
)
)
fig.update_layout(template="plotly_white")
fig.show()
Graph with a shaded the area occupied by multiple lines
You just need to group per individual time unit and calculate the minimum / maximum values. This allows you to plot a geom_ribbon
:
example %>%
group_by(values) %>%
summarize(min = min(value), max = max(value)) %>%
ggplot() +
geom_ribbon(aes(x = values, ymin = min, ymax = max), size = 2,
fill = "#29c8e5", color = "black") +
theme_classic()
If you would rather have the ribbon overlying your original plot, you could do:
ribbon <- example %>%
group_by(values) %>%
summarize(min = min(value), max = max(value))
graph1 +
geom_ribbon(aes(x = values, ymin = min, ymax = max),
data = ribbon, size = 0, fill = "#29c8e5",
color = NA, alpha = 0.3, inherit.aes = FALSE)
For what it's worth, I think the first option is more visually striking.
Related Topics
Compute All Fixed Window Averages with Dplyr and Rcpproll
Customizing the Sankey Chart to Cater Large Datasets
Error: --With-Readline=Yes (Default) and Headers/Libs Are Not Available
R Plot Color Combinations That Are Colorblind Accessible
How to Add a Scale Bar (For Linear Distances) to Ggmap
Building a Box Plot from All Columns of Data Frame with Column Names on X in Ggplot2
Add Download Buttons in Dt::Renderdatatable
How to Get My Blogdown Blog on R-Bloggers
Merge Two Dataframes If Timestamp of X Is Within Time Interval of Y
Hashtag Extract Function in R Programming
Writings Functions (Procedures) for Data.Table Objects
How to Syntax Highlight Inline R Code in R Markdown