カテゴリー

Pythonの発展系

【python&3Dピラミッド】plotlyで3次元ピラミッドを作成

2021年11月18日

こんな人にオススメ

*」を使った2次元の簡易的なピラミッドの作成方法は結構出ているけど、3次元のピラミッドの作成方法はあまり見つからない。

もし作成したいならどういうふうに作成できる?

ということで、今回はplotlyを使用して3次元のピラミッドを作成してみる。pythonでピラミッドを検索すると大抵は「*」を使用した初心者用コードが引っかかる。

でもそれはあくまでも2次元であり、forなどの練習用のピラミッド。今回は見た目を完全にピラミッドに似せるように作成する。

なお、今回は数式などを使用せずにサクッとしたピラミッドとする。もちろん数式から作成してもいいけど、数式を解読するのが面倒なのでパス。

python環境は以下。

  • Python 3.9.7
  • numpy 1.21.3
  • plotly 5.3.1
  • plotly-orca 3.4.2

運営者のメガネです。YouTubeTwitterInstagramも運営中。自己紹介お問い合わせページあります。

運営者メガネ

作成したコード全文

下準備

import numpy as np
import plotly.graph_objects as go
import plotly.io as pio

まずは下準備としてのimport関連。今回はplotlyのSurfaceプロットでグラフ化する。しかし、plotly.express、すなわちpxではSurfaceプロットがないのでgoを使用。

Surfaceにすることで滑らかな面としてプロットすることが可能。もちろん散布図としてプロットすることも可能。plotlyの3次元グラフについては以下参照。

【plotly&3D】goで3Dグラフを作成

続きを見る

【plotly&3D】px.scatter_3dとpx.line_3Dで3Dグラフを作成

続きを見る

グラフ表示・保存用の関数を定義

# グラフ保存用の関数
def show_save(fig, config, save_name):
    fig.show()
    pio.orca.config.executable = '/Applications/orca.app/Contents/MacOS/orca'
    pio.write_html(fig, f"{save_name}.html", config=config,)
    pio.write_image(fig, f"{save_name}.png")

plotlyのグラフを表示・グラフを保存するための関数を予め定義しておく。この関数を使用するだけで簡単にグラフの表示と保存が可能。

関数を予め定義しておくことで、処理の一括管理が可能だったり複数箇所で同じコードを書く時に関数だけ書けば良くなる。関数については以下参照。

【plotly&工夫】楽にグラフを描くためのplotlyの関数化

続きを見る

お試しで作成


まずはお試しでピラミッドを作成。plotlyで3次元Surfaceプロットをするには1次元のx, yと2次元のzの配列が必要。

今回はシンプルにベースをz=0にして1段ごとに+1されるようなピラミッドを作成。上のグラフだと3段。

あとはx, y, zgo.Surfaceに入れ、ちょっとだけ透明にしてグラフを作成した。

x = (-2, -1, 0, 1, 2)
y = (-2, -1, 0, 1, 2)
z = np.array(
    [
        [0, 0, 0, 0, 0],
        [0, 1, 1, 1, 0],
        [0, 1, 2, 1, 0],
        [0, 1, 1, 1, 0],
        [0, 0, 0, 0, 0],
    ]
)

d = go.Surface(
    x=x, y=y, z=z,
    opacity=0.9,  # 透明度
)
fig = go.Figure(data=[d])
save_name = 'plotly-3d-pyramid_yramid_trial'
show_save(fig=fig, config=None, save_name=save_name)

任意の段数のピラミッド配列を作成

先程のグラフを土台としてあとはデータ数を増やせばより滑らかなピラミッドになるけど、さっきのようにいちいちzの2次元配列を作成するのは面倒。

ということで、辺の長さを指定すると自動でピラミッドの配列を作成できるような仕組みを作る。作成手順は以下。

  1. ピラミッドの頂点を含む1/4の部分をベースとする
  2. このベースを1行ずつ作成する(STEP1)
  3. ベースを行反転させて頂点部分の1行を削除した配列を下に結合(STEP2)
  4. 結合後の配列を列反転させて頂点部分の1列を削除した配列を右に結合(STEP3)
  5. 完成

STEP1がかなり面倒になるけどそこを乗り切れば結合はすぐにできる。

ベースの配列を作成

length = 5  # ピラミッドの1辺の長さ
nums = length // 2 + 1  # ピラミッドの値の種類(長さが偶数の時は辺+1したピラミッドを作成)

rows = []  # ベースとなる配列の各行の値を入れる2次元配列
for row in range(nums):  # 1行ごとに見る
    n = 0  # 各行の各列の値
    cols = []  # row行目の値を入れる
    for col in range(nums):  # row行目の各列
        cols.append(n)  # 一番左は0
        if row:  # 0行目の時はここはFalse、それ以外の行目ならTrue
            n += 1  # 各列の値は1ずつ増えていく
            row -= 1  # あと何回、列の値が変わるかに相当
    rows.append(cols)  # row行目の配列を格納

