乙Py先生のプログラミング教室
初学者のためのプログラミング学習サイト

プログラミング

今回は文字列の検索をする際に役立つ
正規表現についてです。


解説動画はこちら




正規表現とは

さて正規表現とは何でしょうか?

正規表現は
「いくつかの文字列を一つの形式で
表現するための表現方法」です

メタ文字と呼ばれる記号を用いて
複数の文字列を1つのパターンで
表現していきます。

メタ文字には次のようなものがあります。


. ^ $ * + ? | ( ) [ ]


正規表現のパターンのルールは細かく
一気に覚えるのが大変です。

基本的なものだと
次のようなものがあります。

a

定義済みの正規表現パターンは
次のようなものがあります。

b


特定位置の正規表現パターンだと
次のようなものがあります。

c


これらを組み合わせて
色々な文字列を1つのパターンで表します。


Python言語で正規表現を使う方法


Pythonで正規表現を使うには
reライブラリを用います

ライブラリを読み込み
正規表現パターンを指定して
reライブラリの検索メソッドで
検索対象の文字列からパターンを探します。
import re

pattern = "正規表現パターン"
text = "検索対象の文字列"

re.検索メソッド(pattern , text)


reライブラリの検索メソッドは
結構たくさんあります。

まずはsearchメソッドです。

これは正規表現に当てはまる箇所を
位置で返します。

結果はmatchオブジェクトになるので
この中から、必要なものを取り出す形になります。
import re

pattern = "[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+"
text = '私のメールアドレスはotupy@otupy.comです'

# 正規表現に当てはまる箇所を位置で返す
result = re.search(pattern , text)
print(result)
print(result.span())
print(result.group())
print(re.search(pattern , text).group())
<re.Match object; span=(10, 25), match='otupy@otupy.com'>
(10, 25)
otupy@otupy.com
otupy@otupy.com



次はfindallメソッドです
これは正規表現に当てはまる部分を
リストで返します。

import re

pattern = "[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+"
text = '私のメールアドレスはotupy@otupy.comです'

# 正規表現に当てはまる部分をリストで返す
results = re.findall(pattern , text)
print(results)
['otupy@otupy.com']



次はsubメソッドです
これはパターンに当てはまる部分を
他の文字列に置換することができます。
import re

pattern = "[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+"
text = '私のメールアドレスはotupy@otupy.comです'

# 正規表現で文字列を置換する
result = re.sub(pattern , 'mail_address', text)
print(result)
私のメールアドレスはmail_addressです



最後はsplitメソッドです
これはパターンに当てはまる部分で
文字列を分割してリスト化します。
import re

pattern = "[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+"
text = '私のメールアドレスはotupy@otupy.comです'

# 正規表現に当てはまる部分で分割する
results = re.split(pattern , text)
print(results)
['私のメールアドレスは', 'です']


reの検索メソッドは、目的に応じて
使い分けるのが良いでしょう。




正規表現のパターンサンプル

ここからは、正規表現の
パターンサンプルを見ていきましょう。

検索メソッドとしては「findall」を主に使用します。

a から始まって z で終わる 3 桁の文字列
pattern = "a.z"
text = 'abz,abc,cbz'

results = re.findall(pattern , text)
print(results)
['abz']


a から始まって z で終わる 3 桁以上の文字列
pattern = "a.+z"
text = 'abz,abc,cbz'

results = re.findall(pattern , text)
print(results)
['abz,abc,cbz']


4 桁の半角数字
pattern = "\d{4}"
text = '123 , 1234 , 23456'

results = re.findall(pattern , text)
print(results)
['1234', '2345']


4 ~ 10 桁の半角数字 (最長一致)
pattern = "\d{4,10}"
text = '123 , 1234 , 23456 , 334455667788'

results = re.findall(pattern , text)
print(results)
['1234', '23456', '3344556677']


桁区切りのカンマ付数字
pattern = "(?:^| )(\d{1,3}((?:,\d{3})*))(?=$| )"
text = '123,456 , 334455667788  1,234'

