2023-01-23

QThreadPool と QRunnable 〜 PySide6 〜

PySide (Qt for Python) は、Qt(キュート)の Python バインディングで、GUI などを構築するためのクロスプラットフォームなライブラリです。配布ライセンスは LGPL で公開されています。最新のバージョンは Qt6 に対応した PySide6(記事執筆時点で 6.4.2)です。

参考サイト [1] に QThreadPool と QRunnable を利用したマルチスレッディングの記事が判り易くまとめられていたので、早速記事を参考にしながら自分でもサンプルを作ってみました。

下記の OS 環境で動作確認をしました。

Fedora Linux 37 (Server Edition) x86_64
python3.11 python3-3.11.1-3.fc37.x86_64
PySide6 6.4.2

qt_threadpool.py

別スレッド側では簡単な処理しかしていませんが、QThread でゴリゴリと記述するのに比べると、QThreadPool と QRunnable を利用すると、随分とスッキリ記述できました。

ただ、QRunnable と Signal を多重継承ができなかったので、Signal 用に別クラスを作らなければならなかったのが、ちょっと面倒なことぐらいです。

このサンプルでは START ボタンを続けてクリックすれば、スレッドが次々できますが、そのようなマルチスレッディングが必要な場面は、自分の用途ではとりあえずありません。むしろ、スレッドが処理している間は、同じ処理のスレッドができないように管理しなければならない場面のほうが多そうです。

参考サイト

  1. Multithreading PySide6 applications with QThreadPool [2022-08-11]
  2. QThreadPool - Qt for Python
  3. QRunnable - Qt for Python

 

 

ブログランキング・にほんブログ村へ bitWalk's - にほんブログ村 にほんブログ村 IT技術ブログ オープンソースへ
にほんブログ村

オープンソース - ブログ村ハッシュタグ
#オープンソース



このエントリーをはてなブックマークに追加

2023-01-18

【備忘録】Python におけるループ変数のスコープ

なんかそうなんじゃないかな、と思っていたのですが、いままでしっかりと確認していませんでした。そもそも、必要がない限りはループ変数をループの外側では使わない癖みたいなものがあって、なんとなくループ外でループ変数を直接参照するのを避けていたような気がします。しかし、今回は気になって調べました。

いつもお世話になっている Stack Overflow に自分と同じ疑問を投稿したものがありました [1]。少しだけ手直ししましたが下記の内容です。

for foo in range(10):
    bar = 2
print(foo, bar)
9 2

Python では、ループ変数はループ内だけで有効になっているわけではない(for 文はスコープを作らない)ということですが、C/C++ や Java ではありえない結果です。

念の為、正式な説明なるものを探したところ、参考サイト [2] を見つけました。

これはバグではなく、意図的に設計されたもので、Python 1 からずっとそうです。

ということです。

Python は、必要に迫られて使い始めたプログラミング言語ということもあって、そもそもまじめに一から学んでいません。自戒を込めて、プログラミング言語が違えば、スコープの扱い方も異なるということで備忘録にしました。

参考サイト

  1. scope - Scoping in Python 'for' loops - Stack Overflow [2010-08-31]
  2. For Loop temporary variable scope should be local to For loop · Issue #88498 · python/cpython [2021-06-07]

 

 

ブログランキング・にほんブログ村へ bitWalk's - にほんブログ村 にほんブログ村 IT技術ブログ オープンソースへ
にほんブログ村

オープンソース - ブログ村ハッシュタグ
#オープンソース



このエントリーをはてなブックマークに追加

2023-01-13

QAbstractButton の利用 〜 PySide6 〜

PySide (Qt for Python) は、Qt(キュート)の Python バインディングで、GUI などを構築するためのクロスプラットフォームなライブラリです。配布ライセンスは LGPL で公開されています。最新のバージョンは Qt6 に対応した PySide6(記事執筆時点で 6.4.2)です。

