0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

概要

scikit-learn(import 名は sklearn)は、Python で古典的な機械学習を進めるときの中心的なライブラリです。分類、回帰、クラスタリング、次元削減から、特徴量エンジニアリング、評価、交差検証、ハイパーパラメータのチューニング、Pipeline まで一通りそろっています。

最初に全体像だけ押さえるなら、sklearn は次の 5 つのモジュール群で見ると整理しやすくなります。

  1. datasets
    • サンプルデータの読み込みや確認に使う。
  2. preprocessing / impute / feature_selection
    • スケーリング、欠損補完、エンコーディング、特徴量選択を担う。
  3. linear_model / neighbors / tree / svm / ensemble / cluster / decomposition
    • 予測モデルやクラスタリング、次元削減の本体を担う。
  4. metrics / model_selection
    • 評価指標、データ分割、交差検証、ハイパーパラメータ探索を担う。
  5. pipeline / compose
    • 前処理からモデルまでを 1 本にまとめ、再現性を高める。

この文章では、この 5 分類を個別に暗記するのではなく、機械学習の処理フローに沿って必要なモジュールを結び付けながら整理します。sklearn を「モデル名の一覧」として覚えるのではなく、「課題整理から改善までの流れ」として理解するのが狙いです。実務でも、次の順で捉えるとつながりやすくなります。

  1. 課題を定義する
  2. EDA でデータを理解する
  3. 仮説を立てて検証する
  4. 特徴量を整える
  5. モデルを選んで学習する
  6. 指標で評価する
  7. チューニングを繰り返す
  8. Pipeline 化して再現性を高める

上の流れに沿って、課題整理、EDA、仮説検証、数値特徴量、カテゴリ特徴量、回帰、分類、アンサンブル、評価、交差検証、グリッドサーチまでを 1 本の流れとして整理しています。

最初に読む人へ

初めて sklearn を学ぶ場合は、すべてを一度に暗記しようとしなくて大丈夫です。まずは「データを分ける」「前処理する」「学習する」「評価する」という基本の流れを 1 回通すことを優先してください。

最初の 1 周では、次の順で読むと負担が軽くなります。

    1. EDA と課題整理
  1. 1-1. 数値特徴量の処理
  2. 1-4. カテゴリ特徴量の処理
  3. 2-1. sklearn の基本操作
  4. 2-3. 分類モデルの比較
  5. 2-6. 評価指標
  6. 3-4. Pipeline と ColumnTransformer

コード例は説明を短くするため、前の例で行った import や変数定義を引き継ぐ箇所があります。コードブロックを単独で試す場合は、必要な import を先頭に追加してください。

最初に押さえる高頻度ポイント

  • 課題整理は EDA の前提である。まず予測目的、誤りコスト、利用場面を言葉にし、その前提で pandasnumpymatplotlibseaborn を使って欠損、分布、相関、外れ値を見て仮説を作り、集計や可視化で検証する。
  • 数値特徴量では、まず StandardScaler を基準に考え、外れ値が強ければ RobustScaler、範囲を固定したければ MinMaxScaler、分布の歪みが強ければ QuantileTransformerPowerTransformer を検討する。
  • カテゴリ特徴量では、目的変数 y を数値ラベルで扱いたいときに LabelEncoder を使える。説明変数のダミー化は OneHotEncoder を基本にする。pd.get_dummies() は EDA や簡易検証には便利だが、本番前提の特徴量エンジニアリングでは Pipeline へ乗せやすい OneHotEncoder が扱いやすい。
  • 距離ベース・線形モデルである KNeighborsClassifierKNeighborsRegressorSVCSVRLogisticRegression ではスケーリングの影響が大きい。
  • 木系モデルである DecisionTreeClassifierRandomForestClassifierRandomForestRegressorGradientBoostingClassifierGradientBoostingRegressor はスケール差の影響を受けにくいが、欠損やカテゴリの表現方法は依然として重要である。
  • RandomForestAdaBoostGradientBoosting などのアンサンブル学習は強力だが、単体モデルで基準を作ってから比較した方が改善理由を説明しやすい。
  • 分類では accuracy だけで判断しない。precisionrecallf1confusion_matrix、必要に応じて ROC 曲線と PR 曲線まで見る。
  • 回帰では MAEMSERMSEMAPER2 を使い分ける。特に MAPE は真値に 0 が含まれると扱いにくい。
  • 改善では、やみくもにモデルを変える前に、欠損処理、エンコーディング、スケーリング、特徴量選択、分割方法、評価指標を見直す。
  • 再現性を高めるには PipelineColumnTransformer を使う。特徴量エンジニアリングとモデルを 1 本にまとめるのが実務では非常に重要である。

全体の読み順

この文章は、次の順で読むと理解がつながりやすくなります。

  1. 課題整理と EDA
    • 課題設定、EDA、仮説構築、検証、データ分割までの流れを整理します。
  2. 特徴量エンジニアリング
    • 数値特徴量、カテゴリ特徴量、文字列処理、特徴量選択を扱います。
  3. モデリングと評価
    • 回帰モデル、分類モデル、アンサンブル学習、教師なし学習、評価指標の位置付けを整理します。
  4. チューニングと改善
    • クロスバリデーション、グリッドサーチ、Pipeline、モデル融合を扱います。
  5. モジュール早見表と学習順
    • sklearn をどこから覚えるか、実務で何が頻出かを整理します。

0. EDA と課題整理

課題整理と EDA は別々の作業ではなく、ひと続きの工程です。先に「何を予測するのか」「何を外すと痛いのか」「予測はどこで使われるのか」を定義すると、EDA で何を見るべきかが決まります。その観察結果から「外れ値が効きそうだ」「クラス不均衡が強い」「欠損には意味がありそうだ」といった仮説を作り、集計や可視化や小さなベースラインで検証していきます。

EDA は sklearn そのものではありませんが、機械学習では必須です。ここでデータの中身を理解せずにモデルへ進むと、欠損、外れ値、リーク、クラス不均衡、不要特徴量、誤った指標選択を見落としやすくなります。

0-1. 課題整理から EDA、仮説、検証へつなぐ

機械学習では、最初に次の流れを意識しておくと迷いにくくなります。

  1. 課題を言葉で定義する
  2. 失敗コストと評価指標の候補を決める
  3. EDA で分布、欠損、偏り、リーク候補を観察する
  4. 前処理やモデル選択の仮説を立てる
  5. 集計、可視化、小さなモデルで仮説を確かめる