results = re.findall(pattern , text)
print(results)
[('123,456', ',456'), ('1,234', ',234')]


携帯電話番号
pattern = '[\(]{0,1}[0-9]{2,4}[\)\-\(]{0,1}[0-9]{2,4}[\)\-]{0,1}[0-9]{3,4}'
text = '携帯番号は 090-1234-5678 です 他は090-9876-4321'

results = re.findall(pattern , text)
print(results)
['090-1234-5678', '090-9876-4321']


日付
pattern = "[0-9]{4}\/[0-9]{1,2}\/[0-9]{1,2}"
text = '今日は2022/10/1です 2023/01/31'

results = re.findall(pattern , text)
print(results)
['2022/10/1', '2023/01/31']

pattern = r"[0-9]{4}年[0-9]{2}月[0-9]{2}日"
text = '今日は2022年01月10日です'

results = re.findall(pattern , text)
print(results)
['2022年01月10日']


日付などはちゃんと日付の妥当性も考えると
もう少し複雑なパターンを考える必要が出てきます。


まとめ
正規表現はどんなプログラム言語でも
共通的に使える表現なので覚えておくと便利です。

正規表現を作り込めば
複雑なコードを書かなくても
正規表現で妥当性のチェックを行うことができ
短いコードで良くなります。

ググれば、正規表現の様々なパターンを
探すことができるので
自分の作りたいパターンに似たものを
流用しながら作るのが良いと思います。

覚えるのが少し大変ですが
覚える価値は有るかと思いますね。

今回はここまでです
それでは。


今回はテキストから画像を自動生成する
stable-diffusionをGoogle Colabで試してみました

解説動画はこちら






さて、こちら
最近公開されたようなのですが
テキストから画像を生成してくれるという
優れものです

使えるようにするには
少しコツがいるので手順を紹介します。

デモサイト



コードを動かすために必要な作業

1.Hugging Faceのアカウント作成(要メールアドレス)

最初はhuggingfaceというサイトのアカウントが必要です
SignUpからメアドとパスワードで登録しましょう。


2.来たメールで認証確認

登録したらメールが来ると思うので
リンクをクリックして認証確認をします。


3.stable-diffusionのModel cardの使用申請をする
申請先

次にこのライブラリを使うためには
Model cardというものの使用許可がいるため
申請をする必要があります。

リンクにアクセスして
チェックを入れて
Access repositoryをクリックします。


4.Access Tokenを取得

その後、Tokenの作成画面に遷移し
「New token」をクリックして
適当な名前で「token」を作成します。
スクリーンショット 2022-08-27 14.34.32

出来上がったら、コピーボタンがあるので
それをコピーします。



ここからはGoogle Colabの設定です。

5.Google Colabのランタイムの変更

CPUでは動かないので、ランタイムを設定変更します。

「ランタイム」から「ランタイムタイプの変更」
スクリーンショット 2022-08-27 15.45.44
ハードウェアアクセラレーターを
「None」から「GPU」へ変更します。



6.Access Tokenをコードに入力して実行する

最後に次のコードをコピペして
Tokenを打ち込んで実行します。

そうするとコード類のセットアップが走り
2-3分すると終わります。

#@title 事前セットアップ
 
# 必要なライブラリのインストール
!pip install diffusers==0.2.4 transformers scipy ftfy
 
# アクセス・トークンを設定
token = ""#@param {type:"string"}

# パイプラインを構築
from diffusers import StableDiffusionPipeline
pipe = StableDiffusionPipeline.from_pretrained("CompVis/stable-diffusion-v1-4", use_auth_token=token)
pipe.to("cuda")



あとは下記のコードをコピペして
英文のテキストを入力して画像を生成しましょう。
#@title 画像を生成する
 
# 画像生成
prompt = "A small cabin on top of a snowy mountain in the style of Disney, artstation" #@param {type:"string"}
img = pipe(prompt)["sample"][0]
 
