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

プログラミング

新型コロナウイルスが流行ってきています。

ありがたいことに
データをまとめて頂いている方が
いらっしゃるので
そのデータを用いて可視化をしてみます。


解説動画はこちら




データの提供元は東洋経済新聞社の方のようです。
めちゃくちゃ感謝したいと思います。


新型コロナウイルス国内感染の状況
https://toyokeizai.net/sp/visual/tko/covid19/

制作:荻原 和樹(東洋経済オンライン編集部)
データソース:厚生労働省の報道発表資料。

更新履歴・ソースコード:GitHub
https://github.com/kaz-ogiwara/covid19/

さてそれではデータを可視化をしていきましょう。

まずは
ライブラリの読み込みです。

今回は下記のライブラリを用います。
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
%matplotlib inline


次に
データの読み込みです。
直接データを読み込み指定します。

file_path = 'https://toyokeizai.net/sp/visual/tko/covid19/csv/data.csv'
df = pd.read_csv(file_path)
df.head()


新No.
旧No.確定日年代性別居住地周囲の患者の発生※濃厚接触者\nの状況
0111/1530代神奈川県なし38名特定、健康観察終了
1221/2440代中国(武漢市)なし32名特定、健康観察終了
2331/2530代中国(武漢市)なし7名特定、健康観察終了
3441/2640代中国(武漢市)No.192名特定、健康観察終了
4551/2840代中国(武漢市)なし3名特定、健康観察終了

こんな感じのローデータですね。

年代や性別などで
日別の感染者の状況をまとめてくれています。


はじめに
性年代別で集計してみましょう。

年代の並びがアレなので先に指定しておきます。
# 年代の並びを指定
order = ['10歳未満', '10代', '20代','30代','40代','50代','60代','70代','80代','90代']
data = pd.crosstab(df['性別'],df['年代'])[order]
data
年代10歳未満10代20代30代40代50代60代70代80代90代
性別
01941112151231
4161017283318120

これをヒートマップにしてみましょう。
ヒートマップはseabornライブラリで可視化できます。
data = pd.crosstab( df['性別'],df['年代'])[order]
ax = sns.heatmap(data,annot=True, fmt="d")
ax.set_ylim(len(data), 0)
plt.title('性年代別感染者数')
plt.show()
download-4

棒グラフにもしてみます。
棒グラフはseabornでは
countplotで可視化できます。
sns.countplot(y="年代", hue="性別",data=df,order=order)
plt.title('性年代別感染者数')
plt.show()

download-3
50-60代の男性の感染者が多いですね。

全体的には女性に比べると
男性の方が感染する方が多いようです。

男性の方がかかりやすいのかどうかは
このデータからは分かりませんね。



次に日別です。

日の並びも先に指定します。
date_order = ['1/15', '1/24', '1/25', '1/26', '1/28', '1/29', '1/30', '1/31','2/4', '2/5', '2/11',
       '2/13', '2/14', '2/15', '2/16', '2/17', '2/18', '2/19', '2/20', '2/21',
       '2/22', '2/23', '2/24', '2/25', '2/26', '2/27', '2/28', '調査中']
plt.figure(figsize=(16,9))
sns.countplot(x="確定日", hue="性別",data=df,order=date_order)
plt.title('日別感染者数')
plt.show()
download-2

日別の性別での可視化です。

男女を積み上げ棒グラフにしてみます。
data = pd.crosstab(df['性別'],df['確定日'])[date_order].T

fig = plt.figure(figsize=(16,9))
ax = fig.add_subplot(1, 1, 1)
ax.bar(date_order, data['男'], label='男')
ax.bar(date_order, data['女'], bottom=data['男'], label='女')
ax.legend()
plt.title('日別感染者数')
plt.show()
download-1
2/22,2/27に感染者数が激増していますね。

グラフを累積にしてみます。
累積のデータを先に作って
それを可視化しています。