そのうえで、最低でも次の 5 点を言葉で説明できるようにします。

  1. 何を予測したいのか
  2. それは分類か回帰か
  3. 予測結果は何に使われるのか
  4. 見逃しと誤検知のどちらが痛いのか
  5. 学習時に使えない情報が特徴量へ混ざっていないか

たとえば乳がん判定なら「悪性の見逃しを減らしたい」、売上予測なら「平均的には外しても大きな外れ値を避けたい」といったように、課題の重心によって評価指標も前処理も変わります。つまり EDA は単なる可視化ではなく、課題整理の続きとして、どの仮説を優先して確かめるかを決める工程です。

0-2. データの基本確認

まずは head()info()describe()isnull().sum()value_counts() を確認します。分類タスクではクラス数やクラス比率も必ず見ます。

import pandas as pd
from sklearn.datasets import load_breast_cancer

# サンプルデータを DataFrame として読み込む
breast_cancer = load_breast_cancer(as_frame=True)
df = breast_cancer.frame.copy()

# 目的変数は 0/1 だと読みにくいので、日本語ラベルも追加する
df["target_name"] = df["target"].map({0: "悪性", 1: "良性"})

# 先頭数行で列構成をざっくり確認する
print(df.head())

# info() はメソッド自身が表示するので print() で包まない
df.info()

# 数値列の代表値を確認する
print(df.describe().T.head())

# 欠損の多い列があるかを確認する
print(df.isnull().sum().sort_values(ascending=False).head())

# クラス数とクラス比率の偏りを確認する
print(df["target_name"].value_counts())

ここで見るべき点は次のとおりです。

  • どの列が数値で、どの列がカテゴリか
  • 欠損がどの列にどれだけあるか
  • 目的変数の偏りが強すぎないか
  • そもそも削除すべき ID 列や説明しにくいリーク列がないか

0-3. 分布、外れ値、相関から仮説を立てる

EDA の定番は、ヒストグラム、箱ひげ図、散布図、相関ヒートマップです。ノート 9 と 10 でも、回帰では糖尿病データ、分類では乳がんデータを使って、分布と相関を確認していました。

import matplotlib.pyplot as plt
import seaborn as sns

# フォント設定は環境依存なので、まずはテーマだけ整える
sns.set_theme(context="talk", style="darkgrid")

# まずは相関をざっくり見たい列だけに絞る
focus_columns = ["mean radius", "mean texture", "mean area", "target"]

plt.figure(figsize=(10, 8))
# 相関の強さをヒートマップで確認する
sns.heatmap(
    df[focus_columns].corr(),
    annot=True,
    cmap="coolwarm",
    square=True,
    vmin=-1,
)
plt.show()

fig, axes = plt.subplots(1, 2, figsize=(14, 5), tight_layout=True)

# 箱ひげ図でクラスごとの差を見る
sns.boxplot(data=df, x="target_name", y="mean radius", ax=axes[0])

# 散布図で 2 変数の分離しやすさを見る
sns.scatterplot(data=df, x="mean radius", y="mean texture", hue="target_name", ax=axes[1])
plt.show()

相関や可視化を見たら、ただ眺めるだけで終わらせず、仮説へ落とします。

  • mean radius は悪性で大きい傾向がありそうだ」
  • 「特徴量どうしの相関が強いので、線形モデルでは係数が不安定になるかもしれない」
  • 「外れ値が多いので、StandardScaler より RobustScaler が安定するかもしれない」
  • 「クラス不均衡が強いなら accuracy だけでは危ない」

0-4. 仮説を集計で検証する

可視化で気づいたことは、groupby() や基本集計で裏取りしておくと解釈しやすくなります。

summary = (
    df.groupby("target_name")["mean radius"]
    .agg(["mean", "median", "std", "min", "max"])
)

print(summary)

このような確認は、特徴量を残すか削るか、変換するかしないかの判断材料になります。

0-5. 欠損値の考え方

欠損値を見つけたら、すぐに埋めるのではなく「なぜ欠けているのか」を考えます。

  • 入力漏れなのか
  • 観測不能なケースなのか
  • ある条件のときだけ欠けるのか
  • 欠損そのものに意味があるのか

単純な補完は後で SimpleImputer で行えますが、EDA の時点では欠損の偏りを見ておくことが大切です。

0-6. 分析の終点を決めてから分割する

EDA の最後で、どこまでを前処理に含めるかを決めます。そのうえで、学習用と評価用に分割します。分類では stratify を使ってクラス比率を保つことが重要です。

from sklearn.model_selection import train_test_split

# 説明変数 X と目的変数 y を分ける
X = df.drop(columns=["target_name", "target"])
y = df["target"]

# stratify=y にすると、学習用と評価用でクラス比率がずれにくい
X_train, X_test, y_train, y_test = train_test_split(
    X,
    y,
    test_size=0.3,
    random_state=17,
    stratify=y,
)

# 分割後のデータサイズとクラス比率を確認する
print(X_train.shape, X_test.shape)
print(y_train.mean(), y_test.mean())

1. 特徴量エンジニアリングと処理

特徴量設計は、モデル選択と同じくらい重要です。モデルが強くても、特徴量の作り方が悪ければ精度は伸びません。この章では、数値、カテゴリ、文字列、欠損、特徴量選択を順に整理します。

1-1. 数値特徴量は「分布」と「モデルとの相性」で処理を決める

数値特徴量では、まず次の 3 つを見ます。

  1. スケール差が大きいか
  2. 外れ値が強いか
  3. 分布の歪みが大きいか

代表的な 4 つのスケーラー

手法 クラス 向いている場面
標準化 StandardScaler 最初の基準。線形モデル、SVM、KNN で特に重要
最小最大変換 MinMaxScaler 値を 0〜1 にそろえたいとき
最大絶対値変換 MaxAbsScaler 疎行列や符号を保ちたいデータ
ロバスト標準化 RobustScaler 外れ値の影響を抑えたいとき
import numpy as np
from sklearn.preprocessing import StandardScaler, MinMaxScaler, MaxAbsScaler, RobustScaler