PySide6 で利用できる GUI のウィジェット(部品)は機能が豊富に備わっているので使っていて楽しいのですが、極めて大量かつ同じウィジェット(例えばチェックボックスだけのウィジェット)を表示したいときに、機能が豊富が故にインスタンスあたりのサイズが大きいのか、メモリがたくさん消費されて困ります。

そんな時、特定の目的に機能を絞った軽いウィジェットを作ることができれば、と考えました。チェックボックスのように抽象クラスの QAbstractButton [1] を継承、実装しているボタン系ウィジェットであれば、必要な機能だけを実装したウィジェットを作れば、メモリの消費を抑えられるかもしれません。

そう考えて、まず QAbstractButton から作ったウィジェットのサンプルが公開されていないか探しました。なかなか都合の良い例がありませんでしたが、Toggle ウィジェット(クラス)[2] というのを見つけましたので、PySide6 用に少し直して紹介します。なお、ここでは Swicth と、ウィジェット(クラス)の名前を変えました。

下記の OS 環境で動作確認をしました。

Fedora Linux 37 (Server Edition) x86_64
python3.11 python3-3.11.1-1.fc37.x86_64
PySide6 6.4.2

抽象クラス QAbstractButton を直接継承して機能を実装した Switch クラスを下記に示しました。

qt_custom_switch.oy

Switch は小さなウィジェットです。実行例を示しました。

Switch is True

このサンプルで判ったことは、QAbstractButton を利用するには、ウィジェットを描画して表示する paintEvent メソッドと、表示サイズの推奨値を保持している sizeHint メソッドを、最低限オーバーライドして機能を実装する必要があるということでした。😁

さて、くだんのチェックボックスだけのウィジェットですが、QAbstractButtton を継承して、チェックボックスのチェック/アンチェックのイメージを切り替えて表示するようにしたものを作成したところ、これでメモリ使用量をざっくり3割程度に削減できました。これは 100 × 1000 以上の大きなチェックボックスのマトリックスを、スクロールバー付きで表示するような特殊な用途です。

参考サイト

  1. QAbstractButton - Qt for Python
  2. python source code of qutilities
  3. QPainter - Qt for Python

 

 

ブログランキング・にほんブログ村へ bitWalk's - にほんブログ村 にほんブログ村 IT技術ブログ オープンソースへ
にほんブログ村

オープンソース - ブログ村ハッシュタグ
#オープンソース



このエントリーをはてなブックマークに追加

2023-01-08

【備忘録】主成分分析と一元配置分散分析 ~ Python ~

主成分分析 (Principal Component Analysis, PCA) は、1観測あたり多数の次元/特徴を含む大規模データセットを分析し、最大限の情報量を保持しながらデータの解釈可能性を高め、多次元データの可視化を可能にする手法としてよく利用されています。形式的には、PCA はデータセットの次元を減少させる統計的手法です。これは、データの変動(の大部分)が、初期データよりも少ない次元で記述できるような新しい座標系にデータを線形変換することにより達成できます。

Wikipedia より引用、翻訳・編集

データ解析で頻繁に主成分分析 (PCA) を利用しています。主成分分析をはじめ、よく使用する機械学習の処理部分などは、いつも過去に使ったスクリプトから必要部分をコピペして使い回してしまっているので、思い違いや間違いがあれば、そのままずっと引きずってしまう可能性があります。ちょっと心配になってきたので、簡単なサンプルを使ってスクリプトを整理しようとしています。

まずは主成分分析

今回の目的は、主成分分析で変換された主成分それぞれが、ターゲットに対してどの程度影響があるかを、一元配置分散分析(以降、単に分散分析と表します)を利用して検定することです。

下記の OS 環境で動作確認をしています。

Fedora Linux 37 Workstation x86_64
Python 3.10.9

JupyterLab の Notebook 上で動作確認をしています。今回も主成分分析の結果を利用しますので、まず、主成分分析をまとめて処理してしまいます。本ブログ記事 [1] で紹介した Iris データセットの主成分分析の処理を、必要な部分だけぎゅっとひとつにまとめました。

