Technical Note: Exporting Classification Results in QGIS
QGIS allows feature data to be displayed using various classification methods. I explored a way to export these classification results—represented by values such as 1 through 10, depending on the number of classes—into a new data field. I created a convenient Python script that enables the export of this data regardless of the classification method or number of classes used in the QGIS display.
QGISでは地物データを多用な方法で分類表示できます。この分類表示結果を区分数に応じて1~10などの数値データで新たなフィールドに書き出す方法を検討しました。QGIS画面で表示したどのような分類方法、分類数でもデータ書き出しができる便利なPythonスクリプトを作成しました。
1 フィールド計算機を使った方法 その1
1-1 等間隔分類
サンプルデータ(0~90の範囲内にある小数点のある数値、レコード数3024、フィールドkousaに格納されている。)
フィールドkousaについて、シンボロジ→連続値による定義→フィールド入力→等間隔分類(分類数10)で画面上で分類しました。
画面上での分類結果
この画面上での分類結果を、数値の小さい方から1~10の数字を割り当てた区分データに変換し、それを新たなフィールドkousakubun3に出力する式(例)とフィールド計算機画面は次の通りです。
式(例)
CASE
WHEN "kousa" IS NULL THEN NULL
WHEN maximum("kousa") = minimum("kousa") THEN 1
ELSE
-- kousaの値を1.0〜10.999の範囲に線形変換し、floorで整数化(1〜10になる)
floor(scale_linear("kousa", minimum("kousa"), maximum("kousa"), 1, 10.9999))END
フィールド計算機画面
これで無事等間隔分類を1~10の数値データに変換して、新たなフィールドに書き込むことができました。
1-2 等量分類
等量分類についての式は複雑になり過ぎて、ギブアップしました。
1-3 感想
等間隔分類はなんとかフィールド計算機で再現できましたが、等量分類はギブアップで、他の分類(丸め間隔、固定間隔、対数スケール、標準偏差、自然分類)は思考の意欲が生まれませんでした。
2 フィールド計算機を使った方法 その2
分類のアルゴリズムを再現するのではなく、QGIS操作で生じた分類結果そのものをフィールド計算機にとりこむ方法があります。
例えばシンボロジの分類結果が仮に、
1 : 2.14 ~ 5.72
2 : 5.72 ~ 8.31
3 : 8.31 ~ 11.45
...
10: 47.21 ~ 88.53
となった場合、フィールド計算機では、
CASE
WHEN "kousa" IS NULL THEN NULL
WHEN "kousa" <= 5.72 THEN 1
WHEN "kousa" <= 8.31 THEN 2
WHEN "kousa" <= 11.45 THEN 3
...
WHEN "kousa" <= 47.21 THEN 9
ELSE 10
END
とすれば、画面上の分類と同じ分類を属性として保存できます。
この方法なら、どのような分類でも、さらに一部を意図的に変更したものでも、対応できます。
ただし、分類の数値を手でひろい入力する必要があり、AI時代にふさわしい方法とは言えません。
3 Pythonスクリプトを使った方法
次のPythonスクリプトをプラグイン→Pythonコンソール→エディタの表示でひらくエディタ画面にはりつければ、どのような分類でも1~10などの数値データを新たなフィールドのレコードに書き込むことができます。この方法も分類のアルゴリズムを再現するのではなく、QGIS操作で生じた分類結果そのものをフィールド計算機にとりこむ方法です。
このPythonスクリプトは、レイヤ名称aaa、対象フィールド名称bbb、新フィールド名称bbb_class、分類数10の場合で記述されていますが、それぞれ変更可能です。ChatGPT支援で作成しました。
from qgis.core import (
QgsProject,
QgsGraduatedSymbolRenderer,
QgsField
)
from qgis.PyQt.QtCore import QVariant
# ============================================================
# 設定
# ============================================================
LAYER_NAME = "aaa"
VALUE_FIELD = "bbb"
CLASS_FIELD = "bbb_class"
# ============================================================
# レイヤ取得
# ============================================================
layers = QgsProject.instance().mapLayersByName(LAYER_NAME)
if not layers:
raise Exception(f"レイヤ '{LAYER_NAME}' が見つかりません。")
layer = layers[0]
# bbb の存在確認
if layer.fields().indexFromName(VALUE_FIELD) == -1:
raise Exception(f"フィールド '{VALUE_FIELD}' が見つかりません。")
# ============================================================
# 現在のシンボロジを取得
# ============================================================
renderer = layer.renderer()
if not isinstance(renderer, QgsGraduatedSymbolRenderer):
raise Exception(
"現在のシンボロジが「連続値による定義」ではありません。"
)
# 分類に使用しているフィールドを確認
class_attribute = renderer.classAttribute()
if class_attribute != VALUE_FIELD:
raise Exception(
f"現在の分類フィールドは '{class_attribute}' です。"
f"'{VALUE_FIELD}' ではありません。"
)
# 現在画面で設定されている分類範囲を取得
ranges = list(renderer.ranges())
if len(ranges) != 10:
raise Exception(
f"現在の分類数は {len(ranges)} です。"
"設定分類にしてから実行してください。"
)
# ============================================================
# 分類範囲を確認表示
# ============================================================
print("現在の分類範囲")
print("------------------------------")
for i, r in enumerate(ranges, start=1):
print(
f"Class {i}: "
f"{r.lowerValue()} ~ {r.upperValue()}"
)
print("------------------------------")
# ============================================================
# bbb_class フィールドを作成
# ============================================================
class_idx = layer.fields().indexFromName(CLASS_FIELD)
if class_idx == -1:
if not layer.isEditable():
if not layer.startEditing():
raise Exception(
"レイヤを編集モードにできませんでした。"
)
success = layer.addAttribute(
QgsField(CLASS_FIELD, QVariant.Int)
)
if not success:
raise Exception(
f"フィールド '{CLASS_FIELD}' を作成できませんでした。"
)
layer.updateFields()
class_idx = layer.fields().indexFromName(CLASS_FIELD)
else:
if not layer.isEditable():
if not layer.startEditing():
raise Exception(
"レイヤを編集モードにできませんでした。"
)
# ============================================================
# 各地物を現在のシンボロジと同じ区分に分類
# ============================================================
count = 0
null_count = 0
unclassified_count = 0
for feature in layer.getFeatures():
value = feature[VALUE_FIELD]
# NULLの場合
if value is None or value == QVariant():
layer.changeAttributeValue(
feature.id(),
class_idx,
None
)
null_count += 1
continue
try:
value = float(value)
except (TypeError, ValueError):
layer.changeAttributeValue(
feature.id(),
class_idx,
None
)
unclassified_count += 1
continue
class_number = None
# QGISの graduated renderer と同様に
# 上から順番に該当する範囲を探す
for i, r in enumerate(ranges, start=1):
if (
r.lowerValue() <= value
and
value <= r.upperValue()
):
class_number = i
break
if class_number is not None:
layer.changeAttributeValue(
feature.id(),
class_idx,
class_number
)
count += 1
else:
layer.changeAttributeValue(
feature.id(),
class_idx,
None
)
unclassified_count += 1
# ============================================================
# 結果
# ============================================================
print("")
print("処理完了")
print("==============================")
print(f"分類した地物数 : {count}")
print(f"NULL : {null_count}")
print(f"分類不能 : {unclassified_count}")
print("==============================")
print("")
print(
"属性テーブルの新フィールドに "
"分類番号を書き込みました。"
)
print(
"内容を確認して問題なければ、"
"レイヤの編集内容を保存してください。"
)4 感想
レイヤの属性テーブル 例
今回作成Pythonスクリプトでどのような分類、分類数でも、新しいフィールドのレコードに分類を指標する1~10など数値を書き込むことができるようになりました。画面の分類結果をデータとして残せるようになりました。画面で多種分類方法をシミュレーションして(分布を確認して)、適切なもののデータを残せるのでとても便利です。

















































