LoginSignup
5
4
記事投稿キャンペーン 「2024年!初アウトプットをしよう」

Seabornのオブジェクト・インターフェイスのガイド

Last updated at Posted at 2024-01-22

Seabornのオブジェクト・インターフェイスのガイド

seabornは、matplotlibをベースとしたグラフ描画ライブラリで、特に複雑な統計情報を視覚的に示すことに特化している。

バージョン0.12よりseaborn.objectsを用いた新しい描画方法が導入され、公式ドキュメントも整備されているが、特に日本語で書かれた解説記事はごくわずかであり、多くの場合表面的か、APIを全て翻訳した煩雑なものである。

この記事ではseaborn.objectsについて一通り学ぶことを目的とする。各クラス・メソッドに関して公式APIへのリンクを貼っているので、より細かい設定はそちらを参照。

0. はじめに

ライブラリのインポートを行おう。

インポート
import seaborn as sns
import seaborn.objects as so

本記事では説明のためにsns.load_dataset()から取得できる各種データフレームを用いる。例えば変数df_irissns.load_dataset("iris")に等しい。

Irisデータセットのロード
df_iris = sns.load_dataset("iris")
print(df_iris)
#      sepal_length  sepal_width  petal_length  petal_width    species
# 0             5.1          3.5           1.4          0.2     setosa
# 1             4.9          3.0           1.4          0.2     setosa
# 2             4.7          3.2           1.3          0.2     setosa
# 3             4.6          3.1           1.5          0.2     setosa
# 4             5.0          3.6           1.4          0.2     setosa
# ..            ...          ...           ...          ...        ...
# 145           6.7          3.0           5.2          2.3  virginica
# 146           6.3          2.5           5.0          1.9  virginica
# 147           6.5          3.0           5.2          2.0  virginica
# 148           6.2          3.4           5.4          2.3  virginica
# 149           5.9          3.0           5.1          1.8  virginica
#
# [150 rows x 5 columns]

1. Plot()

Plot()ではグラフの基礎設定、全レイヤーに共通する設定を行う。

まずx軸とy軸のデータを与える。サブグループ化する場合は続いてそれを指定する。

1.1. データの指定

x=引数に横軸のデータ、y=引数にy軸のデータを与える。それぞれのデータは一次元配列でなければならず、また互いに同じ長さでなければならない。

配列からデータを指定する例
so.Plot(x=[0, 1, 2, 3], y=[0, 2, 4, 8])

元データがデータフレーム(pandasでもpolarsでも)の列として格納されている場合、第一引数にデータフレームオブジェクトを与えることで、各引数は列名の文字列を使って指定することができる。

データフレームからデータを指定する例
so.Plot(df_iris, x="sepal_length", y="sepal_width")

# もちろんこれでも良い
so.Plot(x=df_iris["sepal_length"], y=df_iris["sepal_width"])

# こういうことをしても良い
so.Plot(df_iris, x="sepal_length", y=df_iris["sepal_width"])

この段階ではグラフの枠のみは描画されるが、マーカーは描画されない。

so.Plot(
    df_iris,
    x="sepal_length",
    y="sepal_width",
)

後に説明する.add()などでレイヤーを追加することで描画される。

so.Plot(
    df_iris,
    x="sepal_length",
    y="sepal_width",
).add(so.Dot())

1.2. プロパティ(マーカースタイル)によるサブグルーピング

以下に説明する各引数によって、マーカースタイルをサブグループによって異なるものにすることができる。これらはPlot()に与えると全てのレイヤーに共通となる。

プロパティの詳細はこちら

color=にデータを与えると、それに基づいて色分けがなされる。図右の凡例に注目して欲しい。(サンプルコードでは例を図示するため、後に説明する.add()を用いる。)

なお、縁の色だけが変わるedgecolor=や塗りの色だけが変わるfillcolor=もあるようだが、視覚的にわかりづらいため、使うことはほとんどないだろう。

colors=
plot = so.Plot(
    df_iris,
    x="sepal_length",
    y="sepal_width",
    color="species",  # ←
).add(so.Dot())

alpha=にデータを与えると透明度が変わる。

alpha=
plot = so.Plot(
    df_iris,
    x="sepal_length",
    y="sepal_width",
    alpha="species",  # ←
).add(so.Dot())

