problem on generating more data points and plotting
Question:
I am able to plot the stair-step like plot as attached using the below code
import numpy as np
from scipy.interpolate import interp1d
import matplotlib.pyplot as plt
x = np.array([3.45, 3.88, 3.99, 4.33])
y = np.array([14.0, 11.0, 14.0, 31.0])
y = np.cumsum(y)
y0 = np.array([0])
x0 = np.interp([0], y, x)
x = np.concatenate([x0, x])
y = np.concatenate([y0, y])
# Plot
fig, ax = plt.subplots()
ax.step(x, y, color='r', where='post')
ax.set_xlim(2, 5)
ax.set_ylim(0, y.max())
ax.invert_yaxis()
plt.show()

#####################################################
Now I need to generate more data points of x and y using interpolation function and want to generate same plot as above code produced(depicted above). I tried the below mentioned code but it produces different plot(depicted below), But definition says after interpolation, we should have the same plot, we just have more data points. 
import numpy as np
from scipy.interpolate import interp1d
import matplotlib.pyplot as plt
x = np.array([3.45, 3.88, 3.99, 4.33])
y = np.array([14.0, 11.0, 14.0, 31.0])
y = np.cumsum(y)
y0 = np.array([0])
x0 = np.interp([0], y, x)
x = np.concatenate([x0, x])
y = np.concatenate([y0, y])
# I need to generate more data points
NUM = 100 # modify the number of points
funct = interp1d(x, y, kind='next')
x_cont = np.linspace(x[0], x[-1], NUM)
y_cont = funct(x_cont)
# Plot
fig, ax1 = plt.subplots()
ax1.step(x_cont, y_cont, color='r', where='post')
ax1.set_xlim(2, 5)
ax1.set_ylim(0, y.max())
ax1.invert_yaxis()
plt.show()
Answers:
This line
funct = interp1d(x, y, kind='next')
needs to be changed in
funct = interp1d(x, y, kind='previous')
This because you want values in [3.99, 4.33] (e.g.) to refer to the cumsum-value of 3.99, which is ~40 (previous) instead of values related to 4.33 which is ~70 (next).
Update
IIUC, now you want the points where line changes direction. So you can use np.repeat
# Original data
x = np.array([3.45, 3.88, 3.99, 4.33])
y = np.array([14.0, 11.0, 14.0, 31.0])
y = np.cumsum(y)
# Find breakpoints
x_cont = np.hstack([x[0], np.repeat(x, 2)[1:]])
y_cont = np.hstack([0, np.repeat(y, 2)[:-1]])
# Plot
fig, ax = plt.subplots()
ax.scatter(x_cont, y_cont, marker='.', color='blue')
ax.plot(x_cont, y_cont, color='red')
ax.set_xlim(2, 5)
ax.set_ylim(0, y.max())
ax.invert_yaxis()
plt.show()
Output:
>>> x_cont
array([3.45, 3.45, 3.88, 3.88, 3.99, 3.99, 4.33, 4.33])
>>> y_cont
array([ 0., 14., 14., 25., 25., 39., 39., 70.])
Old answer
You want to increase the number of points for each step. You can use a custom function to interpolate values from (x1, y1) to (x2, y1) then from (x2, y1) to (x2, y2). It means you compute x points first then y. The function take 3 couple of parameters and the step size:
x
, y
: your intermediate step points (your two arrays x and y)
x0
, y0
: start coords (here you have to specify y=0 because it does not appear in your data)
xn
, yn
: end coords (here you can reuse the last value of x and y arrays)
step
: the step size (we approximate the value to have points evenly spaced)
def interpolate(x, y, x0, y0, xn, yn, step):
# create arrays with all points
x = np.hstack([x0, x, xn])
y = np.hstack([y0, y, yn])
x_cont = []
y_cont = []
# get points pairwise
for x1, x2, y1, y2 in zip(x, x[1:], y, y[1:]):
# approximate the number of samples per segment
numx = int(np.abs(x2 - x1) / step)
numy = int(np.abs(y2 - y1) / step)
# x-first (left or right move)
a = np.linspace(x1, x2, numx, endpoint=False)
b = np.repeat(y1, numx)
# y-second (top or down move)
c = np.repeat(x2, numy)
d = np.linspace(y1, y2, numy, endpoint=False)
# save points
x_cont.append([*a, *c])
y_cont.append([*b, *d])
# flat all computed values
return np.hstack([*x_cont, xn]), np.hstack([*y_cont, yn])
# Original data
x = np.array([3.45, 3.88, 3.99, 4.33])
y = np.array([14.0, 11.0, 14.0, 31.0])
y = np.cumsum(y) # It seems you want the cumulative sum of y
x_cont, y_cont = interpolate(x, y, x[0], 0, x[-1], y[-1], step=0.1)
Plot the values:
fig, ax = plt.subplots()
ax.step(x_cont, y_cont, color='red', where='post')
ax.set_xlim(2, 5)
ax.set_ylim(0, y.max())
ax.invert_yaxis()
plt.show()
Output:
It seems like you are looking for a function that takes as input a number of pivots p
and the number of points n
to put in the horizontal and vertical straight lines connecting those pivots. I assume that you want the extra points to be evenly (linearly) spaced.
Essentially, you need to:
- identify the pivot points
- ensure the
len(p) <= n
- use repeated values for
x
and linearly spaced values for y
when the lines are vertical
- use repeated values for
y
and linearly spaced values for x
when the lines are horizontal
A simple implementation using NumPy (and specifically np.linspace()
and np.repeat()
) could be:
import typing as typ
import numpy as np
def upscale_steps_equi(
p_x: typ.Sequence[float],
p_y: typ.Sequence[float],
n: int,
start_hor: typ.Optional[str] = None,
dtype: np.dtype = np.float_,
):
"""
The points are equi-spaced (except near the pivots).
The pivots are, in general, not included.
Args:
p_x: Must be strictly monotonic increasing.
p_y: Must be strictly monotonic increasing.
n: Must be larger than max(len(p_x), len(p_y))
start_hor: Must be True for horizontal, False for vertical.
Only used (and needed) if len(p_x) == len(p_y)
"""
p_x_n = len(p_x)
p_y_n = len(p_y)
is_same_len = p_x_n == p_y_n
p_n = max(p_x_n, p_y_n) + 1 # number of points
if p_x_n > p_y_n:
start_hor = True
elif p_x_n < p_y_n:
start_hor = False
elif start_hor is None or not isinstance(start_hor, bool):
raise ValueError("`start_hor` required if len(p_x) == len(p_y)")
p_x_ = np.repeat(p_x, 2) # x-coords of pivots
p_y_ = np.repeat(p_y, 2) # y-coords of pivots
if is_same_len:
if start_hor:
p_x_ = p_x_[1:]
p_y_ = p_y_[:-1]
else:
p_x_ = p_x_[:-1]
p_y_ = p_y_[1:]
else:
if start_hor:
p_x_ = p_x_[1:-1]
else:
p_y_ = p_y_[1:-1]
dx = max(p_x) - min(p_x) # distance covered along x
dy = max(p_y) - min(p_y) # distance covered along y
d = dx + dy
ll = np.linspace(0, d, n, endpoint=True)
x = min(p_x) + ll
y = min(p_y) + ll
last_x_ = last_y_ = last_d_ = None
d_ = d_x_ = d_y_ = 0
for x_, y_ in zip(p_x_, p_y_):
if x_ == last_x_:
dd_y_ = y_ - last_y_
d_y_ += dd_y_
d_ += dd_y_
mask = (ll >= last_d_) & (ll < d_)
x[mask] = x_
y[mask] -= d_x_
elif y_ == last_y_:
dd_x_ = x_ - last_x_
d_x_ += dd_x_
d_ += dd_x_
mask = (ll >= last_d_) & (ll < d_)
y[mask] = y_
x[mask] -= d_y_
last_x_ = x_
last_y_ = y_
last_d_ = d_
x[-1] = p_x[-1]
y[-1] = p_y[-1]
return x, y
to be used like:
import matplotlib.pyplot as plt
x, y = upscale_steps_equi([1, 4, 5], [2, 3, 7, 9], 20)
plt.scatter(x, y, marker='o')
plt.axis('scaled')
The function above does not, in general, include the pivots.
However, if the pivots must be included, one needs to forego the exact number of points and the same spacing for x and y.
Here is a possible solution where the points are equi-spaced within each segment:
import typing as typ
import numpy as np
def upscale_steps_with_pivots(
p_x: typ.Sequence[float],
p_y: typ.Sequence[float],
n: int,
start_hor: typ.Optional[bool] = None,
dtype: np.dtype = np.float_,
):
"""
The points are equi-spaced only within each segment.
The pivots are strictly included.
Args:
p_x: Must be strictly monotonic increasing.
p_y: Must be strictly monotonic increasing.
n: Must be larger than max(len(p_x), len(p_y))
start_hor: Must be `h` for horizontal, `v` for vertical.
Only used (and needed) if len(p_x) == len(p_y)
"""
p_x_n = len(p_x)
p_y_n = len(p_y)
is_same_len = p_x_n == p_y_n
p_n = max(p_x_n, p_y_n) + 1 # number of points
if p_x_n > p_y_n:
start_hor = True
elif p_x_n < p_y_n:
start_hor = False
elif start_hor is None or not isinstance(start_hor, bool):
raise ValueError("`start_hor` required if len(p_x) == len(p_y)")
p_x_ = np.repeat(p_x, 2) # x-coords of pivots
p_y_ = np.repeat(p_y, 2) # y-coords of pivots
if is_same_len:
if start_hor:
p_x_ = p_x_[1:]
p_y_ = p_y_[:-1]
else:
p_x_ = p_x_[:-1]
p_y_ = p_y_[1:]
else:
if start_hor:
p_x_ = p_x_[1:-1]
else:
p_y_ = p_y_[1:-1]
dx = max(p_x) - min(p_x) # distance covered along x
dy = max(p_y) - min(p_y) # distance covered along y
d = dx + dy
n_x = (np.diff(p_x) / d * (n - p_n)).astype(np.int_)
n_y = (np.diff(p_y) / d * (n - p_n)).astype(np.int_)
r = np.ones(len(n_x) + len(n_y) + 1, dtype=np.int_)
if start_hor:
r[:-1 if is_same_len else None:2] = n_x + 1
r[1:-1:2] = n_y + 1
else:
r[:-1 if is_same_len else None:2] = n_y + 1
r[1:-1:2] = n_x + 1
x = np.repeat(p_x_, r).astype(dtype)
y = np.repeat(p_y_, r).astype(dtype)
j = 0
for i, r_ in enumerate(r[:-1]):
if i % 2 != start_hor:
k = np.linspace(p_x_[i], p_x_[i + 1], r_, endpoint=False)
x[j : j + len(k)] = k
else:
k = np.linspace(p_y_[i], p_y_[i + 1], r_, endpoint=False)
y[j : j + len(k)] = k
j += r_
return x, y
to be used like:
import matplotlib.pyplot as plt
x, y = upscale_steps_with_pivots([1, 4, 5], [2, 3, 7, 9], 20)
plt.scatter(x, y, marker='o')
plt.axis('scaled')
(Note that until the computation of d
, the two functions are identical, and it mostly contain the logic to deal with the different possible configurations of segments.)
I am able to plot the stair-step like plot as attached using the below code
import numpy as np
from scipy.interpolate import interp1d
import matplotlib.pyplot as plt
x = np.array([3.45, 3.88, 3.99, 4.33])
y = np.array([14.0, 11.0, 14.0, 31.0])
y = np.cumsum(y)
y0 = np.array([0])
x0 = np.interp([0], y, x)
x = np.concatenate([x0, x])
y = np.concatenate([y0, y])
# Plot
fig, ax = plt.subplots()
ax.step(x, y, color='r', where='post')
ax.set_xlim(2, 5)
ax.set_ylim(0, y.max())
ax.invert_yaxis()
plt.show()
#####################################################
Now I need to generate more data points of x and y using interpolation function and want to generate same plot as above code produced(depicted above). I tried the below mentioned code but it produces different plot(depicted below), But definition says after interpolation, we should have the same plot, we just have more data points.
import numpy as np
from scipy.interpolate import interp1d
import matplotlib.pyplot as plt
x = np.array([3.45, 3.88, 3.99, 4.33])
y = np.array([14.0, 11.0, 14.0, 31.0])
y = np.cumsum(y)
y0 = np.array([0])
x0 = np.interp([0], y, x)
x = np.concatenate([x0, x])
y = np.concatenate([y0, y])
# I need to generate more data points
NUM = 100 # modify the number of points
funct = interp1d(x, y, kind='next')
x_cont = np.linspace(x[0], x[-1], NUM)
y_cont = funct(x_cont)
# Plot
fig, ax1 = plt.subplots()
ax1.step(x_cont, y_cont, color='r', where='post')
ax1.set_xlim(2, 5)
ax1.set_ylim(0, y.max())
ax1.invert_yaxis()
plt.show()
This line
funct = interp1d(x, y, kind='next')
needs to be changed in
funct = interp1d(x, y, kind='previous')
This because you want values in [3.99, 4.33] (e.g.) to refer to the cumsum-value of 3.99, which is ~40 (previous) instead of values related to 4.33 which is ~70 (next).
Update
IIUC, now you want the points where line changes direction. So you can use np.repeat
# Original data
x = np.array([3.45, 3.88, 3.99, 4.33])
y = np.array([14.0, 11.0, 14.0, 31.0])
y = np.cumsum(y)
# Find breakpoints
x_cont = np.hstack([x[0], np.repeat(x, 2)[1:]])
y_cont = np.hstack([0, np.repeat(y, 2)[:-1]])
# Plot
fig, ax = plt.subplots()
ax.scatter(x_cont, y_cont, marker='.', color='blue')
ax.plot(x_cont, y_cont, color='red')
ax.set_xlim(2, 5)
ax.set_ylim(0, y.max())
ax.invert_yaxis()
plt.show()
Output:
>>> x_cont
array([3.45, 3.45, 3.88, 3.88, 3.99, 3.99, 4.33, 4.33])
>>> y_cont
array([ 0., 14., 14., 25., 25., 39., 39., 70.])
Old answer
You want to increase the number of points for each step. You can use a custom function to interpolate values from (x1, y1) to (x2, y1) then from (x2, y1) to (x2, y2). It means you compute x points first then y. The function take 3 couple of parameters and the step size:
x
,y
: your intermediate step points (your two arrays x and y)x0
,y0
: start coords (here you have to specify y=0 because it does not appear in your data)xn
,yn
: end coords (here you can reuse the last value of x and y arrays)step
: the step size (we approximate the value to have points evenly spaced)
def interpolate(x, y, x0, y0, xn, yn, step):
# create arrays with all points
x = np.hstack([x0, x, xn])
y = np.hstack([y0, y, yn])
x_cont = []
y_cont = []
# get points pairwise
for x1, x2, y1, y2 in zip(x, x[1:], y, y[1:]):
# approximate the number of samples per segment
numx = int(np.abs(x2 - x1) / step)
numy = int(np.abs(y2 - y1) / step)
# x-first (left or right move)
a = np.linspace(x1, x2, numx, endpoint=False)
b = np.repeat(y1, numx)
# y-second (top or down move)
c = np.repeat(x2, numy)
d = np.linspace(y1, y2, numy, endpoint=False)
# save points
x_cont.append([*a, *c])
y_cont.append([*b, *d])
# flat all computed values
return np.hstack([*x_cont, xn]), np.hstack([*y_cont, yn])
# Original data
x = np.array([3.45, 3.88, 3.99, 4.33])
y = np.array([14.0, 11.0, 14.0, 31.0])
y = np.cumsum(y) # It seems you want the cumulative sum of y
x_cont, y_cont = interpolate(x, y, x[0], 0, x[-1], y[-1], step=0.1)
Plot the values:
fig, ax = plt.subplots()
ax.step(x_cont, y_cont, color='red', where='post')
ax.set_xlim(2, 5)
ax.set_ylim(0, y.max())
ax.invert_yaxis()
plt.show()
Output:
It seems like you are looking for a function that takes as input a number of pivots p
and the number of points n
to put in the horizontal and vertical straight lines connecting those pivots. I assume that you want the extra points to be evenly (linearly) spaced.
Essentially, you need to:
- identify the pivot points
- ensure the
len(p) <= n
- use repeated values for
x
and linearly spaced values fory
when the lines are vertical - use repeated values for
y
and linearly spaced values forx
when the lines are horizontal
A simple implementation using NumPy (and specifically np.linspace()
and np.repeat()
) could be:
import typing as typ
import numpy as np
def upscale_steps_equi(
p_x: typ.Sequence[float],
p_y: typ.Sequence[float],
n: int,
start_hor: typ.Optional[str] = None,
dtype: np.dtype = np.float_,
):
"""
The points are equi-spaced (except near the pivots).
The pivots are, in general, not included.
Args:
p_x: Must be strictly monotonic increasing.
p_y: Must be strictly monotonic increasing.
n: Must be larger than max(len(p_x), len(p_y))
start_hor: Must be True for horizontal, False for vertical.
Only used (and needed) if len(p_x) == len(p_y)
"""
p_x_n = len(p_x)
p_y_n = len(p_y)
is_same_len = p_x_n == p_y_n
p_n = max(p_x_n, p_y_n) + 1 # number of points
if p_x_n > p_y_n:
start_hor = True
elif p_x_n < p_y_n:
start_hor = False
elif start_hor is None or not isinstance(start_hor, bool):
raise ValueError("`start_hor` required if len(p_x) == len(p_y)")
p_x_ = np.repeat(p_x, 2) # x-coords of pivots
p_y_ = np.repeat(p_y, 2) # y-coords of pivots
if is_same_len:
if start_hor:
p_x_ = p_x_[1:]
p_y_ = p_y_[:-1]
else:
p_x_ = p_x_[:-1]
p_y_ = p_y_[1:]
else:
if start_hor:
p_x_ = p_x_[1:-1]
else:
p_y_ = p_y_[1:-1]
dx = max(p_x) - min(p_x) # distance covered along x
dy = max(p_y) - min(p_y) # distance covered along y
d = dx + dy
ll = np.linspace(0, d, n, endpoint=True)
x = min(p_x) + ll
y = min(p_y) + ll
last_x_ = last_y_ = last_d_ = None
d_ = d_x_ = d_y_ = 0
for x_, y_ in zip(p_x_, p_y_):
if x_ == last_x_:
dd_y_ = y_ - last_y_
d_y_ += dd_y_
d_ += dd_y_
mask = (ll >= last_d_) & (ll < d_)
x[mask] = x_
y[mask] -= d_x_
elif y_ == last_y_:
dd_x_ = x_ - last_x_
d_x_ += dd_x_
d_ += dd_x_
mask = (ll >= last_d_) & (ll < d_)
y[mask] = y_
x[mask] -= d_y_
last_x_ = x_
last_y_ = y_
last_d_ = d_
x[-1] = p_x[-1]
y[-1] = p_y[-1]
return x, y
to be used like:
import matplotlib.pyplot as plt
x, y = upscale_steps_equi([1, 4, 5], [2, 3, 7, 9], 20)
plt.scatter(x, y, marker='o')
plt.axis('scaled')
The function above does not, in general, include the pivots.
However, if the pivots must be included, one needs to forego the exact number of points and the same spacing for x and y.
Here is a possible solution where the points are equi-spaced within each segment:
import typing as typ
import numpy as np
def upscale_steps_with_pivots(
p_x: typ.Sequence[float],
p_y: typ.Sequence[float],
n: int,
start_hor: typ.Optional[bool] = None,
dtype: np.dtype = np.float_,
):
"""
The points are equi-spaced only within each segment.
The pivots are strictly included.
Args:
p_x: Must be strictly monotonic increasing.
p_y: Must be strictly monotonic increasing.
n: Must be larger than max(len(p_x), len(p_y))
start_hor: Must be `h` for horizontal, `v` for vertical.
Only used (and needed) if len(p_x) == len(p_y)
"""
p_x_n = len(p_x)
p_y_n = len(p_y)
is_same_len = p_x_n == p_y_n
p_n = max(p_x_n, p_y_n) + 1 # number of points
if p_x_n > p_y_n:
start_hor = True
elif p_x_n < p_y_n:
start_hor = False
elif start_hor is None or not isinstance(start_hor, bool):
raise ValueError("`start_hor` required if len(p_x) == len(p_y)")
p_x_ = np.repeat(p_x, 2) # x-coords of pivots
p_y_ = np.repeat(p_y, 2) # y-coords of pivots
if is_same_len:
if start_hor:
p_x_ = p_x_[1:]
p_y_ = p_y_[:-1]
else:
p_x_ = p_x_[:-1]
p_y_ = p_y_[1:]
else:
if start_hor:
p_x_ = p_x_[1:-1]
else:
p_y_ = p_y_[1:-1]
dx = max(p_x) - min(p_x) # distance covered along x
dy = max(p_y) - min(p_y) # distance covered along y
d = dx + dy
n_x = (np.diff(p_x) / d * (n - p_n)).astype(np.int_)
n_y = (np.diff(p_y) / d * (n - p_n)).astype(np.int_)
r = np.ones(len(n_x) + len(n_y) + 1, dtype=np.int_)
if start_hor:
r[:-1 if is_same_len else None:2] = n_x + 1
r[1:-1:2] = n_y + 1
else:
r[:-1 if is_same_len else None:2] = n_y + 1
r[1:-1:2] = n_x + 1
x = np.repeat(p_x_, r).astype(dtype)
y = np.repeat(p_y_, r).astype(dtype)
j = 0
for i, r_ in enumerate(r[:-1]):
if i % 2 != start_hor:
k = np.linspace(p_x_[i], p_x_[i + 1], r_, endpoint=False)
x[j : j + len(k)] = k
else:
k = np.linspace(p_y_[i], p_y_[i + 1], r_, endpoint=False)
y[j : j + len(k)] = k
j += r_
return x, y
to be used like:
import matplotlib.pyplot as plt
x, y = upscale_steps_with_pivots([1, 4, 5], [2, 3, 7, 9], 20)
plt.scatter(x, y, marker='o')
plt.axis('scaled')
(Note that until the computation of d
, the two functions are identical, and it mostly contain the logic to deal with the different possible configurations of segments.)