numpyのcumsumで累積データを作ることができます。
data = pd.crosstab(df['性別'],df['確定日'])[date_order]
data2 = np.cumsum(data.T)

fig = plt.figure(figsize=(16,9))
ax = fig.add_subplot(1, 1, 1)
ax.bar(date_order, data2['男'], label='男')
ax.bar(date_order, data2['女'], bottom=data2['男'], label='女')
ax.legend()
plt.title('日別累積感染者数')
plt.show()
download


最後に都道府県別でみてみましょう。
data = pd.crosstab(df['居住地'],df['性別'])
data2 = data.sort_values('男',ascending=True)
data2


fig = plt.figure(figsize=(16,9))
ax = fig.add_subplot(1, 1, 1)
ax.barh(data2.index, data2['男'], label='男')
ax.barh(data2.index, data2['女'], left=data2['男'], label='女')
ax.legend()
plt.title('都道府県別感染者数')
plt.show()
download-5

北海道がすごいことになっていますね。

2番目に東京と愛知です。

地域差はかなりあると思いますが
そもそも目には見えないので
どこに潜んでいるかは
正直感じることもできないと思います。

企業の中には
在宅勤務を始める会社も出てきていて

人混みの中に出ないことが
感染予防につながると思いますので
リモートワークはどんどん広がっていくと
いーなーと思っています。

まあ
我々エンジニアは
客先に行くことなんて稀なので
全然在宅でも仕事できちゃいますよね。

2,3月は
大人しくしていた方が
良さそうな気はしますね。

この機会に
Pythonプログラミングを
覚えてみてはいかがでしょうか?

Youtubeなどでも
講座を公開していますので
ぜひみてみてください。

それでは。

さて久々の画像加工の回です

opencvを用いると簡単に画像の加工ができます。



解説動画はこちら




はいそれではまず最初に
opencvを使うにはライブラリを読み込みましょう。

インストールしていない方は
pip などでインストールしてみてください。

まず最初に画像を加工する関数を作ります。

アニメ画像みたいな加工をする関数を作ります。
def anime_filter(img, K= 20):
    gray = cv2.cvtColor(img, cv2.COLOR_BGRA2GRAY)
    edge = cv2.blur(gray, (13, 13))
    edge = cv2.Canny(edge, 10, 40, apertureSize=3) 
    edge = cv2.cvtColor(edge, cv2.COLOR_GRAY2BGR)
    img = np.array(img/K, dtype=np.uint8)
    img = np.array(img*K, dtype=np.uint8)
    return cv2.subtract(img, edge)
これで画像をアニメ風にします。

やっていることはエッジの抽出とぼかしです

次にモザイク処理する関数を作ります。

def mosaic(src,ratio=0.07):
    img = src.copy()
    x, y, w, h = 0, src.shape[0]//3,src.shape[1],src.shape[0]//3
    tmp = img[y:y + h, x:x + w]
    small = cv2.resize(tmp, None, fx=ratio, fy=ratio, interpolation=cv2.INTER_NEAREST)
    img[y:y + h, x:x + w] = cv2.resize(small, tmp.shape[:2][::-1], interpolation=cv2.INTER_NEAREST)
    return img

モザイクはopencvのresize機能で実現できます。
この関数で画像の一部分にだけ
モザイク処理をすることができます。

はい
それでは実際に画像に処理してみましょう。

画像は4枚使用しますが
画像はご自身で用意して下さいね。

img1_path = 'smap/sawaziri.png'
img2_path = 'smap/makky.png'
img3_path = 'smap/ASKA.png'
img4_path = 'smap/pierre.png'
paths = [img1_path,img2_path,img3_path,img4_path]

fig = plt.figure(figsize=(20,16))

