使用 "plotnine" 库绘制表面 3D 图

Drawing a surface 3D plot using "plotnine" library

问题:使用python库'plotnine',我们可以绘制交互式3D表面图吗?

备份说明

  1. 我想做的是,在 python 环境下,使用 R 绘图语法创建交互式 3D 绘图,就像我们在 R 中使用 ggplot2 库一样。这是因为我很难记住 matplotlib 和其他库(如 seaborn)的语法的时间。

  2. 交互式 3D 绘图是指可以放大、缩小、上下滚动等的 3D 绘图。

  3. 似乎只有 Java 支持的绘图库 scuh as bokeh 或 plotly 可以创建交互式 3D 图。但是我想用库 'plotnine' 创建它,因为该库支持类似 ggplot 的语法,这很容易记住。

  4. 例如,我可以用库 'plotnine' 绘制像下面这样的 3D 曲面图吗?

    import plotly.plotly as py
    import plotly.graph_objs as go
    import pandas as pd
    
    # Read data from a csv
    z_data =
    pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/
    master/api_docs/mt_bruno_elevation.csv')
    
     data = [
            go.Surface(
            z=z_data.as_matrix()
            )]
     layout = go.Layout(
     title='Mt Bruno Elevation',
     autosize=False,
     width=500,
     height=500,
     margin=dict(
     l=65,
     r=50,
     b=65,
     t=90
       )
     )
     fig = go.Figure(data=data, layout=layout)
     py.iplot(fig, filename='elevations-3d-surface')
    

上面的代码生成如下图

您可以在此 link

中查看完整的交互式 3D 表面图

p.s。如果我可以使用类似 ggplot 的语法绘制交互式 3D 绘图,那么它不一定是我们应该使用的 'plotnine' 库。

感谢您花时间阅读这个问题!

如果您愿意稍微扩展 plotnine,这是可能的,并且需要注意。最终代码很简单:

(
    ggplot_3d(mt_bruno_long)
    + aes(x='x', y='y', z='height')
    + geom_polygon_3d(size=0.01)
    + theme_minimal()
)

结果:

首先,您需要将数据转换为长格式:

z_data = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/api_docs/mt_bruno_elevation.csv', index_col=0)
z = z_data.values
nrows, ncols = z.shape
x, y = np.linspace(0, 1, nrows), np.linspace(0, 1, ncols)
x, y = np.meshgrid(x, y)
mt_bruno_long = pd.DataFrame({'x': x.flatten(), 'y': y.flatten(), 'height': z.flatten()})

然后,我们需要为 ggplotgeom_polygon 创建等同于第三维度的意识:

from plotnine import ggplot, geom_polygon
from plotnine.utils import to_rgba, SIZE_FACTOR


class ggplot_3d(ggplot):
    def _create_figure(self):
        figure = plt.figure()
        axs = [plt.axes(projection='3d')]
        
        figure._themeable = {}
        self.figure = figure
        self.axs = axs
        return figure, axs
    
    def _draw_labels(self):
        ax = self.axs[0]
        ax.set_xlabel(self.layout.xlabel(self.labels))
        ax.set_ylabel(self.layout.ylabel(self.labels))
        ax.set_zlabel(self.labels['z'])


class geom_polygon_3d(geom_polygon):
    REQUIRED_AES = {'x', 'y', 'z'}

    @staticmethod
    def draw_group(data, panel_params, coord, ax, **params):
        data = coord.transform(data, panel_params, munch=True)
        data['size'] *= SIZE_FACTOR

        grouper = data.groupby('group', sort=False)
        for i, (group, df) in enumerate(grouper):
            fill = to_rgba(df['fill'], df['alpha'])
            polyc = ax.plot_trisurf(
                df['x'].values,
                df['y'].values,
                df['z'].values,
                facecolors=fill if any(fill) else 'none',
                edgecolors=df['color'] if any(df['color']) else 'none',
                linestyles=df['linetype'],
                linewidths=df['size'],
                zorder=params['zorder'],
                rasterized=params['raster'],
            )
            # workaround for https://github.com/matplotlib/matplotlib/issues/9535
            if len(set(fill)) == 1:
                polyc.set_facecolors(fill[0])

对于交互性,您可以使用您喜欢的任何 matplotlib 后端,我选择了 ipymplpip install ipympl,然后在 jupyter notebook 单元格中使用 %matplotlib widget)。

注意事项是:

编辑:如果数据集变得不可用,这里有一个基于 matplotlib 文档的 self-contained 示例:

import numpy as np

n_radii = 8
n_angles = 36

radii = np.linspace(0.125, 1.0, n_radii)
angles = np.linspace(0, 2*np.pi, n_angles, endpoint=False)[..., np.newaxis]

x = np.append(0, (radii*np.cos(angles)).flatten())
y = np.append(0, (radii*np.sin(angles)).flatten())

z = np.sin(-x*y)
df = pd.DataFrame(dict(x=x,y=y,z=z))

(
    ggplot_3d(df)
    + aes(x='x', y='y', z='z')
    + geom_polygon_3d(size=0.01)
    + theme_minimal()
)