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()

enter image description here
#####################################################

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. enter image description here

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()
Asked By: pro

||

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).

Answered By: Domenico

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.])

enter image description here


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:

enter image description here

Answered By: Corralien

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:

  1. identify the pivot points
  2. ensure the len(p) <= n
  3. use repeated values for x and linearly spaced values for y when the lines are vertical
  4. 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')

plot_equi


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')

plot_w_pivot

(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.)

Answered By: norok2
Categories: questions Tags: , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.