import pandas as pd
import dataframe_image as dfi
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA

# https://www.kaggle.com/datasets/saurabh00007/iriscsv
filename = 'Iris.csv'
df = pd.read_csv(filename, index_col=0)
dfi.export(df.head(), 'table_041_iris.png')
print(df.head())
cols_x = list(df.columns[0:4])
col_y = df.columns[4]

# model pipeline for PCA
pipe = Pipeline(steps=[
    ('scaler', StandardScaler()),
    ('PCA', PCA()),
])
features = df[cols_x]
pipe.fit(features)

# PCA scores
scores = pipe.transform(features)
df_pca = pd.DataFrame(
    scores,
    columns=["PC{}".format(x + 1) for x in range(scores.shape[1])],
    index=df.index
)
cols_pc = list(df_pca.columns)
df_pca.insert(0, col_y, df[col_y].copy())
dfi.export(df_pca.head(), 'table_041_iris_PCA.png')
print(df_pca.head())

これが、読み込んだ Iris のデータセット df(最初の5行)です。

 

次が主成分分析をしたデータセット df_pca(最初の5行)です。

 

生データの分散分析

分散分析は SciPy の stats ライブラリの f_oneway を利用します。個々の因子 (SepalLengthCm, SepalWidthCm, PetalLengthCm, PetalWidthCm) に対し、ターゲット (Species) それぞれ (Iris-setosa, Iris-versicolor, Iris-virginica) の群をリストにまとめて、f_oneway() に渡します。

import numpy as np
from scipy import stats

list_species = list(set(df_pca[col_y]))
cols_stat = ['F-value', 'p-value']

df_anova = pd.DataFrame(columns=cols_stat)
for x in cols_x:
    args = [df[df[col_y] == list_species[i]][x] for i, species in enumerate(list_species)]
    f_value, p_value = stats.f_oneway(*args)
    result = pd.DataFrame(
        np.array([f_value, p_value]).reshape(1, 2),
        columns=cols_stat,
        index=[x]
    )
    # append row
    df_anova = pd.concat([df_anova, result], axis=0)

print(df_anova)
                   F-value       p-value
SepalLengthCm   119.264502  1.669669e-31
SepalWidthCm     47.364461  1.327917e-16
PetalLengthCm  1179.034328  3.051976e-91
PetalWidthCm    959.324406  4.376957e-85

F-value を棒グラフにして表示します。

import matplotlib.pyplot as plt
plt.rcParams['font.size'] = 12

fig, ax = plt.subplots()
plt.bar(df_anova.index, df_anova['F-value'])
ax.tick_params(axis='x', which='major', labelsize=10)
ax.set_ylabel('F-value')
plt.grid(axis='y',linestyle='dotted')
plt.savefig('iris_041_Oneway_ANOVA_raw.png')
plt.show()

 

主成分の分散分析

生データと同様に、主成分 (PC1 - PC4) に対しても分散分析をします。

df_pca_anova = pd.DataFrame(columns=cols_stat)
for pc in cols_pc:
    args = [df_pca[df_pca[col_y] == list_species[i]][pc] for i, species in enumerate(list_species)]
    f_value, p_value = stats.f_oneway(*args)
    result = pd.DataFrame(
        np.array([f_value, p_value]).reshape(1, 2),
        columns=cols_stat,
        index=[pc]
    )
    # append row
    df_pca_anova = pd.concat([df_pca_anova, result], axis=0)

print(df_pca_anova)
         F-value       p-value
PC1  1043.159859  1.412319e-87
PC2    14.429384  1.897877e-06
PC3     5.290448  6.043825e-03
PC4     1.636495  1.981874e-01

F-value を棒グラフにして表示します。

fig, ax = plt.subplots()
plt.bar(df_pca_anova.index, df_pca_anova['F-value'])
ax.set_ylabel('F-value')
plt.grid(axis='y',linestyle='dotted')
plt.savefig('iris_042_Oneway_ANOVA_PCA.png')
plt.show()

 

