StatsFragments

Python, R, Rust, 統計, 機械学習とか

pandas 0.16.0/0.16.1 の主要な新機能

先日 5/11 に pandas 0.16.1 がリリースされた。前バージョンである 0.16.0 とあわせて、主要な変更点である以下3点の概要をまとめたい。各見出しの括弧内には対応したバージョンを記載した。

  • 簡単な列追加 / DataFrame.assign (0.16.0)
  • 文字列処理の強化 (0.16.0/0.16.1)
  • ランダムサンプリング DataFrame.sample (0.16.1)

変更点全体はリリースノートを参照。上記3点以外にも便利な変更はあるのだが、 Categorical や Frequency など 元機能の説明がないとわからない箇所なので別途、、。

簡単な列追加 / DataFrame.assign (0.16.0)

DataFrame への列追加をより簡潔に行うためのメソッドとして、DataFrame.assign が追加された。R {dplyr}mutate 関数に似た書式で複数列の追加ができる。

これまで列追加の際には カラムを指定して代入し、元データを破壊的に変更する必要があった。

import pandas as pd
df = pd.DataFrame({'A':[1, 2, 3], 'B':[4, 5, 6]})
df
#    A  B
# 0  1  4
# 1  2  5
# 2  3  6

# これまでの列追加
df['C'] = [7, 8, 9]
df
#    A  B  C
# 0  1  4  7
# 1  2  5  8
# 2  3  6  9

assign を使うと、作成したい列名をキーワード引数として以下のように書ける。また、戻り値は元データに列追加したコピーとなり、元データ自体は変更されない。

# D列, E列を追加
df.assign(D=[10, 11, 12], E=[13, 14, 15])
#    A  B  C   D   E
# 0  1  4  7  10  13
# 1  2  5  8  11  14
# 2  3  6  9  12  15

# 元データはそのまま
df
#    A  B  C
# 0  1  4  7
# 1  2  5  8
# 2  3  6  9

キーワード引数の値には通常の列作成時と同じ型が渡せる。各列の値を組み合わせた列 + ダミー列を同時に作りたければ、

df.assign(AB=df['A'] * df['B'], AC=df['A'] + df['C'], dummy=1)
#    A  B  C  AB  AC  dummy
# 0  1  4  7   4   8      1
# 1  2  5  8  10  10      1
# 2  3  6  9  18  12      1

キーワード引数として渡せない列名、例えば記号や日本語を含む列名は、一度 辞書にしてから渡せばよい。

keys = {'A*B': df['A'] * df['B'], 'A+B':df['A'] + df['B']}
df.assign(**keys)
#    A  B  A*B  A+B
# 0  1  4    4    5
# 1  2  5   10    7
# 2  3  6   18    9

補足: メソッド自体は 0.16.0 で追加され、0.16.1 でキーワード引数の処理順序がアルファベット順に固定された。

文字列処理の強化 (0.16.0/0.16.1)

pandas での文字列処理について過去に以下の記事を書いたことがあるのだが、当時はアクセサから利用できるメソッドが限定されていて、少し手間がかかるところがあった。

0.16.0, 0.16.1 で、.str アクセサに以下のメソッド群が追加された。それぞれ、 Python 標準の文字列メソッドと同一の処理を Series 内の値に対して適用するもの。

Methods
isalnum isalpha isdigit isspace islower
isupper istitle isnumeric isdecimal find
rfind ljust rjust zfill capitalize
swapcase normalize partition rpartition index
rindex translate

補足 str.normalizeユニコード正規化 ( unicodedata.normalize ) を値に対して適用するもの。

補足 .str アクセサから利用可能なメソッド全体はこちら

df = pd.DataFrame({'A': ['xxx', '3', 'yyy'], 'B': [1, 2, 3]})
df
#      A  B
# 0  xxx  1
# 1    3  2
# 2  yyy  3

# 先頭を大文字に
df['A'].str.capitalize()
# 0    Xxx
# 1      3
# 2    Yyy
# Name: A, dtype: object

# 文字列が数値がどうかを調べる
df['A'].str.isdigit()
# 0    False
# 1     True
# 2    False
# Name: A, dtype: bool

# 5桁分を 0 パディング
df['A'].str.zfill(5)
# 0    00xxx
# 1    00003
# 2    00yyy
# Name: A, dtype: object

やりたいことに応じて適当に組み合わせると、かなり柔軟な処理がかける。例えば 数値の文字列のみを 0 パディングしたければ、

df.loc[df['A'].str.isdigit(), 'A'] = df['A'].str.zfill(5)
df
#        A  B
# 0    xxx  1
# 1  00003  2
# 2    yyy  3

