カテゴリー

当サイトはアフィリエイトプログラムによる収益を得ています〈景品表示法に基づく表記です)

go

【plotly&レーダーチャート】plotlyのRadar Chartの使い方とか設定とか

2021年7月29日

こんな人にオススメ

pythonのplotlyでレーダーチャートって描ける?描けるんならどうやって描くんだい?

ということで、今回はpythonのplotlyを使用してレーダーチャートを作成する。レーダーチャートっていうのはなんかの評価とかに使う、中心が最低点で外枠が最高点となる円もしくは多角形のやつ。下の図みたいなやつ。

ふとこれって作れるんかなと思ったので調べてここでまとめる。plotly自体にレーダーチャート専用の関数が用意されているわけではなく、既存の関数を流用する感じ。バブルチャートと同じ。

bubblechart2017_pop
【plotly&バブルチャート】plotlyで各国の収入と平均寿命をバブルチャートで描く

続きを見る

python環境は以下。

  • Python 3.9.4
  • pandas 1.2.4
  • plotly 4.14.3

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

運営者メガネ

下準備

import sys
import pandas as pd
import plotly.graph_objects as go
import plotly.io as pio

sys.path.append('../../')
import plotly_layout_template as template

まずは下準備のimport関連。今回は各項目でデータを作成するので一括での作成はしない。plotly_layout_templateは自作のplotlyのテンプレートを入れた関数が入ってるファイル。2つ上のディレクトリにあるので'../../'

レーダーチャートを作成

まずはシンプルにレーダーチャートを作成する。チャートの色は変えているが、基本は上の図のように白地に水色っぽい灰色っぽい色のチャートができる。

シンプルに作成すると途切れる

def radar_chart(r: list, theta: list, save_name: str, fill='none'):
    """チャート部分の色を変更したレーダーチャートを作成

    Parameters
    ----------
    r : list
        各項目の値を入れたlist
    theta : list
        各項目名(軸名)を入れたlist
    save_name : str
        ファイル保存時の追加ファイル名
    fill : str, optional
        チャートを塗りつぶすか否か, by default 'none'
    """

    plot = []
    d = go.Scatterpolar(
        r=r,  # レーダーチャートの各データ
        theta=theta,  # レーダーチャートの各項目名
        fill=fill,  # 塗りつぶしするか否か
        line=dict(color='mediumvioletred'),  # 各プロット点の線の色
        marker=dict(color='black'),  # 各プロット点の線の色
        fillcolor='rgba(106, 90, 205, 0.2)'  # 塗りつぶしの色
    )
    plot.append(d)

    layout = go.Layout(
        title='title',  # レーダーチャートのグラフ全体のタイトル
        xaxis=dict(title='x'),  # 指定しても表示なし
        yaxis=dict(title='y'),  # 指定しても表示なし
    )

    fig = go.Figure(data=plot, layout=layout)
    fig.show()

    file = f"radar_chart_{save_name}"
    pio.write_html(fig, f"{file}.html")
    pio.write_image(fig, f"{file}.png")

ということでレーダーチャートを作成するが、使用する関数はgo.Scatterpolarで極座標系のグラフを描くときに使う関数。すなわち、角度theta°に中心からr離れた部分にプロットすることでレーダーチャートを作成するということ。

プロットに必要なのは引数はrthetaで配列で渡して各値と各項目名とする。fillでチャートを塗りつぶしするかどうかを決められる。後のline, marker, fillcolorで見た目を変更している。

r, thetaを設定して実際にプロットすると以下。なお、x軸、y軸という概念がないので軸ラベルは設定してもつかない。

# データが軸の数ピッタリだと最初のデータと最後のデータが繋がらない
r = [1, 2, 3, 4, 5]
theta = ['data1', 'data2', 'data3', 'data4', 'data5']

radar_chart(
    r=r,
    theta=theta,
    save_name='gap',
    fill='none',  # 塗りつぶしなし=線だけ
)