# 画像の保存
img_name = prompt.replace(' ','_')
img_path = img_name + '.png'
img.save(img_path)

# 画像の表示
from IPython.display import Image,display
display(Image(img_path))

画像は画像保存で指定したパスに
保存されます。

画像作成時のポイントとして
・英文でないとダメです
・画風(スタイル)を指定できます


今回使用した英文の例です

A high tech solarpunk utopia in the Amazon rainforest

A pikachu fine dining with a view to the Eiffel Tower

A mecha robot in a favela in expressionist style

an insect robot preparing a delicious meal

A small cabin on top of a snowy mountain in the style of Disney, artstation

A_high_tech_solarpunk_utopia_in_the_Amazon_rainforest
A_pikachu_fine_dining_with_a_view_to_the_Eiffel_Tower
A_mecha_robot_in_a_favela_in_expressionist_style

結構それなりに、いい感じに出来ていると思います。

画像の生成には30秒ほどかかり
Google ColabのGPU使用は
無料版だと12時間ほどかと思うので
結構な枚数は作成できるんじゃないかと思います。

ちょっとしたイラストなんかは
自動で生成出来てしまいますね。

やりたい方は試してみてくださいね
それでは。

今回はセイバーメトリクスについて
少し調べてみました。


解説動画はこちら



さて
今回はセイバーメトリクスについてです
知らない方もいるかもしれないので
簡単な説明ですが

セイバーメトリクスとは
野球においてデータを
統計学的見地から客観的に分析し
選手の評価や戦略を考える
分析手法のことです。

ざっくり考えると
勝つためにデータ分析しましょう
ってことですね。

なので、今回は
勝利に貢献する指標を
見つけてみようと思います。

まず、データに関しては
こちらのサイトのデータを参考にしています。

プロ野球データFreak



さて、チームの成績データを見てみましょう。

TSVやCSVに取得したデータを
まとめていれば、ファイルの読み込みが出来ます。

ファイルの読み込み
import pandas as pd
team_df = pd.read_table('baseball_all_team_stats2.tsv')
team_df.shape
(168, 51)



今回使用するのは、こんな感じのデータです。
スクリーンショット 2022-08-20 17.56.12

2009 - 2022年8月20日までのデータです。



さて次は、最近のリーグ成績を見てみましょう。

こんな感じのコードで時系列での
成績をまとめることが出来ます。

順位(Y軸)は反転させておくと
いい感じになります。

import matplotlib.pyplot as plt
import seaborn as sns

# セリーグ
se_league = team_df[team_df['リーグ']=='セリーグ']
g = sns.relplot(x='年度', y='順位', hue='チーム' , data=se_league, kind='line')
g.fig.axes[0].invert_yaxis()
g.fig.set_figwidth(12)
download

こちらはパリーグです
download-1

これで見ると、セリーグは順位変動が激しく
パリーグは特にソフトバンクが強いですね
ここ最近は5位以下になったことが無さそうです


次は勝率を見てみましょう。

2009 - 2022までの全試合での全チームの勝率
groupby  = team_df[['チーム','試合','勝利','敗北']].groupby('チーム').sum()
groupby['勝率']  = groupby['勝利'] / groupby['試合']
groupby.sort_values('勝率',ascending=False)
スクリーンショット 2022-08-20 17.57.07

パリーグはソフトバンク
セリーグは巨人
の勝率が良いですね

とはいえ、べらぼうに差があるわけでは
無いようです。


次はこの勝利数に貢献する指標を見てみましょう。


勝利数に貢献しそうな指標は何か?
target_kpi = ['得点', '平均得点', '失点', '平均失点', '打率', '本塁打','防御率', '安打', '出塁率', '長打率', 'OPS']