補足 DataFrame.loc の意味はこちらの記事参照。

また、0.16.1 から .str アクセサを Index からも呼び出せるようになった。これまでは 列名/行名に対する文字列処理は .map を使って関数適用する必要があったが、.str を使えば以下のように書ける。

df
#        A  B
# 0    xxx  1
# 1  00003  2
# 2    yyy  3

df.columns.str.lower()
# Index([u'a', u'b'], dtype='object')

# 列名を小文字に変更
df.columns = df.columns.str.lower()
df
#        a  b
# 0    xxx  1
# 1  00003  2
# 2    yyy  3

str.split のように複数の値を返しうる処理については、expand オプションを利用して 返り値の型を制御できる。互換性維持のため、既定値はメソッドにより異なる。APIドキュメントを参照。

  • expand=False: 返り値の次元を増やさない = 返り値は Series もしくは Index
  • expand=True: 返り値の次元を増やす = 返り値は DataFrame もしくは MultiIndex

補足 これまで同様の制御をおこなっていた return_type オプションは deprecate されており、将来のバージョンで削除される。

s = pd.Series(['a,b', 'a,c', 'b,c'])

# 返り値は Series
s.str.split(',')
# 0    [a, b]
# 1    [a, c]
# 2    [b, c]
# dtype: object

# 返り値は DataFrame
s.str.split(',', expand=True)
#    0  1
# 0  a  b
# 1  a  c
# 2  b  c

idx = pd.Index(['a,b', 'a,c', 'b,c'])

# 返り値は 1 レベルの Index
idx.str.split(',')
# Index([[u'a', u'b'], [u'a', u'c'], [u'b', u'c']], dtype='object')

# 返り値は 2 レベルの MultiIndex
idx.str.split(',', expand=True)
# MultiIndex(levels=[[u'a', u'b'], [u'b', u'c']],
#            labels=[[0, 0, 1], [0, 1, 1]])

ランダムサンプリング DataFrame.sample (0.16.1)

Series, DataFrame, Panel から適当なデータをサンプリングするためのメソッドとして、.sample が追加された。

ここでは DataFrame.sample を例としてその処理を記載する。まず以下のようなデータを用意した。

df = pd.DataFrame({'A': [1, 2 ,3, 4, 5], 'B': ['a', 'b', 'c', 'd', 'e']})
df
#    A  B
# 0  1  a
# 1  2  b
# 2  3  c
# 3  4  d
# 4  5  e

DataFrame.sample は 既定では 1 行をランダムにサンプリングする。

  • n: サンプルサイズを指定する。既定は 1。
  • axis: サンプリングするレコードの方向を指定する。0 (既定) で行のサンプリング、1 で列のサンプリング。
# 1 行の抽出
df.sample()
#    A  B
# 3  4  d

# 3 行の抽出
df.sample(3)
#    A  B
# 0  1  a
# 4  5  e
# 2  3  c

# 1 列の抽出
df.sample(axis=1)
#    A
# 0  1
# 1  2
# 2  3
# 3  4
# 4  5

また、サンプルサイズでなく抽出比を指定する場合は frac を指定する。

df.sample(frac=0.4)
#    A  B
# 3  4  d
# 1  2  b

最後に、replace オプションを利用して 抽出方法を変更することができる。

  • replace=False (既定): 非復元抽出。サンプリングされた各要素の重複を許さない。
  • replace=True: 復元抽出。各要素の重複を許す。
# 復元抽出。2行目、4行目が重複
df.sample(n=4, replace=True)
#    A  B
# 1  2  b
# 2  3  c
# 0  1  a
# 2  3  c

# 非復元抽出では 元のデータ数以上のサンプルサイズは取れない
df.sample(n=6)
# ValueError: Cannot take a larger sample than population when 'replace=False'

df.sample(n=6, replace=True)
#    A  B
# 1  2  b
# 4  5  e
# 0  1  a
# 1  2  b
# 2  3  c
# 4  5  e

補足 これまでは index の要素をサンプリングして選択する必要があった。以下の記事参照。

まとめ

0.16.0 / 0.16.1 の変更点のうち、以下 3 点の概要をまとめた。

  • 簡単な列追加 / DataFrame.assign (0.16.0)
  • 文字列処理の強化 (0.16.0/0.16.1)
  • ランダムサンプリング DataFrame.sample (0.16.1)

他にも、バグ修正 / 挙動・仕様の統一など 様々な改善が入っているので使ってみてください。

Pythonによるデータ分析入門 ―NumPy、pandasを使ったデータ処理

Pythonによるデータ分析入門 ―NumPy、pandasを使ったデータ処理