概要
N番煎じですが、R の dplyr でやっていたことを Python の Pandas で実現する方法をまとめました。
私が苦戦したものに絞って、試行錯誤の末に得た自分なりの解法を記載しています。
より良い方法をご存知でしたら教えてください!
dplyr と Pandas の対比まとめ
dplyr, tidyr, ... | Pandas (おすすめ) | Pandas (イマイチ) | 備考 |
---|---|---|---|
%>% |
. or pipe
|
||
select |
.loc[] |
filter or drop
|
|
filter |
[] |
query |
|
mutate |
assign |
||
group_by |
groupby |
||
summarise |
assign + agg
|
||
*_join |
merge |
まだ調査中です | |
gather |
stack |
melt |
|
spread |
unstack |
pivot |
Pandas と dplyr のより網羅的な対比は、既に記事が出ています。
私が参考にしたものをこの記事の末尾で紹介していますので、詳しくはそちらをご覧ください 。
前置き|テーブルのキーの扱い方の違い
Pandas を使い始める前に理解すべきこととして、dplyr と Pandas ではテーブルのキーの扱い方に大きな違いがあります。この違いを認識していなかった私はかなり苦戦しました……
以下の2点を理解して (覚悟して?) おくと、学習コストが多少減るのではないでしょうか。
- Pandas ではテーブルのキーが Index という別オブジェクトで管理されていること
- Pandas ではキーや列名に階層構造を持たせられること
dplyr におけるキーの扱い
R のデータフレームにはテーブルのキーを表す方法として row names という仕組みが用意されています。しかし、dplyr (が利用している、データフレームを拡張したオブジェクトである tibble) では row names をサポートせず (参考:Hadley さんの Advanced R)、テーブルのキーも値と同様に列として扱います (Pandas のドキュメントの表現を借りるなら "SQL-style" で扱います)。そのため、普段 dplyr を使っている人はテーブルのキーを値と分けて管理するという感覚がないのではないかと思います。
以下の表は dplyr で iris データセットを集計した例です。キーを表す species も値と同様にひとつの列として扱われており、row names のように別枠で管理されてはいません。
species | sepal_length_min | sepal_length_max | sepal_width_min | sepal_width_max | petal_length_min | petal_length_max | petal_width_min | petal_width_max |
---|---|---|---|---|---|---|---|---|
setosa | 4.3 | 5.8 | 2.3 | 4.4 | 1 | 1.9 | 0.1 | 0.6 |
versicolor | 4.9 | 7 | 2 | 3.4 | 3 | 5.1 | 1 | 1.8 |
virginica | 4.9 | 7.9 | 2.2 | 3.8 | 4.5 | 6.9 | 1.4 | 2.5 |
Pandas におけるキーの扱い
一方 Pandas は真逆で、テーブルのキーを表す方法として Index という仕組みを用意しており、テーブルのキーと値を積極的に分けて管理します。そのため、Pandas のデータフレームに対する操作には以下の2種類があり、dplyr と比べるとかなり複雑です。
- dplyr のように列を指定するメソッド
- 例)
sort_values
(dplyr::arrange
に相当),melt
(tidyr::gather
に相当)
- 例)
- Index を指定するメソッド
- 例)
sort_index
(dplyr::arrange
に相当),stack
(tidyr::gather
に相当)
- 例)
以下の表は Pandas で iris データセットを集計した例です。species と書かれている列が Index で、値を表す他の列とは別に管理されています。また、列名が2段になっていますが、これは MultiIndex と呼ばれるもので、Pandas ではキーや列名に階層構造を持たせることができます。
sepal_length | sepal_width | petal_length | petal_width | |||||
---|---|---|---|---|---|---|---|---|
min | max | min | max | min | max | min | max | |
species | ||||||||
setosa | 4.3 | 5.8 | 2.3 | 4.4 | 1.0 | 1.9 | 0.1 | 0.6 |
versicolor | 4.9 | 7.0 | 2.0 | 3.4 | 3.0 | 5.1 | 1.0 | 1.8 |
virginica | 4.9 | 7.9 | 2.2 | 3.8 | 4.5 | 6.9 | 1.4 | 2.5 |
本題|dplyr のアレを Pandas でやる方法
サンプルデータの読み込み
Pandas の使い方を説明するためのサンプルデータとして、iris データセットを使います。
import numpy as np
import pandas as pd
from sklearn import datasets
iris = datasets.load_iris()
iris = \
pd.DataFrame(iris.data, columns = iris.feature_names). \
rename(columns = {
"sepal length (cm)": "sepal_length",
"sepal width (cm)": "sepal_width",
"petal length (cm)": "petal_length",
"petal width (cm)": "petal_width"
}). \
assign(species = iris.target). \
assign(species = lambda df: df.species.apply(lambda x: iris.target_names[x])). \
assign(id = np.arange(0, iris.target.size))
sepal_length | sepal_width | petal_length | petal_width | species | id | |
---|---|---|---|---|---|---|
0 | 5.1 | 3.5 | 1.4 | 0.2 | setosa | 0 |
1 | 4.9 | 3.0 | 1.4 | 0.2 | setosa | 1 |
2 | 4.7 | 3.2 | 1.3 | 0.2 | setosa | 2 |
3 | 4.6 | 3.1 | 1.5 | 0.2 | setosa | 3 |
4 | 5.0 | 3.6 | 1.4 | 0.2 | setosa | 4 |
パイプ magrittr::%>%
.
を使う方法
DataFrame オブジェクトのメソッドは基本的に DataFrame オブジェクトを返すので、ひたすら .
でメソッドチェーンをつなげていけば magrittr::%>%
のような書き方ができます。
# iris %>% head(10) %>% tail(5)
iris.head(10).tail(5)
pipe
を使う方法
DataFrame オブジェクトが持っていない関数を適用したい場合には、pipe
メソッドを使います。pipe
メソッドには DataFrame オブジェクトを受け取って DataFrame オブジェクトを返す関数を渡します。DataFrame オブジェクトが持っているメソッドでは不足することが多々あるので、必然的に pipe
メソッドを頻繁に使うことになります。この記事の中でも何度も使います。
iris.pipe(lambda df: df.iloc[5:10, :])
列の選択 dplyr::select
.loc[]
+ pipe
を使う方法
DataFrame オブジェクトには filter
や drop
(後で紹介します) というメソッドで列の選択を行うことができますが、これらのメソッドはできることに制約があり使える場面が限られています。いろいろ試した結果、結局 .loc[]
を使うのが一番だという結論に至りました。
基本構文は次の通りです。直接 iris.loc[:, ...]
とせずに pipe
メソッドを噛ませているところがポイントです。こうすることで、この後紹介する DataFrame オブジェクトのパラメータを参照した高度な列の絞り方を行う場合でもメソッドチェーンをつなぐことが可能になります。
# iris %>% select(sepal_length, sepal_width)
iris.pipe(lambda df: df.loc[:, '''ここに列の選択条件を書く'''])
[2018/10/10 追記]
.loc[]
に関数を指定できることを知りました。次のように書くことでpipe
メソッドを噛ませなくてもメソッドチェーンをつなぐことができます。iris.loc[:, lambda df: '''ここに列の選択条件を書く''']
特定のパターンを含む列だけを取り出したい場合は、DataFrame オブジェクトの列名を表す columns
パラメータ (Index オブジェクト) と、Index オブジェクトに対する文字列処理メソッド群へアクセスするための str
エイリアスを使います (参考:str
エイリアスでアクセスできる文字列処理メソッドの一覧)。
# iris %>% select(startswidth("sepal"))
iris.pipe(lambda df: df.loc[:, df.columns.str.startswith("sepal")])
# iris %>% select(endswith("length"))
iris.pipe(lambda df: df.loc[:, df.columns.str.endswith("length")])
# iris %>% select(contains("^.*_.*$"))
iris.pipe(lambda df: df.loc[:, df.columns.str.contains("^.*_.*$")])
[2018/10/10 追記]
pipe
メソッドを使わなくても、次のように書けます。iris.loc[:, lambda df: df.columns.str.startswith("sepal")]
特定のパターンを含まない列だけを取り出したい場合は、~
で反転させます (boolean 型の NumPy 配列をビット演算子 ~
で反転させて上手く行く根拠を見つけられていないので、ダメな場合があるかもしれません……numpy.invert
のドキュメント を読むと Examples の一番下に boolean 型は boolean 型として反転させる旨が記載されているので、おそらく numpy.__invert__
も同様の実装になっているのではないでしょうか)。
# iris %>% select(-startswidth("sepal"))
iris.pipe(lambda df: df.loc[:, ~df.columns.str.startswith("sepal")])
# iris %>% select(-endswith("length"))
iris.pipe(lambda df: df.loc[:, ~df.columns.str.endswith("length")])
# iris %>% select(-contains("^.*_.*$"))
iris.pipe(lambda df: df.loc[:, ~df.columns.str.contains("^.*_.*$")])
filter
や drop
を使う方法
列を選択には filter
メソッドを使うこともできます。選択する列を列挙できるなら、filter
メソッドを使うほうが .loc[]
+ pipe
よりも簡潔に表現できます。
# iris %>% select(sepal_length, sepal_width)
iris.filter(["sepal_length", "sepal_width"])
filter
メソッドは正規表現が使えるので、柔軟な列選択が可能です。
# iris %>% select(starts_with("sepal"))
iris.filter(regex = "^sepal")
特定の列を除きたい場合は、drop
メソッドを使うこともできます。除去する列を列挙できるなら、drop
メソッドを使うほうが .loc[]
+ pipe
よりも簡潔に表現できます。drop
メソッドは行と列の両方に対応したメソッドなので、列を除きたい場合には引数 column
に列名を列挙します。
# iris %>% select(-petal_length, -petal_width)
iris.drop(columns = ["petal_length", "petal_width"])
行の選択 dplyr::filter
[]
+ pipe
を使う方法
行を選択するには、[]
を使います。
# iris[iris$sepal_length > 5, ]
iris[iris.sepal_length > 5]
ただし、素直に []
を使うとメソッドチェーンをつなげなくなってしまう (条件部で自分自身を参照しているので、連続して行選択を行うには一度チェーンを切って代入しなければならない) ので、メソッドチェーンをつなげたい場合には pipe
メソッドを噛ませます。
# iris %>% filter(sepal_length > 5)
iris.pipe(lambda df: df[df.sepal_length > 5])
[2018/10/10 追記]
.loc[]
と同様に、[]
も 関数を指定することができます。iris[lambda df: df.sepal_length > 5]
query
メソッドを使う方法
[]
の他に query
メソッドを使う方法もありますが、イマイチ使い勝手が良くないです。
iris.query("sepal_length > 5")
行を絞る条件を文字列で渡す都合上、いろいろ制約があります。例えばメソッドを呼び出そうとすると、デフォルトの評価エンジン (numexpr
) ではエラーになります。評価エンジンに python
を指定すると動くようになりますが、そこまでするなら []
+ pipe
でいいかなという感じです。
# Error
iris.query("sepal_length.notnull()")
# OK
iris.query("sepal_length.notnull()", engine = "python")
新しい列の追加 dplyr::mutate
assign
を使う方法
新しい列を追加するには、assign
メソッドを使います。assign
メソッドには DataFrame オブジェクトを受け取り Series オブジェクトを返す関数を渡します。
# iris %>% mutate(sepal_area = sepal_length * sepal_width)
iris.assign(sepal_area = lambda df: df.sepal_length * df.sepal_width)
sepal_length | sepal_width | petal_length | petal_width | species | id | sepal_area | |
---|---|---|---|---|---|---|---|
0 | 5.1 | 3.5 | 1.4 | 0.2 | setosa | 0 | 17.85 |
1 | 4.9 | 3.0 | 1.4 | 0.2 | setosa | 1 | 14.70 |
2 | 4.7 | 3.2 | 1.3 | 0.2 | setosa | 2 | 15.04 |
3 | 4.6 | 3.1 | 1.5 | 0.2 | setosa | 3 | 14.26 |
4 | 5.0 | 3.6 | 1.4 | 0.2 | setosa | 4 | 18.00 |
assign
で特定の値を書き換える方法
ある条件を満たす行の値を書き換えるには、mask
メソッドを使います。mask
メソッドは条件を満たす場合には指定した値を、満たさない場合には元の値を返します。
# iris %>% mutate(sepal_length = if_else(sepal_length < 5, NA_real_, sepal_length))
iris.assign(sepal_length = lambda df: df.sepal_length.mask(df.sepal_length < 5, np.nan))
sepal_length | sepal_width | petal_length | petal_width | species | id | |
---|---|---|---|---|---|---|
0 | 5.1 | 3.5 | 1.4 | 0.2 | setosa | 0 |
1 | NaN | 3.0 | 1.4 | 0.2 | setosa | 1 |
2 | NaN | 3.2 | 1.3 | 0.2 | setosa | 2 |
3 | NaN | 3.1 | 1.5 | 0.2 | setosa | 3 |
4 | 5.0 | 3.6 | 1.4 | 0.2 | setosa | 4 |
同様に、ある条件を満たさない行の値を書き換えるには、where
メソッドを使います。where
メソッドは条件を満たす場合には元の値を、満たさない場合には指定した値を返します。
# iris %>% mutate(sepal_length = if_else(sepal_length < 5, sepal_length, NA_real_))
iris.assign(sepal_length = lambda df: df.sepal_length.where(df.sepal_length < 5, np.nan))
sepal_length | sepal_width | petal_length | petal_width | species | id | |
---|---|---|---|---|---|---|
0 | NaN | 3.5 | 1.4 | 0.2 | setosa | 0 |
1 | 4.9 | 3.0 | 1.4 | 0.2 | setosa | 1 |
2 | 4.7 | 3.2 | 1.3 | 0.2 | setosa | 2 |
3 | 4.6 | 3.1 | 1.5 | 0.2 | setosa | 3 |
4 | NaN | 3.6 | 1.4 | 0.2 | setosa | 4 |
assign
で既存の列の値に応じて新しい列の値を変える方法
既存の列の値に応じて新しい列の値を変えるには、デフォルト値を代入した新しい列をあらかじめ作っておき、その列に対して mask
メソッドや where
メソッドを使って既存の列の値に応じて作った列の値を書き換えます。dplyr::if_else
(base::ifelse
でもいいですが) に相当するメソッドがあればより簡潔に書けそうですが、私が探した限りでは見つけられませんでした。
# iris %>% mutate(islong = if_else(sepal_length >= 5 & petal_length >= 1.3, "long", "short"))
iris. \
assign(islong = "short"). \
assign(islong = lambda df: df.islong.mask((df.sepal_length >= 5) & (df.petal_length >= 1.3), "long"))
sepal_length | sepal_width | petal_length | petal_width | species | id | islong | |
---|---|---|---|---|---|---|---|
0 | 5.1 | 3.5 | 1.4 | 0.2 | setosa | 0 | long |
1 | 4.9 | 3.0 | 1.4 | 0.2 | setosa | 1 | short |
2 | 4.7 | 3.2 | 1.3 | 0.2 | setosa | 2 | short |
3 | 4.6 | 3.1 | 1.5 | 0.2 | setosa | 3 | short |
4 | 5.0 | 3.6 | 1.4 | 0.2 | setosa | 4 | long |
グループ化 dplyr::group_by
と集約 dplyr::summarise
dplyr::summarise
に該当するメソッドはありません。一応 agg
がありますが、動作としては dplyr::summarise_all
や dplyr::summarise_at
に近いです。
agg
で dplyr::summarise_all
相当の操作をやる方法
agg
の引数に集約関数のリストを指定すると、DataFrame オブジェクトの全列に対して指定した全関数を適用した集計結果が得られます (dplyr::summarise_all
相当)。
# iris %>% group_by(species) %>% summarise_all(funs(min, max))
iris. \
groupby("species"). \
agg([min, max])
sepal_length | sepal_width | petal_length | petal_width | id | ||||||
---|---|---|---|---|---|---|---|---|---|---|
min | max | min | max | min | max | min | max | min | max | |
species | ||||||||||
setosa | 4.3 | 5.8 | 2.3 | 4.4 | 1.0 | 1.9 | 0.1 | 0.6 | 0 | 49 |
versicolor | 4.9 | 7.0 | 2.0 | 3.4 | 3.0 | 5.1 | 1.0 | 1.8 | 50 | 99 |
virginica | 4.9 | 7.9 | 2.2 | 3.8 | 4.5 | 6.9 | 1.4 | 2.5 | 100 | 149 |
agg
で dplyr::summarise_at
相当の操作をやる方法
agg
の引数に列名と集約関数のリストをペアにした辞書を渡すと、指定した列に対して指定した関数を適用した集計結果が得られます (dplyr::summarise_at
相当)。
# iris %>% group_by(Species) %>% summarise_at(vars(Sepal.Length, Sepal.Width), funs(min, max))
iris. \
groupby("species"). \
agg({ "sepal_length": [min, max], "sepal_width": [min, max] })
sepal_length | sepal_width | |||
---|---|---|---|---|
min | max | min | max | |
species | ||||
setosa | 4.3 | 5.8 | 2.3 | 4.4 |
versicolor | 4.9 | 7.0 | 2.0 | 3.4 |
virginica | 4.9 | 7.9 | 2.2 | 3.8 |
agg
で dplyr::summarise
相当の操作をやる方法
agg
は既存の列それぞれに対して集約関数を適用するメソッドなので、dplyr::summarise
のように複数の列を組み合わせて集約したり、その結果を新しい列に格納したりすることができません。そういった処理を書きたい場合には、assign
で事前に列を作り、その列に集約直前の状態まで計算した結果を格納し、その列に対して agg
で集約関数を適用します。
# iris %>%
# group_by(Species) %>%
# summarise(
# n = n(),
# n_sepal_over5 = sum(Sepal.Length >= 5),
# n_sepal_over6 = sum(Sepal.Length >= 6),
# n_sepal_over7 = sum(Sepal.Length >= 7)
# )
iris. \
assign(
n = 1,
n_sepal_over5 = lambda df: df.sepal_length.apply(lambda x: 1 if x >= 5 else 0),
n_sepal_over6 = lambda df: df.sepal_length.apply(lambda x: 1 if x >= 6 else 0),
n_sepal_over7 = lambda df: df.sepal_length.apply(lambda x: 1 if x >= 7 else 0)
). \
filter(regex = "species|^n"). \
groupby("species"). \
agg(sum)
n | n_sepal_over5 | n_sepal_over6 | n_sepal_over7 | |
---|---|---|---|---|
species | ||||
setosa | 50 | 30 | 0 | 0 |
versicolor | 50 | 49 | 24 | 1 |
virginica | 50 | 49 | 43 | 12 |
[参考] MultiIndex をフラットにする方法
agg
で集約するとグループ化した列が Index に、列名が 列名×集約関数名の MultiIndex になってしまい、dplyr 勢としては気持ち悪いですね。次のようにすることで tibble 風に変形できます (参考:How to flatten a hierarchical index in columns - Stack Overflow)。
-
pipe(...)
で MultiIndex をフラットにすることができます。-
df.columns.tolist()
で(第一階層の列名, 第二階層の列名)
というタプルのリストを得ます。 -
[e[0] + "_" + e[1] ...]
で各階層の列名を "_" 区切りで結合し、フラットな列名を生成します。 -
pd.DataFrame(df.values, index = df.index, column = ...)
で列名を付け替えます。
-
-
reset_index
で Index を列に戻すことができます。
iris. \
groupby("species"). \
agg([min, max]). \
pipe(lambda df: pd.DataFrame(
df.values,
index = df.index,
columns = [e[0] + "_" + e[1] for e in df.columns.tolist()])
). \
reset_index()
species | sepal_ length_ min |
sepal_ length_ max |
sepal_ width_ min |
sepal_ width_ max |
petal_ length_ min |
petal_ length_ max |
petal_ width_ min |
petal_ width_ max |
id_ min |
id_ max |
|
---|---|---|---|---|---|---|---|---|---|---|---|
0 | setosa | 4.3 | 5.8 | 2.3 | 4.4 | 1.0 | 1.9 | 0.1 | 0.6 | 0.0 | 49.0 |
1 | versicolor | 4.9 | 7.0 | 2.0 | 3.4 | 3.0 | 5.1 | 1.0 | 1.8 | 50.0 | 99.0 |
2 | virginica | 4.9 | 7.9 | 2.2 | 3.8 | 4.5 | 6.9 | 1.4 | 2.5 | 100.0 | 149.0 |
Pandas を使うからには Pandas の文化に従うべきだとは思いますが……
データフレームの結合 dplyr::*_join
(調査中です)
縦持ち横持ち変換 tidyr::gather
tidyr::spread
Pandas で tidyr::gather
や tidyr::spread
をやるためには、冒頭の前置きで触れた Index とまじめに向き合う必要があります。SQL-style を貫き通すことはできません……
stack
で tidyr::gather
相当の操作をやる方法
縦持ちにするには、stack
メソッドを使います。縦持ちしたくない列はすべて set_index
メソッドであらかじめ Index に移しておく必要があります。
# iris %>% gather(key = NA, value = NA, -id, -species)
iris. \
set_index(["id", "species"]). \
stack()
id species
0 setosa sepal_length 5.1
sepal_width 3.5
petal_length 1.4
petal_width 0.2
1 setosa sepal_length 4.9
sepal_width 3.0
petal_length 1.4
petal_width 0.2
...
stack
メソッドで縦持ちにすると MultiIndex の Series オブジェクトが返ってきます。普段 dplyr を使っている身としては SQL-style の DataFrame オブジェクトに直したい気持ちでいっぱいですが、MultiIndex を積極的に使っていけば後続の処理も Series オブジェクトのままで特に困りません。例えば、縦持ちした後に species
別・変数別の最小値と最大値を集計するには次のようにします。
# iris %>%
# gather(key = key, value = value, -Species) %>% # 本当は key = NA, value = NA 相当
# group_by(Species, key) %>%
# summarise_all(funs(min, max))
iris. \
set_index(["id", "species"]). \
stack(). \
groupby(level = [1, 2]). \
agg([min, max])
min | max | ||
---|---|---|---|
species | |||
setosa | sepal_length | 4.3 | 5.8 |
sepal_width | 2.3 | 4.4 | |
petal_length | 1.0 | 1.9 | |
petal_width | 0.1 | 0.6 | |
versicolor | sepal_length | 4.9 | 7.0 |
sepal_width | 2.0 | 3.4 | |
petal_length | 3.0 | 5.1 | |
petal_width | 1.0 | 1.8 | |
virginica | sepal_length | 4.9 | 7.9 |
sepal_width | 2.2 | 3.8 | |
petal_length | 4.5 | 6.9 | |
petal_width | 1.4 | 2.5 |
melt
で tidyr::gather
相当の操作をやる方法
一応、melt
メソッドを使えば tidyr::gather
と同等の操作を実現できます。メソッドの使い勝手も得られる結果もほぼ tidyr::gather
と同じです。ただし、この後で説明しますが tidyr::spread
に対応するメソッドが Pandas には存在しないため、縦持ち横持ちの往復を SQL-style で貫き通すことができません。貫けないのであれば stack で縦持ち変換を行うほうがよいと判断しました。
# iris %>% gather(key = key, value = value, -id, -species)
iris.melt(var_name = "key", value_name = "value", id_vars = ["id", "species"])
id | species | key | value | |
---|---|---|---|---|
0 | 0 | setosa | sepal_length | 5.1 |
1 | 1 | setosa | sepal_length | 4.9 |
2 | 2 | setosa | sepal_length | 4.7 |
3 | 3 | setosa | sepal_length | 4.6 |
4 | 4 | setosa | sepal_length | 5.0 |
unstack
で tidyr::spread
に相当する操作をやる方法
横持ちにするには、unstack
メソッドを使います。stack
メソッドと同じで、横持ちしたくない列はすべて set_index
メソッドであらかじめ Index に移しておく必要があります。
# iris_stacked <-iris %>% gather(key = key, value = value, -id, -species)
iris_stacked = iris.melt(var_name = "key", value_name = "value", id_vars = ["id", "species"])
# iris_stacked %>% spread(key = key, value = value, -id, -species)
iris_stacked. \
set_index(["id", "species", "key"]). \
unstack()
value | |||||
---|---|---|---|---|---|
key | petal_length | petal_width | sepal_length | sepal_width | |
id | species | ||||
0 | setosa | 1.4 | 0.2 | 5.1 | 3.5 |
1 | setosa | 1.4 | 0.2 | 4.9 | 3.0 |
2 | setosa | 1.3 | 0.2 | 4.7 | 3.2 |
3 | setosa | 1.5 | 0.2 | 4.6 | 3.1 |
4 | setosa | 1.4 | 0.2 | 5.0 | 3.6 |
DataFrame オブジェクトに対して unstack
メソッドを呼び出すと複数の列を横持ちにする想定の挙動になるため、得られる結果の列名が MultiIndex になります。横持ちしたい列が一つしかない場合は、set_index
メソッドで横持ち不要な列を Index に移した後に []
で横持ちしたい列だけ取り出して (Series オブジェクトにして) unstack
メソッドを呼ぶことで得られる結果の列名がフラットになります。合わせて reset_index
メソッドで Index を列に移すと、SQL-style にできます。
iris_stacked. \
set_index(["id", "species", "key"])["value"]. \
unstack(). \
reset_index()
key | id | species | petal_length | petal_width | sepal_length | sepal_width |
---|---|---|---|---|---|---|
0 | 0 | setosa | 1.4 | 0.2 | 5.1 | 3.5 |
1 | 1 | setosa | 1.4 | 0.2 | 4.9 | 3.0 |
2 | 2 | setosa | 1.3 | 0.2 | 4.7 | 3.2 |
3 | 3 | setosa | 1.5 | 0.2 | 4.6 | 3.1 |
4 | 4 | setosa | 1.4 | 0.2 | 5.0 | 3.6 |
pivot
で tidyr::spread
相当の操作をやる方法
一応、pivot
メソッドを使えば tidyr::spread
と同等の操作を実現できます。メソッドの使い勝手もほぼ tidyr::spread
と同じです。ただし大きな違いとして、横持ちしない列は一つしか選べない (pivot(index = ...)
の ...
には列を一つしか指定できない) という制約があります。そのため、複合キーを持つ場合や一部の列のみ横持ちさせたい場合に pivot
メソッドは使えません。
# iris_stacked %>% spread(key = key, value = value, -id)
iris_stacked.pivot(index = "id", columns = "key", values = "value")
key | petal_length | petal_width | sepal_length | sepal_width |
---|---|---|---|---|
id | ||||
0 | 1.4 | 0.2 | 5.1 | 3.5 |
1 | 1.4 | 0.2 | 4.9 | 3.0 |
2 | 1.3 | 0.2 | 4.7 | 3.2 |
3 | 1.5 | 0.2 | 4.6 | 3.1 |
4 | 1.4 | 0.2 | 5.0 | 3.6 |
参考
Pandas と dplyr の対比については、以下のページを参考にしました。
- この記事は以下のページから着想を得て作りました。
dplyr と Pandas の考え方の違いにも触れており、わかりやすいです。 - より網羅的な対比を知りたい方は、以下のページが参考になると思います。
各メソッドの細かい使い方については、以下のページを参考にしました。
- パイプでやりきる方法
- 行や列の選択方法
-
str
経由で文字列処理する方法 - DataFrame オブジェクトの結合
- 縦持ち横持ち変換