3D-график: плавный график по оси X - PullRequest
3 голосов
/ 09 января 2020

У меня есть трехмерный полигональный график, и я хочу сгладить график по оси y (т. Е. Я хочу, чтобы он выглядел как «кусочки поверхностного графика»).

Рассмотрим это MWE (взято из here ):

from mpl_toolkits.mplot3d import Axes3D
from matplotlib.collections import PolyCollection
import matplotlib.pyplot as plt
from matplotlib import colors as mcolors
import numpy as np
from scipy.stats import norm

fig = plt.figure()
ax = fig.gca(projection='3d')

xs = np.arange(-10, 10, 2)
verts = []
zs = [0.0, 1.0, 2.0, 3.0]

for z in zs:
    ys = np.random.rand(len(xs))
    ys[0], ys[-1] = 0, 0
    verts.append(list(zip(xs, ys)))

poly = PolyCollection(verts, facecolors=[mcolors.to_rgba('r', alpha=0.6),
                                         mcolors.to_rgba('g', alpha=0.6), 
                                         mcolors.to_rgba('b', alpha=0.6), 
                                         mcolors.to_rgba('y', alpha=0.6)])
poly.set_alpha(0.7)
ax.add_collection3d(poly, zs=zs, zdir='y')
ax.set_xlabel('X')
ax.set_xlim3d(-10, 10)
ax.set_ylabel('Y')
ax.set_ylim3d(-1, 4)
ax.set_zlabel('Z')
ax.set_zlim3d(0, 1)
plt.show()

Теперь я хочу заменить четыре графика нормальными распределениями (для идеального формирования непрерывных линий).

Я создал распределения здесь:

def get_xs(lwr_bound = -4, upr_bound = 4, n = 80):
    """ generates the x space betwee lwr_bound and upr_bound so that it has n intermediary steps """
    xs = np.arange(lwr_bound, upr_bound, (upr_bound - lwr_bound) / n) # x space -- number of points on l/r dimension
    return(xs)

xs = get_xs()

dists = [1, 2, 3, 4]

def get_distribution_params(list_):
    """ generates the distribution parameters (mu and sigma) for len(list_) distributions"""
    mus = []
    sigmas = []
    for i in range(len(dists)):
        mus.append(round((i + 1) + 0.1 * np.random.randint(0,10), 3))
        sigmas.append(round((i + 1) * .01 * np.random.randint(0,10), 3))
    return mus, sigmas

mus, sigmas = get_distribution_params(dists)

def get_distributions(list_, xs, mus, sigmas):
    """ generates len(list_) normal distributions, with different mu and sigma values """
    distributions = [] # distributions

    for i in range(len(list_)):
        x_ = xs
        z_ = norm.pdf(xs, loc = mus[i], scale = sigmas[0])
        distributions.append(list(zip(x_, z_)))
        #print(x_[60], z_[60])

    return distributions

distributions = get_distributions(list_ = dists, xs = xs, mus = mus, sigmas = sigmas)

