データ分析の会社に転職してから3ヶ月。
最初の1ヶ月はPandasの扱いに本当に困ったので、
昔メモしてたことを簡単にブログに記録しておく(o ・ω・)ノ
【追記】2017/07/31 0:36 データが一部間違ってたので修正しました
Pandasとは
行列データを扱いやすくしたり、集計を行うライブラリ。
例えばデータがcsvファイル担っていた場合、pandas.read('hoge.csv')
とするだけで、
扱いやすい(DataFrame型という)行列データとして扱えるようになる。
簡易的な可視化機能もついており、Pythonでデータの分析をする際に、最初に使うことになることが多いライブラリである。
とても便利なのだが、操作にかなり癖があるため、慣れるまでにかなり操作で戸惑うことが多い。
pandasでよく使う型
Pandasで扱う代表的な型が以下の3つである。 最初はどの型で何ができるのかわからなくなるので、タブで以下のリファレンス見ながら操作していくのがいい。
型 | 説明 | リファレンス |
---|---|---|
DataFrame | 行列型 | pandas.DataFrame — pandas 0.20.2 documentation |
Series | DataFrameの中の1列 | pandas.Series — pandas 0.20.2 documentation |
GroupBy | DataFrameやSeriesをグルーピングしたもの | API Reference#groupby — pandas 0.20.2 documentation |
テストデータについて
今回はタイタニック号乗船客の生存者を予測するデータから一部抜粋した以下を使う。
Survived,Pclass,Sex,Age,Fare,Embarked 1,1,male,80.0,30.0,S 1,2,female,4.0,39.0,S 0,2,female,24.0,13.0,S 0,2,male,37.0,26.0,S 0,3,female,11.0,31.275,S 1,3,female,13.0,7.2292,C 0,3,male,22.0,7.25,S
これをsample.csvとする。
ちなみに各列の意味は、以下である。
- Survived 生存者かどうか
- Pclass 部屋のグレード
- Sex 性別
- Age 年齢
- Fare 乗船代金
- Embarked どの港から乗ったか
余談
こちらは機械学習のコンテストで知られるKaggleでも最も有名なチュートリアル用の例題なので、
もしデータの操作や解析に興味がある人はぜひ問題に挑戦してみて欲しい。
データはこちらからDLできる。
Pandasでのデータ操作入門
pandasのload
pandasは慣習的にこのようにimportする。
import pandas as pd
データ(csv)のロード
まずはじめにcsvファイルのロードの仕方。
pd.read_csvでDataFrame型に変換してくれる。
titanic_df = pd.read_csv("sample.csv")
titanic_df
Survived | Pclass | Sex | Age | Fare | Embarked | |
---|---|---|---|---|---|---|
0 | 1 | 1 | male | 80.0 | 30.0000 | S |
1 | 1 | 2 | female | 4.0 | 39.0000 | S |
2 | 0 | 2 | female | 24.0 | 13.0000 | S |
3 | 0 | 2 | male | 37.0 | 26.0000 | S |
4 | 0 | 3 | female | 11.0 | 31.2750 | S |
5 | 1 | 3 | female | 13.0 | 7.2292 | C |
6 | 0 | 3 | male | 22.0 | 7.2500 | S |
pandas.DataFrame — pandas 0.20.3 documentation
データのサイズ
titanic_df.shape
(7, 6)
DataFrame.shapeでその行列のサイズ(行数, 列数)がわかる。
行数を知りたいシチュエーションは多いので、そういうときは以下のようにするのが一般的。
titanic_df.shape[0]
7
データのカラム
データのカラムは DataFrame.columns
で見ることが出来る。
titanic_df.columns
Index(['Survived', 'Pclass', 'Sex', 'Age', 'Fare', 'Embarked'], dtype='object')
行列から必要な列(カラム)を取り出す
# 1列を取り出す: [] でキーを指定 Series型が返ってくる titanic_df['Age']
0 80.0
1 4.0
2 24.0
3 37.0
4 11.0
5 13.0
6 22.0
Name: Age, dtype: float64
# 2列以上を取り出す: []にキーの配列を指定 DataFrame型で帰ってくる titanic_df[['Age', 'Sex']]
Age | Sex | |
---|---|---|
0 | 80.0 | male |
1 | 4.0 | female |
2 | 24.0 | female |
3 | 37.0 | male |
4 | 11.0 | female |
5 | 13.0 | female |
6 | 22.0 | male |
こんな風にして必要なキーだけを宣言し、絞り込むような事が多い。
valiables = ['Survived', 'Pclass', 'Sex', 'Age'] titanic_df = titanic_df[valiables] titanic_df
Survived | Pclass | Sex | Age | |
---|---|---|---|---|
0 | 1 | 1 | male | 80.0 |
1 | 1 | 2 | female | 4.0 |
2 | 0 | 2 | female | 24.0 |
3 | 0 | 2 | male | 37.0 |
4 | 0 | 3 | female | 11.0 |
5 | 1 | 3 | female | 13.0 |
6 | 0 | 3 | male | 22.0 |
条件にマッチするデータを取り出す
1. DataFrame.queryで取り出す
ここでは DataFrame.queryによるデータの取り出し方を紹介する。
Pandasでは様々な方法で条件に合ったデータを取り出せるのだが、
.queryによる取り出しが読んだときに一番「この条件で取り出している」というのがわかりやすいと感じるので、
まずはこの方法を覚えるのが良いかと思う。
# 1つの条件にマッチするデータを取り出す titanic_df.query('Age > 20')
Survived | Pclass | Sex | Age | |
---|---|---|---|---|
0 | 1 | 1 | male | 80.0 |
2 | 0 | 2 | female | 24.0 |
3 | 0 | 2 | male | 37.0 |
6 | 0 | 3 | male | 22.0 |
# 2つ以上の条件にマッチするデータを取り出す titanic_df.query('(Age > 20) & (Sex == "female")')
Survived | Pclass | Sex | Age | |
---|---|---|---|---|
2 | 0 | 2 | female | 24.0 |
True/FalseのSeries型を指定し、Trueの行だけを取り出す
次に各行に対してTrue/FalseがアサインされたSeries型(列)を指定することで、Trueの行だけを取り出す書き方。
Series型に対して条件を並べると、以下のようなTrue/FalseのSeries型が返ってくる。 (numpyの配列もこういった処理に対して同じような挙動をする)
titanic_df['Age'] > 20
0 True
1 False
2 True
3 True
4 False
5 False
6 True
Name: Age, dtype: bool
このTrue/FalseのSeriesを更にDataFrameに指定することで、Trueの行だけを取り出すことができる
titanic_df[titanic_df['Age'] > 20]
Survived | Pclass | Sex | Age | |
---|---|---|---|---|
0 | 1 | 1 | male | 80.0 |
2 | 0 | 2 | female | 24.0 |
3 | 0 | 2 | male | 37.0 |
6 | 0 | 3 | male | 22.0 |
複数条件を並べる場合は()で条件同士はくくってあげる必要がある。
titanic_df[(titanic_df['Age'] > 20) & (titanic_df['Sex'] == 'female')]
Survived | Pclass | Sex | Age | IsChild | |
---|---|---|---|---|---|
2 | 0 | 2 | female | 24.0 | 0 |
Queryと、この配列に条件に入れる2つの方法でデータを取り出すことが多い。
追記(2017/12/14)
queryは多少遅いけど可読性・書きやすさがあるので、どちらを使うかは好み
行列から必要な行番号を指定してを取り出す
行を取り出すには、DataFrame.loc
という関数を使っていく。
locでは、loadした時にdfの一番左に自動的に割り当てられる「index」を指定して行を取り出す。
DataFrame.loc[start:end]としたときに、startとendをどちらも含んだ状態で取り出すので注意。
titanic_df.loc[0:2]
Survived | Pclass | Sex | Age | |
---|---|---|---|---|
0 | 1 | 1 | male | 80.0 |
1 | 1 | 2 | female | 4.0 |
2 | 0 | 2 | female | 24.0 |
他にもilocやixといった行を取り出せる関数があるが、基本はlocを使う場合が多いのでまずはコレだけ覚えられればよいかと。
グループ分けと集計
groupby関数を用いると、指定された列を値毎にグルーピングしてくれる。 帰ってきたgroupbyオブジェクトで集計関数を呼び出すと、 グループごとの平均や最大値、中央値などを調べることができる。
詳しくはこちら: API Reference — pandas 0.20.3 documentation #groupby
# Survive列が0か1かで生存したかどうかを示している。 # 生存した人たちと、していない人たちで各値の平均をとってみる。 titanic_df.groupby(['Survived']).mean()
Pclass | Age | |
---|---|---|
Survived | ||
0 | 2.5 | 23.500000 |
1 | 2.0 | 32.333333 |
※集計関数は、集計出来る列だけを集計してくれる。(今回は文字列のSex/Embarkedは集計されていない)
reset_index()を呼び出すことでこの行列自体を更に操作していきやすくなる。
titanic_df.groupby(['Survived']).mean().reset_index()
Survived | Pclass | Age | |
---|---|---|---|
0 | 0 | 2.5 | 23.500000 |
1 | 1 | 2.0 | 32.333333 |
新たな列を追加する
詳しく知りたい方はこちらが参考になります。
固有値を追加する
ただ単に1をもつ「one」という列追加する方法。自分はassignを使うことが多い。
titanic_df.assign(
One = 1
)
Survived | Pclass | Sex | Age | One | |
---|---|---|---|---|---|
0 | 1 | 1 | male | 80.0 | 1 |
1 | 1 | 2 | female | 4.0 | 1 |
2 | 0 | 2 | female | 24.0 | 1 |
3 | 0 | 2 | male | 37.0 | 1 |
4 | 0 | 3 | female | 11.0 | 1 |
5 | 1 | 3 | female | 13.0 | 1 |
6 | 0 | 3 | male | 22.0 | 1 |
他の列を加工して新たな列を作る
Ageを加工して、20歳以下かどうかを示す列「is_child」を作る。
titanic_df.assign( IsChild = titanic_df['Age'] < 20 )
Survived | Pclass | Sex | Age | IsChild | |
---|---|---|---|---|---|
0 | 1 | 1 | male | 80.0 | False |
1 | 1 | 2 | female | 4.0 | True |
2 | 0 | 2 | female | 24.0 | False |
3 | 0 | 2 | male | 37.0 | False |
4 | 0 | 3 | female | 11.0 | True |
5 | 1 | 3 | female | 13.0 | True |
6 | 0 | 3 | male | 22.0 | False |
True / False だと扱いづらいことも多いので、True=1, False=0 にして入れる場合は以下のようにする
titanic_df.assign( IsChild = (titanic_df['Age'] < 20).astype(int) )
Survived | Pclass | Sex | Age | IsChild | |
---|---|---|---|---|---|
0 | 1 | 1 | male | 80.0 | 0 |
1 | 1 | 2 | female | 4.0 | 1 |
2 | 0 | 2 | female | 24.0 | 0 |
3 | 0 | 2 | male | 37.0 | 0 |
4 | 0 | 3 | female | 11.0 | 1 |
5 | 1 | 3 | female | 13.0 | 1 |
6 | 0 | 3 | male | 22.0 | 0 |
書き換えてしまって良いのであればこういった書き方もできる。
ただし、この書き方はSettingWithCopyWarning
が出る。
titanic_df['IsChild'] = (titanic_df['Age'] < 20).astype(int) titanic_df
Survived | Pclass | Sex | Age | IsChild | |
---|---|---|---|---|---|
0 | 1 | 1 | male | 80.0 | 0 |
1 | 1 | 2 | female | 4.0 | 1 |
2 | 0 | 2 | female | 24.0 | 0 |
3 | 0 | 2 | male | 37.0 | 0 |
4 | 0 | 3 | female | 11.0 | 1 |
5 | 1 | 3 | female | 13.0 | 1 |
6 | 0 | 3 | male | 22.0 | 0 |
他の複数列を加工して新たな列を作る
Pclass の値とSurvivedの値を足した列をつくってみる (この場合はそんな列に意味はないが・・・)
titanic_df.apply(lambda x: x['Pclass'] + x['Survived'], axis=1)
0 2
1 3
2 2
3 2
4 3
5 4
6 3
dtype: int64
これで対応するSeries型がつくれたので、Assignするだけ
titanic_df.assign( X=titanic_df.apply(lambda x: x['Pclass'] + x['Survived'], axis=1) )
Survived | Pclass | Sex | Age | IsChild | X | |
---|---|---|---|---|---|---|
0 | 1 | 1 | male | 80.0 | 0 | 2 |
1 | 1 | 2 | female | 4.0 | 1 | 3 |
2 | 0 | 2 | female | 24.0 | 0 | 2 |
3 | 0 | 2 | male | 37.0 | 0 | 2 |
4 | 0 | 3 | female | 11.0 | 1 | 3 |
5 | 1 | 3 | female | 13.0 | 1 | 4 |
6 | 0 | 3 | male | 22.0 | 0 | 3 |
追記
こっちのほうが列×列で早い。(書き換えたかったらtitanic_df.loc[:, 'X']
に代入すればいい)
↑のやつだと行ごとに処理を行うのでめっちゃ時間かかる。
titanic_df.assign( X=titanic_df['Pclass'] + titanic_df['Survived'] )
Survived | Pclass | Sex | Age | IsChild | X | |
---|---|---|---|---|---|---|
0 | 1 | 1 | male | 80.0 | 0 | 2 |
1 | 1 | 2 | female | 4.0 | 1 | 3 |
2 | 0 | 2 | female | 24.0 | 0 | 2 |
3 | 0 | 2 | male | 37.0 | 0 | 2 |
4 | 0 | 3 | female | 11.0 | 1 | 3 |
5 | 1 | 3 | female | 13.0 | 1 | 4 |
6 | 0 | 3 | male | 22.0 | 0 | 3 |
条件にあったセルだけを書き換える
例えばIsChildの1のところを1ではなく5にしてみる。
この書き方は SettingWithCopyWarning
が出ることがあるのだけど良い書き方がわからないので、
知ってる方がいたら教えて欲しい。 (値自体はちゃんと変わる。)
titanic_df.loc[titanic_df['IsChild'] == 1, ['IsChild']] = 5 titanic_df
Survived | Pclass | Sex | Age | IsChild | |
---|---|---|---|---|---|
0 | 1 | 1 | male | 80.0 | 0 |
1 | 1 | 2 | female | 4.0 | 5 |
2 | 0 | 2 | female | 24.0 | 0 |
3 | 0 | 2 | male | 37.0 | 0 |
4 | 0 | 3 | female | 11.0 | 5 |
5 | 1 | 3 | female | 13.0 | 5 |
6 | 0 | 3 | male | 22.0 | 0 |
setやリストに存在する値のデータだけを取り出す
PClassが1か3の場合の行を取り出したいとする。
そんなときはDataFrame.isin
を使う。
target_set = set([1, 3]) condition = titanic_df['Pclass'].isin(target_set) titanic_df[condition]
Survived | Pclass | Sex | Age | IsChild | |
---|---|---|---|---|---|
0 | 1 | 1 | male | 80.0 | 0 |
4 | 0 | 3 | female | 11.0 | 5 |
5 | 1 | 3 | female | 13.0 | 5 |
6 | 0 | 3 | male | 22.0 | 0 |
さいごに
とりあえずいつも使っている操作をばーっと書いてみた。
Pandasは同じことをするのにいろんな書き方がある(´・ω・`)
今回も他にも書ける書き方があるにもかかわらず、使わないほうがいいと自分が思っているのはあえて紹介しなかった。 自分の書いてる書き方でも、あまりよろしくない書き方とかも有るかもしれない。
触っていくうちに皆自分のベストプラクティス的なのを見つけられるといいのかな。
おまけ
Pandasは書き方を一歩間違えるとめちゃくちゃ計算時間が遅くなったりもする。
行列計算は 行に対して何かをするのでなく、列に対して加工を行おうとする意識が大事。
引数に axis=1
が入る場合は行に対して操作を行うときなので、
自分のコードにあったら、他の書き方がないか考えてみよう。