分析のためにpythonを習得しているので、具体的な集計・分析手法や前処理の方法を学んできたが
ここらでよく使うpandasについてちゃんと勉強してみよう、と思いメモ。
知っている人には当たり前なんだろうけど、どこかふわふわした理解のままではスッキリしないので。
参考文献はこちら
Pythonによるデータ分析入門
参考文献を見ながら、自分の手でコード書いて実行。
重要な箇所を見て気付いたことを少し書く、というスタイルですので無駄に長いです。。。
※引用箇所が参考文献の本文です。
シリーズ(Series)
よく出てきて混乱を起こしたやつ。わかったようなわからないような。
Numpyのarrayとの違いがよくわからなかった。
インデックスについて
シリーズは1次元の配列のようなオブジェクトです
連続した値と、それに関連付けられたインデックスというデータラベルの配列が含まれます
おお、なるほど。知らなかった、インデックスがセットになってくるのが大事なのか。
x = pd.Series([1,2,3,4])
x
<結果>
0 1
1 2
2 3
3 4
1、2、3、4はシリーズのデータで、0、1、2、3というのがインデックスなんですね。
これはインデックスを指定しなかったからこうなるようでインデックスを指定することもできる。
x = pd.Series([1,2,3,4], index = ['a','b','c','d'])
x
<結果>
a 1
b 2
c 3
d 4
インデックスを指定したのでちゃんと変わっています。
Numpyの配列と違って、1つの値や複数の値を参照するときにインデックスのラベルを使って指定することができます。
そういえばそんなことしていたな、と改めて納得。道場で基礎を磨くのはやっぱり悪くない。
x['b']
<結果>
2
計算
条件指定によるフィルタリング、スカラー値の掛け算、数学的な関数の適用、などのNumpyの関数
やNumpy風の操作を行った場合も、インデックスとデータ値との関連は保持されます。
x[x > 2]
<結果>
c 3
d 4
x * 2
<結果>
a 2
b 4
c 6
d 8
np.exp(x)
<結果>
a 2.718282
b 7.389056
c 20.085537
d 54.598150
計算されて値は変わってるけどインデックスは変わってないですね。理解。
シリーズをインデックスとデータ値がマッピングされた固定長の順序つきディクショナリととらえ
る見方もあります。ディクショナリを使う多くの文脈では、シリーズを使うことができるでしょう。
'b' in x
<結果>
True
'e' in x
<結果>
False
いろいろな作成方法
pythonのディクショナリ形式のデータがある場合は、それを使ってシリーズを作成することができます。
y = {'a': 100, 'b': 200, 'c': 300, 'd': 400}
z = pd.Series(y)
z
<結果>
a 100
b 200
c 300
d 400
ここまでやって、エクセルのINDEX関数で参照していたのが、今作っているインデックスなのかなと気付く。理解。
今作ったシリーズに対して
w = ['a','b','C','D']
t = pd.Series(y, index = w)
t
<結果>
a 100.0
b 200.0
C NaN
D NaN
インデックスを付け替えようとすると、元のインデックスと対応しているデータはそのままデータ値で、
そうでない箇所(ここでは大文字のCとD)は、NaNになっています。
NaNはpandasでは欠損値、または、NA値として扱われます。
また'c'と'd'は、指定したwには含まれていないため、作成されたシリーズからは除外されています。
そんな処理になっていたのか。これも知らなかった。
pandasのisnull関数とnotnull関数は欠損値を特定するために使います。
pd.isnull(t)
<結果>
a False
b False
C True
D True
pd.notnull(t)
<結果>
a True
b True
C False
D False
シリーズはこれらの関数をインスタンスメソッドとしても持っています
t.isnull()
<結果>
a False
b False
C True
D True
算術演算をするときに別々のインデックス付けされたデータが自動的に整形される、というものがあります。
z
<結果>
a 100
b 200
c 300
d 400
t
<結果>
a 100.0
b 200.0
C NaN
D NaN
z + t
<結果>
C NaN
D NaN
a 200.0
b 400.0
c NaN
d NaN
シリーズのインデックスは代入して置き換えることができます。
t.index = ['1','2','3','4']
t
<結果>
1 100.0
2 200.0
3 NaN
4 NaN
ちゃんとインデックスが変わっています。
データフレーム(DataFrame)
これもわかるようなわからないようなの代表。
データフレームはテーブル形式のデータ構造を持ち、順序づけられた列を持っています。各列には
別々の型(数値型、文字列型、ブール型など)を持たせることができます。データフレームは行と列の
両方にインデックスを持っています
1列の中では同じ型です、ということかな、どのようにデータを持っているかが大事なのか。
行方向にも列方向にもインデックスがある→インデックスの組み合わせでデータを特定できそう。
データフレームはシリーズをバリューとしても持つディクショナリと見ることができます。
(各シリーズのインデックスを全体で共有しているようなイメージです)
ちょっとイメージが湧かないので、今はスルーします。
作成方法
一般的な方法は同じ長さを持つリスト型のバリューを持ったディクショナリか、Numpyの配列を使う方法です。
data = {'state': ['Ohio', 'Ohio', 'Ohio', 'Nevada', 'Nevada', 'Nevada'],
'year': [2000, 2001, 2002, 2001, 2002, 2003],
'pop': [1.5, 1.7, 3.6, 2.4, 2.9, 3.2]}
frame = pd.DataFrame(data)
frame
<結果>
state year pop
0 Ohio 2000 1.5
1 Ohio 2001 1.7
2 Ohio 2002 3.6
3 Nevada 2001 2.4
4 Nevada 2002 2.9
5 Nevada 2003 3.2
操作
列の並べ替え
列の順番を指定すると、データフレームの列はその順番で並びます。
pd.DataFrame(data, columns=['year', 'state', 'pop'])
<結果>
year state pop
0 2000 Ohio 1.5
1 2001 Ohio 1.7
2 2002 Ohio 3.6
3 2001 Nevada 2.4
4 2002 Nevada 2.9
5 2003 Nevada 3.2
左から指定した順番に並んでいることがわかります。
データの無い列の指定
指定した列がデータを持っていない場合、その列は結果として欠損値が代入されます
frame2 = pd.DataFrame(data, columns=['year', 'state', 'pop', 'debt'],
index = ['one','two','three','four','five','six'])
frame2
<結果>
year state pop debt
one 2000 Ohio 1.5 NaN
two 2001 Ohio 1.7 NaN
three 2002 Ohio 3.6 NaN
four 2001 Nevada 2.4 NaN
five 2002 Nevada 2.9 NaN
six 2003 Nevada 3.2 NaN
もともとなかった’debt’には欠損値のNaNが代入されています。
データの取り出し
データフレームの列はディクショナリ風の参照や、属性指定をすることで、シリーズとして
取り出すことができます。
frame2['state']
<結果>
one Ohio
two Ohio
three Ohio
four Nevada
five Nevada
six Nevada
取り出したシリーズはデータフレームの持っていたインデックスと同じインデックスを持ち、name属性も適切に設定されています。
行も一夜名前で参照することができます。名前で参照するときには、locという属性を使います。
frame2.loc['three']
<結果>
year 2002
state Ohio
pop 3.6
debt NaN
ここでは'three'という名前で参照しているので、'three'行のデータがカラム名と共に表示されています。
データの代入
列の値は代入して変更できます。
frame2['debt'] = 16.5
frame2
<結果>
year state pop debt
one 2000 Ohio 1.5 16.5
two 2001 Ohio 1.7 16.5
three 2002 Ohio 3.6 16.5
four 2001 Nevada 2.4 16.5
five 2002 Nevada 2.9 16.5
six 2003 Nevada 3.2 16.5
先ほどはNaN値であった'debt'に16.5が代入されているのがわかります。
frame2['debt'] = np.arange(6.)
frame2
<結果>
year state pop debt
one 2000 Ohio 1.5 0.0
two 2001 Ohio 1.7 1.0
three 2002 Ohio 3.6 2.0
four 2001 Nevada 2.4 3.0
five 2002 Nevada 2.9 4.0
six 2003 Nevada 3.2 5.0
列の長さが一致していれば、np.arangeを列指定で代入することもできるんですね。
なんとなくやっていたことの理解が深まる。
シリーズを列に代入する場合は、ラベルはデータフレームのインデックスに従って正確に一致
するように代入が行われ、データフレームのインデックスに対応するものがない場合は、欠損値が
挿入されます。
val = pd.Series([-1.2, -1.5, -1.7], index = ['two', 'four', 'five'])
frame2['debt'] = val
frame2
<結果>
year state pop debt
one 2000 Ohio 1.5 Nan
two 2001 Ohio 1.7 -1.2
three 2002 Ohio 3.6 NaN
four 2001 Nevada 2.4 -1.5
five 2002 Nevada 2.9 -1.7
six 2003 Nevada 3.2 NaN
おお、確かにインデックスが一致しているのでその箇所だけ代入されています。
存在しない列への代入(列の生成)、列の削除
存在しない列に代入を行うと、新しい列が作成されます。
delキーワードを使うと、ディクショナリと同じように列を消すことができます。
frame2['eastern'] = frame2.state == 'Ohio'
frame2
<結果>
year state pop debt eastern
one 2000 Ohio 1.5 NaN True
two 2001 Ohio 1.7 -1.2 True
three 2002 Ohio 3.6 NaN True
four 2001 Nevada 2.4 -1.5 False
five 2002 Nevada 2.9 -1.7 False
six 2003 Nevada 3.2 NaN False
もともとなかった'eastern'に代入しようとすると、新しく列が生成され、代入したいデータが入っています。
列の追加は確かにこんな風にやってた。仕組みがわかると面白い。
del frame2['eastern']
frame2
<結果>
year state pop debt
one 2000 Ohio 1.5 NaN
two 2001 Ohio 1.7 -1.2
three 2002 Ohio 3.6 NaN
four 2001 Nevada 2.4 -1.5
five 2002 Nevada 2.9 -1.7
six 2003 Nevada 3.2 NaN
ちゃんと消えていますね。
ネストしたディクショナリ
ネストしたディクショナリをデータフレームに渡すと、pandasは外側のディクショナリのキーを
列のインデックスとして解釈し、内側のインデックスのキーを行のインデックスとして解釈します。
pop = {'Nevata': {2001: 2.4, 2002: 2.9},
'Ohio': {2000: 1.5, 2001: 1.7, 2002: 3.6}}
frame3 = pd.DataFrame(pop)
frame3
<結果>
Nevata Ohio
2001 2.4 1.7
2002 2.9 3.6
2000 NaN 1.5
外側のディクショナリのキー = 'Nevada'、'Ohio'の2つですね。これが列のインデックスになっています。
内側のインデックスのキー = '2000'、'2002'と言った年データです。これが行のインデックスになっています。
行列の入れ替え
データフレームはNumpyの配列と同様な文法で転置することができます。
frame3.T
<結果>
2001 2002 2000
Nevata 2.4 2.9 NaN
Ohio 1.7 3.6 1.5
行と列が入れ替わっているのがわかります。
value参照
value属性を参照すると、データフレームの中のデータが2次元のndarrayとして戻されます。
frame3.values
<結果>
array([[2.4, 1.7],
[2.9, 3.6],
[nan, 1.5]])
インデックスオブジェクト
pandasのインデックスオブジェクトは、軸のラベルやその他のメタデータ(軸のname属性やnames属性など)を
保持する役目を持っています。シリーズやデータフレームを初期化するときに、配列やシーケンスなどで指定した
ラベルは、内部的にはインデックスオブジェクトに変換されます。
obj = pd.Series(range(3), index = ['a','b', 'c'])
index = obj.index
index
<結果>
Index(['a', 'b', 'c'], dtype='object')
説明のまんまですね。インデックスがオブジェクトになっています。
インデックスオブジェクトは配列と似ているだけでなく、固定長のセットしても機能します。
frame3.columns
<結果>
Index(['Nevata', 'Ohio'], dtype='object')
'Ohio' in frame3.columns
<結果>
True
まとめ
この本ですが、最初読んだとき意味不明で泣きそうでしたが、pythonの実際の操作をやってみて
少し慣れた今見るととても理解が進みました。
とりあえず手を動かしてみる、その後しっかり理解する、という順番で学んでいくの良さそうです。