Remrinのpython攻略日記

python3に入門しました。python3についてあれこれとサンプルコードとか。

pythonの集合型 set

pythonの集合型setの使い方について。
 
・重複のない配列。
・順序がないのでs[1]のようなインデックス指定ができない。
・inメソッドによる検索が高速
 
○生成

# coding: utf-8

s1 = set()          # 空のset
print(s1)           # set()
print(type(s1))     # <class 'set'>

s2 = {1, 1, 2, 2, 3, 4}
print(s2)           # {1, 2, 3, 4}
print(len(s2))      # 4 要素数

s3 = {}
print(type(s3))     # <class 'dict'>     
     
list1 = [1, 3, 5, 7]
s4 = set(list1)
print(s4)           # {1, 3, 5, 7}

t = (2, 4, 6, 8)
s5 = set(t)
print(s5)           # {8, 2, 4, 6} 順序は保証されない

d = {"a":1, "b":5}
s6 = set(d)
print(s6)           # {'a', 'b'} キーのみの集合

s7 = set("akasaka") # 文字列からsetを生成
print(s7)           # {'s', 'a', 'k'}

s1 = {1, 3, 5}
s8 = s1.copy()
print(s8)           # {1, 3, 5}

 
○追加

s1 = {1, 3, 5}
s1.add(7)            # 要素7を追加
print(s1)            # {1, 3, 5, 7}

s1.add(1)            # すでに存在する要素を追加しても変更なし
print(s1)            # {1, 3, 5, 7}

s1 = {1, 3, 5}
list1 =[10, 20, 30]
s1.update(list1)     # listを追加
print(s1)            # {1, 3, 5, 10, 20, 30}

s1 = {1, 2, 3}
#s1.update(7, 8, 9)  # TypeError: 'int' object is not iterable
s1.update((7, 8, 9)) # タプルの追加は括弧が必要
print(s1)            # {1, 2, 3, 7, 8, 9}

s1 = {1, 2, 3}
s2 = {4, 5, 6}
d ={7:7, 8:8}
s1.update(s2, d)     # 複数のタプル、リスト、辞書、集合の追加ができる
print(s1)            # {1, 2, 3, 4, 5, 6, 7, 8}

 
○削除

s1 = {1, 3, 5}
s1.remove(5)       # 要素5を削除
print(s1)          # {1, 3}

#s1.remove(4)      # KeyError: 4 存在しない要素はエラー

s1.discard(4)      # 存在しなくてもエラーなし
s1.discard(3)      # 要素3を削除
print(s1)          # (1)

s1 = {1, 3, 5}
s1.pop()           # ランダムで削除
print(s1)          # {3, 5}

s1 = {1, 3, 5}
p = s1.pop()       # ランダムで削除し、その要素をpに代入
print(p)           # 1
          
s1 = {1, 2, 3, 4, 5}
s2 = {0, 1, 2, 3}
s3 = s1 - s2       # s1の要素からs2の要素を引く
print(s3)          # {4, 5}

s1 = {1, 2, 3, 4, 5}
s2 = {0, 1, 2, 3}
s3 = s1.difference(s2)  # s1 - s2と同義だが、set型以外でも。
print(s3)          # {4, 5}
#s4 = s1 + s2      # TypeError: unsupported operand type(s) for +: 'set' and 'set'

 
○全削除

s1 = {1, 3, 5}
s1.clear()     # 要素を全削除
print(s1)      # set()

del s1         # 変数s1を削除
#print(s1)     # NameError: name 's1' is not defined

 
○和集合

s1 = {1, 3, 5}
s2 = {1, 2, 3}
s3 = s1 | s2             # 和集合
print(s3)                # {1, ,2 ,3, 5}
         
s1 = {1, 3, 5}
s2 = {1, 2, 3}
s3 = s1.union(s2)        # s1 | s2 と同義で、set以外でも。
print(s3)                # {1, ,2 ,3, 5}

 
○積集合

s1 = {1, 3, 5}
s2 = {1, 2, 3}
s3 = s1 & s2             #共通部分(積集合)
print(s3)                # {1, 3}
         
s1 = {1, 3, 5}
s2 = {1, 2, 3}
s3 = s1.intersection(s2) # s1 & s2 と同義で、set以外でも。
print(s3)                # {1, 3}

 
○一方のみ