marker=にデータを与えると、ドットの種類が変わる。

marker=
plot = so.Plot(
    df_iris,
    x="sepal_length",
    y="sepal_width",
    marker="species",  # ←
).add(so.Dot())

pointsize=にデータを与えると、ドットの大きさが変わる。

pointsize=
plot = so.Plot(
    df_iris,
    x="sepal_length",
    y="sepal_width",
    pointsize="species",  # ←
).add(so.Dot())

linewidth=にデータを与えると、線の太さが変わる。

linewidth=
so.Plot(
    df_iris,
    x="sepal_length",
    y="sepal_width",
    linewidth="species",  # ←
).add(so.Line())

linestyle=にデータを与えると、線の種類が変わる。

linestyle=
so.Plot(
    df_iris,
    x="sepal_length",
    y="sepal_width",
    linestyle="species",  # ←
).add(so.Line())

1.3. Plotのメソッド

  • .add(): プロットを描画するレイヤーの追加。

    • 第一引数にマークオブジェクト(§2)を与える。
    • 任意で第二引数以降にスタッツオブジェクト(§3)とムーブオブジェクト(§4)を与える。
    • 通常は自動で決定されるが、任意でorient=引数にベースライン(一部のグラフの描画方法に影響する)をx軸かy軸にするかを指定することができる。日付時刻型や離散型・カテゴリー型が有る場合はそちらがベースラインと考えて良い。
  • .scale(): x/y軸やサブグルーピングのスケールの変更・詳細設定(§5

  • .facet(): サブプロットの設定(x/y軸が同一で、異なるサブグループのグラフを並べる場合)

  • .pair(): サブプロットの設定(x/y軸が異なる、同一データのグラフを並べる場合)

  • .share(): サブプロット時のx/y軸の共有に関する設定

  • .layout(): グラフレイアウトの設定

  • .label(): グラフのラベルの設定

  • .limit(): x/y軸の範囲の設定

  • .theme(): 視覚テーマに関する設定

2. プロットの種類

.add()メソッドの第一引数mark=には、マークオブジェクトと通称される、プロットの種類を与える。

2.1. 散布図 — so.Dot()

散布図(あるいはそれに類する、データを点で表現する図)はso.Dot()である。

散布図1: Dot
so.Plot(df_iris, x="sepal_length", y="sepal_width", color="species").add(so.Dot())

so.Dots()を使うこともできる。これはso.Dot()においてプロットの数が多すぎるために重なっていて見づらい場合に特に有用である。各プロパティの(デフォルトの)値が異なり、サイズがやや小さく、かつ半透明となっている。

散布図2: Dots
so.Plot(df_iris, x="sepal_length", y="sepal_width", color="species").add(so.Dots())

また、ドットを描画するso.Dot()に対して、ベースラインと平行な短い線を描画するso.Dash()もある。

散布図3: Dash
so.Plot(df_iris, x="sepal_length", y="sepal_width", color="species").add(so.Dash())

2.2. 折れ線グラフ — so.Path(), so.Line()

折れ線グラフの類はso.Path()またはso.Line()を用いる。

so.Path()は与えられたデータを順番に線で結ぶが、so.Line()はソートした状態で線が引かれる。

折れ線グラフ1: Path
# 折れ線は元の配列通り (x, y) = (1, 10) → (3, 20) → (2, 30) →  (4, 40) を通る
so.Plot(x=[1, 3, 2, 4], y=[10, 20, 30, 40]).add(so.Path())

折れ線グラフ2: Line
# 折れ線はx軸がソートされて (x, y) = (1, 10) → (2, 30) → (3, 20) →  (4, 40) を通る
so.Plot(x=[1, 3, 2, 4], y=[10, 20, 30, 40]).add(so.Line())

so.Line()においてソートされる軸は自動で決定されるが、.add()so.Plot()ではない)にorient="x"またはorient="y"を渡すことで制御できる。

ドットありの折れ線グラフは、①marker=引数を指定する、②so.Dot()のレイヤーと重ねる、の2つの方法がある。

折れ線グラフ3
# 1. `marker=`引数を指定する
so.Plot(
    df_flights, x="month", y="passengers", color=df_flights["year"].astype("string")
).add(so.Line(marker="o"))