Но добавление их в код (с poly = PolyCollection(distributions, ...) и ax.add_collection3d(poly, zs=distributions, zdir='z') выдает ValueError (ValueError: input operand has more dimensions than allowed by the axis remapping), я не могу разрешить.

Ответы [ 2 ]

2 голосов
/ 16 января 2020

Ошибка вызвана передачей distributions в zs, где zs ожидает, что когда verts в PolyCollection имеет форму MxNx2 объект, переданный в zs, имеет форму М . Поэтому, когда он достигает этой проверки

cpdef ndarray broadcast_to(ndarray array, shape):
    # ...
    if array.ndim < len(shape):
        raise ValueError(
            'input operand has more dimensions than allowed by the axis '
            'remapping')
    # ...

в базовом коде numpy, он завершается неудачей. Я считаю, что это происходит потому, что число ожидаемых измерений (array.ndim) меньше, чем число измерений zs (len(shape)). Он ожидает массив формы (4,), но получает массив формы (4, 80, 2).

Эту ошибку можно устранить, используя массив правильной формы - например, zs из исходного примера или dists из вашего кода. Использование zs=dists и установка пределов оси на [0,5] для x, y и z дает

enter image description here

Это выглядит немного странно по двум причинам:

  1. В z_ = norm.pdf(xs, loc = mus[i], scale = sigmas[0]) есть опечатка, которая дает всем распределениям одинаковую сигма, она должна быть z_ = norm.pdf(xs, loc = mus[i], scale = sigmas[i])
  2. Геометрия просмотра: Распределения имеют положительную xz плоскость в качестве своей базы, это также плоскость, которую мы просматриваем.

Изменение геометрии просмотра с помощью ax.view_init даст более четкий график:

enter image description here


Edit

Вот полный код, который генерирует показанный график,

from mpl_toolkits.mplot3d import Axes3D
from matplotlib.collections import PolyCollection
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
import numpy as np
from scipy.stats import norm

np.random.seed(8)
def get_xs(lwr_bound = -4, upr_bound = 4, n = 80):
    return np.arange(lwr_bound, upr_bound, (upr_bound - lwr_bound) / n)

def get_distribution_params(list_):
    mus = [round((i+1) + 0.1 * np.random.randint(0,10), 3) for i in range(len(dists))]
    sigmas = [round((i+1) * .01 * np.random.randint(0,10), 3) for i in range(len(dists))]
    return mus, sigmas

def get_distributions(list_, xs, mus, sigmas):
    return [list(zip(xs, norm.pdf(xs, loc=mus[i], scale=sigmas[i] if sigmas[i] != 0.0 
            else 0.1))) for i in range(len(list_))]

dists = [1, 2, 3, 4]
xs = get_xs()
mus, sigmas = get_distribution_params(dists)
distributions = get_distributions(dists, xs, mus, sigmas)

fc = [mcolors.to_rgba('r', alpha=0.6), mcolors.to_rgba('g', alpha=0.6), 
      mcolors.to_rgba('b', alpha=0.6), mcolors.to_rgba('y', alpha=0.6)]

poly = PolyCollection(distributions, fc=fc)
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.add_collection3d(poly, zs=np.array(dists).astype(float), zdir='z')
ax.view_init(azim=115)
ax.set_zlim([0, 5])
ax.set_ylim([0, 5])
ax.set_xlim([0, 5])

Я основал его на коде, который вы предоставили в вопросе, но сделал некоторые изменения для краткости и быть более совместимым с обычным стилем.


Примечание - приведенный вами пример кода не будет работать в зависимости от np.random.seed(), чтобы убедиться, что он работает, я добавил проверку в вызове к norm.pdf что гарантирует ненулевой масштаб: scale = sigma[i] if sigma[i] != 0.0 else 0.1.

1 голос
/ 15 января 2020

Использование ax.add_collection3d(poly, zs=dists, zdir='z') вместо ax.add_collection3d(poly, zs=distributions, zdir='z') должно исправить проблему.


Кроме того, вы можете заменить

def get_xs(lwr_bound = -4, upr_bound = 4, n = 80):
    """ generates the x space betwee lwr_bound and upr_bound so that it has n intermediary steps """
    xs = np.arange(lwr_bound, upr_bound, (upr_bound - lwr_bound) / n) # x space -- number of points on l/r dimension
    return(xs)

xs = get_xs()

на

xs = np.linspace(-4, 4, 80)

Кроме того, я считаю, что scale = sigmas[0] на самом деле должно быть scale = sigmas[i] в строке

z_ = norm.pdf(xs, loc = mus[i], scale = sigmas[0])

Наконец, я считаю, что вы должны настроить xlim, ylim и zlim соответственно, когда вы поменяли местами размеры y и z и изменили его масштаб при сравнении с эталонным кодом.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...