fill'none'にしているので塗りつぶしはなくて線のみ。で、初めと終わりのデータがくっついていないことがわかるだろう。Referenceを見てもgoだと途切れているチャートしかなくて困惑した。途切れているのは気にしないのか。

最後の値と初めの値を同じにする

# 初めの値に戻るように設定
r = [1, 2, 3, 4, 5, 1]
theta = ['data1', 'data2', 'data3', 'data4', 'data5', 'data1']

最初のデータと最後のデータを一致させることでデータの途切れを解消することができる。このデータを使ってチャートを作成したら以下。

radar_chart(
    r=r,
    theta=theta,
    save_name='toself',
    fill='toself',  # 自分自身を塗りつぶし=範囲内を塗りつぶし
)

radar_chart(
    r=r,
    theta=theta,
    save_name='none',
    fill='none',
)

データの数と軸の数が異なる

さっきのチャートだとデータの数と軸の数が同じだった。ここではデータと軸の数が異なる時にはどのような挙動になるのかを検証。

軸の数の方が多い

# 軸の数の方が多い場合
# データの数に合わせられる
r = [1, 2, 3, 4, 5]
theta = ['data1', 'data2', 'data3', 'data4', 'data5', 'data6']

radar_chart(
    r=r,
    theta=theta,
    save_name='many_axis',
    fill='toself',
)

まずは軸の数の方が多い場合。上のコードではデータrの数が5つなのに対し、軸thetaの数は6つで軸の方が多い。この場合は数の少ないデータの方にチャートは合わせられる。すなわち軸が6ではなく5となる。


データの数の方が多い

# データの数の方が多い場合
# 軸の数に合わせられる
r = [1, 2, 3, 4, 5]
theta = ['data1', 'data2', 'data3', 'data4']

radar_chart(
    r=r,
    theta=theta,
    save_name='many_data',
    fill='toself',
)

じゃあ反対にデータの数の方が多い場合はどうだろうか。この場合も少ない方に合わせられる。ここでは軸の数が4つなのでデータ数も初めから4爪までが使用される。


同じ軸名がある場合

検証はまだ続く。ここでは軸名が同じものがある場合にはどうなるのかを検証。上の例を見ればわかるように、ダブる。

同じ軸名でも構わずプロット

r = [1, 2, 3, 4, 5]
# data1がかぶっている場合
theta = ['data1', 'data1', 'data3', 'data4', 'data5']

radar_chart(
    r=r,
    theta=theta,
    save_name='same_name1',
    fill='toself',
)

このコードだと'data1'が初めに2つダブっている状況。これでチャートを作成すると、r = 1, r = 2は同じ'data1'にプロットされる。要するに勝手に軸を追加とかはないということ。


離れるとクロスするかも

r = [1, 2, 3, 4, 5]
# data1が離れているとクロスしてしまう
theta = ['data1', 'data2', 'data3', 'data1', 'data5']

radar_chart(
    r=r,
    theta=theta,
    save_name='same_name2',
    fill='toself',
)

同じ名称の軸が離れていると、途中で軸に戻るのでチャートがクロスすることになる。なるほど、勝手に軸は追加されない。


同じデータの場合は考えない

軸名がダブっている場合は考えたけど、同じデータの場合はどうなん。となるが、この場合は単純にプロットされるだけ。別に星4.5の評価がいくらあってもいい。高評価なんていくつあってもいいですからね。

プロット数が1, 2, 3データの違い

腐るほど検証しているとけど気になるから許して。先ほどまではプロット数がr = 1, 2, 3, 4, 5の5つがベースだった。ではプロット数が1の時や2の時はどうなるのか。

プロット数が1の時は点、2の時は線、3からは面

# 1の時は点、2の時は線、3から面となる
r = []
theta = []
for num in range(1, 3 + 1):
    r += [num]
    theta += [f"data{num}"]

    radar_chart(
        r=r,
        theta=theta,
        save_name=f"{num}lines",
        fill='toself',
    )