ピラミッドには頂点が必要なので、頂点ができない辺の長さが偶数の場合は変数numsで自動で奇数になるように調節している。lengthを4にするとnumsは3となる。

初めの0行目は全て0で、次の行では1列目で+1、2行目では1, 2列目でそれぞれ+1されるので各行rowが0かそれ以外かをifで判断しつつ列の値nを増やしている。

それと同時に1行目では2列目は1のままなのに対して2行目では2列目は+1されて2となるので、rowを無理矢理減らして1行ずつピラミッド構造になるようにした。

これでSTEP1のベースとなる配列は完成。

ベースを結合

arr = np.array(rows)  # 2次元配列の変換
print(arr, '\\n')
# [[0 0 0]
#  [0 1 1]
#  [0 1 2]]

ベースとなる配列ができたので、これを結合していく。STEP2ではベースの下に配列を結合するので下用の配列を作成する。

配列を[::-1]で行方向に反転し、頂点を含む0行目は使用しないように[1:]で1行目以降を抽出した。

あとはベースの下に結合すればいい。今回はnumpynp.vstackで結合した。

bottom = arr[::-1][1:]  # ベース下用の配列
print(bottom, '\\n')
# [[0 1 1]
#  [0 0 0]]

arr2 = np.vstack([arr, bottom])  # ベース下に配列を結合
print(arr2, '\\n')
# [[0 0 0]
#  [0 1 1]
#  [0 1 2]
#  [0 1 1]
#  [0 0 0]]

配列の右に結合するときも同じようにできる。今度は左右方向なので[:, ::-1]で列の反転をする。また、左右方向への結合はnp.hstackを使用した。

left = arr2[:, ::-1][:, 1:]  # ベース+下の右用の配列
print(left, '\\n')
# [[0 0]
#  [1 0]
#  [1 0]
#  [1 0]
#  [0 0]]

arr3 = np.hstack([arr2, left])  # ベース+下の右に配列を結合
print(arr3, '\\n')
# [[0 0 0 0 0]
#  [0 1 1 1 0]
#  [0 1 2 1 0]
#  [0 1 1 1 0]
#  [0 0 0 0 0]]

これでピラミッドが完成する。

関数化して辺の値だけでピラミッド作成

def make_arr(length):
    # ピラミッドの値の種類(長さが偶数の時は辺+1したピラミッドを作成)
    nums = length // 2 + 1

    rows = []  # ベースとなる配列の各行の値を入れる2次元配列
    for row in range(nums):  # 1行ごとに見る
        n = 0  # 各行の各列の値
        cols = []  # row行目の値を入れる
        for col in range(nums):  # row行目の各列
            cols.append(n)  # 一番左は0
            if row:  # 0行目の時はここはFalse、それ以外の行目ならTrue
                n += 1  # 各列の値は1ずつ増えていく
                row -= 1  # あと何回、列の値が変わるかに相当
        rows.append(cols)  # row行目の配列を格納

    arr = np.array(rows)  # 2次元配列の変換
    bottom = arr[::-1][1:]  # ベース下用の配列
    arr2 = np.vstack([arr, bottom])  # ベース下に配列を結合
    left = arr2[:, ::-1][:, 1:]  # ベース+下の左用の配列
    arr3 = np.hstack([arr2, left])  # ベース+下の左に配列を結合

    return arr3

さっきの処理をいちいち書いてもいいけどこれは面倒。ということで、作成したいピラミッドの辺の値を入力するだけで自動でピラミッド配列を作成してくれる関数を作成。

中身はさっきの処理をそのまま書いた。関数化することで以下のように複数回使用するときにスッキリした見た目になる。

pyramid = make_arr(length=5)
print(pyramid)
# [[0 0 0 0 0]
#  [0 1 1 1 0]
#  [0 1 2 1 0]
#  [0 1 1 1 0]
#  [0 0 0 0 0]]

pyramid = make_arr(length=9)
print(pyramid)
# [[0 0 0 0 0 0 0 0 0]
#  [0 1 1 1 1 1 1 1 0]
#  [0 1 2 2 2 2 2 1 0]
#  [0 1 2 3 3 3 2 1 0]
#  [0 1 2 3 4 3 2 1 0]
#  [0 1 2 3 3 3 2 1 0]
#  [0 1 2 2 2 2 2 1 0]
#  [0 1 1 1 1 1 1 1 0]
#  [0 0 0 0 0 0 0 0 0]]

3Dピラミッドをグラフ化


ということで、最大の難関であったピラミッドの配列をクリアできたのでグラフ化する。なお、横軸・縦軸の配列であるx, yはピラミッドの辺の長さからその半分の値を引いて中心を0となるように設定。