# 3 列目だけ強い外れ値を混ぜた数値データを作る
X_num = np.array(
    [
        [111.0, 80.0, 80.0],
        [112.0, 90.0, 82.0],
        [110.0, 85.0, 79.0],
        [109.0, 70.0, 78.0],
        [113.0, 95.0, 810.0],
    ]
)

scalers = {
    "StandardScaler": StandardScaler(),
    "MinMaxScaler": MinMaxScaler(),
    "MaxAbsScaler": MaxAbsScaler(),
    "RobustScaler": RobustScaler(),
}

for name, scaler in scalers.items():
    transformed = np.round(scaler.fit_transform(X_num), 2)
    print(name)
    print(transformed)
    print("-" * 40)

使い分けの目安は次のとおりです。

  • まずは StandardScaler を基準に試す
  • 外れ値が強く、分布の大部分を守りたいなら RobustScaler
  • 値域を 0〜1 に固定したいなら MinMaxScaler
  • 疎な表現や大きな 0 を崩したくないなら MaxAbsScaler

スケーリングが特に重要なモデル

  • KNeighborsClassifier / KNeighborsRegressor
  • SVC / SVR
  • LogisticRegression
  • 主成分分析など距離や分散に敏感な手法

逆に、木系モデルはスケール差に比較的強いです。ただし、欠損やカテゴリ処理が雑だと普通に性能が落ちます。

1-2. 分布の歪みには非線形変換を使う

ノート 8 では QuantileTransformerPowerTransformer が扱われていました。歪みの強い特徴量を扱いやすくしたいときに有効です。

  • QuantileTransformer: 分位数ベースで分布を一様分布または正規分布へ寄せる
  • PowerTransformer: べき変換で歪みを和らげる
from sklearn.preprocessing import QuantileTransformer, PowerTransformer

# 強く右に歪んだ特徴量を想定する
feature = np.array([1, 2, 3, 4, 5, 8, 13, 21, 34, 55], dtype=float).reshape(-1, 1)

quantile_normal = QuantileTransformer(output_distribution="normal", n_quantiles=len(feature))
quantile_uniform = QuantileTransformer(output_distribution="uniform", n_quantiles=len(feature))
power = PowerTransformer()

print("original:", feature.flatten())
print("quantile_normal:", np.round(quantile_normal.fit_transform(feature).flatten(), 3))
print("quantile_uniform:", np.round(quantile_uniform.fit_transform(feature).flatten(), 3))
print("power:", np.round(power.fit_transform(feature).flatten(), 3))

旧い教材では load_boston() を使っていましたが、現在の sklearn では削除済みです。考え方自体は変わらないので、実際には自前データや他の公開データへ置き換えて確認します。

1-3. 数値を区間に切るならビニングを使う

数値をそのまま使うより、「低い・中くらい・高い」のような区間に切った方が扱いやすいことがあります。そのときは KBinsDiscretizerpd.cut() が便利です。

import pandas as pd
from sklearn.preprocessing import KBinsDiscretizer

x = np.array([3.6, 4.7, 5.7, 5.9, 6.1, 6.4, 6.8, 7.8]).reshape(-1, 1)

discretizer_q = KBinsDiscretizer(n_bins=5, strategy="quantile", encode="ordinal")
discretizer_u = KBinsDiscretizer(n_bins=5, strategy="uniform", encode="ordinal")

print(discretizer_q.fit_transform(x).ravel())
print(discretizer_u.fit_transform(x).ravel())

classes = pd.cut(
    x.ravel(),
    bins=[3.5, 5.0, 6.0, 6.5, 7.0, 8.0],
    labels=["very low", "low", "medium", "high", "very high"],
)
print(classes)

使い分けは次のように考えると分かりやすいです。

  • データ個数が均等になるように切りたいなら strategy="quantile"
  • 値域を等幅で切りたいなら strategy="uniform"
  • ビジネス上のしきい値があるなら pd.cut() で自分で区間を決める

1-4. カテゴリ特徴量は目的変数と説明変数で分けて考える

カテゴリ処理で一番重要なのは、「目的変数の符号化」と「説明変数のエンコーディング」を混同しないことです。

LabelEncoder は主に目的変数で使う

sklearn の分類器は文字列ラベルをそのまま扱えることも多いですが、ラベルの対応関係を明示したいときや、数値ラベルが必要な場面では LabelEncoder が便利です。説明変数へ使うと、存在しない順序をモデルへ持ち込みやすくなります。

import pandas as pd
from sklearn.preprocessing import LabelEncoder

y = pd.Series(["良性", "悪性", "良性", "良性", "悪性"], name="target")

# 文字列ラベルと整数ラベルの対応を学習する
target_encoder = LabelEncoder()
y_encoded = target_encoder.fit_transform(y)

# classes_ にはラベルの並び順が入る
print(target_encoder.classes_)

# 変換後の整数ラベルを確認する
print(y_encoded)

# 必要なら元の文字列ラベルへ戻せる
print(target_encoder.inverse_transform(y_encoded))

文字列ラベルをそのまま受け取れるモデルでは、必ずしもこの変換が必須ではありません。

pd.get_dummies() は速いが、学習・推論で列ずれに注意が必要

pd.get_dummies() は EDA や簡単な確認には非常に便利です。drop_first=True でダミー変数の冗長性を減らし、dummy_na=True で欠損も 1 列として扱えます。

df_cat = pd.DataFrame(
    {
        "Color": ["red", "blue", "green", None, "blue"],
        "Shape": ["circle", "square", "triangle", "circle", "square"],
    }
)

encoded_df = pd.get_dummies(
    df_cat,
    columns=["Color", "Shape"],
    drop_first=True,
    dummy_na=True,
)

print(encoded_df)

ただし、トレインとテストで出現カテゴリが違うと列構成がずれることがあります。これはノート 7 の重要な論点でした。

X_train = pd.DataFrame(
    {
        "Color": ["red", "blue", "green"],
        "Shape": ["circle", "square", "triangle"],
    }
)

X_test = pd.DataFrame(
    {
        "Color": ["red", "purple"],  # train にない purple を含める
        "Shape": ["square", "circle"],
    }
)

print(pd.get_dummies(X_train, drop_first=True).columns)
print(pd.get_dummies(X_test, drop_first=True).columns)

EDA では便利でも、本番前提の処理としてはこの列ずれが危険です。