# 2. `so.Dot()`のレイヤーと重ねる
so.Plot(
    df_flights, x="month", y="passengers", color=df_flights["year"].astype("string")
).add(so.Line()).add(so.Dot())

so.Dot()に対してデータ数が多い場合のためのso.Dots()があったように、so.Path(), so.Line()にも対応するso.Paths(), so.Lines()がある。

2.2.1. 折れ線グラフ+塗りつぶし — so.Area(), so.Band()

so.Area()は折れ線とベースラインで挟まれた領域を塗りつぶす。.add()orient="x"またはorient="y"を渡すことで制御できる。ここでorient=so.Line()と同様にソートされる軸も決定する。

折れ線グラフ4: Area
so.Plot(x=[1, 3, 2, 4], y=[10, 20, 30, 40]).add(so.Area())

# y軸ベースの Area (枠線無し) + Line の例
so.Plot(x=[1, 3, 2, 4], y=[10, 20, 30, 40]).add(so.Area(edgewidth=0), orient="y").add(
    so.Line(), orient="y"
)

so.Band()はいくつかの使い方がある(以下の文章はorient="x"の場合で説明する)。

①元データのx=配列が全ユニークではない場合、すなわちy→xが単射でない場合、すなわち1つのxに対して2つ以上のyの値が対応する場合、その最大yと最小yの間を塗りつぶす。

折れ線グラフ5: Band1
so.Plot(df_flights, x="month", y="passengers").add(so.Band())

Plot()y=ではなくymin=ymax=が与えられている場合、2つの折れ線グラフ(ymin=ymax=)の間を塗りつぶす。

折れ線グラフ5: Band2
so.Plot(
    x=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
    ymax=[0, 2, 4, 9, 16, 25, 36, 49, 64, 81, 100],
    ymin=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
).add(so.Band())

so.Est()を使って信頼区間の図示に用いる。§3.2参照。

2.3. 棒グラフ — so.Bar()

棒グラフの類はso.Bar()を用いる。

棒グラフ1: Bar
so.Plot(df_flights[df_flights["year"] == 1960], x="month", y="passengers").add(so.Bar())

棒の向きは自動で決定されるが、.add()so.Bar()ではない)にorient="x"(縦棒)またはorient="y"(横棒)を渡すことで制御できる。

so.Plot(x=[1, 3, 2, 4], y=[10, 20, 30, 40]).add(so.Bar(), orient="y")

so.Dot()に対してデータ数が多い場合のためのso.Dots()があったように、so.Bar()にも対応するso.Bars()がある。

2.4. so.Range()

so.Range()は、so.Band()の棒グラフ(というか線)版だが、実用的にはほぼso.Est()を用いたエラーバーやデータ範囲の図示にしか使われないと思われる。§3.2参照。

2.5. 文字 – so.Text

so.Text()は、散布図のドットの代わりに文字を描画する。color=引数で文字色、valign=/halign=引数で文字揃え(描画方向)を指定できる。

3. 統計処理

データを直接xy座標としてプロットするのではなく何らかの統計処理を通した形で表現するには、.add()メソッドの第二引数transforms=にスタッツオブジェクトと通称されるものを渡す。

3.1. 集計(平均・合計など) — so.Agg()

so.Agg()により、ベースラインと垂直方向のデータ(add.(orient="x")のときはy)を集計することができる。pandasでいうと、df.groupby(x列)[y列].agg()が行われると考えて良い。

第一変数func=で集計の種類を指定する。デフォルトは平均値func="mean"となっており、それ以外によく使うのは合計値func="sum"と中央値func="median"だろう。

例えば次のような時系列データを

複数の折れ線グラフ
(
    so.Plot(df_flights, x="month", y="passengers", color="year")
    .scale(color=so.Nominal())
    .add(so.Line())
)

月ごとに平均する。

集計1: Agg
(
    so.Plot(df_flights, x="month", y="passengers")
    .scale(color=so.Nominal())
    .add(so.Line(), so.Agg("mean"))  # ←
    .add(so.Band())
)

また、以下のような観察データを

複数の観察データ
so.Plot(df_iris, x="species", y="sepal_length").add(so.Dots(), so.Jitter())

種類ごとに平均する。

集計2: Agg
so.Plot(df_iris, x="species", y="sepal_length").add(so.Bar(), so.Agg("mean"))