また、レイアウトもタイトルの位置やカラーバーの位置を調節した。色々設定できるけど、ややこしくなるのでシンプルにしておいた。

なお、ピラミッドの辺の数は101とした。かなり滑らか。

def make_pyramid(length):
    pyramid = make_arr(length=length)  # ピラミッド作成

    base = np.array(range(len(pyramid)))  # ベースとなる配列
    x = base - length // 2  # 配列の真ん中を0にする
    y = base - length // 2  # yも同様

    # ピラミッドをプロット
    d = go.Surface(
        x=x, y=y, z=pyramid,
        opacity=0.9,
        colorbar=dict(x=0.8)
    )

    # グラフ化
    fig = go.Figure(
        data=[d],
        # レイアウトの調節
        layout=go.Layout(
            title=dict(text=f"length={length}", x=0.5, xanchor='center'),
        ),
    )

    save_name = f"plotly-3d-pyramid_pyramid{length}"
    show_save(fig=fig, config=None, save_name=save_name)

make_pyramid(length=101)

数式がなくてもできる

今回はplotlyを使って3次元のピラミッドをグラフ化する方法を解説した。もちろん今回の方法以外にもアプローチの仕方は多くあるだろう。

しかし、わざわざ数式をイジって作成しなくてもある程度のものができる。数式をイジるとなるとかなりハードルが上がるけど配列を作るだけなら簡単。

このピラミッドがどこで使用できるかは謎だけどこのノウハウはどこかで役立つかもしれない。

関連記事

【plotly&コッホ曲線】フラクタル図形の雪の結晶をスライダーで作成してみる

続きを見る

【plotly&Scattergeo】世界地図に各国の首都の位置をプロット

続きを見る

【plotly&円グラフ】世界のスマホ出荷台数をgo.Pieで円グラフ化

続きを見る

ガジェット

2023/9/18

【デスクツアー2022下半期】モノは少なく、でも効率的に Desk Updating #0

今回はガジェットブロガーなのにデスク環境を構築していない執筆者の ...

ライフハック

2023/9/16

【Audible vs YouTube Premium】耳で聴く音声学習コンテンツを比較

ワイヤレスイヤホンが普及し耳で学習することへのハードルが格段に下 ...

完全ワイヤレスイヤホン(TWS)

2023/9/18

【SENNHEISER MOMENTUM True Wireless 3レビュー】全てが整ったイヤホン

今回は高音質・高機能なSENNHEISERのフラグシップ完全ワイヤレスイヤホン「SENNH ...

ライフハック

2023/3/11

【YouTube Premiumとは】メリットしかないから全員入れ

今回はYouTube Premiumを実際に使ってみてどうなのか、どんなメリット/デメリット ...

マウス

2023/8/17

【Logicool MX ERGOレビュー】疲れない作業効率重視トラックボールマウス

こんな人におすすめ トラックボールマウスの王道Logicool MX ERGOが気になるけどऩ ...

ベストバイ

2023/9/18

【ベストバイ2022】今年買って良かったモノのトップ10

2022年ベストバイ この1年を振り返って執筆者は何を買ったのか。ガジェッ& ...

スマホ

2023/1/15

【楽天モバイル×povo2.0の併用】月1,000円の保険付きデュアルSIM運用

こんな人におすすめ 楽天モバイルとpovo2.0のデュアルSIM運用って実際のとこ ...

マウス

2023/9/16

【Logicool MX ERGO vs MX Master 3】ERGOをメインにした決定的な理由

こんな疑問・お悩みを持っている人におすすめ 執筆者はLogicoolのハイエンӠ ...

macOSアプリケーション

2022/9/30

【Chrome拡張機能】便利で効率的に作業できるおすすめの拡張機能を18個紹介する

こんな人におすすめ Chromeの拡張機能を入れたいけど、調べても同じような ...

macOSアプリケーション

2023/5/3

【Automator活用術】Macで生産性を上げる作業の自動化術

今回はMacに標準でインストールされているアプリ「Automator」を使ってできる ...

Pythonを学びたいけど独学できる時間なんてない人へのすゝめ

執筆者は大学の研究室・大学院にて独学でPythonを習得した。

でも社会人になったら独学で行うには時間も体力もなくて大変だ。

時間がない社会人だからこそプロの教えを乞うのが効率的。

ここでは色んなタイプに合ったプログラミングスクールの紹介をする。

  • この記事を書いた人

メガネ

Webエンジニア駆け出し。独学のPythonで天文学系の大学院を修了。常時金欠のガジェット好きでM2 Pro MacBook Pro(30万円) x Galaxy S22 Ultra(17万円)使いの狂人。自己紹介と半生→変わって楽しいの繰り返しレビュー依頼など→お問い合わせ運営者情報、TwitterX@m_ten_pa、 YouTube@megatenpa、 Threads@megatenpa

-Pythonの発展系
-, ,