Pandasを使ったデータ操作の基本

データ分析の会社に転職してから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は多少遅いけど可読性・書きやすさがあるので、どちらを使うかは好み

cocodrips.hateblo.jp

行列から必要な行番号を指定してを取り出す

行を取り出すには、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

新たな列を追加する

詳しく知りたい方はこちらが参考になります。

sinhrks.hatenablog.com

sinhrks.hatenablog.com

固有値を追加する

ただ単に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が入る場合は行に対して操作を行うときなので、 自分のコードにあったら、他の書き方がないか考えてみよう。