OneHotEncoder は学習・推論をまたぐ処理に向く

OneHotEncoder は、学習時にカテゴリ集合を記憶し、その後の変換でも列構成をそろえられます。Pipeline と相性がよいので、実務ではこちらが本命になりやすいです。

from sklearn.preprocessing import OneHotEncoder

# 未知カテゴリが来てもエラーにしない
# 配列の中身を見やすくするため、ここでは dense 出力にする
encoder = OneHotEncoder(handle_unknown="ignore", sparse_output=False)

# train 側でカテゴリ集合を学習する
train_encoded = encoder.fit_transform(X_train)

# test 側は学習済みのカテゴリ集合で変換する
test_encoded = encoder.transform(X_test)

print(encoder.categories_)
print(encoder.get_feature_names_out())
print(train_encoded)
print(test_encoded)

古い sklearn では sparse_output=False ではなく sparse=False を使う場合があります。

この節の使い分けを短くまとめると次のようになります。

  • LabelEncoder: 必要なときに目的変数 y のラベル対応を明示する
  • pd.get_dummies(): EDA、簡易検証、固定されたデータの確認
  • OneHotEncoder: 本番前提、推論時も含めて列整合を保ちたいとき

drop='first'handle_unknown の考え方

  • 線形モデルで完全な多重共線性を少し避けたいときは drop='first'
  • 推論時に未知カテゴリが来る可能性があるなら handle_unknown='ignore'
  • 欠損もカテゴリとして明示したいなら、特徴量エンジニアリングで欠損を文字列化するか、SimpleImputer(strategy='most_frequent') を先に挟む

1-5. 文字列列は Series.str で特徴量へ分解する

Series.str を使った置換、抽出、分離は、文字列列を特徴量へ分解するときの基本です。機械学習では、自由記述列や ID 形式の列をそのまま入れず、まず文字列加工で特徴量へ分解する場面がよくあります。

import pandas as pd

df_text = pd.DataFrame(
    {
        "ID": ["A-123", "B-456", "A-789", "B-123"],
        "Color": ["py/white black", "red green blue", "py/yellow", "purple white"],
    }
)

# 先頭文字でグループを切り出す
df_text["prefix"] = df_text["ID"].str[:1]

# "white" を含むかどうかを True / False で持たせる
df_text["has_white"] = df_text["Color"].str.contains("white", na=False)

# 分割して新しい列へ展開する
df_text[["id_head", "id_number"]] = df_text["ID"].str.split("-", expand=True)

# 正規表現で接頭辞を抜き出す
df_text["py_tag"] = df_text["Color"].str.extract(r"(py/)", expand=True)

# 不要文字列を除去する
df_text["Color_clean"] = df_text["Color"].str.replace("py/", "", regex=False)

print(df_text)

文字列列を分解してから、必要な列だけを OneHotEncoder へ渡すと、特徴量設計の自由度がかなり上がります。

1-6. 欠損補完は列の種類ごとに変える

欠損補完は、とりあえず 1 つの方法で全部埋めるのではなく、列の性質に合わせて変えます。

  • 数値列: mean より median が安定しやすい場面が多い
  • カテゴリ列: most_frequent が基本
  • 欠損自体に意味がある場合: 欠損フラグ列を別に立てる
import numpy as np
import pandas as pd
from sklearn.impute import SimpleImputer

df_missing = pd.DataFrame(
    {
        "age": [25, np.nan, 42, 37],
        "income": [320, 410, np.nan, 380],
        "city": ["Tokyo", "Osaka", None, "Nagoya"],
    }
)

num_imputer = SimpleImputer(strategy="median")
cat_imputer = SimpleImputer(strategy="most_frequent")

df_missing[["age", "income"]] = num_imputer.fit_transform(df_missing[["age", "income"]])
df_missing[["city"]] = cat_imputer.fit_transform(df_missing[["city"]])

print(df_missing)

1-7. 特徴量選択は「理由を持って削る」

特徴量選択は、精度だけのためではありません。過学習を減らし、説明しやすくし、学習を軽くする意味もあります。

代表的な観点は次のとおりです。

  • 明らかに不要な ID 列を削る
  • 目的変数にリークする列を削る
  • 相関が強すぎる列の扱いを検討する
  • SelectKBest で統計的に強い列を残す
  • 線形モデルの係数や木モデルの feature_importances_ で重要度を見る
import pandas as pd
from sklearn.feature_selection import SelectKBest, f_regression

X_fs = pd.DataFrame(
    {
        "x1": [1, 2, 3, 4, 5],
        "x2": [10, 20, 30, 40, 50],
        "x3": [100, 98, 95, 93, 91],
    }
)
y_fs = [12, 24, 35, 48, 59]

selector = SelectKBest(score_func=f_regression, k=2)
X_selected = selector.fit_transform(X_fs, y_fs)

print(selector.get_support())
print(X_selected)

2. モデリングと評価

ここから sklearn の出番が本格化します。まずは回帰と分類の基準モデルを押さえ、その後でアンサンブル学習と教師なし学習へ広げ、最後に評価指標を整理します。

2-1. sklearn の基本操作を共通フローで理解する

ほとんどのモデルは、次の流れで統一的に扱えます。

  1. クラスを import する
  2. モデルインスタンスを作る
  3. fit() で学習する
  4. predict() で予測する
  5. score() または評価指標で確認する
from sklearn.linear_model import LogisticRegression

model = LogisticRegression(max_iter=500, random_state=17)
model.fit(X_train, y_train)

pred = model.predict(X_test)
score = model.score(X_test, y_test)

print(pred[:5])
print(score)

注意点は、score() の意味がモデル種別で違うことです。

  • 分類モデルの score(): 正解率(accuracy)
  • 回帰モデルの score(): 決定係数 $R^2$

2-2. 回帰モデルは「線形か非線形か」「誤差をどう見たいか」で選ぶ

回帰モデルでは、まず連続値をどの程度なめらかに予測したいか、外れ値をどれだけ重く見るかを決めます。sklearn の糖尿病データを使って基準モデルから比べると、使い分けが見えやすくなります。