for kpi in target_kpi:
    tmp_df = team_df[['チーム','勝利'] + [kpi]]
    print(kpi , '相関係数 : ' , tmp_df.corr().iloc[1,0])
    g=sns.jointplot(x='勝利', y=kpi, data=tmp_df,kind='reg')
    g.fig.set_figwidth(3)
    g.fig.set_figheight(3)
    plt.show()
スクリーンショット 2022-08-20 17.57.24

# 相関係数のみ計算する
corrs = {}
for kpi in target_kpi:
    tmp_df = team_df[['チーム','勝利'] + [kpi]]
    corrs[kpi] = tmp_df.corr().iloc[1,0]

for k,v in sorted(corrs.items() , reverse=True , key=lambda x:x[1]):
        print(k,v)
得点 0.6752214591815026
安打 0.6266412461266061
打率 0.5737515556643754
OPS 0.5707190644752489
出塁率 0.5601397549262012
平均得点 0.5484023969333554
長打率 0.5206867389057273
本塁打 0.4614069008549154
失点 -0.03810712231263921
平均失点 -0.28881452409175656
防御率 -0.30572213281469424



勝利数に貢献しそうな指標としては
得点が一番関係性が高そうです。

野球のルールの性質上
相手チームよりもたくさん点を取った方が勝つので
得点数は勝利に貢献するのは当たり前でしょうね。


次はこの得点に貢献する指標を見てみましょう。

得点に貢献しそうな指標は?
target_kpi = ['打率', '本塁打', '安打', '出塁率', '長打率', 'OPS']

corrs = {}
for kpi in target_kpi:
    tmp_df = team_df[['チーム','得点'] + [kpi]]
    corrs[kpi] = tmp_df.corr().iloc[1,0]

for k,v in sorted(corrs.items() , reverse=True , key=lambda x:x[1]):
        print(k,v)
OPS 0.8708254335866881
長打率 0.8159000896409998
安打 0.8097657028521518
出塁率 0.8054269840935689
本塁打 0.7637663066912697
打率 0.7457514146368397


平均得点だと
OPS 0.9567899908827453
長打率 0.9145865090996436
出塁率 0.8454041544924719
本塁打 0.7794856511615135
打率 0.7363115009552308
安打 0.5156269035543274


OPSは
得点数だと0.870
平均得点だと0.956
という高い相関係数になります。


OPSはOn-base plus sluggingの略で
野球において打者を評価する指標の1つで
出塁率と長打率を足し合わせた値になります。


こういった指標を一つ一つ検証し
どんな指標が貢献するのかを探るのも
セイバーメトリクスの醍醐味かと思います。


今回は勝利数に関しては得点

得点に関してはOPSが
高い貢献度があることが分かりました。

次回はこの、OPS周りを
詳しく深掘りしていきたいと思います。

今回はここまでです
それでは。


 

今年の夏は暑いですねー
毎日アイスを食べたくなりますね

そんなわけでアイスの価格のデータを
見てみることとしました。


解説動画はこちら



E-statに小売物価統計調査(動向編)2021年
というのが有って


結果表 1 調査品目の月別価格及び年平均価格
【都道府県庁所在市及び人口15万以上の市】
「1701 ようかん」 ~ 「1797 弁当」
というのが今回使用したデータです。

アイスは「ハーゲンダッツ・バニラ」
(295円,税込318円)を基準にしている
ようでした。

しかしe-statのデータはこんな感じで
aice

エクセルファイルになっていますが
まだエクセルなのは0点だとしても

ヘッダーに文章が有ったり
縦積みしなければいけない所を
横積みにしていたりと
相当なゴミっぷり-100点です。

なので、2回の整形、加工が必要でした。

まずはこのエクセルから
必要な部分だけをコピーして
TSV形式で保存しなおします。

aice.tsv

次はこのデータの横積みされている所から
縦積みに直します。

こんな感じのコードで整形です。
data = []
with open('aice.tsv') as _f:
    for row in _f:
        rows = row.replace('\n','').split('\t')
        data.append(rows)

