графики поверхности в matplotlib

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

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

data = [(x1,y1,z1),(x2,y2,z2),.....,(xn,yn,zn)]

person Graddy    schedule 07.02.2012    source источник
comment
Вот несколько связанных / похожих / повторяющихся сообщений: 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.   -  person Steven C. Howell    schedule 30.05.2015
comment
Начните отмечать все эти дубликаты surface и закрывать дубликаты друг друга. Также отметьте тегами numpy, mesh для тех, которые касаются генерации сеток.   -  person smci    schedule 01.03.2020


Ответы (9)


Для поверхностей это немного отличается от списка из трех кортежей, вы должны передать сетку для домена в 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

person wim    schedule 07.02.2012
comment
Привет спасибо за это Не могли бы вы подробнее рассказать, как наличие функции f(x,y) -> z дает вам больше информации, чем простое использование спискового подхода, как изначально было в OP. - person Gregory Kuhn; 05.03.2015
comment
Но что делать, если z - независимая переменная, а не функция от x и y? - person Labibah; 09.04.2015
comment
В этом случае, возможно, вам следует вместо этого посмотреть на plot_trisurf. Но, как я уже упоминал, это нетривиально, потому что вам нужно триангулировать поверхность и есть несколько решений. В качестве базового примера рассмотрим только 4 точки, заданные формулами (0, 0, 0,2), (0, 1, 0), (1, 1, 0,2), (1, 0, 0). Если смотреть сверху, он выглядит как квадрат с небольшой складкой внутри. Но по какой диагонали происходит сгиб? Это высокая диагональ в 0,2 или низкая диагональ в 0? Обе подходящие поверхности! Поэтому вам нужно выбрать алгоритм триангуляции, прежде чем у вас будет четко определенное решение. - person wim; 10.04.2015
comment
Почему из mpl_toolkits.mplot3d импортировать Axes3D, а Axes3D нигде в приведенном выше коде не используется? - person 絢瀬絵里; 08.10.2015
comment
У этого импорта есть побочные эффекты. Использование kwarg projection='3d' в вызове fig.add_subplot будет недоступно без этого импорта. - person wim; 08.10.2015
comment
@GregoryKuhn Как сделать так, чтобы метки X и Y начинались с одной стороны? (так как мне переключить ось x вокруг?) - person Hassan Dbouk; 21.06.2019

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

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
person Emanuel Fontelles    schedule 21.09.2017
comment
Собственно говоря, панды здесь не нужны. - person downer; 26.02.2018
comment
Мне сложно воспроизвести этот сюжет. Каковы были бы некоторые (меньшие) выборочные значения для достижения этого? - person JRsz; 23.05.2019

Я столкнулся с той же проблемой. У меня есть равномерно распределенные данные, которые находятся в 3 одномерных массивах вместо двухмерных массивов, которые хочет matplotlib 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()

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

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

person Steven C. Howell    schedule 29.05.2015
comment
Мне было интересно, можно ли удалить линии, выходящие на поверхность (изображение выше), я имею в виду, можно ли придать поверхности глянцевый вид вместо чешуйчатого? Спасибо. @ stvn66 - person diffracteD; 02.09.2015
comment
@diffracteD, попробуйте использовать сетку меньшего размера. Я почти уверен, что это то, что задает ширину между контурами. Оценивая более мелкую сетку, вы должны существенно уменьшить размер пикселя и увеличить разрешение, приближаясь к более плавному градиенту. - person Steven C. Howell; 04.04.2016
comment
Есть ли способ раскрасить вышеуказанную поверхность по определенным категориям? Например, Категория x, y, z - это формат данных, и я хотел бы раскрасить поверхность, проходящую через x, y, z, в соответствии с конкретной категорией. - person Rudresh Ajgaonkar; 16.08.2016
comment
@RudreshAjgaonkar, вы должны иметь возможность использовать три отдельные команды построения графика, по одной для каждой из ваших категорий, используя любую желаемую расцветку для каждой из трех. - person Steven C. Howell; 16.08.2016
comment
не могли бы вы предоставить образец кода? Я новичок в matplotlib и python. - person Rudresh Ajgaonkar; 16.08.2016
comment
Взгляните здесь . Этот ОП задает вопрос, в котором он рисует две разные поверхности с разной окраской. Вам придется пробовать разные вещи, пока вы не получите то, что хотите. Если вы столкнетесь с более конкретным вопросом, я рекомендую вам сформулировать новый вопрос и включить то, что вы пробовали, изображение результата и то, что вы хотели бы выглядеть по-другому. - person Steven C. Howell; 16.08.2016
comment
griddata() этого поста - это функция, которая интерполирует разбросанные тройки x, y, z в обычную сетку x, y. Если вы хотите раскрашивать категории по отдельности, изучите аргумент plot_surface(facecolor=...). - person Dave X; 17.01.2017
comment
@ StevenC.Howell Не могли бы вы дать мне представление о том, что делают "linewidth" и "antialiased" в функции ax.plot_surface ()? Файлы справки matplotlib мне непонятны. У меня есть график данных, спроецированных на единичную сферу, который строит, но появляется почти перед набором осей, поэтому я пытаюсь понять, почему именно. Спасибо за ваше время. - person John Doe; 02.03.2018
comment
@JohnDoe На самом деле это нужно написать как отдельный вопрос. Я рекомендую поиграть с различными вариантами значений, чтобы получить личное понимание. Я считаю, что вне манжеты 'linewidth' устанавливает ширину линий между вокселями (использование 0 исключает линии), а 'antialiased' управляет переходом цвета между границами вокселей. - person Steven C. Howell; 02.03.2018