モデル クラス 向いている場面 主なパラメータ
線形回帰 LinearRegression まずの基準モデル ほぼ既定値でよい
Ridge Ridge 多重共線性が強い alpha
Lasso Lasso 変数選択もしたい alpha
ElasticNet ElasticNet Ridge と Lasso の中間 alpha, l1_ratio
K 近傍回帰 KNeighborsRegressor 近い事例の平均で予測したい n_neighbors, weights
決定木回帰 DecisionTreeRegressor 非線形な分岐を表したい max_depth, min_samples_leaf
SVR SVR 中小規模で滑らかな非線形回帰 C, epsilon, gamma

回帰モデルの比較例

from sklearn.datasets import load_diabetes
from sklearn.linear_model import ElasticNet, Lasso, LinearRegression, Ridge
from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsRegressor
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.svm import SVR
from sklearn.tree import DecisionTreeRegressor

diabetes = load_diabetes(as_frame=True)
X_reg = diabetes.data
y_reg = diabetes.target

X_reg_train, X_reg_test, y_reg_train, y_reg_test = train_test_split(
    X_reg,
    y_reg,
    test_size=0.3,
    random_state=17,
)

regression_models = {
    "LinearRegression": LinearRegression(),
    "Ridge": Ridge(alpha=1.0),
    "Lasso": Lasso(alpha=0.01),
    "ElasticNet": ElasticNet(alpha=0.01, l1_ratio=0.5),
    "DecisionTreeRegressor": DecisionTreeRegressor(max_depth=4, random_state=17),
    "KNeighborsRegressor": Pipeline([
        ("scaler", StandardScaler()),
        ("model", KNeighborsRegressor(n_neighbors=8)),
    ]),
    "SVR": Pipeline([
        ("scaler", StandardScaler()),
        ("model", SVR(C=3.0, epsilon=0.2)),
    ]),
}

for name, model in regression_models.items():
    model.fit(X_reg_train, y_reg_train)
    pred = model.predict(X_reg_test)
    print(name)
    print("MAE:", round(mean_absolute_error(y_reg_test, pred), 3))
    print("RMSE:", round(mean_squared_error(y_reg_test, pred, squared=False), 3))
    print("R2:", round(r2_score(y_reg_test, pred), 3))
    print("-" * 40)

ここでの考え方は次のとおりです。

  • まず LinearRegression で線形関係の強さを見る
  • 係数が不安定なら Ridge、不要特徴も削りたければ LassoElasticNet を試す
  • 局所的な近さで予測したいなら KNeighborsRegressor
  • 明確な分岐を持つ非線形を疑うなら DecisionTreeRegressor
  • 滑らかな非線形を取りたいなら SVR
  • より強い木アンサンブル回帰は後で 2-4 でまとめて扱う

2-3. 分類モデルは「境界の形」と「データ量」で選ぶ

分類モデルでは、境界がほぼ線形か、局所的な近さが効くか、分岐ルールで表せそうかを見ながら候補を絞ります。まずは単体モデルで基準を作り、その後でアンサンブルへ広げる方が改善理由を追いやすくなります。

モデル クラス 向いている場面 主なパラメータ
K 近傍法 KNeighborsClassifier 近いサンプルがそのまま答えになりやすい n_neighbors, weights
ナイーブベイズ GaussianNB 軽量なベースライン、まずの比較用 事前分布の仮定が中心
決定木 DecisionTreeClassifier ルールを説明しやすい max_depth, min_samples_split, min_samples_leaf
ロジスティック回帰 LogisticRegression 線形な基準モデル C, penalty, solver, max_iter
SVM SVC 複雑な境界でも強い C, gamma, kernel, probability

分類モデルの比較例

from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, f1_score
from sklearn.naive_bayes import GaussianNB
from sklearn.neighbors import KNeighborsClassifier
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.svm import SVC
from sklearn.tree import DecisionTreeClassifier

models = {
    "LogisticRegression": Pipeline([
        ("scaler", StandardScaler()),
        ("model", LogisticRegression(max_iter=500, random_state=17)),
    ]),
    "SVC": Pipeline([
        ("scaler", StandardScaler()),
        ("model", SVC(probability=True, random_state=17)),
    ]),
    "KNeighbors": Pipeline([
        ("scaler", StandardScaler()),
        ("model", KNeighborsClassifier(n_neighbors=5, weights="uniform")),
    ]),
    "DecisionTree": DecisionTreeClassifier(max_depth=4, random_state=17),
    "GaussianNB": GaussianNB(),
}

for name, model in models.items():
    model.fit(X_train, y_train)
    pred = model.predict(X_test)
    print(name)
    print("accuracy:", round(accuracy_score(y_test, pred), 4))
    print("f1:", round(f1_score(y_test, pred), 4))
    print("-" * 40)

モデル選択の感覚は次のように整理できます。

  • まず基準にする: LogisticRegression
  • 距離で素直に比較したい: KNeighborsClassifier
  • 解釈しやすいルールがほしい: DecisionTreeClassifier
  • データが小さめで境界が複雑: SVC
  • まず軽く当たりを見たい: GaussianNB

主要パラメータをどうチューニングするか

  • KNeighborsClassifier: n_neighbors を増やすと滑らかになりやすく、減らすと細かく拾いやすい
  • LogisticRegression: C を小さくすると正則化が強くなる
  • SVC: C は誤分類をどこまで許すか、gamma は境界の細かさに影響する
  • DecisionTreeClassifier: max_depth を浅くすると過学習を抑えやすい
  • 単体モデルで基準が見えたら、次節で RandomForestGradientBoosting へ広げる

2-4. アンサンブル学習は複数モデルの強みを束ねる

アンサンブル学習は、複数の弱学習器や複数の木を組み合わせて、単体モデルより精度や安定性を高める考え方です。sklearn で最初に押さえるべきなのは、bagging 系の RandomForest と、boosting 系の AdaBoostGradientBoosting です。

手法 代表クラス 仕組み 強み 注意点
RandomForest RandomForestClassifier / RandomForestRegressor 複数の決定木を並列に学習して平均や多数決を取る 強いベースライン、スケーリングに比較的強い、重要度も見やすい モデルが大きくなりやすく、細かな解釈はしにくい
AdaBoost AdaBoostClassifier / AdaBoostRegressor 前段で外したサンプルへ重みを寄せて順に学習する 浅い木でも改善しやすく、構造が比較的分かりやすい ノイズや外れ値に引っ張られやすい
GradientBoosting GradientBoostingClassifier / GradientBoostingRegressor 前段の残差や誤差を順に埋めるように学習する 非線形と特徴量相互作用を捉えやすく、精度重視で強い learning_rate と木の深さの調整が重要

