участки поверхности в matplotlib

105

У меня есть список из трех кортежей, представляющих набор точек в трехмерном пространстве. Я хочу построить поверхность, покрывающую все эти точки.

plot_surfaceФункция в mplot3dпакете требует в качестве аргументов X, Y и Z , чтобы быть 2d массивов. Подходит ли plot_surfaceфункция для построения поверхности и как мне преобразовать данные в требуемый формат?

data = [(x1,y1,z1),(x2,y2,z2),.....,(xn,yn,zn)]
Градди
источник
8
Вот несколько связанных / похожих / повторяющихся сообщений: stackoverflow.com/q/3012783/3585557 , stackoverflow.com/q/12423601/3585557 , stackoverflow.com/q/21161884/3585557 , stackoverflow.com/q/26074542/3585557 , stackoverflow.com/q/28389606/3585557 , stackoverflow.com/q/29547687/3585557 .
Стивен С. Хауэлл,
Пожалуйста, начните помечать все эти дублирующиеся поверхности и закрывать дубликаты друг на друга. Также отметьте numpy , mesh для тех, которые касаются генерации сетки .
smci

Ответы:

121

Для поверхностей это немного отличается от списка из трех кортежей, вы должны передать сетку для домена в 2d массивах.

Если все, что у вас есть, это список трехмерных точек, а не какая-то функция f(x, y) -> z, тогда у вас возникнет проблема, потому что есть несколько способов триангулировать это трехмерное облако точек на поверхность.

Вот пример гладкой поверхности:

import numpy as np
from mpl_toolkits.mplot3d import Axes3D  
# Axes3D import has side effects, it enables using projection='3d' in add_subplot
import matplotlib.pyplot as plt
import random

def fun(x, y):
    return x**2 + y

fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
x = y = np.arange(-3.0, 3.0, 0.05)
X, Y = np.meshgrid(x, y)
zs = np.array(fun(np.ravel(X), np.ravel(Y)))
Z = zs.reshape(X.shape)

ax.plot_surface(X, Y, Z)

ax.set_xlabel('X Label')
ax.set_ylabel('Y Label')
ax.set_zlabel('Z Label')

plt.show()

3D

слабак
источник
1
Привет спасибо за это Не могли бы f(x,y) -> zвы подробнее рассказать о том, как наличие функции дает вам больше информации, чем простое использование спискового подхода, который изначально был у OP.
Грегори Кун
16
Но что делать, если z - независимая переменная, а не функция от x и y?
Лабиба
4
В этом случае, возможно, вам стоит plot_trisurfвместо этого посмотреть . Но, как я уже упоминал, это нетривиально, потому что вам нужно триангулировать поверхность и есть несколько решений. В качестве базового примера рассмотрим только 4 точки, заданные формулами (0, 0, 0,2), (0, 1, 0), (1, 1, 0,2), (1, 0, 0). Если смотреть сверху, он выглядит как квадрат с небольшой складкой. Но по какой диагонали происходит «складка»? Это «высокая» диагональ 0,2 или «низкая» диагональ 0? Обе подходящие поверхности! Итак, вам нужно выбрать алгоритм триангуляции, прежде чем у вас будет четко определенное решение.
wim
Почему из mpl_toolkits.mplot3d импортировать Axes3D, а Axes3D нигде в приведенном выше коде не используется?
絢 瀬 絵 里
5
У этого импорта есть побочные эффекты. Использование kwarg projection='3d'в вызове fig.add_subplotбудет недоступно без этого импорта.
wim
34

Вы можете читать данные прямо из какого-либо файла и строить график

from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
from matplotlib import cm
import numpy as np
from sys import argv

x,y,z = np.loadtxt('your_file', unpack=True)

fig = plt.figure()
ax = Axes3D(fig)
surf = ax.plot_trisurf(x, y, z, cmap=cm.jet, linewidth=0.1)
fig.colorbar(surf, shrink=0.5, aspect=5)
plt.savefig('teste.pdf')
plt.show()

При необходимости вы можете передать vmin и vmax для определения диапазона шкалы палитры, например

surf = ax.plot_trisurf(x, y, z, cmap=cm.jet, linewidth=0.1, vmin=0, vmax=2000)

поверхность

Бонусный раздел

Мне было интересно, как делать интерактивные графики, в данном случае с искусственными данными

from __future__ import print_function
from ipywidgets import interact, interactive, fixed, interact_manual
import ipywidgets as widgets
from IPython.display import Image