s1 = {1, 3, 5}
s2 = {1, 2, 3}
s3 = s1 ^ s2             # elements in s1 or s2 but not both
print(s3)                # {2, 5}
         
s1 = {1, 3, 5}
s2 = {1, 2, 3}
s3 = s1.symmetric_difference(s2)   # s1 ^ s2 と同義で、set以外でも。
print(s3)                          # {2, 5}

 
○累積演算ができる
s1 |= s2
s1 &= s2
s1 -= s2
s1 |= s2

s1 = {1, 3, 5}
s2 = {1, 2, 3}
s1 &= s2
print(s1)       # {1, 3}

 
○検索
in 含まれていたらTrue
not in 含まれていなかったらTrue

s1 = {1, 3, 5}
print(1 in s1)     # True
print(2 in s1)     # False
print(3 not in s1) # False
print(4 not in s1) # True

 
○部分集合

s1 = {1, 3, 5}
s2 = {1, 3}
s3 = {1, 2}

print(s1 >= s2) # True
print(s1 >= s1) # True
print(s1 > s1)  # False
print(s1 <= s2) # False
print(s3 <= s1) # False

print(s1.issubset(s2))   # print(s1 <= s2) と同義
print(s1.issuperset(s2)) # print(s1 >= s2) と同義

 
○set内包表記

a = {x for x in 'abracadabra' if x not in 'abc'}
print(a)   # {'r', 'd'}

 

すべての部分集合の列挙

集合型で作る方法がわからなかったので、itertoolsで。

# 全ての部分集合の列挙
from itertools import combinations

items = [1, 2, 3, 4]
subsets=[]
for i in range(len(items) + 1):
    for c in combinations(items, i):
        subsets.append(c)

print(subsets)      # [(), (1,), (2,), (3,), (4,), (1, 2), ..., (2, 3, 4), (1, 2, 3, 4)]
print(len(subsets)) # 16

 
・frozensetについてはまたそのうち
 
参考:
pythonドキュメント データ構造

はてなブログ用の表組みの自動生成

少量のデータなら手打ちでいいですが、大量の表形式のデータをブログに書く手間を省きたいものです。

ということで、サンプルです。
 
○例1
百人一首のを収めたテキストファイルから、はてなブログ用の表組みを出力します。
ここのページにデータがあります。

# coding: utf-8

with open("hyaku.txt", "r") as f:  # txt形式の読み込み
    data = f.read()                # ひとまとまりのデータ
   
data = data.split("\n")
print(len(data))                   # 100

for i, string in enumerate(data):
    t = "|*{}|{}|".format(i + 1, string)
    print(t)
#|*1|あきのたの かりほのいほの とまをあらみ わがころもでは つゆにぬれつつ|
#|*2|はるすぎて なつきにけらし しろたへの ころもほすてふ あまのかぐやま|
#|*3|あしびきの やまどりのをの しだりをの ながながしよを ひとりかもねむ|
# ...    

 
この出力をコピーしてはてな記法のページにコピペですね。
ウェブの表組みを読み込んでリストを作る場合は

import pandas as pd

url = 'http://python-remrin.hatenadiary.jp/entry/2017/04/23/000000'
## DataFrameのリストを得る。header=0のオプション指定で、最初の行をheader扱い。
fetched = pd.io.html.read_html(url)
print(len(fetched))  # listの要素であるDataFrameの個数
df = fetched[0]

# 2次元リスト化
data = []
for i in range(len(df)):
    x = []
    for j in range(len(df.iloc[0])):
        x.append(df.iloc[i, j])
    data.append(x)
print(data)

# hatena表組み化
for i in range(len(df)):
    s = "|"
    for j in range(len(df.iloc[0])):
        s += str(df.iloc[i, j]) + "|"
    print(s)

  
○例2
アンスコムの例

url = 'http://python-remrin.hatenadiary.jp/entry/2017/04/23/000000_1'
fetched = pd.io.html.read_html(url) # DataFrameのリスト
print(len(fetched))  # 1   listの要素であるDataFrameの個数

print(fetched[0])
df = fetched[0]