temp.png

3.1.1. データの個数 — so.Count()

データ数をカウントする場合はso.Count()を用いる(so.Agg("count")を使っても良い)。so.Count()を使う場合はPlot()引数のx=またはy=を省略できる。

集計3: Count
so.Plot(df_iris, x="species").add(so.Bar(), so.Count()).label(y="Count")

3.1.2. パーセンタイル値 — so.Perc()

パーセンタイル値を計算する場合はso.Perc()を用いる。2つの指定方法がある。

①第一引数k=に0~100の数値のリストを与える。

集計4: Perc
(
    so.Plot(df_iris, x="species", y="sepal_length")
    .add(so.Dots(), so.Jitter())
    .add(so.Dash(), so.Perc([0, 50, 100]))  # = 最小値・中央値・最大値
)

②第一引数k=にパーセンタイルの個数を与える。

集計5: Perc
(
    so.Plot(df_iris, x="species", y="sepal_length")
    .add(so.Dots(), so.Jitter())
    .add(so.Dash(), so.Perc(5))  # = 0%, 25%, 50%, 75%, 100%
)

3.2. 信頼区間・標準誤差など — so.Est()

so.Est()は信頼区間・標準誤差等を計算する。so.Band()あるいはso.Range()と組み合わせて、データ範囲やエラーバー等を図示する場合に用いる。

errorbar=引数で計算方法を指定する。デフォルトでは("ci", 95)というタプルが指定されており、これはブートストラップ法による95%信頼区間を意味する。("pi", 数値)でパーセンタイル区間、("se", スケール)で標準偏差、("se", スケール)で標準誤差となる(数値・スケールは省略可能)。エラーバーに関する公式チュートリアルも参照。

信頼区間: Est
(
    so.Plot(df_flights, x="month", y="passengers")
    .add(so.Line(), so.Agg("mean"))
    .add(so.Band(), so.Est())
)

temp.png

エラーバー: Est
(
    so.Plot(df_iris, x="species", y="sepal_length")
    .add(so.Dot(), so.Agg("median"))
    .add(so.Range(linestyle=":"), so.Est(errorbar=("pi", 95)))  # 95%tile区間(点線)
    .add(so.Range(), so.Est(errorbar="sd"))  # 1σ区間(実線)
)

3.3. ヒストグラム — so.Hist()

so.Hist()でヒストグラムを計算する。マークオブジェクトはso.Bars()を用いることが推奨されている。

  • stat=: 統計処理方法を文字列で指定。棒グラフの形は変わらないが縦軸(y軸)の意味が変わる。

    • stat="count": 縦軸はデータ数
    • stat="percent": 縦軸は母集団に占める割合(0%~100%)
    • stat="probability": 縦軸は母集団に占める割合(0~1)
    • stat="density": 密度(ビン面積合計が1)
  • bins=: にビン(柱)の本数または各階級区間(境界)のリスト

  • binwidth=: ビンの幅

  • binrange=: 集計対象区間をタプル(min, max)で指定

  • cumulative=True: グラフを累積分布に

ヒストグラム1: Hist
so.Plot(df_iris, x="sepal_length").add(
    so.Bars(), so.Hist("frequency", binwidth=0.5, binrange=(4, 8))
)

temp.png

3.4. カーネル密度分布 — so.KDE()

so.KDE()でカーネル密度推定を行う。

カーネル密度分布1: KDE
so.Plot(df_iris, x="sepal_length", color="species").add(so.Area(), so.KDE())

ヒストグラムと重ねる例。

カーネル密度分布2: KDE
(
    so.Plot(df_iris, x="sepal_length")
    .add(so.Bars(alpha=0.3), so.Hist("density", binwidth=0.5, binrange=(4, 8)))
    .add(so.Line(), so.KDE())
)

temp.png

3.5. 直線・曲線近似 — so.PolyFit()

分布の直線・曲線近似はso.PolyFit()を用いる。

デフォルトでは2次曲線で近似される(下の例はわかりづらいかもしれないが)。

2次曲線近似: PolyFit
(
    so.Plot(df_tips, x="total_bill", y="tip", color="time")
    .add(so.Dot())
    .add(so.Line(), so.PolyFit())
)

第一引数order=1を指定すると1次曲線(つまり直線)、3を指定すると3次曲線、という具合になる。

