mecobalamin’s diary

人間万事塞翁が馬

https://help.hatenablog.com/entry/developer-option

pythonでirisのデータセットを使う

pythonでもirisのデータセットを使える
mecobalamin.hatenablog.com

こちらのサイトで紹介されている
Sklearnを使ってみる1 - ぴろの狂人日記
iris以外のデータセットも使える
scikit-learnのサンプルデータセットの一覧と使い方 | note.nkmk.me


今回はirisのデータセットの内容を確認し
統計処理を行ってグラフを作成する


まずデータセットを確認してみた
irisのデータセットはscikit-learnに含まれている
そこでscikit-learnのdatasetsをインポートする

from sklearn import datasets

irisのデータセットを読み込む

iris = datasets.load_iris()

データセットの型はsklearn.utils.Bunchで
辞書の書式でデータが記録されている

print(type(iris))
print(iris.keys())
print(iris.values())

結果がこちら

<class 'sklearn.utils.Bunch'>
dict_keys(['data', 'target', 'frame', 'target_names', 'DESCR', 'feature_names', 'filename'])
dict_values([array([[5.1, 3.5, 1.4, 0.2],
       [4.9, 3. , 1.4, 0.2],
~~~省略~~~
'petal length (cm)', 'petal width (cm)'], 
'D:\\Python\\Python37-32\\lib\\site-packages\\sklearn\\datasets\\data\\iris.csv'])

データセットについての説明もある

print(iris.DESCR)

データの統計値もある

    ============== ==== ==== ======= ===== ====================
                    Min  Max   Mean    SD   Class Correlation
    ============== ==== ==== ======= ===== ====================
    sepal length:   4.3  7.9   5.84   0.83    0.7826
    sepal width:    2.0  4.4   3.05   0.43   -0.4194
    petal length:   1.0  6.9   3.76   1.76    0.9490  (high!)
    petal width:    0.1  2.5   1.20   0.76    0.9565  (high!)
    ============== ==== ==== ======= ===== ====================


irisのデータセットをpandasのデータフレームに変換する
その時dataのカラム名をfeature_namesを当てる
またtargetをirisの種名に変えてdataのカラムに追加する

df = pd.DataFrame(iris.data, columns = iris.feature_names)
df['target'] = iris.target_names[iris.target]
print(df.head())

出力結果はこんな感じ
(見辛いのではてな記法で表組みした)

sepal length (cm) sepal width (cm) petal length (cm) petal width (cm) target
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

~~~以下略~~~

統計値を確認

print(df.mean())

当然だけどiris.DESCRで表示される結果と同じになる
繰り上がりがちょっと変な気がする

sepal length (cm)    5.843333
sepal width (cm)     3.057333
petal length (cm)    3.758000
petal width (cm)     1.199333
dtype: float64

統計値をまとめて出すこともできる

print({}.format(df.describe().T))

比較しやすいように行と列を入れ替えてある

                   count      mean       std  min  25%   50%  75%  max
sepal length (cm)  150.0  5.843333  0.828066  4.3  5.1  5.80  6.4  7.9
sepal width (cm)   150.0  3.057333  0.435866  2.0  2.8  3.00  3.3  4.4
petal length (cm)  150.0  3.758000  1.765298  1.0  1.6  4.35  5.1  6.9
petal width (cm)   150.0  1.199333  0.762238  0.1  0.3  1.30  1.8  2.5

pandasのgoupbyで種ごとの統計値を計算する

df_grouped = df.groupby(['target'])
print(df_grouped.mean())
            sepal length (cm)  sepal width (cm)  petal length (cm)  petal width (cm)
target                                                               
setosa                  5.006             3.428              1.462             0.246
versicolor              5.936             2.770              4.260             1.326
virginica               6.588             2.974              5.552             2.026


結果を棒グラフとヒートマップにして
画像として保存する

current_dpi = mpl.rcParams['figure.dpi']
print(current_dpi)

path = os.getcwd()
path = os.chdir(os.path.dirname(os.path.abspath(__file__)))
path = os.getcwd()

plt.figure()
df_grouped.mean().T.plot(kind = 'bar', yerr = df_grouped.std().T, rot = 0)
plt.savefig(path + '\\' + 'bar_graph_mean.png', dpi = current_dpi * 1.5)
plt.close()

plt.figure()
plt.figure(figsize=(8, 6))
sns.heatmap(df_grouped.mean().T, square = True, cmap = 'plasma')
plt.savefig(path + '\\' + 'heatmap_mean.png', dpi = current_dpi * 1.5)
plt.close()

グラフは以下の通り

ヒートマップのカラーマップは以下のサイトを参考にした
https://matplotlib.org/tutorials/colors/colormaps.html#grayscale-conversion


実際のコードはこんな感じ

import os

from pandas import Series, DataFrame
import pandas as pd

from sklearn import datasets

import matplotlib as mpl
import matplotlib.pyplot as plt
import seaborn as sns

pd.set_option('display.max_columns', 10)

iris = datasets.load_iris()
print(type(iris))
print(iris.keys())
print(iris.DESCR)

df = pd.DataFrame(iris.data, columns = iris.feature_names)
df['target'] = iris.target_names[iris.target]

print(df.head())
print(df.columns)

df_grouped = df.groupby(['target'])
df_mean = df_grouped.mean()
print(df_mean)

current_dpi = mpl.rcParams['figure.dpi']
print(current_dpi)

path = os.getcwd()
path = os.chdir(os.path.dirname(os.path.abspath(__file__)))
path = os.getcwd()

plt.figure()
df_grouped.mean().T.plot(kind = 'bar', yerr = df_grouped.std().T, rot = 0)
plt.savefig(path + '\\' + 'bar_graph_mean.png', dpi = current_dpi * 1.5)
plt.close()

plt.figure(figsize=(8, 6))
sns.heatmap(df_grouped.mean().T, square = True, cmap = 'plasma')
plt.savefig(path + '\\' + 'heatmap_mean.png', dpi = current_dpi * 1.5)
plt.close()

pandasでgroupbyを使う

pythonのpandasで表の集計をしている
groupbyが便利だったのでメモ

groupbyについて参考にした
Pandasのgroupbyを使った要素をグループ化して処理をする方法 - DeepAge

例えばirisのデータで種毎に平均値を計算してみる

pythonでもirisのデータが使えた
【python】iris(アヤメ)のデータセットをpandasとseabornを使って可視化する
ライブラリscikit-learnに含まれている

irisのカラムは5つ

[5 rows x 5 columns] Index(['sepal length (cm)', 'sepal width (cm)', 'petal length (cm)',
       'petal width (cm)', 'target'],
      dtype='object')

targetが種の名前

   sepal length (cm)  sepal width (cm)  petal length (cm)  petal width (cm)  \
0                5.1               3.5                1.4               0.2   
1                4.9               3.0                1.4               0.2   
2                4.7               3.2                1.3               0.2   
3                4.6               3.1                1.5               0.2   
4                5.0               3.6                1.4               0.2   

   target  
0  setosa  
1  setosa  
2  setosa  
3  setosa  
4  setosa

groupbyでtargetを指定するとtargetで各行がまとめられて
mean()で平均値を計算してくれる

df.groupby(['target']).mean()

df.groupby(['target'])の出力は以下の通りで数値を確認できない

<pandas.core.groupby.generic.DataFrameGroupBy object at 0x07FE0310>

dict(list(df.groupby(['target'])))とすると表示できる


実際に使用したコードはこれ

import pandas as pd
from sklearn import datasets

pd.set_option('display.max_columns', 5)

iris = datasets.load_iris()
df = pd.DataFrame(iris.data, columns=iris.feature_names)
df['target'] = iris.target_names[iris.target]

print(df.head())
print(df.columns)

print(df.groupby(['target']).mean())


計算結果

            sepal length (cm)  sepal width (cm)  petal length (cm)  \
target                                                               
setosa                  5.006             3.428              1.462   
versicolor              5.936             2.770              4.260   
virginica               6.588             2.974              5.552   

            petal width (cm)  
target                        
setosa                 0.246  
versicolor             1.326  
virginica              2.026  
[Finished in 1.624s]

列が省略されないようにするにはset_optionで指定する
Pandas DataFrameの表示を省略したくない時. Jupyter… | by takkii | Music and Technology | Medium

Atomで出力している
結果が折り返されるのが修正できない。。。

Pythonista3でTableviewを使う


PythonではじめるiOSプログラミング

PythonではじめるiOSプログラミング

Pythonisata3でui.tableviewを使うときにハマったのでメモ

.pyuiファイルにTextFieldを配置したとき
TextFieldの中身はsuperviewで読み出せる
例えばTextFieldの名前をtextfield1とするとき
以下のように書く

def on_textfield(sender):
    a = sender.superview['textfield1']

これがTableViewの場合だとsuperviewの前にtableviewを挟まないといけない
例えばTableViewの名前がtableview1とするとき
このように書く

def on_tableview(sender):
    b = sender.tableview.superview['tableview'1]

これでTableViewの読み書きできる

こんな感じで任意のリストを
TableViewに加えることができる

list = ui.ListDataSource([{'title':'dog'}, {'title':'cat'}, {'title':'bird'}])
tableview.data_source.items = []
for i in list.items:
    tableview.data_source.items.append(i)
tableview.reload_data()

関数の引数であるsenderは
.pyui上のどのパーツをタップしたかで
内容が異なっている

for key in sender.__dict__.keys():
    print(key)

で内容を確認できて
_pyuiが含まれていると
superviewを使えるっぽい

以下が確認した内容で
TableViewではsender.tablevewに_pyuiが含まれる

---sender@TextField---
<class '_ui.TextField'>
_pyui
None
---sender@TableView---
<class 'ui.ListDataSource'>
tableview
reload_disabled
delete_enabled
move_enabled
action
edit_action
accessory_action
tapped_accessory_row
selected_row
_items
text_color
highlight_color
font
number_of_lines
None
---sender.tableview@TableView---
<class '_ui.TableView'>
_pyui
None

WSLとJupyter-notebookでCNN、その2、CNNを試す

前回の続き
mecobalamin.hatenablog.com

環境ができたので手持ちのデータセットを試してみる

こちらのサイトを参考にコードを作成した
kerasでCNN 自分で拾った画像でやってみる - Qiita

主な変更点は以下の3点

  • 画像の読み込み
  • mnist_cnn.pyのモデルを使用
  • label、predict、画像ファイル名を保存する

こちらの環境ではlist_picturesが動かなかったので
ファイルを読み込む関数Image_Listを定義した
globは引数で指定したパスのファイル名を再帰的に取得する
ファイル名だけ取得したいのでrelpathで処理している

モデルはmnist_cnn.pyのと同じモデルを使ってみた

画像の分類は手動で行って
negativeとpositiveのディレクトリに保存してある
それぞれの画像がどのように分類されているかを知りたかったので
画像のファイル名とそれぞれがどのようにpredictされているかを
csvファイルに保存した

fit()で出力されるlistのindexも
バージョン違いのせいか"accu"ではエラーが出るので
"accuracy"に修正してある

実際のコードは以下の通り

# ライブラリのインポート
import keras
from keras.utils import np_utils
from keras.layers.convolutional import Conv2D, MaxPooling2D
from keras.models import Sequential
from keras.layers.core import Dense, Dropout, Activation, Flatten
from keras.preprocessing.image import array_to_img, img_to_array, load_img
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt

import pandas as pd
import numpy as np
from glob import glob
from os.path import relpath

# ファイル名の取得
def Image_List(Path_Images, Ext):
    List_Images = glob(Path_Images + "/*." + Ext)
    List_Names = []
    for i in List_Images:
        List_Names.append(relpath(i, Path_Images))
    return List_Names

# 画像の拡張子
Ext_Type = "png"

# 画像のパス
Path_Negative = '/path/to/negative/'
Path_Positive = '/path/to/positive/'

X = []
Y = []

# ネガティブ画像の読み込み
Image_Negative = Image_List(Path_Negative, Ext_Type)
for picture in Image_Negative:
    img = img_to_array(load_img(Path_Negative + picture, color_mode = "grayscale", target_size=(256, 256)))
    X.append(img)

    Y.append(0)


# ポジティブ画像の読み込み
Image_Positive = Image_List(Path_Positive, Ext_Type)
for picture in Image_Positive:
    img = img_to_array(load_img(Path_Positive + picture, color_mode = "grayscale", target_size=(256, 256)))
    X.append(img)

    Y.append(1)


# arrayに変換
# Zはファイル名のリスト
X = np.asarray(X)
Y = np.asarray(Y)
Z = pd.DataFrame(Image_Negative + Image_Positive)

# 画素値を規格化
X = X.astype('float32')
X = X / 255.0

# one-hot表現に変換
Y = np_utils.to_categorical(Y, 2)

# 学習用データとテストデータに分割
# ファイル名のリストも分割
X_train, X_test, y_train, y_test, indices_train, indices_test = train_test_split(X, Y, Z, test_size=0.33, random_state=111)

# モデルの構築
model = Sequential()

model.add(Conv2D(20, kernel_size=(3, 3),
                 activation='relu',
                 input_shape=X_train.shape[1:]))
model.add(Conv2D(40, (3, 3), activation='relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))

model.add(Flatten())
model.add(Dense(512))
model.add(Activation('relu'))
model.add(Dropout(0.5))
model.add(Dense(2))       # クラスは2個
model.add(Activation('softmax'))

# コンパイル
model.compile(loss='categorical_crossentropy',
              optimizer='SGD',
              metrics=['accuracy'])

# 実行
# 出力有り(verbose=1)。
history = model.fit(X_train, y_train, batch_size=300, epochs=4[f:id:mecobalamin:20200608101008p:plain]0,
                   validation_data = (X_test, y_test), verbose = 1)

plt.plot(history.history['accuracy'])
plt.plot(history.history['val_accuracy'])
plt.title('model accuracy')
plt.xlabel('epoch')
plt.ylabel('accuracy')
plt.legend(['acc', 'val_acc'], loc='lower right')
plt.show()

# テストデータに適用
predict_classes = model.predict_classes(X_test)

# マージ。yのデータは元に戻す
mg_df = pd.DataFrame({'predict': predict_classes, 'class': np.argmax(y_test, axis=1)})

# confusion matrix
pd.crosstab(mg_df['class'], mg_df['predict'])

# 結果の保存
# テストに使った画像ファイルのリストも保存
df_h = pd.concat([pd.DataFrame(y_test), indices_test], axis = 1)
pd.DataFrame(y_test).to_csv("/path/to/y_test.csv")
indices_test.to_csv("/path/to/indices_test.csv")
pd.DataFrame(predict_classes).to_csv("/path/to/predict_test.csv")

jupyter-notebookの複数のcellをまとめたが
gistを使うとそのまま貼り付けられるらしい



計算結果は以下の通り
Core i5-7200UのノートPCで
大体6時間ぐらいかかった

Train on 3287 samples, validate on 1619 samples
Epoch 1/40
3287/3287 [==============================] - 522s 159ms/step - loss: 0.6425 - accuracy: 0.5117 - val_loss: 0.5973 - val_accuracy: 0.5374
Epoch 2/40
3287/3287 [==============================] - 494s 150ms/step - loss: 0.5793 - accuracy: 0.6693 - val_loss: 0.5340 - val_accuracy: 0.7739
Epoch 3/40
3287/3287 [==============================] - 493s 150ms/step - loss: 0.5429 - accuracy: 0.7478 - val_loss: 0.5146 - val_accuracy: 0.7573

~~~ 省略 ~~~

Epoch 38/40
3287/3287 [==============================] - 501s 152ms/step - loss: 0.4346 - accuracy: 0.8059 - val_loss: 0.4734 - val_accuracy: 0.7900
Epoch 39/40
3287/3287 [==============================] - 493s 150ms/step - loss: 0.4001 - accuracy: 0.8190 - val_loss: 0.5238 - val_accuracy: 0.7622
Epoch 40/40
3287/3287 [==============================] - 497s 151ms/step - loss: 0.4388 - accuracy: 0.8026 - val_loss: 0.4895 - val_accuracy: 0.7857

Epochが増えると学習データに対するAccuracyは増えているが
テストデータに対するAccuracyはあまり増えていない
Jupyter Notebookをはてなブログに貼り付ける方法 - akatak’s blog

画像の分類は以下の通り
classは学習時に使った画像のラベルで
0はnegative、1はpositiveを表す
predictはcnnで分類された画像で
0/1はやはりnegative/positiveを表す

predict
0 1
class 0 652 104
1 243 620

10%-30%程度間違っている

グラフにしてないけれど損失関数も
mnistのデータを使ったときほど減っていない

使ったモデルが合っていないのか
教師データの分類が不十分なのか
そもそもデータの数が足りないのか

分類できた画像を確認したら
見た目にも分類が不十分に感じる
この画像がネガティブに分類?みたいな

計算はできたけど
ここからの修正はどうしたらいいだろうか

WSLとJupyter-notebookでCNN、その1、環境構築

画像の分類をしたくてDeep Learningについて勉強している
ネットで調べながらこの本を何度も読み直している
ゼロから作るDeep Learning ―Pythonで学ぶディープラーニングの理論と実装
(Amazonのサイトに飛びます)

CourseraのMachine Learningの講義もおすすめ
mecobalamin.hatenablog.com

試してみたいデータもあるのでどこか
似たようなことをやっている人がないか探してみたら
Kerasの開発者がCNNを使ってMNISTの画像認識を行っていた
https://github.com/keras-team/keras/tree/master/examples
いくつもファイルがあるがこの中からmnist_cnn.pyを利用した

mnist_cnn.pyについてはググるといくつも情報が出てくる
https://pondad.net/deep-learning/2016/12/25/keras-mnist.html
【AI初心者向け】mnist_cnn.pyを1行ずつ解説していく(KerasでMNISTを学習させる) - Qiita

手持ちの画像データを使いたいので
画像を読み込ませるために以下のサイトを参考にした
kerasでCNN 自分で拾った画像でやってみる - Qiita
Google Colaboratory で Keras 自作データセットを読み込み - Qiita
sklearn の train_test_split でデータの順番の情報を保持しておく - Qiita

実際に行ったのは

  1. mnist_cnn.pyを動かす環境の構築と実行
  2. 手持ち画像を読み込ませるコードの実行

今回はmnist_cnn.pyを動かす環境の構築
実行環境はWSLに作った
WSLを利用したのはPower Shellには
tensorflowをインストールできなかったためだ

まずはkeras/tensorflowをインストールする仮想環境を用意する
以下の過去記事を参考にした
mecobalamin.hatenablog.com

condaのコマンドを使ってtfという仮想環境を作る

conda create -n tf python=3.7 anaconda

環境の切り替えは

conda activate tf

conda deactivate

で行う

この環境にkerasとtensorflowをWSLにインストールした

keras/tensorflowの関係がいまいちよくわかっていないけど
tensorflowはバックエンドで実際の作業をするライブラリで
kerasはtensorflowを効率よく使うためのライブラリという認識

インストールは以下の通り

pip install --upgrade tensorflow
conda install keras

しようがないとはいえpipとcondaを混ぜて使ってしまったのが若干気になる
condaとpip:混ぜるな危険 - onoz000’s blog
仮想環境下にインストールしているので
やり直しをしやすいと思う

pythonコードの実行環境としてJupyterもインストール

pip install Jupyter

jupyter-notebookを使えるようになる
jupyter-notebookはデータサイエンス・機械学習の分野で
よく使われているらしい
セル毎にコードを実行できるので
修正箇所だけ実行とかできて便利

インストール後にjupyter-notebookを起動すると

jupyter-notebook

以下の表示が出るのでURLを
windowsのbrowserにコピペすると
jupyter-notebookを利用できる

To access the notebook, open this file in a browser:
    file:///home/hogehoge/.local/share/jupyter/runtime/nbserver-hogehoge-fugafuga.html
Or copy and paste one of these URLs:
    http://localhost:8888/?token=hogehogefugafuga
or
    http://127.0.0.1:8888/?token=hogehogefugafuga

実際にはtokenにアルファベットと数字の文字列が入っている

jupyter-notebookで新規にnotebookを作って
mnist_cnn.pyの中身をコピペするとこんな感じ

実行結果は以下の通り

Downloading data from https://s3.amazonaws.com/img-datasets/mnist.npz
11493376/11490434 [==============================] - 5s 0us/step
x_train shape: (60000, 28, 28, 1)
60000 train samples
10000 test samples
Train on 60000 samples, validate on 10000 samples
Epoch 1/12
60000/60000 [==============================] - 138s 2ms/step - loss: 0.2544 - accuracy: 0.9218 - val_loss: 0.0638 - val_accuracy: 0.9800
Epoch 2/12
60000/60000 [==============================] - 133s 2ms/step - loss: 0.0873 - accuracy: 0.9743 - val_loss: 0.0448 - val_accuracy: 0.9861
Epoch 3/12
60000/60000 [==============================] - 134s 2ms/step - loss: 0.0651 - accuracy: 0.9802 - val_loss: 0.0327 - val_accuracy: 0.9894
Epoch 4/12
60000/60000 [==============================] - 139s 2ms/step - loss: 0.0539 - accuracy: 0.9836 - val_loss: 0.0291 - val_accuracy: 0.9901
Epoch 5/12
60000/60000 [==============================] - 130s 2ms/step - loss: 0.0445 - accuracy: 0.9866 - val_loss: 0.0301 - val_accuracy: 0.9900
Epoch 6/12
60000/60000 [==============================] - 131s 2ms/step - loss: 0.0407 - accuracy: 0.9874 - val_loss: 0.0292 - val_accuracy: 0.9900
Epoch 7/12
60000/60000 [==============================] - 123s 2ms/step - loss: 0.0368 - accuracy: 0.9891 - val_loss: 0.0254 - val_accuracy: 0.9907
Epoch 8/12
60000/60000 [==============================] - 74s 1ms/step - loss: 0.0326 - accuracy: 0.9899 - val_loss: 0.0300 - val_accuracy: 0.9908
Epoch 9/12
60000/60000 [==============================] - 77s 1ms/step - loss: 0.0310 - accuracy: 0.9905 - val_loss: 0.0243 - val_accuracy: 0.9921
Epoch 10/12
60000/60000 [==============================] - 76s 1ms/step - loss: 0.0272 - accuracy: 0.9919 - val_loss: 0.0268 - val_accuracy: 0.9921
Epoch 11/12
60000/60000 [==============================] - 76s 1ms/step - loss: 0.0264 - accuracy: 0.9916 - val_loss: 0.0268 - val_accuracy: 0.9917
Epoch 12/12
60000/60000 [==============================] - 77s 1ms/step - loss: 0.0261 - accuracy: 0.9920 - val_loss: 0.0247 - val_accuracy: 0.9922
Test loss: 0.024740188298751492
Test accuracy: 0.9922000169754028

mnist_cnn.pyには

Gets to 99.25% test accuracy after 12 epochs
(there is still a lot of margin for parameter tuning).
16 seconds per epoch on a GRID K520 GPU.

とある

test accuracyは0.9920で大体あっているようだ

また今回使用した
Intel Core i5 7200U、GPUなしのnotebook PCでは
74-139s/Epochなので1/5 - 1/9 程度の計算速度か

値段の差を考えるとまあ順当
NVIDIA GRID k520 8 GB gddr5 PCIe gen3 x16クラウドゲームケプラーGPUグラフィックス900 – 12055 – 0020 – 000

とりあえず環境ができたので
次は手持ちのデータで試してみる

Pythonで数値のみの入力

以前、選択肢を数字で選ばせる
pythonスクリプトの記事を書いた
mecobalamin.hatenablog.com

該当する部分は以下のコード

print('計算の種類を選んで数字を入力してね')
print('1: 少数のたし算・ひき算')
print('2: 大きな数のかけ算')
print('3: あまりのあるわり算')
print('4: 分数のたし算・ひき算')
while True:
	eq_type = input('数値を入力 (1 - 4): ')
	if eq_type > '0' and eq_type < '5':
		break

動いていたから気にしていなかったが
入力は文字なのに大小の比較ができている

なぜだ?

文字列にも等号記号が使えることは知っていたが
調べてみると比較演算子も使えた
Pythonで文字列を比較(完全一致、部分一致、大小関係など) | note.nkmk.me
比較演算子の使い方 | Python入門

unicodeのコードポイントを比較しているそうだ

pythonではord()でコードポイントを取得できる

>>> ord('0')
48
>>> ord('1')
49
>>> ord('2')
50
>>> ord('9')
57

数字の0から9には
48から57が割り当てられている

なので過去記事で使ったコードでも
問題なく動いたようだ

本来ならint()で数値にして
大小比較するのが筋かも
ただし数値以外の文字・記号を
int()の引数にした場合はエラーになるので
try/except文を使って
エラーを弾きつつ
数値の入力を受け付けるように書き直した

try/excptの使い方は以下のサイトを参考にさせてもらった
Pythonの例外処理(try, except, else, finally) | note.nkmk.me

書き直したコードはこんな感じ
確認のためprint()を追加している

while True:
    eq_type = input('数値を入力 (1 - 4): ')
    try:
        int(eq_type)
    except ValueError as e:
        print('1から4までの数字を入力してね')
    else:
        if int(eq_type) >= 1 and int(eq_type) <= 4:
            break
        else:
            print('1から4までの数字を入力してね')

print(eq_type)

もっと簡潔にかけそうな気がする

あと全角・半角の区別はしていない
出力は半角になる
そのうち区別できるようにしたい

Pythonista3でpythonプログラミング

pythonプログラミングに利用している
iOSアプリのPythonista3
mecobalamin.hatenablog.com
mecobalamin.hatenablog.com

割と色々できて便利

まずはpythonistaについて

iOS上で動作する革命的ものづくり環境「Pythonista 3」の魅力をとくと語る
iOS上で動作する革命的ものづくり環境「Pythonista 3」の魅力をとくと語る
iPad ProでPythonプログラム
iPad ProでPythonプログラム - Qiita

Deep Learningの勉強にも使っていたり
「ゼロから作るDeep Learning」をiPhoneのPythonistaだけで学ぶ(2)
「ゼロから作るDeep Learning」をiPhoneのPythonistaだけで学ぶ(2) - blog.tmp.tokyo
iPadで「ゼロから作るDeep Learning」を勉強するために必要なこと
poipoides.hatenablog.com

自分でも試してみたけどコードに変更が必要で
上記のリンク先の方が変更したコードを載せてくれている
3章か4章ぐらいまではpythonistaで動くことを確認した
その先は結局PCのpythonで。


今までやったのは
コンソールに結果を表示する
プログラミング

希望する機能を実装できていたけど
せっかくiPadを使っているのだから
タッチ操作できるプログラミングをしてみたい

pythonistaにはいくつもサンプルコードが入っていて
そのいくつかはタッチ操作のゲームだったりする
実際ゲームを作っている人達もいる
Pythonistaで作るポーカー作成講座(全5回)
Pythonistaで作るポーカー作成講座(全5回) | みやびのどっとぴーわい
Pythonista+sceneでトランプをランダムに表示する
Pythonista+sceneでトランプをランダムに表示する - Qiita

公式でXcodeに変換するコードを公開しているので
その気になればiOSアプリも作れそう

python2に対応
github.com



で、試しに作ってみた
作ったのは例えば何かカードゲームをして
得点を記録するプログラム

一度コンソールに表示するバージョンは書いていて
こんな感じ
f:id:mecobalamin:20200514110316j:plain
それをこのように表示したい
f:id:mecobalamin:20200514110333j:plain
プレイヤーは3人で
ポイントを入力してsubmitを押すと
totalに加算される
そしてroundが一つ進む

コードはpythonプログラムのui.pyと
ボタン等を配置したui.pyuiの2つで一セット
実際のコードはこの記事の最後に載せるとして
ui.pyの説明を書く

import ui
import console

pythonistaにはsceneというライブラリもあるが
今回使用したのはui
consoleもpythonistaのライブラリで
コンソールを操作するのに使う

	v = ui.load_view()
	
	v['num1'].keyboard_type = ui.KEYBOARD_NUMBER_PAD
	v['num2'].keyboard_type = ui.KEYBOARD_NUMBER_PAD
	v['num3'].keyboard_type = ui.KEYBOARD_NUMBER_PAD
	v.present('fullscreen')

ui.load_view()でui.pyuiをロードしている
v['num1'].keyboard_typeは
num1に入力するときのキーボードの種類を設定する
num1は得点の入力なので数値のみ
なので最初から数字入力になっている
キーボードが表示されるようにしている

present('fullsucreen')で
フルスクリーン表示でコードを実行する

よくわかっていないんだけど
このときプログラムは×印をタップして停止させるまで
入力待ちになっているっぽい

関数を実行するにはui.pyに書かれた関数を
ui.pyuiのボタンに紐付けしておく
ボタンをタップすると
紐付けされた関数on_buttonが実行される

def on_button(sender):

引数のsenderには画面上の情報が入っているようで
superview[]とボタンやtextfieldの名前を使って値を取り出す

text_label1 = sender.superview['label1']
Num_1 = sender.superview['num1']

label1やnum1はtextfieldの名前であり
ui.pyuiで編集できる

print文はconsol画面に表示されるため
プログラムを停止するまで表示を見られない

書いただけなので読みやすく修正したいけど
とりあえずここまででやりたいことは実装できた
ui.pyuiも見やすいデザインにしたい

まだ読んでいないけどpytonista3を使った
プログラミング教本らしいのでメモ
PythonではじめるiOSプログラミング 〜iOS+Pythonで数値処理からGUI、ゲーム、iOS機能拡張まで〜

実際のコードは以下の通り

ui.py

import ui
import console

def on_button(sender):

	text_label1 = sender.superview['label1']
	Num_1 = sender.superview['num1']
	Re_1 = sum_score(text_label1.text, Num_1.text)
	text_label1.text = str(Re_1)
	Num_1.text = ''
	
	text_label2 = sender.superview['label2']
	Num_2 = sender.superview['num2']
	Re_2 = sum_score(text_label2.text, Num_2.text)
	text_label2.text = str(Re_2)
	Num_2.text = ''
	
	text_label3 = sender.superview['label3']
	Num_3 = sender.superview['num3']
	Re_3 = sum_score(text_label3.text, Num_3.text)
	text_label3.text = str(Re_3)
	Num_3.text = ''
	
	num_round = sender.superview['num_round']
	num_round.text = str(int(num_round.text) + 1)
	#print(Num_3.text_color)
	
	print('round' + str(int(num_round.text) - 1), Re_1, Re_2, Re_3)
	
def sum_score(m, n):
	r = 0
	if m == '':
		r = int(n)
	elif n == '':
		r = int(m)
	else:
		r = int(m) + int(n)
	
	return r

if __name__ == '__main__':

	console.clear()
	console.set_font('Menlo',20)

	v = ui.load_view()
	
	v['num1'].keyboard_type = ui.KEYBOARD_NUMBER_PAD
	v['num2'].keyboard_type = ui.KEYBOARD_NUMBER_PAD
	v['num3'].keyboard_type = ui.KEYBOARD_NUMBER_PAD
	v.present('fullscreen')

ボタンや数字の入力場所の配置はこんな感じ
f:id:mecobalamin:20200514110503j:plain
赤でマークしたactionにon_button関数を登録した
submitのボタンを押すと
on_button関数が実行される

ui.pyuiはテキストファイルなのでエディタで中身が読める
よくわからないけどファイルの形式はJSON?っぽく見える

ui.pyui

[
  {
    "nodes" : [
      {
        "nodes" : [

        ],
        "frame" : "{{119, 54}, {77, 43}}",
        "class" : "TextField",
        "attributes" : {
          "uuid" : "EB343BBB-5CFB-4AE7-8BCC-41EE1ACDE42E",
          "font_size" : 17,
          "frame" : "{{60, 224}, {200, 32}}",
          "custom_attributes" : "",
          "action" : "",
          "alignment" : "right",
          "autocorrection_type" : "default",
          "text" : "",
          "font_name" : "<System>",
          "spellchecking_type" : "default",
          "class" : "TextField",
          "name" : "num1",
          "flex" : "WHLRTB"
        },
        "selected" : false
      },
      {
        "nodes" : [

        ],
        "frame" : "{{16, 54}, {95, 43}}",
        "class" : "TextField",
        "attributes" : {
          "uuid" : "EB343BBB-5CFB-4AE7-8BCC-41EE1ACDE42E",
          "font_size" : 17,
          "frame" : "{{60, 224}, {200, 32}}",
          "action" : "",
          "alignment" : "left",
          "autocorrection_type" : "default",
          "text" : "",
          "font_name" : "<System>",
          "spellchecking_type" : "default",
          "class" : "TextField",
          "name" : "usr1",
          "flex" : "WHLRTB"
        },
        "selected" : false
      },
      {
        "nodes" : [

        ],
        "frame" : "{{16, 105}, {95, 43}}",
        "class" : "TextField",
        "attributes" : {
          "flex" : "WHLRTB",
          "uuid" : "EB343BBB-5CFB-4AE7-8BCC-41EE1ACDE42E",
          "frame" : "{{60, 224}, {200, 32}}",
          "action" : "",
          "alignment" : "left",
          "autocorrection_type" : "default",
          "text" : "",
          "font_name" : "<System>",
          "spellchecking_type" : "default",
          "class" : "TextField",
          "name" : "usr2",
          "font_size" : 17
        },
        "selected" : false
      },
      {
        "nodes" : [

        ],
        "frame" : "{{16, 156}, {95, 43}}",
        "class" : "TextField",
        "attributes" : {
          "flex" : "WHLRTB",
          "uuid" : "EB343BBB-5CFB-4AE7-8BCC-41EE1ACDE42E",
          "frame" : "{{60, 224}, {200, 32}}",
          "action" : "",
          "alignment" : "left",
          "autocorrection_type" : "default",
          "text" : "",
          "font_name" : "<System>",
          "spellchecking_type" : "default",
          "class" : "TextField",
          "name" : "usr3",
          "font_size" : 17
        },
        "selected" : false
      },
      {
        "nodes" : [

        ],
        "frame" : "{{119, 105}, {77, 43}}",
        "class" : "TextField",
        "attributes" : {
          "uuid" : "EB343BBB-5CFB-4AE7-8BCC-41EE1ACDE42E",
          "font_size" : 17,
          "frame" : "{{60, 224}, {200, 32}}",
          "action" : "",
          "alignment" : "right",
          "autocorrection_type" : "default",
          "text" : "",
          "font_name" : "<System>",
          "spellchecking_type" : "default",
          "class" : "TextField",
          "name" : "num2",
          "flex" : "WHLRTB"
        },
        "selected" : false
      },
      {
        "nodes" : [

        ],
        "frame" : "{{119, 156}, {77, 43}}",
        "class" : "TextField",
        "attributes" : {
          "uuid" : "EB343BBB-5CFB-4AE7-8BCC-41EE1ACDE42E",
          "font_size" : 17,
          "frame" : "{{60, 224}, {200, 32}}",
          "action" : "",
          "alignment" : "right",
          "autocorrection_type" : "default",
          "text" : "",
          "font_name" : "<System>",
          "spellchecking_type" : "default",
          "class" : "TextField",
          "name" : "num3",
          "flex" : "WHLRTB"
        },
        "selected" : false
      },
      {
        "nodes" : [

        ],
        "frame" : "{{204, 54}, {97, 43}}",
        "class" : "Label",
        "attributes" : {
          "uuid" : "F3870D2E-9FCF-4C83-B5AF-C393166D3D4E",
          "flex" : "WHLRTB",
          "corner_radius" : 0,
          "frame" : "{{85, 224}, {150, 32}}",
          "number_of_lines" : 0,
          "border_width" : 1,
          "border_color" : "RGBA(0.000000,0.000000,0.000000,1.000000)",
          "alignment" : "center",
          "text" : "0",
          "font_name" : "<System>",
          "class" : "Label",
          "name" : "label1",
          "font_size" : 18
        },
        "selected" : false
      },
      {
        "nodes" : [

        ],
        "frame" : "{{204, 105}, {97, 43}}",
        "class" : "Label",
        "attributes" : {
          "uuid" : "F3870D2E-9FCF-4C83-B5AF-C393166D3D4E",
          "flex" : "WHLRTB",
          "corner_radius" : 0,
          "frame" : "{{85, 224}, {150, 32}}",
          "number_of_lines" : 0,
          "border_width" : 1,
          "border_color" : "RGBA(0.000000,0.000000,0.000000,1.000000)",
          "alignment" : "center",
          "text" : "0",
          "font_name" : "<System>",
          "class" : "Label",
          "name" : "label2",
          "font_size" : 18
        },
        "selected" : false
      },
      {
        "nodes" : [

        ],
        "frame" : "{{204, 156}, {97, 43}}",
        "class" : "Label",
        "attributes" : {
          "flex" : "WHLRTB",
          "uuid" : "F3870D2E-9FCF-4C83-B5AF-C393166D3D4E",
          "corner_radius" : 0,
          "frame" : "{{85, 224}, {150, 32}}",
          "border_color" : "RGBA(0.000000,0.000000,0.000000,1.000000)",
          "border_width" : 1,
          "alignment" : "center",
          "text" : "0",
          "font_name" : "<System>",
          "class" : "Label",
          "name" : "label3",
          "font_size" : 18
        },
        "selected" : false
      },
      {
        "nodes" : [

        ],
        "frame" : "{{95, 207}, {142, 41}}",
        "class" : "Button",
        "attributes" : {
          "action" : "on_button",
          "flex" : "WHLRTB",
          "border_width" : 1,
          "frame" : "{{120, 224}, {80, 32}}",
          "title" : "submit",
          "uuid" : "BD6B3517-3570-46B3-B6E4-E967C932197B",
          "class" : "Button",
          "corner_radius" : 0,
          "name" : "button1",
          "font_size" : 15
        },
        "selected" : false
      },
      {
        "nodes" : [

        ],
        "frame" : "{{174, 6}, {73, 19}}",
        "class" : "Label",
        "attributes" : {
          "font_size" : 18,
          "flex" : "WHLRTB",
          "frame" : "{{85, 224}, {150, 32}}",
          "uuid" : "AC87D8D9-39BC-4DA0-90E3-861257E72023",
          "class" : "Label",
          "alignment" : "left",
          "text" : "1",
          "custom_attributes" : "",
          "name" : "num_round",
          "font_name" : "<System>"
        },
        "selected" : false
      },
      {
        "nodes" : [

        ],
        "frame" : "{{16, 6}, {150, 19}}",
        "class" : "Label",
        "attributes" : {
          "name" : "Round_title",
          "flex" : "WHLRTB",
          "frame" : "{{85, 224}, {150, 32}}",
          "uuid" : "7CBBD8E2-1DAE-471D-B7D6-2EAA890A3C7F",
          "class" : "Label",
          "alignment" : "right",
          "text" : "Round",
          "font_size" : 18,
          "font_name" : "<System>"
        },
        "selected" : false
      },
      {
        "nodes" : [

        ],
        "frame" : "{{16, 33}, {95, 20}}",
        "class" : "Label",
        "attributes" : {
          "flex" : "WHLRTB",
          "font_name" : "<System>",
          "frame" : "{{85, 224}, {150, 32}}",
          "uuid" : "7CBBD8E2-1DAE-471D-B7D6-2EAA890A3C7F",
          "class" : "Label",
          "alignment" : "center",
          "text" : "User name",
          "name" : "",
          "font_size" : 18
        },
        "selected" : false
      },
      {
        "nodes" : [

        ],
        "frame" : "{{119, 33}, {77, 20}}",
        "class" : "Label",
        "attributes" : {
          "flex" : "WHLRTB",
          "font_size" : 18,
          "frame" : "{{85, 224}, {150, 32}}",
          "uuid" : "7CBBD8E2-1DAE-471D-B7D6-2EAA890A3C7F",
          "class" : "Label",
          "alignment" : "center",
          "text" : "point",
          "name" : "",
          "font_name" : "<System>"
        },
        "selected" : false
      },
      {
        "nodes" : [

        ],
        "frame" : "{{204, 33}, {97, 20}}",
        "class" : "Label",
        "attributes" : {
          "flex" : "WHLRTB",
          "font_name" : "<System>",
          "frame" : "{{85, 224}, {150, 32}}",
          "uuid" : "7CBBD8E2-1DAE-471D-B7D6-2EAA890A3C7F",
          "class" : "Label",
          "alignment" : "center",
          "text" : "total",
          "font_size" : 18,
          "name" : ""
        },
        "selected" : false
      }
    ],
    "frame" : "{{0, 0}, {320, 480}}",
    "class" : "View",
    "attributes" : {
      "tint_color" : "RGBA(0.000000,0.478000,1.000000,1.000000)",
      "enabled" : true,
      "border_color" : "RGBA(0.000000,0.000000,0.000000,1.000000)",
      "background_color" : "RGBA(1.000000,1.000000,1.000000,1.000000)",
      "name" : "",
      "flex" : ""
    },
    "selected" : false
  }
]