Просто чтобы вмешаться, у Эмануэля был ответ, который я (и, вероятно, многие другие) ищу. Если у вас есть трехмерные разбросанные данные в 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 разброс данных

Трехмерный разброс данных

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

    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 of xyz data

person ArtifexR    schedule 02.04.2018

Просто чтобы добавить еще несколько мыслей, которые могут помочь другим решить проблемы с нестандартным типом домена. Для ситуации, когда у пользователя есть три вектора / списка, 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()

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

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

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


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) где они были удалены:

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

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

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

person Graham G    schedule 26.02.2020

проверьте официальный пример. X, Y и Z действительно являются 2-мерными массивами, numpy.meshgrid () - простой способ получить 2-мерную сетку 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

person Dima Tisnek    schedule 07.02.2012
comment
Нет ... X Y Z в этом примере двумерны. - person wim; 11.02.2012
comment
Я исправился. Используйте meshgrid (), если ваши данные равномерно распределены, как в связанном примере. Интерполировать, например, с griddata (), если ваши данные не равномерно распределены. - person Dima Tisnek; 13.02.2012

Это не общее решение, но может помочь многим из тех, кто только что набрал график поверхности matplotlib в Google и приземлился здесь.

Предположим, у вас есть data = [(x1,y1,z1),(x2,y2,z2),.....,(xn,yn,zn)], тогда вы можете получить три одномерных списка, используя x, y, z = zip(*data). Конечно, теперь вы можете создать трехмерную диаграмму рассеяния, используя три одномерных списка.

Но почему нельзя в целом использовать эти данные для построения графика поверхности? Чтобы понять это, рассмотрим пустой трехмерный график:

Теперь предположим, что для каждого возможного значения (x, y) в дискретной регулярной сетке у вас есть значение z, тогда нет проблем, и вы можете фактически получить график поверхности:

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

x = np.linspace(0, 10, 6)  # [0, 2,..,10] : 6 distinct values
y = np.linspace(0, 20, 5)  # [0, 5,..,20] : 5 distinct values
z = np.linspace(0, 100, 30)  # 6 * 5 = 30 values, 1 for each possible combination of (x,y)

X, Y = np.meshgrid(x, y)
Z = np.reshape(z, X.shape)  # Z.shape must be equal to X.shape = Y.shape

fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')

ax.plot_surface(X, Y, Z)

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

Что произойдет, если у вас нет z для всех возможных комбинаций (x, y)? Затем в точке (на пересечении двух черных линий на плоскости x-y на пустом графике выше) мы не знаем, каково значение z. Это может быть что угодно, мы не знаем, насколько «высокой» или «низкой» должна быть наша поверхность в этой точке (хотя ее можно аппроксимировать с помощью других функций, surface_plot требует, чтобы вы предоставили ей аргументы, где X.shape = Y.shape = Z. форма).

person Deep    schedule 26.01.2021

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

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

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

person Evgeni Sergeev    schedule 15.10.2014
comment
См. этот ответ на основе ax.plot_trisurf(..). - person Evgeni Sergeev; 22.10.2014

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

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

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

person lenhhoxung    schedule 19.09.2018