from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits import mplot3d

def f(x, y):
    return np.sin(np.sqrt(x ** 2 + y ** 2))

def plot(i):

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

    theta = 2 * np.pi * np.random.random(1000)
    r = i * np.random.random(1000)
    x = np.ravel(r * np.sin(theta))
    y = np.ravel(r * np.cos(theta))
    z = f(x, y)

    ax.plot_trisurf(x, y, z, cmap='viridis', edgecolor='none')
    fig.tight_layout()

interactive_plot = interactive(plot, i=(2, 10))
interactive_plot
Эмануэль Фонтеллес
источник
5
строго говоря, панды здесь не нужны.
downer
Мне сложно воспроизвести этот сюжет. Какие (меньшие) выборочные значения для этого?
JRsz
21

Я столкнулся с той же проблемой. Я равномерно распределены данные , которые в 3 - 1-D массивов вместо 2-D массивов, matplotlib«S plot_surfaceхочет. Мои данные оказались в виде, pandas.DataFrameпоэтому вот matplotlib.plot_surfaceпример с модификациями для построения 3 одномерных массивов.

from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
from matplotlib.ticker import LinearLocator, FormatStrFormatter
import matplotlib.pyplot as plt
import numpy as np

X = np.arange(-5, 5, 0.25)
Y = np.arange(-5, 5, 0.25)
X, Y = np.meshgrid(X, Y)
R = np.sqrt(X**2 + Y**2)
Z = np.sin(R)

fig = plt.figure()
ax = fig.gca(projection='3d')
surf = ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap=cm.coolwarm,
    linewidth=0, antialiased=False)
ax.set_zlim(-1.01, 1.01)

ax.zaxis.set_major_locator(LinearLocator(10))
ax.zaxis.set_major_formatter(FormatStrFormatter('%.02f'))

fig.colorbar(surf, shrink=0.5, aspect=5)
plt.title('Original Code')

Это оригинальный пример. Добавление этого следующего бита создает тот же график из 3-х одномерных массивов.

# ~~~~ MODIFICATION TO EXAMPLE BEGINS HERE ~~~~ #
import pandas as pd
from scipy.interpolate import griddata
# create 1D-arrays from the 2D-arrays
x = X.reshape(1600)
y = Y.reshape(1600)
z = Z.reshape(1600)
xyz = {'x': x, 'y': y, 'z': z}

# put the data into a pandas DataFrame (this is what my data looks like)
df = pd.DataFrame(xyz, index=range(len(xyz['x']))) 

# re-create the 2D-arrays
x1 = np.linspace(df['x'].min(), df['x'].max(), len(df['x'].unique()))
y1 = np.linspace(df['y'].min(), df['y'].max(), len(df['y'].unique()))
x2, y2 = np.meshgrid(x1, y1)
z2 = griddata((df['x'], df['y']), df['z'], (x2, y2), method='cubic')

fig = plt.figure()
ax = fig.gca(projection='3d')
surf = ax.plot_surface(x2, y2, z2, rstride=1, cstride=1, cmap=cm.coolwarm,
    linewidth=0, antialiased=False)
ax.set_zlim(-1.01, 1.01)

ax.zaxis.set_major_locator(LinearLocator(10))
ax.zaxis.set_major_formatter(FormatStrFormatter('%.02f'))

fig.colorbar(surf, shrink=0.5, aspect=5)
plt.title('Meshgrid Created from 3 1D Arrays')
# ~~~~ MODIFICATION TO EXAMPLE ENDS HERE ~~~~ #

plt.show()

Вот итоговые цифры:

введите описание изображения здесь введите описание изображения здесь

Стивен С. Хауэлл
источник
Мне было интересно, можно ли удалить линии, выходящие на поверхность (изображение выше), я имею в виду, можно ли придать поверхности глянцевый вид вместо чешуйчатого? Спасибо. @ stvn66
diffracteD
@diffracteD, попробуйте использовать сетку меньшего размера. Я почти уверен, что это то, что задает ширину между контурами. Оценивая более мелкую сетку, вы должны существенно уменьшить «размер пикселя» и увеличить разрешение, приближаясь к более плавному градиенту.
Стивен С. Хауэлл,
Есть ли способ раскрасить вышеуказанную поверхность по определенным категориям? Например, Категория x, y, z - это формат данных, и я хотел бы раскрасить поверхность, проходящую через x, y, z, в соответствии с определенной категорией.
Рудреш Айгаонкар
@RudreshAjgaonkar, вы должны иметь возможность использовать три отдельные команды построения графика, по одной для каждой из ваших категорий, используя любой цвет, который вы хотите для каждой из трех.
Стивен С. Хауэлл,
не могли бы вы предоставить образец кода? Я новичок в matplotlib и python.
Рудреш Айгаонкар
4

