本記事は『現場で使える!pandasデータ前処理入門 機械学習・データサイエンスで役立つ前処理手法』の「CHAPTER 2 pandasのデータ構造」より「2.1 シリーズ」を抜粋したものです。掲載にあたり一部を編集しています。
2.1.1 シリーズの概要
シリーズはインデックス付けされた複数のデータ型(int、str、float等)を持つことが可能な1次元配列のオブジェクトです。
インデックスとはデータに対して付与されるラベルです。データの参照や様々な処理で使われます。インデックスには数値や文字列を使うことが可能で、重複した値を指定することもできます。
1次元の配列オブジェクトと言われてもイメージがつきにくいですが、シリーズは図2.1のようにインデックスと1列のデータで構成されます。データ参照や処理を行うため、インデックス(つまりラベル)が各データに対して必ず付与されます。
シリーズの生成
シリーズはSeriesクラスのコンストラクタで作成することが可能です。Pythonのリストやディクショナリから作成することができます。height(身長)のデータをPythonのリストに格納して、Seriesコンストラクタを使ってシリーズを作ってみましょう。シリーズオブジェクトには初期設定で0から始まるインデックスラベルが、RangeIndexクラスにより付与されます(リスト2.1)。
リスト2.1 リストからシリーズを作成する
height_list = [185,162,171,155,191,166] height_series = pd.Series(height_list) print(height_series)
0 185 1 162 2 171 3 155 4 191 5 166 dtype: int64
シリーズはNumPy配列からも作成が可能です。weight(体重)のデータをNumPy配列として作成して、シリーズを生成してみましょう。リスト2.2では、リスト2.1と同様に0から始まる公差1の等差数列がインデックスラベルとして付与されており、各値はweight_arrの値となります。
リスト2.2 NumPy配列からシリーズを作成する
weight_arr = np.array([72,51,69,55,87,78]) weight_series = pd.Series(weight_arr) print(weight_series)
0 72 1 51 2 69 3 55 4 87 5 78 dtype: int64
シリーズにはオプションでname属性を付与することが可能です。name属性はシリーズをデータフレームと連結する時などに使われます。コンストラクタのname引数に文字列を渡してname属性を指定してみましょう(リスト2.3)。
リスト2.3 シリーズのname属性を指定
ser = pd.Series([1,2,3], name='some series') print(ser)
0 1 1 2 2 3 Name: some series, dtype: int64
コンストラクタのindex引数を使ってインデックスラベルを指定することが可能です。インデックスラベルは重複した値を指定することも可能ですが、必ずシリーズオブジェクトと同じ長さでなくてはいけません。リスト2.4のシリーズserのインデックスの値(変数labels)は重複した文字列が含まれますが、問題なくシリーズが生成されています。
リスト2.4 引数indexを指定してシリーズの作成
val = [1,2,3,4,5] labels = ['a','a','c','d','f'] ser = pd.Series(val, index=labels) print(ser)
a 1 a 2 c 3 d 4 f 5 dtype: int64
シリーズのインデックスラベルはシリーズオブジェクトのindex属性で確認できます。リスト2.4でindex引数へ指定した値を持つIndexオブジェクトが戻されます(リスト2.5)。Indexオブジェクトの詳細は2.3節で解説します。
リスト2.5 シリーズのindex属性
print(ser.index)
Index(['a', 'a', 'c', 'd', 'f'], dtype='object')
Pythonのディクショナリからもシリーズを生成することが可能です。コンストラクタのindex引数へ指定がない場合、ディクショナリのキーがインデックス、バリューがシリーズの要素の値になります(リスト2.6)。
リスト2.6 ディクショナリからシリーズを作成する
dic = {'T':185, 'H':162, 'B':171, 'R':155, 'M':191, 'S':166} ser = pd.Series(dic) print(ser)
B 171 H 162 M 191 R 155 S 166 T 185 dtype: int64
Python 3.6以降またはpandas 0.23以降
Pythopp3.6以降またはpandas 0.23以降でディクショナリのキーを使ってシリーズを作成した場合、シリーズの順序はディクショナリの順序通りとなります。それ以前のバージョンの場合はキーの値に応じて辞書順へ自動的に並べ替えられます。
ディクショナリによるシリーズ生成の際にindex引数を指定すると、index引数へ指定したインデックスラベルの値に応じてディクショナリのバリューの値が引き出されます。変数dicのキーは文字列a、b、cです。キーに含まれない「d」をindex引数に追加してシリーズを作成してみましょう。リスト2.7では、作成されたシリーズは変数dicのキーとindex引数の値に対応して要素の値が代入されます。変数dicのキーにdは含まれないので、シリーズのインデックスラベルdの要素は値の欠落を示す欠損値(NaN)となります。
リスト2.7 ディクショナリと引数indexの指定
dic = {'a':0,'b':1,'c':2} a = pd.Series(dic, index=['a','b','c','d']) print(a)
a 0.0 b 1.0 c 2.0 d NaN dtype: float64
MEMO:NaN
pandasでは標準的にNaN(not a member)が欠損値を表すのに使われます。欠損値の詳細は6.1節で詳しく解説します。
スカラー値からもシリーズを作成することが可能ですが、必ずindex引数を指定する必要があります。リスト2.8のように指定されたインデックスラベルの値に準じてスカラー値が代入されます。
リスト2.8 スカラー値からシリーズを作成
pd.Series(10, index=['A','B','C'])
A 10 B 10 C 10 dtype: int64
シリーズのインデックス参照
詳しいインデックス参照の操作方法は3.1節で解説します。この章ではシリーズの初歩的なインデックス参照の方法を確認していきましょう。
pandasのシリーズはNumPy配列と多くの部分で類似しています。リスト2.9ではインデックス参照の動作を確認するため、値に1~5の整数を持ち、インデックスラベルに「あ~お」の文字列を持つシリーズをコンストラクタを使って生成しています。
リスト2.9 シリーズの作成
val = [1,2,3,4,5] ser = pd.Series(val, index=list('あいうえお')) ser
あ 1 い 2 う 3 え 4 お 5 dtype: int64
角括弧[ ]を使ってシリーズのインデックス参照をしてみましょう。シリーズオブジェクトで角括弧を使った場合、シリーズのインデックスラベルによる参照が行われます。ser['あ']はシリーズserのインデックスラベルの値が「あ」の要素を参照します(リスト2.10)。
リスト2.10 角括弧によるインデックス参照
ser['あ']
1
シリーズオブジェクトもPythonのシーケンスと同様にスライスによるインデックス参照が可能です。ser['あ':'え']と表記した場合、インデックスラベル「あ」から「え」までのインデックスの参照を行います(リスト2.11)。インデックスラベルを使ったスライスによるインデックス参照は始点と終点の両方が含まれます。
Pythonのリストでスライスによるインデックス参照を行った場合は終点は含まれません。スライスによるインデックス参照の解説は3.1.2項を参照してください。
リスト2.11 スライスを使ったインデックス参照
ser['あ':'え']
あ 1 い 2 う 3 え 4 dtype: int64
シリーズはインデックスラベルに加えて、整数の位置インデックスでの参照も可能です。シリーズserのインデックスラベルは文字列「あいうえお」ですが、シリーズの要素の位置を指定して参照することが可能です。位置インデックスは0から始まる公差1の等差数列の整数です。位置インデックスによる参照を行う場合はiloc属性を使います。インデックスラベル「あ」の位置インデックスは「0」ですので、ser['あ']とser.iloc[0]が参照する値は同等になります(リスト2.12)。
リスト2.12 インデックスラベルと位置インデックス
print(ser['あ']) print(ser.iloc[0])
1 1
角括弧を使いシリーズの各要素の値に基づいてインデックス参照を行うことも可能です。比較演算子を使いシリーズserの要素が3より大きい要素のインデックス参照を行ってみましょう(リスト2.13)。シリーズの値が4と5の要素が参照されているのが確認できます。
リスト2.13 条件を指定したbool型で参照
ser[ser > 3]
え 4 お 5 dtype: int64
シリーズの演算
シリーズオブジェクトの初歩的な演算操作について確認していきましょう。シリーズserの各要素の値に対して整数2を加算する処理を行ってみます。シリーズはNumPy配列と同じく要素毎に反復処理を行う必要はありません。算術演算子を使ってシリーズの各要素の処理を行うことが可能です。ser + 2を実行してシリーズの各要素に2を加算してみましょう(リスト2.14)。
リスト2.14 シリーズと演算子+
ser + 2
あ 3 い 4 う 5 え 6 お 7 dtype: int64
参考までにですが、Pythonのリストで演算子+は要素の連結を行いますのでシリーズとの動作が大きく異なります。1~5の値を持つリストを作成して変数mylistへ格納し、mylist + [2]を実行してみましょう(リスト2.15)。Pythonのリストを保持するmylistの最後の要素に整数2が連結されているのを確認できます。
リスト2.15 リストと演算子+
mylist = [1,2,3,4,5] mylist + [2]
[1, 2, 3, 4, 5, 2]
Pythonのリストの各要素に2を加算する場合はfor文を使って各要素毎に加算の処理を行う必要があります。for文を使いリストmylistの各要素へ演算子+=を使い2を加算してみましょう(リスト2.16)。pandas のシリーズオブジェクトは演算子+で各要素への加算を行うことができますので、Pythonのリストと動作が異なります。
リスト2.16 リストの要素へ加算処理
for i in range(5): mylist[i] += 2 print(mylist)
[3, 4, 5, 6, 7]
他にも乗算や除算などの演算もpandasのシリーズでは各要素に対して一度に処理を加えることが可能です。それぞれ演算子を使い動作を確認してみましょう(リスト2.17)。
リスト2.17 シリーズの各要素の乗算と除算
ser * 2
あ 2 い 4 う 6 え 8 お 10 dtype: int64
ser / 2
あ 0.5 い 1.0 う 1.5 え 2.0 お 2.5 dtype: float64
シリーズオブジェクト同士の演算ではインデックスラベルの値に基づいて処理が行われます。2つのシリーズオブジェクトの演算はインデックスラベル毎に自動的に調整して処理されます。シリーズserのインデックスラベルは「あいうえお」なのに対し、リスト2.18のシリーズser2のインデックスラベルは「あいうえか」です。
ser + ser2とした結果、ser3のインデックスラベルは両方のシリーズのインデックスラベルの和集合(少なくともどちらか一方の集合に属する要素全体の集合)となります。両シリーズに対応するインデックスラベルの箇所は各要素の加算された値が戻されますが、インデックスラベルの対応がない箇所は欠損値(NaN)が代入されます。
リスト2.18 シリーズ同士の演算
ser2 = pd.Series([6,7,8,9,10], index=list('あいうえか')) ser3 = ser + ser2 print(ser3)
あ 7.0 い 9.0 う 11.0 え 13.0 お NaN か NaN dtype: float64
pandas のオブジェクトはこのようにラベルと要素が紐付いており、原則的にラベルと要素を1つの対として扱います。この原則により、多くのデータ処理を柔軟かつ効率的に行えるメリットがあります。
2.1.2 シリーズの基本操作
シリーズオブジェクトはpandasの最も基本的なデータ構造であり、データ分析の現場ではシリーズを扱うことは多々あります。この項ではシリーズオブジェクトの基本的な属性(Attributes)とメソッド(Methods)を解説します。
データフレーム(2.2節で詳しく解説します)とシリーズの属性、メソッドは共通で使えるものが多数あります。すべての属性・メソッドを覚えておく必要はありませんが、使用頻度の高い初歩的なものは覚えておくとよいでしょう。
シリーズの属性
シリーズオブジェクトのインデックスラベルを確認するindex属性を確認してみましょう。index属性はシリーズのIndexオブジェクトを戻します。
Indexオブジェクトには様々な種類があります。コンストラクタのindex引数を指定しない場合は初期値のRangeIndex、整数を指定した場合はInt64Index、文字列はIndexになります。それぞれ異なるインデックスラベルを持つシリーズをコンストラクタに指定して、index属性で各シリーズのインデックスを確認してみましょう(リスト2.19)。Indexオブジェクトについては2.3節で詳しく解説します。
リスト2.19 シリーズのindex属性
val = [1,2,3,4,5] a = pd.Series(val) b = pd.Series(val, index=[0,1,2,3,4]) c = pd.Series(val, index=list('abcde')) print(a.index) print(b.index) print(c.index)
RangeIndex(start=0, stop=5, step=1) Int64Index([0, 1, 2, 3, 4], dtype='int64') Index(['a', 'b', 'c', 'd', 'e'], dtype='object')
dtype属性ではシリーズの要素のデータ型を確認することができます。それぞれ異なるデータ型の値を用いてdtype属性を確認してみます。シリーズは複数のデータ型を格納することが可能ですが、その場合のデータ型は「object」となります(リスト2.20)。
リスト2.20 シリーズのdtype属性
a = pd.Series(['a','b','c']) b = pd.Series([1,2,3]) c = pd.Series([1.0,2.0,3.0]) d = pd.Series([True,False,True]) e = pd.Series(['a',1,True]) print(a.dtype, b.dtype, c.dtype, d.dtype, e.dtype )
object int64 float64 bool object
シリーズのインデックス参照は角括弧[ ]以外にも、loc属性とiloc属性で参照することが可能です。iloc属性は位置インデックスの値を使って参照を行います。対して、loc属性はインデックスラベルによるインデックス参照を行います。loc属性、iloc属性共にスライスも対応しています。
まずは2つの異なるインデックスラベルを持つシリーズを作成して確認してみましょう(リスト2.21)。シリーズaは整数1~3、シリーズbは文字列a~cのインデックスラベルを指定します。
リスト2.21 異なるラベルを持つシリーズの作成
a = pd.Series([1,2,3], index=[1,2,3]) a
1 1 2 2 3 3 dtype: int64
b = pd.Series([1,2,3], index=list('abc')) b
a 1 b 2 c 3 dtype: int64
loc属性はインデックスラベルの値でデータの参照を行います。シリーズaのラベル「1」、シリーズbのラベル「a」を指定してインデックス参照をしてみましょう。それぞれ各インデックスラベルに該当する要素の値「1」を戻します(リスト2.22)。
リスト2.22 シリーズのloc属性を使ったインデックス参照
print(a.loc[1]) print(b.loc['a'])
1 1
iloc属性は整数による位置インデックスで要素の参照を行います。シリーズaとbの位置インデックス0のデータを参照してみましょう(リスト2.23)。
リスト2.23 シリーズのiloc属性を使ったインデックス参照
print(a.iloc[0]) print(b.iloc[0])
1 1
iloc属性、loc属性共にスライスに対応しています。しかし、終点の扱いが異なるので注意が必要です。loc属性は終点を含みますが、iloc属性は終点は含まれません。少しややこしいですが、正しく覚えておきましょう(リスト2.24)。
リスト2.24 loc属性とiloc属性のスライス
a.iloc[0:1]
1 1 dtype: int64
b.loc['a':'b']
a 1 b 2 dtype: int64
シリーズの他の属性も確認してみましょう。シリーズに含まれる要素の数を確認する場合はsize属性が便利です。size属性はあくまでシリーズに含まれる要素の数を確認するための属性です。値が重複していても関係ありません。重複した値を持つシリーズを作成してsize属性を確認してみましょう(リスト2.25)。
リスト2.25 シリーズのsize属性
ser = pd.Series([1,1,3,4,'a']) print(ser.size)
5
シリーズの要素の重複の可否を確認する際はis_unique属性を使用します。is_unique属性はシリーズの値に重複がなければTrue、重複があればFalseを戻します。一意の値を持つシリーズaと重複した値を持つシリーズbを作成してis_unique属性で確認してみましょう(リスト2.26)。
リスト2.26 シリーズのis_unique属性
a = pd.Series([1,2,3]) b = pd.Series([1,1,3]) print(a.is_unique) print(b.is_unique)
True False
機械学習フレームワークを用いてモデリングを行う際などに、pandasのシリーズの要素をNumPy配列として扱いたいケースもあります。values属性はシリーズをNumPy配列で戻します。ただし、シリーズに含まれるデータ型がpandas特有のcategory型の場合、NumPy配列となりませんので注意が必要です。
文字列と数値を持つシリーズaとbに加えて、category型の値を持つシリーズcを作成して確認してみましょう。シリーズのコンストラクタのdtype引数でシリーズの要素のデータ型を指定することが可能です(リスト2.27)。
リスト2.27 シリーズのvalues属性
a = pd.Series([1,2,3]) b = pd.Series(['a','b','c']) c = pd.Series(['a','a','b'], dtype='category') print(type(a.values), a.values) print(type(b.values), b.values) print(type(c.values), c.values)
[1 2 3] ['a' 'b' 'c'] [a, a, b] Categories (2, object): [a, b]
要素の変更と追加
シリーズの要素の値の変更を行いましょう。値の変更は変更したい箇所のインデックスを参照して値を代入します。スライスを使って複数要素の変更も可能です。要素の値が1~5、インデックスラベルにa~eを持つシリーズを作成しましょう(リスト2.28)。
リスト2.28 シリーズの作成
ser = pd.Series([1,2,3,4,5], index=list('abcde')) print(ser)
a 1 b 2 c 3 d 4 e 5 dtype: int64
ここで、シリーズserのインデックスラベルaの値を1から6へ変更します。また、スライスを使ってインデックスラベルb~dの要素を7へ変更してみましょう(リスト2.29)。
リスト2.29 シリーズの値の変更
ser['a'] = 6 ser['b':'d'] = 7 print(ser)
a 6 b 7 c 7 d 7 e 5 dtype: int64
要素の追加は既存のシリーズに含まれていないインデックスラベルと値を指定することで可能です。シリーズserのインデックスラベルはa~eまでのアルファベットの文字列です。現在シリーズのインデックスラベルに含まれていないインデックスラベル「あ」を角括弧に指定して値4を追加してみましょう(リスト2.30)。
リスト2.30 シリーズへ要素の追加
ser['あ'] = 4 print(ser)
a 6 b 7 c 7 d 7 e 5 あ 4 dtype: int64
シリーズオブジェクトのappendメソッドを使って異なるシリーズを連結することも可能です。値5、6を持ちインデックスラベル「い」「う」を持つシリーズser2を作成して、appendメソッドでシリーズserと連結をしてみましょう(リスト2.31)。
リスト2.31 シリーズのappendメソッド
ser2 = pd.Series([5, 6], index=['い','う']) ser.append(ser2)
a 6 b 7 c 7 d 7 e 5 あ 4 い 5 う 6 dtype: int64
appendメソッドのignore_index引数(初期値False)を使うことで、連結したシリーズのインデックスラベルを新しく振り直すことも可能です。リスト2.31ではシリーズserとser2の元のインデックスラベルが連結後のシリーズのインデッ クスラベルとして使われました。ignore_index引数へTrueを指定して新しくインデックスラベルを振り直してみましょう(リスト2.32)。
リスト2.32 appendメソッドのignore_index引数
ser.append(ser2, ignore_index=True)
0 6 1 7 2 7 3 7 4 5 5 4 6 5 7 6 dtype: int64
要素の排除
シリーズの要素の削除を行うには複数の方法があります。まずはPythonのdel文を使ってシリーズの要素を削除してみましょう。値1~3、インデックスラベルa~cを持つシリーズを作成して、インデックスラベルaの要素をdel文を使いシリーズから削除してみます(リスト2.33)。
リスト2.33 del文によるシリーズの要素の削除
ser = pd.Series([1,2,3],index=list('abc')) del ser['a'] ser
b 2 c 3 dtype: int64
シリーズオブジェクトのdropメソッドでも要素の削除が可能です。dropメソッドは指定した要素を削除したシリーズを戻しますが、inplace 引数(初期値False)をTrueとすることでシリーズを直接変更することが可能です。シリーズser のインデックスラベルb をdropメソッドを使って削除してみましょう(リスト2.34)。
リスト2.34 dropメソッドによるシリーズの要素の削除
ser.drop(index='b', inplace=True) print(ser)
c 3 dtype: int64
重複した値
データ分析では重複した値の処理を行う必要がしばしばあります。シリーズのdrop_duplicatesメソッドは重複した値をシリーズから除外します。keep引数(初期値first)に重複した値の除外方法を指定します。firstは重複した値の最初の値を残し、それ以外を除外します。lastは重複した値の最後を残し、それ以外を除外します。Falseと指定すると重複した値のすべてがシリーズから除外されます。重複する値を持つシリーズを作成して、drop_duplicatesメソッドのkeep引数へfirstを指定して、重複した値の最初の値を残す方法で重複を除外してみましょう(リスト2.35)。
リスト2.35 シリーズのdrop_duplicatesメソッド
ser = pd.Series([1,1,2,2,2,3], index=list('abcdef')) ser
a 1 b 1 c 2 d 2 e 2 f 3 dtype: int64
ser.drop_duplicates(keep='first')
a 1 c 2 f 3 dtype: int64
リスト2.35ではシリーズserのインデックスラベルa、c、fが残り、値が重複しているそれ以外の要素が除外されました。keep引数へFalseを指定した場合、値が重複しているすべての要素の除外を行うため、実行して残るのはインデックスラベルf(値3)のみとなります(リスト2.36)。
リスト2.36 drop_duplicatesメソッドのkeep引数
ser.drop_duplicates(keep=False)
f 3 dtype: int64
欠損値
データの一部が欠落している箇所を「欠損値」と呼びます。pandas の欠損値の扱いについては6.1節で詳しく解説します。この章ではシリーズオブジェクトに用意されている欠損値を扱うメソッドや属性について確認してみましょう。
欠損値の扱い方は様々ですが、最も単純な対処法は欠損値をデータから取り除くことです。欠損値を含むシリーズを作成して欠損値が含まれるかの確認を行い、欠損値をデータから取り除いてみましょう。まずは欠損値を含むシリーズをNumPyで非数を示す定数であるnp.nanを使って作成します(リスト2.37)。
リスト2.37 欠損値を含むシリーズの作成
ser = pd.Series([1,np.nan,3,4,np.nan], index=list('abcde')) print(ser)
a 1.0 b NaN c 3.0 d 4.0 e NaN dtype: float64
シリーズのisnaメソッドは要素に欠損値が含まれるかを確認します。シリーズの要素が欠損値の箇所はTrue、それ以外はFalseの値を持つ同サイズのシリーズを戻します。シリーズserのインデックスラベルbとeの要素はNaNで欠損値となります。isnaメソッドで戻されるシリーズもインデックスラベルbとeのみTrue、それ以外はFalseとなっているのが確認できます(リスト2.38)。
リスト2.38 シリーズのisnaメソッド
ser.isna()
a False b True c False d False e True dtype: bool
isnaメソッドはbool型の値を持つ同じサイズのシリーズを戻しますので、角括弧によるインデックス参照を使うことで欠損値の該当箇所のみをシリーズから取り出すことも可能です(リスト2.39)。
リスト2.39 isnaメソッドによる欠損値の抽出
ser[ser.isna()]
b NaN e NaN dtype: float64
欠損値をデータから除外する場合はdropnaメソッドが便利です。dropnaメソッドは要素が欠損値の箇所を除外したシリーズを戻します。シリーズserの要素が欠損値の箇所であるインデックスラベルbとeが除外されたシリーズが戻されるのが確認できます。(リスト2.40)
リスト2.40 シリーズのdropnaメソッド
ser.dropna()
a 1.0 c 3.0 d 4.0 dtype: float64
pandasのシリーズには他にも多くのメソッドが用意されています。第3章からはデータ参照やファイルの読み込みなど、より実用的な操作方法を詳しく解説します。