直線近似: PolyFit
(
    so.Plot(df_tips, x="total_bill", y="tip", color="time")
    .add(so.Dot())
    .add(so.Line(), so.PolyFit(1))
)

4. ムーブオブジェクト

.add()メソッドの第二引数transforms=(スタッツオブジェクトを渡している場合はその後)にムーブオブジェクトと通称されるものを渡すことで、グラフの描き方の調整ができる。このあたりは言葉で説明するより図を見てもらったほうが早い。

4.1. サブグループの分割 — so.Dodge()

サブグループ化(§1.2)している場合、デフォルトでは重なってプロットされるが、so.Dodge()を与えると、ベースライン方向で分割される。離散型の場合に使う。

Dodge
# 重なっている棒グラフ
so.Plot(df_tips, "day", "total_bill", color="sex").add(so.Bar(), so.Count())

# `so.Dodge()`を与えると横に分割される
so.Plot(df_tips, "day", "total_bill", color="sex").add(so.Bar(), so.Count(), so.Dodge())

4.2. サブグループの積み上げ — so.Stack()

サブグループ化(§1.2)している場合、デフォルトでは重なってプロットされるが、so.Stack()を与えると、ベースラインと垂直方向に積み上げられる。

Stack1
# 重なっている棒グラフ
so.Plot(df_tips, "day", "total_bill", color="sex").add(so.Bar(), so.Count())

# `so.Stack()`を与えると積み上げられる
so.Plot(df_tips, "day", "total_bill", color="sex").add(so.Bar(), so.Count(), so.Stack())

Stack2
# 重なっている分布図(サブグループごとの分布がわかりやすい)
so.Plot(df_iris, x="sepal_length", color="species").add(so.Area(), so.KDE())

# `so.Stack()`を与えると積み上げられる(データ全体の分布とサブグループの割合がわかりやすい)
so.Plot(df_iris, x="sepal_length", color="species").add(so.Area(), so.KDE(), so.Stack())

4.3. データに揺れを与える — so.Jitter()

so.Jitter()はデータに揺れを与える。多くの場合、次の例のような離散型散布図に使う。

Jitter1
# x軸が離散的でドットが重なっている散布図
so.Plot(df_iris, x="species", y="sepal_length").add(so.Dots())

# `so.Jitter()`を与えて横に散らすと分布がわかりやすくなる
so.Plot(df_iris, x="species", y="sepal_length").add(so.Dots(), so.Jitter())

第一引数width=で振れ幅を指定できる。

Jitter2
so.Plot(df_iris, x="species", y="sepal_length").add(so.Dots(), so.Jitter(0.8))

temp.png

4.4. データを平行移動 — so.Shift()

so.Shift(x, y)x/y分だけデータをずらすことができる。

Shift
# データ点と標準偏差を示す縦棒が重なっている状態
(
    so.Plot(df_iris, x="species", y="sepal_length")
    .add(so.Dots(), so.Jitter())
    .add(so.Range(), so.Est(errorbar="sd"))
)

# `so.Shift()`を用いてデータ点を左に、標準偏差を示す縦棒を右に移動
(
    so.Plot(df_iris, x="species", y="sepal_length")
    .add(so.Dots(), so.Jitter(), so.Shift(-0.1))
    .add(so.Range(), so.Est(errorbar="sd"), so.Shift(0.1))
)

5. x/y軸やサブグルーピングのスケール — .scale()

プロットの.scale()メソッドでx/y軸やサブグルーピングのスケールを設定する。

5.1. 対数軸

x=/y=引数に"log"を指定すると対数軸となる。

対数軸
# 通常
so.Plot(df_planets, x="orbital_period", y="mass").add(so.Dot())

# x軸を対数スケールに変更
so.Plot(df_planets, x="orbital_period", y="mass").add(so.Dot()).scale(x="log")

5.2. 軸・サブグループを離散化

数値データや時系列データは、デフォルトでは連続変数として扱われる。離散型変数として扱いたい場合は、x=/y=引数(もちろんcolor=等も可)にso.Nominal()を与える。

軸を離散化
# `df_glue["Year"]`列は整数型であるため連続量として図示されている。
(
    so.Plot(df_glue, x="Year", y="Score")
    .add(so.Dot(), so.Jitter())
)