まず見るべきパラメータは次のとおりです。

  • n_estimators: 木や弱学習器の本数。増やすと安定しやすいが計算は重くなる
  • learning_rate: AdaBoostGradientBoosting の更新幅。小さくするほど n_estimators を増やす考え方が基本になる
  • max_depth: 各木の複雑さ。深くしすぎると過学習しやすい
  • subsample: GradientBoosting で一部サンプルを使う設定。過学習抑制に効くことがある

分類アンサンブルの比較例

from sklearn.ensemble import AdaBoostClassifier, GradientBoostingClassifier, RandomForestClassifier
from sklearn.metrics import accuracy_score, f1_score

ensemble_classifiers = {
    "RandomForest": RandomForestClassifier(n_estimators=200, random_state=17),
    "AdaBoost": AdaBoostClassifier(n_estimators=200, learning_rate=0.5, random_state=17),
    "GradientBoosting": GradientBoostingClassifier(
        n_estimators=200,
        learning_rate=0.05,
        max_depth=2,
        random_state=17,
    ),
}

for name, model in ensemble_classifiers.items():
    model.fit(X_train, y_train)
    pred = model.predict(X_test)
    print(name)
    print("accuracy:", round(accuracy_score(y_test, pred), 4))
    print("f1:", round(f1_score(y_test, pred), 4))
    print("-" * 40)

回帰アンサンブルの比較例

from sklearn.ensemble import AdaBoostRegressor, GradientBoostingRegressor, RandomForestRegressor
from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score

ensemble_regressors = {
    "RandomForestRegressor": RandomForestRegressor(n_estimators=200, random_state=17),
    "AdaBoostRegressor": AdaBoostRegressor(n_estimators=200, learning_rate=0.1, random_state=17),
    "GradientBoostingRegressor": GradientBoostingRegressor(
        n_estimators=200,
        learning_rate=0.05,
        max_depth=2,
        random_state=17,
    ),
}

for name, model in ensemble_regressors.items():
    model.fit(X_reg_train, y_reg_train)
    pred = model.predict(X_reg_test)
    print(name)
    print("MAE:", round(mean_absolute_error(y_reg_test, pred), 3))
    print("RMSE:", round(mean_squared_error(y_reg_test, pred, squared=False), 3))
    print("R2:", round(r2_score(y_reg_test, pred), 3))
    print("-" * 40)

木系アンサンブルは、特徴量重要度の確認にも使いやすいです。

import pandas as pd
from sklearn.ensemble import RandomForestClassifier

rf_model = RandomForestClassifier(n_estimators=300, random_state=17)
rf_model.fit(X_train, y_train)

importance = pd.Series(rf_model.feature_importances_, index=X_train.columns)
print(importance.sort_values(ascending=False).head(10))

重要度はそのまま答えではありませんが、どの列が効いているかの当たりを付けるには便利です。特徴量選択や説明の入口として使い、必要なら permutation importance や SHAP など別の見方で補強します。

2-5. 教師なし学習も sklearn の重要領域

クラスタリングと次元削減も、sklearn を理解するうえで外せません。教師なし学習は教師あり学習のような「予測」ではなく、「構造を見つける」ために使います。

クラスタリング

手法 クラス 向いている場面
KMeans KMeans 基本クラスタリング
階層クラスタリング AgglomerativeClustering 階層構造を見たい
DBSCAN DBSCAN ノイズ込みで密度の塊を見たい
MeanShift MeanShift クラスタ数を決めにくい

次元削減

手法 クラス 向いている場面
PCA PCA 分散の大きい方向を残したい
KernelPCA KernelPCA 非線形構造も圧縮したい
TruncatedSVD TruncatedSVD 疎行列、テキスト特徴
NMF NMF 非負データの分解
from sklearn.cluster import AgglomerativeClustering, DBSCAN, KMeans, MeanShift
from sklearn.decomposition import KernelPCA, NMF, PCA, TruncatedSVD

教師なし学習は、EDA の延長や可視化だけでなく、特徴量エンジニアリングとしても有効です。たとえば PCA で次元を圧縮して線形モデルへ渡したり、KMeans のクラスタ番号を新しい特徴量として加えたりできます。

from sklearn.cluster import KMeans
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler

scaled_reg = StandardScaler().fit_transform(X_reg_train)

cluster_feature = KMeans(n_clusters=4, random_state=17, n_init=10).fit_predict(scaled_reg)
pca_features = PCA(n_components=3).fit_transform(scaled_reg)

X_reg_engineered = pd.DataFrame(
    pca_features,
    columns=["pca_1", "pca_2", "pca_3"],
    index=X_reg_train.index,
)
X_reg_engineered["cluster_id"] = cluster_feature

print(X_reg_engineered.head())

このとき重要なのは、クラスタリングも次元削減も必ず学習データで fit() し、テストデータにはその結果を transform() して使うことです。ここを混ぜるとリークになります。

2-6. 評価指標は目的に応じて変える

分類の評価指標

指標 何を見るか 向いている場面
accuracy_score 全体の正解率 クラス比率が大きく偏っていないとき
precision_score 陽性予測のうち正しかった割合 誤検知を減らしたいとき
recall_score 実際の陽性を拾えた割合 見逃しを減らしたいとき
f1_score precision と recall の調和平均 両方をバランスよく見たいとき
confusion_matrix 何をどう間違えたか 実務上ほぼ必須
ROC-AUC 閾値全体での識別性能 陽性率が極端すぎないとき
PR 曲線 precision と recall の関係 陽性が少ないとき
from sklearn.metrics import classification_report, confusion_matrix, precision_recall_curve, roc_curve, auc

svc_model = Pipeline([
    ("scaler", StandardScaler()),
    ("model", SVC(probability=True, random_state=17)),
])

svc_model.fit(X_train, y_train)
svc_pred = svc_model.predict(X_test)
svc_prob = svc_model.predict_proba(X_test)[:, 1]

print(confusion_matrix(y_test, svc_pred))
print(classification_report(y_test, svc_pred))

precision, recall, threshold_pr = precision_recall_curve(y_test, svc_prob)
fpr, tpr, threshold_roc = roc_curve(y_test, svc_prob)