生データと主成分を分散分析した結果を比較して、あれこれ統計的な解釈をすることはできないのですが、主成分分析の結果、主成分1 (PC1) の影響がほとんどであるのに対し、生データでは PetalLengthCm(花弁の長さ)と PetalWidthCm(花弁の幅)の F-value が大きいことを考えると、Species の違いは花弁の長さと幅(= 面積?)で特徴づけられ、それは主成分分析をすると主成分1 (PC1) に現れていると考えられます。

参考までに、本ブログ記事 [1] で紹介した主成分負荷量のプロットを再掲します。

 

scikit-learn による分散分析

結果は同じですが、scikit-learn の SelectKBest を利用しても分散分析ができます。主成分を分散分析すると次のようになります。

from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import f_classif

selector = SelectKBest(f_classif, k='all')
selector.fit(df_pca[cols_pc], df_pca[col_y])
print('features : ', selector.feature_names_in_)
print('scores   : ', selector.scores_)
print('p-values : ', selector.pvalues_)
features :  ['PC1' 'PC2' 'PC3' 'PC4']
scores   :  [1043.15985867   14.42938424    5.29044846    1.63649473]
p-values :  [1.41231945e-87 1.89787708e-06 6.04382459e-03 1.98187397e-01]

score (F-value) を棒グラフにして表示します。

fig, ax = plt.subplots()
plt.bar(selector.feature_names_in_, selector.scores_)
ax.set_ylabel('Score (= F-value)')
plt.grid(axis='y',linestyle='dotted')
plt.savefig('iris_043_Feature_Selection.png')
plt.show()

 

参考サイト

  1. bitWalk's: 【備忘録】主成分分析 ~ Python / Scikit-Learn ~ [2022-12-27]
  2. bitWalk's: 【備忘録】主成分分析 (2) ~ Python / Scikit-Learn ~ [2023-01-03]
  3. bitWalk's: 【備忘録】主成分分析 (3) ~ Python / Scikit-Learn ~ [2023-01-04]

 

 

ブログランキング・にほんブログ村へ bitWalk's - にほんブログ村 にほんブログ村 IT技術ブログ オープンソースへ
にほんブログ村

オープンソース - ブログ村ハッシュタグ
#オープンソース



このエントリーをはてなブックマークに追加

2023-01-04

【備忘録】主成分分析 (3) ~ Python / Scikit-Learn ~

主成分分析 (Principal Component Analysis, PCA) は、1観測あたり多数の次元/特徴を含む大規模データセットを分析し、最大限の情報量を保持しながらデータの解釈可能性を高め、多次元データの可視化を可能にする手法としてよく利用されています。形式的には、PCA はデータセットの次元を減少させる統計的手法です。これは、データの変動(の大部分)が、初期データよりも少ない次元で記述できるような新しい座標系にデータを線形変換することにより達成できます。

Wikipedia より引用、翻訳・編集

データ解析で頻繁に主成分分析 (PCA) を利用しています。主成分分析をはじめ、よく使用する機械学習の処理部分などは、いつも過去に使ったスクリプトから必要部分をコピペして使い回してしまっているので、思い違いや間違いがあれば、そのままずっと引きずってしまう可能性があります。ちょっと心配になってきたので、簡単なサンプルを使ってスクリプトを整理しようとしています。

もう少し、マハラノビス距離

前のブログ記事 [1] では、二次元に限定してマハラノビス距離を算出して、マハラノビス距離を等高線にして散布図に重ね合わせましたが、本記事ではもう少しマハラノビス距離を扱うことにします。

下記の OS 環境で動作確認をしています。

Fedora Linux 37 Workstation x86_64
Python 3.10.9

JupyterLab の Notebook 上で動作確認をしています。今回も、主成分分析の結果を利用してマハラノビス距離を算出しますので、まず、主成分分析をまとめて処理してしまいます。本ブログ記事 [2] で紹介した Iris データセットの主成分分析の処理を、必要な部分だけぎゅっとひとつにまとめました。