Просто чтобы поддержать разговор, у Эмануэля был ответ, который я (и, вероятно, многие другие) ищу. Если у вас есть трехмерные разбросанные данные в 3 отдельных массивах, pandas будет невероятным подспорьем и работает намного лучше, чем другие варианты. Чтобы уточнить, предположим, что ваши x, y, z - некоторые произвольные переменные. В моем случае это были c, гамма и ошибки, потому что я тестировал машину опорных векторов. Есть много возможных вариантов построения данных:

  • scatter3D (cParams, gammas, avg_errors_array) - это работает, но слишком упрощенно
  • plot_wireframe (cParams, gammas, avg_errors_array) - это работает, но будет выглядеть некрасиво, если ваши данные плохо отсортированы, как это потенциально имеет место с массивными фрагментами реальных научных данных
  • ax.plot3D (cParams, gammas, avg_errors_array) - аналогично каркасу

Каркасный график данных

Каркасный график данных

3D разброс данных

3D разброс данных

Код выглядит так:

    fig = plt.figure()
    ax = fig.gca(projection='3d')
    ax.set_xlabel('c parameter')
    ax.set_ylabel('gamma parameter')
    ax.set_zlabel('Error rate')
    #ax.plot_wireframe(cParams, gammas, avg_errors_array)
    #ax.plot3D(cParams, gammas, avg_errors_array)
    #ax.scatter3D(cParams, gammas, avg_errors_array, zdir='z',cmap='viridis')

    df = pd.DataFrame({'x': cParams, 'y': gammas, 'z': avg_errors_array})
    surf = ax.plot_trisurf(df.x, df.y, df.z, cmap=cm.jet, linewidth=0.1)
    fig.colorbar(surf, shrink=0.5, aspect=5)    
    plt.savefig('./plots/avgErrs_vs_C_andgamma_type_%s.png'%(k))
    plt.show()

Вот окончательный результат:

plot_trisurf данных xyz

ArtifexR
источник
3

проверьте официальный пример. X, Y и Z действительно являются 2d-массивами, numpy.meshgrid () - простой способ получить 2d-сетку x, y из 1d значений x и y.

http://matplotlib.sourceforge.net/mpl_examples/mplot3d/surface3d_demo.py

вот питонический способ преобразовать ваши 3-кортежи в 3 1d массивы.

data = [(1,2,3), (10,20,30), (11, 22, 33), (110, 220, 330)]
X,Y,Z = zip(*data)
In [7]: X
Out[7]: (1, 10, 11, 110)
In [8]: Y
Out[8]: (2, 20, 22, 220)
In [9]: Z
Out[9]: (3, 30, 33, 330)

Вот триангуляция (интерполяция) mtaplotlib delaunay, она преобразует 1d x, y, z во что-то совместимое (?):

http://matplotlib.sourceforge.net/api/mlab_api.html#matplotlib.mlab.griddata

Дима Тиснек
источник
Нет ... XYZ в этом примере двумерны.
wim
Я исправился. Используйте meshgrid (), если ваши данные равномерно распределены, как в связанном примере. Интерполируйте, например, с помощью griddata (), если ваши данные неравномерно распределены.
Дима Тиснек
2

Просто чтобы добавить некоторые дополнительные мысли, которые могут помочь другим с проблемами нестандартного типа домена. Для ситуации, когда у пользователя есть три вектора / списка, x, y, z, представляющие 2D-решение, где z должен быть нанесен на прямоугольную сетку в виде поверхности, применимы комментарии plot_trisurf () от ArtifixR. Аналогичный пример, но с непрямоугольным доменом:

import matplotlib.pyplot as plt
from matplotlib import cm
from mpl_toolkits.mplot3d import Axes3D 

# problem parameters
nu = 50; nv = 50
u = np.linspace(0, 2*np.pi, nu,) 
v = np.linspace(0, np.pi, nv,)

xx = np.zeros((nu,nv),dtype='d')
yy = np.zeros((nu,nv),dtype='d')
zz = np.zeros((nu,nv),dtype='d')