1種類ずつ試すのが面倒だったのでforで簡略化。listを空で作成し、このlistにループごとに要素を足していくことでチャートを作成。

結果、プロット数が1の時は点、2の時は線、3の時は面となった。当たり前。3以降は面ができるので全部面。




軸名を色々な型にしてみる


ここでは軸に指定する型を色々と試してみる。無駄だとは思うが気になるんだもん。使用する関数は以下。

def radar_chart_complex(fill: str, save_name: str, **kwargs):

    plot = []

    keys = list(kwargs.keys())
    values = list(kwargs.values())
    d = go.Scatterpolar(
        r=keys,
        theta=values,
        fill=fill,
    )
    plot.append(d)

    layout = go.Layout(
        title='title',  # レーダーチャートのグラフ全体のタイトル
    )

    fig = go.Figure(data=plot, layout=layout)
    fig.show()

    file = f"radar_chart_{save_name}_{fill}"
    pio.write_html(fig, f"{file}.html")
    pio.write_image(fig, f"{file}.png")

引数を可変長引数**kwargsにして、値と軸名を辞書型で入れるという戦法。可変長引数については以下参照。

【python&関数化】defとかargsとかを使って関数を作成する

続きを見る

今回使用する軸の型は以下。なかなかヤバいメンツ。でも$\LaTeX$は表示されない。

# アルファベット、漢字、True、list、None、
# 改行・タブありの文字列、NaN、空のdict、
# LaTeX、LaTeX、
# False、HTMLコード、Unicode
r_theta = {
    '1': 'A', '2': '十', '3': True, '4': [1, 2, 3], '5': None,
    '6': 'aa\\nbb\\tc', '7': float('nan'), '8': {},
    '9': r'$$\\sum_{n=2}^{5}x$$', '10': r'$\\sum_{n=2}^{5}x$',
    '11': False, '12': '🤔', '13': u'\\u1F914',
}

radar_chart_complex関数とr_thetaよりレーダーチャートを作成。線のみのバージョンは以下。この章の初めのチャートは以下のグラフは塗りつぶしバージョンなので、線バージョンの匹数指定のfillを'toself'にすればいい。

# 線バージョン
radar_chart_complex(
    fill='none',
    save_name='complex',
    **r_theta,
)

できたグラフを見る限り、以下の型では軸が表示されないことがわかる。Falseはセーフなのは意外。あとUnicode入力がバグ(?)った。

  • None
  • NaN

NoneNaNを軸名に使用するときは非表示になるということを忘れずに。

複数データのレーダーチャート

ここまでは1つのデータのみを使用してレーダーチャートを作成した。ここからは複数のデータを同時にレーダーチャートに描く方法とその時の挙動について書いていく。

基本的には今までのgo.Scatterと同じように、関数に必要なデータを入れてlistに格納すればいい。

2データ

def radar_chart_args(save_name: str, opacity: float, **kwargs):

    plot = []
    for color, r in kwargs.items():
        d = go.Scatterpolar(
            r=r,
            theta=['data1', 'data2', 'data3', 'data4', 'data5'],
            fill='toself',
            fillcolor=color,
            line_color=color,
            opacity=opacity,
        )
        plot.append(d)

    layout = go.Layout(
        title='title',
    )
    fig = go.Figure(data=plot, layout=layout)
    fig.show()

    file = f"radar_chart_{save_name}"
    pio.write_html(fig, f"{file}.html")
    pio.write_image(fig, f"{file}.png")

使用するコードは上。可変長引数で色とデータを同時に取得、チャート化するという方法。可変長引数のkeysに色、valuesにデータを入れて使用する。

# 2データ
dct2 = {'violet': [4, 5, 3, 5, 5], 'deepskyblue': [3, 3, 4, 1, 5],}
radar_chart_args(
    **dct2,
    save_name='args_2data_1',
    opacity=None,
)