# hatena表組み化
for i in range(len(df)):
    s = "|"
    for j in range(len(df.iloc[0])):
        s += str(df.iloc[i, j]) + "|"
    print(s)

 
作りたい表の形式が変わるときは、プログラムをいじって対応です。

pandasの使い方(4)ファイルの読み込み

pandasを利用したファイルの読み込みについて。 
 
HTMLの表組み(TABLE)をDataFrameオブジェクトのリストで取得。 
Definition :  

read_html(io, match='.+', flavor=None, header=None, index_col=None, skiprows=None, attrs=None, parse_dates=False, tupleize_cols=False, thousands=',', encoding=None, decimal='.', converters=None, na_values=None, keep_default_na=True)

 
○使用例1
ブログに百人一首のテストデータを用意しました。

# coding: utf-8

import pandas as pd

url = 'http://python-remrin.hatenadiary.jp/entry/2017/04/23/000000'
fetched = pd.io.html.read_html(url) # DataFrameのリスト
print(len(fetched))                 # 1   listの要素であるDataFrameが1個

d = fetched[0]                      # そのデータフレームをdに代入
l = list(d[1])                      # 1の列を抽出してリスト化
print(l[0])                         # あきのたの かりほのいほの ...
print(len(l))                       # 100...100首

string = "\n".join(l)               # 改行コードでつなぐ
with open("hyaku.txt", "w") as f:   # w:上書きモード
    f.write(string)                 # 書き込めるのは文字列型のみ

 
○使用例2
アンスコムの例

url = 'http://python-remrin.hatenadiary.jp/entry/2017/04/23/000000_1'
fetched = pd.io.html.read_html(url) # DataFrameのリスト
print(len(fetched))  # 1   listの要素であるDataFrameの個数

print(fetched[0])
df = fetched[0]

# 2次元リスト化
data = []
for i in range(len(df)):
    x = []
    for j in range(len(df.iloc[0])):
        x.append(df.iloc[i, j])
    data.append(x)
print(data)

# hatena表組み化
for i in range(len(df)):
    s = "|"
    for j in range(len(df.iloc[0])):
        s += str(df.iloc[i, j]) + "|"
    print(s)

 
csvファイルの読み込み
以下の内容のtest.csvを準備して

1,20
2,30
3,40
4,50

「20」の列だけを読み取る

import pandas
csv = pandas.read_csv('test.csv')
print(csv["20"])
# 0    30
# 1    40
# 2    50
# Name: 20, dtype: int64

 
詳しい使い方はもう少し調べてから。
 
参考:
HTML の表 (tableタグ) をスクレイピングする時も pandas が超便利
アンスコムの例 - Wikipedia

pythonでファイルの読み書き

pythonでファイルの読み書きです。
 
python3で外部ファイルを開くときはcodecsモジュールは不要です。
 
パスが通ったディレクトリにhyaku.txtがあることを前提にしています。
なければ、メモ帳などで適当に複数行の文字を書き込んだりで。
今回は百人一首を書き込んだテキストファイルを準備しました。 

# coding: utf-8

#テキストファイルの読み込み
with open("hyaku.txt", "r") as f:  # txt形式の読み込み
    data = f.read()                # ひとまとまりのデータとして読み込む
 
data = data.split("\n")            # 改行コードで1行ずつに分割
print(data)                        # 'あきのたの... と100行分表示
print(len(data))                   # 100
 
#テキストファイルへの書き込み(上書き)
text = "\n".join(data)             # 改行コードでつなぐ
with open("hyaku.txt", "w") as f:  # w:上書きモード
    f.write(text)                  # 書き込めるのは文字列型のみ

 
○open → 読み(書き) → closeとしたり、try/catchする方法もあるが、
 途中の記述で予期せぬエラーが出た場合のため、
 自動的にファイルを閉じるwith asの書き方を推奨。
 
○file()オブジェクト[built-in] のモード

"r" 読み込み(デフォ)
"w" 書き出し
"x" 新規作成して書き込み用に開く。すでに存在すれば失敗
"a" 書き込み用に開き、既に存在するファイルなら末尾に追加
"b", "t" "t":テキストモード(デフォ)、"b":バイナリモード
+ 書き込みと読み出しの両方を可能にする。

 

ファイルの読み込み方