print("ROC-AUC:", round(auc(fpr, tpr), 4))

predict() で得る 0/1 だけを見るのではなく、predict_proba() による確率を見て閾値調整することが大切です。

import numpy as np
from sklearn.metrics import f1_score

# 閾値 0.5 ではなく 0.7 に上げて厳しめに陽性判定する
pred_threshold = np.where(svc_prob >= 0.7, 1, 0)

print("f1:", round(f1_score(y_test, pred_threshold), 4))

回帰の評価指標

指標 何を見るか 向いている場面
mean_absolute_error 平均的なズレ 解釈しやすさ重視
mean_squared_error 大きな誤差を強く罰する 大外しを避けたい
RMSE MSE を元の単位へ戻したもの 現場説明に向く
mean_absolute_percentage_error 相対誤差 スケールの違う系列比較。ただし 0 に注意
r2_score どれだけ説明できたか 全体像の把握
from sklearn.metrics import mean_absolute_percentage_error

lr_model = LinearRegression()
lr_model.fit(X_reg_train, y_reg_train)
lr_pred = lr_model.predict(X_reg_test)

print("MAE:", round(mean_absolute_error(y_reg_test, lr_pred), 3))
print("MSE:", round(mean_squared_error(y_reg_test, lr_pred), 3))
print("MAPE:", round(mean_absolute_percentage_error(y_reg_test, lr_pred), 3))
print("R2:", round(r2_score(y_reg_test, lr_pred), 3))

3. チューニングと改善

機械学習で性能を上げるときは、モデル名を次々変えるより、改善の順番を守る方が効率的です。おすすめは次の順です。

  1. データリークがないか確認する
  2. 欠損処理とエンコーディングを見直す
  3. スケーリングや非線形変換を見直す
  4. 基準モデルを作って比較する
  5. 評価指標を適切に選ぶ
  6. 交差検証で安定性を見る
  7. パラメータ探索を行う
  8. 必要ならアンサンブルやモデル融合を試す

3-1. 過学習と未学習を見分ける

訓練スコアとテストスコアの差から過学習を見て、基本は次の見方です。

  • 訓練もテストも低い: 未学習
  • 訓練だけ高く、テストが低い: 過学習
  • 両方そこそこ高い: バランスがよい可能性が高い
rf_model = RandomForestClassifier(n_estimators=300, random_state=17)
rf_model.fit(X_train, y_train)

print("train:", round(rf_model.score(X_train, y_train), 4))
print("test:", round(rf_model.score(X_test, y_test), 4))

3-2. クロスバリデーションで偶然を減らす

単一の train/test split だけでは、たまたま良かった、たまたま悪かった、という可能性が残ります。分類タスクなら、まず StratifiedKFoldcross_val_score() を使うと、分割ごとのクラス比率をそろえやすくなります。

from sklearn.model_selection import StratifiedKFold, cross_val_score

# 分類では各 fold のクラス比率をなるべくそろえる
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=17)

cv_scores = cross_val_score(
    Pipeline([
        ("scaler", StandardScaler()),
        ("model", LogisticRegression(max_iter=500, random_state=17)),
    ]),
    X,
    y,
    cv=cv,
)

# 各 fold のスコアと平均スコアを確認する
print(cv_scores)
print(cv_scores.mean())

分類タスクでは、まず StratifiedKFold を基準に考えると分かりやすいです。回帰やクラス比率を意識しなくてよい場面では KFold も使います。

3-3. グリッドサーチは「当たりを付けてから」行う

GridSearchCV は便利ですが、やみくもに広い範囲を総当たりすると時間がかかります。まずはモデル特性に合わせて「効きそうなパラメータ」に絞るのが基本です。

代表的なチューニング観点

  • RandomForest: n_estimators, max_depth, min_samples_split, max_features, max_samples
  • SVC: C, gamma, kernel
  • KNeighbors: n_neighbors, weights
  • LogisticRegression: C, penalty, solver
  • GradientBoosting: n_estimators, learning_rate, max_depth, subsample
from sklearn.model_selection import GridSearchCV

rf_pipeline = Pipeline([
    ("model", RandomForestClassifier(random_state=17)),
])

param_grid = {
    "model__n_estimators": [100, 200, 300],
    "model__max_depth": [3, 5, None],
    "model__min_samples_split": [2, 5, 10],
    "model__max_features": ["sqrt", 0.6, 1.0],
}

grid = GridSearchCV(
    rf_pipeline,
    param_grid=param_grid,
    cv=5,
    scoring="f1",
    n_jobs=-1,
)

grid.fit(X_train, y_train)

print(grid.best_params_)
print(grid.best_score_)

候補空間が広いときは RandomizedSearchCV も有効です。特に木系モデルや SVM では、最初に広く当たりを取る手段として使いやすいです。

3-4. PipelineColumnTransformer で処理漏れを防ぐ

実務では、数値列とカテゴリ列を別々に処理してからモデルへ渡すことが多く、そのときに PipelineColumnTransformer が非常に重要になります。

import numpy as np
import pandas as pd
from sklearn.compose import ColumnTransformer
from sklearn.impute import SimpleImputer
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder, StandardScaler

df_mix = pd.DataFrame(
    {
        "age": [22, 35, 41, np.nan, 29],
        "income": [300, 520, 610, 480, np.nan],
        "city": ["Tokyo", "Osaka", "Tokyo", "Nagoya", None],
        "plan": ["A", "B", "A", "C", "B"],
        "target": [0, 1, 1, 0, 1],
    }
)

# 目的変数と説明変数を分ける
X_mix = df_mix.drop(columns=["target"])
y_mix = df_mix["target"]

numeric_features = ["age", "income"]
categorical_features = ["city", "plan"]

# 数値列は中央値補完のあとで標準化する
numeric_transformer = Pipeline([
    ("imputer", SimpleImputer(strategy="median")),
    ("scaler", StandardScaler()),
])

# カテゴリ列は最頻値補完のあとで One-Hot 化する
categorical_transformer = Pipeline([
    ("imputer", SimpleImputer(strategy="most_frequent")),
    ("onehot", OneHotEncoder(handle_unknown="ignore")),
])