しかし、透明度をNoneにしてしまうと完全に塗りつぶされてしまい下側のチャートの内容がわからなくなる。透明度opacityを1より小さくすることで下のチャートを見えるようにできる。

radar_chart_args(
    **dct2,
    save_name='args_2data_2',
    opacity=0.5,
)

3データ

# 3データ
dct3 = {
    'violet': [4, 5, 3, 5, 5],
    'deepskyblue': [3, 3, 4, 1, 5],
    'rgba(143, 188, 143, 0.8)': [5, 2, 3, 4, 5],
}
radar_chart_args(
    **dct3,
    save_name='args_3data_1',
    opacity=0.4,
)

3データの時も同様、データ数が増えるだけ。ただし今回はrgbaの色指定も行ってみた。rgbaaが透明度alphaを示すんだが、この値とopacityが同時に指定されている場合は値が小さい方が優先されて指定されるようだ。

今回の例ではopacityの方が値が小さいので緑系のチャートにはopacity0.4が適用されているようだ。他のvioletdeepskyblue0.4の透明度が適用されている。


一方で、opacityの方が値が高い場合はviolet, deepskyblueにはopacityの値が適用される一方で、rgba指定した緑系のチャートは0.4のままのようだ。

dct3 = {
    'violet': [4, 5, 3, 5, 5],
    'deepskyblue': [3, 3, 4, 1, 5],
    'rgba(143, 188, 143, 0.4)': [5, 2, 3, 4, 5],
}
radar_chart_args(
    **dct3,
    save_name='args_3data_2',
    opacity=0.8,
)

レイアウトを調節する

ここまででレーダーチャートの基本的な挙動については終わった。長かった。ここではレーダーチャートのレイアウトを色々と編集してみる。

template'none'にするだけでレイアウトリセット

def radar_chart_layout(*args, theta: list, save_name: str,
                       showlegend=True, polar_bgcolor='rgba(0, 0, 0, 0)',
                       radialaxis_visible=True, radialaxis_showline=True,
                       radialaxis_showgrid=True, radialaxis_size=None,
                       radialaxis_gridcolor='black', angularaxis_size=None,
                       angularaxis_linecolor='black',
                       angularaxis_gridcolor='black',
                       gridshape='circular',
                       paper_bgcolor='rgb(255, 255, 255)',):

    plot = []
    for r in args:
        d = go.Scatterpolar(
            r=r,
            theta=theta,
            fill='toself',
        )
        plot.append(d)

    layout = go.Layout(
        template='none',  # テンプレートをオフに
        title='title',  # レーダーチャートのグラフ全体のタイトル
        showlegend=showlegend,  # 凡例を表示するか否か
        polar=dict(
            bgcolor=polar_bgcolor,  # レーダーチャートの背景色
            radialaxis=dict(  # 軸目盛関係
                visible=radialaxis_visible,  # 目盛、目盛線、グリッドを表示するか否か
                showline=radialaxis_showline,  # 目盛線を表示するか否か
                showgrid=radialaxis_showgrid,  # グリッドを表示するか否か
                tickfont=dict(
                    size=radialaxis_size,  # 目盛のフォントサイズ
                ),
                gridcolor=radialaxis_gridcolor,  # グリッドの色を黒に
            ),
            angularaxis=dict(  # レーダーチャートの各項目関係
                tickfont=dict(
                    size=angularaxis_size,  # 項目のフォントサイズ
                ),
                linecolor=angularaxis_linecolor,  # チャート部分の枠線の色
                gridcolor=angularaxis_gridcolor,  # 各項目から中心に向かう線の色

            ),
            # circularで円形、linearで多角形のレーダーチャート
            gridshape=gridshape,
        ),
        paper_bgcolor=paper_bgcolor,  # グラフ全体の背景色
    )
    fig = go.Figure(data=plot, layout=layout)
    fig.show()

    file = f"radar_chart_{save_name}"
    pio.write_html(fig, f"{file}.html")
    pio.write_image(fig, f"{file}.png")