# x軸を離散データとして扱う
(
    so.Plot(df_glue, x="Year", y="Score")
    .add(so.Dot(), so.Jitter())
    .scale(x=so.Nominal())
)

サブグループの配色を離散化
# `df_glue["Year"]`列は整数型であるため連続量として図示されている。
(
    so.Plot(df_glue, x="Encoder", y="Score", color="Year")
    .add(so.Dot(), so.Jitter())
)

# サブグループを離散データとして扱う
(
    so.Plot(df_glue, x="Encoder", y="Score", color="Year")
    .add(so.Dot(), so.Jitter())
    .scale(color=so.Nominal())
)

5.3. 配色の変更

color=引数にカラーパレットの名前(参照1, 参照2)や色名(参照)を与えるとサブグループの配色を変更できる。

カラーパレットの変更
# デフォルト
(
    so.Plot(df_healthexp, x="Life_Expectancy", y="Spending_USD", color="Country")
    .add(so.Line(marker="."))
)

# パレットを変更
(
    so.Plot(df_healthexp, x="Life_Expectancy", y="Spending_USD", color="Country")
    .add(so.Line(marker="."))
    .scale(color="twilight")
)

色を直接指定
# デフォルト(データが示す色と図の配色が一致していないのでわかりづらい)
(
    so.Plot(df_taxis, x="distance", color="color")
    .add(so.Area(), so.KDE())
)

# 色を指定
(
    so.Plot(df_taxis, x="distance", color="color")
    .add(so.Area(), so.KDE())
    .scale(color=["goldenrod", "forestgreen"])
)

6. サブプロットの設定

6.1. サブグループ別のサブプロット — .facet()

プロットの.facet()メソッドにサブグループ配列を与えると、グラフがサブグループごとに分割される。それぞれのサブプロットのx軸とy軸は共通である。

第一変数col=は横に分割され、第二変数row=は縦に分割される。

サブプロット1: facet
# 元
(
    so.Plot(df_iris, x="sepal_length", y="sepal_width", color="species")
    .add(so.Dot())
)

# 種類に基づいて横分割
(
    so.Plot(df_iris, x="sepal_length", y="sepal_width", color="species")
    .add(so.Dot())
    .facet(col="species")
)

# 種類に基づいて縦分割
(
    so.Plot(df_iris, x="sepal_length", y="sepal_width", color="species")
    .add(so.Dot())
    .facet(row="species")
)

縦横それぞれ別の属性でサブグループ化することも可能。

サブプロット2: facet
(
    so.Plot(df_titanic, x="age", color="alive")
    .add(so.Bar(), so.Hist(binwidth=5, binrange=(0, 100)), so.Stack())
    .facet(col="class", row="sex")
)

6.2. サブグループ別のサブプロット — .pair()

プロットの.pair()メソッドは、x=/y=をリストで受け取ることができ、異なるx/y軸を持つサブプロットを作ることができる。.pair()を用いる場合はso.Plot()x=/y=引数は空でいい。

サブプロット3: pair
(
    so.Plot(df_iris, color="species")
    .add(so.Dot())
    .pair(x=["sepal_length", "sepal_width"], y=["petal_length"])
)

サブプロット4: pair
(
    so.Plot(df_iris, color="species")
    .add(so.Dots())
    .pair(x=["sepal_length", "sepal_width"], y=["petal_length", "petal_width"])
)

6.3. サブプロット軸の共有設定 ‒ .share()

プロットの.share()メソッドx=/y=引数にTrueFalseを与えることでサブプロットの軸の共有設定を指定できる。

7. その他のグラフの調整

7.1. ラベルの設定 — .label()

プロットの.label()メソッドで図のラベルを設定できる。引数の名前は直感通り。

ラベルの設定: label
(
    so.Plot(df_iris, x="sepal_length", y="sepal_width", color="species", pointsize="petal_length")
    .add(so.Dot())
    .label(title="title", x="x", y="y", color="color", pointsize="pointsize")
)

7.2. x/y軸の範囲設定 — .limit()

プロットの.limit()メソッドでx軸/y軸の表示範囲を設定できる。

(
    so.Plot(df_iris, x="sepal_length", y="sepal_width", color="species")
    .add(so.Dot())
    .limit(x=(5, 7), y=(2, 4))
)

5
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
4