import pandas as pd
import dataframe_image as dfi
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA

# https://www.kaggle.com/datasets/saurabh00007/iriscsv
filename = 'Iris.csv'
df = pd.read_csv(filename, index_col=0)
cols_x = list(df.columns[0:4])
col_y = df.columns[4]

# model pipeline for PCA
pipe = Pipeline(steps=[
    ('scaler', StandardScaler()),
    ('PCA', PCA()),
])
features = df[cols_x]
pipe.fit(features)

# PCA scores
scores = pipe.transform(features)
df_pca = pd.DataFrame(
    scores,
    columns=["PC{}".format(x + 1) for x in range(scores.shape[1])],
    index=df.index
)
cols_pc = list(df_pca.columns)
df_pca.insert(0, col_y, df[col_y].copy())
dfi.export(df_pca.head(), 'table_031_iris_PCA.png')
df_pca.head()

 

参考サイト [3] で紹介されている、MLE (The Maximum Likelihood Estimator) と MCD (The Minimum Covariance Determinant estimator) を用いた2つのマハラノビス距離を、二次元(2つの主成分)ではなく全ての主成分(と言っても、このサンプルでは PC1 ~ PC4 の4種類ですが…)を適用して算出します。

from sklearn.covariance import EmpiricalCovariance, MinCovDet

X = df_pca[cols_pc]
emp_cov = EmpiricalCovariance().fit(X)
robust_cov = MinCovDet().fit(X)

df_pca['MLE'] = emp_cov.mahalanobis(X)
df_pca['MCD'] = robust_cov.mahalanobis(X)
dfi.export(df_pca.head(), 'table_32_iris_PCA_MD.png')
df_pca.head()

 

多次元を二次元の等高線でどう表現するかを考えると、どの断面を切り出すのが適当か判断できないので、さしあたって Violin Plot でプロットしてみました。

import matplotlib.pyplot as plt
import seaborn as sns
plt.rcParams['font.size'] = 14
sns.set()

for md in ['MLE', 'MCD']:
    fig, ax = plt.subplots()
    sns.violinplot(data=df_pca, x='Species', y=md, showfliers=False, ax=ax)
    sns.stripplot(data=df_pca, x='Species', y=md, jitter=True, color='black', ax=ax)

    ax.set_ylabel('MD² (%s)' % md)
    ax.set_title('Principal Components / Mahalanobis Distance (%s)' % md)
    plt.savefig('iris_031_PCA_MD_%s.png' % md)
    plt.show()

 

二次元より大きい多次元データから算出したマハラノビス距離を、ユークリッド空間の二次元チャートに効果的に写像するのは難しいです。まずは、距離はそのまま距離として扱った方が良さそうです。時系列データといった順序のあるデータを扱う場合、マハラノビス距離は異常点の検出に威力を発揮することを期待しています。ブログで扱える適当な時系列的データセットがあれば、あらためてまとめたいと考えています。

参考サイト

  1. bitWalk's: 【備忘録】主成分分析 (2) ~ Python / Scikit-Learn ~ [2023-01-03]
  2. bitWalk's: 【備忘録】主成分分析 ~ Python / Scikit-Learn ~ [2022-12-27]
  3. Robust covariance estimation and Mahalanobis distances relevance — scikit-learn 1.2.0 documentation
  4. 【異常検知】マハラノビス距離を嚙み砕いて理解する (1) - Qiita [2022-01-02]
  5. 【異常検知】マハラノビス距離を嚙み砕いて理解する (2) - Qiita [2022-01-05]

 

 

ブログランキング・にほんブログ村へ bitWalk's - にほんブログ村 にほんブログ村 IT技術ブログ オープンソースへ
にほんブログ村

オープンソース - ブログ村ハッシュタグ
#オープンソース



このエントリーをはてなブックマークに追加