いきなり超ロングなコードだが、このコードに書いてある項目が結構使いそうなレイアウトの設定なのかなと思う。それぞれの設定を関数の引数にしているので自分で指定可能。

シンプルに必要なデータだけ入力するとシンプルなレーダーチャートが出来上がる。これはgo.Layoutの引数template'none'にしたから。こうすることでレイアウトがリセットされるようだ。

r1 = [1, 2, 3, 4, 5]
theta = ['data1', 'data2', 'data3', 'data4', 'data5']

radar_chart_layout(
    r1, theta=theta, save_name='layout0',
)

また、背景を灰色にしてチャートを描くとチャート部分まで灰色になっている。すなわちチャート部分の色が無色透明になっているということ。そうなのか。

radar_chart_layout(
    r1, theta=theta, save_name='layout1',
    paper_bgcolor='lightgray',
)

グリッドを消したり色をいじったり


グリッド線を消したりチャートの色をいじったりすると上のようにドギツイチャートができる。まあこれは変に改造したものだけど、こんなことも一応できる。

radar_chart_layout(
    r1, theta=theta, save_name='layout2',
    polar_bgcolor='violet',
    radialaxis_visible=False,  # Falseに
    radialaxis_showline=False,  # Falseに
    radialaxis_showgrid=False,  # Falseに
    angularaxis_linecolor='blue',  # blueに
    angularaxis_gridcolor='green',  # greenに
    gridshape='linear',  # linearに
    paper_bgcolor='lightgray',  # lightgrayに
)

キレイ目なレーダーチャート


一方でうまく引数の設定をしてあげるとかなりキレイなレーダーチャートが出来上がる。背景を無色透明にしたりグリッドをシルバーにしたり5角形にしたりすればかなり体裁としては整っているように見える。

r2 = [3, 2, 2, 5, 4]
radar_chart_layout(
    r1, r2, theta=theta, save_name='layout3',
    polar_bgcolor='rgba(0, 0, 0, 0)',
    radialaxis_size=20,  # 20に
    radialaxis_gridcolor='silver',  # silverに
    angularaxis_size=20,  # 20に
    angularaxis_linecolor='black',  # blackに
    angularaxis_gridcolor='silver',  # silverに
    gridshape='linear',  # linearに
    paper_bgcolor='rgba(0, 0, 0, 0)',  # rgba(0, 0, 0, 0)に
)

自作テンプレートを適用


以下の記事でplotlyのテンプレートを紹介したが、このテンプレートを使用してガッツリキレイなレーダーチャートを作成してみた。内容もリアリティのある成績関連にした。要するに今回の集大成。

テンプレート適用後のplotlyでのグラフ
【plotlyテンプレート】plotlyのグラフ作成テンプレート

続きを見る

def radar_chart_template(df, save_name: str):

    plot = []
    for num, name in enumerate(df):
        color = ['violet', 'deepskyblue'][num]

        d = go.Scatterpolar(
            r=df[name],
            name=name,
            theta=df.index,
            fill='toself',
            fillcolor=color,  # 塗りつぶし色を設定
            line_color=color,  # 線の色を設定
            opacity=0.2,
            hovertemplate=f"{name}<br>"
            + '%{r} 点/100点<br>'
            + '%{theta}<br>'
            + "<extra></extra>",
        )
        plot.append(d)

    layout = go.Layout(
        template=template.plotly_layout(),
        title=dict(text='成績', x=0.5, xanchor='center'),
        polar=dict(gridshape='linear',),
        radialaxis=dict(range=(0, 100),),
        polar_bgcolor='rgba(0, 0, 0, 0)',
    )
    fig = go.Figure(data=plot, layout=layout)
    fig.show(config=template.plotly_config())

    file = f"radar_chart_{save_name}"
    pio.write_html(fig, f"{file}.html", config=template.plotly_config())
    pio.write_image(fig, f"{file}.png")