# populate x,y,z arrays
for i in range(nu):
  for j in range(nv):
    xx[i,j] = np.sin(v[j])*np.cos(u[i])
    yy[i,j] = np.sin(v[j])*np.sin(u[i])
    zz[i,j] = np.exp(-4*(xx[i,j]**2 + yy[i,j]**2)) # bell curve

# convert arrays to vectors
x = xx.flatten()
y = yy.flatten()
z = zz.flatten()

# Plot solution surface
fig = plt.figure(figsize=(6,6))
ax = Axes3D(fig)
ax.plot_trisurf(x, y, z, cmap=cm.jet, linewidth=0,
                antialiased=False)
ax.set_title(r'trisurf example',fontsize=16, color='k')
ax.view_init(60, 35)
fig.tight_layout()
plt.show()

Приведенный выше код производит:

График поверхности для задачи непрямоугольной сетки

Однако это может не решить все проблемы, особенно если проблема определена в неправильном домене. Кроме того, в случае, когда домен имеет одну или несколько вогнутых областей, триангуляция с задержкой может привести к генерации ложных треугольников, находящихся вне области. В таких случаях эти неправильные треугольники должны быть удалены из триангуляции, чтобы получить правильное представление поверхности. В этих ситуациях пользователю может потребоваться явно включить расчет триангуляции Делоне, чтобы эти треугольники можно было удалить программно. В этих обстоятельствах следующий код может заменить предыдущий код графика:


import matplotlib.tri as mtri 
import scipy.spatial
# plot final solution
pts = np.vstack([x, y]).T
tess = scipy.spatial.Delaunay(pts) # tessilation

# Create the matplotlib Triangulation object
xx = tess.points[:, 0]
yy = tess.points[:, 1]
tri = tess.vertices # or tess.simplices depending on scipy version

#############################################################
# NOTE: If 2D domain has concave properties one has to
#       remove delaunay triangles that are exterior to the domain.
#       This operation is problem specific!
#       For simple situations create a polygon of the
#       domain from boundary nodes and identify triangles
#       in 'tri' outside the polygon. Then delete them from
#       'tri'.
#       <ADD THE CODE HERE>
#############################################################

triDat = mtri.Triangulation(x=pts[:, 0], y=pts[:, 1], triangles=tri)

# Plot solution surface
fig = plt.figure(figsize=(6,6))
ax = fig.gca(projection='3d')
ax.plot_trisurf(triDat, z, linewidth=0, edgecolor='none',
                antialiased=False, cmap=cm.jet)
ax.set_title(r'trisurf with delaunay triangulation', 
          fontsize=16, color='k')
plt.show()

Ниже приведены примеры графиков, иллюстрирующих решение 1) с ложными треугольниками и 2) где они были удалены:

введите описание изображения здесь

треугольники удалены

Я надеюсь, что изложенное выше может помочь людям с вогнутыми данными в данных решения.

Грэм Джи
источник
1

В Matlab я сделал что - то подобное с помощью delaunayфункции на x, yCoords только (не z), а затем заговор с trimeshили trisurf, используя в zкачестве высоты.

SciPy имеет класс Delaunay , который основан на той же базовой библиотеке QHull, что и delaunayфункция Matlab , поэтому вы должны получить идентичные результаты.

Оттуда должно быть несколько строк кода для преобразования этого Plotting 3D Polygons in python-matplotlib в то, что вы хотите достичь, так как Delaunayдает вам спецификацию каждого треугольного многоугольника.

Евгений Сергеев
источник
См. Этот ответ на основе ax.plot_trisurf(..).
Евгений Сергеев
0

Невозможно напрямую создать трехмерную поверхность, используя ваши данные. Я бы порекомендовал вам построить модель интерполяции, используя некоторые инструменты, такие как pykridge . Процесс будет включать три этапа:

  1. Обучите модель интерполяции, используя pykridge
  2. Создайте сетку из Xи Yиспользуяmeshgrid
  3. Интерполировать значения для Z

Создав свою сетку и соответствующие Zзначения, теперь вы готовы к работе plot_surface. Обратите внимание, что в зависимости от размера ваших данных meshgridфункция может работать некоторое время. Обходной путь - создать равномерно распределенные выборки с помощью осей np.linspacefor Xи Y, а затем применить интерполяцию для вывода необходимых Zзначений. В таком случае интерполированные значения могут отличаться от исходных, Zпоскольку Xи Yбыли изменены.

Lenhhoxung
источник