# 列ごとに適切な前処理を振り分ける
preprocess = ColumnTransformer([
    ("num", numeric_transformer, numeric_features),
    ("cat", categorical_transformer, categorical_features),
])

# 前処理とモデルを 1 本の Pipeline にまとめる
pipeline = Pipeline([
    ("preprocess", preprocess),
    ("model", LogisticRegression(max_iter=500, random_state=17)),
])

# fit() すると前処理から学習まで順に実行される
pipeline.fit(X_mix, y_mix)

# predict() でも学習時と同じ前処理が自動で使われる
print(pipeline.predict(X_mix))

この形にしておくと、学習時と推論時で同じ処理が必ず適用されます。処理漏れを防ぐうえで、実務ではほぼ標準形です。

3-5. モデル融合は単体モデルの後に試す

2-4 で見た RandomForestGradientBoosting は、1 つのアルゴリズムの中で完結するアンサンブルです。ここで扱うのは、それとは別に、性質の違う完成済みモデルをあとから組み合わせる VotingStacking です。

sklearn では次のようなモデル融合ができます。

  • VotingClassifier / VotingRegressor: 複数モデルの多数決・平均
  • StackingClassifier / StackingRegressor: 複数モデルの予測をさらに別モデルへ食わせる
from sklearn.ensemble import StackingClassifier, VotingClassifier

voting_model = VotingClassifier(
    estimators=[
        ("lr", Pipeline([("scaler", StandardScaler()), ("model", LogisticRegression(max_iter=500, random_state=17))])),
        ("rf", RandomForestClassifier(n_estimators=200, random_state=17)),
        ("svc", Pipeline([("scaler", StandardScaler()), ("model", SVC(probability=True, random_state=17))])),
    ],
    voting="soft",
)

stacking_model = StackingClassifier(
    estimators=[
        ("rf", RandomForestClassifier(n_estimators=200, random_state=17)),
        ("svc", Pipeline([("scaler", StandardScaler()), ("model", SVC(probability=True, random_state=17))])),
    ],
    final_estimator=LogisticRegression(max_iter=500, random_state=17),
)

モデル融合で精度が上がることはありますが、まず単体モデルや 2-4 の基本アンサンブルで処理と評価が正しくできていることが前提です。未整理の処理を積んだまま融合へ行くと、改善理由が説明できなくなります。

3-6. 改善の順番を実務向けに整理する

改善で迷ったら、次の順で確認すると無駄が減ります。

  1. 目的変数と評価指標は合っているか
  2. 欠損補完の方法は妥当か
  3. カテゴリ処理は OneHotEncoderhandle_unknown を正しく使えているか
  4. 距離ベース・線形モデルに対してスケーリングを忘れていないか
  5. 文字列列をそのまま捨てず、Series.str で分解できないか
  6. 木モデルが過学習していないか
  7. 交差検証で安定しているか
  8. パラメータ探索の範囲が広すぎないか、狭すぎないか
  9. そもそも別系統のモデルへ切り替えるべきではないか

4. sklearn のモジュール早見表

sklearn はモジュール単位で覚えるより、実務フローと対応付けて覚える方が探しやすくなります。

モジュール 主な役割
linear_model 線形回帰、ロジスティック回帰、Ridge、Lasso
neighbors KNN 系
naive_bayes ナイーブベイズ
tree 決定木
ensemble ランダムフォレスト、AdaBoost、GBDT、Voting、Stacking
svm SVC、SVR
preprocessing スケーリング、エンコーディング、ビニング
impute 欠損補完
feature_selection 特徴量選択
metrics 評価指標
model_selection train/test split、CV、GridSearch
pipeline Pipeline
compose ColumnTransformer
cluster クラスタリング
decomposition PCA などの次元削減

5. 学習順のおすすめ

初学者が無理なく理解するなら、次の順が分かりやすいです。

  1. LinearRegressionLogisticRegression
  2. DecisionTreeClassifier / DecisionTreeRegressor
  3. RandomForestClassifier / RandomForestRegressor
  4. KNeighborsClassifier / KNeighborsRegressor
  5. SVC / SVR
  6. StandardScalerOneHotEncoderSimpleImputer
  7. PipelineColumnTransformer
  8. KFoldcross_val_scoreGridSearchCV
  9. KMeansPCA

この順に進めると、「まず予測」「次に非線形」「その後で特徴量処理と運用形」という流れで理解しやすくなります。

6. 実務で特に頻出の組み合わせ

短く答えられるようにしておきたいのは次の組み合わせです。

  1. 数値中心の分類: StandardScaler + LogisticRegression
  2. 強いベースライン分類: RandomForestClassifier
  3. 距離ベース分類: StandardScaler + KNeighborsClassifier
  4. マージン最大化の分類: StandardScaler + SVC
  5. 数値中心の回帰: LinearRegressionRidgeRandomForestRegressor
  6. 混在データの実務形: ColumnTransformer + Pipeline
  7. 再現性ある評価: cross_val_score または GridSearchCV

補足として、XGBoost や LightGBM は sklearn 本体ではありませんが、scikit-learn 互換の API で扱われることが多く、実務でも併せて聞かれやすいで、この周辺知識として押さえておくと役立ちます。


まとめ

scikit-learn を単なる「モデル一覧」として覚えると、何をいつ使うかが曖昧になりやすいです。実務では、次の流れで覚えるのが最も整理しやすくなります。

  1. まず課題を定義し、その前提で EDA と仮説検証を進める
  2. 数値特徴量とカテゴリ特徴量を分けて処理する
  3. 課題に合った回帰モデル・分類モデルの基準を作る
  4. RandomForestAdaBoostGradientBoosting などのアンサンブル学習へ広げる
  5. PCA やクラスタリングも特徴量エンジニアリングとして活用する
  6. 指標を目的に合わせて選ぶ
  7. PipelineColumnTransformer で再現性を確保する
  8. 交差検証、パラメータ探索、必要に応じたモデル融合で改善する

EDA は pandasnumpymatplotlibseaborn が主役であり、その後の特徴量エンジニアリング・モデル化・評価・改善を sklearn が支える、という役割分担で理解すると全体像が非常にきれいにつながります。さらに sklearn は、前処理、モデル、評価、Pipeline をばらばらに覚えるより、課題整理から改善までの一連の流れとしてつなげて理解する方が実務でははるかに使いやすくなります。

0
1
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
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?