使用するデータは以下のように架空の2つの成績。科目は突発的に決めた。

score1 = {
    '国語A': 80,
    '国語B': 90,
    '数学I': 93,
    '数学II': 95,
    '英語1': 70,
    '英語2': 85,
    '物理': 79,
    '化学': 91,
    '地理': 71,
}

score2 = {
    '国語A': 60,
    '国語B': 100,
    '数学I': 30,
    '数学II': 98,
    '英語1': 88,
    '英語2': 76,
    '物理': 78,
    '化学': 100,
    '地理': 60,
}

これらのdictpandasに入れて整理して関数に入れると完成。キレイにできたと思う。

df = pd.DataFrame([score1, score2], index=['学生1', '学生2']).T
print(df)
#       学生1  学生2
# 国語A    80   60
# 国語B    90  100
# 数学I    93   30
# 数学II   95   98
# 英語1    70   88
# 英語2    85   76
# 物理     79   78
# 化学     91  100
# 地理     71   60

radar_chart_template(df, save_name='score')

その他のレイアウトの引数で使えそうなもの

polar = dict(
    sector=[0, 270],  # 0 degから270 degだけ表示する(270 < deg < 360は非表示)
    hole=0.5,  # 中心から50%の部分を無効にし、50%以降の範囲でレーダーチャート作成
    radialaxis=dict(  # 軸目盛関係
        range=[0, 5],  # チャートの表示範囲を0から5に変更
        angle=10,  # 目盛を1つ目のデータから反時計方向へ10 deg回転させる
        # counterclockwiseで目盛を1, 2つ目のデータの間に記載
        # clockwise1つ目、最後のデータの間に記載(初期はclockwise)
        side='counterclockwise',
        title='axis title',  # 目盛のタイトル
        linecolor='red',  # 目盛線の色
        ticks='inside',  # 目盛を1, 2つ目のデータの間に記載(outsideだと1つ目、最後間)
        tickcolor='green',  # 目盛の色
        color='blue',  # 目盛フォント、グリッドとかの色を一括指定
    ),
    angularaxis=dict(  # レーダーチャートの各項目関係
        # counterclockwiseで真右が1つ目のデータで反時計回り
        # clockwiseで真上が1つ目のデータで時計回り
        direction='counterclockwise',
        ticks='outside',  # 項目部分の目盛の向き
        tickfont=dict(
            color='red',  # 項目のフォントカラーを赤に
        ),
    ),
)

上にまあまあ使えるかなレベルの引数を示しておく。今回は使用しなかったけどいつか使えるだろう的なもの。参考までに。

これって結構使える?

今回はplotlyを使用してレーダーチャートなるものを作成した。今までレーダーチャートなんてExcelでも作成したことがなかったから今回作成して新鮮。思ったのがイヤホンのレビュー時にこのレーダーチャートを載せてもいいのかなってこと。

このブログはガジェットとpythonがメインだからガジェット好きの人が多少でもpythonに興味を持ってくれればということで考えている。やってみるか。

関連記事

【python&関数化】defとかargsとかを使って関数を作成する

続きを見る

bubblechart2017_pop
【plotly&バブルチャート】plotlyで各国の収入と平均寿命をバブルチャートで描く

続きを見る

https://www.sony.jp/headphone/products/WF-1000XM4/ より引用
【WF-1000XM4】SONY最新イヤホンを比較して悩む

続きを見る

【Jabra Elite 75tレビュー】今でも十分戦える高性能イヤホン

続きを見る

ガジェット

2023/11/11

【デスクツアー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/11/4

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

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

ベストバイ

2023/10/29

【ベストバイ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(31万円) x Galaxy Z Fold5(25万円)使いの狂人。自己紹介と半生→変わって楽しいの繰り返しレビュー依頼など→お問い合わせ運営者情報、TwitterX@m_ten_pa、 YouTube@megatenpa、 Threads@megatenpa、 Instagram@megatenpa

-go
-, ,