for i,(path,A) in enumerate(zip(paths,['S','M','A','P'])):
    ax = fig.add_subplot(1, 4, i+1)
    img = cv2.imread(path)
    anime = anime_filter(img)
    mos = mosaic(anime)
    cv2.putText(mos, A, (mos.shape[1]//2, mos.shape[0]//4), 
                cv2.FONT_HERSHEY_PLAIN, 4, (255, 255, 255), 5, cv2.LINE_AA)
    ax.imshow(cv2.cvtColor(mos, cv2.COLOR_BGR2RGB))
    ax.set_xticks([])
    ax.set_yticks([])
plt.show()

実行した結果は
・・・・・


download


はいこのようになりました。
アニメっぽくして
モザイクをかけて
文字を追加しております!!!!

S
M
A
P
で始まる方々を
画像加工してみました。

はい
ということで
opencvを用いると
簡単に画像加工をすることができるので
皆さんも試してみて下さいね

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


自然言語処理100本ノック
準備運動の続きです。

解説動画はこちら


自然言語処理100本ノックはこちら
自然言語処理100本ノック


前回はこちら
準備運動1

さて
続きですが

06. 集合

"paraparaparadise"と"paragraph"に含まれる
文字bi-gramの集合を, それぞれ, XとYとして求め,
XとYの和集合,積集合,差集合を求めよ.

さらに,'se'というbi-gramが
XおよびYに含まれるかどうかを調べよ.

文字列の集合を考える問題です。

bi-gramというのはふた文字の組み合わせです。

前回作成したn-gramを作る関数を
用いてbi-gramを作っていきます。
def n_gram(st, n):
    return [st[i:i+n] for i in range(len(st)-n+1)]
pythonでは集合を表現するデータ型があります。
set型は重複を排除したデータ構造です。

リスト型からset関数で作成できるので
bi-gramの結果をそのままsetにします。

X = set(n_gram("paraparaparadise",2))
Y = set(n_gram("paragraph",2))
print(X)
print(Y)
{'ap', 'ad', 'di', 'ra', 'is', 'ar', 'pa', 'se'}
{'ap', 'ph', 'ra', 'ar', 'pa', 'ag', 'gr'}

先頭から二文字ずつ切り取った文字を
集合にしているので重複した部分は
取り除かれます。

Pythonの集合の型同士で
そのまま計算ができます。

和集合、積集合、差集合
文字を含むかどうかの判定は
以下のようなコードになります。

# 和集合 | union
print(X|Y)

# 積集合 & intersection
print(X&Y)

# 差集合 - difference
print(X-Y)

# 含まれるかどうか in
print('se' in X)
print('se' in Y)
{'ap', 'ad', 'di', 'ra', 'ph', 'is', 'ar', 'pa', 'ag', 'gr', 'se'}
{'pa', 'ap', 'ar', 'ra'}
{'is', 'ad', 'se', 'di'}
True
False


07. テンプレートによる文生成

引数x, y, zを受け取り「x時のyはz」という
文字列を返す関数を実装せよ.

さらに,x=12, y="気温", z=22.4として,
実行結果を確認せよ.

恐らくですが
気温を英語に直すと
temperature

なので
template
とかけているんじゃ無いかと!!!!

素敵すぎる問題です。
嫌いじゃ無い、こういうの

ということで
Pythonでは文字列のformatで
文字の差込ができます。

def temperature(x,y,z):
    return '{0}時の{1}は{2}'.format(x,y,z)

temperature(x=12, y="気温", z=22.4)
'12時の気温は22.4'

メールの定型文など
文字の差し込みをする際に非常に便利で
業務でも多用します。

formatは覚えておくと
非常に重宝しますね。

08. 暗号文

与えられた文字列の各文字を,
以下の仕様で変換する関数cipherを実装せよ.
・英小文字ならば(219 - 文字コード)の文字に置換
・その他の文字はそのまま出力
この関数を用い,英語のメッセージを暗号化・復号化せよ.

まずは英小文字かどうかを
判定するという部分です。

これは非常に厄介で
Pythonでは文字列型にそれっぽい関数があるのですが
うまく判定されないという致命的な欠陥があります。

なので判定関数を先に作ります。

reライブラリを用いて
正規表現で英小文字を判定します。

import re

def isalpha(st):
    return re.compile(r'^[a-z]+$').match(st) is not None

この関数を用いると英小文字以外はFalseになります。

この判定結果を用いて
暗号文を作成する関数を作ります。

英小文字の際は
条件として
(219 - 文字コード)の文字に置換 
というのがあります。

文字というのは
全て文字番号が存在し
一文字ずつ番号が降られています。

Pythonでは
ord(文字列)
で文字番号を取得できます。

chr(番号)
で文字を取得できます。

組み合わせると文字を
変換できるというわけです。

最終的にできた関数はこちら。

def cipher(st):
    return ''.join([chr(219-ord(s)) if isalpha(s) else s for s in st])

message = '私の名前は乙pyです'

#暗号化
cipher_text = cipher(message)
print(cipher_text)

#復号化
print(cipher(cipher_text))
私の名前は乙kbです
私の名前は乙pyです

関数の結果は文字が暗号化され
その結果をもう一度関数にかけると
元の文字に復号されます。

09. Typoglycemia

スペースで区切られた単語列に対して,
各単語の先頭と末尾の文字は残し,
それ以外の文字の順序を
ランダムに並び替えるプログラムを作成せよ.
ただし,長さが4以下の単語は並び替えないこととする.

適当な英語の文(例えば
"I couldn't believe that I could actually understand
what I was reading :
the phenomenal power of the human mind ." )
を与え,その実行結果を確認せよ.


これは非常に厄介な問題ですね

考え方はいくつもあり
答えは一つにならないと思いますが
自分のコードはこうなりました。

import random

w = "I couldn't believe that I could actually understand what I was reading : the phenomenal power of the human mind ."

def typoglycemia(w):
    ws = w.split(' ')
    start,end=ws[0],ws[-1]
    base = ['' if len(a)>4 else a for a in ws[1:-1]]
    tmp = [a  for a in ws[1:-1] if len(a)>4]
    random.shuffle(tmp)
    result,c = [start],0
    for b in base:
        if b=='':
            result.append(tmp[c])
            c+=1
        else:
            result.append(b)
    return result + [end]

print(typoglycemia(w))
['I', 'power', 'actually', 'that', 'I', "couldn't", 'reading', 'believe', 'what', 'I', 'was', 'human', ':', 'the', 'understand', 'phenomenal', 'of', 'the', 'could', 'mind', '.']

実行毎に文字の順番は入れ替わります。

この場合の考え方としては
元の文章の四文字以下はそのままに
五文字以上は空白にしたデータを用意します。

別途五文字以上のものをリストに確保します。

元の文章の空白部分に
ランダムに並び替えしたリストを
順番に差し込んでいます。

もう少しスマートに書けるかなと思います
リファクタリングは大事ですね。

さて
これで準備運動は終わりです。

これで
準備運動ですからねー

なかなかパズルのようで
面白いですよね。

続きはまた
それでは。


今回は
自然言語処理100本ノックの準備運動を解いてみました。



解説動画はこちら


自然言語処理とは
コンピューターに言語を処理させるための
一連の方法です

自然言語処理100本ノックはこちら
自然言語処理100本ノック

プログラミングを行う上で
こういった文字列の処理方法を覚えておくと
プログラミングスキルが格段にアップします。

やっておいて損はしないと思います。

どの言語でも対応することは可能です。

さてPython言語を用いて
1つ1つ解いていきましょう。

第1章: 準備運動 からです。

00. 文字列の逆順

文字列"stressed"の文字を逆に
(末尾から先頭に向かって)並べた文字列を得よ.

解き方としては
文字列のリバース方法を考えるです。

Python言語の
リスト型などはリバース用の関数がありますが
文字列には無いようです。

Pythonでは次のようにすることで実現できます。
"stressed"[::-1]
'desserts'

これだけです。
インデックスを用いると文字列を切り出すことができます。

インデックスの書き方は
開始位置 : 終了位置 : 飛ばす個数
になっています。

-1することで終わりから抽出を行います。

01. 「パタトクカシーー」
「パタトクカシーー」という文字列の
1,3,5,7文字目を取り出して連結した文字列を得よ.

文字の奇数番目を取り出す方法です。

Pythonの文字列のインデックスの操作で簡単に取り出せます。
"パタトクカシーー"[0::2]
'パトカー'

これだけですね。
インデックスの最後を2とすることで
1こ飛ばしで文字を抽出できます。

02. 「パトカー」+「タクシー」=「パタトクカシーー」

「パトカー」+「タクシー」の文字を
先頭から交互に連結して文字列「パタトクカシーー」を得よ.

文字を交互に組み上げる問題です。

ここから少し考えるのが必要になりそうですね。
こういうのは業務でも良く出てきそうな問題なので
覚えておくと仕事がはかどりますね。

文字列からの文字の切り出しは
インデックスを用いて
行うことができました。

単純の文字と文字を足すと
文字の連結になってしまいます。

交互にというのがポイントです。

zip関数を使うと2つの文字列やリストなどを組み合わせて
使うことができます。

''.join([p+t for p,t in zip('パトカー','タクシー')])
'パタトクカシーー'

一旦zipで1文字ずつ交互に切り出し、
それを内包表記で書いてあげるとリスト型になります。

文字列型のjoin関数でリスト型を文字列に連結できます。

03. 円周率
"Now I need a drink, alcoholic of course,
after the heavy lectures involving quantum mechanics."
という文を単語に分解し,
各単語の(アルファベットの)文字数を
先頭から出現順に並べたリストを作成せよ.


最終的に欲しいものはリストです。

各単語に分解するというのは
文字列型のsplit関数でスペース区切りで区切ると
実現できます。

アルファベットの・・・
と書かれているので
それ以外はの文字が不要になりますね。

文字列の削除はreplace関数で実現できます。

文章を単語にしたら
欲しいのは文字数ですね。

文字列の文字数はlen関数で測ることができます。

出来上がったコードはこのような形になりました。

w = "Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics."

[len(s) for s in w.replace(',','').replace('.','').split(' ')]
[3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8, 9, 7, 9]

replaceを二回続けていますが
reライブラリを用いると簡素になります。

import re

w = "Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics."

w = re.sub(r'[.,]', "", w)

[len(s) for s in w.split(' ')]
[3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8, 9, 7, 9]


04. 元素記号
"Hi He Lied Because Boron Could Not Oxidize Fluorine.
New Nations Might Also Sign Peace Security Clause.
Arthur King Can."
という文を単語に分解し,
1, 5, 6, 7, 8, 9, 15, 16, 19番目の単語は先頭の1文字,
それ以外の単語は先頭に2文字を取り出し,
取り出した文字列から単語の位置(先頭から何番目の単語か)
への 連想配列(辞書型もしくはマップ型)を作成せよ.


最終的に欲しいものは辞書型ですね。

まず単語分解は前問と同じようにsplitで行います。

次に考えることは何文字切り出すです。
これも文字列のインデックスで
実現できます。

文字の切り出しに条件がついています。
1,5,6,7,8,9,15,16,19番目だけ1文字
他は2文字

ややこしい条件ですよね。
条件分岐は IF文を用います。

文章から単語に切り分けた段階でリスト型になっています。
リスト型では順番に処理することができるので
そこにenumerate関数を加えると
何番目という数値を取ることができます。

あとは条件分岐とうまく設定すればいいです。

コードはこうなりました。

w = "Hi He Lied Because Boron Could Not Oxidize Fluorine. New Nations Might Also Sign Peace Security Clause. Arthur King Can."

{i+1:s[0] if i+1 in [1,5,6,7,8,9,15,16,19] else s[0:2] for i,s in enumerate(w.split(' '))}

内包表記では
FOR , IF文を組み合わせて使うことができます。

05. n-gram
与えられたシーケンス(文字列やリストなど)から
n-gramを作る関数を作成せよ.

この関数を用い,
"I am an NLPer"という文から
単語bi-gram,文字bi-gramを得よ.

n-gram
任意の文字列や文書を連続したn個の文字や単語で
分割するテキスト分割方法.

特に,nが1の場合をユニグラム(uni-gram)
2の場合をバイグラム(bi-gram)
3の場合をトライグラム(tri-gram)と呼ぶ.

ということでバイグラムを取得するための
関数を作れという問題ですね。

文字列はそのままfor文の繰り返しに用いることができます。

そこからインデックスを用いて
n文字を取得していけば良いという考えです。

コードは非常にシンプルになります。

def n_gram(st, n):
    return [st[i:i+n] for i in range(len(st)-n+1)]

for i in range(1,4):
    print(n_gram("I am an NLPer",i))
['I', ' ', 'a', 'm', ' ', 'a', 'n', ' ', 'N', 'L', 'P', 'e', 'r']
['I ', ' a', 'am', 'm ', ' a', 'an', 'n ', ' N', 'NL', 'LP', 'Pe', 'er']
['I a', ' am', 'am ', 'm a', ' an', 'an ', 'n N', ' NL', 'NLP', 'LPe', 'Per']

単語単位でやりたい場合は
一旦スペースでsplitすれば
単語単位のn-gramになります。

for i in range(1,4):
    print(n_gram("I am an NLPer".split(' '),i))
[['I'], ['am'], ['an'], ['NLPer']]
[['I', 'am'], ['am', 'an'], ['an', 'NLPer']]
[['I', 'am', 'an'], ['am', 'an', 'NLPer']]

まだまだ準備運動は続きますね

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



 

Youtubeの動画をダウンロードしたいと
思ったことないでしょうか?


Pythonプログラムなら
・・・

出来てしまうんですねーーー


解説動画はこちら



さてやり方についてですが
まずはプログラムをインストールしましょう。

youtube-dl

Youtubeの動画をダウンロードするプログラムです。

pipなどでインストールしておきましょう。

sudo -H pip install --upgrade youtube-dl

インストールできたら
まずはライブラリのインストールです。

import youtube_dl

これだけ

動画のダウンロードに関しては
まず動画のURLを調べておきます。

例:
https://www.youtube.com/watch?v=XXXXXXXXXXX

このXXXの部分が動画のIDになるので
これを控えておきます。

otupyの過去の動画のリンクを用いて
ダウンロードするプログラムは
以下のようになります。

download_url = 'https://www.youtube.com/watch?v=6PjkBT7QwiM'
output_file_name = 'abe'

ydl_opts = {
    'format': 'bestvideo+bestaudio/best',
    'outtmpl':  output_file_name + '.%(ext)s',
}

ydl = youtube_dl.YoutubeDL(ydl_opts)
result = ydl.extract_info(download_url, download=True)

URLと保存する際のファイル名を設定すれば
プログラムを実行した場所にダウンロードされると思います。

Jupyter Notebookであればノート上での再生も可能です。
import IPython.display
IPython.display.Video('abe.webm',width=480, height=270)
スクリーンショット 2020-02-02 16.26.47



はい、結構簡単に
ダウンロードできるんじゃないでしょうか?

注意点としては
動画の拡張子は
色々あるようで、webm,mkvなどは
そのままでは見れないかもしれません。

その場合は一旦mp4などに変換すれば
見れるかと思います。

大量にダウンロードしたいニーズがあるなら
試してみてはいかがでしょうか

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





このページのトップヘ