Python pandas データのイテレーションと関数適用、pipe
pandas
ではデータを 列 や 表形式のデータ構造として扱うが、これらのデータから順番に値を取得 (イテレーション) して何か操作をしたい / また 何らかの関数を適用したい、ということがよくある。このエントリでは以下の 3 つについて整理したい。
- イテレーション
- 関数適用
pipe
(0.16.2 で追加)
それぞれ、Series
、DataFrame
、GroupBy
(DataFrame.groupby
したデータ) で可能な操作が異なるため、順に記載する。
まずは必要なパッケージを import
する。
import numpy as np import pandas as pd
イテレーション
Series
Series
は以下 2つのイテレーション用メソッドを持つ。各メソッドの挙動は以下のようになる。
図で表すとこんな感じ。矢印が処理の方向、枠内が 1 処理単位。
s = pd.Series([1, 2, 3], index=['a', 'b', 'c']) for v in s: print(v) # 1 # 2 # 3 for i, v in s.iteritems(): print(i) print(v) print('') # a # 1 # # b # 2 # # c # 3
DataFrame
DataFrame
は以下 3つのイテレーション用メソッドを持つ。同様に挙動を示す。
__iter__
:DataFrame
の列名 (columns
) のみをイテレーションiteritems
:DataFrame
の列名と 列の値 (Series
) からなるtuple
をイテレーションiterrows
:DataFrame
の行名と 行の値 (Series
) からなるtuple
をイテレーション
df = pd.DataFrame({'A': [1, 2, 3], 'B': [4, 5, 6]}, index=['a', 'b', 'c']) df # A B # a 1 4 # b 2 5 # c 3 6 for col in df: print(col) # A # B for key, column in df.iteritems(): print(key) print(column) print('') # A # a 1 # b 2 # c 3 # Name: A, dtype: int64 # # B # a 4 # b 5 # c 6 # Name: B, dtype: int64 for key, row in df.iterrows(): print(key) print(row) print('') # a # A 1 # B 4 # Name: a, dtype: int64 # # b # A 2 # B 5 # Name: b, dtype: int64 # # c # A 3 # B 6 # Name: c, dtype: int64
GroupBy
__iter__
:GroupBy
のグループ名と グループ (DataFrame
もしくはSeries
) からなるtuple
をイテレーション
df = pd.DataFrame({'group': ['g1', 'g2', 'g1', 'g2'], 'A': [1, 2, 3, 4], 'B': [5, 6, 7, 8]}, columns=['group', 'A', 'B']) df # group A B # 0 g1 1 5 # 1 g2 2 6 # 2 g1 3 7 # 3 g2 4 8 grouped = df.groupby('group') for name, group in grouped: print(name) print(group) print('') # g1 # group A B # 0 g1 1 5 # 2 g1 3 7 # # g2 # group A B # 1 g2 2 6 # 3 g2 4 8
関数適用
Series
Series
の各値に対して 関数を適用する方法は以下の 2 つ。挙動はほぼ一緒だが、関数適用する場合は apply
を使ったほうが意図が明確だと思う
Series.apply
:Series
の各値に対して関数を適用。Series.map
:Series
の各値を、引数を用いてマッピングする。引数として、dict
やSeries
も取れる。
s = pd.Series([1, 2, 3], index=['a', 'b', 'c']) s.apply(lambda x: x * 2) # a 2 # b 4 # c 6 # dtype: int64 # apply の引数には、Series の値そのものが渡っている s.apply(type) # a <type 'numpy.int64'> # b <type 'numpy.int64'> # c <type 'numpy.int64'> # dtype: object # 関数が複数の返り値を返す場合、結果は tuple の Series になる s.apply(lambda x: (x, x * 2)) # a (1, 2) # b (2, 4) # c (3, 6) # dtype: object # 結果を DataFrame にしたい場合は、返り値を Series として返す s.apply(lambda x: pd.Series([x, x * 2], index=['col1', 'col2'])) # col1 col2 # a 1 2 # b 2 4 # c 3 6 # map の挙動は apply とほぼ同じ (map では結果を DataFrame にすることはできない) s.map(lambda x: x * 2) # a 2 # b 4 # c 6 # dtype: int64 s.map(type) # a <type 'numpy.int64'> # b <type 'numpy.int64'> # c <type 'numpy.int64'> # dtype: object # map は 関数以外に、 mapping 用の dict や Series を引数として取れる s.map(pd.Series(['x', 'y', 'z'], index=[1, 2, 3])) # a x # b y # c z # dtype: object
DataFrame
DataFrame
に対して 関数を適用する方法は以下の 2 つ。
DataFrame.apply
:DataFrame
の各列もしくは各行に対して関数を適用。行 / 列の指定はaxis
キーワードで行う。DataFrame.applymap
:DataFrame
の各値に対して関数を適用。
df = pd.DataFrame({'A': [1, 2, 3], 'B': [4, 5, 6]}, index=['a', 'b', 'c']) df # A B # a 1 4 # b 2 5 # c 3 6 # 各列に対して関数適用 df.apply(lambda x: np.sum(x)) A 6 B 15 dtype: int64 # 各行に対して関数適用 df.apply(lambda x: np.sum(x), axis=1) a 5 b 7 c 9 dtype: int64 # 各値に対して関数適用 df.applymap(lambda x: x * 2) # A B # a 2 8 # b 4 10 # c 6 12 # apply で適用される関数には、各列もしくは各行が Series として渡される df.apply(type) # A <class 'pandas.core.series.Series'> # B <class 'pandas.core.series.Series'> # dtype: object # applymap で適用される関数には、値そのものが引数として渡される df.applymap(type) # A B # a <type 'numpy.int64'> <type 'numpy.int64'> # b <type 'numpy.int64'> <type 'numpy.int64'> # c <type 'numpy.int64'> <type 'numpy.int64'>
GroupBy
GroupBy
については、GroupBy.apply
で各グループに関数を適用できる。
df = pd.DataFrame({'group': ['g1', 'g2', 'g1', 'g2'], 'A': [1, 2, 3, 4], 'B': [5, 6, 7, 8]}, columns=['group', 'A', 'B']) df # group A B # 0 g1 1 5 # 1 g2 2 6 # 2 g1 3 7 # 3 g2 4 8 grouped = df.groupby('group') grouped.apply(np.mean) # A B # group # g1 2 6 # g2 3 7
補足 処理最適化のため、対象となるグループの数 == 関数適用の実行回数とはならないので注意。関数中で破壊的な処理を行うと意図しない結果になりうる。
# 適用される関数 def f(x): print('called') return x # グループ数は 2 grouped.ngroups # 2 # f の実行は 3 回 grouped.apply(f) # called # called # called
pipe
先日 リリースされた v0.16.2 にて pipe
メソッドが追加された。これは R の {magrittr}
というパッケージからインスパイアされたもので、データへの連続した操作を メソッドチェイン (複数のメソッドの連続した呼び出し) で記述することを可能にする。
Series.pipe
、DataFrame.pipe
それぞれ、x.pipe(f, *args, **kwargs)
は f(x, *args, **kwargs)
と同じ。つまり、データ全体に対する関数適用になる。
補足 GroupBy.pipe
は v0.16.2 時点では存在しない。
# 渡される型は呼び出し元のインスタンス s.pipe(type) # pandas.core.series.Series df.pipe(type) # pandas.core.frame.DataFrame np.random.seed(1) df = pd.DataFrame(np.random.randn(10, 10)) # DataFrame を引数として heatmap を描く関数を定義 def heatmap(df): import matplotlib.pyplot as plt fig, ax = plt.subplots() return ax.pcolor(df.values, cmap='Greens') # heatmap(df) と同じ。 df.pipe(heatmap)
まとめ
イテレーション、関数適用、pipe
について整理した。特に関数適用は データの前処理時に頻出するため、パターンを覚えておくと便利。
Pythonによるデータ分析入門 ―NumPy、pandasを使ったデータ処理
- 作者: Wes McKinney,小林儀匡,鈴木宏尚,瀬戸山雅人,滝口開資,野上大介
- 出版社/メーカー: オライリージャパン
- 発売日: 2013/12/26
- メディア: 大型本
- この商品を含むブログ (9件) を見る