(1)f.read() 全行一括読み込み
    ・f.read(100)などとして、100文字ずつ取り出せる。
    ・イテレートすると1文字ずつ取り出す。
    ・1行ごとイテレートする場合は、split("\n")してから。

with open("hyaku.txt", "r") as f:
    data = f.read(100) # 1つながりのstr型として最初の100文字を読み込む    
for i in data:         # str型をイテレータにすると、1文字ずつ取り出す
    print(i)
# あ
# き
# の
# た
# の
# ...

 
(2)f.readlines() 全行を読み込み、行ごとのリストを取得。
    ・改行コードは実行されずに、文字として(\n)を含んだまま。
    ・イテレートすると1行ずつになる。

with open("hyaku.txt", "r") as f:
    lines = f.readlines()
    print(lines) #改行コードを含んだままのstr型のリスト
['あきのたの かりほのいほの とまをあらみ わがころもでは つゆにぬれつつ\n', 'はるすぎて...

 

(3)f.readline() 1行ずつ読み込む。文末の改行コードが実行、つまり改行される。

with open("hyaku.txt", "r") as f:
    line = f.readline()
    while line:
        print(line)
        line = f.readline()

 
(4)ファイルオブジェクトのままイテレートすると、1行ずつになる。
    ・文末の改行コード(\n)は実行、つまり改行される。

with open("hyaku.txt", "r") as f:
    for line in f:
        print(line, end="")      #改行がかぶらないように、endオプションを指定する
# あきのたの かりほのいほの とまをあらみ わがころもでは つゆにぬれつつ
# はるすぎて なつきにけらし しろたへの ころもほすてふ あまのかぐやま
# ... 

 
(5)ファイルの最初の5行を表示する。

print("".join([line for n, line in enumerate(
        open("data/test1.txt", encoding="utf-8")) if n < 5]))

 
[読み込みtips]
・読み込むファイルには空白行が混じっていることもあるので削除。
・文末の改行\n消し。
・タブ(\t)やカンマ、半角スペース区切りを利用して .split("\t")など
・空のリストなどを準備してappendしていく。
・読み込みデータが1行に2つずつなら、この後dict()で辞書型にしたりもできる。
 
○ファイルの探索順序
1.実行中のファイルのあるフォルダ
2.カレントフォルダ
3.環境変数「PYTHONPATH」に列挙したフォルダ
4.sys.pathに登録してあるフォルダ
5 パスの通ったフォルダ内に/Users/ユーザ名/~で始まるパスを記入した.pthファイルを置く。
(例) site-package内にmymodulesというフォルダを置く。
site-package内にmymodules.pthというファイルを置き、中にパス名を記述。
 

ファイルの書き込み

文字列を1つ準備し、ファイルに書き込む

string = "test string"
with open("test.txt", "w", encoding="utf-8") as f:
    f.write(string)

 
文字列のシーケンスを準備して、ファイルに書き込む。
改行コードは付加されない。

strings = ["test",  "string", "No.2"]
with open("test.txt", "w", encoding="utf-8") as f:
    f.writelines(strings)
# txtファイルの内容:teststringNo.2

 
改行コードを付けたいときは、"\n".join()など

strings = ["test",  "string", "No.2"]
with open("test.txt", "w", encoding="utf-8") as f:
    f.write("\n".join(strings))
# txtファイルの内容:
# test
# string
# No.2

 
python用のデータ保存にはpickleライブラリも使えます。
 

バイナリファイルを扱う。

テキスト形式以外のファイルはすべてバイナリファイルとなります。
bytes型のオブジェクトとして読み取るので、比較などは「b"..."」などバイト文字列として扱う。
bytes型についてはこちらを参照

with open("data/img01.png", "rb") as imgfile:
    imgsrc = imgfile.read()
    print(type(imgsrc))             # <class 'bytes'>
    if imgsrc[1:4] == b"PNG":
        print("pngファイルです")
    else:
        print("pngファイルではありません")
# pngファイルです

 

システムのエンコードの確認

import sys
print(sys.getfilesystemencoding())
# mbcs

MBCS: Multibyte Character Set
マルチバイト:1バイト文字や2バイト文字が混在すること。
 

pickleライブラリ

python用のデータをファイルに保存する。
Python オブジェクトの直列化および直列化されたオブジェクトの復元。
バイナリ形式でpython固有のフォーマット。
 
長所:
・ほとんどの組み込み方やクラスも保存できる。
(lambda関数などはpickle化できない)
・型を気にしない。
短所:
python以外との互換性がない。
・型が自由なので整合性がとりにくい場合がある。
 
類似のモジュール:
marshal:非推奨
JSON(JavaScript Object Notation):テキスト形式。python以外でも広く使える。
 
○pickle化のプロトコルバージョン
プロトコルバージョン 0 「人間に判読可能な」プロトコル
プロトコルバージョン 1  旧形式のバイナリフォーマット
プロトコルバージョン 2  Python 2.3以降
プロトコルバージョン 3  Python 3.0以降。デフォルトのプロトコルで、推奨
プロトコルバージョン 4  Python 3.4 で追加されました。
 
○書き込み(pickle化)
・pickle.dump(obj, file, protocol=None, *, fix_imports=True)
 すでにオープンしている ファイルオブジェクト file に書き込む
 Pickler(file, protocol).dump(obj) と等価
・ pickle.dumps(obj, protocol=None, *, fix_imports=True)
 ファイルに書く代わりに、bytes オブジェクトとしてオブジェクトの pickle 表現を返す。

○読み込み
・pickle.load(file, *, fix_imports=True, encoding="ASCII", errors="strict") 
 Unpickler(file).load() と等価
 pickle のプロトコルバージョンは自動的に検出。
 fileオブジェクトにはread(), realdline()メソッドがある。
・pickle.loads(bytes_object, *, fix_imports=True, encoding="ASCII", errors="strict")
 bytes オブジェクトから pickle 化されたオブジェクト階層を読み込み、再構成して返す。 
 

import pickle

data = ["東京", 2020]
data2 = 5

with open('data.pkl', 'wb') as f:  # wb:バイナリ形式で書き込み
    pickle.dump(data, f)
with open('data.pkl', 'ab') as f:  # ab:バイナリ形式で追加書き込み
    pickle.dump(data2, f)

with open("data.pkl", "rb") as f:  # rb:バイナリ形式読み込み
    a = pickle.load(f)
    b = pickle.load(f)

print(a, b)                        #['東京', 2020] 5

 
参考;
pythonドキュメント

pandasの使い方(1)

pandasライブラリの使い方。
 
pandasはSeries(1次元)とDataFrame(2次元)という2つのデータ構造を持つ。
 
今回はまずSeriesの使い方。
 
○シリーズ(1次元配列)を作る。

import pandas as pd
l = [3, 4, 5, 6, 7]
series = pd.Series(l)
print(series)
# 0    3
# 1    4
# 2    5
# 3    6
# 4    7
# dtype: int64

 
index(label)が自動で割り振られます。
indexだけの取得:Series.index
値だけの取得 :Series.values

print(s1.index)         # RangeIndex(start=0, stop=5, step=1)
print(type(s1.index))   # <class 'pandas.indexes.range.RangeIndex'>
print(s1.values)        # [3 4 5 6 7]
print(type(s1.values))  # <class 'numpy.ndarray'>

Seriesのvalueはnumpyのndarreyオブジェクトになっています。
 
indexを自分でつけてSeriesを作るにはindexオプションで配列を指定する。

s2 =pd.Series(l, index=["a", "b", "c", "d", "e"])
print(s2)
# a    3
# b    4
# c    5
# d    6
# e    7
# dtype: int64

print(s2.index) # Index(['a', 'b', 'c', 'd', 'e'], dtype='object')

 
indexを置き換えるときは、index属性に代入

d = pd.date_range("20170501", periods=5)
s1.index = d     # indexをdで置き換える
print(s1)
# 2017-05-01    3
# 2017-05-02    4
# 2017-05-03    5
# 2017-05-04    6
# 2017-05-05    7
# Freq: D, dtype: int64

 
・値の参照、代入はindex指定で。

print(s2["a"]) #  3
s2["a"] = 10

print(s2[["a", "b", "c"]])
# a    10
# b     4
# c     5
# dtype: int64

#print(s2["a", "b", "c"]) # KeyError: ('a', 'b', 'c')

 
フィルタリング、numpy演算、関数適用ができる。

# フィルタリング:5より大きい要素を選ぶ
print(s2[s2 > 5]) 
# a    10
# d     6
# e     7
# dtype: int64

# 演算はnumpyと同様にブロードキャスト
print(s2 * 2)
# a    20
# b     8
# c    10
# d    12
# e    14
# dtype: int64

# 関数適用もブロードキャスト(ユニバーサル)
print(np.power(2, s2))
# a    1024
# b      16
# c      32
# d      64
# e     128
# dtype: int64

 
・辞書型のような存在判定ができる。

print("a" in s2) # True
print("A" in s2) # False

 
・辞書型オブジェクトからSeriesを作れる。
 このとき、indexをキーとしてソートされる。

roman = {"I":1, "V":5, "X":10, "L":50, "C":100}
s3 = pd.Series(roman)
print(s3)
# C    100
# I      1
# L     50
# V      5
# X     10
# dtype: int64

 
・辞書型+index配列指定をした場合、存在しないデータはNaNとなる。
 NaN:not a number(欠損値、NA)

roman = {"I":1, "V":5, "X":10, "L":50, "C":100}
s4 = pd.Series(roman, index=["V", "W", "X", "Y", "Z"])
print(s4)
# V     5.0
# W     NaN
# X    10.0
# Y     NaN
# Z     NaN
# dtype: float64

 
・欠損値の特定はpd.isnull()関数、pd.notnull()関数
 またはSeries.isnull()メソッド、Series.notnull()メソッド

# isnull()関数
print(pd.isnull(s4["W"]))   # True
print(pd.notnull(s4["W"]))  # False
print(pd.isnull(s4))
# V    False
# W     True
# X    False
# Y     True
# Z     True
# dtype: bool

# isnull()メソッド
print(s4.isnull())
# V    False
# W     True
# X    False
# Y     True
# Z     True
# dtype: bool

 
・Seriesどうしの演算。
 どちらか一方がNaNなら結果もNaN

print(s3 + s4)
# C     NaN
# I     NaN
# L     NaN
# V    10.0
# W     NaN
# X    20.0
# Y     NaN
# Z     NaN
# dtype: float64

 
・Seriesやindexにname属性を付けられる。

s3.name = "roman"
s3.index.name = "fig."
print(s3)
# fig.
# C    100
# I      1
# L     50
# V      5
# X     10
# Name: roman, dtype: int64

python開発環境の準備とspyder

anacondaを使う人が多いようですが、私はWinpythonを使っています。
名前的にwindows向けな気がしただけで選びました。
パソコンは5年くらい前のVAIOで、OSはWindows7です。

Winpythonはspyder、ライブラリも同梱でインストールも楽でした。
かなりの数のライブラリが付属しているので、いろいろ助かります。
 
ふだんはspyderを使ってコードを組んでます。
 
シンタックスハイライトで、自動で色を付けてくれる。
 色の設定変更も可能。(ツール→設定→構文強調の配色)
 赤:数値
 緑:文字列
 青:キーワード
 紫:組み込み
 茶:インスタンス
 灰:コメント
 
・リアルタイムコード分析
 コードを実行しなくても、自動で構文エラーなどをチェックし表示する。
 
・かっこを自動で閉じてくれる。
 
・カーソルの位置の変数と同一名変数をアンダーラインで色付け。
 タイプミスがわかりやすい。
 
便利な操作など
・F9 カーソルのある行のコードを実行。複数行も可能。
・CTRL+Iでカーソルの位置のメソッドの詳細をヘルプコンソールに表示
・ドットを打ち込むと対応する属性・メソッドが表示され、TABで補完できる。
 タイプミスがかなり減る。
・CTRL+TABで複数行を一括インデント。解除はSFHIFTで。
・変数エクスプローラーに実行した変数(メモリに残っている変数)が表示。
 変数cがメモリ上に残っている場合、
 右下のコンソールからc??と打ち込むと、変数の詳細情報を表示。

c??
Type:           Counter
String form:    Counter({'b': 2, 'a': 1, 'c': 0})
Length:         3
File:           c:\py3531\python-3.5.3\lib\collections\__init__.py
Source:        
class Counter(dict):