data2 = []
for rows in data[2:]:
    name = rows[1]
    for i,t in enumerate(rows[4:]):
        tmp = [name , '{0:02}月分'.format(i+1) , int(t)]
        data2.append(tmp)


さて、このデータを見ていきましょう。
データフレームに読み込みします。
import pandas as pd

df = pd.DataFrame(data2 , columns=['市町村','月','価格'])

df.shape
(972, 3)


平均価格が安い地域はどこか?
df2 = df.groupby('市町村').mean('価格').sort_values('価格')

df2
スクリーンショット 2022-08-13 16.59.52


最安値の地区は?
df3 = df.sort_values('価格').head()

df3
スクリーンショット 2022-08-13 17.00.12


最安地域の分布
import seaborn as sns
from matplotlib import pyplot as plt

df4 = df[df['市町村'].isin(df2.index[0:10])]

plt.figure(figsize=(10,5))
sns.boxplot(x='市町村',y='価格', data=df4,order=df2.head(10).index)
plt.title('')
plt.show()
download


これで見ると
全国の地域の中で
最も安い値をつけたのは「浦安市」
平均価格でも「浦安市」が安くなっていました。

アイスを安く買いたければ
「浦安市」のスーパーが
安い可能性が高いですね。

でも、なんでこんなに安いんですかね?
ミッキーに脅されていたりしたら
面白いんですけどね

ちなみに
ハーゲンダッツの小売は300円くらいなので
200切っていたら相当な買いですねー

自分は安い時にまとめ買いしてます。

今年は消費量が多いので
データから家計を守るのも
大事と思います。

それでは。

たまには真面目に
仕事で使えそうなプログラミングをしてみましょう


解説動画はこちら


 

1.WEB上にあるCSVファイルを開く

今回はWEB上にあるファイルを
ダウンロードせず、直接開いてみましょう。


読み込みするファイルは、総務省かどっかが出している
E-statのデータです。

男女別人口-全国,都道府県(大正9年~平成27年)
file_url ='https://www.e-stat.go.jp/stat-search/file-download?statInfId=000031524010&fileKind=1'

これを読み込みするには
Pandasライブラリを使います。
import pandas as pd
import warnings
warnings.simplefilter('ignore')

df = pd.read_csv(file_url , encoding='cp932')

df
スクリーンショット 2022-08-06 17.20.11

読み込みは出来ましたが
最後の方にゴミが載っています。


政府が用意しているデータは
リテラシーが低く、ゴミなので
そのままだと使えないことが多いです。

いらない部分を読み飛ばしてみましょう

skipfooter = 最後尾の行数
を加えるといらない部分を飛ばして読み込み出来ます。

df = pd.read_csv(file_url , encoding='cp932', engine="python" , skipfooter=2)

df
スクリーンショット 2022-08-06 17.20.42




Pandasライブラリを用いないで読み込みする場合は
urllibが使えます。

urllib.request.urlopenで開いたファイルは
http.client.HTTPResponseオブジェクトになります

オブジェクト.decode()で
strオブジェクトに変換できます。

import urllib

# そのままurlopenで開いた場合
with urllib.request.urlopen(file_url) as _f:
    for row in _f:
        print(row)
        break
b'"\x93s\x93\xb9\x95{\x8c\xa7\x83R\x81[\x83h","\x93s\x93\xb9\x95{\x8c\xa7\x96\xbc","\x8c\xb3\x8d\x86","\x98a\x97\xef\x81i\x94N\x81j","\x90\xbc\x97\xef\x81i\x94N\x81j","\x92\x8d","\x90l\x8c\xfb\x81i\x91\x8d\x90\x94\x81j","\x90l\x8c\xfb\x81i\x92j\x81j","\x90l\x8c\xfb\x81i\x8f\x97\x81j"\r\n'
# urlopenで開いてdecodeした場合
with urllib.request.urlopen(file_url) as _f:
    for row in _f:
        print(row.decode('cp932'))
        break
"都道府県コード","都道府県名","元号","和暦(年)","西暦(年)","注","人口(総数)","人口(男)","人口(女)"


リストでまとめて読み込んでしまう場合は
次のように書けばいけます。
# urlopenで開いてlist化する
with urllib.request.urlopen(file_url) as _f:
    # 改行コードで分割する
    lines = _f.read().decode('cp932').split('\r\n')
    
lines
['"都道府県コード","都道府県名","元号","和暦(年)","西暦(年)","注","人口(総数)","人口(男)","人口(女)"', '"00","全国","大正",9,1920,"",55963053,28044185,27918868',・・・

ゴミデータが最後の3個に入ってしまっているので
リストのスライス参照で取り除くと良いでしょう。
lines[970:-3]

これでpandasでは難しい1行単位での
データ操作を行えます。



2.データを見る

さて、ファイルが読み込めたので
次は中身を細かくみてみましょう。

ファイルはこんな感じになっています。
スクリーンショット 2022-08-06 17.29.16




全国の人口だけを見る
df[df['都道府県名']=='全国'][['西暦(年)','人口(総数)']]
スクリーンショット 2022-08-06 17.29.33

Pandasのデータフレームでは
条件抽出が行えるので
該当する部分だけを抜き出すことができます。



最近のものだけを見る(2010 , 2015)
df[df['西暦(年)'].isin([2010,2015])][['西暦(年)','都道府県名','人口(総数)']]
スクリーンショット 2022-08-06 17.29.52

isin で値を複数指定して
条件抽出ができます。



2010 , 2015の差分を見る
# 該当年のデータを抽出
df_2010 = df[df['西暦(年)']==2010][['西暦(年)','都道府県名','人口(総数)']]
df_2015 = df[df['西暦(年)']==2015][['西暦(年)','都道府県名','人口(総数)']]

# インデックスのリセット
df_2010.reset_index( inplace=True , drop=True)
df_2015.reset_index( inplace=True , drop=True)

# データフレームの結合
df_concat = pd.concat([df_2010 , df_2015[['人口(総数)']]],axis=1)
df_concat.columns = ['西暦(年)', '都道府県名', '2010_人口(総数)', '2015_人口(総数)']

# 差分の計算
df_concat['差分'] = df_concat['2015_人口(総数)'].astype(int) - df_concat['2010_人口(総数)'].astype(int)
df_concat['増減'] = df_concat['差分'].apply(lambda x: '減少' if x <=0 else '増加')

# 人口が増加した都道府県は?
df_concat[df_concat['増減']=='増加']
スクリーンショット 2022-08-06 17.32.34

差分を見るには
まずはデータフレームとして該当するデータを
年ごとに作成しておき
それを列方向に結合させます。

のちに数値化して差分を計算することができます。

2010 - 2015で
人口が増えた都道府県は8個しか無いようですね
2020年度のデータがなかったようですが
人口は減る傾向にあります



東京都の人口推移
df_plot = df[df['都道府県名']=='東京都'][['西暦(年)','人口(総数)']]
df_plot.reset_index( inplace=True , drop=True)

df_plot['人口(総数)'] = df_plot['人口(総数)'].astype(int)
df_plot['西暦(年)'] = df_plot['西暦(年)'].astype(str)

df_plot.plot(kind='line',x='西暦(年)')
スクリーンショット 2022-08-06 17.32.44
最後に東京都だけを抜き出して
人口推移を見ます

データフレームをそのまま
折れ線グラフに描画することが出来ます

東京都の人口は100年前は400万人弱
1945年に大きく減り
そこから猛烈な勢いで1970年頃までは急成長

1995年以降また増え続けています
こういうのをささっと作ることができるのが
Pythonの強みですね




まとめ

Google Colabなどを使えば
Pythonをインストールしなくても
PC上からファイル操作を行えます

エクセルなどで開けない
ファイルが有るときは
是非Pythonを活用してみましょう。

それでは

このページのトップヘ